CMake - Erweiterte Funktionen
Sobald Sie die Grundlagen von CMake beherrschen, können erweiterte Funktionen Ihnen helfen, die Build-Konfiguration komplexer Projekte flexibler zu verwalten.
Dieser Artikel behandelt sechs Hauptthemen: benutzerdefinierte Module, Multi-Konfigurations-Builds, erweiterte Paketsuche, benutzerdefinierte Build-Schritte, plattformübergreifende Cross-Kompilierung und Konfiguration von Target-Eigenschaften.
Gesamtübersicht des Inhalts:
| Thema | Kernfähigkeit | Typisches Anwendungsszenario |
|---|---|---|
| Benutzerdefinierte Module und Skripte | Wiederverwendbare CMake-Logik kapseln | Einheitliche Compiler-Optionen, benutzerdefinierte Suchlogik, im Team gemeinsam genutzte Build-Regeln |
| Build-Konfigurationen und Targets | Multi-Konfiguration und Multi-Target verwalten | Umschalten zwischen Debug/Release, Erstellen mehrerer Ausgabeartefakte |
| Erweiterte Suche und Konfiguration | Externe Abhängigkeiten flexibel lokalisieren | Auffinden bestimmter Komponenten, Übergeben von Build-Informationen über Konfigurationsdateien |
| Benutzerdefinierte Build-Schritte | Build-Prozess erweitern | Codegenerierung, Ressourcenverarbeitung, Post-Build-Automatisierung |
| Plattformübergreifend und Cross-Compilation | Multiplattform-Builds | Embedded-Entwicklung, Kompilieren für verschiedene Betriebssysteme |
| Target-Eigenschaften und -Konfiguration | Feingranulare Kontrolle über Kompilierung und Linken | Warnstufen und Link-Pfade für bestimmte Targets festlegen |
Benutzerdefinierte CMake-Module und -Skripte
Wenn mehrere CMakeLists.txt-Dateien dieselbe Logik wiederholen müssen, kann das Kapseln dieser Logik in benutzerdefinierte Module die Code-Redundanz stark reduzieren.
Das Wesen benutzerdefinierter Module und Skripte besteht darin, häufig verwendete CMake-Funktionen, Makros und Konfigurationen in separate Dateien auszulagern, um sie in mehreren Projekten wiederverwenden zu können.
Benutzerdefinierte CMake-Module
Ein benutzerdefiniertes Modul ist eine.cmake-Datei mit .cmake als Erweiterung, die CMake-Funktionen und Makros definiert, die über include() geladen werden können.
Erstellungsschritte:
| Schritt | Aktion | Beschreibung |
|---|---|---|
| 1 | Erstellecmake/Verzeichnis |
Erstellen Sie es unter dem Projektstammverzeichnis, um benutzerdefinierte Module zentral zu speichern. |
| 2 | Moduldatei erstellen | Zum Beispielcmake/MyModule.cmakebenutzerdefinierte Funktionen schreiben |
| 3 | Modulsuchpfad erweitern | Registrieren Sie dieses Verzeichnis über CMAKE_MODULE_PATH in CMakeLists.txt |
| 4 | Modul laden | Verwenden Sie include(), um das Modul zu importieren und seine Funktionen aufzurufen |
Beispiel für ein benutzerdefiniertes Modul — MyModule.cmake:
Beispiel
# Benutzerdefiniertes Modul: Stellt Hilfsfunktionen bereit, die im gesamten Projekt verwendet werden
# Funktion: Compiler-Warnoptionen einheitlich für alle Ziele festlegen
# Parameter ARG_TARGET: Name des Ziels, für das Warnungen festgelegt werden müssen
function(example_set_warnings ARG_TARGET)
# Je nach Compilertyp unterschiedliche Warnflags festlegen
if(MSVC)
# MSVC-Compiler: Warnstufe /W4 aktivieren
target_compile_options(${ARG_TARGET} PRIVATE /W4)
else()
# GCC-/Clang-Compiler: übliche Warnungen aktivieren
target_compile_options(${ARG_TARGET} PRIVATE -Wall -Wextra -Wpedantic)
endif()
endfunction()
# Funktion: grundlegende Build-Informationen für ein Ziel ausgeben
function(example_print_target_info ARG_TARGET)
message(STATUS "Zielname: ${ARG_TARGET}")
message(STATUS "Quelldateiliste: $<TARGET_PROPERTY:${ARG_TARGET},SOURCES>")
endfunction()
Laden und verwenden Sie das Modul in CMakeLists.txt:
Beispiel
cmake_minimum_required(VERSION 3.10)
project(MyProject CXX)
# Das Verzeichnis cmake/ zum Modulsuchpfad hinzufügen
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
# Benutzerdefiniertes Modul laden
include(MyModule)
# Ausführbares Ziel hinzufügen
add_executable(MyApp main.cpp)
# Benutzerdefinierte Funktion im Modul aufrufen, um Warnoptionen für MyApp festzulegen
example_set_warnings(MyApp)
# Benutzerdefinierte Funktion im Modul aufrufen, um Zielinformationen auszugeben
example_print_target_info(MyApp)
CMAKE_MODULE_PATHist die Suchpfadliste, die CMake beim Suchen nach Moduldateien verwendet. CMake durchsucht zuerst den integrierten Modulpfad und sucht, falls nicht gefunden, die durch CMAKE_MODULE_PATH angegebenen Pfade.
Benutzerdefinierte CMake-Skripte verwenden
Benutzerdefinierte Skripte ähneln Modulen, werden aber typischerweise verwendet, um Konfigurationsvorgänge auszuführen, anstatt wiederverwendbare Funktionen zu definieren.
Skripte können direkt mit include() geladen werden, und CMake führt jeden darin enthaltenen Befehl der Reihe nach aus.
Skriptdatei config.cmake erstellen:
Beispiel
# Benutzerdefiniertes Skript: Projektkonfigurationsoptionen zentral verwalten
# Standard-Build-Typ auf Release setzen
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build-Typ" FORCE)
message(STATUS "Build-Typ nicht angegeben, standardmäßig wird Release verwendet")
endif()
# Je nach Build-Typ unterschiedliche Kompilierungsoptionen festlegen
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Debug-Modus aktivieren: Optimierungen deaktivieren, Debug-Symbole aktivieren")
else()
message(STATUS "Release-Modus aktivieren: O2-Optimierung aktivieren")
endif()
Rufen Sie das Skript in CMakeLists.txt auf:
# 使用绝对路径加载脚本
include(${CMAKE_SOURCE_DIR}/config.cmake)
Build-Konfigurationen und Ziele
CMake unterstützt die Verwaltung mehrerer Build-Konfigurationen (wie Debug und Release) im selben Projekt und die Definition mehrerer Build-Ziele.
Multi-Konfigurations-Generatoren
Verschiedene CMake-Generatoren behandeln Konfigurationen unterschiedlich.
Zum Beispiel Visual Studio und XcodeMulti-Konfigurations-Generatorenermöglichen das Umschalten zwischen Debug/Release im selben Build-Verzeichnis, während Unix Makefiles usw.Single-Konfigurations-Generatorenden Build-Typ in der Konfigurationsphase bestimmen müssen.
| Generator-Typ | Typische Generatoren | Methode zur Angabe der Konfiguration |
|---|---|---|
| Single-Konfiguration | Unix Makefiles, Ninja | Zur Konfigurationszeit über-DCMAKE_BUILD_TYPE=Releaseangeben |
| Multi-Konfiguration | Visual Studio、Xcode | Zur Build-Zeit über--config Releaseswitch |
Standardkonfiguration in CMakeLists.txt festlegen:
# 仅对单配置生成器生效,多配置生成器会忽略此变量 set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type")
CMAKE_BUILD_TYPE ist nur für Single-Configuration-Generatoren wirksam. Wenn Sie Visual Studio verwenden, wechseln Sie die Konfiguration bitte über cmake --build . --config Release oder in der IDE.
Build-Ziele
In einem CMake-Projekt können mehrere Build-Ziele definiert werden, und jedes Ziel konfiguriert unabhängig Kompilierungsoptionen und Link-Abhängigkeiten.
Dies ermöglicht es Ihnen, mehrere ausführbare Dateien oder mehrere Bibliotheken in einem einzigen Build zu erzeugen.
Beispiel
add_executable(MyApp src/main.cpp)
add_executable(MyTool src/tool.cpp)
# Unterschiedliche Kompilierungsmakros für verschiedene Ziele festlegen, um die bedingte Kompilierung zu steuern
set_target_properties(MyApp PROPERTIES COMPILE_DEFINITIONS "APP_MODE")
set_target_properties(MyTool PROPERTIES COMPILE_DEFINITIONS "TOOL_MODE")
# Unterschiedliche Bibliotheken für verschiedene Ziele verlinken
target_link_libraries(MyApp PRIVATE MyLib)
target_link_libraries(MyTool PRIVATE MyLib CLI11::CLI11)
Erweitertes Finden und Konfigurieren
Neben der grundlegenden Verwendung von find_package unterstützt CMake außerdem das Angeben von Komponenten, die präzise Steuerung von Versionen und die Weitergabe von Build-Informationen über Konfigurationsdateivorlagen.
Erweiterte Verwendung von find_package
COMPONENTSDas Schlüsselwort ermöglicht es, nur bestimmte Untermodule einer Bibliothek statt des gesamten Pakets zu finden.
Dies ist sehr wichtig für große Bibliotheken wie Boost—das bedarfsgerechte Importieren vermeidet das Verlinken nicht benötigter Komponenten.
Beispiel
# filesystem hängt intern von system ab, daher müssen beide deklariert werden
find_package(Boost REQUIRED COMPONENTS filesystem system)
# Prüfen, ob die erforderlichen Komponenten gefunden wurden
if(Boost_FOUND)
message(STATUS "Boost-Version: ${Boost_VERSION}")
message(STATUS "Boost-Include-Verzeichnisse: ${Boost_INCLUDE_DIRS}")
# Jede erforderliche Komponente separat linken
target_link_libraries(MyApp PRIVATE
Boost::filesystem
Boost::system
)
endif()
Suchpfade angeben (für nicht standardmäßige Installationsorte):
# 方式一:通过变量指定根目录 cmake .. -DBOOST_ROOT=/path/to/custom/boost # 方式二:在 CMakeLists.txt 中预先设置 set(BOOST_ROOT "/path/to/custom/boost") find_package(Boost REQUIRED COMPONENTS filesystem)
Konfigurationsdateien und Build-Optionen
configure_file()Die Direktive kann die Werte von CMake-Variablen in Codedateien einfügen, wodurch Konfigurationsinformationen zur Kompilierzeit übergeben werden können.
Dies ist sehr nützlich, wenn Sie Informationen wie Versionsnummern, Build-Zeit usw. in das Programm kompilieren müssen.
Erstellen Sie die Konfigurationsvorlagendatei config.h.in:
Beispiel
// CMake-Konfigurationsvorlagendatei, generiert config.h über configure_file()
// Projektversionsnummer (ersetzt durch CMake-Variable @PROJECT_VERSION@)
#define EXAMPLE_VERSION "@PROJECT_VERSION@"
// Build-Typ (Debug oder Release)
#define EXAMPLE_BUILD_TYPE "@CMAKE_BUILD_TYPE@"
// Build-Zeitstempel
#define EXAMPLE_BUILD_TIMESTAMP "@BUILD_TIMESTAMP@"
Konfigurieren und generieren Sie die Header-Datei in CMakeLists.txt:
Beispiel
string(TIMESTAMP BUILD_TIMESTAMP "%Y-%m-%d %H:%M:%S")
# @variables@ in config.h.in durch die Werte der CMake-Variablen ersetzen
# Ausgabe nach config.h im Build-Verzeichnis
configure_file(config.h.in config.h)
# Das Build-Verzeichnis zum Suchpfad für Headerdateien hinzufügen, damit Quelldateien config.h finden können
include_directories(${CMAKE_BINARY_DIR})
Verwenden Sie die generierte Konfiguration in Quelldateien:
// 在 main.cpp 中包含生成的配置文件 #include "config.h" std::cout << "EXAMPLE Version: " << EXAMPLE_VERSION << std::endl; std::cout << "Build Type: " << EXAMPLE_BUILD_TYPE << std::endl;
Benutzerdefinierte Build-Schritte generieren
Neben der standardmäßigen Kompilierung und Verlinkung ermöglicht CMake das Einfügen benutzerdefinierter Operationen in den Build-Prozess, wie z. B. Codegenerierung, das Kopieren von Ressourcen und Benachrichtigungen nach dem Build.
Benutzerdefinierte Befehle
add_custom_command()Wird verwendet, um einen benutzerdefinierten Befehl zu definieren, der ausgeführt wird, wenn eine Datei aktualisiert werden muss.
Das Kernkonzept dieses Befehls istabhängigkeitsgetrieben— der Befehl wird nur ausgeführt, wenn die OUTPUT-Datei nicht existiert oder die DEPENDS-Datei aktualisiert wurde.
Beispiel
# Nur ausführen, wenn generated_file.cpp nicht existiert oder input_template.txt aktualisiert wurde
add_custom_command(
# Ausgabedatei (CMake prüft diese Datei, um zu entscheiden, ob der Befehl erneut ausgeführt wird)
OUTPUT ${CMAKE_BINARY_DIR}/generated_file.cpp
# Auszuführender Befehl (hier wird cmake -E verwendet, um die integrierte Dateigenerierungsoperation auszuführen)
COMMAND ${CMAKE_COMMAND}
-E echo "// Auto-generated source file" > ${CMAKE_BINARY_DIR}/generated_file.cpp
# Abhängigkeitsdatei (der Befehl wird erneut ausgeführt, wenn sich diese Datei ändert)
DEPENDS ${CMAKE_SOURCE_DIR}/input_template.txt
# Nachricht, die bei der Ausführung angezeigt wird
COMMENT "Quelldatei wird aus Vorlage generiert..."
)
Trigger-Befehl für die Build-Phase — wird nach Abschluss des Linkens ausgeführt:
Beispiel
add_custom_command(
TARGET MyApp POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:MyApp>
${CMAKE_SOURCE_DIR}/deploy/
COMMENT "MyApp in das Deployment-Verzeichnis kopieren"
)
Benutzerdefinierte Targets
add_custom_target()Definiert ein Build-Target, das keine Ausgabedatei erzeugt, und wird normalerweise verwendet, um mehrere benutzerdefinierte Befehle zu gruppieren.
Sofern das Schlüsselwort ALL nicht verwendet wird, werden benutzerdefinierte Targets im Standard-Build nicht ausgeführt; der Target-Name muss explizit angegeben werden.
Beispiel
# ALL bedeutet, dass es während des Standard-Builds (make) automatisch ausgeführt wird
add_custom_target(generate_code ALL
# Hängt von der zuvor definierten OUTPUT-Datei ab
DEPENDS ${CMAKE_BINARY_DIR}/generated_file.cpp
)
# Benutzerdefiniertes Ziel: Hilfsaufgaben ausführen (wird während des Standard-Builds nicht ausgeführt)
add_custom_target(deploy
COMMAND ${CMAKE_COMMAND} -E echo "Auf Server bereitstellen..."
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_BINARY_DIR}/bin
/opt/example/deploy/
COMMENT "Bereitstellungsvorgang ausführen"
)
# Dieses Target während des Builds separat auslösen
# $ cmake --build . --target deploy
add_custom_command und add_custom_target unterscheiden sich: Ersteres definiert eine Regel dafür, "wie Dateien generiert werden", und wird durch Abhängigkeiten ausgelöst; Letzteres definiert ein Aktionsziel für "was zu tun ist" und muss explizit aufgerufen werden oder läuft automatisch, wenn ALL verwendet wird.
Plattformübergreifend und Cross-Compilation
Einer der Kernvorteile von CMake ist die plattformübergreifende Unterstützung — dieselbe CMakeLists.txt kann entsprechende Build-Dateien auf verschiedenen Betriebssystemen und Architekturen generieren.
Plattformübergreifende Builds
CMake erkennt automatisch den Compiler und die Systemumgebung der aktuellen Plattform und generiert passende Build-Dateien.
Sie können Zielplattforminformationen auch explizit über Variablen angeben.
| Variable | Beschreibung | Beispielwert |
|---|---|---|
CMAKE_SYSTEM_NAME |
Zielbetriebssystem | Linux、Windows、Darwin、Android |
CMAKE_SYSTEM_PROCESSOR |
Zielprozessorarchitektur | x86_64、arm、aarch64 |
CMAKE_C_COMPILER |
C-Compiler-Pfad | /usr/bin/arm-linux-gnueabihf-gcc |
CMAKE_CXX_COMPILER |
C++-Compiler-Pfad | /usr/bin/arm-linux-gnueabihf-g++ |
Cross-Kompilierung
Cross-Kompilierung bedeutet, Programme, die auf einer anderen Architektur (wie ARM) laufen, auf der aktuellen Plattform (wie x86_64 Linux) zu kompilieren.
CMake verwendeteine Toolchain-Datei (Toolchain File), um den Compiler, Systeminformationen und die für die Cross-Kompilierung erforderliche Linker-Konfiguration anzugeben.
Erstellen Sie eine Toolchain-Datei toolchain.cmake:
Beispiel
# Konfigurationsdatei für die ARM-Linux-Cross-Kompilierungs-Toolchain
# Zielsystem ist Linux
set(CMAKE_SYSTEM_NAME Linux)
# Zielprozessorarchitektur ist ARM
set(CMAKE_SYSTEM_PROCESSOR arm)
# Cross-Compiler angeben (erforderlich: den Pfad an die tatsächliche Toolchain-Position anpassen)
set(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabihf-g++)
# Root-Dateisystempfad des Zielsystems angeben (optional, wird zum Auffinden von Bibliotheken und Header-Dateien verwendet)
set(CMAKE_FIND_ROOT_PATH /path/to/arm-sysroot)
# Bei der Suche nach Programmen nur in Zielsystempfaden suchen (nicht in Host-Pfaden)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# Bei der Suche nach Bibliotheken nur in Zielsystempfaden suchen
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
# Bei der Suche nach Header-Dateien nur in Pfaden des Zielsystems suchen
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
Cross-Kompilierung mithilfe der Toolchain-Datei:
# 配置时通过 -DCMAKE_TOOLCHAIN_FILE 指定工具链文件 mkdir build_arm && cd build_arm cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake .. # 使用生成的 Makefile 编译(将调用 ARM 交叉编译器) make
Die Toolchain-Datei muss beim ersten Ausführen von cmake angegeben werden. Wenn CMakeCache.txt bereits existiert, müssen Sie das Build-Verzeichnis löschen und neu konfigurieren, um die Toolchain zu wechseln.
Target-Eigenschaften und Konfiguration
CMake ermöglicht die feingranulare Steuerung von Kompilierungsoptionen, Link-Optionen und verschiedenen Eigenschaften auf Target-Basis, was in modernem CMake die empfohlene Vorgehensweise ist.
Target-Eigenschaften
set_target_properties()Dient zum Festlegen von Eigenschaften auf Target-Ebene, die das Verhalten von Compiler und Linker beeinflussen.
| Eigenschaft | Beschreibung | Beispiel |
|---|---|---|
COMPILE_OPTIONS |
Kompilierungsoptionen festlegen | -Wall、-O2 |
COMPILE_DEFINITIONS |
Präprozessor-Makros festlegen | DEBUG、VERSION=2 |
LINK_FLAGS |
Linker-Flags setzen | -L/path/to/lib |
OUTPUT_NAME |
Ausgabedateinamen ändern | Der Zielname ist MyApp, und die Ausgabe ist my_app |
Beispiel
add_executable(MyApp main.cpp)
# Kompilierungsoptionen und Linker-Flags für das Ziel setzen
set_target_properties(MyApp PROPERTIES
COMPILE_OPTIONS "-Wall;-Wextra"
COMPILE_DEFINITIONS "EXAMPLE_DEBUG"
LINK_FLAGS "-L/usr/local/lib"
OUTPUT_NAME "my_app"
)
# Der obige set_target_properties-Befehl entspricht den folgenden drei Befehlen:
# target_compile_options(MyApp PRIVATE -Wall -Wextra)
# target_compile_definitions(MyApp PRIVATE EXAMPLE_DEBUG)
# target_link_options(MyApp PRIVATE -L/usr/local/lib)
In modernem CMake wird empfohlen, target_compile_options(), target_compile_definitions() und target_link_options() anstelle von set_target_properties() zu verwenden, da sie die Sichtbarkeitssteuerung PUBLIC/PRIVATE/INTERFACE unterstützen und Abhängigkeiten klarer machen.
Benutzerdefinierte Kompilierungs- und Link-Optionen
Die Verwendung der target-Befehlsfamilie ermöglicht eine präzisere Steuerung des Weitergabebereichs von Optionen.
Beispiel
# PRIVATE: Nur MyApp selbst verwendet diese Optionen beim Kompilieren
target_compile_options(MyApp PRIVATE -Wall -Wextra -Wpedantic)
# Präprozessor-Definitionen für MyApp festlegen
# Entspricht dem Schreiben von #define EXAMPLE_VERSION "1.0" in der Quelldatei.
target_compile_definitions(MyApp PRIVATE EXAMPLE_VERSION="1.0")
# Link-Optionen für MyApp festlegen
target_link_options(MyApp PRIVATE -L/usr/local/lib)
# Wenn es eine Bibliothek ist, ermöglicht die Verwendung von PUBLIC, dass Ziele, die diese Bibliothek linken, auch diese Optionen erben
# Zum Beispiel: Jedes Ziel, das MyLib verwendet, aktiviert automatisch C++17
target_compile_features(MyLib PUBLIC cxx_std_17)
Hinweise
CMAKE_BUILD_TYPE ist nur für Single-Configuration-Generatoren gültig.Wenn das Projekt sowohl Makefile als auch Visual Studio unterstützen muss, verlassen Sie sich nicht auf den Wert von CMAKE_BUILD_TYPE. Verwenden Sie Generator-Ausdrücke
$<$<CONFIG:Debug>:DEBUG_VALUE>um dynamisch basierend auf der tatsächlichen Konfiguration umzuschalten.
Bevorzugen Sie die target_xxx-Befehlsreihe.Obwohl set_target_properties leistungsfähig ist, fehlt ihm die Sichtbarkeitskontrolle. In Szenarien, die PUBLIC/PRIVATE/INTERFACE-Semantik erfordern, ist es besser, spezielle Befehle wie target_compile_options zu verwenden.
Die Toolchain-Datei muss beim ersten cmake-Aufruf angegeben werden.Beim Cross-Kompilieren kann das erneute Angeben der Toolchain in einem Verzeichnis mit einer vorhandenen CMakeCache.txt zu einer unvollständigen Konfiguration führen. Konfigurieren Sie Cross-Kompilierung immer in einem sauberen Build-Verzeichnis.
Weitere ErweiterungenEs wird empfohlen, Funktionsnamen in benutzerdefinierten Modulen ein Präfix hinzuzufügen.Vermeiden Sie Namenskonflikte mit in CMake integrierten Befehlen oder anderen Modulen. Verwenden Sie beispielsweise den Projektnamen als Präfix: myproject_set_warnings().