Makefile
Pourquoi a-t-on besoin d'un outil pour compiler automatiquement ?
Make est un outil créé en 1976
Compiler avec Makefile ( version naïve)
- Créer un fichier
Makefile. - Y écrire :
all:
gcc myotherCfile.c main.c -o main -Wall
Dans le terminal, on tape make pour construire le projet. Le nom all s'appelle une cible.
Attention, la ligne d'après contient un tab (et pas 4 espaces !) suivi de la commande à exécuter pour construire le projet.
Compiler intelligente avec Makefile
Avec un Makefile, on peut avoir plusieurs cible.
all: main
main: main.o myotherCfile.o
gcc -o main main.o myotherCfile.o
main.o: main.c
gcc -c -o main.o main.c
myotherCfile.o: myotherCfile.c
gcc -c -o myotherCfile.o myotherCfile.c
clean:
rm *.o main
Compilation et liaison
La cible main a besoin d'avoir déjà effectué le travail pour les cibles main.o et myotherCfile.o, et consiste à effectuer gcc -o main main.o myotherCfile.o.
-
Où est-ce qu'a lieu la liaison dans le Makefile ci-dessus ?
Dans la cible main. -
Qu'est ce que fait
gcc -c -o main.o main.c?
main.c en main.o en laissant des trous pour les fonctions déclarées mais non définies.
Variables dans un MakeFile
On peut définir des constantes dans un MakeFile. Par exemple, on définit la constante CC qui donne le nom du compilateur.
Pour avoir le contenu de la constante on écrit $(CC). Ecrire CC ça écrit juste CC ; nous on veut le contenu.
CC=gcc
all: main
main: main.o myotherCfile.o
$(CC) -o main main.o myotherCfile.o
main.o: main.c
$(CC) -c -o main.o main.c
myotherCfile.o: myotherCfile.c
$(CC) -c -o myotherCfile.o myotherCfile.c
clean:
rm *.o main
Pattern
Voici trois règles qui ont le même pattern :
myotherCfile.o: myotherCfile.c
$(CC) -c -o myotherCfile.o myotherCfile.c
bloup.o: bloup.c
$(CC) -c -o bloup.o bloup.c
miaou.o: miaou.c
$(CC) -c -o miaou.o miaou.c
Au lieu de cela, on peut écrire :
%.o: %.c
$(CC) -c -o $@ $^
- Le
%signifienimportequelnomdefichier. $@= le nom de la règlenimportequelnomdefichier.o$^= la prémisse, icinimportequelnomdefichier.c
| nom de la règle: | prémisse |
|---|---|
$@ | $^ |
Lister les fichiers
La commande principale pourrait être :
main: main.o myotherCfile.o bloup.o miaou.o
$(CC) -o main main.o myotherCfile.o bloup.o miaou.o
Pour réaliser cela, on a besoin de lister les .o. Or, on ne les connait pas encore. Mais on sait qu'il y a en un par fichier source .c. On peut lister les fichiers sources avec la commande wildcard :
SOURCES=$(wildcard *.c)
La fonction wildcard prend un argument qui est une expression régulière de fichiers et elle produit la liste des fichiers qui correspondent à l'expression régulière. Le terme wildcard signifie, dans un jeu de cartes, une carte qui remplace n'importe quel autre (typiquement le joker). En informatique, cela désigne ici tous les fichiers qui matchent avec *.c. Dans l'exemple, la constante SOURCES vaut main.c myotherCfile.c bloup.c miaou.c.
Pour obtenir la liste des .o correspondantes, on fait une substitution :
main.c myotherCfile.c bloup.c miaou.c
🡇
main.o myotherCfile.o bloup.o miaou.o
Pour cela on écrit :
OBJECTS=$(SOURCES:.c=.o)
Et maintenant, la règle principale qui était :
main: main.o myotherCfile.o bloup.o miaou.o
$(CC) -o main main.o myotherCfile.o bloup.o miaou.o
devient
main: $(OBJECTS)
$(CC) -o main $^
Organisation en dossiers
Le must est d'organiser son projet avec différents dossiers :
- un dossier
headerqui contient les fichiers header - un dossier
srcqui contient les fichiers .c - un (futur) dossier
buildqui contient les fichier .o créés à la compilation
Ainsi voici un exemple de fichier Makefile qui gère ça :
CC=gcc
SOURCES=$(wildcard src/*.c)
OBJECTS=$(patsubst src/%.c,build/%.o,$(SOURCES))
main.exe: $(OBJECTS)
$(CC) $^ -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 -o main.exe
build/%.o: src/%.c build
$(CC) -Iheader -c -o $@ $<
build:
mkdir build
.PHONY:clean run
clean:
-rm -r build
-rm ./main.exe
run: main.exe
./main.exe
La fonction patsubst permet de réaliser une substitution d'un pattern. Ici :
src/main.c src/myotherCfile.c src/bloup.c src/miaou.c
🡇
build/main.o build/myotherCfile.o build/bloup.o build/miaou.o
Le flag -Iheader indique qu'il faut cherche dans le dossier header pour les #include (-I).
Le symbole $< correspond à la première partie d'une prémisse. Ici : src/%.c build on prend src/%.c (par exemple src/main.c).
La directive .PHONY permet de dire qu'une règle ne correspond pas à un fichier. La directive clean par exemple devrait s'exécuter même si un fichier clean existe ! Ca n'a rien à voir !
Le tiret - avant rm permet d'ignorer l'erreur si le répertoire build n'existe pas, ou si l'exécutable n'existe pas.
Aller plus loin
On peut faire des boucles et autres en Makefile... bref...