Makefile - Introduction Au Langage C
Maybe your like
Warning
Cette fiche contient en fait bien plus qu'une introduction aux Makefile. Si vous êtes tombés dessus par hasard, nous espérons que vous ne vous êtes pas faits mal. Si vous débutez votre apprentissage du C, nous vous conseillons fortement de potasser le début de cette fiche jusqu'à apprendre comment écrire un Makefile très simple, puis de la laisser de côté pour le moment. Vous aurez toujours le temps d'y revenir plus tard pour bien comprendre comment écrire des Makefile qui permettent de compiler des projets de plusieurs centaines de fichiers en 3 lignes et demi.
Un Makefile est un fichier, utilisé par le programme make, regroupant une série de commandes permettant d’exécuter un ensemble d’actions, typiquement la compilation d’un projet. Un Makefile peut être écrit à la main, ou généré automatiquement par un utilitaire de plus haut niveau comme par exemplecmake, automake ou gmake.
Un Makefile est constitué d’une ou de plusieurs règles de la forme :
| 1 2 | cible:dépendances commandes |
Lors du parcours du fichier, le programme make évalue d’abord la première règle rencontrée, ou celle dont le nom est spécifié en argument. L’évaluation d’une règle se fait récursivement :
-
Les dépendances sont analysées et évaluée : si une dépendance est la cible d’une autre règle, cette règle est à son tour évaluée ;
-
Lorsque l’ensemble des dépendances a été analysé, et si la cible est plus ancienne que les dépendances, les commandes correspondant à la règle sont exécutées.
Commande : make
Un fichier dénommé Makefile ayant été écrit, on utilise la commande make sans argument pour lancer l'évaluation de la première règle rencontrée :
| 1 | make |
Options utiles :
-
-n : affiche l'ensemble des commandes qui seraient exécutées, mais sans les exécuter réellement.
-
-d : affiche des informations de débogage en plus du traitement normal. Très verbeux, à utiliser quand vous ne trouvez pas le problème de votre Makefile. Les informations de débogage indiquent quels fichiers sont évalués pour la reconstruction, quelles dates de fichiers sont comparées et avec quels résultats, quels fichiers ont réellement besoin d’être recréés, quelles règles implicites sont prises en compte et lesquelles sont appliquées - bref, tout ce qu’il y a d’intéressant à savoir sur la manière dont make décide de ce qu’il doit faire.
Exemple de Makefile
Ce qui suit présente la création d’un Makefile pour un exemple de projet. Supposons pour commencer que ce projet regroupe trois fichiers exemple.h, exemple.c et main.c.
Compilation séparée
Pour compiler l'exemple étudié, une ligne de commande basique serait par exemple :
| 1 | gcc-omon_executableexemple.cmain.c-std=c99-Wall-Wextra |
Cette ligne de commande compile les 2 fichiers sources en fichiers objets et génère le programme exécutable final en faisant l'édition de liens. L'inconvénient est qu'on (re)compile systématiquement exemple.c et main.c même si l'un d'eux n'a pas été modifié.
Pour compiler séparément, on procède de la façon suivante :
| 1 2 3 4 5 6 7 8 9 | # On compile exemple.c et on génère le fichier objet exemple.o gcc-oexemple.o-cexemple.c-std=c99-Wall-Wextra-g # On compile main.c et on génère le fichier objet main.o gcc-omain.o-cmain.c-std=c99-Wall-Wextra-g # On fait l'édition de liens pour lier main.o et exemple.o en # un programme exécutable mon_programme gcc-omon_executableexemple.omain.o |
Un Makefile est basé sur cette notion de compilation séparée.
L'objectif d'un Makefile est ainsi de ne (re)compiler que les fichiers sources ayant été modifiés depuis la dernière compilation et de générer une nouvelle version du programme exécutable final.
Makefile de base
Un fichier Makefile de base de ce projet s’écrit comme suit :
| 1 2 3 4 5 6 7 8 | mon_executable:exemple.o main.o gcc-omon_executableexemple.omain.o exemple.o:exemple.c gcc-oexemple.o-cexemple.c-std=c99-Wall-Wextra-g main.o:main.c exemple.h gcc-omain.o-cmain.c-std=c99-Wall-Wextra-g |
En effet pour créer l’exécutable mon_executable, nous avons besoin des fichiers objets exemple.o et main.o. make va d’abord rencontrer la dépendance exemple.o, et va donc évaluer la règle dont ce fichier est la cible. La seule dépendance de cette règle étant exemple.c, qui n’est pas cible d’une autre règle, make va exécuter la commande :
| 1 | gcc -o exemple.o -c exemple.c -std=c99 -Wall -Wextra -g |
De la même façon, make va ensuite rencontrer la dépendance main.o et donc évaluer la règle dont main.o est la cible et exécuter la commande :
| 1 | gcc -o main.o -c main.c -std=c99 -Wall -Wextra -g |
Finalement, toutes les dépendances de la règle initiale ayant été analysées, make va exécuter la commande :
| 1 | gcc -o monexecutable exemple.o main.o |
NOTA BENE :
-
la syntaxe du Makefile impose l'utilisation de la tabulation pour décaler les lignes de commandes.
-
les décalages observés dans l'exemple de Makefile ci-dessus au niveau des lignes gcc ... doivent donc être des tabulations et non des espaces.
Un premier exercice pour tester par vous même l'utilisation des notions ci-dessus :
Télécharger l'exercice n°1
Le programme exécutable final à générer sera nommé : prog
Le programme est composé de 3 fichiers sources : main.c (programme principal), interface.c (interface utilisateur), fonctions.c (fonctions de calcul : factorielle et 2 puissance n)
Écrivez le Makefile selon le modèle expliqué ci-dessus et lancer le programme prog généré
Un premier raffinement
Pour améliorer ce Makefile, on peut rajouter quelques cibles standards :
-
all : à placer généralement au début du fichier ; les dépendances associées correspondent à l’ensemble des exécutables à produire ;
-
clean : normalement pas de dépendance ; la commande associée supprime tous les fichiers intermédiaires (notamment les fichiers objets .o) ;
-
mrproper : la commande correspondante supprime tout ce qui peut être regénéré (fichiers objets .o, exécutable, ...), ce qui permet une reconstruction complète du projet lors de l’appel suivant à make.
Notre Makefile devient donc ici :
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | all:mon_executable mon_executable:exemple.o main.o gcc-omon_executableexemple.omain.o exemple.o:exemple.c gcc-oexemple.o-cexemple.c-std=c99-Wall-Wextra-g main.o:main.c exemple.h gcc-omain.o-cmain.c-std=c99-Wall-Wextra-g clean: rm-f*.o mrproper:clean rm-fmon_executable |
Reprenez l'exercice n°1 téléchargé précédemment et ajoutez ces raffinements dans votre Makefile.
Introduction de variables
Il est possible de définir des variables dans un Makefile. Elles se déclarent sous la forme NOM=valeur et sont appelées sous la forme $(NOM), à peu près comme dans un shellscript.
Parmi quelques variables standards pour un Makefile de projet C ou C++, on trouve :
- CC qui désigne le compilateur utilisé ;
- CFLAGS qui regroupe les options de compilation ;
- LDFLAGS qui regroupe les options d’édition de liens ;
- EXEC ou TARGET qui regroupe les exécutables.
Pour notre projet exemple, cela donne :
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | CC=gcc CFLAGS=-std=c99-Wall-Wextra-g LDFLAGS= EXEC=mon_executable all:$(EXEC) mon_executable:exemple.o main.o $(CC)-omon_executableexemple.omain.o$(LDFLAGS) exemple.o:exemple.c $(CC)-oexemple.o-cexemple.c$(CFLAGS) main.o:main.c exemple.h $(CC)-omain.o-cmain.c$(CFLAGS) clean: rm-f*.o mrproper:clean rm-f$(EXEC) |
Reprenez l'exercice n°1 téléchargé précédemment et ajoutez ce type de variables dans votre Makefile.
Il existe aussi, et c’est encore plus intéressant car très puissant, des variables internes au Makefile, utilisables dans les commandes ; notamment :
- $@ : nom de la cible ;
- $< : nom de la première dépendance ;
- $ˆ : liste des dépendances ;
- $? : liste des dépendances plus récentes que la cible ;
- $* : nom d’un fichier sans son suffixe.
On peut donc encore compacter notre Makefile :
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | CC=gcc CFLAGS=-std=c99-Wall-Wextra-g LDFLAGS= EXEC=mon_executable all:$(EXEC) mon_executable:exemple.o main.o $(CC)-o$@$^$(LDFLAGS) exemple.o:exemple.c $(CC)-o$@-c$<$(CFLAGS) main.o:main.c exemple.h $(CC)-o$@-c$<$(CFLAGS) clean: rm-f*.o mrproper:clean rm-f$(EXEC) |
Reprenez l'exercice n°1 téléchargé précédemment et ajoutez ce type de variables dans votre Makefile.
Règles d’inférences
On voit néanmoins qu’il y a encore moyen d’améliorer ce Makefile. En effet les règles dont les deux fichiers objets sont les cibles se ressemblent fortement. Or justement, on peut créer des règles génériques dans un Makefile. Pour cela il suffit d’utiliser le symbole % à la fois pour la cible et pour la dépendance. Il est également possible de préciser séparément d’autres dépendances pour les cas particuliers, par exemple pour ne pas oublier la dépendance au fichier exemple.h pour la règle dont main.o est la cible. Ceci nous donne :
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | CC=gcc CFLAGS=-std=c99-Wall-Wextra-g LDFLAGS= EXEC=mon_executable all:$(EXEC) mon_executable:exemple.o main.o $(CC)-o$@$^$(LDFLAGS) main.o:exemple.h %.o:%.c $(CC)-o$@-c$<$(CFLAGS) clean: rm-f*.o mrproper:clean rm-f$(EXEC) |
Tester par vous même l'utilisation des notions ci-dessus :
Télécharger l'exercice n°2
Le programme principal est affiche.c, il fait appel aux fonctions contenues dans les 20 fichiers fct01.c à fct20.c
Le programme exécutable final sera nommé : affiche
Votre Makefile ne devra contenir aucun des noms : fct01.c à fct20.c
Liste des fichiers objets et liste des fichiers sources
On peut encore simplifier ce Makefile. En effet, on constate que la génération de l’exécutable dépend de tous les fichiers objets. Ceci peut être long à écrire dans le cas d’un gros projet !
Pour simplifier l’écriture, sachant que tous les fichiers objets correspondent aux fichiers sources en remplaçant l’extension .c par l’extension .o, on peut utiliser les variables SRC et OBJ et définir OBJ=$(SRC :.c=.o). SRC sera définie par la liste des fichiers sources... ce qui n’a fait que déplacer le problème ! Là encore, une syntaxe particulière permet de simplifier le Makefile : SRC=$(wildcard *.c).
Le Makefile du projet exemple devient finalement :
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | CC=gcc CFLAGS=-std=c99-Wall-Wextra-g LDFLAGS= EXEC=mon_executable SRC=$(wildcard*.c) OBJ=$(SRC:.c=.o) all:$(EXEC) mon_executable:$(OBJ) $(CC)-o$@$^$(LDFLAGS) main.o:exemple.h %.o:%.c $(CC)-o$@-c$<$(CFLAGS) clean: rm-f*.o mrproper:clean rm-f$(EXEC) |
Reprenez l'exercice n°2 téléchargé précédemment et ajoutez ces améliorations dans votre Makefile :
Votre Makefile ne devra contenir aucune des noms : fct01 à fct20
Le programme exécutable final sera nommé : affiche
Un dernier exercice pour vérifier par vous même que vous avez vraiment tout compris :
Télécharger l'exercice n°3
2 programmes exécutables sont à générer par votre Makefile : affiche1 et affiche2
affiche1.c fait appel aux fonctions fct01.c à fct09.c. Vous vous attacherez à ne compiler que les fichiers concernés pour générer ce premier programme affiche1.
affiche2.c fait appel aux fonctions fct10.c à fct20.c. Vous vous attacherez à ne compiler que les fichiers concernés pour générer ce deuxième programme affiche2.
Votre Makefile ne devra contenir aucune des noms de fonctions : fct01 à fct19
Il existe encore bien d’autres possibilités pour simplifier un Makefile. Il est notamment possible de créer des Makefile pour des sous-répertoires correspondant à des sous-parties du projet, et d’avoir un Makefile “maître” très simple qui appelle ces “sous-Makefile”, avec la variable MAKE. Il existe également des outils de génération automatique.
Tag » Apprendre Makefile
-
Introduction à Makefile.
-
Make Et Makefile : Petit Résume Rapide
-
[PDF] Concevoir Un Makefile
-
Make - écrire Un Makefile - YouTube
-
[PDF] Ecrire Un Makefile, Sans Douleur Et En Quelques Leçons.
-
Comment Créer Et Utiliser Makefile En C ++
-
[PDF] Makefile - RIP Tutorial
-
Tutoriel Vidéo Unix & Make : Makefile | Grafikart
-
C : Créer Un Makefile Pour Ses Projets
-
[PDF] Make Et Makefiles
-
[PDF] Techniques De Production De Programmes
-
Comment Utiliser Make - Morgan Ridel
-
Dev:makefile [LinuxPedia]
-
Comment Faire Un Makefile Qui Compile Tous Les .cpp - Zeste De Savoir