Comprendre un makefile

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.

Les généralités qui nous intéressent

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.

Le Makefile pour "bonjour toi en tcl/tk"

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 :

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.

Utilisation

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.

En savoir plus

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/

Références :

a+

Auteur : Xavier GARREAU
Modifié le 14.09.2004