Entraînement sur la construction de logiciel C++ (dont fait partie cmake)

Michel SIMATIC

15 avril 2026

1 Introduction

Ce TP a pour but de faire diverses manipulations pour :

La durée totale de ce TP est estimée à 0h45.

2 Etape 0 : Mise en place du TP

Téléchargez l’archive TPCmake.zip et décompressez-la dans le répertoire de votre choix. Cette archive contient la structure suivante :

TPCmake/
├── CMakeLists.txt
└── src
    └── main
        ├── CMakeLists.txt
        └── main.cpp

Analysez le fichier src/main/main.cpp pour vérifier que vous comprenez :

Ouvrez un terminal.

Si vous êtes sous Windows et que vous utilisez VisualStudio, tapez la commande (y compris le &) :

& 'C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\Launch-VsDevShell.ps1'

(Une autre manière de faire pour éviter de taper cette commande, c’est d’ouvrir le terminal en lançant VisualStudio, puis Outils > Ligne de commande > Powershell Développeur).

3 Etape 1 : Où on constate que cmake dispense de connaître les outils de la chaîne de compilation sur le système d’exploitation utilisé

Dans ce terminal, générez votre exécutable en tapant les commandes adéquates (Pour chacune, vérifiez que vous comprenez son rôle). NB : Pour la commande de génération de l’exécutable proprement dite, ajoutez l’option pour une génération verbeuse.

Cliquez pour de l'aide
cd VotreRépertoire/TPCmake # Se positionne dans le répertoire contenant le projet
cmake -B build -G Ninja # Génère dans le sous-répertoire build tous les scripts de pilotage, scripts correspondant à l'utilitaire Ninja
cmake --build build --verbose # Génère l'exécutable dans le sous-répertoire build

Dans l’affichage généré par la commande, retrouvez l’étape de compilation et l’étape d’édition de liens. Puis, remarquez que :

  1. Le compilateur travaille sur le fichier .cpp pour générer un fichier objet.
  2. L’éditeur de liens travaille sur le fichier .obj (Windows) ou .o (Linux/macOS) pour générer l’exécutable.
Cliquez si vous n'arrivez vraiment pas à retrouver ce qu'on vous demande
#
# Sous Windows
#
> cmake --build build --verbose
Change Dir: 'C:/temp/CSC4526/TPCmake/build'

Run Build Command(s): C:/software/Strawberry/c/bin/ninja.exe -v
# Etape de compilation du fichier C:\temp\CSC4526\TPCmake\src\main\main.cpp
[1/2] C:\PROGRA~1\MICROS~4\2022\COMMUN~1\VC\Tools\MSVC\1444~1.352\bin\Hostx86\x86\cl.exe  /nologo /TP   /DWIN32 /D_WINDOWS /EHsc /Ob0 /Od /RTC1 -std:c++latest -MDd -Zi /showIncludes /Fosrc\main\CMakeFiles\main.dir\main.cpp.obj /Fdsrc\main\CMakeFiles\main.dir\ /FS -c C:\temp\CSC4526\TPCmake\src\main\main.cpp
# Etape d'édition de liens avec le fichier src\main\CMakeFiles\main.dir\main.cpp.obj (précédemment généré)
[2/2] C:\WINDOWS\system32\cmd.exe /C "cd . && C:\software\Strawberry\c\bin\cmake.exe -E vs_link_exe --intdir=src\main\CMakeFiles\main.dir --rc=C:\PROGRA~2\WI3CF2~1\10\bin\100261~1.0\x86\rc.exe --mt=C:\PROGRA~2\WI3CF2~1\10\bin\100261~1.0\x86\mt.exe --manifests  -- C:\PROGRA~1\MICROS~4\2022\COMMUN~1\VC\Tools\MSVC\1444~1.352\bin\Hostx86\x86\link.exe /nologo src\main\CMakeFiles\main.dir\main.cpp.obj  /out:bin\main.exe /implib:src\main\main.lib /pdb:bin\main.pdb /version:0.0 /machine:X86 /debug /INCREMENTAL /subsystem:console  kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib && cd ."

#
# Sous Linux/macOS
#
$ cmake --build build --verbose
Change Dir: '/home/simatic/GIT/csc4526_dev_cpp/Documents/CMake/Sources/TPCmake/build'

Run Build Command(s): /usr/bin/ninja -v
# Etape de compilation du fichier /home/simatic/GIT/csc4526_dev_cpp/Documents/CMake/Sources/TPCmake/src/main/main.cpp
[1/2] /usr/bin/c++   -std=gnu++23 -MD -MT src/main/CMakeFiles/main.dir/main.cpp.o -MF src/main/CMakeFiles/main.dir/main.cpp.o.d -o src/main/CMakeFiles/main.dir/main.cpp.o -c /home/simatic/GIT/csc4526_dev_cpp/Documents/CMake/Sources/TPCmake/src/main/main.cpp
# Etape d'édition de liens avec le fichier src/main/CMakeFiles/main.dir/main.cpp.o (précédemment généré)
[2/2] : && /usr/bin/c++  -Wl,--dependency-file=src/main/CMakeFiles/main.dir/link.d src/main/CMakeFiles/main.dir/main.cpp.o -o bin/main   && :

Voici l’arborescence que vous avez obtenue :

TPCmake/
├── CMakeLists.txt
├── build
│   ├── Plein de fichiers et de répertoires générés par cmake et dont nous n'avons pas à nous préoccuper
│   └── bin
│       └── main # main.exe sous Windows
└── src
    └── main
        ├── CMakeLists.txt
        └── main.cpp

Notez que tout ce qui est généré est localisé dans build : Si vous voulez supprimer tout ce qui a été généré, il vous suffit de supprimer build.

Exécutez maintenant votre programme en lançant l’exécutable dans votre terminal.

Cliquez pour de l'aide
# Sous Windows
.\build\bin\main.exe 
# Sous Linux/macOS
./build/bin/main

Vous devriez obtenir l’affichage suivant (et en plus, sous Windows, la fenêtre ci-dessous):

# Sous Windows
1 + 2 = 3
6 / 2 = 3
6 / 0 = 
# Sous Linux/macOS
1 + 2 = 3
6 / 2 = 3
terminate called after throwing an instance of 'std::invalid_argument'
  what():  division by zero
Aborted (core dumped)
Fenêtre sous Windows liée à la levée de l’exception dans le programme

Ce “plantage” est normal puisque notre main() contient un test pour vérifier qu’une exception est bien levée quand un·e développeur·se utilise divide() pour faire une division par zéro. Toutefois, vu le comportement (message, voire fenêtre d’erreur), cela donne l’impression que notre programme ne fonctionne pas, vu qu’il “plante”. Dans 2 sections, nous utiliserons GoogleTest pour 1) éviter de vérifier visuellement que notre code fournit les résultats attendus et 2) faire un test qui vérifie que notre code lève bien une exception en cas de division par 0 sans pour autant “planter”.

Auparavant, expérimentons que cmake facilite aussi la génération d’un programme sur une machine distante.

4 Etape 2 : Génération du projet sur un autre système d’exploitation avec éventuellement un autre compilateur

Dans cette section, vous allez vérifier que votre projet se génère et s’exécute sur une machine Linux (Ubuntu) de l’école, avec le compilateur g++, puis le compilateur clang. Si vous êtes déjà sous Linux, nous vous invitons à faire quand même les opérations pour vous remémorer les commandes scp et ssh.

Commencez par supprimer la ligne std::cout << "6 / 0 = " << divide(6, 0) << std::endl; de src/main/main.cpp et régénérez votre exécutable. Pourquoi n’avez-vous pas besoin d’exécuter la commande cmake -B build -G Ninja pour ce faire ?

Exécutez votre programme et vérifier qu’il ne plante plus désormais.

Dans votre terminal, exécutez maintenant les commandes suivantes :

rm -r build # Cela supprime tout ce que vous avez généré jusqu'à maintenant : Vous ne gardez que les sources.
cd .. # Remonte dans le répertoire parent de TPCmake
tar cvfz TPCmake.tgz TPCmake # Crée, dans le fichier TPCmake.tgz, une archive du répertoire TPCmake

# Si la commande scp suivante vous affiche `Are you sure you want to continue connecting (yes/no/[fingerprint])?`, répondez Yes
scp TPCmake.tgz usernameDeVotreCompteLinuxEcole@ssh.imtbs-tsp.eu:. # Recopie le fichier TPCmake.tgz dans le répertoire racine de votre compte Linux de l'école
# Tapez ensuite votre mot de passe : Le fichier zip est recopié sur la machine distante.

ssh usernameDeVotreCompteLinuxEcole@ssh.imtbs-tsp.eu # Se connecte à votre compte Linux de l'école.
# Tapez votre mot de passe pour vous connecter à la machine distante.

Vous êtes maintenant connecté·e à une machine Ubuntu de l’école. Exécutez les commandes suivantes :

tar xvfz TPCmake.tgz # Extrait lr contenu de l'archive TPCmake.tgz et crée donc le répertoire TPCMake
cd TPCmake

cmake -B build # Noter qu'on enlève `-G Ninja` car cet outil n'a pas ete déployé par la DISI
cmake --build build --verbose
./build/bin/main

# Générons maintenant notre projet avec le compilateur clang
rm -Rf build

export CC=/usr/bin/clang-22
export CPP=/usr/bin/clang-cpp-22
export CXX=/usr/bin/clang++-22
export LD=/usr/bin/ld.lld-22

cmake -B build # Noter qu'on enlève `-G Ninja` car cet outil n'a pas ete déployé par la DISI
cmake --build build --verbose
./build/bin/main

Notez que, a priori, vous ne devriez avoir aucun problème de génération/exécution sachant que :

Vous pouvez maintenant nettoyer tout ce que vous venez de créer votre compte Linux de l’école. Tapez les commandes

cd .. && rm -rf TPCMake* # pour faire le ménage
exit # pour revenir sur votre machine.

5 Etape 3 : Utilisation de GoogleTest pour les tests unitaires

#include <gtest/gtest.h>

TEST(MathUtilsTest, Addition) {
  EXPECT_EQ(add(1, 2), 3);
}

TEST(MathUtilsTest, Division) {
  EXPECT_EQ(divide(6, 2), 3);
}

TEST(MathUtilsTest, DivisionByZero) {
  EXPECT_THROW(divide(6, 0), std::invalid_argument);
}
cmake -B build -G Ninja
cmake --build build --verbose # Cette commande échoue (cf. ci-dessous)
[[nodiscard]] int add(int a, int b);
int divide(int a, int b);
add_executable(unitTests
  unitTests.cpp ../main/main.cpp # Ligne modifiée pour signifier que l'exécutable unitTests sera construit avec les fichiers objet issus de unitTests.cpp ../main/main.cpp
)
Test project <Chemin absolu vers votre répertoire build>
No tests were found!!!

Pour information, voici l’arborescence à la fin de cette étape :

TPCmake
├── CMakeLists.txt
└── src
    ├── main
    │   ├── CMakeLists.txt
    │   ├── main.cpp
    │   └── math_utils.cpp
    └── test
        ├── CMakeLists.txt
        └── unitTests.cpp

6 Etape 4 : Utilisation d’un .h pour être certain·e que déclaration et définition sont cohérents

Remarquez que nous n’avons aucune garantie que les déclarations de add() et divide() dans main.cpp et unitTests.cpp soient cohérentes entre elles et surtout avec les définitions de ces fonctions dans math_utils.cpp. Or, une incohérence n’est pas forcément détectable à la compilation ou à l’édition de liens et peut entraîner des erreurs de fonctionnement. Pour vous en convaincre :

1 + 2 = 3
6 / 2 = 6.08231e-33

Pour éviter ces erreurs de fonctionnement, il faut garantir la cohérence en centralisant les déclarations de add() et divide() dans un fichier math_utils.h :

Pour information, voici l’arborescence à la fin de cette étape :

TPCmake
├── CMakeLists.txt
└── src
    ├── main
    │   ├── CMakeLists.txt
    │   ├── main.cpp
    │   ├── math_utils.cpp
    │   └── math_utils.h
    └── test
        ├── CMakeLists.txt
        └── unitTests.cpp

7 Etape 5 : Génération d’une bibliothèque pour éviter de multiples compilations

Nos CMakeLists.txt actuels font que des génération de projet peuvent conduire à des compilations inutiles. Pour vous en convaincre :

Pour l’instant, nous n’avons qu’un seul fichier compilé deux fois. Mais imaginez le temps perdu si nous avions systématiquement 100 fichiers à compiler 2 fois !

Pour éviter ces compilations inutiles, nous allons créer une bibliothèque lib_core contenant le résultat de la compilation de math_utils.cpp, main et unitTests étant construits en s’appuyant sur cette bibliothèque au moment de l’édition de liens :

Pour information, voici l’arborescence à la fin de cette étape :

TPCmake/
├── CMakeLists.txt
└── src
    ├── core
    │   ├── CMakeLists.txt
    │   ├── math_utils.cpp
    │   └── math_utils.h
    ├── main
    │   ├── CMakeLists.txt
    │   └── main.cpp
    └── test
        ├── CMakeLists.txt
        └── unitTests.cpp

8 Etape 6 : Utilisation d’une bibliothèque externe au projet et d’un fichier ressources

{
  "add_operand_1": 3,
  "add_operand_2": 4
}
#include "math_utils.h"
#include <iostream>

#include <fstream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;

int main() {
  std::ifstream f("res/param.json");
  json data = json::parse(f);

  const int add_operand_1{ data["add_operand_1"] };
  const int add_operand_2{ data["add_operand_2"] };

  std::cout << add_operand_1 << " + " << add_operand_2 << " = " << add(add_operand_1, add_operand_2) << std::endl;
  std::cout << "6 / 2 = " << divide(6, 2) << std::endl;
}

Modifiez les CMakeLists.txt qui vous semblent concernés par cette évolution pour que ce code s’exécute correctement.

NB :

Pour information, voici l’arborescence à la fin de cette étape :

TPCmake/
├── CMakeLists.txt
├── res
│   └── param.json
└── src
    ├── core
    │   ├── CMakeLists.txt
    │   ├── math_utils.cpp
    │   └── math_utils.h
    ├── main
    │   ├── CMakeLists.txt
    │   └── main.cpp
    └── test
        ├── CMakeLists.txt
        └── unitTests.cpp

9 Etape 7 : Ajout d’une option pour contrôler la génération du projet

Les tests unitaires sont actuellement systématiquement générés. Dans CMakeLists.txt, ajoutez une ligne

option(ENABLE_TESTS "Build tests" ON)

et un if (ENABLE_TESTS) de sorte que les tests unitaires ne sont générés que si ENABLE_TESTS vaut ON, c’est-à-dire est vrai. Mettez ENABLE_TESTS à OFF, régénérez votre projet et vérifiez que les tests unitaires ne sont effectivement pas générés.

Cliquez pour de l'aide
# Option pédagogique : activer/désactiver les tests
option(ENABLE_TESTS "Build unit tests" OFF)

if (ENABLE_TESTS)
  #
  # Include GoogleTest
  #
  FetchContent_Declare(
    googletest
    URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.tar.gz
  )

  # For Windows: Prevent overriding the parent project's compiler/linker settings
  set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

  FetchContent_MakeAvailable(googletest)

  # enable_testing() must be in the source directory root (see cmake documentation at https://cmake.org/cmake/help/latest/command/enable_testing.html)
  # Otherwise, Visual Studio test explorer does not see unit tests (See ticket https://developercommunity.visualstudio.com/t/No-tests-discovered-for-googletest-and-C/1148799#T-ND1150621)
  include(GoogleTest)
  enable_testing()
  add_subdirectory(src/test)
endif()

10 Conclusion

Nous voici rendu·es à la fin de ce TP qui avait pour objectif de vous donner le minimum de survie concernant cmake et la chaîne de génération C++. Logiquement, vous devriez être plus autonome pour la suite de vos travaux dans le cadre de cette Unité d’Enseignement.

Remarque finale : Pour tous les autres projets fournis dans le cadre de cette unité d’enseignement, du fait d’un souci de génération avec VSCode, les exécutables ne seront pas générés dans build/bin comme pour ce TP, mais dans un sous-répertoire cheminDeBuild/src/main ou cheminDeBuild/src/test.