User Tools

Site Tools


projtec:make

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.

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

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

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

Makefile
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

projtec/make.txt · Last modified: 2024/03/18 15:06 by 127.0.0.1