CMake-Grundlagen

CMake ist ein plattformübergreifendes Build-System-Generator-Tool, das verwendet wird, um den Kompilierungsprozess von C/C++-Projekten zu verwalten.

Durch das Schreiben von CMakeLists.txt-Konfigurationsdateien können Sie die Build-Regeln, Abhängigkeiten und Kompilierungsoptionen des Projekts definieren. CMake generiert automatisch die entsprechenden plattformspezifischen Build-Dateien (wie Makefile unter Unix oder Visual Studio-Projektmappe unter Windows).


CMakeLists.txt-Datei

CMakeLists.txt ist die zentrale Konfigurationsdatei von CMake. Jedes CMake-Projekt erfordert mindestens eine CMakeLists.txt-Datei.

CMake liest die Befehle in der Datei, um die Projektstruktur, die Quelldateiliste und die Kompilierungsanforderungen zu verstehen.

Grundlegende Syntax

CMakeLists.txt besteht aus einer Reihe von CMake-Befehlen, jeder im Format:Befehlsname (Argumentliste)。

Im Folgenden finden Sie die am häufigsten verwendeten Befehle und ihre Beispiele beim Erstellen von CMake-Projekten.

Minimale CMake-Version angeben:

Beispiel

# Syntax: cmake_minimum_required(VERSION <version>)
# Dieser Befehl muss ganz oben in CMakeLists.txt platziert werden
# Beispiel: CMake-Version 3.10 oder höher erforderlich
cmake_minimum_required(VERSION 3.10)

Definieren Sie den Projektnamen und die Sprache:

Beispiel

# Syntax: project(<project-name> [<language>...])
# Der Sprachparameter ist optional. Übliche Werte: CXX (C++), C (C-Sprache)
# Nach dem Aufruf von project() werden Variablen wie PROJECT_NAME automatisch gesetzt
project(MyProject CXX)

Eine ausführbare Datei hinzufügen:

Beispiel

# Syntax: add_executable(<target-name> <source-files>...)
# Die angegebenen Quelldateien kompilieren, um eine ausführbare Datei zu erzeugen
add_executable(MyApp main.cpp utils.cpp)

Eine Bibliothek hinzufügen:

Beispiel

# Syntax: add_library(<target-name> [STATIC | SHARED | MODULE] <source-files>...)
# STATIC: statische Bibliothek (.a / .lib), wird zur Kompilierzeit direkt in die ausführbare Datei eingebettet
# SHARED: dynamische Bibliothek (.so / .dll), zur Laufzeit geladen
# Wenn der Typ nicht angegeben wird, wird er durch die Variable BUILD_SHARED_LIBS bestimmt
add_library(MyLib STATIC-Bibliothek.cpp)

Bibliotheken mit einem Target verknüpfen:

Beispiel

# Syntax: target_link_libraries(<target> <libraries>...)
# Verknüpft die angegebenen Bibliotheken mit dem Target (Executable oder andere Bibliotheken)
# Du kannst Bibliotheks-Targets aus deinem eigenen Projekt verknüpfen oder externe Bibliotheken einbinden
target_link_libraries(MyApp PRIVATE MyLib)

Variablen setzen:

Beispiel

# Syntax: set(<variable-name> <value>...)
# Definiert eine normale Variable, die später über ${variable_name} referenziert wird
set(CMAKE_CXX_STANDARD 17)

# Mehrere Werte auf einmal setzen (Liste)
set(SOURCES main.cpp utils.cpp helper.cpp)
add_executable(MyApp ${SOURCES})

Geben Sie den Header-Dateipfad für das Ziel an:

Beispiel

# Syntax: target_include_directories(<target>
# [BEFORE | AFTER]
# [SYSTEM]
# [PUBLIC | PRIVATE | INTERFACE]
# <path>...)
# PUBLIC: Das aktuelle Ziel und Ziele, die davon abhängen, können diesen Pfad verwenden
# PRIVATE: Nur das aktuelle Ziel kann diesen Pfad verwenden
# INTERFACE: Nur andere Ziele, die vom aktuellen Ziel abhängen, können diesen Pfad verwenden
target_include_directories(MyApp PRIVATE ${PROJECT_SOURCE_DIR}/include)

Installationsregeln festlegen:

Beispiel

# Syntax: install(TARGETS <target>...
# [RUNTIME DESTINATION <Installationspfad für ausführbare Dateien>]
# [LIBRARY DESTINATION <Installationspfad für gemeinsam genutzte Bibliotheken>]
# [ARCHIVE DESTINATION <Installationspfad für statische Bibliotheken>]
# [INCLUDES DESTINATION <Installationspfad für Header-Dateien>])
# Definieren Sie die Installationsorte für jeden Dateityp beim Ausführen von make install
install(TARGETS MyApp RUNTIME DESTINATION bin)

Bedingte Anweisungen:

Beispiel

# Syntax: if(<condition>) ... elseif(<condition>) ... else() ... endif()
# Zu den von CMake unterstützten bedingten Ausdrücken gehören: Vergleich, logische Operationen, Variablenprüfungen usw.
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    # Debug-Informationen im Debug-Modus aktivieren
    message(STATUS "Derzeit im Debug-Build-Modus")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
else()
    # Optimierung im Release-Modus aktivieren
    message(STATUS "Derzeit im Release-Build-Modus")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2")
endif()

Benutzerdefinierte Befehle:

Beispiel

# Syntax: add_custom_command(
# TARGET <Ziel>
# PRE_BUILD | PRE_LINK | POST_BUILD
# COMMAND <Befehl> [Argumente...]
# [COMMENT <Beschreibung>]
# [VERBATIM])
# PRE_BUILD: Vor der Kompilierung ausführen
# PRE_LINK: Vor dem Linken ausführen
# POST_BUILD: Nach Abschluss des Builds ausführen
add_custom_command(
    TARGET MyApp POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E echo "Build abgeschlossen!"
    COMMENT "Build-Abschlussinformationen ausgeben."
)

Vollständiges Beispiel

Unten ist ein vollständiges CMakeLists.txt-Beispiel, das mehrere der oben genannten Direktiven verwendet.

Beispiel

# Dateipfad: Projektwurzel/CMakeLists.txt

# 1. Minimale CMake-Version angeben (muss ganz oben stehen)
cmake_minimum_required(VERSION 3.10)

# 2. Projektnamen, Version und Sprache definieren
project(MyProject VERSION 1.0.0 LANGUAGES CXX)

# 3. C++-Standard auf C++17 setzen und seine Verwendung erzwingen
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 4. Ein statisches Bibliotheksziel hinzufügen
add_library(MyLib STATIC
    src/lib/core.cpp      # Implementierung der Kernfunktionalität
    src/lib/helper.cpp    # Implementierung der Hilfsfunktion
)

# 5. Ein ausführbares Ziel hinzufügen
add_executable(MyApp
    src/main.cpp           # Programmeinstieg
    src/utils.cpp          # Hilfsfunktionen
)

# 6. Öffentlichen Header-Dateipfad für das Bibliotheksziel angeben
# PUBLIC bedeutet, dass Ziele, die MyLib verwenden, auch auf diesen Pfad zugreifen können
target_include_directories(MyLib PUBLIC ${PROJECT_SOURCE_DIR}/include)

# 7. Privaten Header-Dateipfad für das ausführbare Ziel angeben
target_include_directories(MyApp PRIVATE ${PROJECT_SOURCE_DIR}/src)

# 8. MyLib mit MyApp verknüpfen
target_link_libraries(MyApp PRIVATE MyLib)

# 9. Installationsregeln festlegen
install(TARGETS MyApp RUNTIME DESTINATION bin)
install(TARGETS MyLib ARCHIVE DESTINATION lib)

Die typischen Befehle zum Erstellen dieses Projekts sind wie folgt:

# 在项目根目录下创建 build 目录(推荐外部构建)
$ mkdir build && cd build

# 生成构建文件
$ cmake ..

# 编译项目
$ cmake --build .

# 安装到系统目录(可选)
$ cmake --install .

Variablen und Cache

CMake verwendet Variablen, um Konfigurationsinformationen zu speichern und weiterzugeben. Variablen können in CMakeLists.txt definiert und verwendet werden.

Variablen werden in zwei Kategorien unterteilt:Normale VariablenundCache-Variablen, und ihre Gültigkeitsbereiche und Lebensdauern unterscheiden sich.

Normale Variablen

Normale Variablen werden in CMakeLists.txt definiert, und ihr Gültigkeitsbereich ist das aktuelle Verzeichnis und seine Unterverzeichnisse.

Normale Variablen werden bei jedem CMake-Lauf neu berechnet und nicht dauerhaft gespeichert.

Beispiel

# Eine String-Variable definieren
set(MY_APP_NAME "example-app")

# eine Listen-Variable (durch Semikolons oder Leerzeichen getrennt)
⟦SOURCE_FILES main.(SOURCE_FILES main.cpp utils.cpp helper.cpp)

# Variablen verwenden: Referenz über ${variable}
message(STATUS "Projektname: ${MY_APP_NAME}")

# Listen-Variablen verwenden: über jede Quelldatei schleifen
foreach(FILE ${SOURCE_FILES})
    message(STATUS "Quelldatei: ${FILE}")
endforeach()

Cache-Variablen

Cache-Variablen werden in der Datei CMakeCache.txt gespeichert und behalten ihre Werte über mehrere CMake-Läufe hinweg.

Cache-Variablen werden häufig verwendet, damit Benutzer Build-Einstellungen während der CMake-Konfigurationsphase anpassen können, z. B. zum Umschalten von Funktionen oder zum Angeben von Installationspfaden.

Cache-Variablenwerte sind persistent. Wenn Sie den Standardwert einer Cache-Variable in CMakeLists.txt ändern, müssen Sie CMakeCache.txt löschen oder cmake erneut ausführen, damit der neue Standardwert wirksam wird.

Beispiel

# Syntax: set(<variable_name> <default_value> CACHE <type> <description>)
# Verfügbare Typen: STRING, BOOL, PATH, FILEPATH
# Wenn die Variable bereits in CMakeCache.txt vorhanden ist, überschreibt set den vorhandenen Wert nicht

# Eine STRING-Cache-Variable definieren (vom Benutzer änderbarer Installationspfad)
set(MY_INSTALL_PATH "/usr/local" CACHE PATH "Installationspfad des Programms")

# Eine BOOL-Cache-Variable definieren (Feature-Schalter)
set(ENABLE_LOGGING ON CACHE BOOL "Ob die Logging-Funktion aktiviert werden soll")

# Cache-Variablen wie jede andere Variable verwenden
if(ENABLE_LOGGING)
    message(STATUS "Logging-Funktion aktiviert")
    add_definitions(-DENABLE_LOGGING)
endif()

Benutzer können Cache-Variablen über Befehlszeilen-Dparameter ändern:

# 在 cmake 命令中通过 -D 修改缓存变量
$ cmake .. -DMY_INSTALL_PATH=/opt/example -DENABLE_LOGGING=OFF

Suchpfade für Header-Dateien

In CMake gibt es zwei Möglichkeiten, die Pfade anzugeben, in denen der Compiler nach Header-Dateien sucht:include_directories()undtarget_include_directories()。

Das Verständnis des Unterschieds zwischen beiden ist sehr wichtig, um saubere, wartbare CMake-Projekte zu schreiben.

Vergleich von include_directories und target_include_directories

Beide können Suchpfade für Header-Dateien für den Compiler hinzufügen, unterscheiden sich jedoch in Geltungsbereich und Grad der Kontrolle.

Merkmal include_directories() target_include_directories()
Geltungsbereich Globaler Geltungsbereich: betrifft alle Ziele im aktuellen Verzeichnis und seinen Unterverzeichnissen Gilt nur für das angegebene Ziel, wirkt sich nicht auf andere Ziele aus
Empfehlung Nicht empfohlen, außer bei der Wartung von Legacy-CMake-Projekten Empfohlen zur vorrangigen Verwendung, folgt modernen CMake-Best-Practices
Zielzuordnung Nicht direkt mit einem bestimmten Ziel verknüpft, kann versehentlich andere Ziele beeinflussen Explizit an das angegebene Ziel gebunden, Abhängigkeiten sind auf einen Blick klar
Wartbarkeit Schlecht, verursacht leicht globale Pfadverschmutzung, Probleme sind schwer zu debuggen Gut, Pfade sind an Ziele gebunden, klare Logik
Sichtbarkeitssteuerung Der Weitergabebereich kann nicht präzise gesteuert werden Präzise gesteuert über PUBLIC, PRIVATE, INTERFACE

Bedeutung von PUBLIC / PRIVATE / INTERFACE

target_include_directories()Der entscheidende Vorteil ist, dass die Weitergabe von Header-Dateipfaden über Sichtbarkeits-Schlüsselwörter präzise gesteuert werden kann.

Schlüsselwort Für das aktuelle Ziel verfügbar Für andere Ziele verfügbar, die von diesem Ziel abhängen Typisches Szenario
PRIVATE Ja nein Interne Header-Dateien, die nur von der eigenen Implementierung benötigt werden
PUBLIC Ja Ja Öffentliche API-Header-Dateien der Bibliothek, die sowohl von ihr selbst als auch von Konsumenten benötigt werden
INTERFACE nein Ja Header-only-Bibliothek

Beispiel

# Szenario: Die Header-Dateien von MyLib befinden sich im Verzeichnis include/
# MyLib selbst benötigt include/, und MyApp, das MyLib verwendet, benötigt ebenfalls include/

# PUBLIC: Sowohl sie selbst als auch Konsumenten können auf diesen Pfad zugreifen
target_include_directories(MyLib PUBLIC ${PROJECT_SOURCE_DIR}/include)

# PRIVATE: Nur MyApp selbst kann darauf zugreifen (interne Header-Dateien für Hilfsfunktionen)
target_include_directories(MyApp PRIVATE ${PROJECT_SOURCE_DIR}/src/internal)

# INTERFACE: MyLib ist eine Header-only-Bibliothek, sie muss nicht selbst kompiliert werden, sondern gibt den Pfad nur an Konsumenten weiter
add_library(HeaderOnlyLib INTERFACE)
target_include_directories(HeaderOnlyLib INTERFACE ${PROJECT_SOURCE_DIR}/header_only)

Bibliotheken und Pakete finden

Viele Projekte hängen von externen Bibliotheken ab (wie Boost, OpenCV). CMake stelltfind_package()Befehle bereit, um diese externen Abhängigkeiten automatisch zu erkennen und zu konfigurieren.

find_package-Befehl

find_package()Es sucht nach auf dem System installierten Bibliotheken und setzt die entsprechenden Variablen (wie Header-Dateipfade, Bibliotheksdateipfade und Bibliothekszielnamen).

Beispiel

# Grundlegende Verwendung: Boost-Bibliothek finden (keine Versionsbeschränkung)
# REQUIRED bedeutet, die CMake-Konfiguration abzubrechen, wenn sie nicht gefunden wird
find_package(Boost REQUIRED)

# Mindestversion angeben: Boost-Version nicht niedriger als 1.70 erfordern
find_package(Boost 1.70 REQUIRED)

# Suchpfad angeben: OpenCV in einem benutzerdefinierten Pfad suchen
find_package(OpenCV REQUIRED PATHS /path/to/opencv)

Verwenden der gefundenen Bibliothek

find_package()Bei Erfolg werden typischerweise die folgenden Elemente zur Verwendung bereitgestellt:

Bereitgestellter Inhalt Beispiel Beschreibung
Importierte Ziele Boost::Boost Empfohlener moderner CMake-Ansatz, direkt über target_link_libraries verwendet
Variablen für Header-Dateipfade ${Boost_INCLUDE_DIRS} Veraltete Verwendung, verwendet mit include_directories
Variablen für Bibliotheksdateipfade ${Boost_LIBRARY_DIRS} Veraltete Verwendung, verwendet mit link_directories

Es wird dringend empfohlen, die Methode derimportierten Ziele(z. B. Boost::Boost) zu verwenden, anstatt Header- und Bibliothekspfade manuell zusammenzusetzen. Importierte Ziele behandeln automatisch alle Kompilierungs- und Linkoptionen und reduzieren Konfigurationsfehler.

Beispiel für die Verwendung einer Drittanbieterbibliothek

Im Folgenden finden Sie ein vollständiges Beispiel, das zeigt, wie die Boost-Bibliothek in einem CMake-Projekt eingebunden und verwendet wird.

Beispiel

# Dateipfad: Projektwurzel/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyBoostProject CXX)

# C++-Standard festlegen
set(CMAKE_CXX_STANDARD 17)

# Boost-Bibliothek finden; mit einem Fehler beenden, wenn sie nicht gefunden wird
find_package(Boost REQUIRED)

# Eine ausführbare Datei hinzufügen
add_executable(MyApp main.cpp)

# Boost über das importierte Ziel linken
# Boost::Boost legt automatisch Pfade zu Header-Dateien und Bibliotheksdateien fest
target_link_libraries(MyApp PRIVATE Boost::Boost)

Hinweise

Bevorzugen Sie die Befehlsfamilie target_xxx.include_directories() und link_directories() sind globale Befehle, die leicht eine Pfadverschmutzung verursachen. In großen Projekten sollten Sie immer target_include_directories() und target_link_libraries() verwenden; sie machen Abhängigkeiten klarer und leichter wartbar.

Geben Sie immer den C++-Standard an.Wenn CMAKE_CXX_STANDARD nicht gesetzt ist, verwendet der Compiler den voreingestellten Standard (normalerweise C++98 oder C++14, abhängig von der Compilerversion). Es wird empfohlen, auch CMAKE_CXX_STANDARD_REQUIRED auf ON zu setzen, damit der Build sofort fehlschlägt, wenn der Compiler den angegebenen Standard nicht unterstützt.

Verstehen Sie den Unterschied zwischen PUBLIC / PRIVATE / INTERFACE.Der falsche Gebrauch dieser Schlüsselwörter ist die häufigste Fehlerquelle für CMake-Anfänger. Einfache Merkregel: Von sowohl dem Ziel selbst als auch von Konsumenten benötigt → PUBLIC; nur vom Ziel selbst benötigt → PRIVATE; nur von Konsumenten benötigt (z. B. Header-only-Bibliotheken) → INTERFACE.

Standardwerte von Cache-Variablen werden nicht automatisch aktualisiert.Nachdem Sie den Standardwert von set(... CACHE ...) in CMakeLists.txt geändert haben, wird die vorhandene CMakeCache.txt nicht überschrieben. Sie müssen die CMakeCache.txt im Build-Verzeichnis oder das gesamte Build-Verzeichnis löschen und cmake erneut ausführen.

Weitere Erweiterungen