Les makefiles constituent un outil fondamental pour automatiser la compilation de programmes, en particulier en langage C. Un tutoriel paru en 2008, toujours pertinent pour les développeurs débutants ou intermédiaires, propose une introduction progressive à leur rédaction, depuis le cas le plus simple jusqu'à une structure de projet incluant des répertoires séparés pour les fichiers sources, les en-têtes et les bibliothèques.
Un premier exemple pour comprendre le principe
Le tutoriel part de trois fichiers typiques : un programme principal (hellomake.c), un fichier de fonctions (hellofunc.c) et un fichier d'en-tête (hellomake.h). La commande de compilation classique gcc -o hellomake hellomake.c hellofunc.c -I. suffit à produire l'exécutable. L'option -I. indique au compilateur de chercher les fichiers d'en-tête dans le répertoire courant. Sans makefile, le développeur doit retaper cette commande à chaque modification, ou utiliser la flèche haut du terminal pour rappeler la dernière commande. Cette méthode devient inefficace dès que le projet s'agrandit ou que l'on change de machine.
La première règle : un makefile minimal
Le plus simple des makefiles ne contient qu'une règle. Il suffit d'écrire dans un fichier nommé Makefile ou makefile :
hellomake: hellomake.c hellofunc.c
gcc -o hellomake hellomake.c hellofunc.c -I.
En tapant make dans le terminal, la première règle du fichier est exécutée. La ligne après les deux-points liste les fichiers dont dépend la cible : si l'un d'eux est modifié, make recompile. Le tutoriel insiste sur un point crucial : la ligne de commande doit impérativement commencer par une tabulation, et non par des espaces.
Introduire des variables pour plus de souplesse
Le deuxième exemple introduit des constantes : CC=gcc (le compilateur C) et CFLAGS=-I. (les options de compilation). Les fichiers objets .o remplacent les sources dans la dépendance :
CC=gcc
CFLAGS=-I.
hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o
Avec cette forme, make sait qu'il doit d'abord compiler les fichiers .c en .o avant de construire l'exécutable. Cette approche convient à la plupart des petits projets. Cependant, elle ne tient pas compte des modifications apportées aux fichiers d'en-tête : si l'on change hellomake.h, les fichiers .c ne sont pas recompilés.
Gérer les dépendances aux en-têtes
La troisième version corrige ce problème en définissant une macro DEPS = hellomake.h et en écrivant une règle générique pour tous les fichiers .o :
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o
Le % est un joker qui s'applique à tous les fichiers .o. Les macros automatiques $@ (la cible, à gauche des deux-points) et $< (le premier élément de la liste des dépendances) évitent de répéter les noms de fichiers.
Généraliser avec $@ et $^
Le quatrième makefile simplifie encore la règle finale en utilisant $^, qui représente tous les éléments à droite des deux-points :
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
OBJ = hellomake.o hellofunc.o
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS)
Les variables DEPS et OBJ rendent le makefile plus lisible et facile à modifier lorsque de nouveaux fichiers s'ajoutent au projet.
Une structure avancée avec des répertoires séparés
Le dernier exemple propose une organisation professionnelle : les fichiers d'en-tête dans ../include, le code source dans le répertoire courant (où se trouve aussi le makefile), les bibliothèques dans ../lib, et les fichiers objets dans un sous-répertoire obj. La variable IDIR pointe vers le dossier des en-têtes, LDIR vers celui des bibliothèques. La macro LIBS = -lm permet d'inclure la bibliothèque mathématique.
IDIR =../include
CC=gcc
CFLAGS=-I$(IDIR)
ODIR=obj
LDIR =../lib
LIBS=-lm
_DEPS = hellomake.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))
_OBJ = hellomake.o hellofunc.o
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))
$(ODIR)/%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
.PHONY: clean
clean:
rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~
La règle .PHONY: clean empêche make de confondre la cible clean avec un éventuel fichier du même nom. Le tutoriel précise que l'on peut ajouter plusieurs règles dans un même makefile et même créer des règles qui en appellent d'autres.
Pour aller plus loin
Ce tutoriel ne couvre qu'une partie des possibilités offertes par make. Les développeurs souhaitant approfondir peuvent consulter le manuel officiel de GNU Make, disponible en ligne. La maîtrise des makefiles reste une compétence précieuse pour gérer efficacement des projets de taille petite à moyenne, en évitant les recompilations inutiles et en assurant la portabilité des instructions de compilation.