Navigate back to the homepage

Comment utiliser Make

Morgan Ridel
January 26th, 2017 · 4 min read
0 x 0

Aujourd’hui, je vais parler de Make. Il était impossible pour moi de continuer d’écrire des articles sans vous expliquer l’origine du nom du blog ! Make est un outil qui permet d’automatiser la création de fichiers et particulièrement d’automatiser la compilation et création de vos fichiers exécutables à partir des fichiers sources. Oui, une fois votre Makefile (fichier utilisé par Make) configuré correctement, vous pourrez recompiler tout votre projet en une seule ligne de commande.

Principe de base

Make utilise un fichier nommé Makefile contenant des règles. Chaque règle représente en fait une cible à créer, par exemple votre fichier exécutable final. Une règle se base donc sur le modèle suivant:

1cible : dépendance(s)
2 commandes...

Attention : La tabulation est indispensable au bon fonctionnement du Makefile

Ainsi, pour générer la cible il suffit de se placer dans le dossier contenant le Makefile et de taper : make cible Make vérifie alors si le(s) dépendance(s) sont plus récentes que la cible (ou si la cible n’existe pas encore). Si c’est le cas, la cible est (re)générée en exécutant les commandes.

Compiler un projet avec un Makefile

Dans cette partie nous allons considérer avoir un dossier projet codé en C avec l’arborescence suivante:

  • Projet

    • bin : Contiendra l’exécutable du projet
    • include : Les fichiers header .h
    • src : Les fichiers sources .c

Le projet est composé de 3 fichiers .c : main.c fichier1.c fichier2.c ainsi que de leurs headers.

Pour obtenir l’exécutable de ce projet on pourrait faire un simple makefile de ce style:

1bin/executable : src/main.o src/fichier1.o src/fichier2.o
2      gcc  -o bin/executable src/main.o src/fichier1.o src/fichier2.o
3
4
5src/main.o : src/main.c
6    gcc -o src/main.o -c src/main.c -Wall -pedantic -g -std=c99  -I include
7
8
9src/fichier1.o : src/fichier1.c
10    gcc -o src/fichier1.o -c src/fichier1.c $(CFLAGS) -Wall -pedantic -g -std=c99  -I include
11
12src/fichier2.o : src/fichier2.c
13    gcc -o src/fichier2.o -c src/fichier2.c -Wall -pedantic -g -std=c99  -I include

Avec ce makefile, lancer _make executable _dans un terminal devrait donc compiler chaque fichier puis créer l’exécutable… Mais ce fichier est très lourd ! Et ici on a seulement 3 fichiers sources ! Rassurez-vous, il y a moyen de se simplifier la vie. Make permet l’utilisation de variable permettant de rendre notre makefile plus concis.

Les variables

Nous allons utiliser 2 types de variables dans notre makefile pour le simplifier, celles que nous déclarerons nous-même, et celles pré-existantes.

Faire ses propres variables

Il est possible de déclarer soi-même une variable à laquelle on assignera différentes valeurs que nous utilisons souvent. Pour définir une nouvelle variable, il suffit d’écrire au début du makefile:

1NOMDELAVARIABLE=maValeur

Pour l’appeler il faut utiliser cette syntaxe:

1$(NOMDELAVARIABLE)

On va donc définir plusieurs variables au début du fichier pour éviter de récrire tout le temps certaines choses:

1BINDIR=bin
2INCLUDEDIR=include
3CC = gcc
4CFLAGS=-Wall -pedantic -g -std=c99  -I$(INCLUDEDIR) 
5EXEC=executable

Plus besoin d’écrire tous les paramètres de gcc à chaque fois, CFLAGS s’en occupe ! On obtient alors le code suivant:

1BINDIR=bin
2INCLUDEDIR=include
3CC = gcc
4CFLAGS=-Wall -pedantic -g -std=c99  -I$(INCLUDEDIR) 
5EXEC=executable
6
7$(BINDIR)/$(EXEC) : $(SRCDIR)/main.o $(SRCDIR)/fichier1.o $(SRCDIR)/fichier2.o
8    $(CC) -o $(BINDIR)/$(EXEC) $(SRCDIR)/main.o $(SRCDIR)/fichier1.o $(SRCDIR)/fichier2.o
9
10$(SRCDIR)/main.o : $(SRCDIR)/main.c
11    $(CC) -o $(SRCDIR)/main.o -c $(SRCDIR)/main.c $(CFLAGS)
12
13$(SRCDIR)/fichier1.o : $(SRCDIR)/fichier1.c
14    $(CC) -o $(SRCDIR)/fichier1.o  -c $(SRCDIR)/fichier1.c $(CFLAGS)
15
16$(SRCDIR)/fichier2.o : $(SRCDIR)/fichier2.c
17    $(CC) -o $(SRCDIR)/fichier2.o  -c $(SRCDIR)/fichier2.c $(CFLAGS)

On gagne aussi un avantage énorme. Si nous décidons de renommer des dossiers ou de rajouter des paramètres à la compilation à tout moment, il suffit de changer la variable correspondante sans avoir à changer les paramètres dans la règle de chaque fichiers. Malgré cette nette amélioration on voit encore pas mal de redondance sur l’écriture du nom des fichiers…

Les variables internes

Make dispose également de panoplie de variables qui nous arrange bien pour écrire nos règles, en voici:

  • $@ : nom de la cible de la règle
  • $< : nom de la première dépendance de la règle
  • $^ : liste des dépendances de la règle
  • $? : liste des dépendances de la règle plus récente que la cible

On peut donc à nouveau simplifier notre code (et surtout éviter de taper les potentielles dizaines dépendances si notre règle les affectent toutes):

1BINDIR=bin
2INCLUDEDIR=include
3CC = gcc
4CFLAGS=-Wall -pedantic -g -std=c99  -I$(INCLUDEDIR) 
5EXEC=executable
6
7$(BINDIR)/$(EXEC) : $(SRCDIR)/main.o $(SRCDIR)/fichier1.o $(SRCDIR)/fichier2.o
8    $(CC) -o $@ $^
9
10$(SRCDIR)/main.o : $(SRCDIR)/main.c
11    $(CC) -o $@ -c $< $(CFLAGS)
12
13$(SRCDIR)/fichier1.o : $(SRCDIR)/fichier1.c 
14 $(CC) -o $@ -c $< $(CFLAGS)
15
16$(SRCDIR)/fichier2.o : $(SRCDIR)/fichier2.c 
17 $(CC) -o $@ -c $< $(CFLAGS)

Plus propre n’est-ce pas ? Il reste un dernier problème, si nous avons énormément de fichier à compiler dans notre projet, il faut écrire une règle pour chaque ! Pourtant on voit bien que les règles de création de .o sont clairement similaires…

Une règle générique

Pour éviter d’écrire plusieurs fois la règle qui va transformer tous nos fichiers sources en code objet, nous allons créer une règle générique. Dès qu’on aura un .o en dépendance, c’est cette règle qui sera utilisée:

1BINDIR=bin
2INCLUDEDIR=include
3CC = gcc
4CFLAGS=-Wall -pedantic -g -std=c99  -I$(INCLUDEDIR) 
5EXEC=executable
6
7$(BINDIR)/$(EXEC) : $(SRCDIR)/main.o $(SRCDIR)/fichier1.o $(SRCDIR)/fichier2.o
8    $(CC) -o $@ $^
9
10$(SRCDIR)/%.o : $(SRCDIR)/%.c
11    $(CC) -o $@ -c $< $(CFLAGS)

Avec ce makefile, même si nous avons 300 fichiers sources, nous devrons seulement ajouter des dépendances à la règle qui crée l’exécutable.

La touche finale

Pour finir, on peut par convention utiliser quelques règles dans le makefile. Nous allons créer une règle all qui se contentera d’utiliser la règle pour créer l’exécutable ainsi qu’une règle clean qui nettoiera les répertoires en supprimant les fichiers générés.

1BINDIR=bin
2INCLUDEDIR=include
3CC = gcc
4CFLAGS=-Wall -pedantic -g -std=c99  -I$(INCLUDEDIR) 
5EXEC=executable
6
7all :  $(BINDIR)/$(EXEC)
8
9$(BINDIR)/$(EXEC) : $(SRCDIR)/main.o $(SRCDIR)/fichier1.o $(SRCDIR)/fichier2.o
10    $(CC) -o $@ $^
11
12$(SRCDIR)/%.o : $(SRCDIR)/%.c
13    $(CC) -o $@ -c $< $(CFLAGS)
14
15clean :
16    rm -rf $(BINDIR)/*
17    rm -rf $(SRCDIR)/*.o

Et voilà ! Il suffit maintenant d’ouvrir un terminal et de taper make all pour compiler tout notre projet ! make clean supprimera tous les fichiers crées pour laisser un répertoire propre. Note: On peut aussi utiliser seulement la commande make seule, ce qui aura pour effet de lancer la première règle, ici all.

Conclusion

Vous savez désormais comment créer un makefile pour compiler tout vos projets en une seule ligne de commande. Je vous invite à vous renseigner davantage pour apprendre à faire des makefile encore plus généraux !

Certains d’entre vous pensent peut-être que faire un makefile est inutile car la plupart des IDE s’occupent entièrement de la compilation d’un projet. Je vous répondrai alors qu’effectivement un IDE permet de s’abstraire des commandes de compilation mais que je pense qu’il peut être intéressant de connaître au moins de loin le fonctionnement de la compilation (le passage par les fichiers .o en C par exemple) ne serait-ce que pour pouvoir changer/rajouter des paramètres à vos compilations si vous en avez besoin un jour. Je ne suis pas un connaisseur en compilation moi-même mais je trouve que savoir compiler en ligne de commande reste une compétence intéressante ! De plus, au delà de la compilation, Make permet d’automatiser n’importe quelle tâche en ligne de commande ! Vous pouvez l’utiliser pour effectuer des actions régulières et répétitives mais identiques (décompresser une archive et convertir des fichiers par exemple).

Le nom du blog fait donc référence à l’utilisation d’un makefile ! Plus précisément à l’utilisation d’une cible tests qui généraient des tests unitaires lors d’un projet réalisé en cours de programmation, pour apprendre l’utilisation de CUnit. Ce blog représente donc mes “tests”, ce que j’y explique n’est pas forcément parfait et peut être erroné/à corriger, j’y partage mon expérience à mon échelle ! Je vous propose de m’aider à me corriger si vous trouvez des erreurs ;)

Et vous, avez-vous déjà utilisé des makefile et qu’en pensez-vous ?

Pour plus d’informations: http://www.gnu.org/software/make/manual/html_node/index.htmlhttp://gl.developpez.com/tutoriel/outil/makefile/

More articles from Morgan Ridel

Inauguration de Make Tests

Bienvenue à vous si vous lisez ces lignes, vous êtes sur l’introduction de mon blog. Make Tests est un blog concernant surtout la…

January 23rd, 2017 · 2 min read

Authentication for multiple apps behind a reverse proxy

Note: Contrary to the last blog posts, this one is written in english, mostly to reach a wider audience. The vast majority of my old posts…

January 4th, 2022 · 16 min read
© 2017–2022 Morgan Ridel
Link to $https://twitter.com/morganridelLink to $https://github.com/morganridelLink to $https://www.linkedin.com/in/morgan-ridel-017a9ab6/