Vous êtes sur la page 1sur 38

12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Principe général de compilation avec cmake


Ex1 : Construire et installer un exécutable
Fichiers de configuration
Compilation et installation
Ex2 : Construire et installer une librairie dynamique
Mise en place du projet
Fichiers de configuration
Ajouter des FLAGS de compilation/linkage
Ex3 : Gérer des dépendances
find_package
pkg_check_modules
Comment accéder/modifier les propriétés d'une cible
Un example
Ex4 : Rendre mon projet pkg-config compatible
Ecrire un fichier pc depuis CMakeLists
Configurer un fichier template
Ex5 : Et la documentation avec doxygen ?
sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 1/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Ex6 : Tests unitaires


Ex7 : Créer une archive tar.gz
Exercice de synthèse : faire un package cmake à partir de fichiers en vrac
Bonus : Compiler à plusieurs coeurs ? Priorité entre les cibles ? Mais qu'elles sont les variables
définies par cmake ? Comment débugger une construction cmake qui ne passe pas comme prévue ?
Construire des packages deb, rpm, osx, windows installables ?

Cmake est un "moteur de production" : hein ?! mais pourquoi ?!

Dans ce TL, nous allons étudier l'utilisation de cmake, un moteur de production particulier qu'on
retrouvera par la suite quand on utilisera ROS par exemple. Mais qu'est ce qu'un moteur de production
et pourquoi on s'y intéresserait ?

Prenons un exemple et racontons une petite histoire. Vous développez une librairie C++ sous Linux.
Vous êtes un peu calé en C++ et vous savez compiler "à la main" votre librairie. Supposons que ma
librairie ne soit définie que par le fichier malib.cpp,

bash:$ g++ -shared -fPIC -o libmalib.so malib.cpp

On vient de créer la librairie malib.

Ok très bien mais en plus vous souhaitez inclure des exemples pour montrer aux utilisateurs de votre
librairie comment l'utiliser. Pas de problème, on sait compiler des exemples "à la main" en précisant à
l'édition de lien notre librairie.

bash:$ g++ -o ex1 ex1.cpp -L. -lmalib


bash:$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 2/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

bash:$ ./ex1

Mais au fait, comme disait un auteur célèbre "un code sans commentaire est comme un commentaire
sans code", donc on commente le code et on voudrait générer une documentation Doxygen pour avoir
de jolies pages de documentation facilement exploitables. Et puis imaginons qu'on ait envie d'ajouter
des exemples à compiler séparément, faut-il vraiment tout se compiler à la main en se souvenant de
toutes ces syntaxes ? Et si jamais seul le fichier d'exemple a changé, on sait qu'on a besoin de
recompiler uniquement le binaire associé. D'accord, mais si vous êtes plusieurs à travailler sur le projet
comment savoir ce qui doit être recompilé au regard de ce qui a changé ? Une première réponse qui
n'est pas complètement satisfaisante est d'utiliser des Makefile. Les Makefiles vous permettent
d'automatiser en partie la compilation, s'assurant en particulier de ne recompiler que ce qui est
nécessaire et fournissant également des règles de compilation génériques applicables à plusieurs cibles.
Make qui utilise les fichiers de configuration Makefile est un moteur de production. Néanmoins la
syntaxe des Makefiles reste un peu lourde (croyez moi). On lui préfère parfois soit des alternatives
(comme scons, distutils, ant), soit des surcouches à Make qui vont générer des Makefiles à partir de
quelques fichiers de configuration plus simples à écrire que les Makefile. C'est ce deuxième aspect
qu'on va voir dans ce TL.

On va en particulier s'intéresser à CMake qui présente plusieurs avantages par rapport à d'autres
générateurs de Makefiles (comme les autotools) :

on compile généralement "en dehors" du répertoire source et on laisse donc le répertoire source
intact
on utilise une seule syntaxe relativement simple
l'outil est cross-plateform, il tourne aussi bien sous Linux, MacOS, Windows

Pour utiliser CMake, on doit :

définir des fichiers CMakeLists.txt à placer dans le répertoire source


sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 3/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

faire appel à cmake pour qu'il génère les makefiles (et éventuellement d'autres formats, on en
reparlera)
faire appel à make pour compiler le projet

L'objectif de ce TL est qu'à la fin vous sachiez comment :

créer un projet cmake qui compile et qui s'installe


générer des librairies et des exécutables
générer et installer des fichiers pkg-config avec les bons pointeurs vers les chemins des librairies et
fichiers d'entête
compiler une documentation Doxygen et l'installer
définir et exécuter des tests unitaires
générer une archive tar.gz pour votre projet

Principe général

Le principe d’une compilation à l’aide de CMake est d’écrire des fichiers CMakeLists.txt dans tous les
répertoires qui contiennent du code, documentation, .. à compiler/installer. Appelons ROOT le répertoire
racine du projet. La coutume est alors de créer un sous-répertoire build, dans lequel on va compiler
notre package puis d’y appeler cmake. On dit alors qu'on construit le projet "out-of-source", tout les
fichiers temporaires seront dans le sous-répertoire ROOT/build qu'on pourra supprimer si besoin sans

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 4/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

affecter notre projet. cmake va alors créer des fichiers de compilation dépendants de l’architecture/OS.
CMake peut générer différents fichiers projets ou de compilation :

Makefile pour Unix


Projets Codeblocks, Eclipse, Visual Studio, KDevelop, ..
et bien d'autres (voir cmake generators)

Pour générer des makefiles Unix, on tapera par exemple:

bash:~$ cd ROOT
bash:~/ROOT$ mkdir build
bash:~/ROOT$ cd build
bash:~/ROOT/build/$ cmake .. -G"Unix Makefiles"
// Ou plus simplement sous Linux
bash:~/ROOT/build/$ cmake ..
bash:~/ROOT/build/$ make
// et pour l'installation
bash:~/ROOT/build/$ make install

Cmake est multi-plateforme et présente l'avantage de n'utiliser qu'une seule syntaxe pour les
CMakeLists.txt par rapport aux autotools pour lesquels la syntaxe peut être un peu obscure.

Ex1 : Construire et installer un exécutable

Fichiers de configuration

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 5/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

On souhaite compiler un exécutable. Définissez-vous l'arborescence suivante:

Ex1
|----- CMakeLists.txt
|----- src
|---- CMakeLists.txt
|---- main.cpp
|----- build

On se définit le fichier source suivant:

Fichier Ex1/src/main.cpp
#include <iostream>

int main(int argc, char * argv[]) {


std::cout << "! dlrow olleH" << std::endl;

Il nous reste à définir les fichiers CMakeLists.txt. Le fichier à la racine s'assure que la version de cmake
est suffisamment récente, définit le nom du projet (pas nécessaire pour les Makefile Unix mais
nécessaires pour générer, par exemple des fichiers de projet Eclipse, CodeBlocks, ..) indique de
compiler en release (en incluant les options d'optimisation du compilateur) et indique le sous-répertoire
dans lequel il y a quelque chose à faire (en l'occurrence compiler notre exécutable).

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 6/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Fichier Ex1/CMakeLists.txt

cmake_minimum_required( VERSION 2.8 )


set(CMAKE_BUILD_TYPE Release)
project (ex1)
add_subdirectory(src)

Si vous vouliez compiler en mode Debug, il suffirait d'indiquer set(CMAKE_BUILD_TYPE Debug).

Dans le sous-répertoire src/, on indique un exécutable à construire, son nom et les fichiers sources
nécessaires pour le compiler en appelant add_executable.

Fichier Ex1/src/CMakeLists.txt : début

add_executable (mainDemo main.cpp)

La commande install permet d'installer des fichiers comme par exemple notre exécutable une fois
compilé. Il existe en fait plusieurs commandes install en fonction des arguments qui lui sont passés :
"install(TARGETS ...)", "install(FILES ...)", "install(PROGRAMS ...)", "install(DIRECTORY ...)". Pour
installer notre exécutable, nous allons utiliser "install(PROGRAMS ...)" qui donne les droits d'exécution
au fichier installé. La commande prends également en argument la destination qui doit se comprendre
comme relative au chemin définit par la variable CMAKE_INSTALL_PREFIX qui vaut par défaut
/usr/local. On va voir un peu plus loin comment le changer.

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 7/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Fichier Ex1/src/CMakeLists.txt (suite)

add_executable (mainDemo main.cpp)

install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/mainDemo
DESTINATION bin
RENAME ${CMAKE_PROJECT_NAME}-mainDemo)

Vous voyez également dans la commande ci-dessus apparaître la variable


CMAKE_CURRENT_BINARY_DIR qui fait référence, dans notre cas, au chemin Ex1/build/src/. Il existe
d'autres variables de ce type qu'on utilisera un peu plus tard mais que je vous donne tout de suite :

CMAKE_BINARY_DIR : dans notre example Ex1/build


CMAKE_SOURCE_DIR : dans notre example Ex1/
CMAKE_CURRENT_BINARY_DIR : ça dépend du CMakeLists qui contient cette variable. Si c'est
Ex1/src/CMakeLists.txt, alors CMAKE_CURRENT_BINARY_DIR=Ex1/build/src
CMAKE_CURRENT_SOURCE_DIR : ça dépend du CMakeLists qui contient cette variable. Si c'est
Ex1/src/CMakeLists.txt, alors CMAKE_CURRENT_SOURCE_DIR=Ex1/src

Compilation et installation

Pour compiler le projet, il suffit de se rendre dans le répertoire build puis de générer des makefiles en
invoquant cmake et enfin de compiler le projet:

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 8/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

cd Ex1/build
cmake ..
make
./src/mainDemo

Si vous voulez voir les commandes de compilation utilisées, appelez make VERBOSE=true.
Pour l'installation, tentons

make install

Oups, l'installation ne fonctionne pas. Vous devriez voir dans le message d'erreur que cmake essayer
d'installer votre cible dans /usr/local/, chemin auquel vous n'avez pas en principe droit d'écriture. Si
c'est malgré tout l'endroit ou vous souhaitiez installer votre cible, il faudrait invoquer la commande
"sudo make install". Mais imaginons qu'on souhaite installer nos cibles dans "MONCHEMIN/..". On
invoquerait alors cmake ainsi :

cmake .. -DCMAKE_INSTALL_PREFIX=MONCHEMIN
make install

Ex2 : Construire et installer une librairie dynamique

Mise en place du projet

Pour construire une librairie, le principe est similaire à la construction d’un binaire sauf qu’on fait appel
à la commande add_library au lieu de add_executable. On considère l’arborescence suivante :

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 9/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Ex2
|----- CMakeLists.txt
|----- src
|---- CMakeLists.txt
|---- fonct.cpp
|---- fonct.hpp
|---- monct.cpp
|---- monct.hpp
|----- examples
|---- CMakeLists.txt
|---- ex1.cpp
|---- ex2.cpp
|----- build

On se donne les sources suivantes:

Fichier Ex2/src/fonct.hpp
#pragma once

int f(int x);

Fichier Ex2/src/fonct.cpp
#include "fonct.hpp"

int f(int x) {
return 3 * x;
}

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 10/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Fichier Ex2/src/monct.hpp
#pragma once

double m(double x);

Fichier Ex2/src/monct.cpp
#include "monct.hpp"
#include <math.h>

double m(double x) {
return sqrt(x);
}

Fichier Ex2/examples/ex1.cpp
#include <iostream>
#include <cassert>
#include "fonct.hpp"

int main(int argc, char** argv) {


std::cout << "f(3) = " << f(3) << std::endl;
assert(f(3) == 9);
return 0;
}

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 11/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Fichier Ex2/examples/ex2.cpp
#include <iostream>
#include <cassert>
#include "monct.hpp"

int main(int argc, char** argv) {


std::cout << "m(9.) = " << m(9.) << std::endl;
assert(m(9.) == 3.);
return 0;
}

Fichiers de configuration

A la racine du projet, le fichier CMakeLists est comme le précédent, en ajoutant le sous-répertoire


examples

Fichier Ex2/CMakeLists.txt

cmake_minimum_required(VERSION 2.8)

set(CMAKE_BUILD_TYPE Release)

project(ex2)

add_subdirectory(src)
add_subdirectory(examples)

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 12/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Le fichier src/CMakeLists.txt introduit la syntaxer pour compiler une librairie partagée (dynamique). La
deuxième partie du fichier introduit une nouvelle commande, la commande file que nous utilisons ici
pour collecter tout les fichiers hpp. Pour le coup, on peut ajouter des hpp dans le répertoire, pas besoin
de toucher au CMakeLists.txt pour qu'ils soient pris en compte. On ne pourrait pas faire de même
pour définir les sources permettant de compiler la librairie ?

Fichier Ex2/src/CMakeLists.txt

add_library(toto
SHARED
fonct.cpp monct.cpp)

install (TARGETS toto


DESTINATION lib)

file(
GLOB
headers
*.hpp
)

install(FILES ${headers}
DESTINATION include/${CMAKE_PROJECT_NAME})

Il nous reste un dernier CMakeLists à définir, celui du répertoire Ex2/examples. Je vous le montre et on
le commente ensuite.

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 13/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Fichier Ex2/examples/CMakeLists.txt

include_directories (${CMAKE_SOURCE_DIR}/src)

file(
GLOB
usage_examples
*.cpp
)

foreach(f ${usage_examples})
get_filename_component(exampleName ${f} NAME_WE)
add_executable (${exampleName} ${f})
target_link_libraries(${exampleName} toto)
install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${exampleName}
DESTINATION bin
RENAME ${CMAKE_PROJECT_NAME}-${exampleName})
endforeach(f)

Alors, on voit plusieurs choses:

Le include_directories permet d'ajouter le chemin ${CMAKE_SOURCE_DIR}/src dans les chemins de


compilation où trouver des .hpp et cet include sera considéré pour toutes les cibles du
CMakeLists courant. Je vous rappelle qu'ici ${CMAKE_SOURCE_DIR}/src = Ex2/src,,
la commande file permet de collecter tout les fichiers qui finissent par .cpp et on stocke cette liste
dans la variable usage_examples, l'idée étant ensuite d'itérer sur cette liste pour compiler chaque
example,
on boucle sur les éléments d'une liste avec la commande foreach, l'itérateur étant ici f
pour définir le nom de l'exécutable, il faut enlever l'extension de f, le résultat étant stocké dans
exampleName; NAME_WE signifie "Name Without Extension", voir get_filename_component,

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 14/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

on définit ensuite l'exécutable comme précédemment


comme l'exécutable utilise des fonctions définies dans la librairie toto, il faut linker l'exécutable
avec la librairie, ce qui est fait grâce à target_link_libraries
enfin l'example compilé est installé et renommé à la volée

Ajouter des FLAGS de compilation/linkage

Que vous compiliez un exécutable ou une librairie, vous pouvez avoir besoin d'ajouter des flags de
compilation/linkage à votre cible. Nous verrons dans la prochaine partie comment récupérer des flags
de compilation/linkage lorsque votre cible a des dépendances externes (e.g. écrire une interface
graphique GTK, QT, ..) et que nous pouvons récupérer grâce à l'outil pkg-config ou à la macro CMake
find_package. Qu'en est-il si vous souhaitez ajouter des flags propres à votre projet ? Les flags à ajouter
sont de deux natures :

les flags de compilation :


les flags de compilation tels que les répertoires non-standards dans lesquels rechercher des
headers : -I/chemin/vers/les/headers
les flags qui sont des options passées au compilateur, tels que les flags d'optimisation, les flags
de diagnostic, les flags de debug, les flags pour le préprocesseur e.g. -O3 -Wall -Wno-pedantic -
std=c++11 ...
les flags pour l'édition de lien :
les librairies avec lesquelles linker votre cible (e.g. -lmalib) ou les options passés à l'éditeur de
liens (e.g. -Wl,--no-as-needed)Link options
les flags pour indiquer des répertoires : -L/chemin/vers/des/libs

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 15/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Pour modifier ces différents flags, on peut utiliser les commandes cmake ci-dessous, appliquées à
"macible" construite par add_executable ou add_library :

target_include_directories(macible PUBLIC /chemin/vers/les/headers;chemin2;chemin3)


Se réécrit dans la ligne de compilation en :"-I/chemin/vers/les/headers -Ichemin2 -Ichemin3"
target_compile_options(macible PUBLIC -std=c++11 -Wall -Wno-pedantic)
target_link_libraries(macible "-Wl,--allow-multiple-definition;/chemin/vers/libtruc.so")

Pour voir le chemin de compilation/linkage utilisé à la compilation, vous pouvez invoquer make
VERBOSE=true. Vous pouvez aussi inspecter et modifier les variables utilisées pour construire votre cible
:

get_target_property(leincludedir mainDemo INCLUDE_DIRECTORIES)


MESSAGE("Le INCLUDE_DIRECTORIES de mainDemo est ${leincludedir}")
set_property(TARGET mainDemo PROPERTY INCLUDE_DIRECTORIES ${foo_INCLUDE_DIRS})

Notez que certaines options sont déjà passées par cmake lors de l'appel de certaines macros :

set(CMAKE_BUILD_TYPE Release) -> -O3 -DNDEBUG


set(CMAKE_BUILD_TYPE Debug) -> -g
add_library(... SHARED ...) -> -shared

Ex3 : Gérer des dépendances

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 16/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Pour le moment, nous avons construit des exécutables et des librairies qui vivent dans leur monde et
n'ont pas besoin de fonctions définies par des librairies non standards (autres que libc, libstdc++ par
example). On pourrait les spécifier à la main dans cmake (il y a des variables pour les CFLAGS et
LDFLAGS) mais ça ne serait pas élégant et pas efficace. Regardez par exemple ce qu'il faut préciser
quand on compile un exécutable utilisant gtk+2.0 :

pkg-config --libs --cflags gtk+-2.0


==>
-pthread -I/usr/include/gtk-2.0 -I/usr/lib/i386-linux-gnu/gtk-2.0/include -I/usr/include/atk-1.0
-I/usr/include/cairo -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/pango-1.0 -I/usr/include/gio-unix-2.0/
-I/usr/include/freetype2 -I/usr/include/glib-2.0 -I/usr/lib/i386-linux-gnu/glib-2.0/include
-I/usr/include/pixman-1 -I/usr/include/libpng12 -I/usr/include/harfbuzz -lgtk-x11-2.0 -lgdk-x11-2.0
-latk-1.0 -lgio-2.0 -lpangoft2-1.0 -lpangocairo-1.0 -lgdk_pixbuf-2.0 -lcairo -lpango-1.0
-lfontconfig -lgobject-2.0 -lglib-2.0 -lfreetype

Il y a au moins deux façons d'indiquer à cmake de chercher des librairies non standards et de les inclure
dans les chemins de compilation.

find_package

La première façon de faire est d'utiliser la commande find_package. Son utilisation est assez simple, on
insère dans un CMakeLists.txt une ligne du style : find_package(MON_MODULE REQUIRED) . Le mot
REQUIRED est optionnel mais stoppe la compilation si le package n’est pas trouvé. find_package fait
appel à des fichiers fournit lors de l’installation de CMake qui permette de trouver un certain nombre de
packages standards. Pour savoir lesquels, faites :

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 17/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

cmake --help-module-list

En invoquant find_package(toto) par example, cmake va chercher un script FindToto.cmake; Ces scripts
sont par défauts dans /usr/share/cmake-2.8/Modules/. On trouve par example FindGTK2, FindQT,
FindBoost, ...; En passant, vous pouvez créer vos propres scripts pour rechercher des modules (voir ici).
Prenons un example plus concret en supposant que vous ayiez besoin de la librairie gtk2.0, on écrirait
alors dans notre CMakeLists.txt :

find_package(GTK2 REQUIRED)

Si vous regardez le script /usr/share/cmake-2.8/Modules/FindGTK2.cmake, vous verrez qu'il définit un


certain nombre de variable, notamment GTK2_FOUND, GTK2_INCLUDE_DIRS, GTK2_LIBRARIES. Les deux
dernières vont nous permettre d'indiquer à cmake où chercher les entêtes pour compiler un exécutable
dépendant de gtk2 et les librairies à utiliser pour l'édition de liens.
Il reste maintenant à ajouter ces dépendances à une cible. Supposons par exemple qu'on souhaite
compiler le fichier toto.cpp qui dépend de GTK2. Il faut indiquer à CMake comment :

ajouter les chemins vers les entêtes de GTK2


linker toto.o avec les librairies fournies par GTK2
éventuellement ajouter des cflags

Fichier Ex3/src/CMakeLists.txt avec find_package

# Trouver un package avec find_package


find_package(GTK2 REQUIRED)

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 18/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

# On construit notre binaire


add_executable (mainDemo main.cpp)
target_link_libraries (mainDemo ${GTK2_LIBRARIES})
target_include_directories(mainDemo PUBLIC ${GTK2_INCLUDE_DIRS})

Dans cet exemple, on voit apparaître les commandes :

target_link_libraries : permet d'ajouter des librairies dans le chemin de compilation d'une cible (les
-lxxxx)
target_include_directories : permet d'ajouter des chemins vers des entêtes d'une cible (les -Ixxxx)

pkg_check_modules

La deuxième façon consiste à utiliser pkg-config. Pour pouvoir utiliser pkg-config, il faut l'inclure dans
CMake et mettre dans le CMakeLists et l'invoquer:

find_package(PkgConfig)
pkg_check_modules(GTK2 gtk+-2.0 REQUIRED)

Comme pour find_package, on a ensuite accès aux variables GTK2_INCLUDE_DIRS, GTK2_LIBRARIES plus
quelques autres, notamment GTK2_CFLAGS_OTHER. Vous pouvez regarder la doc de pkg_ckeck_modules
en ouvrant le fichier /usr/share/cmake-2.8/Modules/FindPkgConfig.cmake. Il reste maintenant à
ajouter ces dépendances à une cible. Supposons par exemple qu'on souhaite compiler le fichier toto.cpp
qui dépend de GTK2. Il faut indiquer à CMake comment :

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 19/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

ajouter les chemins vers les entêtes de GTK2


linker toto.o avec les librairies fournies par GTK2
éventuellement ajouter des cflags

Fichier Ex3/src/CMakeLists.txt avec pkg_check_modules

# Trouver un package avec pkg-config


find_package(PkgConfig)
pkg_check_modules(GTK2 gtk+-2.0 REQUIRED)

# On construit notre binaire


add_executable (mainDemo main.cpp)
target_link_libraries (mainDemo ${GTK2_LIBRARIES})
target_compile_options(mainDemo PUBLIC ${GTK2_CFLAGS_OTHER})
target_include_directories(mainDemo PUBLIC ${GTK2_INCLUDE_DIRS})

Dans cet exemple, on voit apparaître les commandes :

target_link_libraries : permet d'ajouter des librairies dans le chemin de compilation d'une cible (les
-lxxx)
target_include_directories : permet d'ajouter des chemins vers des entêtes d'une cible (les -Ixxxx)
target_compile_options : permet d'ajouter des "cflags" comme -std=c++11 à une cible

Il y a également la possibilité de passer des variables pour le préprocesseur avec


target_compile_definitions. Par exemple, target_compile_definitions(mainDemo PUBLIC TOTO) ajoute

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 20/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

au chemin de compilation de main.cpp -DTOTO.

Modifier directement la ligne de compilation/linkage d'une cible ou plus


généralement comment accéder aux propriétés d'une cible?

Lorsqu'on invoque la commande include_directories, elle s'applique à toutes les cibles du


CMakeLists courant. Lorsqu'on invoque les commandes target_xxxx, elle ne s'applique qu'à une cible.
Dans tous les cas, elles modifient la ligne de production d'une cible en changeant la valeur de propriétés
de la cible. On peut accéder en lecture ou écriture aux propriétés d'une cible (définies dans la
documentation) grâce aux commandes get_target_property et set_property. Par exemple, pour lire la
propriété INCLUDE_DIRECTORIES de la cible mainDemo :

get_target_property(leincludedir mainDemo INCLUDE_DIRECTORIES)


MESSAGE("Le INCLUDE_DIRECTORIES de mainDemo est ${leincludedir}")
set_property(TARGET mainDemo PROPERTY INCLUDE_DIRECTORIES ${foo_INCLUDE_DIRS})

Notez que le set_property ainsi écrit écrase la propriété. Si vous souhaitez ajouter un élément à la
propriété, vous devez ajouter APPEND_STRING aux arguments de set_property.

Un example

On se construit un example avec l'arborescence suivante :

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 21/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Ex3
|----- CMakeLists.txt
|----- src
|---- CMakeLists.txt
|---- main.cpp
|----- build

Recopiez le fichier main.cpp.


Le fichier CMakeLists.txt à la racine est standard:

Fichier Ex3/CMakeLists.txt

CMAKE_MINIMUM_REQUIRED( VERSION 2.8 )

project(ex3)

add_subdirectory (src)

Le fichier src/CMakeLists quand à lui fait appel à find_package ou pkg_check_modules, au choix:

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 22/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Fichier Ex3/src/CMakeLists.txt

# # Trouver un package avec find_package


# find_package(GTK2 REQUIRED)

# # On construit notre binaire


# add_executable (mainDemo main.cpp)
# target_link_libraries (mainDemo ${GTK2_LIBRARIES})
# target_include_directories(mainDemo PUBLIC ${GTK2_INCLUDE_DIRS})

# Trouver un package avec pkg-config


find_package(PkgConfig)
pkg_check_modules(GTK2 gtk+-2.0 REQUIRED)

# On construit notre binaire


add_executable (mainDemo main.cpp)
target_link_libraries (mainDemo ${GTK2_LIBRARIES})
target_compile_options(mainDemo PUBLIC ${GTK2_CFLAGS_OTHER} -std=c++11)
target_include_directories(mainDemo PUBLIC ${GTK2_INCLUDE_DIRS})

Ex4 : Rendre mon projet pkg-config compatible

Pour qu'un utilisateur puisse utiliser votre librairie facilement, en particulier en utilisant pkg-config, il
faut fournir et installer un fichier ".pc". On peut utiliser deux approches : la première consiste à écrire
le fichier pc depuis cmake, la seconde consiste à se définir un template et à le remplir depuis cmake.
Tout d'abord, parlons un peu de la structure d'un fichier pc (vous en trouverez de nombreux exemples
dans /usr/lib/pkgconfig ou /usr/lib/i386-linux-gnu/pkgconfig/ sur 32 bits) en regardant
"gtk+-2.0.pc":

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 23/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

prefix=/usr
exec_prefix=${prefix}
libdir=/usr/lib/i386-linux-gnu
includedir=${prefix}/include
target=x11

gtk_binary_version=2.10.0
gtk_host=i686-pc-linux-gnu

Name: GTK+
Description: GTK+ Graphical UI Library (${target} target)
Version: 2.24.23
Requires: gdk-${target}-2.0 atk cairo gdk-pixbuf-2.0 gio-2.0 pangoft2
Libs: -L${libdir} -lgtk-${target}-2.0
Cflags: -I${includedir}/gtk-2.0

Au début du fichier, on trouve la définition de quelques variables, le contenu vraiment intéressant étant
la dernière partie, celle à laquelle on accède quand on invoque pkg-config en lui demandant par
exemple les libs ou les cflags. pkg-config va non seulement utiliser les libs et cflags qu'introduisent ce
fichier pc mais également les libs et cflags des dépendances (champ Requires).

Ecrire un fichier pc depuis CMakeLists

Pour écrire un fichier depuis CMakeLists, il suffit d'invoquer la commande file avec la signature WRITE.
On peut faire apparaître dans la chaîne de caractères à écrire des variables de cmake, par exemple :

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 24/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}.pc
"
Name: ${CMAKE_PROJECT_NAME}
Description: C'est une super librairie qui utilise gtk2
Version: 0.0.1
Requires: gtk2
Libs: -L${CMAKE_INSTALL_PREFIX}/lib
Cflags: -I${CMAKE_INSTALL_PREIFX}/include
"
)

install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}.pc
DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig)

Configurer un fichier template

La deuxième façon de faire est de se définir un fichier dans lequel il reste des variables à substituer.
Pourquoi ? et bien parce que le contenu du fichier pkg-config va dépendre, par exemple, du préfixe
d'installation choisi par l'utilisateur. Cette fois ci on va se définir un fichier ex3.pc.in, qu'on placerait à la
racine du projet, avec des variables CMake entourées par des "@":

Fichier Ex3/ex3.pc.in
sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 25/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Name: @CMAKE_PROJECT_NAME@
Description: C'est une super librairie qui utilise gtk2
Version: 0.0.1
Requires: gtk2
Libs: -L@CMAKE_INSTALL_PREFIX@/lib
Cflags: -I@CMAKE_INSTALL_PREIFX@/include

Dans le CMakeLists, on fait appel à la commande configure_file :

CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/ex3.pc.in
${CMAKE_CURRENT_BINARY_DIR}/ex3.pc @ONLY)
install (FILES ${CMAKE_CURRENT_BINARY_DIR}/ex3.pc
DESTINATION lib/pkgconfig/)

Ex5 : Et la documentation ?

Pour générer la documentation, nous allons utiliser Doxygen. On va partir sur un projet dont la
hiérarchie est la suivante (on reprends ex2) :

Ex5
|----- CMakeLists.txt
|----- src
|---- CMakeLists.txt
|---- fonct.cpp

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 26/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

|---- fonct.hpp
|---- monct.cpp
|---- monct.hpp
|----- doc
|---- CMakeLists.txt
|---- Doxyfile.in
|----- examples
|---- CMakeLists.txt
|---- ex1.cpp
|----- build

Pour générer le fichier Doxyfile.in, il suffit de le demander à Doxygen :

doxygen -g Doxyfile.in

Au sein du fichier Doxyfile.in, il faut modifier quelques variables pour que ce fichier soit configuré par
cmake :

PROJECT_NAME = ${CMAKE_PROJECT_NAME}
OUTPUT_DIRECTORY = ${CMAKE_BINARY_DIR}/doc/
INPUT = ${CMAKE_SOURCE_DIR}/src
EXAMPLE_PATH = ${CMAKE_SOURCE_DIR}/examples
EXTRACT_ALL = YES

Il nous reste maintenant à indiquer à cmake de générer la documentation. Première chose, on ajoute le
sous-répertoire doc au CMakeLists.txt principal :

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 27/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Fichier Ex5/CMakeLists.txt

cmake_minimum_required(VERSION 2.8)

set(CMAKE_BUILD_TYPE Release)

project(ex5)

add_subdirectory(src)
add_subdirectory(examples)
add_subdirectory(doc)

Ensuite, pour le fichier doc/CMakeLists.txt, on peut le définir comme suit :

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 28/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Fichier Ex5/doc/CMakeLists.txt

find_package(Doxygen)
if(NOT DOXYGEN_FOUND)
message("Doxygen not found, I will not generate/install the documentation")
else()
configure_file(Doxyfile.in Doxyfile)

set(DOXYGEN_INPUT ${CMAKE_BINARY_DIR}/doc/Doxyfile)
set(DOXYGEN_OUTPUT ${APIDOC_DIR}/html/index.html)

add_custom_target(doc ALL
COMMAND ${CMAKE_COMMAND} -E echo_append "Building API Documentation..."
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_INPUT} > /dev/null
COMMAND ${CMAKE_COMMAND} -E echo "Done."
)

install(DIRECTORY ${CMAKE_BINARY_DIR}/doc/html
DESTINATION share/doc/${CMAKE_PROJECT_NAME})

endif()

Commentons un peu ce fichier :

la première ligne fait appel à find_package pour trouver Doxygen. Incidemment, cette commande
définit la variable DOXYGEN_FOUND qui permet de générer ou non la documentation si
l'exécutable est présent
configure_file remplace les variables cmake dans le fichier Doxyfile.in; Par défaut un chemin relatif
pour le fichier d'entrée (Doxyfile.in) est à comprendre relativement au répertoire source courant
(CMAKE_CURRENT_SOURCE_DIR) et le chemin relatif du fichier destination (Doxyfile) est à
comprendre relativement au répertoire binaire courant (CMAKE_CURRENT_BINARY_DIR)

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 29/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

enfin, après quelques définitions de variables pour rendre plus lisible le code, on se définit la cible
doc à l'aide de add_custom_target. On liste l'ensemble des commandes à exécuter lors de l'appel de
make doc. Le mot clé ALL indique que la cible doc sera construite sans avoir à l'expliciter, i.e. quand
on tape make tout court.

Ex5b : Elle est moche la doc, je veux un example qui en jette un peu
plus.

Work in progress .... Dans cette partie, vous n'avez rien de particulier à faire à part compiler le projet.
J'espère vous convaincre qu'on peut faire une jolie documentation avec Doxygen. On va partir d'un code
C++ que vous avez vu en cours et qui nous donnera une bonne base pour générer une belle
documentation. J'ai un tout petit peu modifié le code pour faire apparaître des balises Doxygen.

Ex6 : Tests unitaires

Cmake permet d'intégrer des tests unitaires à votre projet. Les tests unitaires sont des tests qui
s'assurent de la stabilité de certaines fonctionnalités fournies par votre projet. Il existe plusieurs
frameworks permettant de définir des tests unitaires, on va ici utiliser celui de Boost avec des exemples
extrêmement simples, le but étant de montrer l'intégration dans cmake. On va partir de l'arborescence
suivante :

Ex6
|----- CMakeLists.txt
|----- src
|---- CMakeLists.txt
|---- fonct.cpp

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 30/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

|---- fonct.hpp
|---- monct.cpp
|---- monct.hpp
|----- doc
|---- CMakeLists.txt
|---- Doxyfile.in
|----- tests
|---- CMakeLists.txt
|---- test_lib.cpp
|----- examples
|---- CMakeLists.txt
|---- ex1.cpp
|----- build

Le fichier tests/test_lib.cpp est définit comme suit :

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 31/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Fichier Ex6/tests/test_lib.cpp
#define BOOST_TEST_MODULE TotoTests

#include "fonct.hpp"
#include "monct.hpp"
#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_CASE(TestFonct)
{
BOOST_CHECK_EQUAL(6, f(2));
}

BOOST_AUTO_TEST_CASE(TestMonct)
{
BOOST_CHECK_EQUAL(2, m(4));
}

Le fichier de configuration tests/CMakeLists va s'assurer de la présence des librairies boost et


compiler/linker notre test unitaire :

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 32/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Fichier Ex6/tests/CMakeLists.txt

find_package (Boost COMPONENTS system filesystem unit_test_framework REQUIRED)

include_directories (${CMAKE_SOURCE_DIR}/src
${Boost_INCLUDE_DIRS}
)

add_definitions (-DBOOST_TEST_DYN_LINK)

add_executable (test_mylib test_lib.cpp)


target_link_libraries(test_mylib
toto
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY}
${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}
)

Il nous reste maintenant à modifier le CMakeLists à la racine.

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 33/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Fichier Ex6/CMakeLists.txt

cmake_minimum_required(VERSION 2.8)

set(CMAKE_BUILD_TYPE Release)

project(ex6)

add_subdirectory(src)
add_subdirectory(examples)
add_subdirectory(doc)

add_subdirectory(tests)
enable_testing ()
add_test (NAME MyTest
COMMAND test_mylib
)

Dans ce script on fait plusieurs choses :

On ajoute le sous-répertoire tests pour compiler le test unitaire


on indique à cmake qu'on veut autoriser les tests unitaires enable_testing(), sans quoi la
commande add_test serait sans effet
on ajoute un test unitaire

On peut ensuite lancer le test unitaire en invoquant :

bash$: make test


Running tests...
Test project /home/fix_jer/TutoCMAKE/Codes/Ex6/build

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 34/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Start 1: MyTest
1/1 Test #1: MyTest ........................... Passed 0.00 sec

100% tests passed, 0 tests failed out of 1

Total Test time (real) = 0.01 sec

Modifiez votre code ou vos tests unitaires pour faire échouer le test unitaire. Vous remarquerez que
make test n'est pas très verbeux et que si il y a un échec, on ne sait pas quel test a échoué. Pour que la
sortie soit verbeuse, il suffit d'exécuter CTEST_OUTPUT_ON_FAILURE=TRUE make test.

Ex7 : Créer une archive tar.gz

La dernière commande que j'aimerais introduire est une commande permettant de facilement générer
une archive .tar.gz en créant la cible make dist. Pour cela, il suffit d'ajouter au CMakeLists de la racine
un appel à add_custom_target :

SET(DIST_DIR "${CMAKE_PROJECT_NAME}")
ADD_CUSTOM_TARGET(dist
COMMAND rm -rf ${DIST_DIR}
COMMAND mkdir ${DIST_DIR}
COMMAND cp -r ${CMAKE_SOURCE_DIR}/* ${DIST_DIR} || true
COMMAND rm -rf ${DIST_DIR}/build
COMMAND mkdir ${DIST_DIR}/build
COMMAND tar --exclude="*~" --exclude="._*" -zcvf ${DIST_DIR}.tar.gz ${DIST_DIR}
COMMAND rm -rf ${DIST_DIR}

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 35/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

WORKING_DIRECTORY ${CMAKE_BINARY_DIR})

La commande est définie pour faire une copie des sources dans le répertoire
build/${CMAKE_PROJECT_NAME}. On s'assure que le répertoire build existe et soit vide puis on
invoque tar pour créer l'archive.

Tout ensemble : à vous de packager

Pour finir, je vous propose un ensemble de fichiers C++ en vrac et je vous demande de construire un
package cmake tout ficelé. L'archive ExEnsemble.tar.gz contient:

les fichiers drawing.cpp, drawing.hpp, gtkmmExample.hpp pour compiler une librairie


les fichiers example-000.cpp, example-001.cpp qui sont des exemples d'utilisation de la librairie
la librairie dépends de la librairie non standards gtkmm-3.0

J'aimerais que le package compile/installe la librairie, installe les fichiers d'entête, génère un fichier
pkg-config et l'installe, compile les exemples, la documentation. A vous de jouer !

Une solution.

Bonus : Compiler à plusieurs coeurs ? Priorité entre les cibles ? Mais


quelles sont les variables définies par cmake ? Comment débugger une
construction cmake qui ne passe pas comme prévue ? Construire des
packages deb, rpm, osx, windows installables ?

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 36/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

Si votre machine possède plusieurs coeurs, vous pouvez en profiter avantageusement à la phase de
compilation/linkage en appelant make -jn en remplaçant n par le nombre de processus à lancer; e.g.
sur une machine à 4 coeurs :

bash$: make -j4

Il peut être nécessaire d'indiquer des priorités de compilation entre les cibles, ce que cmake appelle des
dépendances. Si la cible target1 (issue d'un add_custom_target, add_executable, add_library) doit être
compilée avec la cible target2, on pourra l'indiquer à cmake en invoquant add_dependencies:

add_dependencies(target2 target1)

Nous n'avons pas eu à l'utiliser dans les exemples ci-dessous parce que target_link_libraries ajoute de
lui même une dépendance entre l'exécutable linké et la librairie compilée dans notre projet.

Je l'ai évoqué un peu plus haut, quand vous compilez votre code, n'hésitez pas à lancer

bash$: make VERBOSE=true

pour voir les commandes exécutées par cmake.

Pour débugger du cmake, on peut simplement afficher un message pendant que cmake génère les
Makefiles en invoquant la commande message

On peut également demander à cmake d'afficher les variables qu'il a défini à l'aide de la commande
get_cmake_property :

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 37/38
12/03/2023, 01:04 Tutoriel CMAKE, CentraleSupélec

get_cmake_property(_variableNames VARIABLES)
foreach (_variableName ${_variableNames})
message(STATUS "${_variableName}=${${_variableName}}")
endforeach()

Sachez qu'il est également possible de construire des paquets deb, rpm, osx, windows .. installables
grâce à cpack.

sirien.metz.supelec.fr/depot/SIR/TutorielCMake/index.html 38/38

Vous aimerez peut-être aussi