Les Makefiles sont des fichiers permettant d’executer un ensemble d’actions, comme la compilation d’un projet, la mise à jour d’un rapport, où l’archivage de données. Les Makefiles sont des fichiers shell particulier ils respectent cependant les conventions de codages du shell. L’idée de ce document est de présenté l’idée générale d’un Makefile via un exemple basique, puis d’aller vers un exemple plus complexe, le Makefile pour notre robot.

Support de présentation

Rediffusion de la formation

Exercice

Archive Exercice Correction

Un exemple basique

Considérons un exemple de projet très basique, constitué de 3 fichiers : hello.c, hello.h et main.c.De manière littérale nous avons définit des fonctions dans hello.c, ces fonctions sont reférencées par leurs prototypes dans hello.h. Par ailleurs nous avons définies des fonctions dans main.c, nous sous entendons que les fonctions de main.c font appels à celles de hello.c (d’où l’inclusion de hello.h dans main.c).

Nous voulons donc compiler main.c pour créer un executable que nous appellerons project. Si nous essayons directement de faire shell gcc -Wall -Werror -std=c11 main.c project nous allons avoir des références indéfinies. En effet il faut d’abbord compiler hello.c en hello.o pour que la machine connaisse les fonctions définit dans le hello.c (c’est le rôle du hello.o). Ainsi pour créer project il faut faire deux lignes de commande gcc -Wall -Werror -std=c99 hello.c -c puis gcc -Wall -Werror -std=c99 hello.o main.c -o project.

Deux nouvelles options se présentent -c qui permet de dire au compilateur de créer un fichier “.o” et -o qui permet d’appeler le linker (c’est lui qui dit au compilateur que certainnes fonctions appelées dans main.c sont définies dans hello.o).

Maintenant que nous avons compris comment nous pouvions compiler ce projet, nous allons regarder comment automatiser ces étapes. Pour cela regardons la synthaxe type d’un Makefile.

  cible : dependances
          commandes

Analysons ces deux lignes de commandes, le cible permet de donner un nom ainsi dans le shell nous pourrons taper make cible pour effectuer les actions correspondantes. Dans notre cas la cible serait project. Ensuite les dépendances sont les fichiers nécessaires à la création de la cible, dans notre cas cela serait ``hello.o’’. Un exemple de Makefile complet pour ce projet serait le suivant

hello.o:
        gcc -Wall -Werror -std=c99 hello.c -c

project : hello.o
        gcc -Wall -Werror -std=c99 hello.o main.c -o project

Un exemple plus complexe

Nous allons maintenant passer à un exemple plus complexe, l’exemple du Makefile utilisé pour le robot. Plusieurs complications et exigences vont se révéler avec ce projet

  • Beaucoup de fichiers pour le projet
  • Des dépendances entre les fichiers
  • Arreter de compiler les dépendances ``à la main’’
  • Avoir deux main (un main et un test)
  • Nettoyer son dossier
  • Vouloir changer les options de compilation / le compilateur facilement
  • Avoir des fichiers dans différents dossier

Commençons par présenter les différents fichiers du projet.

code/
| Makefile
| rasp
|   | src
|   |   | actionneur.hpp
|   |   | affichage.cpp
|   |   | affichage.hpp
|   |   | asserv.cpp
|   |   | asserv.hpp
|   |   | detection.cpp
|   |   | detection.hpp
|   |   | main.cpp
|   |   | main.hpp
|   |   | navigation.cpp
|   |   | navigation.hpp
|   |   | protocole.cpp
|   |   | protocole.hpp
|   |   | world.cpp
|   |   | world.hpp
|   | tst
|   |   | test.cpp
|   |   | test.hpp
|

Chaque figure de ce graphique correspond à un .cpp et un .hpp (même principe que un .c et un .h en c++). Cela soulève donc un premier point : beaucoup de fichier à gérer il va donc falloir créer tous les fichiers binaires (les .o) pour tout ces fichiers. Nous devons donc créer asserv.o, world.o, navigation.o, detection.o, protocole.o, affichage.o. Nous allons vouloir automatiser la création de ces binaires pour cela nous allons utiliser des commandes spéciales aux Makefile[^1]. Pour automatiser la transformation des .cpp en .o nous avons les lignes suivantes

[^1]:Je passe sous silence les commandes spéciales, si vous voulez des précision demandez à Lucas H. ou SD

%.o: rasp/src/%.cpp
   $(CC) $(CFLAGS) -c $<

Cela constitue la première règle de notre Makefile, elle permet de transformer n’importe quel .cpp dans code/rasp/src/ en un .o (qui sera mis dans code/). Nous voyons apparaitre deux variables inconnues $(CC) et $(CFLAGS), ces variables seront définies en début de Makefile et permettent de régler le problème 6. En effet CC va contenir le compilateur et CFLAGS les options de compilation. Nous ajustons donc notre Makefile comme suit.

CFLAGS = -Wall -Wextra -std=c++11 -g -O3
CC=g++
%.o: rasp/src/%.cpp
	$(CC) $(CFLAGS) -c $<

Maintenant que nous avons obtenu un moyen de transformer tous les fichiers .cpp en fichier binaire .o nous allons voir comment créer nos deux executables que nous appelerons project (qui contiendra le projet en entier) et le test (qui contiendra le test à effectuer) nous créons alors deux règles :

CFLAGS = -Wall -Wextra -std=c++11 -g -O3
FILE=navigation.o detection.o protocole.o asserv.o world.o affichage.o
CC=g++

all: project

%.o: rasp/src/%.cpp
	$(CC) $(CFLAGS) -c $<

project: $(FILE) rasp/src/main.cpp
	$(CC) $(CFLAGS) $(FILE) rasp/src/main.cpp -o project

test: $(FILE) rasp/tst/test.cpp
	$(CC) $(CFLAGS) $(FILE) rasp/tst/test.cpp -o test

Nous voyons alors une nouvelle variable apparaitre, la variable $FILE, cette dernière va lister les dépendances (c’est à dire tous les fichier .o nécessaire pour la réalisation des règles). A ce stade il ne reste plus beaucoup de chose à faire sur le Makefile, si vous avez tout compris jusque là vous pourrez comprendre et créer un Makefile sans trop de soucis. Il faut encore rajouter deux règles qui sont très souvent présentes dans les Makefile all. et clean (qui permet de nettoyer le dossier). Nous fournissons alors le Makefile final qui permet de réaliser le projet. Ce Makefile est totalement générique et est réutilisable pour tous vos projets.

CFLAGS = -Wall -Wextra -std=c++11 -g -O3 FILE=navigation.o detection.o protocole.o asserv.o world.o affichage.o CC=g++

## Ceci Bloc de documentation doxygen ... pour plus tard
# Project Title
#
# @file
# @version 0.1

all: project

%.o: rasp/src/%.cpp
	$(CC) $(CFLAGS) -c $<

project: $(FILE) rasp/src/main.cpp
	$(CC) $(CFLAGS) $(FILE) rasp/src/main.cpp -o project

test: $(FILE) rasp/tst/test.cpp
	$(CC) $(CFLAGS) $(FILE) rasp/tst/test.cpp -o test

clean:
	rm -f *.o project test test_protocole
# end