
L’outil make permet de fabriquer des fichiers selon des règles de production décrites dans une syntaxe très simple. Dans cet article, nous allons explorer les grands principes de l’écriture de ces règles et pour ceux qui ne sont pas encore familiers de cet outil, vous faire regretter d’avoir attendu si longtemps pour le connaître…
« C’est l’histoire d’un make…
Vous la connaissez ? Non ? Oui ? Non, parce que sinon… Parce que des fois y a des makes… Bon… Ah oui... Parce que y a des makes…
Vous la connaissez ? Non, dites-le parce que quand les gens y la connaissent, après on a l'air d'un con.
Alors là, le make…
Ah oui ! Parce que y a des makes des fois… Non, c'est un exemple… Oui, y a des make…
Alors, euh… Ça dépend des makes, parce que y a des makes… Alors, bon, des fois, c'est l'histoire avec des programmes, tout ça… Et puis le make oui, euh… Mais là, non ! Ah oui ! Non là, c'est l'histoire d'un make, mais un make normal…
Ah oui, parce que dans les histoires, y'a deux genres de makes… Ah oui…
Mais là, non. Un make normal. Pas un automake.
Ah oui, parce que y a des histoires… Y a deux genres d'histoires, ah oui…
Y a des histoires, c'est plus rigolo quand c'est un automake… Si on est… Pas CMake… Ben oui, faut un minimum…
Et puis y a les histoires, c'est plus rigolo quand c'est un CMake… Oui… Si on est… pas SnakeMake… »
Mais pour ça, il faut lire les autres articles parus et à venir se rapportant à tous ces outils.
Alors le make… est un utilitaire qui permet, à partir d’un fichier de description, de fabriquer d’autres fichiers. Par défaut, le fichier de description peut s’appeler GNUmakefile, makefile ou Makefile, mais peut porter n’importe quel autre nom, dans ce cas, il faut le spécifier explicitement lors de l’invocation du programme make.
Avant de commencer, il faudra donc installer cet outil :
- soit via votre gestionnaire de paquets préféré :
-
$ sudo apt install make
- soit à partir des sources disponibles sur https://www.gnu.org/software/make/.
1. Un make créant
Pour mieux comprendre le fonctionnement de cet outil, créons un nouveau répertoire test-make dans lequel nous travaillerons, puis lançons la commande make sans option, puis avec l’option --debug=all (qui comme son nom le laisse deviner affiche tout plein d’informations sur ce que fait la commande) :
À la lecture des lignes affichées, il est aisé de comprendre que tout l’intérêt de cet outil réside donc dans le fameux fichier décrivant les règles de production.
Par habitude, j’utilise le nom Makefile pour créer mes fichiers, mais rien n’interdit d’utiliser un nom quelconque (p. ex., exemple-makefile-glmf.txt). Dans ce cas, il suffit de spécifier le fichier avec l’option --file (ou --makefile ou encore -f).
1.1 Mon premier livre de recettes
Il faut comprendre que le Makefile n’est ni plus ni moins qu’un livre de recettes, décrivant comment produire des fichiers à partir d’autres fichiers (existants ou à fabriquer également).
La syntaxe d’une recette suit le motif suivant :
Commentaires
Comme tout langage de programmation, la grammaire du Makefile autorise l’écriture de commentaires, ce qui est plus que recommandé – même si vous ne trouverez aucun commentaire dans les exemples de cet article (mais il y a le texte autour des exemples…).
La syntaxe des commentaires est la même que pour le Shell. Tout ce qui suit un dièse et qui n’est pas dans une chaîne de caractères ou une action est un commentaire du Makefile.
Bien évidemment, les actions étant des commandes de Shell, celles-ci peuvent également bénéficier de commentaires ;-).
1.2 Ma première recette
Dans une recette, la cible est le fichier à produire, les dépendances sont les fichiers nécessaires pour fabriquer la cible et la succession d’actions correspond aux commandes du Shell à exécuter pour produire la cible. Chaque action doit commencer par une tabulation (et pas une suite d’espaces !!!) et être écrite sur une seule ligne. Toutefois, il est possible de continuer l’écriture d’une action sur la ligne d’après en utilisant le caractère \ qui – à l’instar du Shell – signifie que la commande se poursuit sur la ligne suivante.
Construisons donc un exemple simple en créant un premier fichier de recette Makefile :
Lors de la première invocation de make, le fichier boujour-GLMF.txt n’existant pas, il est construit avec la recette indiquée. À l’invocation suivante, il n’est pas reconstruit puisqu’il existe déjà.
1.3 Un peu de ménage
Étoffons notre exemple en ajoutant une deuxième recette :
On s’aperçoit que lors de l’invocation de make, il tente de reconstruire le fichier bonjour-GLMF.txt, mais comme celui-ci est à jour, il ne se passe rien de plus. Cependant, en spécifiant la cible clean, c’est cette recette qui est utilisée. L’outil make essaie donc de créer le fichier clean qui n’existe pas, et exécute les actions spécifiées. Cependant, aucune des actions ne crée le fichier clean. C’est une astuce plutôt sympathique pour faire du ménage sans tout effacer accidentellement...
Le lecteur attentif aura cependant noté qu’il ne faudrait pas qu’un fichier appelé clean existe dans ce répertoire, sans quoi les actions de nettoyage ne seraient jamais exécutées, pour les lecteurs moins attentifs (ce n’est pas grave, mais qu’on ne vous y reprenne pas non plus !), vérifions le tout simplement :
Fort heureusement, la syntaxe du Makefile permet de gérer ce genre de situations facilement en listant les cibles factices explicitement dans une cible particulière appelée .PHONY.
Maintenant que le fichier clean n’est plus gênant, nous pouvons le supprimer.
1.4 Une recette avec des ingrédients, c’est mieux
Créons une troisième recette qui utilise le fichier bonjour-GLMF.txt en substituant "Bonjour" par "Bonsoir" (et mettons nos règles de nettoyage à jour au passage).
Puis testons ce nouveau cahier de recettes.
Cela semble bien fonctionner. Lors de l’invocation de make sans cible, la première recette rencontrée a été utilisée et a créé le fichier bonjour-GLMF.txt (.PHONY n’est pas une véritable recette). Ensuite, lorsque la cible bonsoir-GLMF.txt a été spécifiée, la recette demandée a été appliquée et le fichier a été correctement créé. Lorsque la cible bonsoir-GLMF.txt a de nouveau été spécifiée, l’outil make a vérifié l’existence du fichier bonsoir-GLMF.txt et celui-ci étant à jour, il n’a pas été régénéré. Mais que se passerait-il si le fichier bonjour-GLMF.txt n’existait pas ?
Mais qu’ s’est-il passé (dites-le à voix haute si vous n’avez pas décelé le jeu de mots) ?
Après nettoyage, lorsque la cible bonsoir-GLMF.txt a été spécifiée, l’outil make a essayé d’appliquer la recette qui a échoué, car le fichier bonjour-GLMF.txt n’existe plus.
En réalité, il faut expliciter la dépendance existante de bonjour-GLMF.txt vers bonsoir-GLMF.txt :
Et ainsi cela fonctionne correctement :
Le mode debug avec l’option i est assez parlant…
1.5 Les recettes à refaire
Les dépendances ont une autre utilité, savoir quand un fichier construit par une recette est obsolète et doit être refabriqué. En effet, si la date de dernière modification de la cible est antérieure à la date de dernière modification d’une des dépendances, alors l’outil make va relancer la recette de fabrication de la cible.
1.6 Un make unique
Rapidement et avant de passer à la suite, ajoutons une cible factice qui permet, par défaut, de créer tous les fichiers :
Comme all est la première cible du fichier, c’est donc la recette qui sera appliquée par défaut.
2. Un make passe partout
La construction d’un Makefile peut sembler lourde, car pour chaque fichier à fabriquer, une recette doit être définie. Cependant, il arrive souvent que les recettes soient les mêmes, aux noms des fichiers près. Fort heureusement, il est possible de définir des variables au sein du fichier de recettes, et même des recettes types.
2.1 Souvent make varie, bien fol qui s’y fie
La déclaration d’une variable suit la syntaxe clé = valeur et pour utiliser une variable ainsi définie, il suffit d’utiliser l’opérateur $(nom_de_la_variable).
Pour illustrer ce concept, reprenons l’exemple précédent et déclarons la variable FICHIERS dans laquelle nous affectons les noms de fichiers bonjour-GLMF.txt et bonsoir-GLMF.txt, puis remplaçons ces deux noms dans la dépendance de la cible all et dans la première action de la cible clean par l’appel au contenu de la variable :
Le caractère $ étant réservé à la lecture du contenu d’une variable, pour l’utiliser (par exemple dans une action), il faut donc l’échapper. Cela se fait en doublant le symbole $.
Ainsi la règle clean pourrait devenir :
Ce qui produit la sortie suivante lorsque la recette est appliquée :
2.2 Un make masqué
Vous en conviendrez, cela devient parfois désagréable de voir s’afficher la commande avant qu’elle ne soit exécutée. Pour masquer l’affichage d’une commande particulière, il suffit de commencer l’action par le symbole @. La commande sera exécutée, mais ne sera pas affichée.
Voici la nouvelle cible clean :
Recette qui se doit d’être testée :
Et si l’on veut pousser le vice un peu plus loin, une astuce consiste à déclarer une variable qui contiendrait ce symbole et de préfixer chaque action du contenu de cette variable. Ainsi, pour afficher/masquer les actions exécutées, il suffit de changer la valeur de cette variable.
Ce Makefile commence à avoir la classe !
2.3 Des variables systématiques
Outre les variables que chacun peut déclarer, l’outil make vient avec un certain nombre de variables prédéfinies. Notamment, pour chaque recette, il existe 3 variables bien pratiques : $@, $^ et $< qui contiennent respectivement la cible, les dépendances et la première des dépendances.
Amusons-nous à remplacer les noms de fichiers cibles et de dépendances dans notre Makefile.
Cela ne change rien au résultat.
2.4 Des recettes systématiques
Il est possible d’aller encore plus loin avec make qui propose des fonctionnalités de manipulation de variables assez variées.
Par exemple, il est possible de définir la variable FICHIERS ainsi :
Dans un premier temps, la variable MODELES est définie avec comme valeur bonjour-GLMF.txt. Ensuite, la variable FICHIERS est définie comme étant égale au contenu de la variable MODELES, puis on lui ajoute (opérateur +=) le résultat de la substitution de bonjour par bonsoir au contenu de la variable MODELES.
Poussons encore l’exemple plus loin et remplaçons la définition des variables par :
Cette fois-ci, les modèles représentent un motif qui est utilisé pour créer la liste des fichiers. Pour cela, une boucle est opérée sur le contenu de la variable NOMS avec nom comme variable de parcours et une substitution du suffixe @[email protected] par le contenu de la variable nom suivi de l’extension .txt est opérée sur la variable MODELES.
Vous me voyez venir avec mes gros sabots, l’objectif sera clairement d’ajouter des noms. Il faut donc modifier les règles de production des fichiers…
Nous allons pour cela utiliser deux astuces :
- la première est qu’il est possible de déclarer des variables spécifiques à une cible en utilisant la syntaxe cible : clé = valeur ;
- la seconde est l’utilisation du caractère % qui permet de faire correspondre un motif d’une cible avec ses dépendances.
Ainsi, nous allons ajouter la règle qui consiste à déclarer, pour tout fichier correspondant au motif bonjour‑%.txt (où % représente un motif quelconque), la variable nom avec pour valeur le résultat de la substitution du motif bonjour‑%.txt par le motif contenu dans % à partir de la variable $@. En clair, pour la cible bonjour-GLMF.txt, cela signifie que nom = GLMF
Il reste à adapter les recettes permettant de créer les fichiers répondant aux motifs bonjour-%.txt et bonsoir-%.txt :
Nous pouvons maintenant ajouter de nouveaux noms à la variable NOMS (via l’opérateur += par exemple), ce qui donne le Makefile final :
Il est important de noter que la cible bonjour‑%.txt apparaît bel et bien deux fois (ce n’est pas une erreur). La première fois, c’est pour pouvoir extraire la variable nom, la seconde fois pour décrire la recette de fabrication. Voyons ce qui se passe avec ce livre de recettes :
Conclusion : Un make content
En guise de conclusion, regardons ce qui se passe avec notre livre de recettes si l’on demande la création du fichier bonsoir-make.txt.
Le lecteur attentif aura remarqué – l’emphase aidant – que l’outil make sait créer ce fichier à partir du fichier bonjour-make.txt. Donc, il crée ce premier fichier en suivant les recettes, mais comme celui-ci a été créé à titre temporaire, make le supprime automatiquement. Vous aurez remarqué également que la cible clean ne nettoie pas le fichier bonsoir-make.txt, car il ne fait pas partie des fichiers « connus » du Makefile. Toutefois, on peut également passer en argument à make des valeurs de variables, le cas échéant, celles-ci prévalent sur celles définies dans le Makefile.
Il est trop fort ce make !
Envie d’aller plus loin ?
Bien évidemment, l’objectif de cette introduction n’est pas de dévoiler toutes les fonctionnalités de l’outil make (le manuel fait 220 pages (https://www.gnu.org/software/make/manual/), je n’ai pas la prétention de faire mieux). Il est évident que pour de petits développements, faire un Makefile est entre judicieux et indispensable – ne serait-ce que pour faire le ménage sans se tromper.
Toutefois, pour des développements plus importants, la génération des Makefiles devient lourde et – dans la mesure où beaucoup de recettes se ressemblent – il existe des outils permettant de générer des Makefiles (autoconf/automake (https://www.gnu.org/software/autoconf/, https://www.gnu.org/software/automake), CMake (https://cmake.org/), module ExtUtils::MakeMaker pour Perl (https://metacpan.org/pod/ExtUtils::MakeMaker)…) ou s’en inspirant fortement (SnakeMake (https://snakemake.readthedocs.io/)).
Bonus
Voici un Makefile « simple » qui permet de compiler un document LaTeX un peu complexe :