Copyright © 2005—2007, Renaud Blanch.
mise à jour : 23 novembre 2007
GNU Make est un utilitaire qui permet d'automatiser la compilation de programmes comportant plusieurs fichiers sources. Sa principale fonction est de prendre en compte les dépendances entre fichiers pour qu'après la modification de l'un d'entre-eux, tous les fichiers qui en dépendent, mais seulement ceux-là, soient recompilés.
Pour ce faire, GNU Make utilise les informations contenues dans un fichier texte, le makefile
.
Ce fichier doit contenir toutes les informations de dépendances entre les sources du programme, et toutes les informations nécessaires pour compiler ces fichiers (compilateurs à utiliser, options de compilation, etc.)
Il existe des clones de GNU Make, qui remplissent tous à peu près le même rôle.
Ce document décrit GNU Make, qui est disponible partout, et dont le comportement est identique d'une plate-forme à l'autre.
Pour éviter toute mauvaise surprise, il faut utiliser GNU Make, ou bien connaître les particularités de l'outil qui le remplace.
GNU Make s'invoque en tapant make
dans une console :
[cheeseburger:~] blanch% make -v GNU Make version 3.79, by Richard Stallman and Roland McGrath. Built for powerpc-apple-darwin7.0 Copyright (C) 1988, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Report bugs to <bug-make@gnu.org>.
On trouve sur la page de GNU Make un lien vers la documentation de make
dans divers formats.
La page consacrée à GNU Make par Paul D. Smith, l'un de ses contributeurs, comporte un ensemble de règles à suivre dans l'écriture des makefiles
, et des informations sur l'usage avancé de make
.
Pour présenter l'usage de make
, on va utiliser un petit exemple en c++.
Le projet comporte 3 fichiers :
hello.cpp
qui comporte le point d'entrée (main) du programme ; functions.h
qui comporte les déclarations des fonctions utilisées par hello.cpp
; et functions.cpp
qui comporte les réalisations de ces fonctions.
functions.h
est donc inclus dans hello.cpp
et functions.cpp
.
Pour fabriquer le programme, on doit compiler les sources en objets :
[cheeseburger:~/src] blanch% g++ -c functions.cpp [cheeseburger:~/src] blanch% g++ -c hello.cpp
Puis lier les objets en un exécutable :
[cheeseburger:~/src] blanch% g++ -o hello hello.o functions.o
La question qui se pose alors, est celle de savoir ce qu'il faut recompiler lorsque l'on a modifié l'un des fichier. Le graphe des dépendances entre les fichiers est ici assez simple. Sur le schéma ci-dessous, une flèche de A vers B indique que l'on a besoin de A pour fabriquer B, et donc que si A a changé (i.e. si sa date de modification est postérieure à celle de B), il va falloir refabriquer B. Par transitivité, il faudra alors aussi refabriquer tous les fichiers qui dépendent de B et ainsi de suite ...
hello.cpp --- > hello.o / \ functions.h - > hello \ / functions.cpp --- > functions.o
C'est ce graphe que l'on va fournir à make
grâce au makefile
qui contient des règles de production de la forme :
cible: source ... commande de production ...
La première ligne donne tout d'abord le fichier qui est fabriqué (source ...
).
Viennent ensuite, indentées avec une tabulation, les commandes qui permettent de produire la cible à partir des sources.
Une première version du makefile
pour notre programme est donc :
# makefile version 1 # graphe de dependances hello: hello.o functions.o g++ -o hello hello.o functions.o hello.o: hello.cpp functions.h g++ -c hello.cpp functions.o: functions.cpp functions.h g++ -c functions.cpp
Les premières lignes commencent par #, ce sont des commentaires ignorés par make
.
La première règle permet de produire le programme, elle sera invoquée par défaut lorsque l'on exécutera make
dans le repertoire où se situe le makefile
.
Si hello.o
ou functions.o
n'existent pas, make
cherchera alors les règles qui permettent de les produire.
Quelques tests nous montrent que le makefile
permet bien de produire le programme et de ne recompiler que ce qui est nécessaire (touch
change la date de modification d'un fichier à la date actuelle, faisant croire à make
que le fichier vient d'être modifié) :
[cheeseburger:~/src] blanch% rm hello *.o [cheeseburger:~/src] blanch% ls functions.cpp functions.h hello.cpp makefile [cheeseburger:~/src] blanch% make g++ -c hello.cpp g++ -c functions.cpp g++ -o hello hello.o functions.o [cheeseburger:~/src] blanch% touch hello.cpp [cheeseburger:~/src] blanch% make g++ -c hello.cpp g++ -o hello hello.o functions.o [cheeseburger:~/src] blanch% touch functions.h [cheeseburger:~/src] blanch% make g++ -c hello.cpp g++ -c functions.cpp g++ -o hello hello.o functions.o
Pour en savoir plus : An Introduction to Makefiles, et Writing Rules.
C'est toujours la cible de la première règle que make
essaie de construire lorsqu'on ne lui donne pas d'argument.
Si on lui passe des arguments, il essaie de construire les cibles correspondantes.
Dans notre exemple, make
et make hello
sont donc équivalents.
make hello.o
permet de ne construire (si besoin est) que l'objet hello.o
.
On ajoute souvent, par convention, une première règle dont la cible s'appelle all
, et qui a pour dépendances les cibles réelles du makefile
.
De même, on ajoute une autre pseudo-cible, clean
, qui supprime tous les fichiers produits par la compilation.
Comme il y a très peu de chance pour qu'il y ait des fichiers s'appelant all
et clean
à côté du makefile
, make
va toujours exécuter les commandes qui sont associées à ces règles.
# makefile version 2 # graphe de dependances, pseudo-cibles all: hello hello: hello.o functions.o g++ -o hello hello.o functions.o hello.o: hello.cpp functions.h g++ -c hello.cpp functions.o: functions.cpp functions.h g++ -c functions.cpp clean: rm -f functions.o hello.o rm -f hello
On peut utiliser des variables pour faciliter l'écriture des makefiles
.
Par exemple, on peut simplifier la première règle ainsi :
# makefile version 3 # graphe de dependances, pseudo-cibles, variables objects = hello.o functions.o program = hello all: $(program) $(program): $(objects) g++ -o $(program) $(objects) hello.o: hello.cpp g++ -c hello.cpp functions.o: functions.cpp g++ -c functions.cpp $(objects): functions.h clean: rm -f $(objects) rm -f $(program)
Les premières lignes définissent des variables qui continennent le nom des fichiers exécutable et objets à produire. La valeur d'une variable peut être prise grâce à l'opérateur dollar ($).
La première règle est plus simple, et surtout, on est sûr de ne rien oublier entre la première ligne qui spécifie les dépendances et la ligne de commande qui suit.
Les deux autres règles ont aussi été simplifiées en supprimant la source functions.h
et en regroupant cette dépendance commune aux deux objets dans une nouvelle règle.
Cette règle ne comporte pas de commande, elle n'indique pas comment produire sa cible.
Elle sert simplement à indiquer que les objets doivent être reconstruits si functions.h
a été modifié.
Ce sont les règles précédentes qui seront alors utilisées pour la compilation.
Pour en savoir plus : How make Reads a Makefile.
make
initialise quelques variables automatiquement avant d'invoquer les commandes d'une règle, en particulier :
On peut donc réécrire notre makefile
ainsi :
# makefile version 4 # graphe de dependances, pseudo-cibles, variables, variables automatiques objects = hello.o functions.o program = hello all: $(program) $(program): $(objects) g++ -o $@ $^ hello.o: hello.cpp g++ -c $< functions.o: functions.cpp g++ -c $< $(objects): functions.h clean: rm -f $(objects) rm -f $(program)
Pour en savoir plus : Automatic Variables.
make
permet de définir des règles qui ne comportent pas de nom de fichier mais seulement leurs extensions.
Ces règles permettent par exemple de définir comment faire un objet à partir d'un fichier source.
On peut ainsi remplacer les deux règles permettant de crééer nos objets, qui sont finalement les mêmes, par une seule :
# makefile version 5 # graphe de dependances, pseudo-cibles, variables, variables automatiques, # regles generiques objects = hello.o functions.o program = hello all: $(program) $(program): $(objects) g++ -o $@ $^ %.o: %.cpp g++ -c $< $(objects): functions.h clean: rm -f $(objects) rm -f $(program)
Les règles génériques utilisent le symbole % qui permet de remplacer une partie des noms de fichier dans la ligne de dépendance de la règle. % représente la même chaîne du côté de la cible et de celui des sources. Le fait d'utiliser les variables automatiques dans les commandes fait qu'on n'a pas non plus besoin de savoir dans les commandes quel est le nom du fichier.
Cette règle permet donc de faire hello.o
à partir de hello.cpp
, functions.o
à partir de functions.cpp
, mais aussi n'importe quel autre objet.
Si on ajoute un nouveau fichier à notre projet, à ce stade, on a uniquement besoin de modifier ... la première ligne du makefile
!
Pour en savoir plus : Defining and Redefining Pattern Rules.
make
comporte un ensemble de variables et de règles qu'il utilise par défaut.
On peut les connaître grâce à l'option -p
.
S'il y a un makefile
dans le répertoire courant, il faut spécifier qu'on ne veut pas le prendre en compte (pour ne pas avoir ses propres variables et règles), en faisant :
[cheeseburger:~/src] blanch% make -p -f /dev/null # GNU Make version 3.79, by Richard Stallman and Roland McGrath. # Built for powerpc-apple-darwin7.0 # Copyright (C) 1988, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 # Free Software Foundation, Inc. # This is free software; see the source for copying conditions. # There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. ...
On trouve par exemple la règle :
%.o: %.cpp $(COMPILE.cpp) $(OUTPUT_OPTION) $<
Et les variables :
OUTPUT_OPTION = -o $@ COMPILE.cpp = $(COMPILE.cc) COMPILE.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c CXX = g++
Ce qui donne après substitution :
%.o: %.cpp g++ $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $<
Ce qui est à peu de chose près notre règle générique.
On peut donc encore simplifier notre makefile
en supprimant cette règle puisque make
la connaît déjà :
# makefile version 6 # graphe de dependances, pseudo-cibles, variables, variables automatiques, # regles generiques, regles implicites objects = hello.o functions.o program = hello all: $(program) $(program): $(objects) g++ -o $@ $^ $(objects): functions.h clean: rm -f $(objects) rm -f $(program)
Pour en savoir plus : Using Implicit Rules, et Catalog of Implicit Rules.
Dans l'exemple précédent, on peut remarquer la présence de variables comme CXXFLAGS
ou CPPFLAGS
dans les règles implicites.
Celles-ci permettent de passer des options de compilation ou de redéfinir certains outils.
Les variables à connaître sont :
CPPFLAGS
: options pour le préprocesseur (defines ...) ; CFLAGS
, CXXFLAGS
: options pour le compilateur c, ou c++ (chemins pour les includes ...) ; LDFLAGS
: options pour l'éditeur de liens (chemins pour les bibliothèques) ; et LDLIBS
: bibliothèques à utiliser pendant l'édition de liens.
On peut ainsi activer les warnings, ou les optimisations du compilateur, en ajoutant des options à la variable CPPFLAGS
.
############################################################################### # makefile version finale # graphe de dependances, pseudo-cibles, variables, variables automatiques, # regles generiques, regles implicites, options # options de compilation ###################################################### # en dev, instrumenter le code, afficher tous les warnings CPPFLAGS += -g -Wall -ansi -pedantic # en prod, activer toutes les optimisations #CPPFLAGS += -O3 # variables ################################################################### objects = hello.o functions.o program = hello # regles ###################################################################### all: $(program) $(program): $(objects) g++ -o $@ $^ clean: rm -f $(objects) rm -f $(program) # dependances ################################################################# $(objects): functions.h
On peut aussi encore simplifier notre makefile
.
En effet, la seule règle conservée est celle qui réalise l'édition de liens du programme en utilisant g++
.
En fait, make
a une règle implicite qui fait ce travail, mais qui utilise gcc
(éditeur de liens du c) au lieu de g++
(pour le c++), et ne lie pas automatiquement avec la bibliothèque standard c++.
On peut utiliser cette règle (et supprimer la notre), à la condition de spécifier explicitement qu'il faut réaliser l'édition de liens à l'aide de g++
.
On peut le faire en forçant la variable CC
qui contient le nom du compilateur du c à CXX
, mais en lui donnant cette valeur pour une seule règle seulement.
Notre makefile
devient alors :
############################################################################### # makefile version finale # graphe de dependances, pseudo-cibles, variables, variables automatiques, # regles generiques, regles implicites, options # options de compilation ###################################################### # en developpement, instrumenter le code, afficher tous les warnings CPPFLAGS += -g -Wall -ansi -pedantic # en production, activer toutes les optimisations #CPPFLAGS += -O3 # variables ################################################################### objects = hello.o functions.o program = hello # regles ###################################################################### all: $(program) $(program): CC = $(CXX) $(program): $(objects) clean: rm -f $(objects) rm -f $(program) # dependances ################################################################# $(objects): functions.h
Ce makefile
est plus long que le makefile
original, cependant il est bien plus facile à tenir à jour.
Ajouter un nouveau programme, ou un nouveau fichier, changer des options de compilation, tout cela s'effectue maintenant simplement.
Pour en savoir plus : Variables Used by Implicit Rules, et Target-specific Variable Values.
CXXFLAGS = -I/usr/X11R6/include LDFLAGS = -L/usr/X11R6/lib LDLIBS = -lX11 -lXi -lXmu -lGL -lglut
CXXFLAGS = LDFLAGS = LDLIBS = -framework OpenGL -framework GLUT