Exercice pratique CMake

Cet article montre comment utiliser CMake pour gérer un projet C++ modérément complexe, couvrant le processus complet de la création du projet à la compilation et à l'exécution.

Le contenu couvre la configuration de base, la liaison de bibliothèques, les tests unitaires, les commandes personnalisées et des fonctionnalités avancées telles que la compilation croisée multiplateforme.


Processus de build CMake

Le diagramme suivant montre le processus complet de build CMake, du code source à l'exécutable final :

CMake 构建流程


Aperçu du projet

Nous allons construire un projet C++ qui contient un programme principal, une bibliothèque statique et des tests unitaires.

La bibliothèque fournit des opérations mathématiques (addition, soustraction, multiplication). Le programme principal appelle ces fonctions, et le code de test vérifie leur exactitude.

Structure des répertoires du projet

MyProject/
├── CMakeLists.txt          # 根构建配置(项目定义、全局设置)
├── src/
│   ├── main.cpp            # 主程序入口
│   ├── CMakeLists.txt      # src 子目录构建配置(库 + 可执行文件)
│   ├── lib/
│   │   ├── module1.cpp     # 数学模块一:加法、减法
│   │   └── module2.cpp     # 数学模块二:乘法
│   └── include/
│       └── mylib.h         # 公共头文件(函数声明)
└── tests/
    ├── test_main.cpp       # 单元测试用例
    └── CMakeLists.txt      # 测试子目录构建配置

Compilation hors sourceC'est la bonne pratique de CMake : tous les artefacts de build (Makefiles, fichiers objets, exécutables) sont placés dans un répertoire de build séparé, ce qui garde le répertoire source propre.


Création du fichier CMakeLists.txt

CMakeLists.txt est le fichier de configuration central de CMake. Il utilise une structure hiérarchique, avec un fichier dans le répertoire racine et un dans chaque sous-répertoire.

CMakeLists.txt du répertoire racine

La configuration racine définit les informations au niveau du projet et gère les sous-répertoires via add_subdirectory.

Exemple

cmake_minimum_required(VERSION 3.10)    # Spécifier la version minimale requise de CMake
project(MyProject VERSION 1.0)          # Définir le nom et le numéro de version du projet

# Définir la norme C++ sur C++11 et la rendre obligatoire
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Ajouter un chemin de recherche global pour les fichiers d'en-tête (PROJECT_SOURCE_DIR est le répertoire racine du projet)
include_directories(${PROJECT_SOURCE_DIR}/src/include)

# Ajouter des sous-répertoires ; CMake traitera leurs fichiers CMakeLists.txt dans l'ordre
add_subdirectory(src)      # Traiter la bibliothèque et l'exécutable
add_subdirectory(tests)    # Traiter les tests

CMakeLists.txt du répertoire src

Ce fichier définit la cible de bibliothèque (MyLib) et la cible exécutable (MyExecutable), et établit la relation de dépendance.

Exemple

# Créer la cible de bibliothèque statique MyLib, composée de deux fichiers source
add_library(MyLib STATIC
    lib/module1.cpp
    lib/module2.cpp
)

# Spécifier le chemin d'accès public des fichiers d'en-tête pour MyLib (PUBLIC signifie que les consommateurs hériteront aussi de ce chemin)
target_include_directories(MyLib PUBLIC ${CMAKE_SOURCE_DIR}/src/include)

# Créer la cible exécutable MyExecutable
add_executable(MyExecutable main.cpp)

# Lier MyLib à l'exécutable (PRIVATE signifie que seul MyExecutable en a besoin)
target_link_libraries(MyExecutable PRIVATE MyLib)

La différence entre PRIVATE et PUBLIC :PRIVATEIndique que la dépendance n'est requise que par la cible actuelle,PUBLICIndique que la dépendance est propagée aux autres cibles qui sont liées à la cible actuelle.

Répertoire tests CMakeLists.txt

La configuration des tests utilise le framework Google Test ; commencez par rechercher et inclure le paquet GTest.

Exemple

# Ajouter le contenu suivant à src/CMakeLists.txt

# Afficher un message après la construction de MyExecutable
add_custom_command(
    TARGET MyExecutable
POST_BUILD # Exécuter après la compilation
COMMAND ${CMAKE_COMMAND} -E echo "Compilation de MyExecutable terminée !"
COMMENT "Afficher le message de fin de compilation" # Commentaire affiché dans le journal de compilation
)

Cibles personnalisées (add_custom_target)

Une cible personnalisée est une cible indépendante qui ne produit pas de fichiers de sortie et sert à effectuer des tâches spécifiques.

Exemple

# Ajoutez ce qui suit à src/CMakeLists.txt

# Créez une cible run pour exécuter le programme compilé
add_custom_target(run
    COMMAND ${CMAKE_BINARY_DIR}/src/MyExecutable
    DEPENDS MyExecutable                # Dépend de MyExecutable pour s'assurer qu'il est compilé en premier
    COMMENT "Exécuter MyExecutable"
)

Exécution d'une cible personnalisée :

# 在 build 目录下执行
make run
# ou 
cmake --build . --target run

Différences entre les commandes personnalisées et les cibles personnalisées :add_custom_commandAttachée à une cible, déclenchée à une étape spécifique de la construction de la cible ;add_custom_targetEst une cible indépendante, exécutée uniquement lorsqu'elle est explicitement spécifiée.


Multiplateforme et compilation croisée

L'un des avantages fondamentaux de CMake est la prise en charge multiplateforme, ce qui permet de changer facilement de plateforme et d'architecture cibles.

Spécification de la plateforme cible

La plateforme cible peut être spécifiée à l'aide de la variable CMAKE_SYSTEM_NAME.

# 为 Linux 平台交叉编译
cmake -DCMAKE_SYSTEM_NAME=Linux ..

# 为 Windows 平台交叉编译
cmake -DCMAKE_SYSTEM_NAME=Windows ..

Utilisation des fichiers de chaîne d'outils

Pour les scénarios de compilation croisée complexes, il est recommandé d'utiliser un fichier de chaîne d'outils pour gérer de manière centralisée toutes les configurations liées à la plateforme.

Tout d'abord, créez le fichier de chaîne d'outils toolchain.cmake :

Exemple

# Chemin du fichier : MyProject/toolchain.cmake
# Configuration de la chaîne d'outils de compilation croisée ARM Linux

# Spécifier le système cible
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

# Spécifier le chemin du compilateur croisé (à adapter selon l'environnement réel)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)

# Spécifiez le chemin du système de fichiers racine du système cible (facultatif, utilisé pour trouver les bibliothèques et les fichiers d'en-tête)
set(CMAKE_FIND_ROOT_PATH /path/to/arm/sysroot)

# Rechercher uniquement les programmes dans le sysroot cible (et non dans le système hôte)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# Rechercher uniquement les fichiers de bibliothèque dans le sysroot cible
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
# Rechercher uniquement les fichiers d'en-tête dans le sysroot cible
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

Construction avec le fichier de chaîne d'outils :

# 在 build 目录下执行
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake ..
cmake --build .

Le fichier de chaîne d'outils gère de manière centralisée les configurations liées à la plateforme, telles que les compilateurs, les éditeurs de liens et les chemins des bibliothèques, de sorte que changer de plateforme cible nécessite uniquement de spécifier un fichier de chaîne d'outils différent sans modifier CMakeLists.txt.


Types de build et options courantes

CMake prend en charge plusieurs types de build, contrôlés par la variable CMAKE_BUILD_TYPE.

Type de build Options du compilateur Scénarios applicables
Debug -g (symboles de débogage) Phase de développement et de débogage, lorsque les points d'arrêt et l'inspection des variables sont nécessaires
Release -O2 -DNDEBUG (optimisation + suppression des assertions) Version de production, visant la vitesse d'exécution
RelWithDebInfo -O2 -g (optimisation + symboles de débogage) Version Release nécessitant une analyse des performances ou un débogage optimisé
MinSizeRel -Os (optimisation de la taille) Systèmes embarqués ou scénarios sensibles à la taille

Spécification du type de compilation :

# Debug 构建
cmake -DCMAKE_BUILD_TYPE=Debug ..

# Release 构建
cmake -DCMAKE_BUILD_TYPE=Release ..
Autres extensions