The art of making makefile by Danjer

Il existe plusieurs types de make1). Ce document traitera de ceux-ci :

  • gmake (GNU make)
  • pmake2) (BSD)

Le plus répandu est gmake, c’est probablement celui qui offre le plus de possibilités. Si vous voulez avoir un maximum de compatibilité sur différents systèmes, mieux vaut utiliser gmake3).

Lorsque vous tapez make à l’invite de commandes vous exécuterez l’un ou l’autre en fonction de votre système d’exploitation. Si vous êtes sur NetBSD ce sera pmake. Si c’est sur un Linux se sera gmake. Tout au long de ce document, des fonctionnalités de gmake seront développées. Si lors des exemples quelque chose ne fonctionne pas, essayez de taper gmake plutôt que make.

Lors de l’exécution de gmake celui-ci va lire dans le répertoire ouvrant un makefile. Dans l’ordre gmake va rechercher, GNUMakefile, Makefile, makefile. Si vous réalisez un makefile dans l’esprit GNU nommez le GNUMakefile car si pmake est exécuté il ne trouvera pas le fichier et s’interrompra. Ceci évite les exclamations : “Ah mais oui ça ne marche qu’avec gmake !”.

Pour plus d’information sur make :

Convention :

  • – Makefile – : indique le contenu d’un fichier Makefile (compatible gmake, pmake)
  • – GNUMakefile – : indique le contenu d’un Makefile (gmake)

La première question qu’il faut se poser c’est :

A quoi ça sert ?

L’utilité n’est pas forcément flagrante. Surtout si l’on n’a jamais programmé de sa vie et/ou passé de longues heures à recompiler un petit bout de programme encore et encore, car la fatigue ou la flemme nous fait passer en mode programmation Pifomètre, à essayer toutes les valeurs possibles. Lorsque l’on regarde la première ligne du man de make (BSD) on peut lire :

make - maintain program dependencies

Là il faut se rappeler les étapes de générations d’un programme exécutable. Prenons les étapes de compilation du C. Pour passer d’un simple fichier texte à un exécutable fonctionnel il y a plusieurs étapes.

cpp - C PreProcessor

Cpp va lire un fichier .c et générer un fichier .i Cette étape a pour but de réaliser un certain nombre de manipulations de texte :

  • Inclure d’autres fichiers, dans la plupart des cas ce seront des .h, des headers.
  • Résoudre les macros, les macros ont pour but d’augmenter la lisibilité 4) du code...
  • Permet d’inclure ou d’exclure des parties de texte en fonction de conditions variables (ex:type d’OS).
  • Le contrôle de ligne. Grâce à cela si une erreur intervient plus loin, le compilateur sera capable de situer l’erreur dans le texte.

Le fichier de sortie ressemble encore à du C mais ne contient plus les directives (#define, #ifdef ou #include) et les macros sont expansées.

cc1 - C Compiler passe one...

Cc1 va lire un fichier .i et générer un fichier .s Cette étape va interpréter le code en C et le transformer en code pseudo-assembleur. C’est à ce point de la compilation que le compilateur détecte un certain nombre d’erreurs.

as - Assembler

As va lire un fichier .s et écrire un fichier .o Cette étape va lire le code assembleur et le transformer en langage machine. Le plus important c’est surtout la création du fichier de sortie dit fichier objet. Ce fichier objet peut être transmis, avec son header (.h) à un autre programmeur. Il pourra utiliser le ou les fonctions contenues dans le fichier objet mais sans qu’il puisse voir le code en C d’origine.

ld - Linker (d pour eDitor)

Ld va prendre un ensemble de fichiers objets ou de fichiers archives (les .a ou lib) et va les lier entre eux et résoudre l’adresse des symboles. Il va permettre qu’une fonction puisse trouver et exécuter une autre fonction qui n’est pas définie dans le même fichier objet, mais dans un autre, compilé séparément.

Un fois cette étape terminée, ld génère un exécutable.

ar - Archiver

Ar va créer un fichier archive qui va contenir, l’ensemble des fichiers objets passés en paramètre.

ranlib - generate index to archive.

Ranlib crée un index dans un fichier archive. Ranlib devra être exécuté à chaque fois que le fichier archive sera modifié.

On va récapituler

.c		->	cpp (gcc -E)	->	.i
.i		->	cc1 (gcc -S)	->	.s
.s		->	as  (gcc -c)	->	.o
.o ou .a	->	ld  (gcc) 	->	a.out (ou elf, pe...)

Ce qui est le plus long généralement, lors de la création d’un programme, c’est la génération des différents fichiers objets. Si l’on veut modifier une fonction contenue dans un des fichiers objets il n’est pas nécessaire de tous les recompiler, il suffit juste de régénérer le fichier objet concerné.

fonct_a __ fich_ab.c --[gcc -c]-> fich_ab.o ______[ld]-> abcdef
fonct_b /      		       	 	         /
               		       	 	        /
fonct_c __ fich_cd.c --[gcc -c]-> fich_cd.o ___/
fonct_d /				      /
                 	       	             /
fonct_e __ fich_ef.c --[gcc -c]-> fich_ef.o /
fonct_f /

Si la fonct_a est modifiée, il faut juste recompiler le fich_ab.c et lier tout les fichiers objets pour obtenir l’exécutable fonctionnel avec la fonct_a modifiée dans le code.

Évidemment, il parait moins fatiguant de tout recompiler quand on ne se souvient pas sur quels fichiers on était en train de travailler.

Mais comme l’informaticien est feignant il y a un moyen d’automatiser cette tâche, par un outil. Cet outil, c’est make. Mais make peut servir pour d’autres compilateurs comme g++, latex, voire même de l’administration système (pour mettre à jour les bases NIS).

Comment ça marche ?

Les règles - Approche simpliste

Basiquement, make fonctionne simplement en comparant les dates des fichiers. Il suffit juste de préciser les fichiers que make doit surveiller. Pour cela il faut écrire une règle. Une règle s’écrit sous cette forme :

nom_du_fichier : dépendance_à_satisfaire
              les_choses_à_faire
              encore_des_trucs_à_faire
              toujours_des_bidules_à_faire
              qui_souvent_finissent_par_créer_le_fichier

Un petit exemple pour clarifier tout ça. Le grand classique du hello world, mais est-il à jour ?

Créez un répertoire et faites un copier-coller dans un fichier Makefile des lignes qui suivent.

 -- Makefile --

hello :
	touch hello

 --

Éditez le fichier et vérifiez qu’il y ait bien une tabulation devant ‘touch hello’. Ensuite, faites les opérations suivantes.

sh> ls
Makefile

A cette étape, il n’existe qu’un seul fichier : Makefile

sh> make
touch hello

Make s’exécute, il essaye de résoudre les dépendances de la règle hello. Or la première des dépendances c’est le fichier qui a le même nom que la règle.
Alors make se pose la question : Est-ce que le fichier ‘hello’ existe ?.
Non, il n’existe pas.
Ensuite il se demande : Ai-je des dépendances à satisfaire ?.
Non, il n’y en a pas.
Enfin il peut exécuter les commandes pour créer le fichier : ‘touch hello’

sh> ls
Makefile  hello

On voit clairement que le fichier hello maintenant existe.

sh> make
`hello' is up to date.

Make s’exécute, il essaye de résoudre les dépendances de la règle hello. Le fichier hello existe. Alors make vérifie les dépendances de la règle hello pour comparer leurs dates avec celle du fichier hello. Mais il y a pas de dépendances, make en conclu que le fichier hello est à jour, donc il n’a pas besoin de refaire les étapes pour créer le fichier.

Les règles - Soyons un peu pédant.

Dans la partie précédente, on a employé à tort un certain nombre de termes. Par exemple, la notion de dépendances ne correspond à rien. Mais c’est souvent ce que l’on entend (en salle machine). A partir de maintenant on va employer les bons termes. Un règle est composée :

  • d’une cible (Le fichier a générer).
  • de pré-requis (Autre règle qu’il faut appliquer avant de faire la cible)
  • de commandes (Comment qui génère la cible)

Une règle peut s’écrire de deux façons. On va commencer par écrire la cible suivie de ‘:’, puis ensuite les pré-requis. Et sur chaque ligne qui suivra la cible, on pourra écrire une commande, mais chaque ligne de commandes devra impérativement commencer par une tabulation. Tout ce qui est alignement et intervalle sont à l’appréciation de l’artiste. ex :

cible : prérequis

but il nous faut des pré-requis, des bases. Avec ces bases on peut à l’aide de commandes, d’outils construire cette cible.

On va prendre un autre exemple. Recopiez ces lignes dans un Makefile. Prenez garde aux tabulations et vérifiez que tout les fichiers hello, coucou et salut ont bien été effacés.

 -- Makefile --

hello :	coucou salut
	touch hello

coucou:
	touch coucou

salut :
	touch salut

 --

Avec ces règles on dit : pour faire le fichier hello il faut le fichier coucou et le fichier salut.

Voici comment ce comporte make :

> rm coucou hello salut
> ls
Makefile
> make
touch coucou
touch salut
touch hello
> ls
Makefile  coucou  hello  salut
> rm hello
> make
touch hello

Si l’on efface la cible hello, alors que tous les pré-requis existent, alors seule la cible est recréé.

> rm coucou
> make
touch coucou
touch hello

Si l’on efface le pré-requis coucou, alors le pré-requis et la cible sont recréés.

Les variables et le texte

Make est doté de variables pouvant contenir du texte. Pour affecter une valeur à une variable, on utilise la syntaxe suivante :

VARIABLE = un texte

Pour exploiter le contenu d’une variable, il faut faire précéder l’identifiant par un $.

UNEAUTREVARIABLE = $(VARIABLE)
ou
uneregle :
	 unecommande $(VARIABLE)

On peut aussi appeler une variable sans utiliser les parenthèses, mais l’on s’expose à certaines erreurs.

 -- Makefile --

V = hihi
VARIABLE = un texte

all:
        echo $(VARIABLE)

 --

Exécutez make, regardez le résultat. Ensuite modifiez la ligne :

	echo $(VARIABLE)
par
	echo $VARIABLE

Dans un des cas on n’obtiendra pas le résultat attendu, c’est pour cette raison qu’il vaut mieux toujours utiliser les parenthèses pour exploiter une variable.

Lorsque l’on affecte une valeur à une variable celle-ci prend la valeurs de la partie droite de l’expression, jusqu’à la fin de la ligne. Mais celle-ci ne doit contenir aucun de ces caractères: ‘:‘, ‘=‘, ‘#‘. Si l’on veut affecter plusieurs lignes a une variable placer un backslash ‘\’ avant la fin de la ligne.

 -- Makefile --

SRC = fonctions_simple.c			\
      fonctions_un_peu_plus_dures.c		\
      fonctions_dures.c				\
      fonctions_plus_dures.c			\
      fonctions_tres_dures.c			\
      fonctions_grave_dures.c			\
      fonctions_meta_dures.c

all:
        echo $(SRC)

 --

Manipulation de texte

Plusieurs fonctions de manipulation de texte sont incluses. Mais on ne les détaillera pas ici. Vous pouvez retrouvez les autres fonctions de manipulation de texte dans ‘info make’.

Une fonction s’écrit de la manière suivante (soit avec des ‘()’ soit avec des ‘{}’):

$(FONCTION ARGUMENTS)
ou
${FONCTION ARGUMENTS}

patsubst5) permet d’effectuer des substitutions.

$(patsubst MOTIFDECHERCHE,MOTIFDUSUBSTITUTION,LAVARIABLE)

Le caractère ‘%’ est un méta caractère. Il représente toutes les possibilités de caractères (sauf bien sur l’espace et la tabulation) que le motif de recherche peut satisfaire. Si ‘%’ est présent dans le motif de substitution il représente la valeur du ‘%’ dans le motif de recherche6).

Si une erreur intervient pendant l’exécution de make, pour cet exemple, tapez gmake au lieu de make.

 -- GNUMakefile --

SRC = fichier_source_de_fonctions_simples.c		\
      fichier_source_de_fonctions_un_peu_plus_durs.c	\
      fichier_source_de_fonctions_durs.c		\
      fichier_source_de_fonctions_plus_durs.c		\
      fichier_source_de_fonctions_tres_durs.c		\
      fichier_source_de_fonctions_grave_durs.c		\
      fichier_source_de_fonctions_meta_durs.c

OBJ = $(patsubst fichier_source%.c,fichier_objet%.o,$(SRC))

all:
	echo $(OBJ)

 --

Le plus souvent on se contentera de substituer juste le suffixe (.c vers .o).

$(patsubst %.c,%.o,$(SRC))

Cette fonction peut être aussi écrite sous cette forme :

$(SRC:.c=.o)

C’est la seule forme acceptée par pmake. Le caractère ‘%’ est disponible sous cette forme que pour gmake.

 -- Makefile --

SRC = fichier_source_de_fonctions_simples.c		\
      fichier_source_de_fonctions_un_peu_plus_durs.c	\
      fichier_source_de_fonctions_durs.c		\
      fichier_source_de_fonctions_plus_durs.c		\
      fichier_source_de_fonctions_tres_durs.c		\
      fichier_source_de_fonctions_grave_durs.c		\
      fichier_source_de_fonctions_meta_durs.c

OBJ = $(SRC:.c=.o)

all:
	echo $(OBJ)

 --

La magie

Pour l’instant nous avons vu que make est capable, avec des règles, de comparer des dates de fichiers et de manipuler du texte.

Pour cette partie il faut préparer quelques fichiers bidons pour les tests.

> touch fonctions_simples.c 			\
      fonctions_un_peu_plus_dures.c		\
      fonctions_dures.c				\
      fonctions_plus_dures.c			\
      fonctions_tres_dures.c			\
      fonctions_grave_dures.c			\
      fonctions_meta_dures.c

> echo 'int main(int argc, char **argv) {return 0;}' > main.c

On va maintenant réaliser un petit Makefile, qui a pour particularité d’être compatible avec les différents types existant de make.

 -- Makefile --

NAME = bidon
SRC  = fonctions_simples.c			\
       fonctions_un_peu_plus_dures.c		\
       fonctions_dures.c			\
       fonctions_plus_dures.c			\
       fonctions_tres_dures.c			\
       fonctions_grave_dures.c			\
       fonctions_meta_dures.c			\
       main.c

CFLAGS = -Wall
OBJ = $(SRC:.c=.o)

$(NAME) : $(OBJ)
	$(CC) $(OBJ) -o $(NAME)


 --
> make
cc -Wall   -c -o fonctions_simples.o fonctions_simples.c
cc -Wall   -c -o fonctions_un_peu_plus_dures.o \
	fonctions_un_peu_plus_dures.c
cc -Wall   -c -o fonctions_dures.o fonctions_dures.c
cc -Wall   -c -o fonctions_plus_dures.o fonctions_plus_dures.c
cc -Wall   -c -o fonctions_tres_dures.o fonctions_tres_dures.c
cc -Wall   -c -o fonctions_grave_dures.o \
	fonctions_grave_dures.c
cc -Wall   -c -o fonctions_meta_dures.o fonctions_meta_dures.c
cc -Wall   -c -o main.o main.c
cc fonctions_simples.o fonctions_un_peu_plus_dures.o \
fonctions_dures.o fonctions_plus_dures.o \
fonctions_tres_dures.o fonctions_grave_dures.o \
fonctions_meta_dures.o main.o -o bidon

Ici, nous observons les commandes invoquées par make. Pourtant, dans le Makefile, on invoque explicitement qu’une seule commande :

$(CC) $(OBJ) -o $(NAME)

C’est bien la dernière ligne que l’on retrouve. Mais alors d’où viennent les autres commandes ? C’est la où make devient magique. Pourtant on peut trouver la réponse dans l’énoncé de la règle.

$(NAME) : $(OBJ)
	$(CC) $(OBJ) -o $(NAME)

Pour faire $(NAME) on doit exécuter $(CC) $(OBJ) -o $(NAME). Mais pour faire cette commande les fichiers objets contenu dans $(OBJ) doivent être présents. Make alors est capable tout seul de construire ces fichiers objets grâce à des règles implicites. Make connaît un certain nombre de règles implicites. Pour construire un fichier objet, make va exécuter la commande appropriée. Pour un fichier objet c’est :

$(CC) $(CFLAGS) -c -o src.o src.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o src.o src.c (pour gmake)

Il y a d’autres points magiques, à aucun moment on a défini la variable CC pourtant make lui en connaît une valeur. C’est le cas pour d’autres variables (pour gmake en tout cas) :

  • AR
  • AS
  • CC
  • CXX
  • CO
  • CPP
  • FC
  • LEX
  • YACC
  • TEX
  • RM

Il existe un autre type de variable qui est utilisé dans les règles implicites telle que CFLAGS qui permet de modifier la commande sans avoir à la ré-écrire. Ce type de variable s’appelle les variables automatiques.(un peu douteux comme explication)

On va ré-écrire cette règle implicite.

N’oubliez pas d’effacer les fichiers objets et la cible avant de tester cet exemple.

 -- Makefile --

NAME = bidon
SRC  = fonctions_simples.c			\
       fonctions_un_peu_plus_dures.c		\
       fonctions_dures.c			\
       fonctions_plus_dures.c			\
       fonctions_tres_dures.c			\
       fonctions_grave_dures.c			\
       fonctions_meta_dures.c			\
       main.c

CFLAGS = -Wall
OBJ = $(SRC:.c=.o)

$(NAME) : $(OBJ)
	$(CC) $(OBJ) -o $(NAME)

.c.o	:
	@echo
	@echo cible $@
	@echo source $<
	$(CC) $(CFLAGS) -c $< -o $@

 --

Maintenant que dit cette règle ? Pour convertir un fichier source en fichier objet, il n’y a pas de pré-requis, en apparence, mais make sait qu’il a besoin du fichier source. Ce sera, dans ce cas le seul pré-requis.

.c.o	:
	$(CC) $(CFLAGS) -c $< -o $@

La variable $@ représente la cible et la variable $< le 1er des pré-requis.

Pour le fichier main.o on aurait pu écrire :

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

Ceci dit si l’on doit écrire toutes les règles pour chaque fichier objet ça sera un peu long.

Voila maintenant vous savez tout du Makefile de base.

Comment faire des trucs un peu plus compliqués ?

Commandes

Chacune des commandes peut être précédée d’un préfixe qui modifie l’interprétation de la commande par make. Voici la liste des préfixes :

  • ‘@’ N’imprime pas la commande avant de l’exécuter.
  • ‘-’ N’interrompt pas l’exécution de make si cette commande échoue.
  • ‘+’ N’interrompt pas l’exécution de make si cette commande échoue même avec les options -q, -t, -k, -n, etc...

Ce qui permet de faire quelques petits trucs sympa.

 -- Makefile --
NAME = bidon
SRC  = fonctions_simples.c			\
       fonctions_un_peu_plus_dures.c		\
       fonctions_dures.c			\
       fonctions_plus_dures.c			\
       fonctions_tres_dures.c			\
       fonctions_grave_dures.c			\
       fonctions_meta_dures.c			\
       main.c

CFLAGS = -Wall
OBJ = $(SRC:.c=.o)
RM = rm



$(NAME) : $(OBJ)
	@echo . $(NAME) done
	@$(CC) $(OBJ) -o $(NAME)

.c.o	:
	@echo -n .
	@$(CC) $(CFLAGS) -c $< -o $@


all : $(NAME)

clean :
	-$(RM) $(OBJ)

 --

Voila une utilisation des ‘@’ et des ‘-‘. Remarquez l’apparition des règles all et clean. Par convention ces deux règles doivent toujours être présentes dans un makefile. De plus elles permettent d’invoquer make sous cette forme : make clean all.

Variables

Variables Automatiques

Pour facilité un certain nombre d’opération dans les règles, il existe des variables locales à la règle. Ces variables sont parfois appelées : Variables automatiques Voici la liste des plus courantes:

  • $@ Représente le nom de la cible courante de la règle.
  • $< Représente le nom du premier des pré-requis.
  • $^ Représente les noms de l’ensemble des pré-requis (pour gmake).
  • $> Représente les noms de l’ensemble des pré-requis (pour pmake).
  • $? Représente les noms de l’ensemble des pré-requis plus récent que la cible.
  • $* ne contient que le préfixe de la cible.(Voir l’exemple)

Opérateurs d'affectation

Les variables n’ont pas que l’opérateur ‘=’ pour affecter des valeurs. Voici la liste de tout les opérateurs : * ‘=’ dit récursif Si la partie droite de l’expression contient une référence, comme une variable ou une fonction, celle-ci n’est évalué que lors de la résolution de la variable.7)

  • ‘:=’ dit static Si la partie droite de l’expression contient une référence celle-ci sera résolue dès l’affectation de la variable.
  • ‘+=’ dit concat concatène à la fin de la valeur actuelle de la variable la valeur de la partie droite de l’expression.
  • ‘?=’ dit condi n’affecte la valeur à la variable que si cette variable n’a pas de valeur.
  • ‘!=’ dit shell Cette opérateur n’existe que pour pmake, l’equivalent sous gmake est la fonction “$(shell cmd)“. Donc la partie droite de l’expression est résolue, s’il y a des références, et est exécuté dans un shell.
 -- GNUMakefile --

DATE_REC	=	$(shell sleep 2; date)
DATE_STA	:=	$(shell sleep 2; date)
APP		?= salut
APP		+= les
APP		+= amiches
APP		?= ta mere

rec_or_sta :
     @echo Recursif ou statique ?
     @echo before rec : $(DATE_REC)
     @echo before sta : $(DATE_STA)
     sleep 7;
     @echo after rec : $(DATE_REC)
     @echo after sta : $(DATE_STA)

append_and_cond :
       @echo $@
       @echo $APP;

 --

Regardez bien le comportement de DATE_STA et de DATE_REC, la variable affecté en récursif est évalué a chaque fois qu’elle est appelé. Alors que l’autre est évaluée une bonne fois pour toute lors de son évaluation. Pour les autres types d’évaluation l’exemple parle de lui même.

Règles

Règles spéciales

Comme on l’a vu auparavant, une règle possède une cible. Cette cible représente un fichier à réaliser. Il peut arriver que l’on ait besoin de créer une règle qui n’a pas besoin d’une cible: c’est le cas de la règle all ou clean. On peut faire cette petite blague à toute personne ne comprenant pas bien le fonctionnement de make. Si une personne venait à placer un fichier qui se nomme comme la règle, alors make considérerait qu’il n’est pas nécessaire d’exécuter la règle. Comme un ‘touch all’ ou un ‘touch clean’ donnerait pour résultat :

> make clean
'clean' is up to date.

Il existe une règle spéciale qui permet d’éviter la vérification entre la cible d’une règle et d’un fichier portant le même nom éventuellement présent. Il suffit de rajouter :

.PHONY : clean all

Il existe d’autres règles spéciales... cherchez dans les docs bandes de feignasses.

Cibles multiples

Pour l’instant toutes les règles que l’on a vu, n’avait qu’une seule cible. Une règle à cibles multiples peut servir dans plusieurs cas. Si on ne veux pas écrire les mêmes commandes pour deux cibles. Si l’on connaît deux cibles qui ont les même pré-requis.

-- Makefile --

all : salut coucou hihi bouh
	echo \< $<
	echo \> $>
	echo \^ $^
	echo \? $?
	echo \* $*

salut coucou hihi bouh :
      echo $@

.PHONY .SILENT : salut coucou hihi bouh all

--

Une règle à cibles multiples peut être très utile pour parcourir des répertoires ou permettre de rajouter des pré-requis. Par exemple, dans le cas de la programmation en C, les makefile que l’on a réalisés pour le moment ne vérifient jamais si des headers (fichier.h) ont été modifiés depuis la dernière fois. Voici une manière simple :

-- Makefile --

NAME = bidule
SRC = main.c truc.c chose.c
INC = gen.h syst.h
OBJ =  $(SRC:.c=.o)

$(NAME) : $(OBJ)
	$(CC) -o $(NAME) $(OBJ)

$(OBJ) : $(INC)

--

Le seul inconvénient de cette méthode si le fichier chose.c n’inclut pas syst.h, sera recréé si le fichier syst.h est modifié.

Opérateurs de règles

(en travaux) FIXME

Makefile avancé

Dans cette partie, on va développer des fonctionnalités plus complexes pour résoudre un problème donné.

Fichiers objets et répertoires

On aimerait pouvoir compiler les fichiers objets dans un autre répertoire pour éviter certains désagréments. Par exemple à l’école les comptes utilisateurs sont sur le réseau. Les écritures et lectures successives pour écrire les fichiers objets peuvent entraîner, si 400 personnes le font en même temps, certains désagréments. Pour éviter tout cela, il suffirait de placer les fichiers objets dans un répertoire en local sur la machine tout en permettant à make de continuer de vérifier ces dépendances.

Solution pmake

En écrivant ce document nous avons découvert les fonctionnalités de pmake. Malgré ce que l’on pourrait croire pmake à presque autant de possibilités que gmake. Au lieu de contenir les fonctionnalités dans le binaire comme dans gmake, il faut inclure les fichiers qui contienne ces fonctionnalités. Ce qui permet d’écrire un makefile en deux lignes.

-- Makefile --
PROG = bidon

.include "bsd.prog.mk"
--

Ce makefile permet de construire le programme avec le fichier source du même nom que le programme avec le suffixe ‘.c’ (bidon.c dans le cas présent), ainsi que le man associé avec le suffixe ‘.[1-9]’ (bidon.1). C’est grâce à ce fichier inclus “bsd.prog.mk“, que l’on a accès à toutes les fonctionnalités d’un gros makefile. Sous cette forme aux règles : “clean“, “cleandir“, “depend“, “install“, “tags“, etc...

Mais aussi aux variables prédéfinies : RM, CC, LIBC, LIBCURSES, etc.

C’est assez pratique ! Vous avez un header ? Il vous suffit d’appeler pmake avec la règle “depend” et un fichier “.depend” est créé. Ce fichier est inclus lors de l’interprétation du makefile et permet de rajouter les dépendances du fichier source.

Vous avez plusieurs fichiers sources ? Rajoutez la Variable SRCS, et donnez la liste des fichiers à utiliser.

Vous ne voulez pas créez de man avec vos sources ? Ajoutez cette ligne à votre makefile :

NOMAN = noman

Vous voulez construire le fichier objet dans un autre répertoire ? Pas de problème...

Rajoutez la variable BSDOBJDIR dans votre makefile :

BSDOBJDIR = obj

En conclusion, si vous êtes sur un système avec pmake et que vous ne comptez pas faire un makefile pour un autre système qu’un BSD, utilisez ce procédé... adonf™.

-- Makefile --
PROG = bidon
NOMAN = noman
BSDOBJDIR = /tmp/obj
SRCDIR = src
SRC =	$(SRCDIR)/fonctions_dures.c		\
	$(SRCDIR)/fonctions_grave_dures.c	\
	$(SRCDIR)/fonctions_meta_dures.c	\
	$(SRCDIR)/fonctions_plus_dures.c	\
	$(SRCDIR)/fonctions_simples.c		\
	$(SRCDIR)/fonctions_tres_dures.c	\
	$(SRCDIR)/fonctions_un_peu_plus_dures.c	\
	$(SRCDIR)/main.c

.include "bsd.prog.mk"
--

Solution gmake

Pour ces exemples vous devez effectuer les opérations suivantes :

> mkdir src obj
> touch src/fonctions_simples.c			\
      src/fonctions_un_peu_plus_dures.c		\
      src/fonctions_dures.c			\
      src/fonctions_plus_dures.c		\
      src/fonctions_tres_dures.c		\
      src/fonctions_grave_dures.c		\
      src/fonctions_meta_dures.c

> echo 'int main(int argc,char **argv) {return 0;}' >src/main.c
-- GNUMakefile --

NAME = bidon
SRCDIR = src
OBJDIR = obj

SRC =	$(SRCDIR)/fonctions_dures.c		\
	$(SRCDIR)/fonctions_grave_dures.c	\
	$(SRCDIR)/fonctions_meta_dures.c	\
	$(SRCDIR)/fonctions_plus_dures.c	\
	$(SRCDIR)/fonctions_simples.c		\
	$(SRCDIR)/fonctions_tres_dures.c	\
	$(SRCDIR)/fonctions_un_peu_plus_dures.c	\
	$(SRCDIR)/main.c

OBJ = $(subst $(SRCDIR),$(OBJDIR),$(SRC:.c=.o))

$(NAME)	: $(OBJ)
	@echo Making $@
	@$(CC) -o $@ $(OBJ)

all : $(NAME)

clean	:
	-$(RM) $(OBJ)

fclean : clean
       -$(RM) $(NAME) *~

re : clean all

$(OBJDIR) :
	  mkdir -p $(OBJDIR)

$(OBJDIR)/	@echo Compile $*
	@$(CC) -c $< -o $@

.PHONY : re fclean clean all
--

Avec gmake on peut utiliser le méta-caractère %+ récupérer un motif et le réutiliser dans les pré-requis. On récupère le motif avec la variable $* dans la partie commande.

1) Les systèmes Linux (entre autres) utilisent GNU Make par défaut. Il s’appelle dans ce cas `make’ tout court. La meilleure méthode pour reconnaître un GNU make d’un autre make est de tester si `make –version’ marche ou pas.
2) C’est sous cette forme que le binaire make BSD est nommé sous linux Debian,Slackware...
3) Attention, c’est mal formulé: gmake est très portable, donc on peut le compiler et l’utiliser sur plein de plates-formes différentes. Par contre gmake a des extensions spécifiques à GNU, et donc un Makefile écrit pour gmake avec des extensions NE MARCHERA en général PAS avec un autre make.
4) Hmmm. En général, ça nuit à la lisibilité :-) Les macros ont pour but de FACTORISER du code identique ou similaire.
5) Attention !!! C’est spécifique à GNU Make
6) pas terrible comme explication
7) Pour ceux qui n’ont pas compris y a un exemple après.
 
tech/the_art_of_making_makefile.txt · Dernière modification: 2007/07/04 15:52 par danjer
 
Recent changes RSS feed Valid XHTML 1.0 Valid CSS Driven by DokuWiki Powered by Lescampeurs