Comprendre Make
August 1, 2026 · – visiteurs
make apparaît dans presque tous les projets embarqués et systèmes auxquels j'ai touché — recettes Yocto, builds de noyau, U-Boot, petits dépôts de firmware. C'est aussi l'un de ces outils que l'on utilise pendant des années par imitation, en copiant un Makefile du projet précédent sans jamais vraiment cerner le modèle sous-jacent. Voici ce modèle, exposé directement.
Qu'est-ce que Make ?
Make est un outil d'automatisation de build : il lit un fichier (un Makefile) décrivant un ensemble de cibles, les prérequis dont dépend chaque cible, et la recette (des commandes shell) nécessaire pour produire cette cible à partir de ses prérequis. À partir de cette description, make détermine quelles cibles sont obsolètes et n'exécute que les recettes nécessaires pour les remettre à jour — rien de plus.
main: main.o utils.o
gcc -o main main.o utils.o
main.o: main.c
gcc -c main.c
Lancez make, et il ne reconstruira main.o que si main.c est plus récent que main.o (ou si main.o n'existe pas encore), puis ne relinkera main que si main.o ou utils.o a changé. Rien n'est reconstruit inutilement.
À qui s'adresse-t-il ?
Make a été écrit par Stuart Feldman aux Bell Labs en avril 1976. Selon son propre récit, l'élément déclencheur fut un collègue — Steve Johnson, auteur de yacc — qui avait perdu une matinée entière à déboguer un programme qui, en réalité, était correct ; le binaire n'avait simplement pas été relinké après une modification du source. Feldman a reçu l'ACM Software System Award 2003 pour cela. L'outil né de cet après-midi — suivre les dépendances une seule fois, laisser la machine déterminer ce qui doit être refait — est devenu l'une des idées les plus copiées de l'outillage logiciel. GNU Make, la version que presque tout le monde utilise aujourd'hui, est une réimplémentation libre issue du projet GNU (1) (5) (6).
En pratique, il s'adresse à quiconque assemble un artefact non trivial — un binaire compilé, un document, un ensemble de fichiers générés — à partir de plus petites pièces, où reconstruire tout depuis zéro à chaque changement serait un gaspillage ou trop lent. Cela couvre les projets C/C++, les builds de noyau et de bootloader, les documents LaTeX, et beaucoup d'automatisation ad hoc qui n'a rien à voir avec la compilation de code.
Quand y avoir recours ?
Make se justifie lorsque trois conditions sont réunies :
- Il existe un véritable graphe de dépendances — certaines sorties dépendent d'autres, et reconstruire ne consiste pas simplement à « tout régénérer ».
- Les reconstructions sont suffisamment coûteuses pour que l'économie des reconstructions inutiles compte (une compilation de deux secondes n'en a pas besoin ; un build de noyau de vingt minutes, si).
- Les règles sont raisonnablement stables — le modèle de Make basé sur l'horodatage des fichiers ne gère pas des pipelines très dynamiques aussi élégamment qu'un exécuteur de tâches avec invalidation explicite le ferait.
Pour une poignée de fichiers sans interdépendances, un script shell est souvent plus simple et plus honnête. Make commence à être rentable quand « est-ce que ceci avait vraiment besoin d'être reconstruit ? » devient une question qui mérite d'être posée automatiquement plutôt qu'à la main.
Où se situe-t-il dans la chaîne d'outils ?
Make se situe généralement une couche au-dessus du compilateur et une couche en dessous de ce qu'exécute un humain. Dans un projet C typique : fichiers source → (compilateur, piloté par Make) → fichiers objets → (éditeur de liens, piloté par Make) → binaire final. Make lui-même ne compile ni ne linke rien — il invoque les outils qui le font, sous forme de commandes shell dans chaque recette.
Il a aussi tendance à se situer en dessous d'outils de configuration de build de plus haut niveau. CMake, Autotools et BitBake de Yocto, par exemple, ne remplacent pas tant Make qu'ils ne génèrent des Makefiles (ou pilotent un équivalent) comme véritable backend d'exécution. Ainsi, même les projets qui ne montrent jamais directement de Makefile en font souvent tourner un quelque part dans le pipeline.
Pourquoi fonctionne-t-il ainsi ?
Le choix de conception central — comparer les dates de modification des fichiers pour décider ce qui est périmé — est ce qui rend Make rapide sur de grands arbres : vérifier un horodatage est peu coûteux, si bien que reconstruire un projet énorme après un changement d'une ligne peut sauter presque tout et rester correct, à condition que le graphe de dépendances qu'on lui a fourni soit exact.
Cette dernière clause est à l'origine de presque toutes les plaintes « Make est cassé » que l'on rencontre, et elle mérite qu'on s'y attarde : Make n'est jamais plus juste que le graphe qu'on lui a communiqué. Si un fichier .c fait un #include d'un en-tête qui n'est pas listé comme prérequis du fichier .o correspondant, modifier cet en-tête ne déclenchera pas de reconstruction — non parce que Make est perdu, mais parce que personne ne lui a jamais dit que cette dépendance existait. C'est pourquoi les Makefiles du monde réel sont presque toujours associés à une étape de génération de dépendances par le compilateur (-MMD -MP avec GCC/Clang) plutôt qu'à des listes d'en-têtes écrites à la main — le compilateur connaît déjà le véritable graphe d'inclusion, il en est donc la seule source fiable (4).
Comment fonctionne-t-il concrètement ?
Cibles, prérequis, recettes. L'unité de base est :
target: prerequisite1 prerequisite2
recipe line 1
recipe line 2
Les lignes de recette doivent être indentées avec une tabulation, pas des espaces — une célèbre verrue historique qui piège encore les gens aujourd'hui.
Les variables automatiques réduisent la répétition :
%.o: %.c
gcc -c $< -o $@
$@ est la cible, $< est le premier prérequis — cette seule règle de motif peut construire chaque .o à partir de son .c correspondant sans une ligne par fichier.
Les cibles .PHONY marquent des noms qui ne correspondent pas à de vrais fichiers — clean, all, test — afin que Make ne se trouble pas si un fichier portant ce nom existe par hasard, et exécute toujours la recette plutôt que de vérifier les horodatages : (2)
.PHONY: clean
clean:
rm -f *.o main
Les variables évitent de répéter des flags partout et facilitent la réutilisation entre projets :
CC = gcc
CFLAGS = -Wall -O2
main.o: main.c
$(CC) $(CFLAGS) -c main.c
La génération automatique de dépendances comble l'écart décrit plus haut :
CFLAGS = -Wall -O2 -MMD -MP
-include $(wildcard *.d)
%.o: %.c
$(CC) $(CFLAGS) -c $<
-MMD fait en sorte que le compilateur émette un fichier .d par objet, listant chaque en-tête réellement inclus lors de cette compilation. -include réintègre ces fichiers dans le Makefile pour que le graphe de dépendances reste exact à mesure que les en-têtes changent — sans que personne n'ait à le maintenir à la main (3).
Pour conclure
La plupart de ce qui ressemble à des bizarreries de Make — les comparaisons d'horodatage, l'insistance sur les tabulations, la façon dont une dépendance manquante échoue silencieusement plutôt que bruyamment — découle d'une seule décision de conception prise en 1976 : garder le modèle simple et la vérification peu coûteuse, et faire confiance au Makefile pour décrire fidèlement la réalité. Une fois qu'on adopte cet angle, la plupart des moments où « Make se comporte bizarrement » se révèlent être « le graphe que je lui ai donné était incomplet » — ce qui est bien plus facile à corriger.
À Lire Aussi
- Git : supprimer des branches, fusionner et annuler un mauvais push — les commandes de branche et de push du quotidien
Références
- Feldman, S. I. (1979). Make — A Program for Maintaining Computer Programs. Software: Practice and Experience, 9(4), 255–265. — l'article de Feldman lui-même présentant Make, avec la motivation originale.
- GNU Make Manual — la référence complète et faisant autorité sur la syntaxe et la sémantique de GNU Make.
- GNU Make Manual: Automatic Prerequisites — le motif
-MMD/-includedans les mots des mainteneurs eux-mêmes. - GCC: Preprocessor Options (
-Mfamily) — ce que font-MMDet-MPen coulisses. - Make (software) — Wikipedia — histoire générale et filiation des implémentations de Make (BSD Make, GNU Make, NMAKE, etc.).
- Stuart Feldman — Wikipedia — contexte sur le travail de Feldman aux Bell Labs et l'ACM Software System Award 2003.
Commentaires