Les fonctions (functions) vous permettent d'effectuer du traitement de texte à l'intérieur d'un makefile. Cela rend possible de calculer les fichiers sur lesquels opérer ou de composer les commandes à utiliser dans les recettes (recipes). On utilise une fonction sous la forme d'un appel de fonction : vous fournissez le nom de la fonction ainsi que le texte (que l'on appelle les arguments) que la fonction doit traiter, la fonction effectue son traitement, et son résultat est inséré à l'endroit de l'appel. C'est exactement le même mécanisme que celui par lequel une variable est développée et remplacée.
Un appel de fonction ressemble beaucoup à une référence de variable. Il peut apparaître partout où une référence de variable peut apparaître, et il est développé selon les mêmes règles qu'une référence de variable. Un appel de fonction se présente sous la forme suivante :
$(function arguments)
ou bien sous cette forme :
${function arguments}
Ici function est un nom de fonction, l'un de la courte liste de noms intégrés à make. À noter que la fonction intégrée call permet, en pratique, de créer vos propres fonctions.
arguments sont les arguments de la fonction. Ils sont séparés du nom de la fonction par une ou plusieurs espaces ou tabulations, et lorsqu'il y a plusieurs arguments, ceux-ci sont séparés entre eux par des virgules. Ces espaces et virgules de séparation ne font pas partie de la valeur des arguments. Les délimiteurs qui entourent l'appel de fonction peuvent être des parenthèses ou des accolades, mais si un argument contient ce type de parenthèses, elles doivent être appariées ; en revanche, l'autre type de délimiteur peut apparaître seul, sans être apparié. Si les arguments contiennent eux-mêmes d'autres appels de fonction ou références de variable, il est plus sage d'utiliser le même type de délimiteurs pour toutes les références : il faut donc écrire « $(subst a,b,$(x)) » et non « $(subst a,b,${x}) ». C'est plus clair, et parce qu'un seul type de délimiteur est mis en correspondance pour trouver la fin de la référence.
Chaque argument est développé avant que la fonction ne soit invoquée (sauf indication contraire ci-dessous). Le développement se fait dans l'ordre où les arguments apparaissent.
Lorsque vous voulez utiliser, comme arguments d'une fonction, des caractères qui ont une signification particulière pour make, vous pouvez avoir besoin de les masquer. GNU make ne prend pas en charge l'échappement de caractères au moyen de barres obliques inverses ou d'autres séquences d'échappement ; cependant, comme les arguments sont découpés avant d'être développés, vous pouvez masquer ces caractères en les plaçant dans des variables.
Parmi les caractères que vous pourriez avoir besoin de masquer figurent :
Par exemple, vous pouvez définir des variables comma et space dont les valeurs sont, respectivement, un unique caractère virgule et un unique caractère espace, puis insérer ces variables là où de tels caractères sont voulus, de la façon suivante :
comma:= , empty:= space:= $(empty) $(empty) foo:= a b c bar:= $(subst $(space),$(comma),$(foo)) # bar vaut maintenant 'a,b,c'.
Ici la fonction subst remplace chaque espace par une virgule, à travers la valeur de foo, et insère le résultat.
Voici quelques fonctions qui opèrent sur des chaînes de caractères :
$(subst from,to,text)Effectue un remplacement textuel sur le texte text : chaque occurrence de from est remplacée par to. Le résultat est inséré à la place de l'appel de fonction. Par exemple,
$(subst ee,EE,feet on the street)
produit la valeur « fEEt on the strEEt ».
$(patsubst pattern,replacement,text)Cherche dans text les mots séparés par des espaces qui correspondent à pattern et les remplace par replacement. Ici pattern peut contenir un « % » qui agit comme un caractère générique (joker), correspondant à un nombre quelconque de caractères quelconques à l'intérieur d'un mot. Si replacement contient lui aussi un « % », ce « % » est remplacé par le texte qui a correspondu au « % » de pattern. Les mots qui ne correspondent pas au motif sont conservés sans changement dans la sortie. Seul le premier « % » de pattern et de replacement est traité de cette manière ; tout « % » suivant reste inchangé.
Les caractères « % » dans les invocations de la fonction patsubst peuvent être protégés (quoted) en les faisant précéder d'une barre oblique inverse (« \ »). Les barres obliques inverses qui protégeraient sinon des caractères « % » peuvent elles-mêmes être protégées par des barres obliques inverses supplémentaires. Les barres obliques inverses qui protègent un caractère « % » ou une autre barre oblique inverse sont retirées du motif avant que celui-ci ne soit comparé à des noms de fichiers ou qu'un radical (stem) n'y soit inséré. En revanche, les barres obliques inverses qui ne risquent pas de protéger un caractère « % » sont laissées intactes. Par exemple, le motif the\%weird\\%pattern\\ a « the%weird\ » avant le caractère « % » effectif, puis « pattern\\ » à sa suite. Les deux dernières barres obliques inverses sont laissées telles quelles, car elles ne peuvent affecter aucun caractère « % ».
Les espaces entre les mots sont réduites à un unique caractère espace ; les espaces de tête et de fin sont supprimées.
Par exemple,
$(patsubst %.c,%.o,x.c.c bar.c)
produit la valeur « x.c.o bar.o ».
Les références de substitution (voir Références de substitution) constituent un moyen plus simple d'obtenir l'effet de la fonction patsubst :
$(var:pattern=replacement)
est équivalent à
$(patsubst pattern,replacement,$(var))
Cette seconde forme abrégée simplifie l'un des usages les plus courants de patsubst — le remplacement du suffixe à la fin de noms de fichiers.
$(var:suffix=replacement)
est équivalent à
$(patsubst %suffix,%replacement,$(var))
Par exemple, supposons que vous ayez une liste de fichiers objets :
objects = foo.o bar.o baz.o
Pour obtenir la liste des fichiers source correspondants, au lieu d'utiliser la forme générale,
$(patsubst %.o,%.c,$(objects))
vous pouvez simplement écrire :
$(objects:.o=.c)
$(strip string)Supprime les espaces de tête et de fin de string et remplace chaque suite interne d'un ou plusieurs caractères d'espacement par une seule espace. Ainsi, « $(strip a b c ) » donne pour résultat « a b c ».
La fonction strip est très utile lorsqu'on l'emploie en combinaison avec des conditionnelles. Lorsqu'on compare quelque chose à la chaîne vide « » à l'aide de ifeq ou ifneq, on souhaite souvent qu'une chaîne composée uniquement d'espaces corresponde à la chaîne vide (voir Les parties conditionnelles des makefiles).
Ainsi, l'exemple suivant pourrait ne pas donner le résultat souhaité :
.PHONY: all ifneq "$(needs_made)" "" all: $(needs_made) else all:;@echo 'Nothing to make!' endif
Remplacer la référence de variable « $(needs_made) » par l'appel de fonction « $(strip $(needs_made)) » dans la directive ifneq le rendrait plus robuste.
$(findstring find,in)Cherche si find apparaît dans in. S'il y apparaît, la valeur est find ; sinon, la valeur est vide. Vous pouvez utiliser cette fonction dans une conditionnelle pour tester la présence d'une sous-chaîne particulière dans une chaîne donnée. Ainsi, les deux exemples,
$(findstring a,a b c) $(findstring a,b c)
produisent respectivement les valeurs « a » et « » (la chaîne vide). Pour une application pratique de findstring, voir Les conditionnelles qui testent des drapeaux.
$(filter pattern…,text)
Renvoie tous les mots de text, séparés par des espaces, qui correspondent à l'un des mots de pattern, en éliminant ceux qui n'y correspondent pas. Les motifs s'écrivent à l'aide de « % », tout comme les motifs utilisés dans la fonction patsubst ci-dessus.
La fonction filter peut servir à séparer différents types de chaînes (comme des noms de fichiers) au sein d'une variable. Par exemple :
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
indique que foo dépend de foo.c, bar.c, baz.s et ugh.h, mais que seuls foo.c, bar.c et baz.s doivent être passés dans la commande au compilateur.
$(filter-out pattern…,text)
Renvoie tous les mots de text, séparés par des espaces, qui ne correspondent à aucun des mots de pattern, en éliminant les mots qui correspondent à un ou plusieurs d'entre eux. C'est exactement l'opposé de la fonction filter.
Par exemple, étant donné :
objects=main1.o foo.o main2.o bar.o mains=main1.o main2.o
l'expression suivante engendre une liste qui contient tous les fichiers objets ne figurant pas dans « mains » :
$(filter-out $(mains),$(objects))
$(sort list)Trie les mots de list dans l'ordre lexicographique, en supprimant les mots en double. La sortie est une liste de mots séparés par des espaces uniques. Ainsi,
$(sort foo bar lose)
renvoie la valeur « bar foo lose ».
Soit dit en passant, comme sort supprime les mots en double, vous pouvez l'utiliser dans ce seul but, même si l'ordre du tri vous importe peu.
$(word n,text)Renvoie le n-ième mot de text. Les valeurs valides de n commencent à 1. Si n est supérieur au nombre de mots de text, la valeur est vide. Par exemple,
$(word 2, foo bar baz)
renvoie « bar ».
$(wordlist s,e,text)Renvoie la liste des mots de text allant du mot s au mot e (bornes incluses). Les valeurs valides de s commencent à 1 ; e peut commencer à 0. Si s est supérieur au nombre de mots de text, la valeur est vide. Si e est supérieur au nombre de mots de text, les mots jusqu'à la fin de text sont renvoyés. Si s est supérieur à e, rien n'est renvoyé. Par exemple,
$(wordlist 2, 3, foo bar baz)
renvoie « bar baz ».
$(words text)
Renvoie le nombre de mots de text. Ainsi, le dernier mot de text s'obtient par $(word $(words text),text).
$(firstword names…)L'argument names est considéré comme une suite de noms séparés par des espaces. La valeur est le premier nom de la suite. Les noms restants sont ignorés.
Par exemple,
$(firstword foo bar)
produit le résultat « foo ». Bien que $(firstword text) soit identique à $(word 1,text), la fonction firstword est conservée pour sa simplicité.
$(lastword names…)L'argument names est considéré comme une suite de noms séparés par des espaces. La valeur est le dernier nom de la suite.
Par exemple,
$(lastword foo bar)
produit le résultat « bar ». Bien que $(lastword text) soit identique à $(word $(words text),text), la fonction lastword a été ajoutée pour sa simplicité et ses meilleures performances.
Voici un exemple réaliste d'utilisation de subst et patsubst. Supposons qu'un makefile utilise la variable VPATH pour spécifier une liste de répertoires dans lesquels make doit chercher les fichiers prérequis (prerequisites) (voir Le chemin de recherche VPATH pour tous les prérequis). Cet exemple montre comment indiquer au compilateur C de chercher les fichiers d'en-tête dans cette même liste de répertoires.
La valeur de VPATH est une liste de répertoires séparés par des deux-points, par exemple « src:../headers ». On utilise d'abord la fonction subst pour changer les deux-points en espaces :
$(subst :, ,$(VPATH))
Cela produit « src ../headers ». On utilise ensuite patsubst pour transformer chaque nom de répertoire en un drapeau « -I ». Le résultat ainsi obtenu peut être ajouté à la valeur de la variable CFLAGS, qui est passée automatiquement au compilateur C, de la façon suivante :
override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))
L'effet est d'ajouter le texte « -Isrc -I../headers » à la valeur précédemment donnée à CFLAGS. On utilise ici la directive override afin que la nouvelle valeur soit bien affectée même si la valeur précédente de CFLAGS avait été spécifiée par un argument de la ligne de commande (voir La directive override).
Plusieurs des fonctions de développement intégrées concernent spécifiquement la décomposition de noms de fichiers ou de listes de noms de fichiers.
Chacune des fonctions suivantes effectue une transformation particulière sur un nom de fichier. L'argument de la fonction est considéré comme une suite de noms de fichiers séparés par des espaces (les espaces de tête et de fin sont ignorées). Chaque nom de fichier de la suite est transformé de la même manière, et les résultats sont concaténés avec une seule espace entre eux.
$(dir names…)Extrait la partie répertoire de chaque nom de fichier de names. La partie répertoire d'un nom de fichier est tout ce qui précède la dernière barre oblique qu'il contient (cette barre oblique incluse). Si le nom de fichier ne contient aucune barre oblique, la partie répertoire est la chaîne « ./ ». Par exemple,
$(dir src/foo.c hacks)
produit le résultat « src/ ./ ».
$(notdir names…)Extrait tout sauf la partie répertoire de chaque nom de fichier de names. Si le nom de fichier ne contient aucune barre oblique, il reste inchangé. Sinon, tout ce qui va jusqu'à la dernière barre oblique en est retiré.
Un nom de fichier qui se termine par une barre oblique devient une chaîne vide. C'est regrettable, car cela signifie que le résultat n'a pas toujours le même nombre de noms de fichiers séparés par des espaces que l'argument ; mais nous ne voyons aucune autre solution valable.
Par exemple,
$(notdir src/foo.c hacks)
produit le résultat « foo.c hacks ».
$(suffix names…)Extrait le suffixe de chaque nom de fichier de names. Si le nom de fichier contient un point, le suffixe est tout ce qui commence au dernier point. Sinon, le suffixe est la chaîne vide. Cela signifie fréquemment que le résultat sera vide alors que names ne l'est pas, et si names contient plusieurs noms de fichiers, le résultat peut en contenir moins.
Par exemple,
$(suffix src/foo.c src-1.0/bar.c hacks)
produit le résultat « .c .c ».
$(basename names…)Extrait tout sauf le suffixe de chaque nom de fichier de names. Si le nom de fichier contient un point, le basename est tout ce qui va jusqu'au dernier point (ce point exclu). Les points figurant dans la partie répertoire sont ignorés. S'il n'y a aucun point, le basename est le nom de fichier entier. Par exemple,
$(basename src/foo.c src-1.0/bar hacks)
produit le résultat « src/foo src-1.0/bar hacks ».
$(addsuffix suffix,names…)L'argument names est considéré comme une suite de noms séparés par des espaces ; suffix est traité comme un tout. La valeur de suffix est ajoutée à la fin de chaque nom individuel, et les noms ainsi allongés sont concaténés avec une seule espace entre eux. Par exemple,
$(addsuffix .c,foo bar)
produit le résultat « foo.c bar.c ».
$(addprefix prefix,names…)L'argument names est considéré comme une suite de noms séparés par des espaces ; prefix est traité comme un tout. La valeur de prefix est préfixée au début de chaque nom individuel, et les noms ainsi allongés sont concaténés avec une seule espace entre eux. Par exemple,
$(addprefix src/,foo bar)
produit le résultat « src/foo src/bar ».
$(join list1,list2)Concatène les deux arguments mot à mot : les deux premiers mots (un de chaque argument) concaténés forment le premier mot du résultat, les deux deuxièmes mots forment le deuxième mot du résultat, et ainsi de suite. Le n-ième mot du résultat provient donc du n-ième mot de chaque argument. Si l'un des arguments comporte plus de mots que l'autre, les mots excédentaires sont copiés sans changement dans le résultat.
Par exemple, « $(join a b,.c .o) » produit « a.c b.o ».
Les espaces entre les mots des listes ne sont pas préservées ; elles sont remplacées par une seule espace.
Cette fonction peut fusionner les résultats des fonctions dir et notdir pour reconstituer la liste de fichiers d'origine qui avait été donnée à ces deux fonctions.
$(wildcard pattern)
L'argument pattern est un motif de nom de fichier, contenant typiquement des caractères génériques (comme dans les motifs de noms de fichiers du shell). Le résultat de wildcard est une liste, séparée par des espaces, des noms des fichiers existants qui correspondent au motif. Voir Utilisation de caractères génériques dans les noms de fichiers.
$(realpath names…)
Pour chaque nom de fichier de names, renvoie le nom absolu canonique. Un nom canonique ne contient aucun composant . ou .., ni séparateur de chemin (/) en double, ni lien symbolique. En cas d'échec, la chaîne vide est renvoyée. Consultez la documentation de realpath(3) pour une liste des causes d'échec possibles.
$(abspath names…)
Pour chaque nom de fichier de names, renvoie un nom absolu qui ne contient aucun composant . ou .., ni séparateur de chemin (/) en double. Notez que, contrairement à la fonction realpath, abspath ne résout pas les liens symboliques et n'exige pas que les noms de fichiers désignent un fichier ou un répertoire existant. Utilisez la fonction wildcard pour tester l'existence.
Il existe quatre fonctions qui permettent le développement conditionnel. Une caractéristique essentielle de ces fonctions est que tous les arguments ne sont pas développés d'emblée. Seuls les arguments qui ont besoin d'être développés le sont.
$(if condition,then-part[,else-part])
La fonction if permet le développement conditionnel dans un contexte fonctionnel (par opposition aux conditionnelles de makefile de GNU make telles que ifeq (voir Syntaxe des conditionnelles)).
Le premier argument, condition, est d'abord débarrassé de toutes les espaces de tête et de fin, puis développé. S'il se développe en une chaîne non vide, la condition est considérée comme vraie. S'il se développe en une chaîne vide, la condition est considérée comme fausse.
Si la condition est vraie, le deuxième argument, then-part, est évalué, et c'est lui qui sert de résultat à l'évaluation de l'ensemble de la fonction if.
Si la condition est fausse, le troisième argument, else-part, est évalué, et c'est lui qui constitue le résultat de la fonction if. S'il n'y a pas de troisième argument, la fonction if ne produit rien (la chaîne vide).
Notez que seul l'un des deux, then-part ou else-part, est évalué, jamais les deux. Chacun peut donc contenir des effets de bord (tels que des appels à la fonction shell, etc.).
$(or condition1[,condition2[,condition3…]])
La fonction or fournit une opération OU « à court-circuit (short-circuiting) ». Chaque argument est développé, dans l'ordre. Dès qu'un argument se développe en une chaîne non vide, le traitement s'arrête et le résultat du développement est cette chaîne. Si, après que tous les arguments ont été développés, tous sont faux (vides), alors le résultat du développement est la chaîne vide.
$(and condition1[,condition2[,condition3…]])
La fonction and fournit une opération ET « à court-circuit (short-circuiting) ». Chaque argument est développé, dans l'ordre. Dès qu'un argument se développe en une chaîne vide, le traitement s'arrête et le résultat du développement est la chaîne vide. Si tous les arguments se développent en une chaîne non vide, alors le résultat du développement est le développement du dernier argument.
$(intcmp lhs,rhs[,lt-part[,eq-part[,gt-part]]])
La fonction intcmp permet la comparaison numérique d'entiers. Cette fonction n'a pas d'équivalent parmi les conditionnelles de makefile de GNU make.
Le membre de gauche, lhs, et le membre de droite, rhs, sont développés et analysés comme des nombres entiers en base 10. Le développement des arguments restants est gouverné par la façon dont le membre de gauche numérique se compare au membre de droite numérique.
S'il n'y a pas d'autres arguments, la fonction se développe en vide si le membre de gauche et le membre de droite ne sont pas égaux, ou en leur valeur numérique s'ils sont égaux.
Sinon, si le membre de gauche est strictement inférieur au membre de droite, la fonction intcmp s'évalue en le développement du troisième argument, lt-part. Si les deux membres sont égaux, la fonction intcmp s'évalue en le développement du quatrième argument, eq-part. Si le membre de gauche est strictement supérieur au membre de droite, la fonction intcmp s'évalue en le développement du cinquième argument, gt-part.
Si gt-part est absent, sa valeur par défaut est eq-part. Si eq-part est absent, sa valeur par défaut est la chaîne vide. Ainsi, « $(intcmp 9,7,hello) » comme « $(intcmp 9,7,hello,world,) » s'évaluent en la chaîne vide, tandis que « $(intcmp 9,7,hello,world) » (remarquez l'absence de virgule après world) s'évalue en « world ».
La fonction let offre un moyen de limiter la portée d'une variable. L'affectation des variables nommées dans une expression let n'a d'effet qu'à l'intérieur du texte fourni par l'expression let, et cette affectation n'a aucune incidence sur la variable de même nom dans une portée extérieure.
De plus, la fonction let permet le « déballage (unpacking) » d'une liste en affectant toutes les valeurs non assignées à la dernière variable nommée.
La syntaxe de la fonction let est la suivante :
$(let var [var ...],[list],text)
Les deux premiers arguments, var et list, sont développés avant toute autre chose ; notez que le dernier argument, text, n'est pas développé en même temps. Ensuite, chaque mot de la valeur développée de list est lié tour à tour à chacun des noms de variables var, le dernier nom de variable étant lié à tout le reste de la list développée. Autrement dit, le premier mot de list est lié à la première variable var, le deuxième mot à la deuxième variable var, et ainsi de suite.
S'il y a plus de noms de variables dans var qu'il n'y a de mots dans list, les noms de variables var restants reçoivent la chaîne vide. S'il y a moins de var que de mots dans list, la dernière var reçoit tous les mots restants de list.
Les variables de var sont affectées comme des variables à expansion simple pendant l'exécution de let. Voir Les deux types de variables.
Une fois toutes les variables ainsi liées, text est développé, et c'est lui qui constitue le résultat de la fonction let.
Par exemple, la macro suivante inverse l'ordre des mots de la liste donnée comme premier argument :
reverse = $(let first rest,$1,\
$(if $(rest),$(call reverse,$(rest)) )$(first))
all: ; @echo $(call reverse,d c b a)
Cela affiche a b c d. Au premier appel, let développe $1 en d c b a. Il affecte ensuite d à first et c b a à rest. Il développe alors l'instruction if, où $(rest) n'est pas vide ; on invoque donc récursivement la fonction reverse avec la valeur de rest, qui est maintenant c b a. L'invocation récursive de let affecte c à first et b a à rest. La récursion se poursuit jusqu'à ce que let soit appelé avec une seule valeur, a. Là, first vaut a et rest est vide ; on ne récurse donc pas et l'on se contente de développer $(first) en a et de revenir, ce qui ajoute b, et ainsi de suite.
Une fois l'appel à reverse terminé, les variables first et rest ne sont plus définies. Si des variables portant ces noms existaient au préalable, elles ne sont pas affectées par le développement de la macro reverse.
La fonction foreach ressemble à la fonction let, mais diffère beaucoup des autres fonctions. Elle fait en sorte qu'un même fragment de texte soit utilisé de façon répétée, chaque fois avec une substitution différente. La fonction foreach ressemble à la commande for du shell sh et à la commande foreach du C-shell csh.
La syntaxe de la fonction foreach est la suivante :
$(foreach var,list,text)
Les deux premiers arguments, var et list, sont développés avant toute autre chose ; notez que le dernier argument, text, n'est pas développé en même temps. Ensuite, pour chaque mot de la valeur développée de list, la variable nommée par la valeur développée de var reçoit ce mot, et text est développé. text contient vraisemblablement des références à cette variable, de sorte que son développement sera différent à chaque fois.
Le résultat est que text est développé autant de fois qu'il y a de mots séparés par des espaces dans list. Les multiples développements de text sont concaténés, avec des espaces entre eux, pour former le résultat de foreach.
Cet exemple simple affecte à la variable « files » la liste de tous les fichiers situés dans les répertoires de la liste « dirs » :
dirs := a b c d files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
Ici text est « $(wildcard $(dir)/*) ». La première itération trouve la valeur « a » pour dir, ce qui produit le même résultat que « $(wildcard a/*) » ; la deuxième itération produit le résultat de « $(wildcard b/*) », et la troisième, celui de « $(wildcard c/*) ».
Cet exemple donne le même résultat (à part le fait de définir « dirs ») que l'exemple suivant :
files := $(wildcard a/* b/* c/* d/*)
Lorsque text est compliqué, vous pouvez en améliorer la lisibilité en lui donnant un nom, à l'aide d'une variable supplémentaire :
find_files = $(wildcard $(dir)/*) dirs := a b c d files := $(foreach dir,$(dirs),$(find_files))
Nous utilisons ici la variable find_files de cette façon. Nous employons un simple « = » pour la définir comme une variable à expansion récursive, afin que sa valeur contienne un véritable appel de fonction à redévelopper sous le contrôle de foreach ; une variable à expansion simple ne conviendrait pas, car wildcard ne serait appelé qu'une seule fois, au moment de la définition de find_files.
Comme la fonction let, la fonction foreach n'a pas d'effet permanent sur la variable var ; sa valeur et son type après l'appel de la fonction foreach sont les mêmes qu'avant. Les autres valeurs prises dans list ne sont en vigueur que temporairement, durant l'exécution de foreach. La variable var est une variable à expansion simple pendant l'exécution de foreach. Si var était indéfinie avant l'appel de la fonction foreach, elle reste indéfinie après l'appel. Voir Les deux types de variables.
Vous devez être prudent lorsque vous utilisez des expressions de variable complexes qui produisent des noms de variables, car bien des chaînes étranges constituent des noms de variables valides, mais ce n'est probablement pas ce que vous vouliez. Par exemple,
files := $(foreach Esta-escrito-en-espanol!,b c ch,$(find_files))
pourrait être utile si la valeur de find_files référence la variable dont le nom est « Esta-escrito-en-espanol! » (es un nombre bastante largo, no? [c'est un nom plutôt long, non ?]), mais c'est plus probablement une erreur.
La fonction file permet au makefile d'écrire dans un fichier ou d'y lire. Deux modes d'écriture sont pris en charge : « écrasement », où le texte est écrit au début du fichier et tout contenu existant est perdu, et « ajout », où le texte est écrit à la fin du fichier, le contenu existant étant préservé. Dans les deux cas, le fichier est créé s'il n'existe pas. C'est une erreur fatale si le fichier ne peut pas être ouvert en écriture ou si l'opération d'écriture échoue. Lors d'une écriture dans un fichier, la fonction file se développe en la chaîne vide.
Lors d'une lecture depuis un fichier, la fonction file se développe en le contenu littéral du fichier, à ceci près que le dernier saut de ligne (s'il y en a un) est retiré. Tenter de lire depuis un fichier inexistant se développe en la chaîne vide.
La syntaxe de la fonction file est la suivante :
$(file op filename[,text])
Lorsque la fonction file est évaluée, tous ses arguments sont d'abord développés, puis le fichier indiqué par filename est ouvert dans le mode décrit par op.
L'opérateur op peut être > pour indiquer que le fichier sera écrasé avec un nouveau contenu, >> pour indiquer que le contenu actuel du fichier sera complété par ajout, ou < pour indiquer que le contenu du fichier sera lu. filename spécifie le fichier dans lequel écrire ou depuis lequel lire. Il peut éventuellement y avoir des espaces entre l'opérateur et le nom du fichier.
Lors d'une lecture de fichier, fournir une valeur text est une erreur.
Lors d'une écriture de fichier, text est écrit dans le fichier. Si text ne se termine pas déjà par un saut de ligne, un saut de ligne final est écrit (même si text est la chaîne vide). Si l'argument text n'est pas donné du tout, rien n'est écrit.
Par exemple, la fonction file peut être utile si votre système de construction a une taille de ligne de commande limitée et que votre recette exécute une commande capable d'accepter ses arguments depuis un fichier. Beaucoup de commandes suivent la convention selon laquelle un argument préfixé d'un @ désigne un fichier contenant d'autres arguments. Vous pourriez alors écrire votre recette de cette façon :
program: $(OBJECTS)
$(file >$@.in,$^)
$(CMD) $(CMDFLAGS) @$@.in
@rm $@.in
Si la commande exige que chaque argument figure sur une ligne distincte du fichier d'entrée, vous pourriez écrire votre recette ainsi :
program: $(OBJECTS)
$(file >$@.in) $(foreach O,$^,$(file >>$@.in,$O))
$(CMD) $(CMDFLAGS) @$@.in
@rm $@.in
La fonction call est unique en ce qu'elle peut servir à créer de nouvelles fonctions paramétrées. Vous pouvez écrire une expression complexe comme valeur d'une variable, puis utiliser call pour la développer avec des valeurs différentes.
La syntaxe de la fonction call est la suivante :
$(call variable,param,param,…)
Lorsque make développe cette fonction, il affecte chaque param à des variables temporaires $(1), $(2), etc. La variable $(0) contient variable. Il n'y a pas de nombre maximal d'arguments de paramètres. Il n'y a pas non plus de minimum, mais il n'y a aucun intérêt à utiliser call sans paramètres.
variable est alors développée comme une variable make dans le contexte de ces affectations temporaires. Ainsi, toute référence à $(1) dans la valeur de variable se résout au premier param de cette invocation de call.
Notez que variable est le nom de la variable, et non une référence à cette variable. Vous n'utiliserez donc normalement ni « $ » ni parenthèses pour l'écrire (vous pouvez toutefois utiliser une référence de variable dans le nom si vous ne voulez pas que le nom soit une constante).
Si variable est le nom d'une fonction intégrée, c'est toujours la fonction intégrée qui est invoquée (même s'il existe par ailleurs une variable make de ce nom).
La fonction call développe les arguments param avant de les affecter aux variables temporaires. Pour cette raison, les valeurs de variable qui contiennent des références à des fonctions intégrées ayant des règles de développement particulières, comme foreach ou if, peuvent ne pas fonctionner comme vous l'attendez.
Quelques exemples rendront cela plus clair.
Cette macro se contente d'inverser ses arguments :
reverse = $(2) $(1) foo = $(call reverse,a,b)
Ici foo contient « b a ».
Celle-ci est un peu plus intéressante : elle définit une macro qui cherche le premier emplacement où un programme se trouve dans PATH :
pathsearch = $(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(PATH))))) LS := $(call pathsearch,ls)
La variable LS contient désormais /bin/ls ou quelque chose d'analogue.
La fonction call peut être imbriquée. Chaque invocation récursive obtient ses propres valeurs locales de $(1), etc., qui masquent les valeurs du call de niveau supérieur. Par exemple, voici une implémentation d'une fonction map :
map = $(foreach a,$(2),$(call $(1),$(a)))
Vous pouvez alors appliquer (map) une fonction qui ne prend normalement qu'un seul argument (par exemple origin) à plusieurs valeurs en une seule étape :
o = $(call map,origin,o map MAKE)
et vous vous retrouvez avec o contenant quelque chose comme « file file default ».
Une dernière mise en garde : soyez prudent lorsque vous ajoutez des espaces aux arguments de call. Comme avec les autres fonctions, toute espace contenue dans le deuxième argument et les suivants est conservée ; cela peut produire d'étranges effets. Lorsque vous passez des paramètres à call, le plus sûr est en général de retirer toute espace superflue.
La fonction value offre un moyen d'utiliser la valeur d'une variable sans la faire développer. Notez bien que cela n'annule pas les développements qui ont déjà eu lieu ; par exemple, si vous créez une variable à expansion simple, sa valeur est développée au moment de la définition ; dans ce cas, la fonction value renverra le même résultat que l'utilisation directe de la variable.
La syntaxe de la fonction value est la suivante :
$(value variable)
Notez que variable est le nom de la variable, et non une référence à cette variable. Vous n'utiliserez donc normalement ni « $ » ni parenthèses pour l'écrire (vous pouvez toutefois utiliser une référence de variable dans le nom si vous ne voulez pas que le nom soit une constante).
Le résultat de cette fonction est une chaîne contenant la valeur de variable telle quelle, sans aucun développement. Par exemple, dans le makefile suivant :
FOO = $PATH
all:
@echo $(FOO)
@echo $(value FOO)
la sortie de la première ligne est ATH. C'est parce que « $P » est développé comme une variable make. La sortie de la deuxième ligne, en revanche, est la valeur actuelle de votre variable d'environnement $PATH. C'est parce que la fonction value a évité le développement.
La fonction value est le plus souvent utilisée en combinaison avec la fonction eval (voir La fonction eval).
La fonction eval est très particulière. Elle vous permet de définir de nouvelles constructions de syntaxe de makefile non constantes — celles qui résultent de l'évaluation d'autres variables et fonctions. L'argument de la fonction eval est d'abord développé, puis le résultat de ce développement est analysé comme de la syntaxe de makefile. Le résultat développé peut définir de nouvelles variables make, des cibles, des règles implicites ou explicites, etc.
Le résultat de la fonction eval est toujours la chaîne vide. On peut donc la placer presque n'importe où dans un makefile sans provoquer d'erreur de syntaxe.
Ce qu'il faut comprendre ici, c'est que l'argument de eval est développé deux fois : une première fois par la fonction eval, puis une seconde fois lorsque le résultat de ce développement est analysé comme syntaxe de makefile, où il est de nouveau développé. Cela signifie qu'en utilisant eval vous pourriez avoir besoin de niveaux d'échappement supplémentaires pour le caractère « $ ». Dans de telles situations, la fonction value (voir La fonction value) peut être utile pour éviter un développement non désiré.
Voici un exemple de la manière dont on peut utiliser eval. Cet exemple combine plusieurs concepts et d'autres fonctions. Il peut sembler exagéré, dans cet exemple, d'utiliser eval plutôt que d'écrire les règles directement, mais considérez deux choses. D'abord, la définition du modèle (dans PROGRAM_template) pourrait avoir besoin d'être bien plus complexe que ce qui est montré ici. Ensuite, vous pourriez placer la partie compliquée et « générique » de cet exemple dans un autre makefile, puis l'inclure dans chacun des makefiles individuels. Ainsi, vos makefiles individuels resteraient bien plus nets.
PROGRAMS = server client
server_OBJS = server.o server_priv.o server_access.o
server_LIBS = priv protocol
client_OBJS = client.o client_api.o client_mem.o
client_LIBS = protocol
# Tout ce qui suit est la partie générique
.PHONY: all
all: $(PROGRAMS)
define PROGRAM_template =
$(1): $$($(1)_OBJS) $$($(1)_LIBS:%=-l%)
ALL_OBJS += $$($(1)_OBJS)
endef
$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog))))
$(PROGRAMS):
$(LINK.o) $^ $(LDLIBS) -o $@
clean:
rm -f $(ALL_OBJS) $(PROGRAMS)
La fonction origin, à la différence de la plupart des autres fonctions, n'opère pas sur la valeur d'une variable. Elle vous renseigne plutôt à propos d'une variable. Plus précisément, elle vous indique d'où celle-ci provient.
La syntaxe de la fonction origin est la suivante :
$(origin variable)
Notez que variable est le nom de la variable interrogée, et non une référence à cette variable. Vous n'utiliserez donc normalement ni « $ » ni parenthèses pour l'écrire (vous pouvez toutefois utiliser une référence de variable dans le nom si vous ne voulez pas que le nom soit une constante).
Le résultat de cette fonction est une chaîne qui vous indique comment la variable variable a été définie :
Si variable n'a jamais été définie.
Si variable possède une définition par défaut, comme c'est le cas pour CC et d'autres. Voir Variables utilisées par les règles implicites. Notez que, si vous redéfinissez une variable par défaut, la fonction origin renvoie l'origine de la définition ultérieure.
Si variable a été héritée de l'environnement fourni à make.
Si variable a été héritée de l'environnement fourni à make, et qu'elle redéfinit le réglage de variable dans le makefile par suite de l'option « -e » (voir Résumé des options).
Si variable a été définie dans un makefile.
Si variable a été définie sur la ligne de commande.
Si variable a été définie par une directive override dans un makefile (voir La directive override).
Si variable est une variable automatique définie pour l'exécution de la recette de chaque règle (voir Variables automatiques).
Ces renseignements servent surtout (curiosité mise à part) à décider si l'on peut faire confiance à la valeur d'une variable. Par exemple, supposons que vous ayez un makefile foo qui inclut un autre makefile bar. Vous voulez qu'une variable bletch soit définie dans bar lorsque vous lancez la commande « make -f bar », même si l'environnement contient une définition de bletch. Cependant, si foo a défini bletch avant d'inclure bar, vous ne voulez pas que cette définition soit redéfinie. Cela pourrait s'obtenir en utilisant une directive override dans foo, donnant à cette définition la priorité sur la définition ultérieure dans bar ; malheureusement, la directive override redéfinirait aussi les définitions de la ligne de commande. Vous pourriez donc inclure dans bar ce qui suit :
ifdef bletch ifeq "$(origin bletch)" "environment" bletch = barf, gag, etc. endif endif
Si bletch a été défini depuis l'environnement, cela le redéfinit.
Si vous vouliez redéfinir bletch lorsque sa définition précédente provient de l'environnement, même sous « -e », vous pourriez écrire à la place :
ifneq "$(findstring environment,$(origin bletch))" "" bletch = barf, gag, etc. endif
Ici, la redéfinition a lieu si « $(origin bletch) » renvoie soit « environment » soit « environment override ». Voir Fonctions de substitution et d'analyse de chaînes.
La fonction flavor, tout comme la fonction origin, n'opère pas sur la valeur d'une variable mais vous renseigne à propos d'une variable. Plus précisément, elle vous indique le type (flavor) de la variable (voir Les deux types de variables).
La syntaxe de la fonction flavor est la suivante :
$(flavor variable)
Notez que variable est le nom de la variable interrogée, et non une référence à cette variable. Vous n'utiliserez donc normalement ni « $ » ni parenthèses pour l'écrire (vous pouvez toutefois utiliser une référence de variable dans le nom si vous ne voulez pas que le nom soit une constante).
Le résultat de cette fonction est une chaîne qui indique le type de la variable variable :
Si variable n'a jamais été définie.
Si variable est une variable à expansion récursive.
Si variable est une variable à expansion simple.
Ces fonctions contrôlent le fonctionnement de make. En général, elles servent à fournir des informations à l'utilisateur du makefile ou à arrêter make lorsqu'une erreur d'environnement quelconque est détectée.
$(error text…)Engendre une erreur fatale dont le message est text. Notez que l'erreur est forcément engendrée au moment où cette fonction est évaluée. Par conséquent, si vous la placez à l'intérieur d'une recette ou dans le membre de droite d'une affectation de variable à expansion récursive, elle n'est évaluée que plus tard. text est développé avant que l'erreur ne soit engendrée.
Par exemple,
ifdef ERROR1 $(error error is $(ERROR1)) endif
engendre une erreur fatale pendant la lecture du makefile si la variable make ERROR1 est définie. Ou bien,
ERR = $(error found an error!) .PHONY: err err: ; $(ERR)
engendre une erreur fatale pendant l'exécution de make si la cible err est invoquée.
$(warning text…)
Cette fonction fonctionne comme la fonction error ci-dessus, à ceci près que make ne se termine pas. À la place, text est développé et le message ainsi obtenu est affiché, mais le traitement du makefile se poursuit.
Le résultat du développement de cette fonction est la chaîne vide.
$(info text…)Cette fonction ne fait rien d'autre qu'afficher ses arguments (développés) sur la sortie standard. Aucun nom de makefile ni numéro de ligne n'est ajouté. Le résultat du développement de cette fonction est la chaîne vide.
La fonction shell est différente de toutes les autres fonctions, à l'exception de la fonction wildcard (voir La fonction wildcard), parce qu'elle communique avec le monde extérieur à make.
La fonction shell offre à make la même fonctionnalité que les accents graves (« ` ») dans la plupart des shells : elle effectue une substitution de commande. Cela signifie qu'elle prend comme argument une commande shell et se développe en la sortie de cette commande. Le seul traitement que make effectue sur ce résultat est de convertir chaque saut de ligne (ou couple retour chariot / saut de ligne) en une seule espace. S'il y a un (retour chariot et) saut de ligne final, il est simplement retiré.
La commande exécutée par un appel à la fonction shell est exécutée au moment où cet appel de fonction est développé (voir Comment make lit un makefile). Comme cette fonction entraîne le lancement d'un nouveau shell, vous devriez bien peser ses implications sur les performances selon que vous utilisez la fonction shell dans une variable à expansion récursive ou dans une variable à expansion simple (voir Les deux types de variables).
L'opérateur d'affectation « != » constitue une alternative à la fonction shell. Il se comporte de façon similaire, avec de subtiles différences (voir Définir des variables). L'opérateur d'affectation « != » figure dans la norme POSIX plus récente.
Après l'utilisation de la fonction shell ou de l'opérateur d'affectation « != », leur code de retour est stocké dans la variable .SHELLSTATUS.
Voici quelques exemples d'utilisation de la fonction shell :
contents := $(shell cat foo)
Cela affecte à contents le contenu du fichier foo, chaque ligne étant séparée par une espace (et non par un saut de ligne).
files := $(shell echo *.c)
Cela affecte à files le résultat du développement de « *.c ». À moins que make n'utilise un shell vraiment singulier, cela donne le même résultat que « $(wildcard *.c) » (du moins tant qu'au moins un fichier « .c » existe).
Toutes les variables marquées pour être exportées (export) sont également transmises au shell que la fonction shell lance. Cela peut entraîner une boucle de développement de variable. Considérez le makefile suivant :
export HI = $(shell echo hi) all: ; @echo $$HI
Lorsque make s'apprête à exécuter la recette, il doit ajouter la variable HI à l'environnement. Pour cela, il doit développer HI. La valeur de cette variable nécessite un appel à la fonction shell, et pour l'appeler il faut construire son environnement. Comme HI est exportée, construire cet environnement nécessite de développer HI. Et cela continuerait indéfiniment. Dans ce cas obscur, plutôt que de tomber dans une boucle ou de signaler une erreur, make utilise la valeur de cette variable telle qu'elle figure dans l'environnement fourni à make, ou la chaîne vide à défaut. C'est généralement ce que vous voulez. Par exemple :
export PATH = $(shell echo /usr/local/bin:$$PATH)
Cela dit, dans ce cas, il serait plus simple et plus efficace d'utiliser dès le départ une variable à expansion simple (« := »).
Si GNU make a été compilé avec la prise en charge de GNU Guile comme langage d'extension intégré, alors la fonction guile est disponible. La fonction guile prend un seul argument, qui est d'abord développé normalement par make, puis passé à l'évaluateur de GNU Guile. Le résultat de l'évaluateur est converti en chaîne et utilisé comme résultat du développement de la fonction guile dans le makefile. Pour plus de détails sur la manière d'écrire des extensions de make en Guile, voir Intégration de GNU Guile.
Vous pouvez déterminer si la prise en charge de GNU Guile est disponible en cherchant le mot « guile » dans la variable .FEATURES.