title: Make
url: https://www.arthurperret.fr/cours/make.html
hash_url: 821fa93388
archive_date: 2024-03-28
og_image: https://www.arthurperret.fr/img/scriptor.jpg
description: Cette page présente le logiciel Make, un outil simple et fiable pour automatiser des tâches.
favicon: https://www.arthurperret.fr/cours/favicon-32x32.png
language: fr_FR
Make fait partie des logiciels d’automatisation des tâches : il sert à créer des processus dans lesquels des actions s’enchaînent automatiquement.
À l’origine, Make est un outil conçu pour le développement de logiciels : il sert à automatiser la fabrication de programmes exécutables à partir de fichiers contenant du code.
Mais en réalité, Make peut être utilisé pour gérer toutes sortes de projets. En tant que langage, il permet de définir des processus de manière très simple (encore plus simple que les scripts shell). Et en tant qu’outil, il permet de gagner du temps, avec une gestion efficace des états des fichiers qui fait économiser du travail aussi bien à l’humain qu’à la machine.
Make a été inventé pour automatiser la fabrication de programmes exécutables. Un programme exécutable est un fichier qui se fabrique en deux temps : d’abord, on écrit du code dans des fichiers texte ; puis on exécute un programme appelé compilateur qui transforme le code en un fichier exécutable – cette deuxième étape, c’est la compilation.
Make a été conçu en particulier pour déterminer quels fichiers doivent être recompilés lors de la fabrication d’un programme. Lorsqu’un fichier est modifié, il déclenche automatiquement les commandes appropriées. L’objectif était de ne pas tout recompiler si on ne modifiait qu’une petite partie du code d’un programme complexe, afin d’économiser des ressources et gagner du temps.
Pour faire cela, Make s’appuie sur deux choses : la date de dernière modification des fichiers ; et un arbre de dépendances, c’est-à-dire une représentation des relations entre des fichiers cible, qu’on veut fabriquer, et des fichiers source (les « dépendances »), qui sont nécessaires à la fabrication des fichiers cible. Évidemment, un fichier cible peut à son tour être fichier source pour fabriquer un autre fichier cible, et ainsi de suite.
Voici un exemple d’arbre de dépendances :
Si l’une des dépendances change, Make recompile le ou les fichiers cible correspondants. Dans l’exemple ci-dessus, si one.h
est modifié, Make recompile one.cpp
puis main.cpp
, mais pas two.cpp
.
Le fonctionnement de Make repose sur des fichiers texte appelés Makefiles. Dans un Makefile, on rédige des instructions appelées règles (rules) ; chaque règle définit une recette (recipe) pour fabriquer une ou plusieurs cibles (targets) suivant des prérequis (prerequisites).
Les règles s’écrivent de la manière suivante :
Ce sont les relations entre cibles et prérequis qui constituent l’arbre de dépendances modélisé par Make.
Une recette est constituée d’une ou plusieurs commandes : des instructions textuelles qu’on exécuterait habituellement dans un terminal.
Comme dans de nombreux langages informatiques, la syntaxe des Makefiles inclut la possibilité de créer des variables, de laisser des commentaires, de définir des fonctions qui incluent des étapes logiques, etc.
Pour exécuter les instructions contenues dans un Makefile, il faut ouvrir un terminal, se déplacer à l’emplacement du Makefile, puis exécuter la commande make
(ce qui déclenche les instructions qui sont contenues dans le Makefile).
Si vous souhaitez découvrir comment utiliser un terminal, consultez ma page Ligne de commande.
Progressivement, d’autres usages de Make ont émergé. En effet, son fonctionnement a une portée plus générale que la seule fabrication de programmes, car il ne pose aucune restriction sur la nature de la cible, des prérequis ou de la recette. On peut utiliser Make pour automatiser le déclenchement de n’importe quel programme qui s’utilise habituellement à la ligne de commande.
Donc si Make est souvent présenté comme un outil d’automatisation pour la compilation de programmes, il serait en fait plus juste de le décrire comme un outil d’automatisation pour la ligne de commande. C’est un outil qui permet de définir des enchaînements de commandes textuelles ; dit de façon plus abstraite, Make permet de créer des processus, des séquences d’actions, où chaque action est une instruction envoyée à un ou plusieurs programmes à interface textuelle. Bref : Make permet de fabriquer des fabriques. (Clin d’œil à Antoine Fauchié qui a beaucoup travaillé sur ce concept de fabrique, voir son blog et sa thèse.)
J’ai partagé un modèle de Makefile qui automatise l’utilisation de Pandoc pour générer différents exports à partir d’une même source : Pandoc-SSP. Make a complètement remplacé mon usage des scripts shell pour automatiser le déclenchement de commandes Pandoc. J’utiliserai Pandoc comme exemple à plusieurs reprises sur cette page.
Make me sert donc à rédiger des recettes dans lesquelles je passe d’ingrédients de base (des fichiers) à un résultat final (d’autres fichiers). Mais comme me l’a fait remarquer David Larlet, on peut aussi l’utiliser pour des processus qui n’ont pas forcément de fichiers en entrée ou en sortie, comme la mise en place d’un serveur ou bien l’installation d’un logiciel. Make est un outil versatile.
Pour utiliser Make, vous avez besoin des choses suivantes :
make --version
dans un terminal. Sur Windows, vous pouvez installer Make par exemple en installant le gestionnaire de programmes Chocolatey, puis en utilisant Chocolatey pour installer Make en ouvrant un terminal (je recommande PowerShell) et en exécutant la commande choco install make
.
Make s’utilise en écrivant des Makefiles et en utilisant la commande make
dans un terminal. Par défaut, make
cherche un fichier appelé makefile
ou Makefile
situé dans le répertoire courant. On peut aussi nommer un Makefile par n’importe quel nom
et utiliser make
avec l’option --file=nom
ou -f nom
.
Un Makefile contient une ou plusieurs règles.
La syntaxe générale d’une règle est la suivante :
Notez l’espacement avant le mot « recette » : dans une règle, la recette doit être indentée avec une tabulation (une seule tabulation, pas d’espaces).
(Si comme moi vous utilisez régulièrement YAML, un langage qui requiert d’indenter les lignes avec des espaces, préparez-vous à vous mélanger les pinceaux pendant quelques temps !)
Les cibles et les prérequis sont des noms de fichiers, séparés par des espaces. En pratique, il n’y a souvent qu’une cible par règle.
La recette est constituée d’une ou plusieurs commandes, qui sont une série d’étapes utilisées pour créer la cible. Make applique la recette :
Dans le Makefile suivant, la cible est un fichier PDF, doc.pdf
. Elle a un prérequis qui est un fichier texte rédigé en Markdown, doc.md
. La recette est constituée par une commande qui exécute le programme Pandoc.
Si j’exécute make
:
doc.pdf
n’existe pas, ou qu’il existe mais que doc.md
est plus récent (donc qu’il a été modifié depuis la dernière fabrication de doc.pdf
), Make lance la commande Pandoc qui convertit le fichier Markdown en PDF.doc.pdf
existe et que doc.md
n’est pas plus récent, rien ne se passe.Les cibles peuvent être des cibles « factices » (en anglais phony). Une cible factice ne correspond pas à un fichier réel : c’est une sorte de nom de code pour une série d’opérations qu’on veut exécuter. Les cibles factices doivent être déclarées comme prérequis de la cible spéciale .PHONY
.
Je reprends l’exemple précédent, avec cette fois deux fichiers Markdown, doc-fr.md
et doc-en.md
. Je définis une cible all
qui a deux prérequis, les cibles doc-fr.pdf
et doc-en.pdf
.
.PHONY: all
doc-fr.pdf : doc-fr.md
pandoc doc-fr.md -o doc-fr.pdf
doc-en.pdf : doc-en.md
pandoc doc-en.md -o doc-en.pdf
all : doc-fr.pdf doc-en.pdf
Si j’exécute make all
, Make déclenchera la fabrication des deux cibles correspondantes, à moins que les cibles existent déjà et qu’elles soient plus récentes que les prérequis.
Si un Makefile contient plusieurs règles, par défaut make
fabrique la première cible qu’il trouve dans le fichier. Il est possible de définir manuellement la cible à exécuter par défaut grâce à la variable spéciale .DEFAULT_GOAL
(voir la section Variables spéciales plus bas).
La commande make
dispose de nombreuses options qui modifient son comportement. Utilisez make --option
pour appliquer l’option correspondante lors de l’utilisation de Make. Plusieurs options peuvent être utilisées simultanément. La plupart des options sont disponibles sous plusieurs noms (notamment pour des questions de compatibilité), dont des formes raccourcies avec un seul tiret et une seule lettre, pour aller plus vite lorsqu’on saisit les commandes manuellement.
Quelques exemples :
-n
(ou --just-print
, --dry-run
, --recon
) : affiche les commandes qui seraient exécutées, sans les exécuter. Ceci permet de vérifier ce que ferait un Makefile sans l’appliquer réellement.-j [jobs]
(ou --jobs[=jobs]
) : autorise Make à exécuter simultanément un certain nombre de commandes (jobs
). Si votre machine dispose d’un processeurs avec plusieurs cœurs, ceci permet de réduire considérablement le temps d’exécution du Makefile. Par exemple, avec un processeur à huit cœurs, vous pouvez utiliser l’option -j 8
pour que Make lance jusqu’à huit commandes en parallèle (une par cœur).Dans un terminal, utilisez man make
pour afficher le manuel et découvrir les options. Appuyez sur la touche q pour quitter le manuel.
Par défaut, Make affiche dans le terminal chaque ligne d’une recette exécutée. On appelle cela « l’écho », par analogie avec la commande echo
qu’on utilise dans un terminal pour afficher des messages.
Il y a plusieurs manières de supprimer l’écho :
@
pour supprimer l’écho pour cette ligne.Remarque : il existe d’autres préfixes qu’on peut ajouter à une ligne pour modifier le comportement de Make. Par exemple, préfixer une ligne par -
indique à Make qu’il ne doit pas s’interrompre en cas d’erreur, ce qui est pratique lorsqu’on dépanne un processus.
.SILENT
en indiquant des prérequis, ce qui supprimera l’écho pour la fabrication des prérequis correspondants.Remarque : .PHONY
et .SILENT
sont deux exemples de cibles spéciales. Voir Special Targets dans le manuel GNU Make pour la liste complète.
-s
(ou --silent
, --quiet
) au moment d’exécuter la commande make
. Toutes les recettes s’exécuteront alors silencieusement.Make permet de créer des variables, c’est-à-dire d’affecter une valeur à un nom pour pouvoir ensuite utiliser le nom à la place de la valeur. L’intérêt premier des variables, c’est d’éviter d’écrire plusieurs fois la même chose, pour économiser du travail et réduire le risque de faire des erreurs.
Pour définir une variable :
Pour utiliser la valeur de cette variable :
Dans le Makefile ci-dessous, une longue liste de fichiers apparaît plusieurs fois :
edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o
clean :
rm edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o
On peut définir une variable dont la valeur est cette liste de fichiers, puis utiliser le nom de la variable partout où cette liste doit être utilisée :
Une variable peut être redéfinie, c’est-à-dire qu’on peut la définir à nouveau dans le même Makefile. La nouvelle valeur remplace alors la précédente.
La valeur d’une variable peut elle-même contenir une ou plusieurs variables.
La plupart des langages informatiques reconnaissent plusieurs types de données, soit de manière implicite, soit de manière explicite grâce à l’ajout de caractères spéciaux. Exemple en JavaScript :
var = true // booléen (boolean)
var = 28 // nombre entier (integer)
var = "Bonjour !" // chaîne de caractères (string)
Make ne reconnaît qu’un type de données : la chaîne de caractères (en anglais : string). Par conséquent, il interprète les guillemets littéralement. Il est donc conseillé de n’utiliser les guillemets que lorsque la chaîne va être interprétée dans un autre environnement, par exemple lorsqu’on passe une variable au shell pour exécuter une commande.
Ainsi, plutôt que :
On écrira :
On parle d’expansion pour désigner le fait de remplacer une expression de la forme $(nom)
par la valeur de la variable correspondante.
Ce concept devient important lorsqu’on définit des variables qui contiennent d’autres variables, et plus encore lorsque des variables sont redéfinies.
En effet, Make permet de gérer de manière très fine l’ordre dans lequel se passe l’expansion. Ceci repose sur l’utilisation de différents opérateurs de définition (voir la section How Make reads a Makefile du manuel de GNU Make pour la liste des opérateurs et leurs effets). Or les différences de comportement entre ces opérateurs sont souvent source de confusion.
Les deux opérateurs qu’on rencontre le plus fréquemment sont =
et :=
.
=
pour définir une variable, l’expansion des variables contenues dans la valeur se fait au moment où la variable est utilisée. Ceci permet de changer la valeur en cours de route, si on redéfinit les variables utilisées dans la valeur.:=
pour définir une variable, l’expansion des variables contenues dans la valeur se fait au moment où la variable est définie. Ceci permet de fixer la valeur une fois pour toutes. Exemple :mot = bonjour
hello := $(mot) à vous !
# la valeur de "hello" est "bonjour à vous !"
# on redéfinit "mot"
mot = bonsoir
# la valeur de "hello" reste "bonjour à vous !"
# on redéfinit cette fois "hello", on répète la première définition
hello := $(mot) à vous !
# la valeur de "hello" est maintenant "bonsoir à vous !"
En résumé :
=
et :=
suivant les besoins.=
ou :=
ne fait aucune différence, puisqu’il n’y a rien dans la valeur de la variable définie qui nécessite une expansion.=
ou :=
ne fait aucune différence non plus, puisqu’il n’y a aucune redéfinition qui vient affecter l’expansion.Make inclut quelques variables spéciales, dont le nom est réservé pour un comportement prédéfini.
Par exemple, lorsqu’on exécute make
sans argument, c’est la première règle contenue dans le Makefile qui est appliquée. On peut utiliser la variable spéciale .DEFAULT_GOAL
pour définir la règle à appliquer par défaut.
Avec le Makefile ci-dessous, exécuter make
fabriquera doc.html
au lieu de all
:
Voir Special Variables dans le manuel GNU Make pour la liste complète.
Make inclut la possibilité d’utiliser des fonctions pour manipuler du texte. Il y a des fonctions prédéfinies qui correspondent aux opérations les plus courantes : trier, chercher, filtrer, tronquer, etc. Il est également possible de définir ses propres fonctions.
Une fonction s’utilise en faisant appel à son nom et en lui passant du texte en argument :
$(wildcard pattern)
cherche les noms de fichiers qui correspondent au motif pattern
.$(var:a=b)
remplace la chaîne de caractères a
par b
dans la variable var
.Dans l’exemple ci-dessous, on combine ces deux fonctions pour créer une variable input
qui contient les noms de fichiers Markdown présents dans le répertoire où se situe le Makefile, puis une variable output
qui contient les mêmes noms en remplaçant l’extension .md
par .html
.
Une fonction peut avoir pour argument une valeur littérale, une variable ou une fonction. Plusieurs fonctions peuvent ainsi être imbriquées les unes dans les autres, comme des poupées russes :
L’ordre d’exécution est simple à déterminer : c’est la fonction qui ne contient pas elle-même une autre fonction (la plus petite poupée russe) qui s’exécute en premier ; puis elle passe son résultat à la fonction qui la contient, et ainsi de suite jusqu’à la dernière fonction (la plus grande poupée russe), celle qui n’est pas elle-même contenue dans une autre fonction.
La fonction shell
est particulièrement utile. Comme son nom l’indique, elle permet de passer des commandes au shell, comme si on les exécutait dans un terminal.
Voir Fonctions dans le manuel GNU Make pour la liste complète des fonctions.
Contrairement aux règles de base (aussi appelées règles explicites), les règles implicites ne définissent pas comment fabriquer une cible précise à partir de prérequis définis, mais comment fabriquer des types de cibles à partir de types de prérequis.
Une règle implicite ne définit pas de cibles. Lorsqu’on utilise une règle implicite, il faut donc par ailleurs définir explicitement les cibles à fabriquer.
Dans une règle implicite, au lieu d’indiquer explicitement le nom de cibles et de prérequis, on indique le modèle (en anglais pattern) que doivent suivre ces noms. Un modèle de nom contient le symbole pourcentage %
, qui constitue la part variable du modèle, avec éventuellement un préfixe et/ou un suffixe, qui constituent la part fixe du modèle.
Exemples :
%.txt
désigne tous les fichiers dont le nom finit par .txt
_%
désigne tous les fichiers dont le nom commence par _
_%.txt
désigne tous les fichiers dont le nom commence par _
et finit par .txt
Dans le Makefile ci-dessous, on génère automatiquement les noms des cibles en utilisant la fonction wildcard
et la fonction de référence avec substitution, et on crée une règle implicite qui définit comment fabriquer des fichiers HTML à partir de fichiers Markdown, quel que soit leur nom.
Comme pour les variables spéciales, les variables automatiques sont des variables dont le nom est réservé. En revanche, leur valeur n’est pas définie manuellement mais calculée automatiquement (d’où leur nom) en fonction du contexte.
$@ |
nom de la cible |
$< |
nom du premier prérequis |
$^ |
noms de tous les prérequis (séparés par des espaces) |
Les variables automatiques ne peuvent être utilisées que dans les recettes.
Les variables automatiques sont pensées pour être utilisées dans des règles implicites. Combiner les deux permet de réaliser facilement des traitements de fichiers par lots. À mes yeux, c’est l’une des fonctionnalités les plus utiles de Make.
On reprend l’exemple précédent de la règle qui définit comment fabriquer des fichiers HTML à partir de fichiers Markdown et on ajoute une recette qui consiste à exécuter Pandoc en lui donnant le nom des prérequis $^
comme entrée et le nom des cibles $@
comme sortie.
.PHONY: html
input = $(wildcard *.md)
output = $(input:.md=.html)
html : $(output)
%.html : %.md
pandoc $^ --output=$@
Et voici une légère variation de la recette : on ajoute un prérequis, du coup on remplace $^
par $<
pour ne plus passer en entrée tous les prérequis mais uniquement le nom du fichier Markdown.
Voir Automatic variables dans le manuel GNU Make pour la liste complète des variables automatiques.