Aller au contenu principal

Makefile - Tutoriel et astuces

Astuces essentielles pour automatiser avec un Makefile. Apprenez à vérifier l'existence d'un fichier, lancer des targets dépendantes, utiliser des conditions, ignorer les erreurs et intégrer Docker dans vos scripts.

· 14 minutes de lecture
Voir la source
Christophe
Amoureux de Markdown, WSL et Docker ~ Curieux insatiable.
Cet article existe aussi en anglais — lire la version originale.

Traduction automatique

Cette traduction a été produite par une IA. Elle peut contenir des contresens ou des maladresses, en particulier sur les termes techniques. En cas de doute, consultez l'article de référence en anglais — c'est lui qui fait foi.

Cet article a plus d'un an, mais il a été relu le 30 juillet 2026 — son contenu est toujours exact.

Makefile - Tutoriel et astuces

⚡
TL;DR

Voici une grande référence personnelle d'astuces Makefile accumulées au fil du temps : vérifier l'existence d'un fichier ou d'un dossier, chaîner des targets dépendantes, masquer l'affichage des commandes, écrire des conditions, ignorer les erreurs, forcer Bash au lieu de /bin/sh, substituer des variables, les pièges tabulations-vs-espaces, les paramètres nommés, étendre une target avec ::, lire des valeurs depuis un fichier .env, basculer en mode verbeux, des targets utilitaires pour git, une vérification de fraîcheur du dossier vendor pour les projets Composer, et une target make help auto-documentée.

Quand j'apprends, je prends généralement des notes. Je trouve que c'est l'une des meilleures façons de retenir ce que j'ai vu et de pouvoir y revenir à tout moment.

Voici une note que j'ai prise et révisée plusieurs fois quand j'ai pris le temps de créer mes premiers fichiers .make.

Deux articles compagnons : Linux Makefile - Quand utiliser un makefile pour le pourquoi, et Linux Makefile - Ajouter un écran d'aide pour la target make auto-documentée qui rend toutes ces astuces découvrables par vos collègues.

`make` n'est pas encore installé ? Cliquez pour déplier.

Lancez simplement les commandes suivantes pour installer l'exécutable Make sur votre machine hôte :

>user@machine: ~/yourproject

Comment vérifier si un fichier existe ou non​

Vous pouvez utiliser l'instruction test -s comme ci-dessous :

@test -s .config/phpunit.xml || echo "phpunit.xml didn't exist"
@test -s .config/phpunit.xml && echo "phpunit.xml exists"

Ces deux lignes vérifient l'existence du fichier .config/phpunit.xml de manière opposée. La première affiche un message d'erreur si le fichier n'existe pas ou est vide, tandis que la deuxième affiche un message de confirmation s'il existe et contient du contenu.

Contrairement à l'instruction ifeq, test doit être indenté.

On peut utiliser la notation { ... } si on doit lancer plus d'une commande ; par exemple :

@test -s .config/phpunit.xml || { echo "phpunit.xml file is missing, please take actions! Exiting..."; exit 1; }

Ce script vérifie que le fichier .config/phpunit.xml existe et n'est pas vide ; s'il manque ou est vide, il affiche un message d'erreur et arrête l'exécution avec un code d'erreur.

On peut aussi faire ça avant, par exemple, d'inclure un fichier externe :

ifneq ("$(wildcard .env)","")
include .env
endif

Ce snippet vérifie si un fichier .env existe dans le répertoire courant, et si c'est le cas, il l'inclut dans le Makefile pour charger les variables d'environnement qu'il contient.

Bien sûr, on peut aussi faire simple, c'est-à-dire juste utiliser le - avant la commande pour ignorer les erreurs ; ainsi, ci-dessous, si le fichier n'existe pas, aucune erreur ne sera levée et le script continuera.

-include .env

Ce fichier Makefile inclut conditionnellement un fichier .env qui contient probablement des variables d'environnement ou de configuration. Le tiret au début signifie que make ne s'arrêtera pas si le fichier .env n'existe pas, ce qui permet au Makefile de fonctionner même sans ce fichier de configuration optionnel.

Comment vérifier si un dossier existe ou non​

Vous pouvez utiliser l'instruction ifeq avec une commande shell Linux comme ci-dessous :

ifeq ($(shell test -e vendor/a/b/c || echo -n no),no)
@echo "The folder didn't exist"
endif
ifeq ($(shell test -e vendor/a/b/c && echo -n yes),yes)
@echo "The folder exists"
endif

Ce snippet Makefile vérifie l'existence d'un dossier vendor/a/b/c de deux façons différentes. D'abord il teste si le dossier n'existe pas et affiche un message, puis il teste si le dossier existe et affiche un autre message. Les deux approches font la même vérification mais avec une logique inversée.

Contrairement à l'instruction test, ifeq doit être utilisé sans indentation. Directement à la colonne zéro.

Lancer des targets dépendantes​

En lançant make php-cs-fixer, on va d'abord lancer vendor puis update-them, et enfin php-cs-fixer ; autrement dit, on peut définir une liste de targets dépendantes.

php-cs-fixer: vendor update-them
@echo "And finally run php-cs-fixer"
vendor/bin/php-cs-fixer
vendor:
@echo "First get vendors"
update-them:
@echo "Then update vendors"

Ce Makefile définit des cibles pour gérer les dépendances PHP du projet. La cible php-cs-fixer exécute l'outil de formatage de code après avoir mis à jour les vendeurs, tandis que les cibles vendor et update-them sont des étapes intermédiaires qui affichent des messages informatifs.

>user@machine: ~/yourproject

Arrêter le job si une target échoue​

Si l'une d'elles échoue, le script s'arrête. Dans l'exemple ci-dessous, php-cs-fixer n'affichera jamais And finally run php-cs-fixer.

php-cs-fixer: vendor update-them
@echo "And finally run php-cs-fixer"
vendor/bin/php-cs-fixer
vendor:
@echo "First get vendors"
update-them:
@echo "Then update vendors"
exit 2 && echo "Oups, an error has occurred"

Ce Makefile définit trois cibles pour gérer les dépendances PHP : vendor télécharge les packages via Composer, update-them les met à jour, et php-cs-fixer exécute l'outil de formatage de code après les deux étapes précédentes. La cible php-cs-fixer dépend de update-them, qui dépend implicitement de vendor.

Lancer make php-cs-fixer affichera ceci :

>user@machine: ~/yourproject

Utiliser la commande shell find pour récupérer toutes les dépendances​

Imaginez que vous avez beaucoup de fichiers .md dans le répertoire. Si l'un change, on les concatène à nouveau. Si rien ne change, rien à faire.

Ça se fait avec une commande shell pour la dépendance :

concat: $(shell find *.md -type f)
$(shell concat.sh)

Ce fragment Makefile cherche tous les fichiers Markdown du répertoire courant et les stocke dans une variable concat, puis exécute le script concat.sh pour les traiter. C'est une façon d'automatiser la fusion de plusieurs fichiers Markdown dans une seule étape de build.

(source https://tech.davis-hansson.com/p/make/#specifying-inputs)

Ne pas afficher la commande​

Lancer make helloWorld comme ci-dessous affichera deux lignes dans la console.

helloWorld:
echo "Hello world"

Ce snippet définit une cible Makefile appelée helloWorld qui exécute une commande echo pour afficher le texte 'Hello world' dans le terminal. C'est une simple démonstration d'une tâche Makefile basique.

Et la sortie dans la console :

>user@machine: ~/yourproject

Pour éviter la première, c'est-à-dire l'affichage de l'instruction lancée, préfixez-la simplement d'une arobase (@).

helloWorld:
@echo "Hello world"

Ceci définit une cible de tâche Makefile nommée helloWorld qui, quand elle est exécutée, affiche simplement le texte « Hello world » sur la console.

Maintenant, seule la sortie est affichée ; plus l'instruction elle-même.

Utiliser des conditions​

On peut écrire une condition comme ceci mais attention à l'indentation :

init:
ifeq ($(shell expr $(SET_PHP_VERSION) \>= 8), 1)
@printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Upgrade dependencies to PHP 8"
else
@printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Downgrade dependencies to PHP 7"
endif

Ce snippet Makefile affiche un message de couleur jaune selon la version PHP configurée — il dit d'upgrader les dépendances si PHP 8 est utilisé, ou de les downgrader si c'est PHP 7. C'est une cible init qui aide les développeurs à savoir quelle action prendre pour l'environnement de dépendances.

make init SET_PHP_VERSION=7.4 affichera le message de downgrade tandis que make init SET_PHP_VERSION=8.1 affichera celui d'upgrade.

Un autre exemple :

ifneq ("$(DB_TYPE)","")
echo "The DB_TYPE variable is not empty
endif
ifeq ("$(DB_TYPE)","")
echo "The DB_TYPE variable is empty
endif

Ce snippet teste la variable DB_TYPE à deux reprises : d'abord il vérifie qu'elle n'est pas vide, puis il vérifie qu'elle est vide. Chaque test affiche un message différent selon le résultat. C'est une démonstration basique des conditions dans un Makefile.

Ignorer une erreur​

Ne pas s'arrêter en cas d'erreur : ajoutez un - avant la ligne, comme ceci : -php --lint index.php

phplint:
-php --lint index.php
echo "Yes, this script will continue..."

Ce Makefile définit une cible phplint qui exécute le vérificateur de syntaxe PHP sur index.php, puis affiche un message de confirmation. C'est un outil de développement pour valider rapidement que le fichier PHP n'a pas d'erreurs de syntaxe avant de le déployer.

En cas d'erreur de linting dans index.php, on n'arrête pas l'exécution du script et on passe à la commande suivante.

Note : make peut éventuellement afficher un message comme make: [makefile:114: up] Error 1 (ignored) pour informer l'utilisateur qu'une erreur s'est produite mais a été ignorée. Si vous n'en voulez pas du tout (sortie silencieuse), voici comment faire :

[...]
@-docker network create -d bridge my_network >/dev/null 2>&1 || true
[...]

Ce snippet crée un réseau Docker de type bridge appelé my_network, en supprimant tout message de sortie. Le @ au début empêche l'affichage de la commande elle-même, et || true garantit que le Makefile continue même si le réseau existe déjà.

Cette commande va créer un nouveau réseau Docker appelé my_network et, en cas d'erreur (le réseau existe déjà, par exemple), on s'en fiche et on ne veut voir aucune sortie.

Assurez-vous d'utiliser Bash​

Par défaut, make utilise /bin/sh. On peut changer ça en ajoutant cette affectation en haut du makefile :

SHELL:=bash

Ce Makefile définit la variable SHELL pour utiliser bash au lieu du shell par défaut du système. Cela garantit que toutes les commandes exécutées dans les recettes Make utiliseront bash plutôt que sh, ce qui permet d'accéder à des fonctionnalités bash spécifiques.

(source : https://tech.davis-hansson.com/p/make/#always-use-a-recent-bash)

Définir la target par défaut​

Par défaut, la première target définie dans le fichier sera celle par défaut, c'est-à-dire celle lancée quand l'utilisateur tape simplement make en ligne de commande.

default: hello
hello:
@echo "Hello World"

Ce fichier Makefile définit une cible par défaut qui exécute la cible hello, laquelle affiche simplement 'Hello World' à l'écran. C'est un exemple élémentaire montrant comment les Makefiles structurent les tâches exécutables.

Substitution​

Prenons make convert INFILE=readme.md

outfile=$(INFILE:.md=.pdf)
COLOR_BLUE:=34
convert:
@printf "\e[1;${COLOR_BLUE}m%s\e[0m\n\n" "INPUT FILE IS ${INFILE}; OUTPUT WILL BE ${outfile}"

Ce snippet Makefile configure la conversion de fichiers Markdown en PDF. Il définit le nom du fichier de sortie en remplaçant l'extension .md par .pdf, puis utilise une variable de couleur pour afficher un message formaté qui indique le fichier d'entrée et le fichier de sortie attendu.

La syntaxe outfile=$(INFILE:.md=.pdf) remplace .md par .pdf ; dans cet exemple, on peut donc déduire le fichier de sortie à partir du fichier d'entrée.

Tabulation, pas espace​

L'indentation à utiliser dans un makefile est la tabulation ; pas les espaces. Utiliser des espaces casse le fichier.

helloWorld:
echo "Hello World"

Ce fichier Makefile définit une cible appelée helloWorld qui, lorsqu'elle est exécutée, affiche le texte 'Hello World' à l'écran. C'est un exemple simple de tâche automatisée qu'on peut invoquer avec la commande make.

Il est toutefois possible d'adapter ce comportement avec .RECIPEPREFIX :

.RECIPEPREFIX = >
changeExt:
### This is a comment
> @printf "%s\n" "It works"

Ce Makefile définit une règle appelée changeExt qui affiche un message de confirmation. La première ligne change le préfixe des commandes de recette de tabulation à >, ce qui permet d'utiliser > à la place de Tab pour indenter les commandes.

(source : https://tech.davis-hansson.com/p/make/#dont-use-tabs)

Utiliser des paramètres​

Lancer une target avec un paramètre se fait via des paramètres nommés, comme ceci :

>user@machine: ~/yourproject

Ça crée une variable appelée firstname, que l'on peut ensuite utiliser :

hello:
@echo "Hi ${firstname}!"

C'est une cible Makefile nommée hello qui affiche un message de salutation personnalisé. Quand on exécute make hello, elle utilise echo pour afficher un texte qui incorpore la variable firstname, ce qui permet de créer des salutations dynamiques.

Vérifier que les paramètres sont bien définis​

Imaginons qu'on veuille lancer make runsql SQL='SELECT * FROM users LIMIT 10' : l'argument SQL doit être défini, sinon on a un problème.

COLOR_RED:=31
COLOR_YELLOW:=33
runsql:
@[ "${SQL}" ] && printf "\e[1;${COLOR_YELLOW}m\n%s\e[0m\n\n" "Running ${SQL}" || ( printf "\e[1;${COLOR_RED}m\n%s\e[0m\n\n" "ERROR - Please set the SQL to execute; consult the help if needed"; exit 1 )

Ce Makefile définit deux codes de couleur ANSI (rouge et jaune) et crée une cible runsql qui affiche un message coloré avant d'exécuter une commande SQL, ou affiche un message d'erreur en rouge si la variable SQL n'est pas définie.

Travailler avec Docker​

Dans un makefile, on peut s'arrêter tôt si on a besoin qu'un container Docker donné soit démarré.

L'instruction if ci-dessous vérifie que le container sonarqube tourne ; si ce n'est pas le cas, parce qu'il n'a pas encore été créé ou qu'il est arrêté par exemple, une instruction d'erreur sera exécutée et le script sera arrêté.

sonar-scan:
ifeq ($(shell docker ps -a -q -f name=sonarqube -f status=running),)
### The sonarqube container didn't exist yet or not running (exit mode f.i.)
@printf $(_RED) "ERROR - Please first run \"make sonar-server\""
exit 1
endif

Cette cible Makefile vérifie que le conteneur SonarQube est actuellement en cours d'exécution avant de procéder à une analyse. Si le conteneur n'existe pas ou n'est pas actif, elle affiche un message d'erreur en rouge et arrête l'exécution.

Le if ci-dessous vérifie si le container existe et, si non, le crée. Le else sait donc que le container existe, mais va s'assurer qu'il tourne.

ifeq ($(shell docker ps -a -q -f name=sonarqube),)
### The sonarqube container didn't exist yet, create it.
docker run -d --name sonarqube sonarqube:latest
else
### Make sure the container is running
-@docker container start sonarqube > /dev/null 2>&1
endif

Ce fragment Makefile vérifie si un conteneur Docker nommé sonarqube existe. S'il n'existe pas, il le crée à partir de l'image sonarqube:latest. S'il existe déjà, il s'assure que le conteneur est en cours d'exécution.

Configurer Visual Studio Code​

Ajouter l'extension makefile​

https://marketplace.visualstudio.com/items?itemName=ms-vscode.makefile-tools

Ajouter le fichier .editorconfig​

Assurez-vous que votre fichier Makefile est correctement formaté ; ajoutez un fichier appelé .editorconfig dans votre répertoire racine.

[*]
end_of_line = lf
insert_final_newline = true
[Makefile]
indent_style = tab
indent_size = 4

Ce fichier de configuration EditorConfig définit les règles de formatage pour les fichiers du projet. La section générale impose les fins de ligne en LF et l'ajout d'une nouvelle ligne à la fin de chaque fichier, tandis que la section Makefile configure l'indentation avec des tabulations de taille 4.

Quelques astuces​

Comment étendre une target​

Vous avez une target existante, disons hello dans notre exemple, et vous souhaitez l'étendre en ajoutant des actions supplémentaires.

hello peut être définie dans le même makefile ou dans un fichier inclus, mais illustrons ça avec un exemple simple : on souhaite ajouter la sortie Nice to meet you.

hello:
@echo "Hello world"
hello:
@echo "Nice to meet you"

Ce fichier Makefile définit deux cibles nommées hello qui affichent des messages différents. Quand tu exécutes make hello, c'est la deuxième définition (ligne 4) qui s'exécute et surcharge la première, car elle la redéfinit.

Si on lance ce fichier, voici la sortie.

>user@machine: ~/yourproject

La solution : utiliser :: (on parle de règle explicite) et non un simple : après la recette ; voyez l'exemple suivant :

hello::
@echo "Hello world"
hello::
@echo "Nice to meet you"
hello::
@echo "Do you have any plans for this weekend?"

Ce Makefile définit une cible hello multiple fois avec des recettes différentes. Quand on exécute make hello, toutes les recettes s'exécutent séquentiellement dans l'ordre où elles apparaissent, affichant trois messages successifs.

Si on lance ce fichier, voici la sortie.

>user@machine: ~/yourproject

Les recettes sont simplement étendues, la deuxième est ajoutée à la première et ainsi de suite ; l'ordre est donc important.

Récupérer le répertoire courant dans une variable​

PWD:=$(shell pwd)
current_dir:
@echo "The current directory is ${PWD}"

Ce Makefile définit une variable PWD contenant le répertoire courant en exécutant la commande shell pwd, puis crée une cible current_dir qui affiche cette valeur. C'est un exemple simple montrant comment capturer le résultat d'une commande shell dans une variable Makefile.

Récupérer une liste de fichiers et initialiser une variable​

Prenons un cas réel : scanner un dossier appelé .docker et récupérer la liste des fichiers compose*.yaml qui s'y trouvent.

L'objectif est d'initialiser une variable d'environnement appelée COMPOSE_FILE (voir https://docs.docker.com/compose/environment-variables/envvars/#compose_file) pour qu'en lançant docker compose on puisse utiliser tous les fichiers d'un coup (c'est-à-dire sans ajouter --file file1.yml --file file2.yml et ainsi de suite).

Récupérer la liste des compose*.yaml se fait donc comme ceci :

DOCKER_YAML_FILES := $(shell find ./.docker -name 'compose*.yaml')
DOCKER_YAML_FILES := $(shell echo "$(DOCKER_YAML_FILES)" | tr ' ' ':')

Ce snippet cherche tous les fichiers YAML de composition Docker dans le répertoire .docker, puis les joint en une seule chaîne de caractères séparée par des deux-points. C'est utile pour passer plusieurs fichiers de configuration à une commande Docker Compose en une seule variable.

La première ligne retournera par exemple compose.yaml compose.override.yaml compose.mysql.yaml.

La deuxième instruction remplacera l'espace par un deux-points (:).

On obtiendra donc compose.yaml:compose.override.yaml:compose.mysql.yaml.

Maintenant, pour lancer docker compose config par exemple, il suffit de faire ceci : déclarer d'abord la variable d'environnement COMPOSE_FILE puis lancer l'action souhaitée.

.PHONY: config
config:
COMPOSE_FILE=${DOCKER_YAML_FILES} docker compose config

Cette cible Makefile affiche la configuration fusionnée de tous les fichiers Docker Compose spécifiés. Elle utilise la variable DOCKER_YAML_FILES pour indiquer à docker compose quels fichiers charger, puis exécute la commande config pour montrer la configuration résultante.

Récupérer des informations depuis le fichier .env​

Récupérer une valeur depuis un fichier .env est facile : incluez-le puis utilisez les variables :

DOCKER_IMAGE=cavo789/my_image

Ce script définit une variable d'environnement DOCKER_IMAGE qui stocke le nom complet d'une image Docker, soit le propriétaire cavo789 et l'image my_image. Cette variable peut ensuite être utilisée dans d'autres commandes Docker pour faire référence à cette image spécifique.

include .env
helloDocker:
@echo "The name of the image is ${DOCKER_IMAGE}"

Ce Makefile charge les variables d'environnement du fichier .env, puis définit une cible helloDocker qui affiche le nom de l'image Docker stocké dans la variable DOCKER_IMAGE. Le @ au début supprime l'affichage de la commande elle-même.

Cette astuce de l'include fonctionne avec n'importe quel fichier définissant une variable et sa valeur.

On peut parfaitement avoir un fichier appelé Make.config, pas .env.

Définir une variable à partir des variables d'environnement du .env​

Imaginons que vous avez une variable appelée APP_ENV dans votre fichier .env.

Cette variable peut valoir local, test ou ce que vous voulez. Elle vaudra production quand l'application tourne en environnement de production.

À partir de cette variable, on peut donc définir une variable comme ceci :

ifeq ($(APP_ENV), production)
CMD:=
else
CMD:=docker compose exec my_docker_image
endif

Ce snippet de Makefile définit une variable CMD qui change selon l'environnement. En production, CMD reste vide pour exécuter les commandes directement, tandis qu'en développement, CMD préfixe les commandes avec docker compose exec pour les lancer dans le conteneur.

Ça veut dire : si on ne tourne pas en production, chaque commande sera lancée dans notre container Docker. Si on tourne en production, la commande sera exécutée directement.

Voici un exemple :

composer-update:
${CMD} composer update --no-interaction

Cette cible Makefile exécute la commande composer update avec le flag --no-interaction pour mettre à jour les dépendances PHP du projet sans attendre d'entrée utilisateur.

Utiliser une valeur par défaut si la variable n'est pas définie​

Si la variable d'environnement système PHP_VERSION n'est pas définie, mettre sa valeur par défaut à 8.1

PHP_VERSION := $(or $(PHP_VERSION),8.1)

Ce fichier définit la version de PHP à utiliser pour ce projet. Il utilise une variable Makefile qui définit PHP_VERSION à 8.1 si aucune valeur n'a déjà été fournie, permettant aux utilisateurs de surcharger cette valeur par défaut lors de l'exécution.

Un autre exemple : imaginez que vous avez un fichier .env avec la variable DOCKER_APP_HOME. Mais si la variable n'est pas définie, la syntaxe ci-dessous permet de fixer une valeur par défaut.

echo $(or ${DOCKER_APP_HOME},/var/www/html)

Ce script affiche le chemin du répertoire d'accueil de l'application Docker. S'il existe une variable d'environnement DOCKER_APP_HOME, il l'utilise ; sinon il affiche le chemin par défaut /var/www/html. C'est utile pour déterminer dynamiquement où l'application est installée.

Ça permet des choses comme ci-dessous : cibler une version personnalisée d'une image Docker selon la version de PHP sélectionnée.

DOCKER_PHPQA:=jakzal/phpqa:php${PHP_VERSION}-alpine

Ce fragment définit une variable Makefile appelée DOCKER_PHPQA qui construit le nom complet d'une image Docker. L'image combine le nom de base jakzal/phpqa avec la version PHP spécifiée dans la variable PHP_VERSION, et cible l'architecture alpine pour un conteneur minimaliste.

Surcharger une variable​

Même si la variable est déjà définie, vous pouvez la surcharger en la passant en ligne de commande :

>user@machine: ~/yourproject

Ça lancera la target yamllint avec PHP_VERSION à 8.1 même si la variable est déjà définie et vaut, par exemple, 7.4.

Comment vérifier si une variable commence par une valeur donnée ?​

Le cas d'usage : on a une variable appelée PHP_VERSION et on doit détecter si on a affaire à du code PHP 7 ou à du PHP 8 ou supérieur.

IS_PHP_7 := $(shell [[ $(PHP_VERSION) =~ 7[0-9.]+$$ ]] && echo "yes")
doThing:
ifdef IS_PHP_7
@echo "Hey, it's an old version of PHP, why not migrate to PHP 8?"
else
@echo "Nice! You're using PHP 8 or greater"
endif

Ce Makefile définit une variable IS_PHP_7 en exécutant une vérification de shell pour savoir si la version de PHP correspond au pattern 7.x, puis la cible doThing affiche un message différent selon que PHP 7 est détecté ou non.

Comme Makefile ne supporte pas les expressions régulières, on s'appuie sur le shell pour exécuter la regex et retourner une chaîne non vide si l'expression correspond. Dans ce cas, la variable IS_PHP_7 est définie et la construction ifdef sera vérifiée.

Mode verbeux​

L'idée : ne pas afficher de message informatif lors de l'exécution de certaines targets.

Le code ci-dessous vérifie la présence de l'argument / valeur --quiet dans la variable standard $ARGS.

Exemple de code pour montrer comment activer/désactiver le mode verbeux dans un makefile.

En utilisant l'argument "--quiet" dans ARGS.

  • Mode verbeux : lancez make testme en ligne de commande
  • Mode silencieux : lancez make testme ARGS="--quiet" en ligne de commande
QUIET=$(if $(findstring --quiet,${ARGS}),true,false)
testme:
ifeq ($(QUIET),false)
@printf '\e[1;30m%s\n\e[m' "QUIET MODE NOT ENABLED - We'll show any informative text."
endif
@printf '\e[1;32m%s\n\n\e[m' "This is an important message"

Ce Makefile configure un mode silencieux en vérifiant si le flag --quiet est présent dans les arguments, puis la cible testme affiche un message d'avertissement si ce mode n'est pas activé, suivi d'un message important qui s'affiche toujours.

Le code ci-dessus définit une variable globale QUIET qui vaudra true ou false selon la présence du mot-clé --quiet dans ARGS.

Ensuite, utilisez la structure conditionnelle ifeq pour afficher (ou masquer) le message informatif.

En lançant make testme ARGS="--quiet", seul This is an important message sera affiché.

Travailler avec git​

Récupérer quelques informations importantes​

Récupérer quelques variables importantes depuis le shell :

GIT_CURRENT_BRANCH:=$(shell git rev-parse --abbrev-ref HEAD)
GIT_ROOT_DIR:=$(shell git rev-parse --show-toplevel)
GIT_URL_REPO:=$(shell git config --get remote.origin.url | sed -r 's:git@([^/]+)\:(.*\.git):https\://\1/\2:g' | grep -Po '.*(?=\.)')

Ce bloc Makefile capture des informations sur le dépôt Git courant : la branche actuelle, le répertoire racine du projet, et l'URL du dépôt distant en la convertissant du format SSH au format HTTPS. Ces variables sont stockées pour être réutilisées dans d'autres règles du Makefile.

Quelques targets git​

Une fois les variables initialisées, on peut faire des choses comme ceci :

git_open_repo:
@sensible-browser ${GIT_URL_REPO}
git_open_pipeline:
@sensible-browser ${GIT_URL_REPO}/pipelines
git_open_wiki:
@sensible-browser ${GIT_URL_REPO}/-/wikis/home

Ce Makefile définit trois cibles qui ouvrent différentes pages du dépôt Git dans le navigateur par défaut. Chaque cible utilise sensible-browser avec une URL construite à partir de la variable GIT_URL_REPO, en ajoutant des chemins spécifiques selon la page souhaitée.

Git - Travail en cours​

Lancez make git_wip pour pousser rapidement vos changements vers le repository distant et sauter les hooks locaux :

git_wip:
git add .
git commit -m "wip --no-verify
git push

Ce makefile définit une cible git_wip qui crée rapidement un commit de travail en cours sans vérifications. Elle ajoute tous les fichiers modifiés, les commite avec le message 'wip', puis pousse les changements vers le dépôt distant.

Travailler avec un projet PHP et les vendors​

Mettre à jour le dossier vendor seulement quand c'est nécessaire​

Avoir une target comme ci-dessous (appelée vendor) permet de répondre à la question Faut-il mettre à jour vendor ou non ?. Ça se fait en lançant d'abord la target composer.lock. L'idée est ensuite de comparer la date/heure de ce fichier et celle de composer.json. S'il y a une différence, l'outil Make lancera composer update et générera une nouvelle version de composer.lock et donc une nouvelle version de vendor aussi.

Si aucun changement n'a été fait dans le fichier composer.json, il n'y a rien à faire puisque vendor est considéré à jour.

Plutôt simple.

composer.lock: composer.json
${CMD} composer update --no-interaction
vendor: composer.lock
${CMD} composer install

Ce Makefile définit deux cibles qui gèrent les dépendances PHP du projet avec Composer. La cible composer.lock met à jour les dépendances déclarées dans composer.json, tandis que vendor installe les dépendances basées sur les versions verrouillées dans composer.lock. Les deux utilisent ${CMD} pour exécuter les commandes Composer.

Pour jouer le scénario, lancez simplement make vendor.

PHP - Contrôles qualité​

La portion ci-dessous peut simplement être copiée/collée dans votre propre Makefile pour ajouter des fonctionnalités de contrôle qualité basées sur l'image Docker https://github.com/jakzal/phpqa.

COLOR_BLUE:=34
COLOR_CYAN:=36
COLOR_YELLOW:=33
#### region - Implement `Quality Assurance` features; relying on the jakzal/phpqa Docker image)
#### Define the Docker image to use for PHPQA (https://github.com/jakzal/phpqa)
#! PHP_VERSION should be defined in the `.env` file
PHP_VERSION:=$(or $(PHP_VERSION),8.1)
DOCKER_PHPQA:=jakzal/phpqa:php${PHP_VERSION}-alpine
#### The full "docker run" command to be able to run a command in the jakzal/phpqa container
DOCKER_PHPQA_ARGS:=-it --rm -v ${PWD}:/project -w /project -v ${PWD}/.output/tmp-phpqa:/tmp
#### Get the current user/group ID and define the docker "-u" argument
DOCKER_PHPQA_UID_GID:=-u $(shell id -u):$(shell id -g)
#### Finally, the final command for running a command in the jakzal/phpqa container
DOCKER_PHPQA_COMMAND:=docker run ${DOCKER_PHPQA_ARGS} ${DOCKER_PHPQA_UID_GID} ${DOCKER_PHPQA}
##### Run PHAN and report problems
phan:
clear
#### This is how to define a local variable
$(eval COMMAND := $(shell echo "phan --progress-bar --config-file .config/phan.php"))
@printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Report PHAN errors in your PHP files (no fix) using $(DOCKER_PHPQA)"
@printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Running \"${COMMAND}\""
@printf "\e[1;${COLOR_BLUE}m%s\e[0m\n\n" "Note: make sure your /vendor folder is populated..."
@docker run -it ${DOCKER_PHPQA_ARGS} ${DOCKER_PHPQA} phan --version
@echo ""
${DOCKER_PHPQA_COMMAND} ${COMMAND}
##### Run PHP-CS-FIXER on the codebase IN A DRY-RUN MODE
php-cs-fixer-dry-run:
clear
#### This is how to define a local variable
$(eval COMMAND := $(shell echo "php-cs-fixer fix --verbose --config=/project/.config/.php-cs-fixer.php --using-cache=no --diff"))
@printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Report code formatting issues in your PHP files (no fix) using $(DOCKER_PHPQA)"
@printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Running \"${COMMAND} --dry-run\""
${DOCKER_PHPQA_COMMAND} ${COMMAND} --dry-run
##### Run PHP-CS-FIXER on the codebase and fix problems
php-cs-fixer-fix:
clear
#### This is how to define a local variable
$(eval COMMAND := $(shell echo "php-cs-fixer fix --verbose --config=/project/.config/.php-cs-fixer.php --using-cache=no --diff"))
@printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Automatically fix formatting issues in your PHP files using $(DOCKER_PHPQA)"
@printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Running \"${COMMAND}\""
${DOCKER_PHPQA_COMMAND} ${COMMAND}
##### PHP - Check the syntax of PHP files and report syntax errors if any
phplint:
clear
@printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Check syntax validity of php files using $(DOCKER_PHPQA)"
#### https://github.com/overtrue/phplint
${DOCKER_PHPQA_COMMAND} phplint --no-cache --exclude=vendor
##### YAML - Check the syntax of yaml files in the directory root folder and report syntax errors if any
yamllint:
clear
@printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Check syntax validity of yaml files using $(DOCKER_PHPQA)"
#### https://github.com/j13k/yaml-lint
#### (it seems yaml-lint can't search in all the tree structure and *.yml won't get .gitlab-ci.yml; strange)
${DOCKER_PHPQA_COMMAND} yaml-lint .*.yml *.yml .install/CI/*.yml
#### endregion

Ce fichier Makefile configure un ensemble de tâches d'assurance qualité pour PHP en utilisant l'image Docker jakzal/phpqa. Il définit les variables nécessaires pour exécuter des outils comme PHAN, php-cs-fixer et phplint dans des conteneurs Docker isolés, puis expose plusieurs cibles make (phan, php-cs-fixer-dry-run, php-cs-fixer-fix, phplint, yamllint) que les développeurs peuvent appeler pour analyser et corriger leur code.

Quelques fonctions​

Nettoyer des dossiers​

COLOR_YELLOW:=33
clean:
@printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Remove node_modules and vendor folders"
rm --recursive --force node_modules/ vendor/
@printf "\e[1;${COLOR_YELLOW}m\n%s\e[0m\n\n" "Empty .cache, .output and storage/logs"
rm --recursive --force .cache/* .output/* storage/logs/*

Ce Makefile définit une cible clean qui supprime les dossiers de dépendances et de cache du projet. Quand on exécute make clean, il affiche des messages jaunes en gras pour expliquer ce qui se passe, puis efface node_modules, vendor, .cache, .output et storage/logs.

On peut aussi d'abord tester l'existence du dossier :

clean:
test ! -e vendor || rm -rf vendor

Cette cible Makefile nettoie le répertoire vendor en le supprimant s'il existe. C'est une tâche courante dans les projets pour repartir d'une ardoise vierge en supprimant les dépendances téléchargées.

Ouvrir un navigateur web​

open-browser:
@sensible-browser http://www.google.fr
@echo "The site has been opened in your browser"

Cette cible Makefile ouvre le navigateur par défaut sur Google France en utilisant sensible-browser, puis affiche un message de confirmation à l'utilisateur.

C'est plutôt pratique quand vous travaillez avec des repositories git :

GIT_URL_REPO:=$(shell git config --get remote.origin.url | sed -r 's:git@([^/]+)\:(.*\.git):https\://\1/\2:g' | grep -Po '.*(?=\.)')
git_open_repo:
@sensible-browser ${GIT_URL_REPO}

Ce makefile extrait l'URL du dépôt Git distant, la convertit au format HTTPS si elle est en SSH, en supprime l'extension .git, puis fournit une cible pour ouvrir ce dépôt dans le navigateur par défaut du système.

Makefile auto-documenté​

Vous pouvez utiliser une astuce pour créer une target comme help. L'idée est de scanner le makefile courant et d'extraire la liste de tous les verbes et leur description.

Makefile auto-documenté

Il y a plusieurs façons d'y arriver. J'ai déjà écrit un article de blog à ce sujet : Linux Makefile - Ajouter un écran d'aide.

La complexité vient quand vous ajoutez des fichiers via include (comme le fichier .env) et que la structure peut être un peu différente de celle de votre makefile.

La solution donnée ici fonctionne pour moi.

Tutoriels​


Cet article vous a-t-il été utile ?
Avez-vous suivi les étapes de cet article ?