make est un outil qui permet de déterminer quelles parties d'un programme nécessitent une recompilation et de lancer les commandes nécessaires pour les recompiler. C'est un outil qui est très utilisé dans le cas de programmes C, mais que l'on peut utiliser pour des cas beaucoup plus proches du développement web. Il a aussi l'avantage d'être largement disponible sur les systèmes ce qui permet de ne pas avoir à installer une dépendance supplémentaire juste pour utiliser make.
Makefile
Afin d'utiliser make
il faut commencer par créer un fichier Makefile
qui va permettre de décrire la relation entre les fichiers du programme et les commandes à éxécuter pour les obtenir. La syntaxe d'un Makefile est très simple :
cible: prerequis1 prerequis2
command1
command2
Attention ! les commandes doivent être précédée d'une tabulation (pas de 2 ou 4 espaces, une vraie tabulation). Dans le cas d'un projet C on peut ainsi indiquer comment compiler les fichiers à partir de leur dépendances
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
Ainsi pour générer le fichier main.o
, make sait qu'il doit regarder le fichier main.c
et defs.h
. Si le fichier main.o
est plus récent que les 2 prérequis alors aucune opération ne sera effectuée, sinon il éxécutera la commande cc -c main.c
.
Mais je fais du web pas du C !
Dans le cas d'un gros projet C on peut comprendre que make permet d'économiser du temps en évitant de devoir tout recompiler en permanence. Mais du coup, comment make peut être utilé dans le cadre d'un projet web ?
Il peut par exemple servir à décrire la phase d'installation des différentes dépendances. Par exemple dans le cadre d'un projet PHP :
.PHONY: install update
composer.lock: composer.json
composer update
vendor: composer.lock
composer install
install: vendor
On indique ici plusieurs choses :
- Si le fichier
composer.json
est plus récent que le fichiercomposer.lock
on met à jour les dépendances via la commandecomposer update
. - Si le fichier
composer.lock
est plus récent que le dossiervendor
alors on installe les dépendances. - On crée une "fausse" cible (déclarée dans le .PHONY) qui permet de lancer l'installation via un simple
make install
L'utilisation de la cible ".PHONY" permet d'utiliser make
comme un simple système d'alias :
.PHONY: test server cache-clear install help
.DEFAULT_GOAL= help
composer.lock: composer.json
composer update
vendor: composer.lock
composer install
install: vendor
help:
@grep -E '(^[a-zA-Z_-]+:.*?##.*$$)|(^##)' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-10s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/'
test: install ## Lance les tests unitaire
php ./vendor/bin/phpunit --stop-on-failure
cache-clear: ## Nettoie le cache
rm -rf ./tmp
server: install ## Lance le serveur interne de PHP
ENV=dev php -S localhost:8000 -t public/ -d display_errors=1
On remarque ici que notre cible install
est utilisée en prérequis de la cible test
. Ainsi, si on lance la commande make test
sans avoir préinstallé les dépendances, make le détectera et commancera par éxécuter les cibles nécessaires.
Les variables
On peut pousser les choses un peu plus loin en utilisant des variables. Il est par exemple possible de spécifier l'éxécutable à utiliser dans une variable.
test: install ## Lance les tests unitaire
$(PHP) ./vendor/bin/phpunit --stop-on-failure
On peut ainsi facilement redéfinir la variable en amont et lancer les tests unitaires avec différentes versions :
PHP=php
CURRENT_DIR=$(shell pwd)
ifdef VERSION
PHP=docker run -it --rm --name phpcli -v $(CURRENT_DIR):/usr/src/myapp -w /usr/src/myapp php:$(VERSION)-cli php
endif
test: install ## Lance les tests unitaire
$(PHP) ./vendor/bin/phpunit --stop-on-failure
On choisit ici de lancer nos tests gràce à docker, ce qui nous permet de facilement utiliser plusieurs versions d'un même outil :
make test
make test VERSION=7.0
make test VERSION=7.1
make test VERSION=7.2-rc
Pour en apprendre plus sur l'utilisation des variables n'hésitez pas à faire un tour sur le manuel.
Pattern Rule
Il est aussi possible de définir des motifs de règles gràce au symbole %
:
images/optimized/%.jpg: images/raw/%.jpg
mkdir -p images/optimized
guetzli --quality 85 --verbose $< $@
Dès qu'une autre règle aura besoin d'une image située dans le dossier optimized, make sera en mesure de comprendre comment la construire. Combiné avec l'utilisation de variables et de fonctions il est possible de créer des tâches plus complexes :
RAW_IMAGES=$(subst images/raw/,images/optimized/,$(wildcard images/raw/*.jpg))
images/optimized/%.jpg: images/raw/%.jpg
mkdir -p images/optimized
guetzli --quality 85 --verbose $< $@
images: $(SRC)
Si on tape make images
le système va automatiquement optimiser les nouvelles images et les placer dans le dossier optimized. Il est aussi possible, gràce au drapeau -j
de paralléliser les tâches.
make -j8 images
Cette parallélisation peut aussi être utilisée pour démarrer plusieurs tâches en même temps. Par exemple pour lancer le serveur web interne de PHP et browser-sync pour actualiser automatiquement la page.
PHP=php
PORT?=8000
HOST?=127.0.0.1
COM_COLOR = \033[0;34m
OBJ_COLOR = \033[0;36m
OK_COLOR = \033[0;32m
ERROR_COLOR = \033[0;31m
WARN_COLOR = \033[0;33m
NO_COLOR = \033[m
server: install ## Lance le serveur interne de PHP
echo -e "Lancement du serveur sur $(OK_COLOR)http://$(HOST):$(PORT)$(NO_COLOR)"
ENV=dev $(PHP) -S $(HOST):$(PORT) -t public/ -d display_errors=1
browsersync:
browser-sync start --port 3000 --proxy localhost:$(PORT) --files 'src/**/*.php' --files 'src/**/*.twig'
watch: server browsersync
La commande make -j2 watch
permettra donc de lancer les cibles server et browsersync en parallèle.