Considérons le petit exemple suivant :
Voici un fichier CMakeLists.txt minimaliste pour compiler ce petit projet :
cmake_minimum_required(VERSION 2.6) project(HelloWorld C) add_executable(hello hello.c)
Pour générer un Makefile et compiler notre projet “sur place”, il faut taper les commandes suivantes :
cmake . make clean ; make make VERBOSE=1 # compilation en mode verbose make help # pour voir la liste des cibles possibles
Pour compiler “en dehors des sources” :
mkdir build cd build cmake .. make
Un des avantages de cette méthode est quelle permet de ne pas mélanger les fichiers générés par CMake et Make avec les fichiers sources. Du coup, il suffit de supprimer le répertoire build pour tout nettoyer.
Considérons maintenant dans notre projet plusieurs fichiers sources (hello.c, pouet.c, pouet.h) et une bibliothèque extérieure (la library m associé à <math.h> qui contient la définition de la fonction sqrt()).
#include <stdio.h> #include "pouet.h" int main() { printf("Hello World!\n"); pouet(); return 0; }
void pouet();
Voici donc le fichier CMakeLists.txt pour compiler tout cela. On a également ajouté des variables CMAKE par défaut : CMAKE_C_FLAGS et CMAKE_INSTALL_PREFIX pour le répertoire d'installation…
cmake_minimum_required (VERSION 2.6) project (HelloWorld C) set(CMAKE_BUILD_TYPE DEBUG) set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_C_COMPILER gcc) set(CMAKE_C_FLAGS "-std=c99 -g -Wall") set(CMAKE_INSTALL_PREFIX "install") add_executable(hello hello.c pouet.c) target_link_libraries(hello m) install(TARGETS hello RUNTIME DESTINATION bin)
Notons que CMake scanne automatiquement les dépendances (sans faire néanmoins de preprocessing). Il en résulte que la dépendance sur pouet.h est ajouté implicitement.
Ensuite, on fait…
cmake . make make install # make install DESTDIR="/some/absolute/path"
Considérons maintenant le projet cmake-v2.zip, dont la structure est décrit ci-dessous :
├── CMakeLists.txt ├── hello.c ├── mylib │ ├── CMakeLists.txt │ ├── mylib.c │ └── mylib.h ├── pouet.c └── pouet.h
Ce projet contient un éxecutable hello qui utilise la bibliothèque interne mylib définit par un second fichier CMakeLists.txt dans le sous-répertoire mylib. Voici donc les deux fichiers CMake :
cmake_minimum_required (VERSION 2.6) project (HelloWorld C) set(CMAKE_C_FLAGS "-std=c99 -g -Wall") set(CMAKE_INSTALL_PREFIX "install") include_directories(mylib) # link_directories(mylib) add_executable(hello hello.c pouet.c) target_link_libraries(hello m mylib) install(TARGETS hello RUNTIME DESTINATION bin) add_subdirectory(mylib)
add_library(mylib mylib.c) install(TARGETS mylib ARCHIVE DESTINATION lib) install(FILES mylib.h DESTINATION include)
Ensuite, on fait comme d'habitude…
cmake . make make install
On trouve alors dans le répertoire install les fichiers suivants :
├── bin │ └── hello ├── include │ └── mylib.h └── lib └── libmylib.a
On ajoute à notre projet un sous-répertoire tests pour vérifier les fonctions de mylib (cmake-v3.zip).
├── CMakeLists.txt ├── hello.c ├── mylib │ ├── CMakeLists.txt │ ├── mylib.c │ └── mylib.h ├── pouet.c ├── pouet.h └── tests ├── CMakeLists.txt ├── test-mylib-bar.c └── test-mylib-foo.c
Voici le fichier CMake pour compiler et exécuter les tests. Par defaut, pour chaque test, CMake vérifie que le programme termine normalement (pas de signal SEGV par exemple ) et que la valeur de retour vaut EXIT_SUCCESS (0). On peut également rajouter d'autres propriétés à vérifier comme un timeout ou encore vérifier la sortie standard par une expression régulière.
include(CTest) enable_testing() add_executable(test-mylib-foo test-mylib-foo.c) target_link_libraries(test-mylib-foo mylib) add_executable(test-mylib-bar test-mylib-bar.c) target_link_libraries(test-mylib-bar mylib) add_test(test1 test-mylib-foo) set_tests_properties(test1 PROPERTIES PASS_REGULAR_EXPRESSION "foo" TIMEOUT 3) add_test(test2 test-mylib-bar) set_tests_properties(test2 PROPERTIES PASS_REGULAR_EXPRESSION "foo" TIMEOUT 3)
Pour lancer les tests :
make test
Pour aller un peu plus loin, on peut lancer des tests de varification de la mémoire (memcheck) :
make ExperimentalMemCheck
Ces tests se base sur la variable MEMORYCHECK_COMMAND qui est par défault sur /sur/bin/valgrind (enfin s'il est installé…). Les résultats sont dans Testing/<DATE-HOUR>/DynamicAnalysis.xml.
Les logs sont disponibles dans : Testing/Temporary/MemoryChecker.*.log
Il est possible d'ajouter les options suivantes pour obtenir des logs encore plus détaillés…
find_program(MEMORYCHECK_COMMAND valgrind) set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full" CACHE STRING "" FORCE)
Pour aller encore plus loin…
cmake_minimum_required(VERSION 3.0) include(CTest) enable_testing() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=c99 -g --coverage") add_executable(foo foo.c) add_test(test1 ./foo 2) add_test(test2 ./foo 5)
cmake . make ExperimentalTest # ou make test make ExperimentalCoverage more Testing/CoverageInfo/*
Il est plus élégant de n'activer le coverage que en mode DEBUG…
cmake_minimum_required(VERSION 3.0) include(CTest) enable_testing() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -g --coverage") add_executable(foo foo.c) add_test(test1 ./foo 2) add_test(test2 ./foo 5)
Il faut alors compiler le code en mode DEBUG (et non pas en mode RELEASE) :
cmake -DCMAKE_BUILD_TYPE=DEBUG . make ExperimentalTest make ExperimentalCoverage more Testing/CoverageInfo/*