15 avril 2026
Ce TP a pour but de faire diverses manipulations pour :
main() doit être
stockée dans un fichier dédié,.h et des
bibliothèques.La durée totale de ce TP est estimée à 0h45.
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 :
add() et divide() qui y sont
définies.main().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).
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.
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 buildDans l’affichage généré par la commande, retrouvez l’étape de compilation et l’étape d’édition de liens. Puis, remarquez que :
.cpp pour
générer un fichier objet..obj
(Windows) ou .o (Linux/macOS) pour générer
l’exécutable.#
# 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.
# Sous Windows
.\build\bin\main.exe
# Sous Linux/macOS
./build/bin/mainVous 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)
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.
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/mainNotez que, a priori, vous ne devriez avoir aucun problème de génération/exécution sachant que :
CMakeLists.txt pour résoudre ces soucis en
fonction de l’OS sur lequel vous générez.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.src/test/unitTests.cpp avec pour contenu :#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);
}CMakeLists.txt présents dans l’archive
SampleGoogleTestAndPugixml.zip, modifiez :
TPCmake/CMakeLists.txt en y ajoutant toutes les lignes
après le commentaire # Include GoogleTest dans
SampleGoogleTestAndPugixml/CMakeLists.txtTPCmake/src/test/CMakeLists.txt
SampleGoogleTestAndPugixml/src/test/CMakeLists.txttarget_link_libraries(unitTests GTest::gtest_main lib_core)
par target_link_libraries(unitTests GTest::gtest_main)cmake -B build -G Ninja
cmake --build build --verbose # Cette commande échoue (cf. ci-dessous)unitTests.cpp, car il manque au compilateur les
déclarations de add() et divide() lui
permettant de vérifier que nous utilisons correctement ces fonctions
(type des paramètres OK, code retour bien utilisé). Aussi, dans le
fichier src/test/unitTests.cpp, ajoutez après le
#include :[[nodiscard]] int add(int a, int b);
int divide(int a, int b);Vu que vous n’avez pas modifié de CMakeLists.txt,
vous pouvez simplement lancer la commande
cmake --build build --verbose. Cette commande échoue à
nouveau, mais à l’édition de lien de l’exécutable
unitTests, car l’éditeur de liens n’arrive pas à trouver la
définition (c’est-à-dire le code) de add() et
divide().
Comme ces fonctions sont définies dans
src/main/main.cpp, indiquons à
src/test/CMakeLists.txt qu’il faut inclure le code de
src/main/main.cpp à l’exécutable unitTests.
Pour ce faire, modifiez l’instruction
add_executable(unitTests avec :
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
)Exécutez cmake -B build, puis
cmake --build build. La génération se finit bien !
Exécutez maintenant vos tests en tapant la commande
.\build\bin\unitTests.exe sous Windows
(./build/bin/unitTests sous Linux/MacOS) : Vous obtenez le
même affichage qu’avec l’exécutable build\bin\main.exe
(build/bin/main), et non le déroulement habituel concernant
vos tests unitaires (qui affiche habituellement la liste des tests OK et
KO) ! Pour avoir confirmation qu’il y a quelque chose d’anormal, dans
votre terminal, tapez cd build, puis la commande
ctest pour lancer les tests unitaires grâce à un utilitaire
lié à cmake. Vous obtenez l’affichage :
Test project <Chemin absolu vers votre répertoire build>
No tests were found!!!
main.cpp définit une
fonction main() et que la bibliothèque
GTest::gtest_main contient aussi une fonction
main(). L’éditeur de liens choisit systématiquement la
définition de fonction (main() en l’occurrence) qui n’est
pas dans une bibliothèque, donc celle de main.cpp : Le code
de vos tests ne peut pas être invoqué (Pour information, il se pourrait
même que l’éditeur de liens ait même supprimé le code de vos tests de
l’exécutable final, vu que vos tests sont inutilisés). Pour corriger ce
problème, il ne faut pas que unitTests contienne le
main() de main.cpp :
src/main/math_utils.cpp et mettez-y
les définitions des fonctions add() et
divide().src/main/main.cpp, ne conservez que le code de la
fonction main()CMakeLists.txt pour que l’ensemble de vos
exécutables soient générés.
main.cpp ne compile plus, car il
n’a pas de déclaration de add() et divide().
Pour l’instant, ajoutez dans main.cpp les déclarations de
add() et divide() comme vous l’avez fait dans
unitTests.cpp.ctest
lancée en vous mettant dans le répertoire build. Ca y est,
vos tests sont exécutés (et sont OK, même celui testant l’exception qui
n’arrête pas votre programme de test).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
.h pour être certain·e que déclaration et définition sont
cohérentsRemarquez 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 :
main.cpp, remplacez
int divide(int a, int b); par
float divide(int a, int b);main.cpp.obj : error LNK2019: symbole externe non résolu "float __cdecl divide(int,int)" (?divide@@YAMHH@Z) référencé dans la fonction _main
et
Conseil sur les symboles définis et susceptibles de correspondre : "int __cdecl divide(int,int)"6 / 2) :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 :
src/main/math_utils.h contenant les
déclarations de add() et divide().main.cpp et
unitTests.cpp par #include "math_utils.h.#include "math_utils.h" au début de
math_utils.cpp.unitTests.cpp échoue car le compilateur ne trouve pas
math_utils.h. Aussi, ajoutez la ligne
include_directories(../main) à
src/test/CMakeLists.txt pour rendre visibles les
.h situés dans le répertoire ../main.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
Nos CMakeLists.txt actuels font que des génération de
projet peuvent conduire à des compilations inutiles. Pour vous en
convaincre :
src/main/math_utils.cpp pour ajouter un espace
dans le fichier.cmake --build --verbose : Remarquez que le
générateur compile 2 fois math_utils.cpp :
main,unitTests !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 :
Créez un répertoire src/core. Transférez-y
math_utils.cpp et math_utils.h.
Modifiez vos CMakeLists.txt en vous inspirant des
CMakeLists.txt de l’exemple
SampleGoogleTestAndPugixml évoqué précédemment. NB :
src/core/CMakeLIsts.txt contient une ligne
target_include_directories(lib_core PUBLIC ./). Cette ligne
signifie que, si le CMakeLists.txt construisant un
exécutable mentionne lib_core dans sa ligne
target_link_libraries(), il inclut automatiquement le(s)
répertoire(s) mentionné(s) dans l’instruction
target_include_directories(). Cela signifie que, dans
src/test/CMakeLists.txt, la ligne
include_directories(../main) est désormais inutile.add_subdirectory(src/core) dans
./CMakeLists.txtRégénérez votre projet.
Vérifiez que :
math_utils.cpp n’est désormais compilé qu’une seule
fois.
Les exécutables main.exe et
unitTests.exe s’exécutent toujours correctement.
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
main()
appelle add() soient désormais lus dans le fichier
JSON res/param.json (que vous devez créer)
contenant :{
"add_operand_1": 3,
"add_operand_2": 4
}Pour ce faire, nous choisissons de nous appuyer sur la bibliothèque JSON de Niels Lohmann (FYI cette page vous permet de comparer JSON et XML) pour lire les valeurs de ces deux opérandes dans le fichier JSON.
Modifiez src/main/main.cpp de la manière suivante
:
#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 :
FetchContent, voir cette
documentation.terminate called after throwing an instance of 'nlohmann::json_abi_v3_12_0::detail::parse_error' / what(): [json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON / Aborted (core dumped),
il est probable que vous avez oublié de modifier
src/main/CMakeLists.txt pour y intégrer la recopie du
répertoire res.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
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.
# 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()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.