Si vous avez déjà tenté de compiler à la main un projet serveur C++ multi-fichiers avec g++, vous savez à quelle vitesse la ligne de commande tourne au cauchemar : des dizaines de fichiers .cpp, des chemins d'inclusion, des bibliothèques externes et un projet qui se recompile entièrement à chaque modification. C'est précisément là qu'intervient la question CMake, c'est quoi. CMake est un générateur de système de build multiplateforme qui vous permet de décrire comment votre projet se compile dans un seul fichier de recette. Dans cet article, nous allons configurer CMake de zéro à partir d'un serveur de jeu/chat comme exemple.
CMake, c'est quoi et pourquoi en a-t-on besoin ?
CMake n'est pas un compilateur en soi. Il lit le fichier CMakeLists.txt que vous écrivez et génère les vrais fichiers de build adaptés à votre système : généralement des Makefile sous Linux, des projets Visual Studio sous Windows, ou des fichiers Ninja si vous préférez. Autrement dit, CMake est un « méta système de build » : vous écrivez une seule recette, et elle fonctionne sur toutes les plateformes.
- Portabilité : le même
CMakeLists.txttourne sur votre VPS Linux et sur la machine Windows où vous développez. - Gestion des dépendances : CMake suit quel fichier dépend de quel autre et ne recompile que les fichiers modifiés.
- Liaison des bibliothèques : vous intégrez des bibliothèques comme pthread, OpenSSL ou Boost en une seule ligne.
Les projets serveur ont exactement ces trois besoins : beaucoup de fichiers, des bibliothèques externes et la capacité de compiler dans différents environnements. C'est pourquoi CMake est pratiquement la norme pour tout serveur C++ sérieux.
Exemple d'arborescence du projet
Supposons que nous écrivions un simple serveur TCP. Répartissons les fichiers dans des dossiers logiques ; cela compte autant pour la lisibilité que pour la configuration CMake.
server/
├── CMakeLists.txt
├── include/
│ └── server/
│ ├── Server.hpp
│ └── Connection.hpp
├── src/
│ ├── main.cpp
│ ├── Server.cpp
│ └── Connection.cpp
└── build/ (la sortie de compilation va ici)
Garder les en-têtes sous include/ et les sources sous src/ est une organisation courante. Le dossier build/ contient les fichiers temporaires générés par CMake et on l'ajoute généralement au .gitignore.
Votre premier CMakeLists.txt
Écrivons maintenant un CMakeLists.txt à la racine. Je vais parcourir chaque ligne et son rôle.
cmake_minimum_required(VERSION 3.16)
project(GameServer VERSION 1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(server
src/main.cpp
src/Server.cpp
src/Connection.cpp
)
target_include_directories(server PRIVATE include)
Ligne par ligne :
cmake_minimum_required: déclare la version minimale de CMake que vous souhaitez prendre en charge. 3.16 est une base raisonnable pour les fonctionnalités modernes.project(...): définit le nom, la version et le langage du projet (CXX= C++).set(CMAKE_CXX_STANDARD 17): utilise le standard C++17. AvecSTANDARD_REQUIRED ON, la compilation échoue si ce standard est indisponible au lieu de se rabattre silencieusement.add_executable: crée une cible (target) exécutable nomméeserveret indique à CMake à partir de quelles sources la construire.target_include_directories: indique au compilateur de chercher les en-têtes sousinclude/.PRIVATEsignifie que ce réglage appartient uniquement à cette cible.
Compiler le projet : build hors source
La méthode recommandée pour compiler avec CMake consiste à garder les fichiers de build séparés de l'arborescence source. Nous travaillons donc dans un dossier build/ dédié :
mkdir build
cd build
cmake ..
cmake --build .
La commande cmake .. lit le CMakeLists.txt du dossier parent et génère le système de build (cette étape s'appelle la configuration). Ensuite cmake --build . exécute le Makefile ou le fichier Ninja généré pour effectuer la compilation réelle. Cette commande est indépendante de la plateforme et plus portable que d'appeler make directement. Le résultat est un exécutable nommé server dans build/.
Lier des bibliothèques et découper en modules
Les serveurs sont rarement autonomes. Ils ont souvent besoin de threads (pthread), de chiffrement (OpenSSL) ou d'autres bibliothèques. CMake résout cela avec find_package et target_link_libraries :
find_package(Threads REQUIRED)
target_link_libraries(server PRIVATE Threads::Threads)
find_package(Threads REQUIRED) localise la bibliothèque de threads du système ; grâce à REQUIRED, l'étape de configuration s'arrête si elle est introuvable. Nous lions ensuite la cible Threads::Threads à server. C'est l'approche « CMake moderne » : vous liez les bibliothèques en tant que cibles plutôt que par leur nom brut, ce qui apporte automatiquement les chemins d'inclusion et les drapeaux du compilateur.
À mesure que votre projet grandit, il devient judicieux d'extraire une partie du code dans une bibliothèque distincte. Par exemple, vous pouvez transformer la couche réseau en bibliothèque réutilisable :
add_library(netcore
src/Server.cpp
src/Connection.cpp
)
target_include_directories(netcore PUBLIC include)
add_executable(server src/main.cpp)
target_link_libraries(server PRIVATE netcore Threads::Threads)
Ici, netcore est une cible bibliothèque, et grâce à PUBLIC include, tout ce qui s'y lie hérite automatiquement du chemin include/. C'est un patron puissant qui évite les répétitions dans les grands projets.
Debug, Release et conseils pratiques
Pendant le développement du serveur, vous voulez des informations de débogage ; lors de la mise en production, vous voulez de l'optimisation. CMake gère cela via les types de build :
cmake -DCMAKE_BUILD_TYPE=Release ..
Debug ajoute les symboles et désactive l'optimisation ; Release active des optimisations comme -O2. Quelques conseils pratiques :
- Activez les avertissements :
target_compile_options(server PRIVATE -Wall -Wextra)permet de repérer tôt les bugs cachés. - Ajoutez toujours le dossier
build/au.gitignore; ne versionnez jamais les fichiers générés. - Pour compiler plus vite, utilisez Ninja :
cmake -G Ninja ..est un changement d'une ligne mais bien plus rapide en compilation parallèle.
Questions fréquentes
Quelle est la différence entre CMake et Make ?
Make est un outil de build direct qui exécute les Makefile. CMake génère ces Makefile (ou d'autres formats comme Ninja et les projets Visual Studio) pour vous. CMake travaille donc à un niveau supérieur, indépendant de la plateforme, tandis que Make traite la sortie qu'il produit.
Dois-je lister chaque fichier individuellement dans add_executable ?
Oui, c'est la méthode la plus fiable. Bien que l'on puisse collecter automatiquement les fichiers avec file(GLOB ...), CMake peut ne pas remarquer l'ajout d'un nouveau fichier. Lister les sources explicitement est l'approche recommandée.
Dois-je versionner le dossier build ?
Non. build/ contient des fichiers entièrement générés et spécifiques à la machine. Ajoutez-le au .gitignore ; chacun peut le régénérer sur sa propre machine avec cmake ...
Besoin d'une configuration de build solide pour votre projet serveur ? Qu'il s'agisse d'une configuration CMake de zéro ou de faire compiler un projet C++ existant, nous pouvons régler cela ensemble. Contactez-moi.