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

# Syntaxe : cmake_minimum_required(VERSION <version>)
# 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

# Syntaxe : project(<project-name> [<language>...])
# 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

# Syntaxe : add_executable(<target-name> <source-files>...)
# 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

# Syntaxe : add_library(<target-name> [STATIC | SHARED | MODULE] <source-files>...)
# 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

# Syntaxe : target_link_libraries(<target> <libraries>...)
# 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

# Syntaxe : set(<variable-name> <value>...)
# 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

# Syntaxe : target_include_directories(<target>
# [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

# Syntaxe : install(TARGETS <target>...
# [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

# Syntaxe : if(<condition>) ... elseif(<condition>) ... else() ... endif()
# 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

# Syntaxe : add_custom_command(
# 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

# Chemin du fichier : racine du projet/CMakeLists.txt

# 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

# Définir une variable de chaîne
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

# Syntaxe : set(<variable_name> <default_value> CACHE <type> <description>)
# 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

# Scénario : les fichiers d'en-tête de MyLib se trouvent dans le répertoire include/
# 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

# Utilisation de base : trouver la bibliothèque Boost (sans restriction de version)
# 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

# Chemin du fichier : racine du projet/CMakeLists.txt
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.

Les 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.

Autres extensions