source .notebooksrc
Makefile
.Pour les gros projets, GNU proprose un ensemble d'outils regroupés sous le nom d'autotools (Make
en fait partie).
Il s'agit d'une chaîne assez complexe d'outils qui vise à écrire un Makefile
qui s'adapte l'architecture et au système d'exploitation :
CMake
(pour cross platform make) est un moteur de production multiplate-forme.
À partir d'un fichier de configuration CMakeLists.txt
(généralement un par projet), CMake génère des fichiers fichiers descriptifs qui permettent de construire le projet.
Par dédaut, le fichier descriptif est Makefile
mais CMake
peut générer d'autres types de fichiers destinés aux IDE comme Visual Studio.
Dans le cadre de ce cours, nous allons explorer Make
et CMake
.
Makefile
(à la main)¶L'invocation de la commande make
seule (sans cible) va chercher à réaliser la première règle rencontrée dans le fichier Makefile
.
Un fichier Makefile
ressemblant à ça :
# Définition des variables
variable1 = valeur1
variable2 = valeur2
# Définition des règles
# règle 1
cible1: dépendance1
recette1
# règle 2
cible2: dépendance2
recette2
Important :
recette1
est précédée d'une tabulation.
La règle 1
donne plusieurs informations :
cible1
(en général un fichier) est obsolète et doit être regénérée si elle n'existe pas ou si la dépendance1
(un autre fichier) est plus récente que cible1
cible1
en invoquant la recette1
.Dans le cas où cible2 = dépendence1
, alors la règle 2
va vérifier que la cible2
est bien réalisée avant de réaliser la règle 1
.
maillage
¶Make fonctionne avec un système de dépendances chaînées donc il est plus facile d'écrire un Makefile
en commençant par la cible finale :
cd $ROOTDIR/maillage
# On affiche les 3 premières lignes de Makefile_1
pygmentize -l make Makefile | head -n3
Les dépendances de main.e
sont obtenues en compilant chaque fichier source pour produire un objet :
# On affiche les lignes suivantes de Makefile_1
pygmentize -l make Makefile_1 |sed -n '5,9p'
Il faut répéter ce type de règles pour tous les fichiers objets :
# On affiche les autres lignes de Makefile_1
pygmentize -l make Makefile_1 |sed -n '10,24p'
On ajoute également une régle pour effacer les fichiers produits par Make
pygmentize -l make Makefile_1 | tail -n 4
Le fichier complet compile correctement :
make -f Makefile_1 clean
make -f Makefile_1
Note : remarquez la syntaxe
make -f Makefile_1
pour utiliser le fichierMakefile_1
.
mais il contient beaucoup de répétitions, sources potentielles d'erreurs :
pygmentize -l make Makefile_1
On commence par définir les variables suivantes :
pygmentize -l make Makefile_2
On compile à nouveau :
make -f Makefile_2 clean
make -f Makefile_2
On peut aller plus loin en utilisant :
%
Voici les principales variables automatiques fournies par make
:
$@
: la cible$<
: la première dépendance$?
: toutes les dépendances plus récentes que la cible$^
: toutes les dépendancesOn va les utiliser pour construire la règle générique qui capture tous les fichiers .o
:
%.o: %.cpp %.hpp
$(CXX) $(CXXFLAGS) -o $@ -c $<
Ca simplifie beaucoup le Makefile
!
pygmentize -l make Makefile_3
On vérifie la compilation :
make -f Makefile_3 clean
make -f Makefile_3
On peut faire encore plus générique et plus clair en remplaçant la règle en utilisant $^
pour désigner toutes les dépendances (et en regroupant la liste des objets dans une variable).
pygmentize -l make Makefile_4
On compile pour vérifier :
make -f Makefile_4 clean
make -f Makefile_4
La gestion manuelle des dépendances est délicate et source d'erreurs, même pour les petits projets. Make permet de générer automatiquement les dépendances entre les fichiers :
pygmentize Makefile
On compile une première fois :
make clean
make
On vérifie que la compilation prend en compte les dépendances en modifiant triangle.hpp
qui est inclus dans quadrangle.cpp
:
touch triangle.hpp
make
Dans le répertoire maillage
, modifier le fichier Makefile
pour :
libmaillage.a
libmaillage.a
pour construire l'exécutable main.e
voir fichier Makefile_arch
.
Remarque : la variable automatique
$?
désigne la liste des dépendances plus récentes que la cible.
pygmentize -l make Makefile_arch
make -f Makefile_arch
./main.e
Make
¶Ca complique l'écriture du Makefile !
Dans le répertoire maillage_struct
, on propose un Makefile
adapté à l'organisation suivante des fichiers :
cd $ROOTDIR
make -s -C maillage_struct clean
tree maillage_struct
Le Makefile
correspondant est sensiblement plus compliqué :
cd $ROOTDIR/maillage_struct
pygmentize Makefile
L'avantage est que si l'on rajoute un fichier source de test, il est automatiquement pris en compte par le Makefile
.
On rajoute par exemple le fichier test/test_triangle.cpp
:
# On compile d'abord le projet :
make clean
make
# Puis on rajoute le fichier source :
cat >test/test_triangle.cpp <<EOL
#include <iostream>
#include "triangle.hpp"
int main() {
triangle t;
t.all_testsu();
return 0;
}
EOL
On vérifie qu'il est pris en compte :
make
./test/test_triangle.e
make
connaît les dépendances donc il peut déduire les compilations indépendantes et les exécuter en parallèle.
Pour laisser le système choisir le nombre de processus parallèles :
make -j
Pour forcer à n processus :
make -j 4
Comparons les temps de compilation en mode séquentiel et parallèle :
cd $ROOTDIR/maillage
make clean
echo "Temps séquentiel [s] :"
time make -s
make clean
echo "Temps parallèle [s] :"
time make -s -j
L'accélération est appréciable, surtout pendant les phases de développement !
Cette section n'est qu'un aperçu de l'utilisation de Make
.
On pourrait aller bien plus loin en suivant la documentation officielle : https://www.gnu.org/software/make/manual/.
Toutefois, on a constaté que l'écriture et la maintenance d'un Makefile pouvait se révéler délicate et longue, même pour de petits projets.
On va maintenant voir comment simplifier cet travail en utilisant CMake
.
CMake possède son propre langage de programmation. Dans cette partie, nous allons découvrir les rudiments de ce langage. Pour aller plus loin, on peut se référer au site CMake qui fournit :
Dans le répertoire cmake_point/
, on a rassemblé 3 fichiers sources :
cd $ROOTDIR
cd cmake_point
ls *pp
point.*
sont des copies des fichiers du même nom dans le répertoire maillage/
.main.cpp
contient l'exécution des tests unitaires de la classe point
:pygmentize main.cpp
On commence par écrire le fichier CMakeLists.txt
qui décrit les étapes pour générer l'exécutable point.e
à partir des fichiers sources.
# On crée un lien vers le fichier CMakeLists.txt_basic
rm -f CMakeLists.txt && ln -s CMakeLists.txt_basic CMakeLists.txt
pygmentize ./CMakeLists.txt
CMake est conçu pour compiler dans un répertoire séparé des sources.
C'est une bonne pratique qui permet d'isoler les fichiers générés par la compilation des fichiers sources.
On crée donc le sous-répertoire build/
:
rm -rf build # nettoyage préalable
mkdir build
cd build
On invoque la commande cmake
en indiquant le chemin du répertoire où se trouve le fichier CMakeLists.txt
:
cmake ..
ls -l
CMakeCache.txt
et le répertoire CMakeFiles
contiennent des informations propres au projet et à la plateformecmake_install.cmake
indique où seront installés les exécutables générésMakefile
permet à make
de gérer la compilationCMakeCache.txt
et CMakeFiles
ne sont pas regénérés entièrement à chaque invocation de la commande cmake
.
cmake ..
Si on veut reprendre la génération de zéro, il faut les effacer.
rm -rf CMakeCache.txt CMakeFiles # On efface CMakeCache.txt et CMakeFiles
cmake ..
Grâce à la présence du Makefile
, on peut maintenant constuire le projet :
make
puis exécuter le fichier point.e
:
./point.e
On peut obtenir le détail des commandes de compilation utilisée par make
avec make VERBOSE=1
:
make clean
make VERBOSE=1
CMakeLists.txt
¶Debug
, Release
, etc.)# On crée un lien vers le fichier CMakeLists.txt_options
cd ..
rm -f CMakeLists.txt && ln -s CMakeLists.txt_options CMakeLists.txt
pygmentize ./CMakeLists.txt
On génère avec le type de construction par défaut Release
:
cd $ROOTDIR/cmake_point/build
rm -rf CMakeCache.txt CMakeFiles # On efface CMakeCache.txt et CMakeFiles
cmake ..
Ou avec le type Debug
en spécifiant l'option en ligne de commande :
rm -rf CMakeCache.txt CMakeFiles # On efface CMakeCache.txt et CMakeFiles
cmake -D CMAKE_BUILD_TYPE=Debug ..
On vérifie que les options de compilation pour Debug
sont bien prises en compte :
make VERBOSE=1
CMake se révèle particulièrement intéressant pour les projets plus complexes. Comme on l'a vu précédemment :
include/
, src/
, etc.)Prenons par exemple le projet maillage_cmake/
cd $ROOTDIR
tree -L 1 maillage_cmake
Ce projet contient un fichier CMakeLists.txt
que nous allons détailler :
cd maillage_cmake
rm -f test/test_echec.cpp
rm -f CMakeLists.txt
ln -s CMakeLists.txt_base CMakeLists.txt
pygmentize CMakeLists.txt
Générons le projet CMake :
rm -rf build
mkdir build
cd build
cmake ..
Compilons :
make -j
Nous avons créé la bibliothèque libmaillage.a
et l'exécutable maillage.a
:
tree -L 1
./maillage.e
Avec un fichier descripteur CMakeLists.txt
de seulement 8 lignes, CMake construit l'ensemble du projet et gère automatiquement ses dépendances en ne recompilant que les fichiers nécessaires :
touch ../include/point.hpp
make -j
touch ../include/quadrangle.hpp
make -j
cd $ROOTDIR/maillage_cmake
tree test
Chaque fichier source est un programme principal qui exécute les tests unitaires de la classe du même nom. Par exemple :
pygmentize test/test_point.cpp
Pour utiliser CTest, on ajoute les lignes suivantes au fichier CMakeLists.txt
:
cd $ROOTDIR/maillage_cmake
rm -f CMakeLists.txt
ln -s CMakeLists.txt_test CMakeLists.txt
tail -n 15 CMakeLists.txt |pygmentize -l cmake
On génère le projet CMake correspondant :
rm -rf build_ctest
mkdir build_ctest
cd build_ctest
cmake ..
On compile :
make -j
On lance CTest :
ctest
Pour afficher la sortie des tests, on peut exécuter CTest en mode verbeux :
ctest -V
Créons un test qui échoue :
cat > ../test/test_echec.cpp <<EOL
#include "point.hpp"
int main() {return 1;}
EOL
On met à jour le projet :
cmake ..
make -j
L'exécution de CTest fait apparaître un échec mais ne s'interrompt pas :
ctest
CMake possède un système de recherche de paquets externes (bibliothèques, fichiers d'en-tête, etc.) qui permet de gérer l'installation des dépendances sur différents systèmes.
Dans le cadre de ce cours, nous allons prendre l'exemple de la bibliothèque matplotlib-cpp qui permet de tracer des graphes directement depuis le programme C++.
Ce choix présente plusieurs avantages :
matplotlib-cpp
s'appuie sur la bibliothèque matplotlib
pour Python qui est largement utiliséeNous allons utiliser matplotlib
pour représenter un maillage généré par notre bibliothèque.
Note : Il s'agit seulement d'un exercice et non d'une utilisation optimale de
matplotlib
!
L'exemple se trouve dans le projet maillage_plot
qui contient l'en-tête matplotlibcpp.h
dans le sous-répertoire ext/
:
cd $ROOTDIR
tree -L 2 maillage_plot/ext
Par rapport au CMakeLists.txt
du projet maillage_cmake
, on ajoute les lignes suivantes :
cd maillage_plot
pygmentize CMakeLists.txt |sed '5,28!d'
On ajoute la fonction trace()
dans le fichier include/maillage.hpp
:
pygmentize include/maillage.hpp | sed '176,213!d'
On appelle la fonction trace()
depuis le programme principal :
pygmentize src/main.cpp
Comme d'habitude avec CMake, on compile dans un répertoire build/
séparé :
rm -rf build
mkdir build
cd build
Sur Mac, on aide CMake à trouver l'installation Python :
cmake -D Python3_ROOT_DIR=~/anaconda3 ..
On compile :
make -j
On exécute le programme principal qui crée un maillage triangulère régulier et le trace dans le fichier maillage.png
:
./maillage.e
On affiche le résultat :
cat maillage.png | display
Make
qui permet de constuire un projet en gérant les dépendances entre fichiers.Make
seul est compliqué à mettre en oeuvre et à maintenir pour de gros projets et pour des plateformes multiples.CMake
, un outil de plus haut niveau :