Exporter la page en format Open Document

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

Attention, make ne reconnaît que les tabulations pour l'indentation des commandes.
Certains éditeurs de texte sont configurés pour utiliser des espaces, ce qui produit des makefiles invalides

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.

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

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ées CFLAGS pour une compilation en C, CXXFLAGS pour le C++)
  • LDFLAGS regroupant les options de l'édition de liens
  • EXEC 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)

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)

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)
$ 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.

hello.c
#include <stdio.h>
#include <stdlib.h>
 
void Hello(void)
  {
      printf("Hello World\n");
  }
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;
  }