L'essentiel de ce document est une synthèse du document en anglais que l'on peut trouver à l'adresse http://www.gnu.org/manual/make/html_mono/make.html sur le site officiel de GNU (http://www.gnu.org/).
Tous les makes ne sont pas des GNU make. Au département d'informatique de l'ULP, tous les PC ont seulement GNU make. Par contre, plusieurs makes sont installés sur le serveur ada :
L'objectif premier de l'outil make est de permettre de construire des fichiers, éventuellement à partir d'autres fichiers, par des commandes shell, et de manière automatique. Exemples :
Évidemment, l'outil make ne sait pas, à l'avance, distinguer les fichiers que vous considérez comme inutiles de ceux que vous voulez conserver. Il ne sait pas non plus quels fichiers il faut construire, à partir de quels autres fichiers, ni dans quelles conditions et avec quelles commandes il faut mettre à jour ces fichiers. C'est l'utilisateur qui doit indiquer tout cela en éditant un fichier qui contiendra toutes les informations nécessaires. Par exemple pour construire le fichier scanline à partir des informations contenues dans le fichier scanline.mk, il faut taper : « make scanline -f scanline.mk ». Il est également possible de taper simplement « make scanline » Mais alors les informations qui permettent de construire le fichier scanline doivent être contenues dans un fichier qui s'appelle Makefile (ou makefile). Ainsi la commande précédente sera l'exact équivalent de : « make scanline -f Makefile » Dans les sections suivantes, les fichiers contenant ces informations seront tous appelés des makefiles. Nous allons voir comment écrire un fichier de type makefile et comment l'utiliser.
Les makefiles sont, en grande partie, constitués d'une suite de règles. Une règle (rule en anglais) est constituée de :
cible1 cible2 cible3 ... cibleN : prerequis1 prerequis2 prerequis3 ... prerequisD commande1 ; commande2 ; commande3 ; ... ; commandeCou bien
cible1 cible2 ... cibleN : prerequis1 prerequis2 ... prerequisD ; commande1 ; commande2 ; ... ; commandeCou encore
cible1 cible2 cible3 ... cibleN : prerequis1 prerequis2 prerequis3 ... prerequisD commande1 commande2 commande3 ... commandeCTous les cas intermédiaires sont possibles :
cible1 cible2 cible3 ... cibleN : prerequis1 prerequis2 prerequis3 ... prerequisD ; commande1 ; commande2 commande3 ; ... ; commandeCLes cibles doivent être séparées par des espaces (blancs ou tabulations), ainsi que les prérequis. Par contre, deux commandes consécutives sur une même ligne doivent être séparées par des points-virgules « ; ».
Les cibles et les prérequis doivent se trouver sur une même ligne. Or pour des raisons esthétiques et de lisibilité, il est quelquefois préférable de répartir certaines expressions sur plusieurs lignes. Pour cela on utilise le caractère « \ » comme dans les programmes C et dans tout ce qui utilise le préprocesseur cpp. Lorsque make lit un fichier makefile, il ignore toutes les occurrences du caractère « \ » suivi d'un caractère « \n » (newline). Donc la règle :
cible1 cible2 \ cible3 ... cibleN : \ prerequis1\ prerequis2 prerequis3 \ ... prerequisD ; commande1 ; commande2 commande3 ; ... ; commandeB ; \ commandeCest l'exact équivalent de la règle :
cible1 cible2 cible3 ... cibleN : prerequis1 prerequis2 prerequis3 ... prerequisD ; commande1 ; commande2 commande3 ; ... ; commandeB; commandeC
Lorsqu'une ligne commence avec le caractère « \t », ce qui suit ce caractère est interprété par make comme une commande shell. Réciproquement, une ligne de commande ne peut pas se trouver tout à fait en début de ligne, elle doit nécessairement être précédée d'un caractère « \t ». La seule exception est une commande séparée de la commande précédente par un « ; » suivi d'un « \ » suivi d'un « \n » (cf. ce qui se passe entre commandeB et commandeC ci-dessus). Évidemment, un ensemble de caractères « espace » de même longueur ne remplit pas du tout la même fonction. Ce fonctionnement peut être parfois déroutant au départ. Il arrive souvent que l'on écrive une commande que make ne considère pas comme une commande parce qu'il n'a pas trouvé le caractère « \t » au début de la ligne. Réciproquement il arrive souvent aussi qu'un caractère « \t », tapé en début de ligne pour des raisons esthétiques et aussi pour éviter de taper N fois sur la touche « espace » , ait dérouté make qui a essayé d'interpréter ce qui suit comme une commande shell.
# Ceci est un exemple de makefile coucou.dvi : coucou.tex latex coucou.tex article.ps : coucou.dvi figure.eepic dvips coucou.dvi -o article.ps bang : banner coucouSuivons le parcours de make. Lorsqu'on tape :
make coucou.dvimake s'intéresse à la première règle. Le seul prérequis de cette règle est coucou.tex qui ne correspond à la cible d'aucune autre règle. Donc make peut se désintéresser de toutes les autres règles et se demander s'il faut ou non mettre à jour le fichier coucou.dvi et, le cas échéant, de réaliser effectivement la mise à jour. Par contre, lorsqu'on tape :
make article.psla situation est tout à fait différente. make s'intéresse à la deuxième règle. Dans cette règle, un des deux prérequis (coucou.dvi) est la cible de la première règle. Donc, dans un premier temps, l'exécution de la commande de la deuxième règle (dvips coucou.dvi -o article.ps ) n'est pas du tout à l'ordre du jour. make doit d'abord s'intéresser à la règle dont coucou.dvi est la cible et, le cas échéant, exécuter la commande associée. C'est seulement quand la première règle a été traitée qu'on peut s'intéresser à la commande associée à la cible article.ps et éventuellement l'exécuter. Si plusieurs prérequis d'une règle R sont des cibles d'autres règles, alors avant de pouvoir exécuter la commande associée à R, make devra d'abord s'intéresser à toutes ces règles, dans l'ordre où elles ont été écrites dans la liste des prérequis.
Ces cibles sont un moyen pratique de demander au makefile de réaliser certaines actions qui n'ont pas nécessairement trait à la construction de fichiers. Ainsi, dans l'exemple du paragraphe précédent, la troisième règle a une cible qui est phony. En l'occurrence la commande de cette règle n'effectue rien d'utile. Elle se contente d'écrire à l'écran « coucou » en grosses lettres. Pour exécuter cette commande, il suffit de taper : make bang. Le résultat est un affichage à l'écran qui dépend du système (cf. http://coriolis.u-strasbg.fr/habibi/PPP/BannerLinux.html pour voir le résultat obtenu sur les machines Linux du département, et http://coriolis.u-strasbg.fr/habibi/PPP/BannerSolaris.html pour le résultat obtenu sur steed).
Dans les paragraphes précédents, nous mentionnions simplement que make « se demandait s'il faut ou non exécuter la commande d'une règle ». Quelles sont les conditions qui déclencheront l'exécution de ces commandes ? Supposons que le répertoire dans lequel se trouve le makefile s'appelle rep (entre nous, aucun répertoire ne devrait s'appeler ainsi, tout répertoire devant avoir un nom significatif et descriptif de son contenu). Supposons qu'on s'intéresse à une règle pour savoir si sa commande doit être exécutée ou non. make applique l'algorithme suivant :
Si la cible est le nom d'un fichier appartenant au répertoire rep, Alors Si la règle comporte des prérequis, Alors make compare les dates de dernière modification de la cible et des prérequis. Si au moins un des prérequis est plus jeune que la cible, cela signifie que, depuis la dernière mise à jour de la cible, les prérequis ont été modifiés. Alors la commande est exécutée pour remettre à jour la cible. Sinon (si la règle ne comporte aucun prérequis) la cible est considérée inconditionnellement à jour et la commande associée n'est JAMAIS exécutée (En général on cherche rarement à tomber dans ce cas; on y tombe souvent par erreur.) Sinon (si la cible ne correspond à aucun nom de fichier appartenant au répertoire rep, Si la règle comporte des prérequis Alors make interprète tout de même la cible comme un fichier qui n'existe pas encore ou qui a été effacé et qu'il faut remettre à jour. La commande associée à la règle est exécutée. Sinon (si la règle ne comporte aucun prérequis) make interprète la cible comme phony, et donc exécute inconditionnellement la commande de la règle
On peut se demander ce qui pourrait pousser un programmeur de makefile à écrire une règle pour construire un fichier dans laquelle il ne mettrait aucun prérequis. En réalité, le programmeur ne pensait sans doute pas à faire une telle règle. Il voulait sans doute faire un phony, mais le nom de cette cible s'est révélé être celui d'un vrai fichier. Par exemple, supposons que vous ayez un makefile comme celui de notre exemple (programme article.ps). Le phony « bang » ne fonctionne que s'il n'existe aucun fichier du nom de « bang ». À titre d'expérience, si on crée un fichier du nom de « bang », alors le fait de taper
make bangprovoque le message suivant :
make: `bang' is up to date.ce qui signifie clairement que make considère que par « bang » nous avons voulu désigner le fichier et non l'action à effectuer. Si cela vous empêche d'écrire « coucou » en grosses lettres, le problème n'est pas tellement affolant. Par contre, supposez qu'au lieu d'un phony « bang » vous ayez utilisé un phony « sauvegarde » qui enregistre tous les fichiers de votre répertoire (ou même de votre compte) dans un seul fichier compressé, dans le but de le graver sur un CD. C'est souvent ce qu'on fait par précaution avant de réinstaller un système, de modifier les partitions, etc. Imaginez qu'après une tentative infructueuse ou quelque chose de ce genre, vous ayez justement dans votre répertoire un fichier vide du nom de « sauvegarde ». Dans ce cas, lorsque vous taperez
make sauvegarderien ne sera sauvegardé. On aura simplement le message « sauvegarde is up to date », ce qui est souvent interprété, à tort, comme « la sauvegarde a été effectuée ». Pour éviter ce genre de problème, on peut utiliser ce qu'on appelle une directive du makefile (nous verrons les directives plus loin). La directive .PHONY indique explicitement à make quelles cibles sont phonys. Nous changeons notre makefile :
# Ceci est un exemple de makefile coucou.dvi : coucou.tex latex coucou.tex article.ps : coucou.dvi figure.eepic dvips coucou.dvi -o article.ps .PHONY : bang sauvegarde # cette directive indique a make que bang et sauvegarde ne doivent pas etre # consideres comme des noms de fichiers, meme si ces fichiers existent bang : banner coucou sauvegarde : tar -cvf backup.tar * gzip backup.tarNB : .PHONY n'est pas une règle, mais une directive. Donc le fait d'avoir ou non dans votre répertoire un fichier du nom de « .PHONY » ne change rien au comportement de cette directive.
Reprenons les programmes de l'exercice 1. Dans l'exercice 1, à chaque fois qu'un seul des fichiers sources ou entêtes était modifié, tous les fichiers sources étaient recompilés. Nous voulons que si seul un sous-ensemble des fichiers sources a été modifié, alors ce seul sous-ensemble soit recompilé. Ceci suppose qu'on puisse garder les fichiers intermédiaires vecteur.o, liste.o, matrice.o et main.o pour les fichiers qui n'ont pas été modifiés. Ensuite, une fois que tous les fichiers objets auront été mis à jour, on procédera à l'édition de liens pour créer l'exécutable. Écrivez un makefile avec plusieurs règles où une telle compilation séparée est réalisée lorsqu'on tape simplement make.
Indication : le fait qu'une commande lancée par make retourne avec un statut non nul (erreur) peut entraîner un arrêt de make pour cause d'erreur. Si nous voulons que les erreurs engendrées par une commande soient ignorées, il faut faire précéder la commande du caractère « - » (moins).
Une variable est une chaîne de caractères qui peut désigner des listes de noms de fichiers, des options à passer à un compilateur ou à une autre commande, des noms de programmes à exécuter, des listes de répertoires où chercher d'autres fichiers, où écrire d'autres fichiers, etc. Cette chaîne de caractères peut contenir n'importe quels caractères à l'exception de « = », « : », « # » ou des espaces (« », « \t », « \n », etc). Ceci dit, il est recommandé d'éviter tous les caractères autres que les lettres, les nombres et les « _ » car les autres caractères pourront être utilisés à l'avenir comme des caractères ayant une signification. D'autres part dans certains environnements, ces autres caractères peuvent déjà avoir une signification particulière.
Il y a plusieurs types de variables pour make :
Ici, le terme automatique n'a pas du tout le même sens que dans le langage C. Dans les makefiles, il existe des variables que vous n'avez pas besoin de mettre à jour. Elles sont définies et remises à jour par make. Elles ne peuvent être utilisées que dans une règle et désignent une des cibles, les prérequis, un sous-ensemble de prérequis, etc.
Il existe d'autres variables automatiques ($%, $+, $*, etc.). Pour plus de détails, consulter http://www.gnu.org/manual/make/html_mono/make.html.
Nous verrons plus loin comment utiliser ces variables de façon plus intéressante.
Lorsqu'on écrit :
var=valeur1 valeur2 valeur3 ... valeurnon définit une variable var. Dans tout le fichier makefile (que ce soit avant ou après cette définition) toutes les occurrences de $(var) ou de $var seront remplacées par valeur1 valeur2 ... valeurn. Cette opération est appelée une substitution (en anglais expansion) de la variable var. Plus précisément, lorsque $(var) ou $var doit être évalué, make parcourt le fichier makefile à la recherche des définitions de var. Il ne devrait y avoir qu'une seule définition pour une variable donnée. Mais s'il y en a néanmoins plusieurs, c'est la dernière définition dans le fichier qui prime sur les autres. Si la définition d'une telle variable var contient la référence à d'autres variables, alors ces autres variables sont substituées avec le même algorithme (en cherchant la définition de cette autre variable en commençant par le début du fichier et en ne gardant que la dernière définition s'il y en a plusieurs). Par exemple :
echo NonNon!!! NonNon!!!Ce type de substitution est appelée substitution récursive (par opposition à substitution simple). Dans les autres versions de make (autres que GNU make) c'est le seul type de variable que l'on puisse utiliser. Donc pour des raisons de portabilité, il est conseillé d'utiliser les variables à substitutions récursives. Le grand intérêt de ce type de variable est qu'il n'est pas nécessaire que l'endroit (dans le fichier makefile) où la variable est définie se trouve avant l'endroit où elle est utilisée. Par contre, un inconvénient est qu'on ne peut pas ajouter une variable à une liste déjà existante. En particulier une définition comme :
PATH = $(PATH) /usr/sbinentraînerait, en principe, une boucle infinie et, en pratique, provoque une erreur. C'est la raison pour laquelle GNU make permet d'utiliser les variables à substitution simple. Pour plus de détails sur les différents types de variables et de substitutions, cf. http://www.gnu.org/manual/make/html_mono/make.html.
Pour pouvoir ajouter quelque chose à une variable (à substitution récursive ou simple), on utilise l'opérateur binaire +=.
variable = valeur variable += davantageest équivalent à
temp = valeur variable = $(temp) davantagesauf qu'aucune variable temp n'est définie.
Les variables peuvent être utilisées dans n'importe quel contexte, cible, prérequis, commande, directive, nom de nouvelle variable, etc. D'autre part les variables sont substituées verbatim. Par exemple le makefile suivant :
lettre_c = c lettre_g = g un_point = . prog.o : prog$(un_point)$(lettre_c) $(lettre_g)$(lettre_c)$(lettre_c) - $(lettre_c) prog$(un_point)$(lettre_c)est l'exact équivalent de :
prog.o : prog.c gcc -c prog.cOn peut même remplacer la définition
lettre_g = gpar
debut = lett fin = re_g $(debut)$(fin) = gCe qui précède ne se veut en aucun cas un exemple de makefile lisible. Il est même fortement recommandé d'écrire les makefiles autrement. Le seul mérite de cet exemple est de montrer comment les variables peuvent fonctionner.
Un des multiples intérêts des variables est de limiter la répétition d'une chaîne de caractères, ce qui est non seulement fastidieux, mais peut entraîner des erreurs. En effet, lors d'un changement, on oublie souvent de corriger une des occurrences. C'est pourquoi, le plus possible, il faut tenter d'écrire chaque chose en un seul exemplaire. Ainsi, on peut définir une variable dont la valeur est la chaîne de caractères à répéter. Ensuite, au lieu de répéter la chaîne de caractères, on répète la variable. Par exemple dans le programme de l'exercice 1, le nom de l'exécutable et celui des fichiers sources est répété au moins à deux reprises. Est-ce qu'il ne serait pas possible d'utiliser une variable pour que ces deux objets n'apparaissent qu'une seule fois ?
Un autre des intérêts des variables est de permettre la communication avec make. Certaines variables ont une signification particulière pour make. Nous verrons cela plus en détail avec les règles implicites. Intéressons-nous à la variable SHELL qui définit avec quel interpréteur les commandes seront exécutées. Comme nous l'avons mentionné plus haut, cette variable d'environnement, contrairement aux autres variables du même type, n'est pas héritée de l'environnement et vaut « /bin/sh » par défaut. Définissez cette variable dans le makefile et donnez-lui une autre valeur. Que se passe-t-il lorsqu'on définit cette variable SHELL par commande et qu'on lui donne encore une autre valeur. Quelle est la valeur qui prévaut ? La valeur fournie par le makefile ou celle de la commande ? Même expérience avec une autre variable comme par exemple USER.
Manipulons des fichiers de format LATEX. Ce sont des fichiers comportant des caractères Ascii, mais qui peuvent coder, beaucoup d'autres caractères spéciaux en français, en espagnol, en allemand, etc., des images, des expressions mathématiques, etc. Ce genre de format s'oppose à celui des outils de type Wysiwyg (what you see is what you get) comme Word qui sont plus conviviaux. Un des avantages de LATEX par rapport à Word est qu'il peut fonctionner sous n'importe quel environnement puisque les fichiers LATEX sont des fichiers Ascii.
http://icps.u-strasbg.fr/~vivien/Enseignement/PPP-2001-2002/planning.texest un fichier au format LATEX. Pour pouvoir examiner l'aspect du document final, on peut transformer ce fichier LATEX en un fichier au format pdf (portable document format) qu'on peut visualiser sur toutes les plates-formes. La transformation du fichier planning.tex se fait par la commande : « pdflatex planning.tex » ce qui produit un fichier planning.pdf qu'on peut lire avec l'outil ghostview (ou gv ou GSView ). Pour l'impression papier, il faudrait plutôt transformer le fichier LATEX en un fichier au format PostScript. La commande : latex planning.tex transforme le fichier planning.tex en un fichier planning.dvi qu'on ne peut pas éditer, mais qui donne l'aspect final du document. Un fichier dvi peut être lu par la commande : « xdvi planning.dvi » Enfin un fichier dvi peut être transformé en un fichier PostScript par la commande « dvips planning.dvi -o » qui a pour effet de construire le fichier planning.ps qu'on peut également regarder avec l'outil ghostview (ou gv ou GSView).
Essayer ces commandes sur le fichier planning.tex, au départ sans passer par un makefile. Ensuite écrire un makefile qui puisse, au choix, construire un fichier dvi, un fichier ps ou un fichier pdf. Le fichier pdf dépend des fichiers tex et des fichiers représentant les figures (ici Figure.eepic ). Le fichier dvi dépend du fichier tex. Le fichier ps dépend du fichier .dvi et du fichier représentant la figure.
Écrire une règle qui permette également d'éliminer les fichiers log, aux, dvi et autres backup dont le nom se termine par bak ou par « ~ ». Écrire une règle qui permette en plus d'éliminer les fichiers ps et pdf. Enfin une règle qui permette d'imprimer le fichier seulement si le fichier PostScript est à jour.
À présent, le makefile doit certainement être constellé de fichiers planning.*. Si l'on veut changer le nom du fichier en autre chose, ou d'utiliser le makefile pour un autre fichier LATEX, il faut changer beaucoup d'instances de fichier. Donc on demande que le nom « planning » n'apparaisse qu'une seule fois dans le makefile. Pour cela on utilisera une variable NOM à qui on donnera la valeur planning. À partir de cette variable, on demande de construire le nom des fichiers LATEX, dvi, pdf, PostScript, etc. et de récrire le makefile en fonction de cela.
Comment pourrait-on utiliser ce même makefile (sans aucune modification) pour appliquer les mêmes traitements à un fichier specifications.tex ?
Soit une arborescence de fichiers. Dans un premier temps nous prendrons une arborescence limitée à trois niveaux, mais le principe peut se généraliser à un nombre quelconque de niveaux, à tout un compte, ou à toute une machine. On se place dans un répertoire rep et on étudie les sous-répertoires qui se trouvent dans ce répertoire. Appelons les subrep_1, subrep_2, subrep_3, ..., subrep_n. Chacun de ces sous-répertoires contient un makefile qui permet de construire un certain nombre de fichiers qui permettent de faire une sauvegarde, de nettoyer le répertoire, etc., etc. Pour utiliser les makefiles dans chaque répertoire on peut aller dans chacun de ces répertoires (cd subrep_i) et taper les commandes make adéquats : par exemple make arg_1 arg_2 arg_3 ... arg_N (quelques soient les arguments arg_i). Mais il est également possible de taper ces commandes depuis le répertoire rep en tapant :
make -C subrep_i arg_1 arg_2 arg_3 ... arg_N
Lors de l'appel récursif de make, la commande « make » utilisée est celle définie par défaut sur le système, qui n'est pas forcément celle que vous avez invoquée. Pour contourner ce problème, plutôt que d'utiliser la commande make on aura recours à la variable MAKE qui a pour valeur le make invoqué sur le makefile :
${MAKE} -C subrep_i arg_1 arg_2 arg_3 ... arg_N
On suppose que dans le répertoire projet3, nous avons d'une part un sous-répertoire simulation, un sous-répertoire visualisation, un sous-répertoire rapport et un autre sous-répertoire planning. Les deux premiers répertoires contiennent les fichiers source de programmes écrits en langage C. Le troisième répertoire contient la documentation que vous avez écrite (au format LATEX) au sujet de ces programmes et enfin le quatrième répertoire contient votre planning également écrit en LATEX. On suppose que chacun de ces sous-répertoires possède un makefile. Pour rapport et planning, les makefiles permettent de construire des documents lisibles ou imprimables à partir de fichiers LATEX, de les imprimer, ou de faire le ménage (comme dans l'exercice 8). Construire cette arborescence dans votre compte, en prenant pour programme de simulation et de visualisation les fichiers vides utilisés dans les premiers exercices. Pour les fichiers LATEX, prendre les fichiers de l'exercice 8. Utiliser les makefiles que vous avez vous-mêmes écrits.
En utilisant une commande shell (sans encore écrire un makefile) nettoyer tous les sous-répertoires, non pas en allant dans chacun d'eux, mais en restant dans le répertoire projet3. De même, toujours avec une commande shell, compiler tous les programmes, préparer tous les documents pour l'impression, faire un fichier archive compressé de tout ce que contient le répertoire projet3 en vue d'une sauvegarde.
À présent, écrire un makefile pour le répertoire projet3 qui permet d'effectuer ces opérations de façon automatique (nettoyage global, compilation globale, sauvegarde globale, transformation globale de tous les fichiers LATEX en pdf ou en ps, etc.)
ls af_*g*.caffiche à l'écran tous les fichiers ou sous-répertoires du répertoire courant dont le nom commence par « af_ », se termine par « .c », et contient par ailleurs une occurrence de la lettre « g ». Le caractère « * » représente une chaîne de caractères quelconque, y compris la chaîne vide. D'autre part la commande shell :
ls toto.?affiche à l'écran tous les fichiers ou sous-répertoires du répertoire courant dont le nom commence par le préfixe « toto. » suivi d'un unique caractère. « ? » représente un unique caractère. De même
ls ??c*affiche à l'écran tous les fichiers ou sous-répertoires dont le troisième caractère du nom est un « c ». Enfin la commande shell :
ls Version_[a-f]affiche à l'écran tous les fichiers ou sous-répertoires dont le nom commence par « Version_ » et dont la dernière lettre est une des lettres parmi a, b, c, d, e,et f. Autre exemple :
ls [0-5][0-9]_NavierStokes 6[0-4]_NavierStokesaffiche à l'écran tous les fichiers ou sous-répertoires dont le nom se termine par « _NavierStokes » précédé d'un nombre à deux chiffres compris entre 00 et 64. Plus précisément, « ls [0-5][0-9]_NavierStokes » concerne les fichiers ou sous-répertoires 00_NavierStokes, ..., 59_NavierStokes alors que l'expression « ls 6[0-4]_NavierStokes » concerne les fichiers ou répertoires 60_NavierStokes, ..., 64_NavierStokes.
Évidemment, ces wildcards peuvent être utilisées dans les commandes shells de toutes les règles, puisque ces commandes sont envoyées à un shell. Mais ces wildcards peuvent également être utilisées dans les cibles et les prérequis (qui, eux, ne sont pas envoyés à un shell). Lorsque make rencontre une wildcard, (dans une cible, dans une commande ou dans les prérequis d'une règle) il la remplace par la liste des noms (séparés par des espaces) de tous les fichiers et sous-répertoires du répertoire courant qui correspondent à la chaîne de caractères dans laquelle se trouve le wildcard.
À votre avis, pourquoi est-ce que le fait de remplacer froidement la règle proposée à l'exercice 11 (*.o : *.c ; gcc -c *.c) par la règle proposée ci-dessus (%.o : %.c ; gcc -c $^) ne permet pas d'obtenir un makefile qui fonctionne correctement ?
Que se passe-t-il si on remplace les occurrences de « *.o » par une variable $(OBJETS) représentant le nom de tous les fichiers objets (Liste.o Vecteur.o Matrice.o main.o) ?
Nous nous intéresserons très brièvement aux directives include, define et export, et plus longuement aux directives conditionnelles ifeq, ifneq, ifdef et ifndef.
Il existe d'autres directives comme override, .IGNORE, .PRECIOUS, .DEFAULT, .SILENT, .PHONY (vue plus haut), etc. Pour plus de détails sur ces directives, cf. http://www.gnu.org/manual/make/html_mono/make.html.
Lorsqu'un makefile contient une directive du type
include <nom_fichiers>make, en lisant cette directive, cesse temporairement de lire le makefile auquel appartient cette directive et lit, dans l'ordre, le ou les fichiers dont le nom appartient à la liste <nom_fichiers>. Lorsque cette lecture est finie, make recommence la lecture du makefile d'origine là où il l'avait laissée, c'est-à-dire à partir de la directive include. Cette liste d'arguments <nom_fichiers> peut contenir des motifs, des noms de variables, etc. Par exemple
include toto titi *.mk $(MKS)En l'occurrence, tout se passe comme si make avait remplacé la directive include par le contenu du fichier toto, suivi de celui du fichier titi, de celui de tous les fichiers dont le nom se terminent par « .mk » et par les fichiers dont le nom est contenu dans la valeur de la variable MKS. Ceci peut être intéressant quand plusieurs makefiles doivent avoir un ensemble de variables ou de règles en commun. Dans ce cas, toutes ces variables et règles peuvent être écrites dans un seul fichier et inclus par tous les makefiles (un peu comme les fichiers entêtes). Autre intérêt : certaines commandes permettent de générer des dépendances automatiquement (cf. gcc -M) et écrivent ces dépendances dans un fichier. Une directive include permet d'inclure ces dépendances dans le makefile.
De même que pour les commandes shell, le fait d'écrire « -include ... » au lieu de « include ... » permet de ne pas arrêter make pour cause d'erreur, par exemple au cas où les fichiers à inclure n'existeraient pas.
La directive define est une autre manière de définir des variables. L'intérêt de la directive define par rapport à la définition classique de variables (section 4) est que l'on peut inclure des retours à la ligne (« \n ») dans la variable. Ceci qui permet de définir des variables qui représentent des séquences de commandes. Voici la syntaxe de cette directive :
export varla variable var sera définie dans tous les sous-makes qui pourront être lancés à partir du makefile auquel appartient la directive. De plus la valeur de var sera la même que dans le makefile d'origine au moment où le sous-make a été lancé. Si l'on veut exporter toutes les variables, on peut simplement écrire :
exportLorsqu'on veut exporter toutes les variables sauf certaines d'entre elles, on utilise la directive unexport qui empêche l'héritage de variables. Par exemple si l'on veut que toutes les variables soient héritées sauf la variable var, on écrit :
export
unexport var
Ces directives ne sont pas à confondre avec la fonction if qui sera vue à la section suivante (8). Dans l'ensemble, ces directives permettent d'ignorer toute une partie d'un makefile dans certaines conditions et, éventuellement, d'ignorer d'autres parties si ces conditions ne sont pas réunies. (Donc il ne s'agit pas d'exécuter certaines commandes pendant l'exécution.) Ceci permet souvent d'avoir des programmes qui sont plus portables et qui peuvent s'adapter à un plus grand nombre de situations.
Les deux directives ont la même la syntaxe :
Toutes les variables dans les arguments argument1 et argument2 sont substituées. Si les deux arguments sont égaux, la conditionnelle est équivalente à texte1 (texte2 n'est pas pris en compte), et s'ils sont différents, la conditionnelle est équivalente à texte2. La directive ifneq a le même comportement, mais en intervertissant texte1 et texte2.
Détail important : ifeq, ifneq, ifdef et ifndef doivent être suivis d'une espace (précédant les arguments).
Écrivez un makefile pour pouvoir compiler sur ada comme sur les distas le programme imscan du TP 2 (http://icps.u-strasbg.fr/~vivien/Enseignement/PPP-2001-2002/sources-exemple.tgz) sachant que sur ada les librairies GMP et polylibgmp sont installées respectivement dans les répertoires /users/prof/vivien/IciEstLaGmp et /users/prof/vivien/LaPolylib, et sur les distas dans les répertoires /users/prof/vivien/LinuxGMP et /users/prof/vivien/LinuxPolylib. Le makefile devra déterminer de lui-même sous quel environnement il se trouve et qu'il en déduise la commande de compilation adéquate.
Il arrive souvent que l'on veuille utiliser la valeur d'une variable d'environnement ou d'une variable définie dans la ligne de commande, sans être certain à l'avance, que cette variable a bien été initialisée. On peut prendre des décisions différentes selon que la variable a été initialisée ou non. Le fait qu'une variable attendue n'ait pas été initialisée peut signifier une erreur. On peut réagir en initialisant la variable dans le makefile, ou en lançant d'autres commandes. Mais souvent il ne s'agit pas d'une erreur, mais simplement d'un fait qui nous informe des événements qui ont eu lieu auparavant.
Il arrive que la variable d'environnement USER ou la variable PWD ne soit pas héritée de l'environnement par make. Sur steed c'est USER et sur les distas c'est PWD. Nous voulons que notre makefile puisse fonctionner dans toutes les situations. Donc avant d'utiliser USER ou PWD, nous faisons un test pour savoir si ces variables ont été définies. Si elles n'ont pas été définies, vous les définirez en conséquence. Pour cela, on pourra utiliser la fonction shell (cf. 8.1) de makefile.
Cette section présente un certain nombre de fonctions. Ici on n'étudiera pas l'écriture de nouvelles fonctions, mais l'usage de celles qui ont déjà été définies.
La syntaxe générale d'appel d'une fonction dans un makefile est la suivante :
$(fonction argument1, argument2, ..., argumentN)le nom de la fonction est séparé des arguments par au moins une espace (plus d'éventuelles tabulations, etc). S'il y a plusieurs arguments, ils sont séparés par des virgules.
Il existe de nombreuses autres fonctions que celles décrites dans cette section et il est même possible de créer des fonctions. Pour plus de détails sur les différents types de fonctions, cf. http://www.gnu.org/manual/make/html_mono/make.html.
Cette fonction peut créer des chaînes de caractères résultant de l'exécution d'une commande shell. Si une commande shell, comme ps, produit un résultat sur plusieurs lignes, la fonction shell fournit un résultat sur une seule ligne. Par exemple, la commande ps renvoie :
et dans un makefile la ligne :
$(shell ps)a pour valeur :
PID TTY TIME CMD 1234 pts/0 00:00:01 bash 10999 pts/0 00:00:03 emacs 11219 pts/0 00:00:00 make 11220 pts/0 00:00:00 psc'est-à-dire la même chose, mais où les mots ont été mis bout à bout sur une seule ligne, séparés chacun par une espace.
Définir une variable SOURCES qui contient le nom de tous les fichiers du répertoire courant qui se terminent par *.c.
La fonction subst permet de remplacer une chaîne de caractères par une autre dans une liste de noms. Par exemple
$(subst ien,ienG, Tiens bien, il ne reste plus rien)vaut
TienGs bienG, il ne reste plus rienG
La fonction patsubst permet de reconnaître des motifs dans les mots d'une liste et de les remplacer par d'autres motifs. Le premier argument de patsubst peut contenir un caractère %. patsubst parcourt le troisième argument en cherchant les éléments qui vérifient le motif décrit par ce premier élément. Par exemple « const.% » représente tous les mots qui commencent par « const. » et qui se terminent par une chaîne quelconque X. Si le deuxième élément comprend aussi un caractère « % », ce « % » sera remplacé par la chaîne X.
$(patsubst %ien, %ienG, Tiens bien il ne reste plus rien)donne toujours
Tiens bienG il ne reste plus rienGPar contre, si un répertoire est composé d'autres répertoires, nommés Version.01, Version.02, Version.03, ..., Version.N, on ne peut pas utiliser la fonction subst pour les remplacer par 01_Version, 02_Version, 03_Version, ..., N_Version. Avec patsubst c'est possible :
$(patsubst Version.%, %_Version, Version.01 Version.02 Version.03 ... Version.N)vaut :
01_Version 02_Version 03_Version ... N_Version
Étant donnée une liste de noms de fichiers source, Vecteur.c Matrice.c Interface.c main.c, créer une liste de fichiers objets Vecteur.o Matrice.o Interface.o main.o.
Raccourci :
$(patsubst argument1, argument2, $(variable))est l'exact équivalent de :
$(variable:argument1=argument2)Attention au fait que dans le raccourci, on a bien écrit variable et non $(variable). Enfin, la présence d'espaces autour des opérateurs « : » et « = » peut considérablement perturber le fonctionnement de la substitution.
Reprenez l'exercice 16 en utilisant cette fois-ci le raccourci.
Soit un répertoire contenant les répertoires includes, sources et objects, contenant respectivement les fichiers entêtes, les fichiers sources et les fichiers objets. Le répertoire sources contient Vecteur.c, Matrice.c, Interface.c et main.c. Dans un premier temps le répertoire objects est supposé vide, mais il sera censé contenir les fichiers Vecteur.o Matrice.o Interface.o et main.o. Enfin on suppose, pour simplifier, que le répertoire includes contient les fichiers Vecteur.h, Matrice.h, Interface.h et main.h (ce qui est assez inhabituel, mais qui simplifie notre makefile). Nous verrons plus loin comment nous débarrasser de ce fichier main.h.
sources/Vecteur.c sources/Matrice.c sources/Interface.c sources/main.c
C'est une fonction qui prend en argument une chaîne de caractères et qui retourne la même chaîne de caractère, débarrassée des espaces éventuelles qui la précéderaient ou qui la suivraient. Ceci est intéressant surtout dans le cas où il faudrait comparer deux chaînes de caractères. Il serait contrariant que deux chaînes de caractères soient déclarées différentes uniquement à cause d'espaces.
Quelquefois il est intéressant d'extraire d'une liste de mots ceux qui vérifient (ou ne vérifient pas) un motif donné. Étant donnés une liste texte de mots séparés par des espaces blancs et un motif contenant éventuellement un caractère « % »,
$(filter motif, texte)représente le texte constitué des éléments de texte qui vérifient motif
$(filter-out motif, texte)représente le texte constitué des éléments de texte qui ne vérifient pas motif. En particulier :
$(filter %.c, main.c Vecteur.c Interface.o Matrice.h)vaut
main.c Vecteur.c
Étant donnée une liste de noms, cette fonction les trie dans l'ordre lexicographique et élimine tous les doublons.
lorsqu'on tape make titi on obtient l'affichage :
repertoires : sources/ objects/ objects/ ./
fichiers : Vecteur.c main.o Matrice.o MakefileAinsi, aucune des deux fonctions n'élimine les doublons.
Utilisation : « $(wordlist deb, fin, text) ». À partir d'une liste text de mots, wordlist renvoie la sous-liste qui commence par le debe mot de text et qui se termine par le fine mot de text.
Par exemple : « $(wordlist 2, 3, Vecteur.c Matrice.c Interface.c main.c) » renvoie la liste « Matrice.c Interface.c ».
Cette fonction permet de remplacer explicitement les expressions qui contiennent des wildcards en chaînes de caractères. Par exemple si le fichier makefile qui nous intéresse se trouve dans un répertoire où on peut trouver les fichiers Vecteur.c Matrice.c Interface.c main.c alors « $(wildcard *.c) » vaut « Vecteur.c Matrice.c Interface.c main.c ». Évidemment, dans le makefile suivant :
les deux commandes donnent exactement le même résultat. C'est un cas où l'usage de la fonction wildcard ne se justifie pas. Par contre dans :
Il s'agit d'une fonction qui permet de répéter un certain nombre de fois un texte, à chaque fois avec une substitution différente. Syntaxe : « LIST_TEXT=$(foreach x,list,text) ». Ici, LIST_TEXT sera une suite de N instances de text, N étant le nombre de mots dans list. Si x intervient dans la construction de text, dans la ie instance de text, (1 < i <= N), $(x) est remplacé par le ie mot de list. Ainsi dans l'exemple suivante :
LIST_BIDON=$(foreach x,$(LIST),Pense a souhaiter la bonne annee a $(x).)LIST_BIDON est simplement une suite de phrases du type « Pense a souhaiter la bonne annee a ... ». Simplement, à chaque fois, $(x) est remplacé par un des éléments de LIST. Par exemple pour :
LIST = Julien madame ton_pere le_chien le_tabouretla règle : « all : @ echo $(LIST_BIDON) » donne :
Vous trouverez dans l'archive http://icps.u-strasbg.fr/~vivien/Enseignement/PPP-2001-2002/Exercice19.tgz un répertoire Exercice19 qui contient 12 sous-répertoires :
00_AutomateDEtat 01_Simulation 02_Visualisation 03_MancheABalai 04_PlusieursAvionsOn peut imaginer que chaque répertoire correspond aux différentes étapes de développement d'un même programme, ou qu'il s'agit des différentes composantes d'un seul grand programme. (En réalité ces programmes ne font strictement rien.) Chaque nom de répertoire commence par un chiffre, un underscore, puis un nom qui qualifie le contenu supposé du répertoire. Dans chaque répertoire, l'exécutable et le fichier qui contient la fonction main portent également ce nom (avec les extensions adéquates). Par exemple dans 06_Protocole, l'exécutable s'appelle Protocole et le fichier qui contient la fonction main s'appelle Protocole.c. Le nom des autres fichiers n'a pas d'importance (ici, ils s'appellent rien.c et bidon.c dans tous les cas). Chaque répertoire contient un makefile qui permet de tout compiler et de tout nettoyer. Mais tous ces fichiers makefile sont strictement identiques. Pour les spécialiser, il faut leur donner, sur la ligne de commande, une valeur pour la variable ETAPE. Par exemple pour tout compiler dans le fichier 06_Protocole, il faut taper :
05_JoueurDistant 06_Protocole 07_SynchroTempReel 08_Inference 09_MakeMovie 10_EtapeTests
11_Final
${MAKE} -C 06_Protocole ETAPE=ProtocolePour tout nettoyer dans le même répertoire, il faut taper :
${MAKE} clean -C 06_Protocole ETAPE=ProtocoleL'objectif de cet exercice est de créer un makefile global situé au dessus des 12 répertoires et qui permet de compiler tous les programmes, de nettoyer les 12 répertoires et de les sauvegarder. Ainsi, qualitativement, l'objectif est le même que dans l'exercice 9. Seulement ici, on suppose que le nombre de sous-répertoires est trop grand pour qu'on puisse taper toutes les commandes une par une à la main. Nous voulons créer de façon automatique toutes les commandes de compilation et de nettoyage pour les 12 sous-répertoires.
Il existe un grand nombre d'opérations qui sont extrêmement courantes. Entre autres, les compilations de fichiers contenant du code C, C++, Java, ou même Fortran. La compilation de fichiers LATEX est également une opération courante. L'outil make permet de réaliser ces opérations (et bien d'autres) de façon implicite, c'est-à-dire sans que vous ayez explicitement écrit une règle qui permette de réaliser ces tâches.
Il existe en particulier une règle :
%.o : %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS)qui, même lorsqu'elle n'est pas écrite dans votre makefile, sera tout de même appliquée. On peut néanmoins changer la commande en écrivant cette règle de façon explicite et en y associant la commande voulue. Si l'on souhaite simplement empêcher la règle ci-dessus de s'appliquer, sans nécessairement y associer d'autres commandes, on peut écrire cette règle, mais lui associer de commande.
Par défaut, $(CC)vaut cc, mais on peut en changer la valeur. D'autre part $(CPPFLAGS) n'a aucun rapport avec le langage C++. C'est un flag qui a trait au préprocesseur cpp. Par exemple, dans un répertoire où nous avons les fichiers Vecteur.c Matrice.c Interface.c et main.c, le makefile suivant :
gcc -Wall -I/usr/local/include -I/home/habibi/include -c Liste.c gcc -Wall -I/usr/local/include -I/home/habibi/include -c Matrice.c gcc -Wall -I/usr/local/include -I/home/habibi/include -c Vecteur.c gcc -Wall -I/usr/local/include -I/home/habibi/include -c main.c gcc Liste.o Matrice.o Vecteur.o main.o -o scanlinePourtant aucune règle ne spécifie comment il faut transformer les fichiers de type *.c en fichiers de type *.o. De plus, le makefile ci-dessus indique simplement que les fichiers objets dépendent des fichiers entêtes, sans spécifier ce qu'il faut faire si un des fichiers entête est modifié. Pourtant lorsqu'on modifie, par exemple, Interface.h make recompile bien le fichier Interface.c pour tenir compte de ce changement. Ainsi, du moment que les fichiers sources et les fichiers objets ont les mêmes noms (à l'extension près) make peut automatiquement déduire les fichiers qu'il faut recompiler.
makedepend Vecteur.c Matrice.c Interface.c main.cajoute à la fin du makefile le texte :
# DO NOT DELETE Interface.o: Interface.h Matrice.h Vecteur.h main.o: Interface.h Matrice.h Vecteur.h Matrice.o: Matrice.h Vecteur.h Vecteur.o: Vecteur.hLa ligne contenant « DO NOT DELETE » est utilisée par makedepend pour savoir à quel endroit il peut écrire ces dépendances. Si aucune chaîne de ce type n'est trouvé dans le fichier, alors les dépendances sont ajoutées à la fin du fichier, même si elles s'y trouvent déjà.
Ajouter au makefile ci-dessus une règle phony dont la cible est depend. L'objectif est qu'avant la première compilation, ou après avoir ajouté ou supprimé une directive d'inclusion, il suffise de taper make depend pour que toutes les dépendances soient ajoutées à la fin du fichier.
This document was generated using the LaTeX2HTML translator Version 2K.1beta (1.50)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html -split 0 -nonavigation -show_section_numbers -toc_depth 5 TP03-GNUmake.tex
The translation was initiated by on 2002-03-18