Auto Makefile : un Makefile cross projets automatisé
Je trimballais le même Makefile de projet en projet sans savoir quelle version chacun abritait. Alors j'ai fait un Makefile qui télécharge les autres.
Auto Makefile, c’est un Makefile capable d’en télécharger un autre. Toutes mes commandes de dev, de build et de test passent par make, sur tous mes projets, et je trimballais le même fichier de l’un à l’autre depuis des années. J’en avais assez : voilà l’outil qui a réglé ça.
C’est la première fois que je parle programmation sur ce blog, et pourtant je ne vais pas parler de code. Je vais parler de ce qu’il y a autour : la façon dont on structure la stack d’un projet. Parce qu’un projet qui part bien, c’est d’abord un projet facile à lancer, à builder et à déployer. Le reste vient après.
Le Makefile que je trimballais de projet en projet
Que ce soit du JS, du PHP ou autre chose, tout passe chez moi par un Makefile. Les commandes de dev, le build, les tests, et même les environnements de recette et de production. Une seule porte d’entrée, la même partout, quel que soit le langage derrière.
Le problème, c’est que ce Makefile était un fichier que je copiais. Nouveau projet, copier-coller. Et le jour où j’améliorais quelque chose dedans, il fallait répéter la modification projet par projet. Fastidieux, et surtout jamais fait jusqu’au bout : au bout d’un moment, je ne savais plus quel projet abritait quelle version, ni quelles commandes il connaissait. Deux projets côte à côte, deux comportements différents pour le même make.
D’où Auto Makefile. L’idée tient en une phrase : garder dans le projet uniquement ce qui lui est propre, et aller chercher le générique ailleurs, au moment où on en a besoin.
Un Makefile qui en charge un autre
Concrètement, voilà à quoi ressemble le Makefile d’un projet :
# configuration #
# external resource #
export MAKEFILE_URL=https://raw.githubusercontent.com/Smeagolworms4/auto-makefile/master
export IMPORT_MK=root.mk
# import #
$(shell [ ! -f .makefiles/index.mk ] && mkdir -p .makefiles && curl -L --silent -f $(MAKEFILE_URL)/$(IMPORT_MK) -o .makefiles/index.mk)
include .makefiles/index.mk
export CUSTOM_VAR ## My custom variable 1
export CUSTOM_VAR2 ## My custom variable 2
##########
# Custom #
##########
## My custom rule
hello-world:
echo Hello world
Tu lui donnes une URL, il va chercher le fichier distant et le pose dans .makefiles/. C’est là que tout se range, et c’est un dossier à ignorer dans git : rien de ce qu’il contient n’a besoin d’être versionné, tout se retélécharge.
La seule vraie condition, c’est que le système ait curl. Il faut bien télécharger les fichiers distants un jour. Et justement, ce n’est qu’un jour : le test [ ! -f .makefiles/index.mk ] fait que le téléchargement n’a lieu que si le fichier manque. Une fois le premier make passé, le projet fonctionne hors ligne comme n’importe quel Makefile classique.
make tout court, et tu as l’aide
La première chose que ça apporte, c’est une commande help qui n’est pas à écrire. make sans argument, ou make help, et tu obtiens ça :

Le mécanisme est bête et efficace. Un commentaire ## juste au-dessus d’une règle devient sa description. Un ## à la fin d’une ligne export documente la variable. Et les blocs de dièses encadrés servent de titres de section pour regrouper les règles :
############
# Symfony #
############
## Installe les dépendances
install:
composer install
Écrire la doc et écrire la règle, c’est le même geste. Ce qui fait qu’elle reste juste : personne n’oublie de mettre à jour une aide qui se trouve sur la ligne d’au-dessus.
Les .env chargés dans le bon ordre
Le fichier de base, root.mk, gère aussi les variables d’environnement, et c’est de loin ce dont je me sers le plus. Tu as un .env, il est chargé tout seul. Tu as un .env.local, il est chargé aussi, par-dessus. Et si tu lances make ENV=prod et qu’il trouve un .env.prod, il le prend au passage.
Avec deux fichiers dans le projet et une règle qui se contente d’afficher la variable, ça donne ça :
$ cat .env
APP_SECRET=secret-de-dev
$ cat .env.prod
APP_SECRET=secret-de-prod
$ make debug
ENV=dev APP_SECRET=secret-de-dev
$ make ENV=prod debug
ENV=prod APP_SECRET=secret-de-prod
Il n’y a rien à écrire dans ton Makefile pour ça, c’est le comportement par défaut. Tu poses tes fichiers, ils sont pris.
La variante Docker
Et ça ne s’arrête pas là. Le fichier que tu importes n’est pas figé : il suffit de changer une ligne pour charger une autre variante de l’outil.
export IMPORT_MK=docker.mk
Celle-ci ajoute les commandes Docker de base : make list pour voir les conteneurs, make killall pour repartir de zéro, make clean-none pour dégager les images sans nom. Des commandes qui ne sont liées à aucun projet en particulier, mais que je tape tous les jours.
Attention avec killall : il supprime tous les conteneurs de la machine, pas seulement ceux du projet courant, puis fait un prune des réseaux et des volumes. C’est exactement ce que je veux sur mon poste de dev quand plus rien ne répond. C’est aussi exactement ce qu’il ne faut pas taper sur une machine qui héberge autre chose.
docker-compose.mk, celui que j’utilise vraiment
La dernière variante est celle qui m’intéresse le plus, parce que mes stacks sont full Docker :
export IMPORT_MK=docker-compose.mk
Elle ajoute up, down, logs, docker-build et docker-pull. Et surtout, elle choisit le fichier compose en fonction de l’environnement.
Le principe, dans le détail. Par défaut, l’outil cherche docker/docker-compose.yml. Quand tu lances make ENV=prod up, il regarde d’abord si docker/docker-compose.prod.yml existe. S’il le trouve, c’est celui-là qu’il utilise. Sinon il retombe sur le fichier de base, sans rien dire et sans planter.
$ make debug
COMPOSE_PATH=/projet/docker/docker-compose.yml
$ make ENV=prod debug
COMPOSE_PATH=/projet/docker/docker-compose.prod.yml
Le point important, et c’est un choix de ma part : c’est un remplacement, pas une superposition. Docker Compose sait empiler plusieurs fichiers avec -f base.yml -f prod.yml, où le second ne contient que les différences. Ici, non : le fichier de l’environnement est utilisé à la place de l’autre. Il doit donc être complet, et décrire toute la stack. C’est plus verbeux, mais tu lis un seul fichier pour savoir ce qui tourne en prod, sans reconstituer mentalement une fusion de deux YAML.
Et comme les .env suivent la même variable ENV, une seule commande bascule tout ensemble : le bon fichier compose et les bonnes variables.
make up # docker-compose.yml + .env
make ENV=prod up # docker-compose.prod.yml + .env + .env.prod
Autre détail commode : si tu définis DOCKER_NAME, il est passé en nom de projet à Compose. Deux stacks avec le même nom de dossier ne se marchent plus dessus.
À noter aussi, la mécanique s’empile toute seule. docker-compose.mk télécharge docker.mk, qui télécharge root.mk. Tu n’écris qu’un seul curl dans ton Makefile, et tu te retrouves avec les trois fichiers dans .makefiles/ et toutes les commandes des trois niveaux.
Tout se surcharge
Aucune règle fournie n’est verrouillée. Chaque commande est rangée dans une variable protégée par un ifndef, donc si tu la définis dans ton Makefile, c’est la tienne qui gagne. Trois niveaux d’intervention :
# remplacer complètement ce que fait la règle
export RULE_CMD_UP=$(COMPOSE) up -d --remove-orphans
# ajouter une dépendance : up construira les images avant
export RULE_DEP_UP=docker-build
# greffer une commande avant ou après
export RULE_CMD_UP_AFTER=$(COMPOSE) exec php composer install
Dans les faits, c’est ce que j’utilise le plus : je garde la règle générique et je greffe le spécifique du projet autour. Un make up qui installe les dépendances et joue les migrations derrière, sans avoir réécrit la commande Docker.
Mettre à jour, ou figer une version
Puisque le fichier vient d’ailleurs, il faut pouvoir le rafraîchir. C’est la règle update-makefile, qui vide .makefiles/ et relance make : au passage suivant, tout est retéléchargé.
make update-makefile
Et pour mon problème d’origine, celui de ne jamais savoir quelle version un projet abritait, la réponse tient dans la ligne d’URL. Le dépôt est étiqueté, donc rien n’oblige à pointer sur master :
export MAKEFILE_URL=https://raw.githubusercontent.com/Smeagolworms4/auto-makefile/v1.4.1
Le projet dit alors noir sur blanc de quelle version il dépend, et il s’y tient. Sur mes projets qui bougent, je reste sur master et je fais un update-makefile de temps en temps. Sur ceux qui doivent juste continuer à marcher, j’épingle.
Ce que ça vaut
Ça reste un outil basique et très simple, et c’est volontaire. Il ne compile rien, il ne devine rien de ton projet, il ne remplace pas ton gestionnaire de dépendances. Il fait juste en sorte que make réponde la même chose partout, que le projet soit en PHP, en JS ou en Java, et il est parfait sur une stack full Docker comme j’aime les monter.
Le résultat que je cherchais est atteint : quand j’arrive sur un projet, même vieux de deux ans, je tape make et il me dit ce qu’il sait faire. Le Makefile du projet ne contient plus que ce qui est propre au projet, et il tient en vingt lignes.
Tout est sur le dépôt, avec la doc complète. C’est libre et ouvert, alors j’attends vos retours, et vos suggestions si vous voyez ce qui manque.
Image d’en-tête générée par IA.
SmeagolWorms4
Commentaires