Leçon 4 : L’outil makefile
Objectifs de la leçon 4 « l’outil makefile »
- Déduire les avantages de la compilation conditionnelle. - Construire un graphe de dépendance des fichiers constituant une
application.
- Reconnaitre les règles d’écriture d’un fichier makefile. - Utiliser l’outil makefile pour mettre en place un mécanisme de
compilation conditionnelle.
-
Identifier les commandes qui permettent l’exécution d’un fichier
makefile.
Plan de la leçon 4 « l’outil makefile »
o 4.1 Introduction o 4.2 Règles d’écriture d’un fichier makefile o 4.3 Exemple o 4.4 Exécution d’un fichier makefile o 4.5 Utilisation des variables et des macro-commandes
59
4.1 Introduction :
L’outil Makefile est un moyen pratique pour compiler et lier des programmes repartis sur
plusieurs fichiers. Le principe du Makefile est de construire un graphe de dépendance des
fichiers constituant l’application, surchargé pour chaque fichier par la commande qui permet
de le régénérer à partir des fichiers dont il dépend. On lance l’interpréteur de Makefile par la
commande « make ». La différence entre make et un script Shell classique est que make
connait si une cible est à jour ou non, ce qui lui permet de décider si cette cible doit être
reconstruite, ou peut être conservée (on parle alors de compilation conditionnelle). Lorsqu’on
demande à « make » de refaire une cible, il détermine quelles sont les sous-cibles qui ne se
sont pas à jour, les régénérera avant de régénérer la cible principale. En effet, lorsqu’un seul
fichier parmi les fichiers sources est modifié, il n’est pas nécessairement utile de recompiler
tous les fichiers sources pour régénérer l’exécutable final. Ceci permet de gagner du temps en
ne recompilant que les fichiers sources sur lesquels la modification du code effectuée a un réel
impact. En particulier, la commande « make » permet d’assurer la compilation séparée grâce à
la commande « gcc », et ceci en ne recompilant que le code modifié (compilation
conditionnelle), tout en ayant la possibilité d’utiliser des commandes Shell (ce qui très utile,
surtout pour réaliser des installations).
4.2 Règles d’écriture d’un fichier makefile :
Les fichiers makefile sont structurés selon des règles qui définissent ce qui doit être exécuté et
qui permettent de compiler un programme. En effet, une règle est une suite d’instructions qui
seront exécutées pour construire un cible, mais uniquement si des dépendances sont plus
récentes (par rapport à l’exécution précédente de la cible). La syntaxe d’une règle est comme
suit :
cible : dépendances
Publicité
commandes
60
Avec :
- La « cible » désigne généralement le nom d’un fichier qui va être généré par les
« commandes » qui vont suivre, sachant que l’espace qui précède « commandes » est
obligatoire et doit être fait avec une tabulation.
- Les « dépendances » désignent les fichiers ou les règles nécessaires à la création de
la « cible ». Par exemple, pour la compilation C, les dépendances peuvent contenir un
fichier d’en-tête ou un fichier source.
- Les « commandes » représentent généralement les commandes Shell qui seront
exécutées au moment de la création de la « cible ». Il est possible de faire appel sur la
même ligne à une suite de commandes séparées par un caractère point-virgule « ; ».
Dans le cas où les commandes dépassent une ligne, il est obligatoire de signaler la fin
de ligne avec un caractère antislash « \ ».
Notons que les lignes qui débutent par le caractère « # » sont des lignes de commentaires. Par
exemple, soit le graphe suivant de dépendance des fichiers constituant l’application
(l‘exécutable résultat dans cet exemple est intitulé « sum »), tels que les flèches représentent
les inclusions.
Figure 4.1 Exemple de graphe de dépendance des fichiers constituant une application.
61
Le fichier makefile correspondant à ce graphe est comme suit :
sum: main.o sum.o
gcc –o sum main.o sum.o
main.o: main.c sum.h
gcc –c main.c
sum.o: sum.c sum.h
gcc –c sum.c
Figure 4.2 Contenu du fichier « makefile ».
4.3 Exemple :
Soit le fichier makefile suivant, sachant que « all », qui débute généralement les fichiers
makefile, regroupe dans ces dépendances l’ensemble des exécutables à produire et « clean »
permet de supprimer tout les fichiers intermédiaires. Notons que « mrproper » permet de
supprimer tout ce qui peut être régénéré et permet une reconstruction complète du projet.
all : executable executable : file1.o file2.o
gcc -o executable file1.o file2.o
file1.o : file1.c file1.h gcc -c file1.c file2.o : file2.c file1.h file2.h gcc -c file2.c
Publicité
clean : rm file1.o file2.o executable core
Figure 4.3 Contenu du fichier « makefile ».
Par défaut, la première dépendance rencontrée tentera d’être résolue : « all ». Cette cible
nécessite de remettre à jour toutes les conditions situées à droite des « : » et « make » passe
donc à l’unique cible suivante « executable ». D’après la deuxième ligne du makefile, on voit
62
que « executable » dépend des deux fichiers objets « file1.o » et « file2.o ». Récursivement,
les fichiers dont dépendent « file1.o » et « file2.o » sont recherchés. Si un des fichiers
dépendants est plus récent qu’un des fichiers cibles au cours de cette recherche, le fichier
cible doit être régénéré, et la règle de compilation (« commandes ») associée est exécutée.
4.4 Exécution d’un fichier makefile :
Pour exécuter un fichier makefile, on fait recours à la commande « make » comme suit :
make all
La commande « make » interprète le fichier makefile et exécute les commandes contenues
dans la règle « all », une fois les dépendances ont été vérifiées. Si ces dépendances ont été
modifiées depuis la dernière compilation, la compilation de ces fichiers modifiés entraîne la
mise à jour des cibles. Dans le cas où la commande « make » est utilisée sans argument, elle
exécute la première règle rencontrée. En effet, pour exécuter un ensemble d’actions, la
commande « make » requiert par défaut un fichier nommé makefile. Si le fichier qui contient
l’ensemble d’actions est intitulé autrement (par exemple « actions »), il faut utiliser l’option
« –f » avec la commande « make », tout en spécifiant le nom du fichier, comme suit :
make –f actions
Toutefois, il est possible de ne générer qu’un cible particulier et ceci en spécifiant son nom
en tant que paramètre pour la commande « make ». Par exemple, pour ne générer que le
fichier « file2.o » dans l’exemple précèdent, il suffit d’écrire :
make file2.o
63
Par exemple, soit le fichier makefile suivant :
L’objectif de ce makefile est de créer un exécutable « res » qui dépend de trois fichiers objets
« walmain.o », « walfact.o » et « aff_bjr.o ». Le fichier objet « walmain.o » est généré à partir
du fichier « walmain.c » (par la commande «gcc –c walmain.c » ).
Notons que le fichier « walmain.c » fait appel au fichier « walfct.h » suivant (c.f. Leçon 2.) :
64
Le fichier objet « walfact.o » est généré à partir du fichier « walfact.c » (par la commande
«gcc –c walfact.c » ).
De même, le fichier objet « aff_bjr.o » est généré à partir du fichier « aff_bjr.c » (par la
Publicité
commande «gcc –c aff_bjr.c » ).
Ainsi, pour exécuter ce fichier makefile, on fait recours à la commande « make » sans
arguments, ce qui permet d’interpréter le fichier makefile et ceci en exécutant la première
règle rencontrée (« all »). Cependant, la commande « make clean » ne permet d’exécuter que
la ligne dont la cible est « clean ». En lançant de nouveau la commande « make », le message
«Rien à faire pour « all » » est affiché, vu qu’aucune des dépendances n’a été modifiée entre
temps.
65
4.5 Utilisation des variables et des macro-commandes :
La commande « make » utilise des variables prédéfinies et des macro-commandes, tels que les
variables sont considérées comme des macro-commandes (comme « #define » en langage C).
La déclaration d’une variable doit être placée en début du fichier makefile et elle se fait
comme suit :
Variable=Valeur
Il est aussi possible d’utiliser les variables d’environnement du Shell, qui seront exportées
comme des macros. Ensuite, pour faire appel à une variable, il suffit de précéder son nom
entre parenthèses par le caractère dollar « $ » (comme en Shell). Par exemple, la macro
« $(HOME) » correspondent à la variable d’environnement « HOME ». De même, si on
déclare une variable comme suit « CC = gcc », il est possible par la suite d’utiliser « $(CC)
prog.c » pour compiler le programme « prog.c ». En effet, on adopte souvent les conventions
de nommage suivantes :
66
Variable/Macro- commande
CC CXX ou CPP CFLAGS CXXFLAGS LDFLAGS EXEC RM $@ $< $^ $?
Rôle
Compilateur du langage C (cc ou gcc) Compilateur du langage C++ (c++ ou g++) Paramètres (ou options) à passer au compilateur du langage C Paramètres (ou options) à passer au compilateur du langage C++ Paramètres (ou options) de l’édition des liens Nom de l’exécutable à générer Commande utilisée pour effacer un fichier Nom de la cible Nom de la première dépendance Liste des dépendances Liste des dépendances plus récentes que la cible
Table 4.1 Principales conventions du nommage.
Notons enfin qu’il est aussi possible d’utiliser les caractères jokers (comme en Shell). Par
exemple, l’expression « prog?.c » représente tous les fichiers commençant par « prog » et
finissant par l’extension « .c » avec une lettre entre ces deux chaînes. De même, le caractère
antislash « \ » permet d’inhiber l’action des caractères jokers.
67