Vous répétez les mêmes commandes à chaque build ? Vous oubliez l’ordre des étapes de déploiement ? Le Makefile résout ce problème depuis 1976 — et reste l’outil d’automatisation le plus universel en 2026.
GNU Make exécute des tâches (compilation, tests, déploiement) en respectant les dépendances entre fichiers. Il ne recompile que ce qui a changé, parallélise les tâches, et documente votre workflow dans un fichier versionnable.
Ce guide est fait pour vous si…
Section intitulée « Ce guide est fait pour vous si… »Pourquoi utiliser Make en 2026 ?
Section intitulée « Pourquoi utiliser Make en 2026 ? »Make a 50 ans, mais reste l’outil d’automatisation le plus utilisé dans le monde Unix. Pourquoi ? Parce qu’il résout un problème universel : éviter de refaire ce qui a déjà été fait.
Imaginons que vous modifiez un seul fichier dans un projet de 100 fichiers. Sans Make, vous devriez tout recompiler (lent). Avec Make, seul le fichier modifié et ses dépendants sont recompilés (rapide). C’est le principe de la compilation incrémentale.
Voici ce qui rend Make incontournable :
| Avantage | Ce que ça signifie concrètement |
|---|---|
| Universel | Préinstallé sur Linux/macOS — pas besoin de convaincre l’équipe d’installer un outil |
| Incrémental | Modifiez 1 fichier sur 100 → seul ce fichier est recompilé (gain de temps énorme) |
| Parallélisable | Sur une machine 8 cœurs, -j8 divise le temps de build par ~8 |
| Documentable | Le Makefile est la documentation : lisez-le pour comprendre comment builder |
| Sans dépendances | Pas de Node.js, Python ou Java requis — juste make |
| Intégré CI/CD | make test en local = make test dans GitHub Actions = même résultat |
Installation
Section intitulée « Installation »sudo apt update && sudo apt install build-essentialLe paquet build-essential inclut make, gcc et les outils de compilation.
sudo dnf groupinstall "Development Tools"# Avec Xcode Command Line Toolsxcode-select --install
# Ou via Homebrewbrew install make# Avec Chocolateychoco install make
# Ou via WSL (recommandé)wsl --installVérification
Section intitulée « Vérification »make --versionSortie attendue :
GNU Make 4.3Built for x86_64-pc-linux-gnuCopyright (C) 1988-2020 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.Comprendre la structure d’un Makefile
Section intitulée « Comprendre la structure d’un Makefile »Un Makefile contient des règles qui définissent comment construire des cibles à partir de dépendances.
Anatomie d’une règle
Section intitulée « Anatomie d’une règle »Chaque règle suit la même structure :
cible: dépendances commandePrenons un exemple concret pour décortiquer chaque élément :
hello.o: hello.c hello.h gcc -c hello.c -o hello.oLa cible (hello.o) : c’est le fichier que vous voulez créer. Quand vous tapez make hello.o, Make cherche cette règle.
Les dépendances (hello.c hello.h) : ce sont les fichiers nécessaires pour construire la cible. Si l’un d’eux est plus récent que la cible, Make exécute la commande. Sinon, il considère que la cible est à jour et ne fait rien.
La commande (gcc -c hello.c -o hello.o) : l’instruction shell à exécuter pour construire la cible à partir des dépendances. Elle doit être précédée d’une tabulation.
Premier Makefile
Section intitulée « Premier Makefile »Créons un exemple minimal :
# Mon premier Makefilehello: echo "Hello, Make!"Exécution :
make helloSortie :
echo "Hello, Make!"Hello, Make!Make affiche la commande avant de l’exécuter. Pour masquer la commande, préfixez avec @ :
hello: @echo "Hello, Make!"Variables et substitutions
Section intitulée « Variables et substitutions »Les variables simplifient la maintenance du Makefile.
Définition et usage
Section intitulée « Définition et usage »# DéfinitionCC = gccCFLAGS = -Wall -Wextra -pedanticTARGET = hello
# Usage avec $(...)$(TARGET): main.o $(CC) $(CFLAGS) -o $(TARGET) main.oTypes d’assignation
Section intitulée « Types d’assignation »Make propose plusieurs façons d’assigner une valeur à une variable. La différence est subtile mais importante quand vos variables référencent d’autres variables.
Le piège classique : avec =, la valeur est évaluée à chaque utilisation. Si une variable référencée change entre-temps, le résultat change aussi. Avec :=, la valeur est figée au moment de la définition.
| Syntaxe | Quand l’utiliser | Exemple concret |
|---|---|---|
VAR = valeur | Quand la valeur dépend d’autres variables qui peuvent changer | CFLAGS = $(BASE_FLAGS) $(EXTRA) |
VAR := valeur | Quand vous voulez figer la valeur immédiatement (plus prévisible) | NOW := $(shell date) |
VAR ?= valeur | Pour définir une valeur par défaut surchargeable | DEBUG ?= 0 → make DEBUG=1 |
VAR += valeur | Pour ajouter à une variable existante | CFLAGS += -O2 |
Conseil : utilisez := par défaut. C’est plus prévisible et évite les évaluations en boucle accidentelles.
# Exemple pratiqueCFLAGS := -Wall # := fige la valeur maintenantCFLAGS += -O2 # Ajoute -O2 → CFLAGS = "-Wall -O2"
DEBUG ?= 0 # Valeur par défaut, surchargeable : make DEBUG=1Variables automatiques
Section intitulée « Variables automatiques »Dans une règle, Make crée automatiquement des variables qui contiennent les noms de fichiers impliqués. Ces “raccourcis” évitent de répéter les noms et rendent les règles génériques.
Analogie : pensez à $@ comme “moi” (la cible) et $< comme “mon ingrédient principal” (la première dépendance).
| Variable | Ce qu’elle contient | Quand l’utiliser |
|---|---|---|
$@ | Le nom de la cible qu’on construit | Toujours, pour le fichier de sortie |
$< | La première dépendance | Pour compiler un seul fichier source |
$^ | Toutes les dépendances (séparées par espaces) | Pour l’édition des liens (rassembler plusieurs .o) |
$* | Le “radical” du pattern (la partie qui matche %) | Dans les règles avec % |
Exemple concret : dans la règle main.o: main.c hello.h, les variables valent :
$@=main.o(ce qu’on fabrique)$<=main.c(premier ingrédient)$^=main.c hello.h(tous les ingrédients)
# Compilation : un .c → un .o%.o: %.c $(CC) $(CFLAGS) -c $< -o $@ # $< = le fichier .c (ex: main.c) # $@ = le fichier .o (ex: main.o)
# Édition des liens : plusieurs .o → un exécutablehello: main.o utils.o $(CC) -o $@ $^ # $@ = hello # $^ = main.o utils.oMakefile complet : projet C
Section intitulée « Makefile complet : projet C »Voici un Makefile réaliste pour un projet C, testé et fonctionnel.
Structure du projet
Section intitulée « Structure du projet »projet/├── Makefile├── main.c├── hello.c└── hello.hLe Makefile
Section intitulée « Le Makefile »# VariablesCC = gccCFLAGS = -Wall -Wextra -pedanticTARGET = hello
# Fichiers objetsOBJS = main.o hello.o
# Cible par défautall: $(TARGET)
# Édition des liens$(TARGET): $(OBJS) $(CC) $(CFLAGS) -o $@ $^
# Compilation des fichiers .c en .o%.o: %.c hello.h $(CC) $(CFLAGS) -c $< -o $@
# Nettoyage.PHONY: clean mrproper help
clean: rm -f *.o
mrproper: clean rm -f $(TARGET)
help: @echo "Cibles disponibles :" @echo " all - Compile le programme (défaut)" @echo " clean - Supprime les fichiers objets" @echo " mrproper - Supprime tout (objets + exécutable)" @echo " help - Affiche cette aide"Exécution
Section intitulée « Exécution »# Build completmakeSortie :
gcc -Wall -Wextra -pedantic -c main.c -o main.ogcc -Wall -Wextra -pedantic -c hello.c -o hello.ogcc -Wall -Wextra -pedantic -o hello main.o hello.o# Rien à faire si déjà compilémakemake: Nothing to be done for 'all'.# Modifier un fichier → recompilation partielletouch hello.c && makegcc -Wall -Wextra -pedantic -c hello.c -o hello.ogcc -Wall -Wextra -pedantic -o hello main.o hello.oPattern rules et fonctions
Section intitulée « Pattern rules et fonctions »Règles de pattern
Section intitulée « Règles de pattern »Les patterns avec % évitent de répéter les règles pour chaque fichier :
# Compile tous les .c en .o%.o: %.c $(CC) $(CFLAGS) -c $< -o $@Le % est un joker : main.o matche avec main.c, utils.o avec utils.c, etc.
Fonctions intégrées
Section intitulée « Fonctions intégrées »Make inclut des fonctions pour manipuler les listes de fichiers. Elles évitent de coder en dur les noms de fichiers — le Makefile s’adapte automatiquement quand vous ajoutez ou supprimez des fichiers sources.
Le problème qu’elles résolvent : sans fonctions, vous devez lister manuellement chaque fichier .c. Si vous en ajoutez un, vous oubliez de mettre à jour le Makefile → bug. Avec $(wildcard *.c), Make détecte automatiquement tous les fichiers .c.
| Fonction | Ce qu’elle fait | Exemple d’utilisation |
|---|---|---|
$(wildcard *.c) | Liste tous les fichiers qui matchent le pattern | Détecter automatiquement les sources |
$(patsubst %.c,%.o,$(SRC)) | Remplace .c par .o dans une liste | Générer la liste des fichiers objets |
$(shell cmd) | Exécute une commande shell et récupère la sortie | $(shell date), $(shell git describe) |
$(filter %.c,$(FILES)) | Ne garde que les éléments qui matchent | Séparer les .c des .h dans une liste mixte |
$(info message) | Affiche un message pendant le parsing | Debug : voir la valeur d’une variable |
Exemple typique : générer automatiquement la liste des fichiers objets à partir des sources.
# Avant (manuel, fragile)OBJS = main.o utils.o config.o # Oubli facile si on ajoute un fichier
# Après (automatique, robuste)SRC := $(wildcard *.c) # → main.c utils.c config.cOBJS := $(patsubst %.c,%.o,$(SRC)) # → main.o utils.o config.oExemple avancé avec fonctions
Section intitulée « Exemple avancé avec fonctions »# Détection automatique des sourcesSRC_DIR := .BUILD_DIR := build
SOURCES := $(wildcard $(SRC_DIR)/*.c)OBJECTS := $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SOURCES))TARGET := $(BUILD_DIR)/hello
# Debug : afficher les variables$(info SOURCES = $(SOURCES))$(info OBJECTS = $(OBJECTS))
all: $(BUILD_DIR) $(TARGET)
$(BUILD_DIR): mkdir -p $(BUILD_DIR)
$(TARGET): $(OBJECTS) $(CC) $(CFLAGS) -o $@ $^
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR) $(CC) $(CFLAGS) -c $< -o $@Sortie :
SOURCES = ./hello.c ./main.cOBJECTS = build/hello.o build/main.omkdir -p buildgcc -Wall -Wextra -c hello.c -o build/hello.ogcc -Wall -Wextra -c main.c -o build/main.ogcc -Wall -Wextra -o build/hello build/hello.o build/main.oConditionnelles
Section intitulée « Conditionnelles »Make supporte des conditions basiques :
# Activer le debug si DEBUG=1ifdef DEBUGCFLAGS += -g -DDEBUG$(info Mode DEBUG activé)elseCFLAGS += -O2endifUsage :
# Build optimisé (défaut)make
# Build debugmake DEBUG=1Sortie avec DEBUG=1 :
Mode DEBUG activégcc -Wall -Wextra -g -DDEBUG -c main.c -o main.oSyntaxes conditionnelles détaillées
Section intitulée « Syntaxes conditionnelles détaillées »Make évalue les conditions au moment où il lit le Makefile, pas pendant l’exécution. C’est important : vous ne pouvez pas tester le résultat d’une commande shell dans une condition Make (utilisez $(if ...) ou testez dans la commande shell elle-même).
ifdef VAR : vrai si la variable est définie, même si elle est vide. Utile pour activer un mode optionnel.
# Active le debug si DEBUG est défini (à n'importe quelle valeur)ifdef DEBUG CFLAGS += -g -DDEBUGendif
# Usage : make DEBUG=1 ou même make DEBUG=anythingifeq ($(VAR),value) : vrai si la variable égale exactement la valeur. Attention aux espaces — ifeq ($(VAR), value) (avec espace) ne matche pas value.
# Ajuste les flags selon le système d'exploitationifeq ($(OS),Linux) LIBS += -lpthreadendififeq ($(OS),Darwin) LIBS += -framework CoreFoundationendififneq ($(VAR),) : vrai si la variable n’est pas vide. Plus strict que ifdef (une variable définie mais vide est considérée comme “fausse”).
# Compile en release sauf si DEBUG contient quelque choseifneq ($(DEBUG),) CFLAGS += -gelse CFLAGS += -O2endifMakefile DevOps : Docker et CI/CD
Section intitulée « Makefile DevOps : Docker et CI/CD »Les Makefiles sont parfaits pour orchestrer les workflows DevOps.
Makefile auto-documenté
Section intitulée « Makefile auto-documenté »Le pattern ## commentaire permet de générer automatiquement l’aide :
# Makefile DevOps
APP_NAME := myappVERSION := $(shell git describe --tags 2>/dev/null || echo "dev")DOCKER_REPO := ghcr.io/monrepoIMAGE := $(DOCKER_REPO)/$(APP_NAME):$(VERSION)
.PHONY: help build test lint docker-build docker-push clean
.DEFAULT_GOAL := help
help: ## Affiche cette aide @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ awk 'BEGIN {FS = ":.*?## "}; {printf "%-15s %s\n", $$1, $$2}'
build: ## Compile l'application @echo "Compilation de $(APP_NAME)..." $(CC) $(CFLAGS) -o $(APP_NAME) main.c hello.c
test: build ## Lance les tests @echo "Exécution des tests..." ./$(APP_NAME) @echo "Tests OK"
lint: ## Vérifie le code @echo "Analyse statique..." @which cppcheck > /dev/null && cppcheck --enable=all . || echo "cppcheck non installé"
docker-build: ## Construit l'image Docker docker build -t $(IMAGE) .
docker-push: docker-build ## Pousse l'image sur le registry docker push $(IMAGE)
clean: ## Nettoie les fichiers générés rm -f $(APP_NAME) *.o
info: ## Affiche les informations du projet @echo "Application : $(APP_NAME)" @echo "Version : $(VERSION)" @echo "Image : $(IMAGE)"Exécution :
make helphelp Affiche cette aidebuild Compile l'applicationtest Lance les testslint Vérifie le codedocker-build Construit l'image Dockerdocker-push Pousse l'image sur le registryclean Nettoie les fichiers générésinfo Affiche les informations du projetmake infoApplication : myappVersion : devImage : ghcr.io/monrepo/myapp:devIntégration CI/CD
Section intitulée « Intégration CI/CD »Un Makefile centralise les commandes utilisées dans la CI :
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: make lint - run: make test - run: make docker-buildAvantage : les mêmes commandes fonctionnent en local et en CI.
Débogage et diagnostic
Section intitulée « Débogage et diagnostic »Quand Make ne fait pas ce que vous attendez, plusieurs options permettent de comprendre ce qui se passe “sous le capot”.
Options de debug
Section intitulée « Options de debug »Voici les options les plus utiles, de la plus courante à la plus détaillée :
-n (dry-run) : la plus utile au quotidien. Make affiche les commandes qu’il exécuterait, sans rien exécuter. Parfait pour vérifier avant un make deploy.
make -n cleanrm -f *.orm -f hello-B (force rebuild) : ignore les timestamps et reconstruit tout. Utile quand Make dit “Nothing to be done” alors que quelque chose a changé.
make -B-p (print database) : affiche toutes les variables et règles connues de Make, y compris les règles implicites. Très verbeux, mais indispensable pour comprendre d’où vient une variable.
# Voir la valeur de CC et CFLAGSmake -p | grep -E '^(CC|CFLAGS) ='CC = gccCFLAGS = -Wall -Wextra -pedantic-d (debug mode) : affiche chaque décision prise par Make. Très verbeux (ça peut faire des milliers de lignes), à utiliser en dernier recours.
| Option | Quand l’utiliser |
|---|---|
-n | Vérifier ce que Make ferait avant d’exécuter |
-B | Forcer la reconstruction complète |
-p | Trouver la valeur d’une variable ou l’origine d’une règle |
-d | Comprendre pourquoi Make reconstruit (ou pas) une cible |
Erreurs courantes
Section intitulée « Erreurs courantes »Voici les erreurs que vous rencontrerez le plus souvent, avec leur cause et la solution détaillée.
*** missing separator. Stop.
Section intitulée « *** missing separator. Stop. »C’est l’erreur #1. Make exige une tabulation (pas des espaces) avant chaque commande. Beaucoup d’éditeurs convertissent les tabulations en espaces par défaut.
# ❌ Espaces (invisible mais Make refuse)hello: echo "Hello" # 4 espaces = ERREUR
# ✅ Tabulation (ce que Make veut)hello: echo "Hello" # 1 tabulation = OKSolution : configurez votre éditeur pour insérer de vraies tabulations dans les Makefiles, ou utilisez cat -A Makefile pour voir les caractères invisibles (^I = tabulation, espace = rien).
No rule to make target 'fichier.o'
Section intitulée « No rule to make target 'fichier.o' »Make ne trouve pas comment construire un fichier demandé comme dépendance. Causes possibles :
- Faute de frappe :
main.odépend demian.c(au lieu demain.c) - Fichier absent : le fichier source n’existe pas
- Règle manquante : pas de règle pour transformer
.cen.o
Solution : vérifiez que le fichier existe (ls -la) et que son nom correspond exactement à la dépendance.
Nothing to be done for 'all'
Section intitulée « Nothing to be done for 'all' »Ce n’est pas une erreur. Make considère que la cible est à jour (tous les fichiers de sortie sont plus récents que les sources).
Si c’est inattendu : un fichier source a peut-être été modifié sans que son timestamp change (copie depuis un autre dossier). Utilisez make -B pour forcer la reconstruction.
Command not found
Section intitulée « Command not found »La commande appelée dans une règle n’existe pas dans le PATH.
Solution : installez le programme (apt install gcc) ou spécifiez le chemin absolu (CC := /usr/local/bin/gcc).
Circular dependency dropped
Section intitulée « Circular dependency dropped »La cible A dépend de B, qui dépend de A. Make détecte la boucle et l’ignore, ce qui produit souvent un build incomplet.
Solution : revoyez vos dépendances pour casser le cycle.
Compilation parallèle
Section intitulée « Compilation parallèle »Par défaut, Make exécute les commandes une par une. Sur un processeur multi-cœurs, c’est du gâchis : pendant qu’un fichier compile, les autres cœurs ne font rien.
L’option -j (pour “jobs”) permet d’exécuter plusieurs commandes en parallèle. Make analyse les dépendances pour déterminer ce qui peut s’exécuter simultanément.
# 4 compilations en parallèle (bon pour un quad-core)make -j4
# Autant de jobs que de cœurs (détection automatique)make -j$(nproc)
# Attention : illimité peut saturer la mémoiremake -j # Déconseillé sur les gros projetsGain concret : sur un projet de 100 fichiers avec 8 cœurs, make -j8 peut diviser le temps de compilation par 5 à 7 (pas 8, car l’édition des liens reste séquentielle).
Bonnes pratiques
Section intitulée « Bonnes pratiques »Après avoir utilisé Make sur des dizaines de projets, voici les conventions qui évitent les problèmes.
Structure recommandée
Section intitulée « Structure recommandée »Organisez votre Makefile de haut en bas : d’abord les variables (ce qui change), puis les cibles principales (ce qu’on veut faire), enfin les détails d’implémentation (comment on le fait).
# === Variables ===# Regroupées en haut pour une modification facileCC := gccCFLAGS := -Wall -Wextra
# === Cibles principales ===# Ce que l'utilisateur appelle directement.PHONY: all clean test help
.DEFAULT_GOAL := all # "make" sans argument = "make all"
all: build
# === Build ===build: $(TARGET)
$(TARGET): $(OBJS) $(CC) -o $@ $^
# === Tests ===test: build ./run_tests.sh
# === Nettoyage ===clean: rm -f $(OBJS) $(TARGET)
# === Aide ===help: @echo "Utilisation: make [cible]"Conventions à suivre
Section intitulée « Conventions à suivre »Une cible all comme point d’entrée. C’est ce que les gens attendent quand ils tapent make sans argument.
Une cible clean pour repartir de zéro. Indispensable quand le build est dans un état bizarre.
Une cible help pour documenter. Même vous, dans 6 mois, vous aurez oublié les options.
.PHONY systématiquement pour les cibles non-fichiers. Ça coûte une ligne et évite des bugs subtils.
Variables en MAJUSCULES pour les distinguer des commandes shell. $(CC) est clairement une variable Make, $cc pourrait être une variable shell.
Commentaires pour les règles complexes. Si vous avez besoin de relire la doc Make pour comprendre une ligne, ajoutez un commentaire.
À retenir
Section intitulée « À retenir »Si vous ne devez retenir que l’essentiel de ce guide :
-
Make automatise des tâches en ne ré-exécutant que ce qui est nécessaire. Il compare les dates de modification des fichiers pour décider quoi reconstruire.
-
La tabulation est obligatoire avant chaque commande. C’est la cause d’erreur #1 — configurez votre éditeur correctement.
-
Les variables automatiques vous font gagner du temps :
$@(cible),$<(première dépendance),$^(toutes les dépendances). -
.PHONYdéclare les cibles qui ne sont pas des fichiers (commeclean,test,help). Sans ça, un fichier nommécleanempêcheraitmake cleande fonctionner. -
-n(dry-run) montre ce que Make ferait sans l’exécuter. Utilisez-le systématiquement avant unmake deployou toute action irréversible. -
-j4parallélise les tâches sur 4 cœurs. Sur un projet conséquent, le gain de temps est significatif. -
Le pattern
## commentaireavecgreppermet de générer automatiquement une aide — documentez vos Makefiles ! -
Make n’est pas limité au C : il peut orchestrer n’importe quel workflow (Docker, Terraform, tests, déploiements).