Ce court article présente l'utilisation de make
pour des langages exotiques comme tcl, choisi pour
l'exemple principal de l'article. Il vous permettra d'écrire des Makefile simples et de comprendre ceux, plus compliqués,
générés par les assistants autoconf/automake.
Un Makefile est un simple fichier, nommé "Makefile" ou "makefile" la plupart du temps. Il est utilisé par le programme
make
pour exécuter un certain nombre d'actions, souvent pour compiler un programme à partir de ses
sources, mais pas seulement. Quand on tape make
, le programme cherche un fichier nommé "Makefile" dans
le répertoire courant. On peut toutefois lui spécifier un autre fichier à utiliser en utilisant l'argument -f suivi du fichier
à utiliser.
Un Makefile est composé de variables déclarées grâce à des lignes nom=valeur
. On peut ensuite s'en
servir en notant $(nom)
.
Un Makefile contient également des règles de la forme :
cible: dépendances commande_1 commande_2 ...
La cible
est la plupart du temps un fichier créé à partir des dépendances
par la suite de
commandes
(commande_1
, commande_2
, ...). Pour demander l'exécution de la partie du Makefile
correspondant à une cible, on se place dans
le répertoire du Makefile et on tape make nom_de_la_cible
. Vous reconnaissez certainement là ce que vous
tapez habituellement, c'est à dire make install
ou make clean
. Ce qui vous montre d'ailleurs que les
cibles ne sont pas toujours des fichiers (On ne génère pas un fichier install en tapant make install
). Quand on
tape make
sans spécifier de cible, c'est la première du fichier Makefile qui est utilisée.
Les dépendances peuvent être de simples fichiers mais aussi des cibles
d'autres règles
. Auquel cas,
ces cibles sont construites préalablement à la cible en dépendant, et ce automatiquement.
Les commandes doivent être précédées d'une tabulation. Sans cela, make vous insultera, comme on le verra en fin d'article.
Dans les commandes, on dispose de variables utiles telles que $<
pour la première dépendance et
$@
pour la cible.
Quelques autres variables disponibles dans un Makefile :
$? : toutes les dépendances plus récentes que la cible
$^ et $+ : toutes les dépendances
Voyons un premier exemple simple
V1=1 V2=2 toto: truc1 truc2 touch toto truc1: echo "V1 :" $V1 > truc1 truc2: echo "V2 :" $V2 > truc2 clean: rm toto truc? real-clean: clean echo "Le balai est passé en plus ..."
On commence par déclarer deux variables V1
et V2
valant 1
et 2
respectivement.
On définit ensuite la cible par défaut, toto
. Elle dépend de truc1
et truc2
.
A chaque fois qu'un de ces deux fichiers est mis à jour, on "refait toto". Ici, tout ce que l'on fait est de mettre à jour la date
de modification du fichier toto (s'il n'existe pas, touch
le crée, sinon, il met la date de modification à jour).
Pour les cibles truc1
et truc2
, on écrit la valeur de V1
et V2
dans deux fichiers, respectivement truc1
et truc2
.
Vient ensuite la cible clean
. Elle nous sert à effacer les fichiers. La cible real-clean
fait la
même chose puisqu'elle "appelle" clean à chaque exécution. En effet, clean n'étant pas un fichier, on ne peut pas savoir si celà a
été fait. Cette cible fait, en plus, croire qu'elle a passé le balai. (Il n'en est malheureusement rien, c'est une cible farceuse
voilà tout).
Jouons un peu :
$ make echo "V1 :" 1 > truc1 echo "V2 :" 2 > truc2 touch toto $ make clean rm toto truc? $ make toto echo "V1 :" 1 > truc1 echo "V2 :" 2 > truc2 touch toto $ make make: « toto » est à jour. $ touch truc1 $ make touch toto $ rm truc2 $ make echo "V2 :" 2 > truc2 touch toto $ make real-clean rm toto truc? echo "Le balai est passé en plus ..." Le balai est passé en plus ... $ make clean rm toto truc? rm: ne peut enlever `toto': Aucun fichier ou répertoire de ce type rm: ne peut enlever `truc?': Aucun fichier ou répertoire de ce type make: *** [clean] Erreur 1
On commence par invoquer make
sans spécifier de cible, comme dit plus haut, celà a pour effet de
construire toto. On remarque la construction préalable de truc1 et truc2.
Vient ensuite la démonstration d'un nettoyage puis de la reconstruction de toto en spécifiant la cible cette fois. Tenter de
reconstruire la cible alors qu'elle est à jour vous gratifie d'un message vous en informant.
On voit ensuite que la mise à jour ou la suppression d'une des dépendances entraîne la reconstruction des parties
nécessaires seulement.
Comme dit plus haut, on remarque que real-clean utilise la cible clean mais aussi que make
n'a aucun moyen de savoir si
cette cible a été traitée ou non. On a donc une erreur qui interrompt l'exécution de make
.
On peut ignorer ces erreurs en préfixant la ligne de commande par -.
Enfin, on peut supprimer l'affichage des lignes de commandes en ajoutant @ au début de celles-ci.
On écrirait donc clean et real-clean ainsi :
clean: -rm toto truc? real-clean: clean @echo "Le balai est passé en plus ..."
On reconstruit quelques cibles, pour constater les changements.
$ make clean rm toto truc? $ make real-clean rm toto truc? rm: ne peut enlever `toto': Aucun fichier ou répertoire de ce type rm: ne peut enlever `truc?': Aucun fichier ou répertoire de ce type make: [clean] Erreur 1 (ignorée) Le balai est passé en plus ...
A l'exécution, on constate que le balai a été passé, bien que les fichiers n'aient pas pu être détruits et que l'on n'est plus ennuyé par l'affichage non désiré de certaines commandes.
Précisions sur les Commandes
Les commandes n'ont pas connaissance l'une de l'autre. On doit donc regrouper celles qui sont interdépendantes au
sein d'une même ligne (on peut utiliser le caractère \
pour écrire la succession de commandes sur plusieurs lignes.
Pour ne pas confondre les variables du makefile avec celles du shell utilisé par les commandes, on double le signe '$' avant
ces dernières.
On peut redéfinir des variables du makefile en les "passant" à make. Par exemple prefix ou DESTDIR sont souvent des
variables contenues dans les makefiles, pour en modifier la valeur on lance make prefix="/home/user/usr"
ou
make DESTDIR="/home/user/usr"
. Ainsi, les valeurs renseignées dans le makefile sont ignorées et remplacées pas
celles de la ligne de commande.
Ce script bonjour.tcl
a été écrit dans linuxmag il y a deux mois. Nous voulons en faire une commande,
"l'installer" dans un répertoire du PATH
si on dispose d'un interpréteur tcl. On veut également pouvoir
désinstaller le "logiciel" et se débarasser des fichiers temporaires.
prefix=/usr bindir=$(prefix)/bin BINS=bonjour all: $(BINS) bonjour: bonjour.tcl @TCLSH=`which tclsh 2> /dev/null`; \ echo TCLSH=$$TCLSH; \ if [ "$$TCLSH" = "" ]; then \ echo "Erreur, pas de tclsh dans le PATH"; \ else \ echo "#!$$TCLSH" > $@; \ echo "#" >> $@; \ cat $< >> $@; \ chmod 755 $@; \ echo "Succès ! Vous pouvez maintenant lancer :"; \ echo $(BINS); \ fi install: all mkdir -p $(prefix) mkdir -p $(bindir) for i in $(BINS); do cp $$i $(bindir)/; done clean: -rm $(BINS) uninstall: -for i in $(BINS); do rm $(bindir)/$$i; done -rmdir $(bindir) -rmdir $(prefix) really-clean: clean uninstall
Les explications :
prefix
et bindir
) ainsi que le nom
de l'exécutable (BINS
).$(BINS)
).all
dépend de la présence des exécutables, install
dépend de all
(tout doit être construit avant une installation ...), clean
sert à supprimer les
copies locales des exécutables, uninstall
permet la désinstallation des exécutables et la suppression des
répertoires d'installation, si ces derniers sont vides.bonjour
permettant de créer l'exécutable bonjour
à partir du texte du script,
bonjour.tcl
.Dans les cibles d'installation et de suppression des exécutables, on retrouve la même construction :
for i in $(BINS); do cp $$i $(bindir)/; done
On dit ici que tous les exécutables contenus dans la variable $(BINS)
doivent être
copiés dans $(bindir)
. On constate la nécessité de doubler le caractère '$
' pour les variables
du shell.
La cible bonjour est la plus intéressante ... Elle est constituée de plusieurs commandes exécutées les unes à la suite des
autres (caractère ';
'). On remarque également l'utilisation du caractère '\
' pour écrire la ligne de script sur
plusieurs lignes dans le Makefile.
Dans ce script, on commence par rechercher l'interpréteur tclsh
à l'aide de la commande which
(qui renvoit le chemin complet vers un exécutable accessible dans un des répertoire renseignés dans la
variable d'environnement PATH
). On stocke ce chemin dans une variable shell TCLSH
.
Si cette dernière est vide, on affiche un message d'erreur et on stoppe l'exécution du makefile.
Si l'exécutable est trouvé, on écrit un script. Ce dernier, bonjour
, commence par #!
suivi du chemin vers l'interpréteur, ce qui permet au système de savoir quel interpréteur utiliser lorsqu'on lui demande
d'exécuter ce script (on appelle souvent cette ligne le "shebang").
Vient ensuite le corps du fichier bonjour.tcl
. On rend finalement exécutable le script
et on avertit l'utilisateur du bon déroulement de cette partie du makefile.
Naturellement ce makefile est perfectible. On peut citer à titre d'exemple des améliorations possibles la désinstallation, qui ne tient pas compte d'une éventuelle modification lors de l'installation des chemins d'accès aux exécutables.
Copiez ce makefile et le fichier bonjour.tcl dans un répertoire.
Pour rappel, voici le fichier bonjour.tcl
package require Tk label .l entry .e button .b -text "Bonjour" -command { .l configure -text "Bonjour [.e get]" .e delete 0 end } pack .l .e .b
Ensuite vous pouvez tester le bon fonctionnement du Makefile par les commandes suivantes (vous devez être root pour faire l'installation) :
# make Makefile:23: *** séparateur manquant (voulez-vous dire TAB au lieu de 8 blancs d'espacement?). Arrêt. [... réparation du Makefile ...] # make TCLSH=/usr/bin/tclsh Succès ! Vous pouvez maintenant lancer : bonjour # ./bonjour # make install mkdir -p /usr mkdir -p /usr/bin for i in bonjour; do cp $i /usr/bin/; done # bonjour # make uninstall for i in bonjour; do rm /usr/bin/$i; done rmdir /usr/bin rmdir: `/usr/bin': Le répertoire n'est pas vide. make: [uninstall] Erreur 1 (ignorée) rmdir /usr rmdir: `/usr': Le répertoire n'est pas vide. make: [uninstall] Erreur 1 (ignorée) #
Vous obtiendrez la première erreur si vous n'avez pas utilisé des tabulations mais des espaces pour spécifier les commandes nécessaires à la création de vos cibles. Le reste parle de lui même. Je vous laisse expérimenter, n'hésitez pas à me contacter pour de plus amples informations.
Cet article est plus une accumulation d'expérimentations qu'un makefile propre. Vous pouvez creuser plus avant vous-mêmes pour trouver comment écrire de beaux makefiles. Vous pouvez également vous laisser séduire par autoconf, automake ... mais maintenant, vous êtes aptes à comprendre le code qu'ils génèrent ...
Xavier Garreau - <xavier@xgarreau.org>
http://www.xgarreau.org/
Ingénieur de recherche PRIM'TIME TECHNOLOGY
http://www.prim-time.fr/
Membre fondateur du ROCHELUG
http://www.rochelug.org/
GNU make
et sa page infoGNU make
de Devhelp : http://lidn.sourceforge.net/a+