======CMake ======
* https://cmake.org/documentation/
* https://cmake.org/cmake-tutorial/
* https://cmake.org/cmake/help/v3.3/manual/cmake-commands.7.html
==== Hello World ====
Considérons le petit exemple suivant :
#include
int main() {
printf("Hello World!\n");
return 0;
}
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.
==== Un peu plus compliqué ====
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é à qui contient la définition de la fonction sqrt()).
#include
#include "pouet.h"
int main() {
printf("Hello World!\n");
pouet();
return 0;
}
void pouet();
#include
#include
void pouet() {
printf("sqrt(2) = %.6f\n", sqrt(2));
}
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"
==== Utilisation d'une bibliothèque interne ====
Considérons maintenant le projet {{projtec: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
==== Tests Dynamiques ====
On ajoute à notre projet un sous-répertoire //tests// pour vérifier les fonctions de //mylib// ({{projtec: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
==== Fuites Mémoire ====
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//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)
==== Couverture de Code ====
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/*