MAKEFILE




Makefile redacteur : Emmanuel Bébrune
Modifs d. Dupont

Introduction :

Le système du makefile permet de simplifier la compilation. Ce système se résume en un simple fichier “Makefile”. Ce fichier liste tous les fichiers du projet et permet d’organiser ce dernier en morceaux indépendants.
Le fichier Makefile est lu par la commande make, ainsi pour compiler un projet, avec un fichier Makefile qui_va_bien, il suffit de taper la commande “make” sans aucun arguments.
Structure du fichier Makefile :

Un fichier Makefile est divisé en structures telles que :

cible: dépendances
    commandes

Il faut absolument précéder chaque commande par une tabulation.
Lors de l'utilisation d'un tel fichier via la commande make la première règle rencontrée, ou la règle dont le nom est spécifié, est évaluée. L'évaluation d'une règle se fait en plusieurs étapes :
Les dépendances sont analysées, si une dépendance est la cible d'une autre règle du Makefile, cette règle est à son tour évaluée.
Lorsque l'ensemble des dépendances est analysé et si la cible ne correspond pas à un fichier existant ou si un fichier dépendance est plus récent que la règle, les différentes commandes sont exécutées.
Application sur un exemple basique:

illustration

Pour illustrer un fichier Makefile, j’ai fait un petit programme composé de deux fichiers .h et trois .c. Ce programme a pour simple but de demander à l’utilisateur de saisir une chaîne de caractères, puis l’inscrit à l’écran et quitte.
La partie saisie est composée des fichiers saisie.h et saisie.c, dans le Makefile, nous aurons donc une règle :

saisie.o: saisie.c saisie.h
    gcc -c saisie.c saisie.h

La partie affichage est composée des fichiers affiche.h et affiche.c, dans le Makefile, nous aurons donc une règle :

affiche.o: affiche.c affiche.h
    gcc -c affiche.c affiche.h

Le fichier principal est composé juste du fichier main.c. Nous aurons donc la règle suivante dans le fichier Makefile :

main.o: main.c
    gcc -o main.o -c main.c

Pour finir, il faut définir une règle pour faire l’édition des liens entre tout les fichiers .o créés par les autres règles. On obtient donc :

main: main.o affiche.o saisie.o
    gcc -o main main.o affiche.o saisie.o

En général, on ajoute les règles all et clean pour, respectivement, compiler tout le projet et nettoyer le projet, c’est à dire, supprimer les fichiers compilés et pré-compilés (exécutables et .o). La règle all est généralement placée en tête du fichier pour être la règle appelée par défaut (sans paramètres à l’exécution de make) et la règle clean en dernière.
On ajoute donc les règles suivantes :

all: main

clean:
    rm -rf *.o
    rm -rf main

Variables et paramètres dans le Makefile:

Le système du Makefile est très puissant, ainsi on peut définir des variables afin que l’on ait très peu de modifications à apporter au fichier afin de s’adapter à l’environnement.
Pour cela, il suffit d’ajouter au début du fichier la définition des variables telle que :

CC=gcc
CFLAGS=-Wall -ansi
LDFLAGS=-Wall -ansi
EXEC=main

Ici, j’ai choisi les variables CC pour définir le compilateur (ici gcc), CFLAGS pour les paramètres de compilation, LDFLAGS pour les paramètres d’édition des liens, et enfin EXEC pour le nom de l’exécutable.
Certaines variables sont implicites, et donc utilisables sans définition particulière. Nous avons par exemple : $@ qui représente la cible de la règle courante et $^ pour la ou les dépendances.
On arrive donc aux règles de compilation suivantes :
all: $(EXEC)

main: main.o saisie.o affiche.o
    $(CC) -o $@ $^ $(LDFLAGS)

main.o: main.c
    $(CC) -c $^ $(CFLAGS)

saisie.o: saisie.h saisie.c
    $(CC) -c $^ $(CFLAGS)

affiche.o: affiche.h affiche.c   
    $(CC) -c $^ $(CFLAGS)

clean:
    rm -rf *.o
    rm -rf $(EXEC)

Les fichiers d’exemple :

Le fichier principal (main.c) :


#include "saisie.h"
#include "affiche.h"

int main(){
    char* texte;
    affiche("Veuillez saisir quelques caractères\n");
    texte = saisie();
    affiche("Vous avez saisi : ");
    affiche(texte);
    affiche("\n");
    return 0;
}

Le fichier affiche.h :


#ifndef _AFFICHE_H_
#define _AFFICHE_H_

#include <stdio.h>
#include <stdlib.h>

int affiche(char*);
//rem int affiche(char*){} redéfinie la fonction est crée une erreur de compil
#endif

Le fichier affiche.c :


#include "affiche.h"

int affiche(char* texte){
    printf("%s", texte);
    return 0;
}

Le fichier saisie.h :


#ifndef _SAISIE_H_
#define _SAISIE_H_

#include <stdio.h>
#include <stdlib.h>

char* saisie();
#endif

Le fichier saisie.c :


#include "saisie.h"

char* saisie(){
    char* texte = (char*) malloc(10*sizeof(char));
    scanf("%s", texte);
    return texte;
    free(texte);
}

Et enfin,

le Makefile :

CC=gcc
CFLAGS=-Wall -ansi
LDFLAGS=-Wall -ansi
EXEC=main

all: $(EXEC)

main: main.o saisie.o affiche.o
<TAB>$(CC) -o $@ $^ $(LDFLAGS)

main.o: main.c
<TAB>$(CC) -c $^ $(CFLAGS)

saisie.o: saisie.h saisie.c
<TAB>$(CC) -c $^ $(CFLAGS)

affiche.o: affiche.h affiche.c   
<TAB>$(CC) -c $^ $(CFLAGS)

clean:
<TAB>rm -rf *.o
<TAB>rm -rf $(EXEC)

//rem
<TAB> n'est pas à saisir, il faut mettre une tab
pour approfondir :

On peut aussi définir des règles génériques afin de ne pas avoir à lister tous les couples de fichiers .h et .c, je n’en parlerai pas ici mais le site suivant détaille bien cette fonction et bien d’autres : http://gl.developpez.com/tutoriel/outil/makefile/
C’est la page la plus claire et complète que j’ai trouvée sur le sujet.