Bases de CMake
CMake est un outil générateur de système de compilation multiplateforme utilisé pour gérer le processus de compilation de projets C/C++.
En écrivant des fichiers de configuration CMakeLists.txt, vous pouvez définir les règles de build du projet, les dépendances et les options de compilation. CMake génère automatiquement les fichiers de build correspondants à la plateforme (tels que Makefile sous Unix ou une solution Visual Studio sous Windows).
Fichier CMakeLists.txt
CMakeLists.txt est le fichier de configuration principal de CMake. Chaque projet CMake nécessite au moins un fichier CMakeLists.txt.
CMake lit les commandes dans le fichier pour comprendre la structure du projet, la liste des fichiers sources et les exigences de compilation.
Syntaxe de base
CMakeLists.txt se compose d'une série de commandes CMake, chacune au format :Nom de commande (liste d'arguments)。
Voici les commandes les plus couramment utilisées et leurs exemples lors de la construction de projets CMake.
Spécifiez la version minimale de CMake :
Exemple
# Cette commande doit être placée tout en haut de CMakeLists.txt
# Exemple : exiger la version 3.10 ou supérieure de CMake
cmake_minimum_required(VERSION 3.10)
Définir le nom du projet et le langage :
Exemple
# Le paramètre de langage est facultatif. Valeurs courantes : CXX (C++), C (langage C)
# Après l'appel de project(), des variables telles que PROJECT_NAME sont automatiquement définies
project(MyProject CXX)
Ajouter un exécutable :
Exemple
# Compiler les fichiers source spécifiés pour générer un exécutable
add_executable(MyApp main.cpp utils.cpp)
Ajouter une bibliothèque :
Exemple
# STATIC : bibliothèque statique (.a / .lib), intégrée directement dans l'exécutable au moment de la compilation
# SHARED : bibliothèque dynamique (.so / .dll), chargée à l'exécution
# Si le type n'est pas spécifié, il est déterminé par la variable BUILD_SHARED_LIBS
add_library(MyLib STATIC library.cpp)
Lier des bibliothèques à une cible :
Exemple
# Lier les bibliothèques spécifiées à la cible (exécutable ou autres bibliothèques)
# Vous pouvez lier des cibles de bibliothèque de votre propre projet, ou lier des bibliothèques externes
target_link_libraries(MyApp PRIVATE MyLib)
Définir des variables :
Exemple
# Définir une variable normale, référencée plus tard via ${variable_name}
set(CMAKE_CXX_STANDARD 17)
# Définir plusieurs valeurs à la fois (liste)
set(SOURCES main.cpp utils.cpp helper.cpp)
add_executable(MyApp ${SOURCES})
Spécifiez le chemin du fichier d'en-tête pour la cible :
Exemple
# [BEFORE | AFTER]
# [SYSTEM]
# [PUBLIC | PRIVATE | INTERFACE]
# <path>...)
# PUBLIC : La cible actuelle et les cibles qui en dépendent peuvent utiliser ce chemin
# PRIVATE : Seule la cible actuelle peut l'utiliser
# INTERFACE : Seules les autres cibles qui dépendent de la cible actuelle peuvent l'utiliser
target_include_directories(MyApp PRIVATE ${PROJECT_SOURCE_DIR}/include)
Définir les règles d'installation :
Exemple
# [RUNTIME DESTINATION <chemin d'installation de l'exécutable>]
# [LIBRARY DESTINATION <chemin d'installation de la bibliothèque partagée>]
# [ARCHIVE DESTINATION <chemin d'installation de la bibliothèque statique>]
# [INCLUDES DESTINATION <chemin d'installation des fichiers d'en-tête>])
# Définir les emplacements d'installation pour chaque type de fichier lors de l'exécution de make install
install(TARGETS MyApp RUNTIME DESTINATION bin)
Instructions conditionnelles :
Exemple
# Les expressions conditionnelles prises en charge par CMake comprennent : comparaison, opérations logiques, vérifications de variables, etc.
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
# Activer les informations de débogage en mode Debug
message(STATUS "Actuellement en mode de compilation Debug")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
else()
# Activer l'optimisation en mode Release
message(STATUS "Actuellement en mode de compilation Release")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2")
endif()
Commandes personnalisées :
Exemple
# TARGET <target>
# PRE_BUILD | PRE_LINK | POST_BUILD
# COMMAND <command> [arguments...]
# [COMMENT <description>]
# [VERBATIM])
# PRE_BUILD: Exécuter avant la compilation
# PRE_LINK: Exécuter avant l'édition des liens
# POST_BUILD: Exécuter une fois la compilation terminée
add_custom_command(
TARGET MyApp POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Compilation terminée !"
COMMENT "Afficher des informations sur la fin de la compilation."
)
Exemple complet
Voici un exemple complet de CMakeLists.txt qui utilise plusieurs des directives ci-dessus.
Exemple
# 1. Spécifier la version minimale de CMake (doit être placée tout en haut)
cmake_minimum_required(VERSION 3.10)
# 2. Définir le nom, la version et le langage du projet
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
# 3. Définir la norme C++ sur C++17 et imposer son utilisation
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 4. Ajouter une cible de bibliothèque statique
add_library(MyLib STATIC
src/lib/core.cpp # Implémentation de la fonctionnalité principale
src/lib/helper.cpp # Implémentation de la fonction d'assistance
)
# 5. Ajouter une cible exécutable
add_executable(MyApp
src/main.cpp # Entrée du programme
src/utils.cpp # Fonctions utilitaires
)
# 6. Spécifier le chemin des fichiers d'en-tête publics pour la cible de bibliothèque
# PUBLIC signifie que les cibles qui utilisent MyLib peuvent également accéder à ce chemin
target_include_directories(MyLib PUBLIC ${PROJECT_SOURCE_DIR}/include)
# 7. Spécifier le chemin des fichiers d'en-tête privés pour l'exécutable
target_include_directories(MyApp PRIVATE ${PROJECT_SOURCE_DIR}/src)
# 8. Lier MyLib à MyApp
target_link_libraries(MyApp PRIVATE MyLib)
# 9. Définir les règles d'installation
install(TARGETS MyApp RUNTIME DESTINATION bin)
install(TARGETS MyLib ARCHIVE DESTINATION lib)
Les commandes typiques pour construire ce projet sont les suivantes :
# 在项目根目录下创建 build 目录(推荐外部构建) $ mkdir build && cd build # 生成构建文件 $ cmake .. # 编译项目 $ cmake --build . # 安装到系统目录(可选) $ cmake --install .
Variables et cache
CMake utilise des variables pour stocker et transmettre des informations de configuration. Les variables peuvent être définies et utilisées dans CMakeLists.txt.
Les variables sont divisées en deux catégories :variables normalesetvariables de cache, et leurs portées et durées de vie diffèrent.
Variables normales
Les variables normales sont définies dans CMakeLists.txt, et leur portée est le répertoire courant et ses sous-répertoires.
Les variables normales sont recalculées à chaque exécution de CMake et ne sont pas persistées.
Exemple
set(MY_APP_NAME "example-app")
# Définir une variable de liste (séparée par des points-virgules ou des espaces)
set(SOURCE_FILES main.cpp utils.cpp helper.cpp)
# Utilisation des variables : référence via ${variable_name}
message(STATUS "Nom du projet : ${MY_APP_NAME}")
# Utilisation des variables de liste : parcourir chaque fichier source
foreach(FILE ${SOURCE_FILES})
message(STATUS "Fichier source : ${FILE}")
endforeach()
Variables de cache
Les variables de cache sont stockées dans le fichier CMakeCache.txt et conservent leurs valeurs entre plusieurs exécutions de CMake.
Les variables de cache sont souvent utilisées pour permettre aux utilisateurs de personnaliser les paramètres de build lors de l'étape de configuration de CMake, par exemple pour activer/désactiver des fonctionnalités ou spécifier des chemins d'installation.
Les valeurs des variables de cache sont persistantes. Si vous modifiez la valeur par défaut d'une variable de cache dans CMakeLists.txt, vous devez supprimer CMakeCache.txt ou réexécuter cmake pour que la nouvelle valeur par défaut prenne effet.
Exemple
# Types disponibles : STRING, BOOL, PATH, FILEPATH
# Si la variable existe déjà dans CMakeCache.txt, set n'écrasera pas la valeur existante
# Définir une variable de cache STRING (chemin d'installation modifiable par l'utilisateur)
set(MY_INSTALL_PATH "/usr/local" CACHE PATH "Chemin d'installation du programme")
# Définir une variable de cache BOOL (activation/désactivation de fonctionnalité)
set(ENABLE_LOGGING ON CACHE BOOL "Activer ou non la fonctionnalité de journalisation")
# Utiliser les variables de cache comme n'importe quelle autre variable
if(ENABLE_LOGGING)
message(STATUS "Fonctionnalité de journalisation activée")
add_definitions(-DENABLE_LOGGING)
endif()
Les utilisateurs peuvent modifier les variables de cache via des paramètres de ligne de commande-D:
# 在 cmake 命令中通过 -D 修改缓存变量 $ cmake .. -DMY_INSTALL_PATH=/opt/example -DENABLE_LOGGING=OFF
Chemins de recherche des fichiers d'en-tête
Dans CMake, il existe deux façons de spécifier les chemins où le compilateur recherche les fichiers d'en-tête :include_directories()ettarget_include_directories()。
Comprendre la différence entre les deux est très important pour écrire des projets CMake propres et maintenables.
Comparaison de include_directories et target_include_directories
Les deux peuvent ajouter des chemins de recherche de fichiers d'en-tête pour le compilateur, mais ils diffèrent par leur portée et leur niveau de contrôle.
| Fonctionnalité | include_directories() |
target_include_directories() |
|---|---|---|
| Portée | Portée globale : affecte toutes les cibles du répertoire courant et de ses sous-répertoires | S'applique uniquement à la cible spécifiée, n'affecte pas les autres cibles |
| Recommandation | Non recommandé, sauf lors de la maintenance de projets CMake hérités | Recommandé à utiliser en premier, suit les meilleures pratiques CMake modernes |
| Association de cible | Non directement associé à une cible spécifique, peut affecter accidentellement d'autres cibles | Secara eksplisit terikat ke target yang ditentukan, dependensi terlihat jelas sekilas |
| Kemudahan pemeliharaan | Buruk, mudah menyebabkan polusi jalur global, masalah sulit di-debug | Baik, jalur terikat pada target, logika jelas |
| Kontrol visibilitas | Tidak dapat mengontrol cakupan propagasi secara tepat | Dikontrol secara tepat melalui PUBLIC, PRIVATE, INTERFACE |
Arti PUBLIC / PRIVATE / INTERFACE
target_include_directories()Keunggulan utamanya adalah propagasi jalur file header dapat dikontrol secara tepat melalui kata kunci visibilitas.
| Kata kunci | Tersedia untuk target saat ini | Tersedia untuk target lain yang bergantung pada target ini | Scénario typique |
|---|---|---|---|
PRIVATE |
Oui | non | Fichiers d'en-tête internes nécessaires uniquement à sa propre implémentation |
PUBLIC |
Oui | Oui | Fichiers d'en-tête de l'API publique de la bibliothèque, nécessaires à la fois à elle-même et aux consommateurs |
INTERFACE |
non | Oui | Bibliothèque d'en-têtes uniquement |
Exemple
# MyLib elle-même a besoin de include/, et MyApp qui utilise MyLib a aussi besoin de include/
# PUBLIC : tant elle-même que les consommateurs peuvent accéder à ce chemin
target_include_directories(MyLib PUBLIC ${PROJECT_SOURCE_DIR}/include)
# PRIVATE : seule MyApp elle-même peut y accéder (fichiers d'en-tête de fonctions utilitaires internes)
target_include_directories(MyApp PRIVATE ${PROJECT_SOURCE_DIR}/src/internal)
# INTERFACE : MyLib est une bibliothèque d'en-têtes uniquement, elle n'a pas besoin d'être compilée elle-même, elle transmet uniquement le chemin aux consommateurs
add_library(HeaderOnlyLib INTERFACE)
target_include_directories(HeaderOnlyLib INTERFACE ${PROJECT_SOURCE_DIR}/header_only)
Recherche de bibliothèques et de paquets
De nombreux projets dépendent de bibliothèques externes (telles que Boost, OpenCV). CMake fournitfind_package()des commandes pour détecter et configurer automatiquement ces dépendances externes.
Commande find_package
find_package()Il recherche les bibliothèques installées sur le système et définit les variables correspondantes (telles que les chemins des fichiers d'en-tête, les chemins des fichiers de bibliothèque et les noms des cibles de bibliothèque).
Exemple
# REQUIRED signifie abandonner la configuration CMake si non trouvé
find_package(Boost REQUIRED)
# Spécifier la version minimale : exiger une version de Boost non inférieure à 1.70
find_package(Boost 1.70 REQUIRED)
# Spécifier le chemin de recherche : rechercher OpenCV dans un chemin personnalisé
find_package(OpenCV REQUIRED PATHS /path/to/opencv)
Utilisation de la bibliothèque trouvée
find_package()En cas de succès, les éléments suivants sont généralement fournis pour utilisation :
| Contenu fourni | Exemple | Description |
|---|---|---|
| Cibles importées | Boost::Boost |
Approche recommandée du CMake moderne, utilisée directement via target_link_libraries |
| Variables de chemin des fichiers d'en-tête | ${Boost_INCLUDE_DIRS} |
Utilisation héritée, utilisée avec include_directories |
| Variables de chemin des fichiers de bibliothèque | ${Boost_LIBRARY_DIRS} |
Utilisation héritée, utilisée avec link_directories |
Il est fortement recommandé d'utiliser la méthode descibles importées(par exemple, Boost::Boost) plutôt que d'assembler manuellement les chemins d'en-tête et de bibliothèque. Les cibles importées gèrent automatiquement toutes les options de compilation et d'édition de liens, ce qui réduit les erreurs de configuration.
Exemple d'utilisation d'une bibliothèque tierce
Voici un exemple complet montrant comment introduire et utiliser la bibliothèque Boost dans un projet CMake.
Exemple
cmake_minimum_required(VERSION 3.10)
project(MyBoostProject CXX)
# Définir la norme C++
set(CMAKE_CXX_STANDARD 17)
# Trouver la bibliothèque Boost ; quitter avec une erreur si elle n'est pas trouvée
find_package(Boost REQUIRED)
# Ajouter un exécutable
add_executable(MyApp main.cpp)
# Lier Boost en utilisant la cible importée
# Boost::Boost définit automatiquement les chemins des fichiers d'en-tête et les chemins des fichiers de bibliothèque
target_link_libraries(MyApp PRIVATE Boost::Boost)
Notes
Préférez la famille de commandes target_xxx.include_directories() et link_directories() sont des commandes globales qui causent facilement une pollution des chemins. Dans les grands projets, utilisez toujours target_include_directories() et target_link_libraries() ; elles rendent les dépendances plus claires et plus faciles à maintenir.
Spécifiez toujours la norme C++.Si CMAKE_CXX_STANDARD n'est pas défini, le compilateur utilisera la norme par défaut (généralement C++98 ou C++14, selon la version du compilateur). Il est recommandé de définir également CMAKE_CXX_STANDARD_REQUIRED sur ON, afin que la compilation échoue immédiatement si le compilateur ne prend pas en charge la norme spécifiée.
Comprenez la différence entre PUBLIC / PRIVATE / INTERFACE.Une mauvaise utilisation de ces mots-clés est le piège le plus courant pour les débutants en CMake. Moyen mnémotechnique simple : nécessaire à la fois pour soi-même et pour les consommateurs → PUBLIC ; nécessaire uniquement pour soi-même → PRIVATE ; nécessaire uniquement pour les consommateurs (par ex. bibliothèques header-only) → INTERFACE.
Autres extensionsLes valeurs par défaut des variables de cache ne sont pas mises à jour automatiquement.Après avoir modifié la valeur par défaut de set(... CACHE ...) dans CMakeLists.txt, le CMakeCache.txt existant ne sera pas écrasé. Vous devez supprimer le CMakeCache.txt dans le répertoire de build, ou tout le répertoire de build, puis relancer cmake.