Fonctionnalités avancées de CMake
Une fois que vous maîtrisez les bases de CMake, les fonctionnalités avancées peuvent vous aider à gérer avec plus de flexibilité la configuration de build de projets complexes.
Cet article couvre six grands sujets : modules personnalisés, builds multi-configuration, recherche avancée de packages, étapes de build personnalisées, compilation croisée multiplateforme et configuration des propriétés de cible.
Aperçu général du contenu :
| Sujet | Capacité principale | Scénario d'application typique |
|---|---|---|
| Modules et scripts personnalisés | Encapsuler la logique CMake réutilisable | Options de compilateur unifiées, logique de recherche personnalisée, règles de build partagées par l'équipe |
| Configurations de build et cibles | Gérer la multi-configuration et le multi-cible | Basculement Debug/Release, génération de plusieurs artefacts de sortie |
| Recherche et configuration avancées | Localiser de manière flexible les dépendances externes | Trouver des composants spécifiques, transmettre des informations de compilation via des fichiers de configuration |
| Étapes de compilation personnalisées | Étendre le processus de compilation | Génération de code, traitement des ressources, automatisation après compilation |
| Multi-plateforme et compilation croisée | Compilations multi-plateformes | Développement embarqué, compilation pour différents systèmes d'exploitation |
| Propriétés et configuration des cibles | Contrôle fin de la compilation et de l'édition de liens | Définir les niveaux d'avertissement et les chemins de liaison pour des cibles spécifiques |
Modules et scripts CMake personnalisés
Lorsque plusieurs fichiers CMakeLists.txt doivent répéter la même logique, encapsuler cette logique dans des modules personnalisés peut grandement réduire la redondance du code.
L'essence des modules et scripts personnalisés est d'extraire les fonctions, macros et configurations CMake couramment utilisées dans des fichiers séparés afin de les réutiliser dans plusieurs projets.
Modules CMake personnalisés
Un module personnalisé est un fichier d'extension.cmake, qui définit des fonctions et macros CMake pouvant être chargées via include().
Étapes de création :
| Étape | Action | Description |
|---|---|---|
| 1 | Créezcmake/Répertoire |
Créez-le sous le répertoire racine du projet afin de stocker les modules personnalisés de manière centralisée. |
| 2 | Créer le fichier de module | Par exemplecmake/MyModule.cmakeécrire des fonctions personnalisées |
| 3 | Étendre le chemin de recherche des modules | Enregistrer ce répertoire via CMAKE_MODULE_PATH dans CMakeLists.txt |
| 4 | Charger le module | Utiliser include() pour importer le module et appeler ses fonctions |
Exemple de module personnalisé — MyModule.cmake :
Exemple
# Module personnalisé : fournit des fonctions utilitaires communes au projet
# Fonction : définir uniformément les options d'avertissement du compilateur pour toutes les cibles
# Paramètre ARG_TARGET : nom de la cible pour laquelle les avertissements doivent être définis
function(example_set_warnings ARG_TARGET)
# Définir différents indicateurs d'avertissement en fonction du type de compilateur
if(MSVC)
# Compilateur MSVC : activer le niveau d'avertissement /W4
target_compile_options(${ARG_TARGET} PRIVATE /W4)
else()
# Compilateur GCC / Clang : activer les avertissements courants
target_compile_options(${ARG_TARGET} PRIVATE -Wall -Wextra -Wpedantic)
endif()
endfunction()
# Fonction : afficher les informations de base de construction pour une cible
function(example_print_target_info ARG_TARGET)
message(STATUS "Nom de la cible : ${ARG_TARGET}")
message(STATUS "Liste des fichiers sources : $<TARGET_PROPERTY:${ARG_TARGET},SOURCES>")
endfunction()
Charger et utiliser le module dans CMakeLists.txt :
Exemple
cmake_minimum_required(VERSION 3.10)
project(MyProject CXX)
# Ajouter le répertoire cmake/ au chemin de recherche des modules
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
# Charger le module personnalisé
include(MyModule)
# Ajouter une cible exécutable
add_executable(MyApp main.cpp)
# Appeler la fonction personnalisée dans le module pour définir les options d'avertissement pour MyApp
example_set_warnings(MyApp)
# Appeler la fonction personnalisée dans le module pour afficher les informations de la cible
example_print_target_info(MyApp)
CMAKE_MODULE_PATHest la liste des chemins de recherche utilisée par CMake lors de la recherche de fichiers de module. CMake recherche d'abord le chemin de module intégré, puis, s'il ne le trouve pas, recherche les chemins spécifiés par CMAKE_MODULE_PATH.
Utilisation de scripts CMake personnalisés
Les scripts personnalisés sont similaires aux modules, mais sont généralement utilisés pour effectuer des opérations de configuration plutôt que pour définir des fonctions réutilisables.
Les scripts peuvent être chargés directement avec include(), et CMake exécute chaque commande qu'ils contiennent dans l'ordre.
Créer le fichier de script config.cmake :
Exemple
# Script personnalisé : gérer de manière centralisée les options de configuration du projet
# Définir le type de compilation par défaut sur Release
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Type de compilation" FORCE)
message(STATUS "Type de compilation non spécifié, utilisation de Release par défaut")
endif()
# Définir différentes options de compilation en fonction du type de compilation
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Activer le mode Debug : désactiver les optimisations, activer les symboles de débogage")
else()
message(STATUS "Activer le mode Release : activer l'optimisation O2")
endif()
Invoquez le script dans CMakeLists.txt :
# 使用绝对路径加载脚本
include(${CMAKE_SOURCE_DIR}/config.cmake)
Configurations de génération et cibles
CMake prend en charge la gestion de plusieurs configurations de génération (telles que Debug et Release) dans le même projet et la définition de plusieurs cibles de génération.
Générateurs multi-configuration
Différents générateurs CMake gèrent les configurations différemment.
Tels que Visual Studio et Xcode, les générateurs multi-configurationpermettent de basculer entre Debug/Release dans le même répertoire de génération, tandis que Unix Makefiles, etc.Les générateurs mono-configurationdoivent déterminer le type de génération au moment de la configuration.
| Type de générateur | Générateurs typiques | Méthode de spécification de la configuration |
|---|---|---|
| Mono-configuration | Unix Makefiles、Ninja | Au moment de la configuration via-DCMAKE_BUILD_TYPE=Releasespécifier |
| Multi-configuration | Visual Studio, Xcode | Lors de la compilation via--config Releaseswitch |
Définir la configuration par défaut dans CMakeLists.txt :
# 仅对单配置生成器生效,多配置生成器会忽略此变量 set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type")
CMAKE_BUILD_TYPE n'est effectif que pour les générateurs à configuration unique. Si vous utilisez Visual Studio, veuillez changer de configuration via cmake --build . --config Release ou dans l'IDE.
Cibles de compilation
Plusieurs cibles de compilation peuvent être définies dans un projet CMake, et chaque cible configure indépendamment les options de compilation et les dépendances de liaison.
Cela vous permet de produire plusieurs exécutables ou plusieurs bibliothèques en une seule compilation.
Exemple
add_executable(MyApp src/main.cpp)
add_executable(MyTool src/tool.cpp)
# Définir différentes macros de compilation pour différentes cibles afin de contrôler la compilation conditionnelle
set_target_properties(MyApp PROPERTIES COMPILE_DEFINITIONS "APP_MODE")
set_target_properties(MyTool PROPERTIES COMPILE_DEFINITIONS "TOOL_MODE")
# Lier différentes bibliothèques pour différentes cibles
target_link_libraries(MyApp PRIVATE MyLib)
target_link_libraries(MyTool PRIVATE MyLib CLI11::CLI11)
Recherche et configuration avancées
En plus de l'utilisation de base de find_package, CMake prend également en charge la spécification de composants, le contrôle précis des versions et la transmission d'informations de build via des modèles de fichiers de configuration.
Utilisation avancée de find_package
COMPONENTSLe mot-clé permet de rechercher uniquement des sous-modules spécifiques d'une bibliothèque plutôt que le paquet entier.
Ceci est très important pour les grandes bibliothèques comme Boost—l'importation à la demande évite de lier des composants inutiles.
Exemple
# filesystem dépend en interne de system, donc les deux doivent être déclarés
find_package(Boost REQUIRED COMPONENTS filesystem system)
# Vérifier si les composants requis ont été trouvés
if(Boost_FOUND)
message(STATUS "Version de Boost : ${Boost_VERSION}")
message(STATUS "Répertoires d'inclusion de Boost : ${Boost_INCLUDE_DIRS}")
# Lier chaque composant requis séparément
target_link_libraries(MyApp PRIVATE
Boost::filesystem
Boost::system
)
endif()
Spécifier les chemins de recherche (pour les emplacements d'installation non standard) :
# 方式一:通过变量指定根目录 cmake .. -DBOOST_ROOT=/path/to/custom/boost # 方式二:在 CMakeLists.txt 中预先设置 set(BOOST_ROOT "/path/to/custom/boost") find_package(Boost REQUIRED COMPONENTS filesystem)
Fichiers de configuration et options de construction
configure_file()La directive peut injecter les valeurs des variables CMake dans les fichiers de code, ce qui permet de transmettre des informations de configuration au moment de la compilation.
Ceci est très utile lorsque vous devez intégrer dans le programme des informations telles que les numéros de version, l'heure de compilation, etc.
Créez le fichier modèle de configuration config.h.in :
Exemple
// Fichier modèle de configuration CMake, génère config.h via configure_file()
// Numéro de version du projet (remplacé par la variable CMake @PROJECT_VERSION@)
#define EXAMPLE_VERSION "@PROJECT_VERSION@"
// Type de compilation (Debug ou Release)
#define EXAMPLE_BUILD_TYPE "@CMAKE_BUILD_TYPE@"
// Horodatage de compilation
#define EXAMPLE_BUILD_TIMESTAMP "@BUILD_TIMESTAMP@"
Configurez et générez le fichier d'en-tête dans CMakeLists.txt :
Exemple
string(TIMESTAMP BUILD_TIMESTAMP "%Y-%m-%d %H:%M:%S")
# Remplacer @variables@ dans config.h.in par les valeurs des variables CMake
# Écrire dans config.h dans le répertoire de build
configure_file(config.h.in config.h)
# Ajouter le répertoire de build au chemin de recherche des fichiers d'en-tête afin que les fichiers source puissent trouver config.h
include_directories(${CMAKE_BINARY_DIR})
Utilisez la configuration générée dans les fichiers source :
// 在 main.cpp 中包含生成的配置文件 #include "config.h" std::cout << "EXAMPLE Version: " << EXAMPLE_VERSION << std::endl; std::cout << "Build Type: " << EXAMPLE_BUILD_TYPE << std::endl;
Génération d'étapes de build personnalisées
Outre la compilation et l'édition de liens standard, CMake vous permet d'insérer des opérations personnalisées dans le processus de build, telles que la génération de code, la copie de ressources et les notifications après build.
Commandes personnalisées
add_custom_command()Utilisé pour définir une commande personnalisée qui est exécutée lorsqu'un fichier doit être mis à jour.
Le concept central de cette commande estpiloté par les dépendances— la commande ne s'exécute que lorsque le fichier OUTPUT n'existe pas ou que le fichier DEPENDS a été mis à jour.
Exemple
# Exécuter uniquement lorsque generated_file.cpp n'existe pas ou que input_template.txt a été mis à jour
add_custom_command(
# Fichier de sortie (CMake vérifie ce fichier pour décider s'il faut réexécuter la commande)
OUTPUT ${CMAKE_BINARY_DIR}/generated_file.cpp
# Commande à exécuter (ici cmake -E est utilisé pour effectuer l'opération intégrée de génération de fichier)
COMMAND ${CMAKE_COMMAND}
-E echo "// Fichier source généré automatiquement" > ${CMAKE_BINARY_DIR}/generated_file.cpp
# Fichier de dépendance (la commande se réexécute lorsque ce fichier change)
DEPENDS ${CMAKE_SOURCE_DIR}/input_template.txt
# Message affiché lors de l'exécution
COMMENT "Génération du fichier source à partir du modèle..."
)
Commande de déclenchement de l'étape de build — s'exécute après la fin de l'édition de liens :
Exemple
add_custom_command(
TARGET MyApp POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:MyApp>
${CMAKE_SOURCE_DIR}/deploy/
COMMENT "Copier MyApp dans le répertoire de déploiement"
)
Cibles personnalisées
add_custom_target()Définit une cible de construction qui ne produit pas de fichier de sortie, généralement utilisée pour regrouper plusieurs commandes personnalisées.
Sauf si le mot-clé ALL est utilisé, les cibles personnalisées ne s'exécuteront pas dans la build par défaut ; vous devez spécifier explicitement le nom de la cible.
Exemple
# ALL signifie qu'il s'exécutera automatiquement pendant le build par défaut (make)
add_custom_target(generate_code ALL
# Dépend du fichier OUTPUT défini précédemment
DEPENDS ${CMAKE_BINARY_DIR}/generated_file.cpp
)
# Cible personnalisée : effectuer des tâches auxiliaires (ne s'exécutera pas pendant le build par défaut)
add_custom_target(deploy
COMMAND ${CMAKE_COMMAND} -E echo "Déployer vers le serveur..."
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_BINARY_DIR}/bin
/opt/example/deploy/
COMMENT "Exécuter l'opération de déploiement"
)
# Déclencher cette cible séparément pendant la construction
# $ cmake --build . --target deploy
add_custom_command et add_custom_target diffèrent : le premier définit une règle pour « comment générer des fichiers » et est déclenché par les dépendances ; le second définit une cible d'action pour « quoi faire » et doit être invoqué explicitement ou s'exécute automatiquement lorsque ALL est utilisé.
Multiplateforme et compilation croisée
L'un des principaux avantages de CMake est la prise en charge multiplateforme — le même CMakeLists.txt peut générer les fichiers de build correspondants sur différents systèmes d'exploitation et architectures.
Compilations multiplateformes
CMake détecte automatiquement le compilateur et l'environnement système de la plateforme actuelle et génère les fichiers de build correspondants.
Vous pouvez également spécifier explicitement les informations sur la plateforme cible via des variables.
| Variable | Description | Valeur d'exemple |
|---|---|---|
CMAKE_SYSTEM_NAME |
Système d'exploitation cible | Linux, Windows, Darwin, Android |
CMAKE_SYSTEM_PROCESSOR |
Architecture du processeur cible | x86_64, arm, aarch64 |
CMAKE_C_COMPILER |
Chemin du compilateur C | /usr/bin/arm-linux-gnueabihf-gcc |
CMAKE_CXX_COMPILER |
Chemin du compilateur C++ | /usr/bin/arm-linux-gnueabihf-g++ |
Compilation croisée
La compilation croisée consiste à compiler des programmes qui s'exécutent sur une autre architecture (telle qu'ARM) sur la plateforme actuelle (telle que x86_64 Linux).
CMake utiliseun fichier de chaîne d'outils (Toolchain File)pour spécifier le compilateur, les informations système et la configuration de l'éditeur de liens requis pour la compilation croisée.
Créez un fichier de chaîne d'outils toolchain.cmake :
Exemple
# Fichier de configuration de la chaîne d'outils de compilation croisée ARM Linux
# Le système cible est Linux
set(CMAKE_SYSTEM_NAME Linux)
# L'architecture du processeur cible est ARM
set(CMAKE_SYSTEM_PROCESSOR arm)
# Spécifiez le compilateur croisé (requis : ajustez le chemin en fonction de l'emplacement réel de la chaîne d'outils)
set(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabihf-g++)
# Spécifiez le chemin du système de fichiers racine du système cible (facultatif, utilisé pour rechercher les bibliothèques et les fichiers d'en-tête)
set(CMAKE_FIND_ROOT_PATH /path/to/arm-sysroot)
# Lors de la recherche de programmes, rechercher uniquement dans les chemins du système cible (pas dans les chemins de l'hôte)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# Lors de la recherche de bibliothèques, rechercher uniquement dans les chemins du système cible
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
# Lors de la recherche des fichiers d'en-tête, rechercher uniquement dans les chemins du système cible
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
Compiler en cross-compilation à l'aide du fichier de chaîne d'outils :
# 配置时通过 -DCMAKE_TOOLCHAIN_FILE 指定工具链文件 mkdir build_arm && cd build_arm cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake .. # 使用生成的 Makefile 编译(将调用 ARM 交叉编译器) make
Le fichier de chaîne d'outils doit être spécifié lors de la première exécution de cmake. Si CMakeCache.txt existe déjà, vous devez supprimer le répertoire de build et reconfigurer pour changer de chaîne d'outils.
Propriétés et configuration des cibles
CMake permet un contrôle précis des options de compilation, des options de liaison et de diverses propriétés cible par cible, ce qui est la pratique recommandée dans CMake moderne.
Propriétés des cibles
set_target_properties()Utilisé pour définir des propriétés au niveau de la cible, qui affectent le comportement du compilateur et de l'éditeur de liens.
| Propriété | Description | Exemple |
|---|---|---|
COMPILE_OPTIONS |
Définir les options de compilation | -Wall、-O2 |
COMPILE_DEFINITIONS |
Définir des macros de préprocesseur | DEBUG、VERSION=2 |
LINK_FLAGS |
Définir les options de l'éditeur de liens | -L/path/to/lib |
OUTPUT_NAME |
Modifier le nom du fichier de sortie | Le nom de la cible est MyApp, et la sortie est my_app |
Exemple
add_executable(MyApp main.cpp)
# Définir les options de compilation et les options de l'éditeur de liens pour la cible
set_target_properties(MyApp PROPERTIES
COMPILE_OPTIONS "-Wall;-Wextra"
COMPILE_DEFINITIONS "EXAMPLE_DEBUG"
LINK_FLAGS "-L/usr/local/lib"
OUTPUT_NAME "my_app"
)
# La commande set_target_properties ci-dessus est équivalente aux trois commandes suivantes :
# target_compile_options(MyApp PRIVATE -Wall -Wextra)
# target_compile_definitions(MyApp PRIVATE EXAMPLE_DEBUG)
# target_link_options(MyApp PRIVATE -L/usr/local/lib)
Dans le CMake moderne, il est recommandé d'utiliser target_compile_options(), target_compile_definitions() et target_link_options() au lieu de set_target_properties(), car elles prennent en charge le contrôle de visibilité PUBLIC/PRIVATE/INTERFACE et rendent les dépendances plus claires.
Options personnalisées de compilation et d'édition de liens
L'utilisation de la série de commandes target permet un contrôle plus précis de la portée de propagation des options.
Exemple
# PRIVATE : seul MyApp lui-même utilise ces options lors de la compilation
target_compile_options(MyApp PRIVATE -Wall -Wextra -Wpedantic)
# Définir les définitions de préprocesseur pour MyApp
# Équivalent à écrire #define EXAMPLE_VERSION "1.0" dans le fichier source
target_compile_definitions(MyApp PRIVATE EXAMPLE_VERSION="1.0")
# Définir les options de liaison pour MyApp
target_link_options(MyApp PRIVATE -L/usr/local/lib)
# S'il s'agit d'une bibliothèque, utiliser PUBLIC permet aux cibles qui lient cette bibliothèque d'hériter également de ces options
# Par exemple : toute cible utilisant MyLib activera automatiquement C++17
target_compile_features(MyLib PUBLIC cxx_std_17)
Notes
CMAKE_BUILD_TYPE n'est valide que pour les générateurs à configuration unique.Si le projet doit prendre en charge à la fois Makefile et Visual Studio, ne vous fiez pas à la valeur de CMAKE_BUILD_TYPE. Utilisez des expressions de générateur
$<$<CONFIG:Debug>:DEBUG_VALUE>pour basculer dynamiquement en fonction de la configuration réelle.
Préférez la série de commandes target_xxx.Bien que set_target_properties soit puissant, il manque de contrôle de visibilité. Dans les scénarios nécessitant la sémantique PUBLIC/PRIVATE/INTERFACE, il est préférable d'utiliser des commandes dédiées telles que target_compile_options.
Le fichier de chaîne d'outils doit être spécifié lors de la première invocation de cmake.Lors d'une compilation croisée, spécifier à nouveau la chaîne d'outils dans un répertoire contenant un CMakeCache.txt existant peut produire une configuration incomplète. Configurez toujours la compilation croisée dans un répertoire de compilation propre.
Autres extensionsIl est recommandé d'ajouter un préfixe aux noms de fonctions dans les modules personnalisés.Évitez les conflits de noms avec les commandes intégrées de CMake ou d'autres modules. Par exemple, utilisez le nom du projet comme préfixe : myproject_set_warnings().