Makefile
Un makefile permet d'automatiser le processus de compilation et d'assemblage d'un programme.
Un Makefile est un fichier constitué de plusieurs règles de la forme :
cible: dependance
commandes
Certains éditeurs de texte sont configurés pour utiliser des espaces, ce qui produit des makefiles invalides
Makefile minimal
Un exemple simple d'un Makefile :
hello: hello.o main.o gcc -o hello hello.o main.o hello.o: hello.c gcc -o hello.o -c hello.c -W -Wall main.o: main.c hello.h gcc -o main.o -c main.c -W -Wall
Explication : Nous cherchons à créer le fichier exécutable hello, la première dépendance est la cible d'une des régles de notre Makefile
, nous évaluons donc cette règle. Comme aucune dépendance de hello.o
n'est une règle, aucune autre règle n'est à évaluer pour compléter celle-ci.
Deux cas se présentent ici : soit le fichier hello.c
est plus récent que le fichier hello.o
, la commande est alors exécutée et hello.o
est construit, soit hello.o
est plus récent que hello.c
est la commande n'est pas exécutée. L'évalution de la règle hello.o
est terminée.
Les autres dépendances de hello
sont examinées de la même manière puis, si nécessaire, la commande de la règle hello
est exécutée et hello
est construit.
Makefile enrichi
Plusieurs cas ne sont pas gérés dans l'exemple précédent :
- Un tel Makefile ne permet pas de générer plusieurs exécutables distincts.
- Les fichiers intermédiaires restent sur le disque dur même lors de la mise en production.
- Il n'est pas possible de forcer la regénération intégrale du projet
Ces différents cas conduisent à l'écriture de règles complémentaires :
all
: généralement la première du fichier, elle regroupe dans ces dépendances l'ensemble des exécutables à produire.clean
: elle permet de supprimer tout les fichiers intermédiaires.mrproper
: elle supprime tout ce qui peut être régénéré et permet une reconstruction complète du projet.
En ajoutant ces règles complémentaires, notre Makefile devient donc :
all: hello hello: hello.o main.o gcc -o hello hello.o main.o hello.o: hello.c gcc -o hello.o -c hello.c -W -Wall -ansi -pedantic main.o: main.c hello.h gcc -o main.o -c main.c -W -Wall -ansi -pedantic clean: rm -rf *.o mrproper: clean rm -rf hello
Variables personnalisées
Il est possible de définir des variables dans un Makefile, ce qui rend les évolutions bien plus simples et plus rapides, en effet plus besoin de changer l'ensemble des règles si le compilateur change, seule la variable correspondante est à modifier.
Une variable se déclare sous la forme NOM=VALEUR et se voir utiliser via $(NOM)
.
Nous allons donc définir quatre variables dans notre Makefile :
CFLAGS
regroupant les options de compilation (Généralement cette variable est nomméesCFLAGS
pour une compilation en C,CXXFLAGS
pour leC++
)LDFLAGS
regroupant les options de l'édition de liensEXEC
contenant le nom des exécutables à générer- Une désignant le compilateur utilisée nommée CC (une telle variable est typiquement nommé CC pour un compilateur C, CPP pour un compilateur C++)
Nous obtenons ainsi :
CC=gcc CFLAGS=-W -Wall -ansi -pedantic LDFLAGS= EXEC=hello all: $(EXEC) hello: hello.o main.o $(CC) -o hello hello.o main.o $(LDFLAGS) hello.o: hello.c $(CC) -o hello.o -c hello.c $(CFLAGS) main.o: main.c hello.h $(CC) -o main.o -c main.c $(CFLAGS) clean: rm -rf *.o mrproper: clean rm -rf $(EXEC)
Variables internes
Il existe plusieurs variables internes au Makefile, citons entre autres :
$@ | Le nom de la cible |
$< | Le nom de la première dépendance |
$^ | La liste des dépendances |
$? | La liste des dépendances plus récentes que la cible |
$* | Le nom du fichier sans suffixe |
Notre Makefile ressemble donc maintenant à :
CC=gcc CFLAGS=-W -Wall -ansi -pedantic LDFLAGS= EXEC=hello all: $(EXEC) hello: hello.o main.o $(CC) -o $@ $^ $(LDFLAGS) hello.o: hello.c $(CC) -o $@ -c $< $(CFLAGS) main.o: main.c hello.h $(CC) -o $@ -c $< $(CFLAGS) clean: rm -rf *.o mrproper: clean rm -rf $(EXEC)
Les règles d'inférence
Makefile permet également de créer des règles génériques (par exemple construire un .o à partir d'un .c) qui se verront appelées par défaut. Une telle règle se présente sous la forme suivante :
%.o: %.c commandes
Il devient alors aisé de définir des règles par défaut pour générer nos différents fichiers
CC=gcc CFLAGS=-W -Wall -ansi -pedantic LDFLAGS= EXEC=hello all: $(EXEC) hello: hello.o main.o $(CC) -o $@ $^ $(LDFLAGS) %.o: %.c $(CC) -o $@ -c $< $(CFLAGS) clean: rm -rf *.o mrproper: clean rm -rf $(EXEC)
Version optimisée :
CC=gcc CFLAGS=-W -Wall -ansi -pedantic LDFLAGS= EXEC=hello all: $(EXEC) hello: hello.o main.o $(CC) -o $@ $^ $(LDFLAGS) main.o: hello.h %.o: %.c $(CC) -o $@ -c $< $(CFLAGS) clean: rm -rf *.o mrproper: clean rm -rf $(EXEC)
Les options
$ make -j 2
L'option -j X
partage le travail vers les X processeurs de l'ordinateur (Compilation parallèle). (donc -j 2
pour les double coeurs !)
$ make 2&> .txt
L'option 2&>
copie les erreurs lors de l'exécution dans un fichier.
Exemple pour le test
- hello.h
#ifndef H_HELLO #define H_HELLO void Hello(void); #endif
- main.c
#include <stdio.h> #include <stdlib.h> #include "hello.h" int main(void) { Hello(); return 0; }
Liens
sourceDeveloppez.net