Quelques trucs avec CMake
CMake, c’est bien, c’est puissant, c’est utilisé partout sur GitHub. Seulement, passés les tutoriels simples, CMake, c’est un mur pas vraiment évident à escalader. J’ai créé ce blog pour ne pas oublier les trucs et astuces qui pourraient me resservir, alors quoi de plus naturel que de faire un article avec quelques trucs sur CMake ? Parce qu’il y a certains trucs que j’ai mis très longtemps à trouver…
CMake est un outil qui évolue vite. En cherchant comment réaliser une action particulière, on trouve des tas de solutions différentes sur le net et beaucoup sont obsolètes. J’ai essayé de lister ici les techniques modernes mais malheureusement elles seront peut-être obsolètes dans quelques temps… D’ici là, enjoy : )

Choisir les versions des langages C et C++
La solution simple moderne est de définir les variables dédiées : CMAKE_C_STANDARD et CMAKE_CXX_STANDARD. Par exemple pour utiliser C++11 :
set(CMAKE_CXX_STANDARD 11)
Vous pouvez lire cet article pour plus de détails et de manière de choisir les versions des langages : Enabling C++11 And Later In CMake par Craig Scott.
Attention ! Par défaut, GCC compilera avec les extensions ! Si ce n’est pas ce que vos voulez, dites à CMake que vous en voulez pas d’extensions :
set(CMAKE_CXX_EXTENSIONS OFF)
Ajouter des options de compilations
La solution la plus simple et portable est d’utiliser add_compile_options(). Par exemple pour utiliser l’option -Wall :
add_compile_options(-Wall)
-Wall sera ajouté pour le C et le C++, pour tous les cibles, pour toutes les configurations.
Si vous ne souhaitez ajouter cette option qu’à une seule cible, il faut utiliser target_compile_options() à la place. Certains, comme Boost, pensent qu’on ne devrait utiliser que target_compile_options().
Si vous ne souhaitez que cette option que pour le C ou que pour C++, vous pourriez être tentés de modifier directement les variables CMAKE_lang_FLAGS_config, comme par exemple :
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -Wsuggest-override")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og")
Il est toutefois possible d’utiliser une generator expression pour faire ça (et c’est visiblement le style CMake moderne) :
# uniquement en C++ add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wsuggest-override>) # uniquement en debug add_compile_options($<$<CONFIG:Debug>:-Og>) # uniquement en C++ en debug add_compile_options($<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:Debug>>:-Og>)
NOTE : Chaque fois que vous devez modifier une variable interne de CMake, pensez à ajouter à sa valeur courante et non pas simplement à l’écraser (voir l’item 1 de cet article) ! Sauf si c’est vraiment ce que vous voulez faire…
NOTE : Chaque fois que vous ajoutez des options spécifiques à un compilateur, n’oubliez pas que vous casser la possible d’utiliser votre projet avec un autre compilateur. Si vous voulez continuer à supporter plusieurs compilateurs, il faudrait tester le compilateur choisi et s’adapter, soit avec des if() / endif() soit directement avec des generator expressions.
Spécifier le type de build
Vous avez un projet CMake et vous faites :
cmake -G "Unix Makefiles" .
Vous obtenez un jeu de makefiles répondant au type de build par défaut, mais lequel ? Debug ou Release ? Et bien, rien… Il n’y a pas de mode par défaut, ce qui signifie que la variable CMAKE_BUILD_TYPE est vide ! Cela implique que les variables comme CMAKE_CXX_FLAGS_DEBUG n’est pas utilisées, qu’une générateur expression comme $<CONFIG:Debug> sera égale à 0, etc. Cela signifie aussi que CMake n’ajoute pas automatiquement des options comme -g ou -O2 pour gcc.
N’oubliez donc pas de choisir !
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug . cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release .
Ajouter des options particulières à certains fichiers ou dossiers
Il est possible d’ajouter des options de compilations pour un fichier en particulier grâce à set_source_files_properties(). C’est très utile pour optimiser un fichier en particulier :
set_source_files_properties(chemin/vers/mon_fichier.cpp PROPERTIES COMPILE_FLAGS -O3)
Attention ! Il s’agit bien d’ajouter une option, pas de remplacer les options déjà appliquées. Si vous voulez par exemple ne plus avoir les warnings générés par -Wconversion, ajoutez l’option -Wno-conversion pour l’annuler.
Il est possible de passer une liste de fichiers et on peut ainsi ruser pour modifier un dossier :
file(GLOB_RECURSE MON_DOSSIER chemin/vers/mon/dossier/*.cpp)
set_source_files_properties(${MON_DOSSIER} PROPERTIES COMPILE_FLAGS -O3)
Afficher la taille de l’exécutable après le build
Vous avez sans doute envie garder un œil sur la taille de votre exécutable. C’est particulièrement le cas si vous travaillez sur un projet embarqué. Une solution simple est d’appeler size (ou un programme équivalent de votre toolchain) grâce à add_custom_command() en tant que post build action. Voici un exemple avec une toolchain GCC pour ARM :
add_custom_command(TARGET program.out
POST_BUILD
COMMAND arm-none-eabi-size program.out)
Créer un fichier fichier hex à partir de l’exécutable elf et supprimer ce hex quand ‘clean’ est invoquée
Cette astuce (très spécifique pour des projets embarqués, je l’avoue) est aussi basé sur add_custom_command() :
add_custom_command(TARGET program.out
POST_BUILD
COMMAND arm-none-eabi-objcopy.exe -I elf32-little -O ihex
program.out program.hex)
Si besoin, j’ai écrit il y a longtemps un article sur (arm-none-eabi-)objcopy et les paramètres à utiliser pour réaliser une telle conversion.
Ce fichier n’est bien sûr pas supprimé quand vous faites appel à la cible clean puisque CMake n’est pas vraiment au courant de ce fichier program.hex… Je ne n’ai pas trouvé de solution portable pour le supprimer mais si vous utilisez make derrière CMake, vous pouvez utiliser la variable ADDITIONAL_MAKE_CLEAN_FILES :
set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${PROJECT_NAME}.hex)
Exclure des éléments du build quand on utilise GLOB_RECURSE
Utiliser file(GLOB_RECURSE …) est souvent considéré comme une mauvaise pratique en CMake. Mais bon, des fois c’est pratique… Si voulez exclure certains fichiers ou dossiers, il faut ensuite modifier la liste obtenue. Voici un exemple :
# Lister les fichiers dans 'source/'
file(GLOB_RECURSE SOURCE_FILES source/*.cpp source/*.hpp source/*.c source/*.h)
# Enlever le dossier 'source/stm32f4xx'
list(FILTER SOURCE_FILES EXCLUDE REGEX ${CMAKE_SOURCE_DIR}/source/stm32f4xx/*)
# Enlever le fichier 'source/debug/Hardfault.c'
list(REMOVE_ITEM SOURCE_FILES ${CMAKE_SOURCE_DIR}/source/debug/Hardfault.c)
pthread
Si votre projet sous Linux utilise des pthreads et si vous vous mangez des undefined references vers des fonctions de pthread lors de l’édition des liens, alors commencez par vérifier que la bibliothèque est bien installée sur votre système puis regardez du côté du module FindThreads. Voici un bout de code à rajouter à votre CMakeLists.txt :
find_package (Threads)
add_executable (myapp main.cpp ...)
target_link_libraries (myapp ${CMAKE_THREAD_LIBS_INIT})
Activer la compilation multicœur dans Eclipse CDT
Mon premier article sur ce blog expliquait comment dire à make d’utiliser plusieurs cœurs lors de la compilation. Il suffit d’utiliser l’option -j pour spécifier le nombre de jobs pouvant s’exécuter en parallèle. Si vous utilisez Eclipse CDT et son builder interne, il existe aussi une option activer la compilation multicœur. Je l’ai trouvé par hasard aujourd’hui, elle est dans les propriétés du projet puis C/C++ Build et Behavior :

J’ai étonné que cette option ne soit pas cochée par défaut et j’ai bien sûr immédiatement testé ça. Mon projet n’est pas encore très gros, il n’a que 78 fichiers. En l’activant, je suis passé de 31s.792 ms à 13s.114 ms pour le builder. Wouhou !


