Cours — CMake (Complet)

✅ CMake — Cours complet

class: lead

Un cours structuré et pratique pour maîtriser CMake (projets C/C++ modernes)

Cours — CMake (Complet)

🎯 Objectifs

  • Comprendre le modèle de CMake et la philosophie "targets-first"
  • Structurer des projets (librairies / executables / tests)
  • Gérer dépendances, install, export, et packaging
Cours — CMake (Complet)

💡 Pourquoi CMake ? (vs Makefiles)

  • Makefiles: écrire les commandes de compilation manuellement, dépendances explicites
  • CMake: génère les Makefiles (ou Ninja, VS) automatiquement selon la plateforme
  • Avantages CMake:
    • Portabilité (Linux, Windows, macOS) — un seul CMakeLists.txt
    • Détection automatique compilateurs, bibliothèques
    • Gestion dépendances modernes (FetchContent, find_package)
    • Tests intégrés (CTest), packaging (CPack)
Cours — CMake (Complet)

Makefile vs CMakeLists.txt — Exemple simple

Makefile classique:

hello: main.c
	gcc -o hello main.c
clean:
	rm -f hello

CMakeLists.txt équivalent:

cmake_minimum_required(VERSION 3.22)
project(hello C)
add_executable(hello main.c)

CMake génère automatiquement les règles de build, clean, rebuild, etc.

Cours — CMake (Complet)

Plan du cours

  1. Installation & première configuration
  2. Projet minimal & out-of-source build
  3. Bibliothèques (statique vs partagée)
  4. Variables, cache et options
  5. Control-flow (if, foreach), scopes
  6. Targets et propagation (target_*)
  7. Fonctions, macros et modularité
  8. Export / install / find_package()
  9. Dépendances externes (FetchContent, pkg-config)
  10. Tests avec CTest et GoogleTest
Cours — CMake (Complet)

Module 0 — Installation

  • Objectifs: installer CMake, tester la configuration et choisir un générateur (Ninja, MSBuild)
  • Commands:
    • cmake --version — vérifier la version
    • Debian/Ubuntu : sudo apt install cmake (ou installer une version plus récente via kit)
    • Windows : installer Visual Studio + "Desktop development with C++" ou CLion
Cours — CMake (Complet)

Générateurs CMake (Ninja, Make, MSBuild, Xcode)

  • CMake = outil de configuration, générateur = système de build qu'il produit
  • Ninja (recommandé): très rapide, parallélisme excellent, builds incrémentaux efficaces
    • cmake -S . -B build -G Ninja
  • Unix Makefiles: universel, léger
    • cmake -S . -B build -G "Unix Makefiles"
  • Visual Studio: multi-config (Debug/Release dans même build)
    • cmake -S . -B build -G "Visual Studio 17 2022"
  • Xcode: support complet IDE (macOS)
    • cmake -S . -B build -G Xcode
Cours — CMake (Complet)

Installation Ubuntu recommandée

sudo apt install build-essential make binutils gdb valgrind \
  cppcheck clang-tidy clang-format cmake ninja-build

Vérifier: cmake --version && ninja --version && gcc --version

Cours — CMake (Complet)

Out-of-source Build (Bonnes pratiques)

  • Séparer sources / build output (clean facile, configs multiples)
mkdir build && cd build
cmake .. -G Ninja
cmake --build . --parallel
Cours — CMake (Complet)

Module 1 — Exemple minimal

  • Minimal CMakeLists.txt :
cmake_minimum_required(VERSION 3.22)
project(hello C)
add_executable(hello main.c)
  • Workflow out-of-source:
    • cmake -S . -B build — configure projet (génère build system)
    • cmake --build build --parallel — compile
  • Conseils: vérifier CMAKE_BUILD_TYPE (Debug/Release) et CMAKE_C_FLAGS
Cours — CMake (Complet)

Anatomie d'un CMakeLists.txt minimal

cmake_minimum_required(VERSION 3.22)    # Version minimale requise
project(hello C)                        # Nom projet + langage (C, CXX, ...)
add_executable(hello main.c)            # Cible "hello" depuis main.c

Équivalence Makefile:

# CMake génère automatiquement:
hello: main.c
	gcc -o hello main.c -std=c11

CMake détecte automatiquement le compilateur (gcc, clang, msvc) et flags standards.

Cours — CMake (Complet)

Commandes CMake essentielles (pour néophytes)

Configuration (génération build system):

cmake -S . -B build       # -S source dir, -B build dir
cmake -S . -B build -G Ninja   # choisir générateur
cmake -B build -DCMAKE_BUILD_TYPE=Release  # définir config

Build:

cmake --build build                # build tout
cmake --build build --parallel 4   # paralléliser (4 jobs)
cmake --build build --target hello # build cible spécifique

Clean:

cmake --build build --target clean  # nettoyer artefacts
rm -rf build/                       # supprimer build complet
Cours — CMake (Complet)

Module 2 — Bibliothèques (Static vs Shared)

  • Création: add_library(foo STATIC foo.c) ou SHARED
  • Liaison: target_link_libraries(app PRIVATE foo)
  • Bonnes pratiques: exposer headers via target_include_directories(foo PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
  • Exemple: utiliser POSITION_INDEPENDENT_CODE pour les bibliothèques partagées
Cours — CMake (Complet)

Bibliothèques — Exemple complet (libfoo)

Structure:

libfoo/
  foo.h
  foo.c
  CMakeLists.txt

libfoo/CMakeLists.txt:

add_library(foo STATIC foo.c)
target_include_directories(foo PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_features(foo PUBLIC c_std_11)

Équivalence Makefile:

libfoo.a: foo.o
	ar rcs libfoo.a foo.o
foo.o: foo.c foo.h
	gcc -c foo.c -o foo.o -std=c11

CMake gère automatiquement .o, .a et règles de dépendances.

Cours — CMake (Complet)

Lier une bibliothèque à un exécutable

Makefile classique:

hello: main.o libfoo.a
	gcc -o hello main.o -L. -lfoo

CMakeLists.txt:

add_executable(hello main.c)
target_link_libraries(hello PRIVATE foo)

CMake résout automatiquement:

  • Chemins includes (via target_include_directories)
  • Chemins libs (via dépendances de cibles)
  • Ordre de linkage (dépendances transitives)
Cours — CMake (Complet)

Module 3 — Variables, Cache & Options

  • Cache: set(MY_PATH "/opt/lib" CACHE PATH "Path to lib")
  • Option: option(BUILD_TESTS "Build tests" ON)
  • Utiliser cmake -LAH pour lister le cache et cmake-gui ou ccmake pour l'éditer
Cours — CMake (Complet)

Variables CMake expliquées (pour néophytes)

Variables simples:

set(SOURCES main.c foo.c bar.c)
add_executable(app ${SOURCES})  # expansion: main.c foo.c bar.c

Variables de cache (persistantes entre runs):

set(MY_LIB_PATH "/usr/local/lib" CACHE PATH "Library path")

Override depuis ligne de commande:

cmake -B build -DMY_LIB_PATH=/opt/lib

Équivalence Makefile:

SOURCES = main.c foo.c bar.c
app: $(SOURCES)
	gcc -o app $(SOURCES)
Cours — CMake (Complet)

Options et conditionnels — Cas pratique

Activer/désactiver fonctionnalités:

option(BUILD_TESTS "Build unit tests" ON)
option(ENABLE_OPTIMIZATIONS "Enable -O3" OFF)

if(BUILD_TESTS)
  enable_testing()
  add_subdirectory(tests)
endif()

if(ENABLE_OPTIMIZATIONS)
  target_compile_options(app PRIVATE -O3)
endif()

Usage:

cmake -B build -DBUILD_TESTS=OFF -DENABLE_OPTIMIZATIONS=ON
Cours — CMake (Complet)

Variable Scope — Functions vs Macros

  • Macro: accès parent scope (effets bord)
  • Function: scope isolé (plus sûr)
macro(my_macro var)
  set(${var} "changed")  # parent modifié
endmacro()

function(my_function var)
  set(${var} "changed" PARENT_SCOPE)  # explicit
endfunction()

Préférer function() pour encapsulation.

Cours — CMake (Complet)

Module 4 — Targets & Propagation

  • Favoriser target_* : target_include_directories, target_compile_definitions, target_compile_options
  • PUBLIC / PRIVATE / INTERFACE contrôlent la propagation des propriétés
    • PUBLIC: lié à la cible et passé aux consommateurs
    • PRIVATE: affecte le build de la cible, pas passé à liés
    • INTERFACE: non affect le build, mais passé aux consommateurs
  • Exemple avec BUILD_INTERFACE pour sourcing vs installation :
add_library(foo foo.c)
target_include_directories(foo
  PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
         $<INSTALL_INTERFACE:include>)
Cours — CMake (Complet)

Targets — Philosophie CMake moderne

❌ Ancien style (global, fragile):

include_directories(/usr/local/include)  # affect TOUT le projet
link_libraries(pthread)                  # lié à TOUTES les cibles
add_definitions(-DDEBUG)                 # propagé partout

✅ Style moderne (target-based, précis):

add_library(foo foo.c)
target_include_directories(foo PUBLIC /usr/local/include)
target_link_libraries(foo PRIVATE pthread)
target_compile_definitions(foo PRIVATE DEBUG)

Avantage: contrôle granulaire + propagation explicite (PUBLIC/PRIVATE).

Cours — CMake (Complet)

PUBLIC vs PRIVATE vs INTERFACE — Cas concrets

Exemple:

add_library(foo foo.c)
target_include_directories(foo PUBLIC include/)    # foo.h exposé aux consommateurs
target_compile_definitions(foo PRIVATE FOO_IMPL)   # interne à foo
target_link_libraries(foo PRIVATE pthread)         # pthread pas propagé

add_executable(app main.c)
target_link_libraries(app PRIVATE foo)
# app voit include/ (PUBLIC propagé)
# app ne voit PAS FOO_IMPL ni pthread (PRIVATE)

Équivalence Makefile:

# Makefile nécessite gestion manuelle includes/libs pour chaque cible
app: main.o libfoo.a
	gcc -o app main.o -Iinclude -L. -lfoo -lpthread  # tout manuel
Cours — CMake (Complet)

Module 5 — Fonctions, Macros & Modularité

  • Fonctions CMake : bien scoper les variables, préférer function() à macro() quand possible
  • Exemple d'une fonction :
function(add_my_library name)
  add_library(${name} ${ARGN})
  target_compile_features(${name} PUBLIC c_std_11)
endfunction()
  • Modularisation avec add_subdirectory() pour projets multi-modules :
# Root CMakeLists.txt
add_subdirectory(libfoo)
add_subdirectory(hello)
add_subdirectory(tests)
  • Conseil: documenter les fonctions et éviter les effets de bord sur variables globales
Cours — CMake (Complet)

Module 6 — Export, Install & Find

  • Installer cibles :
    • install(TARGETS foo EXPORT fooTargets DESTINATION lib)
    • install(EXPORT fooTargets FILE FooConfig.cmake NAMESPACE Foo:: DESTINATION lib/cmake/Foo)
  • Générer un FooConfig.cmake simple pour find_package(Foo CONFIG REQUIRED)
  • CMAKE_PREFIX_PATH ou -DCMAKE_INSTALL_PREFIX pour tester localement
Cours — CMake (Complet)

Installation — Pourquoi et comment ?

Makefile classique:

install:
	cp libfoo.a /usr/local/lib/
	cp foo.h /usr/local/include/

CMakeLists.txt:

install(TARGETS foo DESTINATION lib)
install(FILES foo.h DESTINATION include)

Exécution:

cmake --install build --prefix /usr/local  # ou ~/local pour test local

CMake gère automatiquement les permissions, chemins multi-plateformes.

Cours — CMake (Complet)

Export & find_package — Cas d'usage

Scénario: distribuer libfoo pour utilisation dans d'autres projets.

1. Exporter la bibliothèque (projet libfoo):

install(TARGETS foo EXPORT fooTargets DESTINATION lib)
install(EXPORT fooTargets FILE FooConfig.cmake 
        NAMESPACE Foo:: DESTINATION lib/cmake/Foo)

2. Consommer la bibliothèque (projet client):

find_package(Foo REQUIRED)
add_executable(app main.c)
target_link_libraries(app PRIVATE Foo::foo)

Équivalence Makefile: inexistant (gestion manuelle pkg-config ou hardcode paths).

Cours — CMake (Complet)

Module 7 — Dépendances externes

  • FetchContent pour importer des dépendances sources au moment de la configuration
  • pkg-config ou modules FindFoo.cmake pour projets non-CMake
  • Exemple FetchContent :
include(FetchContent)
FetchContent_Declare(googletest URL https://github.com/google/googletest/archive/release-1.14.0.zip)
FetchContent_MakeAvailable(googletest)
Cours — CMake (Complet)

FetchContent expliqué (pour néophytes)

Problème Makefile: télécharger/compiler dépendances manuellement.

Solution CMake:

include(FetchContent)
FetchContent_Declare(
  json
  URL https://github.com/nlohmann/json/archive/refs/tags/v3.11.2.tar.gz
)
FetchContent_MakeAvailable(json)

add_executable(app main.c)
target_link_libraries(app PRIVATE nlohmann_json::nlohmann_json)

CMake télécharge, configure, compile et lie automatiquement la dépendance.

Cours — CMake (Complet)

find_package — Utiliser bibliothèques système

Chercher bibliothèque installée (ex: Threads, OpenSSL):

find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED)

add_executable(server main.c)
target_link_libraries(server PRIVATE 
  Threads::Threads 
  OpenSSL::SSL 
  OpenSSL::Crypto)

Équivalence Makefile:

# Hardcoded ou pkg-config manuel
LIBS = -lpthread -lssl -lcrypto
server: main.c
	gcc -o server main.c $(LIBS)

CMake détecte automatiquement chemins système.

Cours — CMake (Complet)

Module 8 — Tests & CTest

  • enable_testing(), add_test(NAME X COMMAND x)
  • Intégrer GoogleTest et utiliser ctest --output-on-failure
  • Exercice: écrire tests de régression et configurer CTestTestfile.cmake pour CI
Cours — CMake (Complet)

Tests unitaires — Workflow CMake + CTest

Makefile classique:

test: test_foo
	./test_foo

CMakeLists.txt:

enable_testing()
add_executable(test_foo test_foo.c)
target_link_libraries(test_foo PRIVATE foo)
add_test(NAME FooTests COMMAND test_foo)

Exécution:

cmake --build build
ctest --test-dir build --output-on-failure

CTest gère automatiquement découverte, exécution, reporting des tests.

Cours — CMake (Complet)

GoogleTest + FetchContent — Exemple complet

# Root CMakeLists.txt
option(BUILD_TESTS "Build tests" ON)
if(BUILD_TESTS)
  enable_testing()
  add_subdirectory(tests)
endif()

# tests/CMakeLists.txt
include(FetchContent)
FetchContent_Declare(googletest
  URL https://github.com/google/googletest/archive/release-1.14.0.zip)
FetchContent_MakeAvailable(googletest)

add_executable(test_foo test_foo.cpp)
target_link_libraries(test_foo PRIVATE gtest_main foo)
add_test(NAME FooTests COMMAND test_foo)
ctest --test-dir build --output-on-failure -V  # verbose
Cours — CMake (Complet)

TP1 : Projet minimal + Bibliothèque

  • Structure:
project/
  CMakeLists.txt
  libfoo/
    CMakeLists.txt
    foo.c
    foo.h
  hello/
    CMakeLists.txt
    main.c
  • Root CMakeLists.txt:
cmake_minimum_required(VERSION 3.22)
project(my_project C)
add_subdirectory(libfoo)
add_subdirectory(hello)
  • Commandes:
    • cmake -S . -B build -G Ninja
    • cmake --build build --parallel
    • ./build/hello
Cours — CMake (Complet)

TP2 : Tests unitaires avec GoogleTest

  • Ajouter dossier tests/ avec CMakeLists.txt et test_foo.cpp
enable_testing()
add_subdirectory(tests)
  • Dans tests/CMakeLists.txt:
include(FetchContent)
FetchContent_Declare(googletest
  URL https://github.com/google/googletest/archive/refs/tags/release-1.14.0.zip)
FetchContent_MakeAvailable(googletest)

add_executable(test_foo test_foo.cpp)
target_link_libraries(test_foo PRIVATE gtest_main foo)
add_test(NAME FooTests COMMAND test_foo)
  • Exécution: ctest --test-dir build --output-on-failure
Cours — CMake (Complet)

TP3 : Installation & find_package()

  • Ajouter install() rules dans libfoo/CMakeLists.txt:
install(TARGETS foo EXPORT fooTargets
        ARCHIVE DESTINATION lib
        LIBRARY DESTINATION lib
        INCLUDES DESTINATION include)
install(FILES foo.h DESTINATION include)
install(EXPORT fooTargets FILE FooConfig.cmake
        NAMESPACE Foo:: DESTINATION lib/cmake/Foo)
  • Tester localement:
    • cmake --install build --prefix ~/local
    • cmake -S . -B build2 -DCMAKE_PREFIX_PATH=~/local
    • find_package(Foo CONFIG REQUIRED)
Cours — CMake (Complet)

Bonnes pratiques CMake

  • ✅ Always use target_* commands (pas variables globales)
  • ✅ Out-of-source build (séparer sources et artefacts)
  • ✅ Explicite > implicite : déclarer target_compile_features, c_std_*
  • ✅ Namespace cibles (ex: Foo::foo pour imports)
  • ✅ Version CMake minimum : 3.15+ de préférence
  • ✅ Tests et CI : enable_testing() + ctest dans CI/CD
  • ❌ Eviter: politiques globales (set(CMAKE_...)), include_directories() global, link_libraries() global
Cours — CMake (Complet)

Recap — Checklist finalisateur

  • [ ] CMake 3.15+ requis
  • [ ] Out-of-source build fonctionnel
  • [ ] Toutes les cibles avec target_* (includes, definitions, options)
  • [ ] PUBLIC/PRIVATE/INTERFACE utilisés correctement
  • [ ] Tests unitaires avec add_test() + enable_testing()
  • [ ] install() rules pour cibles/headers/exports
  • [ ] find_package() testé localement
  • [ ] Format code CMake standard (indentation, commentaires)
  • [ ] Aucun warning CMake sur politique dépréciée
  • [ ] Documentation (README, exemples, commentaires)
Cours — CMake (Complet)

📚 Ressources & Références

Cours — CMake (Complet)

📝 Changelog — V0.0.21

  • 02/02/2026 00:10 — Enrichissement complet du deck pour néophytes connaissant Makefiles:
    • Ajout slides "Pourquoi CMake ?" et comparaison Makefile vs CMakeLists
    • Exemple minimal avec anatomie détaillée et équivalences Makefile
    • Commandes CMake essentielles (configure, build, clean) expliquées
    • Bibliothèques: exemples complets avec équivalences Makefile (ar, gcc -c, -L, -l)
    • Variables CMake détaillées (simples, cache, override CLI) + équivalences Makefile
    • Options et conditionnels avec cas pratiques
    • Targets modernes: ancien style vs nouveau (global vs target-based)
    • PUBLIC/PRIVATE/INTERFACE avec cas concrets et équivalences Makefile
    • Installation expliquée (vs cp manuel)
    • Export/find_package avec scénario complet (distribuer/consommer lib)
    • FetchContent et find_package expliqués avec équivalences Makefile
    • Tests CTest avec workflow complet GoogleTest
    • Footer: C Avancé M2 - V0.0.21 - 02/02/2026 00:10 - Réda BOUREBABA <r.bourebaba@ynov.com>.
Cours — CMake (Complet)

class: small
Questions? email: Réda BOUREBABA r.bourebaba@ynov.com