Makefiles

Introduction

La partie précédente a montré aux naifs qui en doutaient encore qu'une gestion de projet nécessitait de mettre en oeuvre la compilation séparée des composantes de l'application. Le compilateur est invoqué pour chaque fichier source et une fois supplémentaire pour l'édition de liens, dans le cas où l'on ne génère pas de librairies intermédiaires.

Lorsqu'un seul des fichiers sources est modifié, il n'est pas forcément utile de recompiler tous les fichiers sources pour regénérer l'application. On gagnera du temps à ne recompiler que les fichiers sources sur lesquels la modification du code effectuée a un réel impact. C'est à ce moment-là que le Makefile entre en jeu.

Le principe du Makefile est de construire un graphe de dépendance des fichiers constituant l'application, surchargé pour chaque fichier par la commande qui permet de le regénérer à partir des fichiers dont il dépend.

Un exemple simple

Le Makefile le plus simple à comprendre est celui où toutes les dépendances apparaissent explicitement. Exemple:

Example 1. Un exemple simple de Makefile

all : executable
executable : file1.o file2.o
        gcc -o executable file1.o file2.o
file1.o : file1.c file1.h
        gcc -c file1.c
file2.o : file2.c file1.h file2.h
        gcc -c file2.c
clean :
        rm file1.o file2.o executable core
     

On lance l'interpréteur de Makefile par la commande make. Par défaut, la première dépendance rencontrée tentera d'être résolue : all. Cette target nécessite de remettre à jour toutes les conditions situées à droite des :. make passe donc à l'unique target suivante, executable

D'après la deuxième ligne du Makefile, on voit que executable dépend des deux fichiers objets file1.o et file2.o. Récursivement, les fichiers dont dépendent file1.o et file2.o sont recherchés. Si un des fichiers dépendant est plus récent qu'un des fichiers cible au cours de cette recherche, le fichier cible doit être regénéré, et la règle de compilation associée est exécutée.

% make clean
rm file1.o file2.o executable core
rm: cannot remove `core': No such file or directory
% make
gcc -c file1.c
gcc -c file2.c
gcc -o executable file1.o file2.o
% touch file2.h
% make
gcc -c file2.c
gcc -o executable file1.o file2.o
% touch file2.o
% make
gcc -o executable file1.o file2.o
% touch file1.h
% make
gcc -c file1.c
gcc -c file2.c
gcc -o executable file1.o file2.o
    

Règles génériques

Des règles génériques évitent d'avoir à écrire ligne de Makefile pour chaque fichier source.

Example 2. Un autre exemple de Makefile

CC = gcc
CFLAGS = -O2 -c
OBJS = file1.o file2.o

all : executable
.c.o :
        $(CC) $(CFLAGS) $<
executable : $(OBJS)
        $(CC) -o $@ $(OBJS)
     

La règle .c.o: s'applique pour tous les fichiers .o et ont comme unique dépendance le fichier .c correspondant. $< est alors substitué par le nom du fichier sur lequel cette règle est instanciée.

On englobe pratiquement le premier exemple de Makefile, à la seule différence que l'on ne tient pas compte des dépendances sur les fichiers .h. La commande makedepend permet d'automatiser la génération de ces dépendances supplémentaires.

Example 3. Les dépendances supplémentaires

% cat Makefile
CC = gcc
CFLAGS = -O2 -c
OBJS = file1.o file2.o
SRCS = file1.c file2.c

all : executable
.c.o :
        $(CC) $(CFLAGS) $<
executable : $(OBJS)
        $(CC) -o $@ $(OBJS)CC = gcc
depend :
        makedepend -I. $(SRC)
% cat file1.c
#include "file1.h"
main() {}
% cat file2.c
#include "file2.h"
#include "file1.h"
% makedepend -I. file1.c file2.c
% cat Makefile
CC = gcc
CFLAGS = -O2 -c
OBJS = file1.o file2.o
SRCS = file1.c file2.c

all : executable
.c.o :
        $(CC) $(CFLAGS) $<
executable : $(OBJS)
        $(CC) -o $@ $(OBJS)CC = gcc
depend :
        makedepend -I. $(SRC)

# DO NOT DELETE

file1.o: ./file1.h
file2.o: ./file2.h ./file1.h
     

Plus loin

Les makefiles peuvent servir à automatiser toutes les tâches nécessitant d'appliquer plusieurs traitements consécutifs à un document. A titre d'exemple, ce document a été généré en utilisant un Makefile. Le fichier source est un document SGML, il est traité par la commande jade pour produire un document HTML et un document TeX. Le document TeX est ensuite compilé à son tour. Le makefile utilisé dans ce cas est le suivant:

all : development.ps index.html 

index.html : development.sgml
        jade -ihtml -t sgml -d formation.dsl\#html development.sgml
development.tex : development.sgml
        jade -t tex -d formation.dsl\#print development.sgml
development.dvi : development.tex
        jadetex $<
development.ps : development.dvi
        dvips -o $@ $<