Make Et Makefile : Petit Résume Rapide

Make et Makefile : petit résume rapide (Vos commentaires sont les bienvenus : jean-claude.iehl at univ-lyon1.fr) Make est outil tr�s g�n�ral permettant, entre autre, d'automatiser la compilation d'un projet. Supposons que le projet soit constitu� des sources C suivants : main.c, structure.c, et operation.c . Il est possible de compiler ce projet de trois mani�res diff�rentes, les parties suivantes pr�cisent ces diff�rentes �tapes.

1. une seule commande

gcc -o prog -Wall main.c structure.c operation.c
(rappel des options de gcc : man gcc, info gcc)

2. une commande pour chaque fichier source et une autre pour créer l'éxécutable :

gcc -c -Wall main.c gcc -c -Wall structure.c gcc -c -Wall operation.c
Les commandes pr�c�dentes compilent chaque fichier source et cr�ent les fichiers objets correspondants qui s'appellent : main.o, structure.o et operation.o. Un fichier objet est le r�sultat de la compilation d'un fichier source et contient les instructions machines associ�es � chaque fonction du fichier source, ainsi que la liste des fonctions appel�es qui ne se trouvent pas dans le fichier source (par exemple, les fonctions des librairies standards printf, scanf, malloc, etc. et les fonctions que vous avez �crites mais qui se trouvent dans les autres fichiers sources (voir l'utilisation du mot cl� extern)).
gcc -o prog main.o structure.o operation.o
Cette derni�re commande cr�e l'�x�cutable prog en assemblant (cette partie de la compilation s'appelle l'�dition de liens) les ensembles d'instructions associ�s � chaque fichier objet. C'est � ce moment que le compilateur v�rifie qu'il connait l'ensemble d'instructions associ� � chaque fonction. Le cycle de travail sur le projet n�cessitera g�n�ralement plusieurs corrections d'erreurs. Il faudra donc, apr�s chaque correction, recompiler le fichier source modif� (apr�s avoir enregistr� les modifications ...). La compilation de ce fichier source produira un fichier objet et il faudra bien sur recr�er l'�x�cutable. Prenons un exemple : supposons que l'on ait modifi� main.c, la commande suivante permet de le recompiler (et de cr�er une nouvelle version de main.o) :
gcc -c -Wall main.c
il faut encore reconstruire l'�x�cutable, afin de tenir compte des modifications apport�es � main.c (et donc main.o). Par contre, les objets structure.o et operation.o n'ont pas �t� modifi�s, il n'est donc pas n�cessaire de les recompiler.
gcc -o prog main.o structure.o operation.o
On peut maintenant v�rifier le bon fonctionnement du programme. Un projet "moyen" comporte rapidement une dizaine de fichiers sources, et il devient vite p�nible de taper toutes les commandes n�cessaires � la compilation du projet. De plus, le risque d'erreur ou d'oubli augmente tr�s rapidement, d'ou l'utilit�, voire la n�cessit�, d'un outil permettant d'automatiser la construction du projet.

3. Make

L'utilisation de make est relativement simple, une fois que l'on a compris que son r�le est de produire automatiquement la s�quence de commandes permettant de construire un projet. Pour cr�er l'ex�cutable, la premi�re fois, il faut que make g�n�re la s�quence de commandes d�crite dans la partie 2 :
gcc -c -Wall main.c gcc -c -Wall structure.c gcc -c -Wall operation.c gcc -o prog main.o structure.o operation.o
De la m�me mani�re, apr�s la modification de main.c, make doit g�n�rer la s�quence de commandes suivante :
gcc -c -Wall main.c gcc -o prog main.o structure.o operation.o
Le raisonnement qui nous a permis d'�crire cette s�quence de commande est bas� sur les d�pendances (ou les relations) entre ces fichiers :
main.c produit main.o et main.o, structure.o et operation.o permettent de construire le projet prog.
Il suffit de d�crire ces relations � make, ainsi que les commandes associ�es pour qu'il puisse produire la s�quence de commandes correcte. Ces relations sont d�crites dans un fichier texte, nomm� Makefile ou makefile. Ce fichier est constistu� des descriptions des relations entre les fichiers sources et les fichiers objets ainsi que des relations entre les fichiers objets et le projet. Une relation s'�crit de la mani�re suivante dans le fichier makefile :
produit : source commande
Cette r�gle indique plusieurs choses � make. Premi�rement, que produit est cr�e � partir de source et que c'est commande qui permet de le faire. On peut donc d�crire la relation entre main.c et main.o, qui est produit par la compilation de main.c :
main.o : main.c gcc -c -Wall main.c
De m�me, la relation entre les fichiers objets et le projet s'�crit :
prog: main.o structure.o operation.o gcc -o prog main.o structure.o operation.o
Pour achever la cr�ation du makefile, il ne reste plus qu'� �crire les r�gles pour structure et operation :
structure.o : structure.c gcc -c -Wall structure.c operation.o : operation.c gcc -c -Wall operation.c
Un dernier d�tail, comme le makefile est compos� de plusieurs relations, il faut indiquer � make laquelle construire en priorit� : par convention, c'est tout simplement la premi�re. Il suffit donc d'�crire la r�gle d�crivant la construction du projet au d�but du fichier makefile. Le makefile complet ressemblera donc � :
prog: main.o structure.o operation.o gcc -o prog main.o structure.o operation.o main.o : main.c gcc -c -Wall main.c structure.o : structure.c gcc -c -Wall structure.c operation.o : operation.c gcc -c -Wall operation.c
Lors d'une modification, make se base sur la date de modification des fichiers pour d�terminer les mises � jours � effectuer. Dans le sc�nario pr�c�dent, le projet complet est compil� et �x�cut�. Lors de ce premier appel � make, ni les fichiers objets, ni l'�x�cutable n'existent, ils sont donc cr��s en utilisant la commande associ�e � leur r�gle. Ensuite, main.c est modifi�, main.c devient donc plus r�cent que main.o et prog. Make analyse les r�gles d�finies dans le makefile et v�rifie les dates des fichiers. Comme main.c est plus r�cent que main.o, ce dernier ne peut �tre le produit de la version actuelle de main.c, il faut donc le recompiler, la commande associ�e � la r�gle main.o sera donc �x�cut�e (gcc -c -Wall main.c). Apr�s la compilation, la date de main.o est plus r�cente que celle de prog, il faudra donc r�cr�er prog avec la commande correspondante (gcc -o prog main.o structure.o operation.o).

3. Quelques idées d'utilisation

Make ne pose aucune contrainte sur la compilation. Il ne sait pas qu'il est en train de compiler un projet, vous pouvez donc en profiter pour lui faire faire autre chose, comme effacer les fichiers temporaires, cr�er une archive de votre projet, ou d�finir plusieurs versions du m�me projet ou de plusieurs projets dans le m�me makefile. Les sections suivantes pr�sentent quelques r�gles � ajouter au makefile. Pour indiquer � make que l'on veut �x�cuter une r�gle particuli�re il suffit de lui indiquer : make regle, ce qui �x�cutera les commandes associ�es � la r�gle regle.

3.1 Effacer les fichiers temporaires

Lors de modifications importantes d'un projet, on a souvent besoin de recompiler la totalit� des sources, ou de nettoyer le r�pertoire de travail. Il est toujours possible de taper une commande rm dans un terminal, mais on peut aussi le faire faire � make. Comment �crire cette r�gle ? Quelle relation existe-t-il entre un fichier et un fichier effac� ? La r�ponse est simple : aucune. Mais on peut quand m�me �crire une r�gle qui �x�cute toujours sa commande, il suffit de ne pas indiquer de source et de donner � la r�gle un nom qui n'est pas un fichier. Pour effacer les fichiers objets du projet, il suffit de d'�crire la r�gle suivante qui s'appelle clean par convention :
clean : rm -f prog *.o
L'option -f (force) indique juste � rm de ne pas signaler les erreurs �ventuelles (plus de d�tail sur les options de rm, man rm). Le *.o est un joker qui indique � rm d'effacer tous les fichiers se terminant par .o, c'est � dire les fichiers objets. make clean �x�cutera donc la commande rm -f prog *.o associ�e � la r�gle clean.

3.2 Archiver le projet

Les r�gles sans sources (voir ci-dessus) permettent d'�x�cuter une commande quelconque, on peut en profiter pour archiver le projet, par exemple. On peut compl�ter le makefile par la r�gle :
zip: tar -zcvf prog.tar.gz main.c structure.c operation.c Makefile
La commande tar permet de cr�er une archive compress�e, il suffit d'indiquer le nom de l'archive � cr�er et la liste des fichiers � archiver (plus de d�tails sur l'utilisation de tar, man tar). Vous pouvez aussi envoyer l'archive par mail (man mail) ou la copier sur disquette. make zip �x�cutera donc la commande tar -zcvf prog.tar.gz main.c structure.c operation.c Makefile associ�e � la r�gle zip.

3.3 Plusieurs versions du projet

Dans le TP3, il �tait demand� de fournir plusieurs versions du m�me programme. On peut cr�er plusieurs projets s�par�s, ou tout simplement d�crire les diff�rentes versions dans le m�me makefile. Le TP3 �tait compos� des sources C suivants : blob.c tga.c tri_blob.c. Voici le fichier Makefile fourni avec le sujet :
blob: blob.o tga.o tri_blob.o gcc -o blob blob.o tga.o tri_blob.o -lm blob.o: blob.c gcc -Wall -c blob.c tga.o: tga.c gcc -Wall -c tga.c tri_blob.o: tri_blob.c gcc -Wall -c tri_blob.c tarball: tar -zcvf blob.tar.gz blob.c tga.c tri_blob.c blob.h tga.h Makefile clean: rm *.o blob
Ce Makefile cr�e un �x�cutable nomm� blob � partir de blob.o, tga.o et de tri_blob.o. Le travail demand� consistait principalement a �crire de nouvelles fonctions de tri. Supposons que le tri par insertion se trouve dans le source tri_insertion.c, et que le tri par tas se trouve dans tri_tas.c. On veut cr�er une version de blob, blob_insertion, compil�e avec le tri par insertion et une autre compil�e avec le tri par tas, blob_tas. Les deux nouvelles r�gles :
blob_insertion: blob.o tga.o tri_insertion.o gcc -o blob_insertion blob.o tga.o tri_insertion.o -lmtri_insertion.o: tri_insertion.c gcc -Wall -c tri_insertion.c
permettent de cr�er l'�x�cutable blob_insertion qui est bien blob compil� avec la fonction de tri par insertion. De m�me, il suffit de rajouter les r�gles suivantes pour la version tri par tas :
blob_tas: blob.o tga.o tri_tas.o gcc -o blob_tas blob.o tga.o tri_tas.o -lmtri_tas.o: tri_tas.c gcc -Wall -c tri_tas.c
Au final, le Makefile ressemblera � :
blob: blob.o tga.o tri_blob.o gcc -o blob blob.o tga.o tri_blob.o -lmblob_insertion: blob.o tga.o tri_insertion.o gcc -o blob_insertion blob.o tga.o tri_insertion.o -lmblob_tas: blob.o tga.o tri_tas.o gcc -o blob_tas blob.o tga.o tri_tas.o -lmtri_tas.o: tri_tas.c gcc -Wall -c tri_tas.c tri_insertion.o: tri_insertion.c gcc -Wall -c tri_insertion.c blob.o: blob.c gcc -Wall -c blob.ctga.o: tga.c gcc -Wall -c tga.ctri_blob.o: tri_blob.c gcc -Wall -c tri_blob.ctarball: tar -zcvf blob.tar.gz blob.c tga.c tri_blob.c blob.h tga.h Makefileclean: rm *.o blob
Les trois premi�res r�gles (blob, blob_insertion, blob_tas) correspondent aux trois versions du projet et les r�gles suivantes d�crivent la compilation des diff�rents sources des projets.

4. Makefile avancé

La r�daction d'un makefile est relativement d�licate, il est courant d'oublier de rajouter un objet dans les sources d'une r�gle ou dans la liste des objets � compiler pour cr�er l'�x�cutable. De m�me, l'�criture des r�gles de compilation est particuli�rement longue. Heureusement make dispose de fonctionnalit�s suppl�mentaires permettant d'automatiser une partie de la r�daction. Les deux sections suivantes pr�sentent l'utilisation des variables afin de simplifier l'�criture des makefile.

4.1 variables

Des variables existent dans make, ce sont simplement des cha�nes de caract�res. On peut par exemple d�finir une variable indiquant quel compilateur utiliser, quelles options de compilation utiliser ou encore une liste de fichiers. Pour d�finir une variable, il suffit de lui donner un nom et une valeur. Un nom de variable est une suite de caract�res ne contenant pas `:', `#' et `='. Une valeur est une cha�ne de caract�res quelconque. Pour substituer une variable � sa valeur, il suffit d'�crire $nomvariable. Quelques exemples :
CC= gcc blob: blob.o tga.o tri_blob.o $(CC) -o blob blob.o tga.o tri_blob.o -lm...
ou
CC= gcc objets= blob.o tga.o tri_blob.o blob: $(objets) $(CC) -o blob $(objets) -lm...
Les parenth�ses autour des noms de variables ne sont pas obligatoires mais permettent d'�viter certains probl�mes lors de la substitution de la variable par sa valeur (c'est un simple remplacement de caract�res).

4.2 variables automatiques et règles implicites

Il existe un autre type de variables : les variables automatiques, leurs noms sont impos�s et make d�fini leurs valeurs. Voici les plus utilis�es : $@ : produit (ou but) de la règle $< : nom de la première dépendance (ou source) $? : toutes les dépendances plus récentes que le but $^ : toutes les dépendances $+ : idem mais chaque dépendance apparait autant de fois qu'elle est cité et l'ordre d'apparition est conservé. La variable $@ repr�sente le produit (ou le but) d'une r�gle, en reprenant l'exemple pr�c�dent :
CC= gcc objets= blob.o tga.o tri_blob.o blob: $(objets) $(CC) -o $@ $(objets) -lm
La variable $^ repr�sente toutes les d�pendances d'une r�gle, par exemple tous les objets lors de la cr�ation du projet :
CC= gcc objets= blob.o tga.o tri_blob.o blob: $(objets) $(CC) -o $@ $^ -lm
La variable $< repr�sente la premi�re d�pendance, on peut l'utiliser pour compiler un source C :
CC= gcc -Wallblob.o: blob.c $(CC) -o $@ -c $<
L'utilisation des variables automatiques est particuli�rement puissante lors de la d�finition de r�gles implicites qui s'appliquent � plusieurs fichiers. Par exemple, il possible, avec une seule r�gle implicite, de compiler tous les sources C :
%.o: %.c $(CC) -o $@ -c $<
Le but est d�fini par un pattern, ou un joker : %.o . Cette r�gle indique que chaque fichier .o n�cessaire � la cr�ation du projet est obtenu � partir du fichier .c correspondant et que la commande $(CC) -o $@ -c $< permet de cr�er le fichier objet � partir du fichier source. Les variables automatiques permettent de retrouver le nom du fichier concern� par la r�gle. En r�sum�, il est donc possible de compiler un projet quelconque avec un Makefile de quelques lignes, par exemple blob :
CC= gcc # compilateur CFLAGS= -Wall # options de compilation pour les sources C objets= blob.o tga.o tri_blob.o blob: $(objets) $(CC) -o $@ $^ -lm %.o: %.c $(CC) $(CFLAGS) -o $@ -c $<

4.3 Génération automatique des noms de fichiers

Il est possible d'utiliser des substitutions dans les noms de fichiers, par exemple pour g�n�rer automatiquement les noms des fichiers objets � partir des noms des fichiers sources, en rempla�ant l'extension .c par .o : SRC= main.c struct.c operation.c # nommage automatique des fichiers objets d'apres les noms des sources C OBJ= $(SRC:.c=.o) dans cet exemple $(OBJ) contiendra main.o struct.o operation.o exemple du makefile pr�cedent avec nommage automatique des objets a partir des fichiers sources .c : CC= gcc # compilateur CFLAGS= -Wall # options de compilation pour les sources C sources= blob.c tga.c tri_blob.c objets= $(sources:.c=.o) blob: $(objets) $(CC) -o $@ $^ -lm %.o: %.c $(CC) $(CFLAGS) -o $@ -c $<

5. Génération automatique des dépendences

Lorsque un projet prends un petit peu d'importance, de nombreux headers (.h) sont utilis�s. Lorsque l'on modifie un header, il est n�cessaire de recompiler tous les sources qui l'utilisent. Il est �videment possible d'ajouter les fichiers .h comme d�pendance des sources, mais c'est particuli�rement fastidieux. Le compilateur est le mieux plac� pour savoir quels sont les headers utilis�s par chaque source et il est m�me possible de r�cup�rer ces d�pendances au format des r�gles de make. Il suffit de stocker cette nouvelle r�gle dans un fichier (.d par convention) et de l'inclure dans le makefile. Pour vous convaincre, essayer sur un de vos sources : gcc -o source.d -MM source.c cat source.d regardez les options -MM dans le manuel de gcc (la suite, bient�t ...) exemple de makefile complet : 3 projets diff�rents sont d�crits, archivage de l'ensemble, num�rotation automatique du projet, g�n�ration automatique de changelog ... # correction TP5 systemes d'exploitation 2003 : partage de fichiers CFLAGS= -g -Wall -D_REENTRANT LDFLAGS= LIB= -lpthread CC= gcc $(CFLAGS) LD= gcc # serveur d'annonce ASRC=annonce.c \ annonce_sha1_f.c \ mtfifo.c \ critique.c \ socket.c \ fichier.c \ util.c \ debit.c \ version.c \ sha1.c \ sha1_f.c # nommage automatique des fichiers objets d'apres les noms des sources C AOBJ= $(ASRC:.c=.o) # nommage automatique des fichiers de dependance d'apres les noms des sources C ADEP= $(ASRC:.c=.d) # serveur de fichiers FSRC=serveur_fichier.c \ serveur_fichier_sha1.c \ serveur_fichier_sha1_f.c \ mtfifo.c \ critique.c \ socket.c \ fichier.c \ util.c \ debit.c \ version.c \ sha1.c \ sha1_f.c # nommage automatique des fichiers objets d'apres les noms des sources C FOBJ= $(FSRC:.c=.o) # nommage automatique des fichiers de dependance d'apres les noms des sources C FDEP= $(FSRC:.c=.d) #client CSRC=client_sha1.c \ client_sha1_f.c \ mtfifo.c \ critique.c \ socket.c \ fichier.c \ util.c \ debit.c \ version.c \ sha1.c \ sha1_f.c # nommage automatique des fichiers objets d'apres les noms des sources C COBJ= $(CSRC:.c=.o) # nommage automatique des fichiers de dependance d'apres les noms des sources C CDEP= $(CSRC:.c=.d) # ensemble des fichiers de dependance DEP= $(ADEP) $(FDEP) $(CDEP) # numero de version auto include build # BIN=annonce \ serveur_fichier \ client # VER=annonce.version \ serveur_fichier.version \ client.version .PHONY: all all: $(BIN) $(VER) changelog # numerotation des compilations completes build: echo BUILD= 1 > build rebuild: build @echo BUILD= `expr $(BUILD) + 1` > build # genere un fichier C avec une chaine de caracteres decrivant la version du projet version.c: build @echo "char version_id[]= \"build $(BUILD)\";" > $@ # conserve les commentaires sur les versions du projet changelog: build @echo -- changelog build $(BUILD) @echo -e --\\n$(USER)@`hostname -s` -- build $(BUILD) $(HOSTTYPE) -- `date` > [email protected] @if test -f $@; then cat $@ >> [email protected]; fi @mv [email protected] $@ annonce.version: annonce @echo $< -- $(USER) build $(BUILD) -- `date` > $@ annonce: $(AOBJ) @echo -- build $(BUILD) $(LD) $(LDFLAGS) -o $@ $+ $(LIB) # serveur_fichier.version: serveur_fichier @echo $< -- $(USER) build $(BUILD) -- `date` > $@ serveur_fichier: $(FOBJ) @echo -- build $(BUILD) $(LD) $(LDFLAGS) -o $@ $+ $(LIB) # client.version: client @echo $< -- $(USER) build $(BUILD) -- `date` > $@ client: $(COBJ) @echo -- build $(BUILD) $(LD) $(LDFLAGS) -o $@ $+ $(LIB) # %.o: %.c $(CC) -o $@ -c $< %.d: %.c $(CC) -MM -MD -o $@ $< .PHONY: clean clean: rebuild rm -f $(BIN) $(OBJ) $(DEP) *.i *.s .PHONY: tarball tarball: @echo -- build $(BUILD) -tar -zcf annonce_b$(BUILD).tar.gz Makefile build changelog *.[ch] # inclusion des dependances -include $(DEP)

6. Conclusion

Il existe de nombreuses fonctionnalit�s, lisez le manuel : info make

Tag » Apprendre Makefile