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:

CMake 构建流程


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

cmake_minimum_required(VERSION 3.10)    # Mindestversion von CMake angeben
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

# Erstelle das statische Bibliotheksziel MyLib, bestehend aus zwei Quelldateien
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

# Füge den folgenden Inhalt zu src/CMakeLists.txt hinzu

# 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

# Fügen Sie Folgendes zu src/CMakeLists.txt hinzu

# 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

# Dateipfad: MyProject/toolchain.cmake
# 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