======Makefile======
//Pour compiler automatiquement et efficacement un projet de programmation, le Makefile est une solution élégante.//
Un fichier //Makefile// est un simple fichier texte qui définit un ensemble de régles, chaque règle permettant de générer un fichier cible à partir de ses dépendances (une liste de fichiers). Voici la syntaxe d'un règle :
cible1: dep1 dep2 dep3 ...
commande
Attention, il faut une tabulation avant d'écrire la //commande// qui réalise la cible !
Notons que la commande n'est exécutée que si le fichier //cible1// n'existe pas, ou que la cible est périmée, c'est-à-dire qu'il y a au moins une dépendance qui est plus récente que la cible. Dans ce dernier cas, il faut mettre à jour la cible. Par ailleurs, les dépendances peuvent également être la cible d'autres règles. Dans ce cas, si le fichier d'une dépendance comme //dep1// n'existe pas (ou qu'elle est périmée), alors on va appliquer cette autre règle en cascade pour mettre à jour cette dépendance, avant de mettre à jour //cible1//.
dep1: autredep1 autredep2 ...
autre_commande
Au final, grâce à un chaînage précis des dépendances, le Makefile permet de re-compiler un projet en ne mettant à jour que les cibles qui dépendent des fichiers modifiés.
Voici un exemple type de fichier Makefile pour le programme C précédent.
# variable standard pour la compilation
CC=gcc # compilateur
CFLAGS=-Wall -g -std=c99 # options de compilation
LDFLAGS= # options de link
LDLIBS=-lm # bibliothèques
CPPFLAGS= # options de preprocessing
# cible principale (par défaut)
all: toto
# règle spécifique pour générer l'exécutable
toto: toto.o tutu.o tata.o
$(CC) $(LDFLAGS) $^ -o $@ $(LDLIBS)
# règle générique de compilation des fichiers C (implicite)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f *.o *~ toto
Dans ce Makefile, il y a quelques notions à comprendre :
* Les commentaires sont précédés du symbole #.
* La variable %%$%%@ représente la cible courante.
* La variable %%$%%^ représente la liste des dépendances.
* La variable %%$%%< représente la première dépendance.
* La cible .PHONY permet d'indiquer des cibles particulières qui ne sont pas des fichiers, comme par exemple //clean//.
Par ailleurs, il n'est pas nécessaire d'indiquer la règle générique "%.o: %.c" ici, car cette règle est implicite dans les Makefile !
Pour fabriquer une cible particulière, on fait //make cible//. Par défault, l'appel à //make// fabrique la première cible (//all// dans notre exemple.
$ make
gcc -Wall -g -std=c99 -c toto.c -o toto.o
gcc -Wall -g -std=c99 -c tutu.c -o tutu.o
gcc -Wall -g -std=c99 -c tata.c -o tata.o
gcc toto.o tutu.o tata.o -o toto -lm
$ make clean
rm -f *.o *~ toto
L'utilisation de la règle générique (implicitement ou pas) est pratique, mais elle ne prend pas en compte toutes les dépendances, comme les fichiers auxiliaires (*.h). Pour améliorer notre Makefile, il faut ajouter les règles de dépendance explicitement, comme ci-dessous.
# variable standard pour la compilation
CC=gcc # compilateur
CFLAGS=-Wall -g -std=c99 # options de compilation
LDFLAGS= # options de link
LDLIBS=-lm # bibliothèques
CPPFLAGS= # options de preprocessing
# cible principale (par défaut)
all: toto
# règle spécifique pour générer l'exécutable
toto: toto.o tutu.o tata.o
$(CC) $(LDFLAGS) $^ -o $@ $(LDLIBS)
# dépendances explicites
toto.o: toto.c tata.h tutu.h
tata.o: tata.c tata.h
tutu.o: tutu.c tutu.h
.PHONY: clean
clean:
rm -f *.o *~ toto
Plus d'info sur les régles implicites : https://www.gnu.org/software/make/manual/html_node/Catalogue-of-Rules.html#Catalogue-of-Rules
==== Pour aller un peu plus loin ====
L'écriture des dépendances peut être fastidieux dans un gros projet, on peut alors utiliser la commande "gcc -MM".
$ gcc -MM *.c
toto.o: toto.c tata.h tutu.h
tata.o: tata.c tata.h
tutu.o: tutu.c tutu.h
Pour aller encore plus loin, on peut inclure les dépendances dans le Makefile, que l'on générère explicitement avec la cible dep.
# variable standard pour la compilation
CC=gcc # compilateur
CFLAGS=-Wall -g -std=c99 # options de compilation
LDFLAGS= # options de link
LDLIBS=-lm # bibliothèques
CPPFLAGS= # options de preprocessing
# cible principale (par défaut)
all: toto
# règle spécifique pour générer l'exécutable
toto: toto.o tutu.o tata.o
$(CC) $(LDFLAGS) $^ -o $@ $(LDLIBS)
.PHONY: clean dep
# génération des dépendances
dep:
$(CC) -MM *.c > depends.txt
clean:
rm -f *.o *~ toto
# inclusion des dépendances
-include depends.txt
Dans ce cas, il faut commencer par faire "make dep" pour générer le fichier des dépendances, puis après on peut faire "make". Il faut penser à remettre à jour les dépendances avec "make dep" quand on ajoute de nouveaux fichiers ou que l'on modifie les //includes// !
==== Pour aller encore plus loin ====
//Parler de Makefile recursif avec "make -C subdir" ; parler des fonctions %%$%%(wildcard *.c) ; etc.//
SOURCES := $(wildcard *.c)
INCLUDES := $(wildcard *.h)
OBJECTS := $(SOURCES:.c=.o)
==== Nota Bene ====
//Quelques astuces...//
* @ suppresses the normal 'echo' of the command that is executed.
* - means ignore the exit status of the command that is executed (normally, a non-zero exit status would stop that part of the build).
* + means 'execute this command under make -n' (or 'make -t' or 'make -q') when commands are not normally executed. See also the POSIX specification for make and also §9.3 of the GNU Make manual.
Plus d'info : https://stackoverflow.com/questions/3477292/what-do-and-do-as-prefixes-to-recipe-lines-in-make