make
2004
Make est un utilitaire permettant d’automatiser la compilation de
logiciels. Il permet de ne recompiler que les modules ayant été modifiés
depuis la dernière compilation. Il se base sur un fichier décrivant les
relations entre les modules et les actions à effectuer : le Makefile.
Ce fichier doit s’appeler exactement Makefile
pour que make
puisse le trouver automatiquement. Si on lui donne un autre nom, il faut lancer make avec l’option -f
:
Un Makefile est un fichier texte dans lequel on trouve principalement des déclarations de constantes et des règles. Les déclarations de constantes facilitent la lecture et la modification du Makefile. Les règles décrivent le travail à effectuer par make. Elles doivent être écrites selon une syntaxe très stricte.
Une règle doit respecter la syntaxe suivante :
<cibles>: <dépendances> <retour à la ligne>
<tabulation> <commande> <retour à la ligne>
<tabulation> <commande> <retour à la ligne>
...
sont les fichiers qui seront générés par les actions.
Note : on ne traitera ici que des Makefiles où la liste des cibles de chaque règle ne comporte qu’un seul fichier.
Exemple :
\
). Exemple :Lorsqu’on lance make sans argument, il prend la cible de la première
règle comme cible principale. On peut aussi préciser la cible principale
explicitement : make prog.o
. On détaille le fonctionnement de make sur un exemple :
# Règle 1
prog: prog.o module.o
gcc -o prog prog.o module.o
# Règle 2
prog.o: prog.c
gcc -c prog.c
# Règle 3
module.o: module.c
gcc -c module.c
L’utilisateur a écrit un programme en C composé de deux fichiers, prog.c
et module.c
. Il lance make pour compiler son programme. La cible principale est celle de la première règle, à savoir prog.
prog.o
et module.o
, qui n’existent pas non plus.prog.o
et la trouve (règle 2). Il cherche le fichier prog.c
et le trouve. Il exécute l’action gcc -c prog.c
correspondant à la règle 2. Le fichier prog.o
existe donc maintenant.module.o
et la trouve (règle 3). Il cherche le fichier module.c et le trouve. Il exécute l’action gcc -c module.c
correspondant à la règle 3. Le fichier module.o
existe donc maintenant.prog.o
et module.o
existent, make peut appliquer l’action gcc -o prog prog.o module.o
correspondant à la règle 1. Le fichier prog
existe donc maintenant : la cible principale a été générée, make s’arrète avec succés.L’utilisateur relance immédiatement make.
prog
existe et c’est le cas.prog.o
et trouve la règle 2. Il cherche alors une règle permettant de construire prog.c
qui est la seule dépendance de prog.o
et n’en trouve pas. Il considère donc que prog.c
est à jour.prog.o
avec celle de prog.c
. Puisque prog.o
est plus récent que prog.c
, il considère que prog.o
est à jour.module.o
et module.c
, d’après la règle 3. Puisque prog.o
et module.o
sont à jour, make considère que prog
l’est aussi.Target prog is up to date.
» et aucune action n’est exécutée.L’utilisateur modifie le fichier prog.c
et relance make. Make remonte jusqu’à prog.c
comme précédement, mais cette fois-ci, prog.o
est plus ancien que prog.c
qui vient juste d’être modifié. Make exécute donc la commande gcc -c -o prog.o prog.c
pour mettre à jour prog.o
. Le fichier module.c
n’ayant pas été modifié, make considère module.o
à jour. Mais comme prog.o
vient d’être regénéré, il est donc plus récent que prog
, qui n’est donc plus à jour. Donc make exécute la commande gcc -o prog prog.o module.o
pour mettre à jour prog
et s’arrète avec succés.
On voit donc que Make travaille sur le graphe des dépendances entre les fichiers, chaque noeud étant étiquetté avec sa date de dernière modification. On notera que ce graphe des dépendances ne doit pas contenir de cycle (eg : si A dépend de B qui dépend de C qui dépend de A).
Make permet d’utiliser des constantes dans un Makefile afin d’en faciliter la lecture et la mise à jour. Ainsi, on pourra écrire :
Ceci permettra de changer facilement de compilateur C sans avoir à
modifier les règles. Par convention, les constantes s’écrivent en
majuscules. Comme on le voit, un nom de constante doit être entouré de $()
lorsqu’on veut lire sa valeur.
Il est parfois utile de définir des cibles qui ne sont pas des fichiers. Par exemple, pour supprimer les fichiers intermédiaires lors de la compilation précédente, on pourrait écrire :
Make accepte les règles sans dépendance, dans ce cas le fichier est
considéré à jour s’il existe. Ce Makefile simpliste fonctionnera
correctement la plupart du temps. Il existe cependant un cas dans lequel
il ne se comportera pas comme prévu : s’il existe dans le répertoire
courant un fichier ou un répertoire de nom clean
, make affichera le message «Target clean is up to date
»
et ne fera rien. C’est assez logique puisque comme on l’a dit, un
fichier cible sans dépendance est considéré comme étant à jour s’il
existe. Pour éviter cette erreur, il suffit de rajouter la directive .PHONY: clean
au sommet du Makefile. Ainsi, make sait que clean
n’est pas un fichier mais une cible particulière (phony target).
Cette directive a de plus un effet de bord intéressant. Par défaut,
make s’arrète dès qu’une erreur est détectée. Ainsi, avec le Makefile :
s’il n’existe pas de fichier .bak
dans le répertoire courant, rm va renvoyer un code d’erreur et make s’arrêtera sans avoir effacé les fichiers .o
. Avec la directive .PHONY
, make exécute toutes les commandes de la règle correspondante et ignore les erreurs.
Make est capable de générer certains fichiers même si on ne lui donne
pas de règle appropriée. En effet, certaines commandes sont assez
standard, comme la compilation d’un fichier .c
en .o
par exemple. Ainsi, dans l’exemple ci-dessus, si make n’avait pas trouvé de règle pour construitre prog.o
à partir de prog.c
,
il aurait tout de même été capable de le faire. Cependant,
l’utilisation de ces règles implicites induit des difficultés
supplémentaires lors de la rédaction du Makefile. On désactivera donc
cette fonction de make en utilisant la directive .SUFFIXES:
.
Make permet d’utiliser des raccourcis pour éviter d’avoir à taper des longues listes de fichiers. On présente ici les trois raccourcis les plus utiles :
$@
$^
$<
Exemple :
Ces raccourcis sont surtout utiles lorsqu’on utilise des règles génériques.
Dans l’exemple ci-dessus, on écrit deux règles très similaires pour générer prog.o et module.o :
Pour éviter d’écrire ces deux règles, on peut définir une règle générique :
Cette règle peut être lue comme «chaque fichier .o
dépend du fichier .c
de même nom et peut être généré en utilisant la commande gcc -c -o $@ $^
». On remarque que l’on est ici obligé d’utiliser les raccourcis puisque les noms des fichiers sont variables.
Il est possible d’utiliser make dans une commande. On parle alors d’appel récursif. Par exemple :
Si l’on veut exécuter cette commande dans un autre répertoire, il suffit d’utiliser l’option -C <repertoire>
.
Lorsqu’il exécutera la commande associée à la cible clean, make entrera dans le répertoire subdir
, lancera la commande make clean
qui utilisera le Makefile présent dans le répertoire subdir, puis une
fois cette commande terminée, reviendra dans le répertoire de départ et
exécutera rm -f *.o
. Cette option est utile pour éviter
d’avoir à écrire de très gros Makefiles. En général, on place un
Makefile par répertoire, dans lequel on écrit les règles correspondant
aux fichiers présents dans ce répertoire.
Makefile
# Ces cibles ne sont pas des vrais fichiers
.PHONY: clean install real-clean
# On désactive toutes les règles implicites
.SUFFIXES:
# Déclarations de constantes
CC = gcc
CFLAGS = -O4 -W -Wall
LD = gcc
LDFLAGS = -s
# $@ == prog.exe et $^ == main.o fctg1.o fct2.o
prog.exe: main.o fct1.o fct2.o
$(LD) $(LDFLAGS) -o $@ $^
# Règle générique : $< == le fichier .c compilé
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f *.o
# Appel récursif à make avec ce Makefile
real-clean:
make clean
rm -f bin/prog.exe
# Appel récursif à make avec un Makefile dans un autre répertoire
install:
mv prog.exe bin/
make -C bin/ install
bin/Makefile