Il existe plusieurs types de make1). Ce document traitera de ceux-ci :
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 :
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 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 :
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 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 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 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 va créer un fichier archive qui va contenir, l’ensemble des fichiers objets passés en paramètre.
Ranlib crée un index dans un fichier archive. Ranlib devra être exécuté à chaque fois que le fichier archive sera modifié.
.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).
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.
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 :
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.
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)
--
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)
--
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) :
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.
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.
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)
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.
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.
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é.
(en travaux)
Dans cette partie, on va développer des fonctionnalités plus complexes pour résoudre un problème donné.
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.
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" --
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.