-
Vers des Makefile génériques : partie 1
Make est un système de construction logicielle né en 1977, peu avant la disparition des dinosaures et la Création.
On pourrait penser qu'il est complètement dépassé par des outils très sérieux et très postérieurs en Java mâchant du XML. C'est bien possible, mais moi aussi. Il faudra aussi parler du joli concurrent CMake.
GNU/Make présente l'avantage de la simplicité et de la lisibilité. Il est disponible à peu près partout. Comme Python, sa courbe d'apprentissage est progressive, et on peut utiliser Make de façon rudimentaire ou complexe.
Je vous propose d'apprendre ou réviser l'utilisation de GNU/Make en deux ou trois petits articles : reformulation de script, utilisation des dépendances, factorisation.
Voyons aujourd'hui comment passer d'un script shell à un véritable Makefile, puis, après une introduction assez brutale à la gestion des dépendances, comment obtenir non pas un, mais bien deux Makefile modulaires !
De la reformulation d'un script à un Makefile simple
Beaucoup de gens utilisent des scripts de build en bash.
Considérons aujourd'hui que nous travaillons avec un projet jouet dont voici l'arborescence utile :
projet/
README
src/
source1.c
source2.c
On veut fournir un fichier ZIP release.zip contenant le fichier README et deux exécutables résultant respectivement de la compilation de source1.c et source2.c.
Approche naive
Première transformation
Après avoir tapé deux cent cinquante six fois les commandes permettant de compiler, disons que vous ayez produit le script suivant :
build.sh
1 #!/bin/bash 2 #remove trailing spaces 3 find . -name "*.c" |xargs sed -i 's/[ \t]*$$//' 4 5 cd src 6 7 gcc source1.c -o executable1 8 gcc source2.c -o executable2 9 zip ../release.zip executable1 executable2 ../README
Il est très simple de le transformer naivement en un Makefile :
Makefile
1 #Une seule «recette» (recipe)/cible (target) pour tout faire 2 release.zip: 3 #remove trailing spaces 4 find . -name "*.c" |xargs sed -i 's/[ \t]*$$//' 5 6 cd src 7 8 gcc source1.c -o executable1 9 gcc source2.c -o executable2 10 11 zip ../release.zip executable1 executable2 ../README
C'est un peu plus long, mais ça sera aussi plus joli à la fin.
L'indentation dans le Makefile doit être composée de caractères de tabulation horizontale, strictement (ASCII 9).
Une « cible » désigne quelque chose que l'on veut construire. On lui associe des pré-requis (voir plus loin), et une recette, qui suit la déclaration de la cible et est indentée.
Invocation par défaut pour construire notre cible
Pour construire votre zip, lancez simplement dans votre shell 'make' ou 'make release.zip'.
Les shells dotés d'une fonction de complètement automatique vous proposeront d'ailleurs les cibles (target) disponibles.
S'il est invoqué sans l'option "-f", make recherche automatiquement dans le répertoire courant un fichier nommé Makefile, ou makefile pour y trouver des recettes, et sauf indication contraire, exécute la première recette.
Différence avec le script build.sh plus haut : votre recette s'exécutera comme si vous aviez entré
% set -x (echo des commandes exécutées)
et
% set -e (arrêt en cas d'erreur)
Une seconde cible : le nettoyage
Pour faire réaliste, prenons en compte un système rustique de nettoyage
clean.sh
1 #!/bin/bash 2 find . -name "*.o" -delete
Ajoutons une cible clean à notre Makefile :
13 clean: 14 find . -name "*.o" -delete
Invoquer une cible ou l'autre
La première cible définie plus haut sera toujours construite par
% make
Pour nettoyer :
% make clean
Cibles spéciales et dépendances
En trente-quatre ans, beaucoup de gens ont rencontré des Makefile, et il serait dommage de ne pas se conformer à des conventions si aisées à suivre. Il existe un certain nombre de cibles dont tout le monde comprend le sens ; clean est l'une d'elles, all une autre.
Je vous propose maintenant d'introduire la cible all en haut de votre Makefile, comme suit :
1 all: release.zip
On a créé une cible nommée all, qui dépend de la cible release.zip décrite précédemment.
Implicitement, toutes les cibles désignent des fichiers à construire.
Par économie, make ne reconstruit pas un fichier qui existe déjà (sauf si les dépendances ont changé, nous allons voir cela).
Mauvaise nouvelle : make est frileux
Un effet de bord de ces fonctionnalités est que si par hasard vous créez maintenant un fichier nommé clean, make ne voudra plus nettoyer ; si vous créez un fichier nommé all, make ne voudra plus compiler, et c'est ennuyeux.
Il existe un moyen de passer outre et de construire une cible inconditionnellement :
Il faut pour cela ajouter dans le Makefile la déclaration :
3 .PHONY: all clean
Par contre, nous ne déclarerons pas la cible release.zip comme .PHONY puisqu'elle correspond à un vrai fichier qu'on peut stater.
Bonne nouvelle : make est paresseux
Il est possible d'ajouter des dépendances sous formes de fichiers intermédiaires à construire.
Par exemple, comme on sait que release.zip doit être construit à l'aide de projet/README et de deux exécutables compilés dans projet/src/, nous indiquons cette information à make :
17 release.zip: src/executable1 src/executable2 README 18 zip $@ $^ 19 #Voyez les explications de cette commande magique ci-dessous
Oups, j'ai utilisé des variables automatiques sans prévenir ! Pas de panique, c'est très simple :
- $@ désigne la cible courante (release.zip)
- $^ désigne la liste de pré-requis (à droite du caractère «:» de la ligne numérotée 17).
La commande zip src/executable1src/executable2 README ne sera exécutée que si l'un au moins des trois fichiers requis par la cible nommée release.zip est plus récent que le fichier release.zip.
Au début on a foncé, maintenant on accélère : abordons la récursion.
Un peu de récursion et de structure
Voyons maintenant comment structurer les recettes et sous-recettes de construction.
De nombreuses variables implicites sont accessibles dans Make (voyez la doc de GNU/Make à l'occasion). Nous allons les utiliser pour gagner en dynamisme.
Un nom de variable de plus d'un caractère doit indiqué entre parenthèses préfixées par «$».
Ingrédients
Variables simples $(MAKE) désigne l'exécutable 'make' et permet les invocations récursives $(SRCDIRS), $(GENERIC) variables définies au sein du Makefile Variables automatiques $@ désigne la cible $^ contient tous les pré-requis de cette recette $< contient le premier pré-requis causant l'appel de cette recette Cible fichier release.zip Cibles virtuelles (PHONY) all clean Ici on continuera malgré les éventuelles erreurs (IGNORE)
Maintenant voici simplement les fichiers résultant, abondamment commentés.projet/Makefile
1 #Le ou les répertoires dans lesquels des cibles doivent être construites 2 SRCDIRS = src 3 #recettes fonctionnant dans les répertoires ci-dessus 4 GENERIC = all clean 5 6 # La règle qui suit sera exécutée pour les cibles 'all' et 'clean' dans chaque répertoire de SRCDIRS 7 # l'option -C passée à make permet de conserver l'état global du process de construction 8 #Dans la recette «all», $@ vaut «all» et $< vaut «src» 9 $(GENERIC): $(SRCDIRS) 10 $(MAKE) -C $< $@ 11 12 #Ajout d'une dépendance spécifique à all 13 all: release.zip 14 15 #La construction de release.zip est désormais effectuée relativement au répertoire racine 16 #-c'était moche ces .., non ? 17 release.zip: src/executable1 src/executable2 README 18 zip $@ $^ 19 # zip release.zip src/executable1 src/executable2 README 20 21 .PHONY: all clean
projet/src/Makefile
Remarquons la symétrie des opérations :
1 all: 2 gcc source1.c -o executable1 3 gcc source2.c -o executable2 4 5 clean: 6 rm executable1 7 rm executable2 8 9 .PHONY: all clean 10 11 #IGNORE permet d'ignorer les erreurs lors de l'exécution d'une règle, 12 #car sinon, contrairement aux shells, Make s'arrête à la moindre erreur. 13 .IGNORE: clean
Conclusion
Nous avons aujourd'hui abordé les bases de l'utilisation de GNU/Make. Grâce à cette connaissance, vous pouvez proposer l'interface la plus standard standard à la construction de votre projet : taper "make". Les Makefile décrits ici constituent une base extensible que nous rendrons flexible dans un prochain billet.
Je vous recommande de jouer avec Make en essayant de générer des erreurs (supprimez des fichiers, ou créez un fichiers nommé clean, etc) afin de découvrir les messages correspondants ou les informations de progression fournies par ce très bon outil.
Post scriptum-
merci à mike_perdide pour sa relecture
- merci à vim pour ses conversions de code vers le html (et à la tribune linuxfr qui m'a dit comment faire) : par exemple, vim +"highlight makeSpecTarget ctermbg=black ctermfg=yellow" +"highlight LineNr ctermfg=grey ctermbg=black" +"highlight makeComment ctermfg=darkblue" +"set background=light" +f src/Makefile +"syntax on" +"so /usr/share/vim/vim73/syntax/2html.vim" +"wq" +"q" (oui, je ferai un Makefile pour ça aussi)
Source des images :
- Office religieux dans une église
- Pompier au balais
- Magicien réalisant un tour de cartes
- Grand cloître de la Chartreuse (1558), Villefranche-de-Rouergue
- Grue-tour du port de Kristansand
Tags : make, makefile
-
Commentaires