~ / howtos /

HOWTO GNU Make & makefile

Copyright © 2005—2007, Renaud Blanch.
mise à jour : 23 novembre 2007

Présentation

Fonction

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.)

Remarque

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>.

Documentation

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.

Exemple

Structure

Pour présenter l'usage de make, on va utiliser un petit exemple en c++. Le projet comporte 3 fichiers :

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

Dépendances

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é (cible), puis les fichiers dont il dépend pour la production (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.

Pseudo-cibles

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

Variables

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.

Variables automatiques

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.

Règles génériques

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.

Règles implicites

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.

Utilisation des variables prédéfinies

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 :

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.

Annexes

Variables pour un programme qui utilise OpenGL et GLUT

Linux

CXXFLAGS = -I/usr/X11R6/include
LDFLAGS  = -L/usr/X11R6/lib
LDLIBS   = -lX11 -lXi -lXmu -lGL -lglut

Mac OS X

CXXFLAGS =
LDFLAGS  =
LDLIBS   = -framework OpenGL -framework GLUT