CMake-Praxisübung
Dieser Artikel zeigt, wie man CMake verwendet, um ein mäßig komplexes C++-Projekt zu verwalten, und deckt den vollständigen Prozess von der Projekterstellung bis zur Kompilierung und Ausführung ab.
Der Inhalt deckt grundlegende Konfiguration, Bibliotheksverknüpfung, Unit-Tests, benutzerdefinierte Befehle und erweiterte Funktionen wie plattformübergreifende Cross-Kompilierung ab.
CMake-Build-Prozess
Das folgende Diagramm zeigt den vollständigen CMake-Build-Prozess vom Quellcode bis zur finalen ausführbaren Datei:
Projektübersicht
Wir erstellen ein C++-Projekt, das ein Hauptprogramm, eine statische Bibliothek und Unit-Tests enthält.
Die Bibliothek stellt mathematische Operationen bereit (Addition, Subtraktion, Multiplikation). Das Hauptprogramm ruft diese Funktionen auf, und der Testcode überprüft ihre Korrektheit.
Projektverzeichnisstruktur
MyProject/
├── CMakeLists.txt # 根构建配置(项目定义、全局设置)
├── src/
│ ├── main.cpp # 主程序入口
│ ├── CMakeLists.txt # src 子目录构建配置(库 + 可执行文件)
│ ├── lib/
│ │ ├── module1.cpp # 数学模块一:加法、减法
│ │ └── module2.cpp # 数学模块二:乘法
│ └── include/
│ └── mylib.h # 公共头文件(函数声明)
└── tests/
├── test_main.cpp # 单元测试用例
└── CMakeLists.txt # 测试子目录构建配置
Out-of-Source-BuildEs ist die bewährte Vorgehensweise von CMake: Alle Build-Artefakte (Makefiles, Objektdateien, ausführbare Dateien) werden in einem separaten Build-Verzeichnis abgelegt, wodurch das Quellverzeichnis sauber bleibt.
Erstellen der CMakeLists.txt-Datei
CMakeLists.txt ist die zentrale Konfigurationsdatei von CMake. Sie verwendet eine hierarchische Struktur, mit einer im Stammverzeichnis und einer in jedem Unterverzeichnis.
CMakeLists.txt im Stammverzeichnis
Die Konfiguration im Stammverzeichnis definiert projektweite Informationen und verwaltet Unterverzeichnisse über add_subdirectory.
Beispiel
project(MyProject VERSION 1.0) # Projektnamen und Versionsnummer definieren
# Setze den C++-Standard auf C++11 und mache ihn verpflichtend
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Füge einen globalen Suchpfad für Header-Dateien hinzu (PROJECT_SOURCE_DIR ist das Projektstammverzeichnis)
include_directories(${PROJECT_SOURCE_DIR}/src/include)
# Füge Unterverzeichnisse hinzu; CMake verarbeitet deren CMakeLists.txt-Dateien in der Reihenfolge
add_subdirectory(src) # Verarbeite die Bibliothek und das ausführbare Programm
add_subdirectory(tests) # Verarbeite die Tests
src-Verzeichnis CMakeLists.txt
Diese Datei definiert das Bibliotheksziel (MyLib) und das ausführbare Ziel (MyExecutable) und legt die Abhängigkeitsbeziehung fest.
Beispiel
add_library(MyLib STATIC
lib/module1.cpp
lib/module2.cpp
)
# Gib den öffentlichen Header-Datei-Pfad für MyLib an (PUBLIC bedeutet, dass Nutzer diesen Pfad ebenfalls erben)
target_include_directories(MyLib PUBLIC ${CMAKE_SOURCE_DIR}/src/include)
# Erstelle das ausführbare Target MyExecutable
add_executable(MyExecutable main.cpp)
# MyLib mit dem ausführbaren Target verknüpfen (PRIVATE bedeutet, dass nur MyExecutable sie benötigt)
target_link_libraries(MyExecutable PRIVATE MyLib)
Der Unterschied zwischen PRIVATE und PUBLIC:PRIVATEGibt an, dass die Abhängigkeit nur für das aktuelle Target erforderlich ist,PUBLICGibt an, dass die Abhängigkeit an andere Targets weitergegeben wird, die auf das aktuelle Target verlinken.
tests-Verzeichnis CMakeLists.txt
Die Testkonfiguration verwendet das Google-Test-Framework; zuerst das GTest-Paket suchen und einbinden.
Beispiel
# Eine Nachricht ausgeben, nachdem MyExecutable gebaut wurde
add_custom_command(
TARGET MyExecutable
POST_BUILD # Nach dem Build ausführen
COMMAND ${CMAKE_COMMAND} -E echo "MyExecutable-Build abgeschlossen!"
COMMENT "Build-Abschlussmeldung ausgeben" # Im Build-Log angezeigter Kommentar
)
Benutzerdefinierte Targets (add_custom_target)
Ein benutzerdefiniertes Target ist ein unabhängiges Target, das keine Ausgabedateien erzeugt und dazu verwendet wird, bestimmte Aufgaben auszuführen.
Beispiel
# Erstellen Sie ein run-Target, um das kompilierte Programm auszuführen
add_custom_target(run
COMMAND ${CMAKE_BINARY_DIR}/src/MyExecutable
DEPENDS MyExecutable # Hängt von MyExecutable ab, damit es zuerst gebaut wird
COMMENT "MyExecutable ausführen"
)
Ausführen eines benutzerdefinierten Targets:
# 在 build 目录下执行 make run # oder cmake --build . --target run
Unterschiede zwischen benutzerdefinierten Befehlen und benutzerdefinierten Zielen:add_custom_commandAn ein Ziel angehängt, ausgelöst in einer bestimmten Phase des Builds des Ziels;add_custom_targetIst ein unabhängiges Ziel, das nur bei expliziter Angabe ausgeführt wird.
Plattformübergreifend und Cross-Kompilierung
Einer der Kernvorteile von CMake ist die plattformübergreifende Unterstützung, die ein einfaches Wechseln von Zielplattformen und Architekturen ermöglicht.
Angeben der Zielplattform
Die Zielplattform kann mithilfe der Variable CMAKE_SYSTEM_NAME angegeben werden.
# 为 Linux 平台交叉编译 cmake -DCMAKE_SYSTEM_NAME=Linux .. # 为 Windows 平台交叉编译 cmake -DCMAKE_SYSTEM_NAME=Windows ..
Verwenden von Toolchain-Dateien
Für komplexe Cross-Kompilierungsszenarien wird empfohlen, eine Toolchain-Datei zu verwenden, um alle plattformbezogenen Konfigurationen zentral zu verwalten.
Erstellen Sie zuerst die Toolchain-Datei toolchain.cmake:
Beispiel
# Toolchain-Konfiguration für die ARM-Linux-Cross-Kompilierung
# Zielsystem angeben
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
# Cross-Compiler-Pfad angeben (an die tatsächliche Umgebung anpassen)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
# Root-Dateisystempfad des Zielsystems angeben (optional, wird zum Suchen von Bibliotheken und Header-Dateien verwendet)
set(CMAKE_FIND_ROOT_PATH /path/to/arm/sysroot)
# Nur im Ziel-Sysroot nach Programmen suchen (nicht im Hostsystem)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# Nur im Ziel-Sysroot nach Bibliotheksdateien suchen
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
# Nur im Ziel-Sysroot nach Header-Dateien suchen
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
Erstellen mit der Toolchain-Datei:
# 在 build 目录下执行 cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake .. cmake --build .
Die Toolchain-Datei verwaltet zentral plattformbezogene Konfigurationen wie Compiler, Linker und Bibliothekspfade, sodass das Wechseln der Zielplattformen nur die Angabe einer anderen Toolchain-Datei erfordert, ohne CMakeLists.txt zu ändern.
Build-Typen und allgemeine Optionen
CMake unterstützt mehrere Build-Typen, die durch die Variable CMAKE_BUILD_TYPE gesteuert werden.
| Build-Typ | Compiler-Optionen | Anwendbare Szenarien |
|---|---|---|
| Debug | -g (Debug-Symbole) | Entwicklungs- und Debugging-Phase, wenn Breakpoints und Variableninspektion benötigt werden |
| Release | -O2 -DNDEBUG (Optimierung + Entfernen von Assertions) | Produktions-Release mit Fokus auf Laufzeitgeschwindigkeit |
| RelWithDebInfo | -O2 -g (Optimierung + Debug-Symbole) | Release-Version, die Leistungsanalyse oder optimiertes Debugging erfordert |
| MinSizeRel | -Os (Größenoptimierung) | Eingebettete Systeme oder Szenarien, die größenempfindlich sind |
Festlegen des Build-Typs:
# Debug 构建 cmake -DCMAKE_BUILD_TYPE=Debug .. # Release 构建 cmake -DCMAKE_BUILD_TYPE=Release ..Weitere Erweiterungen