Présentation de CMake

CMake est un outil de build dans la même veine que ant ou encore make, sauf qu’il se place à plus haut niveau. Il a l’avantage d’être plus convivial à utiliser que make et est utilisé par de gros projets dont KDE depuis dix ans. On peut donc dire qu’il est devenu stable.

En fait un des gros avantages de CMake est qu’il ne permet pas que de générer des Makefile, mais aussi les fichiers de projets pour les principaux IDE tels que Code::Blocks ou encore Visual Studio pour ceux d’entre vous qui utilisent encore Microsoft Windows.

Dans la suite on va voir comment utiliser CMake pour un usage basique, ainsi que pour un usage plus avancé. On part également du principe que vous utilisez un vrai système même si CMake existe aussi sous Windows. A noter qu’ici on a construit nos exemples à partir de CMake 2.6 car de nombreuses entreprises sont sur des plateformes qui ont encore cette version, dont les RHEL 6.x où x est inférieur strictement à 6.

Utilisation basique de CMake

Tout comme ant ou make, CMake a ses propres fichiers pour le build. Chacun de ces fichiers est dénommé CMakeLists.txt.

Contrairement à make ou ant il n’est pas nécessaire d’avoir un fichier CMakeLists.txt par dossier. En fait vous pouvez parfaitement vous contenter d’un fichier à la racine, même si dès lors que vous devez compiler des sources il est fortement recommandé d’avoir un fichier CMakeLists.txt par dossier de sources.

Pour la suite, on va imaginer un projet dont le layout est le suivant sur votre gestionnaire de sources :


<root>
|-- scripts
|   `-- foobar.sh
`-- src
    |-- foo.h
    |-- bar.h
    |-- foo.c
    `-- bar.c

Sur le projet présenté ci-dessus, on aura typiquement un fichier CMakeLists.txt à la racine du projet, ainsi qu’un autre sous le dossier src/ qui contient les sources C à compiler.

Le fichier CMakeLists.txt de la racine du projet

Le fichier CMakeLists.txt contiendra typiquement des instructions sur comment « installer » le projet, autrement dit son chemin d’installation, et indiquera également les sous-dossiers à traiter. Son contenu pourrait être :


cmake_minimum_required(VERSION 2.6)

if(NOT MYDESTDIR)
   set(MYDESTDIR /foo/bar)
endif()

project(foo C)

install(DIRECTORIES scripts DESTINATION ${MYDESTDIR})
add_subdirectory(src)

La première ligne indique que pour que le script tourne, il faut au minimum CMake 2.6. Le bloc if() permet de définir la variable MYDESTDIR si et seulement si elle n’est pas déjà définie lorsqu’on lance une ligne de commande CMake.

La ligne project permet de spécifier le nom du projet, mais surtout son langage, ici du C. Par défaut CMake supporte le C, paramètre C, et le C++, paramètre CXX. Cependant il est parfaitement possible d’en utiliser d’autres.

La ligne install sera traitée lorsqu’on fera un make install, et dit d’installer le dossier scripts dans MYDESTDIR. A noter que si on avait écrit scripts/ avec un slash à la fin, ça aurait signifié qu’il fallait installer le contenu du dossier scripts dans MYDESTDIR.

Enfin la ligne add_subdirectory indique à CMake qu’il doit ensuite traiter le fichier CMakeLists.txt du sous-dossier src. Un truc important ici est que les variables définies dans un fichier CMakeLists.txt parent sont visibles au niveau des enfants.

Le fichier CMakeLists.txt du sous-dossier src

Dans le fichier CMakeLists.txt du sous-dossier src on voudra typiquement compiler des fichiers C. Son contenu pourrait être :


SET(MYHEADERS *.h)
SET(MYSOURCES *.c)

add_executable(foo ${MYHEADERS} ${MYSOURCES})
install(TARGETS foo DESTINATION ${MYDESTDIR}/bin)

Les deux premières lignes du fichier ne sont pas indispensables mais permettent de mettre dans des variables la liste des fichiers .h et la liste des fichiers .c.

La ligne add_executable permet de créer l’exécutable foo à partir des fichiers sources en paramètre. Cette commande crée aussi une cible au niveau du Makefile généré qui sera invoquée par d’autres tâches.

Enfin la commande install, qui est utilisée lors du make install, indique qu’il faut prendre le résultat de la cible foo et l’installer dans le sous-dossier bin de MYDESTDIR.

Lancement de CMake

Une fois tous ces fichiers créés, on va créer un sous-dossier build situé sous le dossier racine, en effet il ne s’agit pas de polluer nos sources. Placez-vous ensuite dans ce dossier build, puis tapez :

cmake ..

Si tout se passe bien vous devriez voir un Makefile apparaître, ainsi que tout un tas de fichiers qui n’ont finalement que peu d’importance pour nous. Ensuite vous pouvez compiler votre projet en tapant :

make

Si tout va bien vous verrez votre exécutable apparaître dans un sous-dossier appelé src.

Enfin pour finir de tester vous pouvez taper :

make install DESTDIR=.

Ici vous verrez une arborescence foo/bar apparaître sous votre dossier de build, avec l’exécutable généré et les scripts.

Génération d’autre chose que des Makefile avec CMake

Comme indiqué plus haut on peut notamment générer des fichiers de projets pour différents IDE avec CMake. Par exemple pour Code::Blocks la commande à taper est :

cmake -G "CodeBlocks - Unix Makefiles"

Vous trouverez la liste des formats possibles ici.

Plus loin avec CMake : création de votre propre bibliothèque de fonctions

Pour la suite on part du principe que vous utilisez un Linux RedHat ou CentOS. Les fichiers de modules CMake sont donc dans /usr/share/cmake/Modules. Vous devrez adapter ce chemin suivant votre propre installation.

Par ailleurs l’exemple donné fonctionne avec CMake 2.6. Les adaptations pour les versions ultérieures sont indiquées.

Il est parfaitement possible de créer votre propre bibliothèque de fonctions CMake. Pour ce faire, il suffit de placer un fichier avec l’extension .cmake dans le dossier de modules de CMake, dans notre cas /usr/share/cmake/modules. Si vous distribuez votre code pensez à distribuer également vos règles CMake utilisées pour le construire !

Partons de l’exemple ci-dessus. Imaginons qu’on n’ait plus envie de taper les lignes add_executable et install du fichier CMakeLists.txt du sous-dossier src/. On peut tout à fait créer une bibliothèque CMake pour ça. Pour ce faire, il suffit de créer un fichier nommé MyLibrary.cmake et de le placer dans le dossier de modules.

Le contenu de ce fichier serait quelque chose comme :


macro(build_my_exec)
cpack_parse_arguments(MYARG "TARGET;SOURCES;DESTINATION" "" ${ARGN})

    if(NOT MYARG_TARGET)
        message(FATAL_ERROR "You must specify the target parameter")
    endif()

    if(NOT MYARG_SOURCES)
        message(FATAL_ERROR "You must specify the sources parameter")
    endif()

    if(NOT MYARG_DESTINATION)
        message(FATAL_ERROR "You must specify the destination parameter")
    endif()

    add_executable(${MYARG_TARGET} ${MYARG_SOURCES})
    install(TARGETS ${MYARG_TARGET} DESTINATION ${MYARG_DESTINATION})

endmacro(build_my_exec)

La ligne macro() permet de déclarer la fonction. Rien de spécial à dire dessus.

La ligne en dessous, cpack_parse_arguments est bien plus intéressante. En fait elle permet de séparer les arguments passés à la fonction de telle sorte qu’on puisse l’invoquer avec un nombre arbitraire d’arguments par paramètre donné. Pour faire plus clair, si on a 5 fichiers sources on pourrait tout à fait invoquer notre fonction de la manière suivante :


build_my_exec(TARGET foo SOURCES 1.c 2.h 3.c 4.c 5.h DESTINATION /usr/bin)

Un tel mécanisme ne serait pas possible sans paramètre nommé. Par ailleurs comme vous pouvez le voir, les variables générées suivent le pattern PREFIX_NOMPARAM où PREFIX est le nom du premier argument passé à la fonction cpack_parse_arguments, et NOMPARAM correspond au nom du paramètre de la fonction.

Attention : cpack_parse_arguments n’est pas une fonction officielle de l’API CMake, par conséquent elle pourrait disparaître à tout moment. Dès lors si vous utilisez CMake 2.8 et plus, utilisez plutôt CMakeParseArguments, plus complet et qui surtout est une API officielle. L’utilisation de cette dernière commande est semblable à cpack_parse_arguments.

Vous n’êtes pas obligé d’utiliser une commande comme cpack_parse_arguments, vous pouvez parfaitement utiliser brutalement les arguments donnés en paramètres. Mais il faut reconnaître que c’est tout de même une bonne pratique de le faire dès lors que vous avez à faire une distinction entre les différents arguments passés à votre fonction.

Sur la suite il n’y a pas grand chose à dire, ce sont des sanity checks pour vérifier que les paramètres sont bien passés, et ensuite les lignes add_executable et install vues plus haut.

Utilisation de votre bibliothèque CMake

Une fois le fichier créé et proprement enregistré, on revient sur notre projet exemple. Dans le fichier CMakeLists.txt à la racine du projet, on déclare qu’on utilise notre librarie. Pour ce faire on donne la directive :

include(<MyLib>)

Cette commande indique à CMake qu’il faut utiliser les fonctions incluses dans le fichier /usr/share/cmake/modules/MyLib.cmake.

Dans notre cas le fichier deviendrait :


cmake_minimum_required(VERSION 2.6)

<strong>include(MyLibrary)</strong>

if(NOT MYDESTDIR)
   set(MYDESTDIR /foo/bar)
endif()

project(foo C)

install(DIRECTORIES scripts DESTINATION ${MYDESTDIR})
add_subdirectory(src)

Là où ça devient sympa, c’est au niveau du fichier CMakeLists.txt du sous-dossier src/. Ce dernier est réduit de beaucoup et devient :


SET(MYHEADERS *.h)
SET(MYSOURCES *.c)

build_my_exec(TARGET foo SOURCES ${MYHEADERS} ${MYSOURCES} DESTINATION ${MYDESTDIR}/bin)

Evidemment l’exemple donné est relativement trivial, mais dans la vraie vie on peut s’économiser pas mal de temps en créant ses propres librairies.

Le mot de la fin

CMake est un système de build supplémentaire mais qui a l’avantage d’être concis et plutôt sympa à lire. Il est actuellement bien utilisé dans de nombreux projets open source dont KDE, et tend globalement à remplacer petit à petit les autotools. On ne s’en plaindra pas, car là où il suffit d’apprendre un seul langage pour CMake, pour les autotools il y en avait 8.

Pour ceux qui veulent une référence voici un lien vers la documentation de CMake 2.6 ainsi qu’un lien vers la documentation de la dernière version à l’heure de l’écriture de ce billet, la 3.3..

Cet article vous a plu ? Vous aimerez sûrement aussi :

Julien
Moi c’est Julien, ingénieur en informatique avec quelques années d’expérience. Je suis tombé dans la marmite étant petit, mon père avait acheté un Apple – avant même ma naissance (oui ça date !). Et maintenant je me passionne essentiellement pour tout ce qui est du monde Java et du système, les OS open source en particulier.

Au quotidien, je suis devops, bref je fais du dév, je discute avec les opérationnels, et je fais du conseil auprès des clients.

Son Twitter Son LinkedIn

gojul

Recent Posts

MICI au travail : Le handicap invisible qui révèle des forces insoupçonnées

Les maladies inflammatoires chroniques de l’intestin ou "MICI" sont invisibles, mais leurs impacts sur la…

3 jours ago

Exploiter les NPUs pour de l’IA embarquée dans les applis webs

Depuis l'été, j'ai un Pixel qui intègre à la fois un TPU (Tensor Processing Unit)…

6 jours ago

Qcm saison hiver 2024 : toutes les infos.

On se retrouve dans un nouvel article avec toutes les infos sur cette nouvelle saison…

3 semaines ago

L’inclusion numérique est essentielle.

Pourquoi l’inclusion numérique est essentielle : le point avec Mathieu Froidure. Dans un monde de…

4 semaines ago

Communauté Tech et féminine : Interview avec Helvira de Motiv’her

Elles sont passées où les femmes dans la tech ? Entre le manque de représentation…

1 mois ago