Les informations qui indiquent à make comment recompiler un système proviennent de la lecture d’une base de données appelée makefile.
MAKEFILESmake lit un makefileUn makefile contient cinq sortes de choses : des règles explicites, des règles implicites, des définitions de variables, des directives et des commentaires. Les règles, les variables et les directives sont décrites en détail dans des chapitres ultérieurs.
objects comme la liste de tous les fichiers objets (voir Les variables simplifient les makefiles).
make de faire quelque chose de particulier pendant la lecture du makefile. On y trouve notamment :
# littéral, échappez-le par une barre oblique inverse (par exemple \#). Les commentaires peuvent apparaître sur n’importe quelle ligne du makefile, bien qu’ils soient traités de façon particulière dans certaines situations.
On ne peut pas utiliser de commentaires à l’intérieur des références de variables ou des appels de fonctions : toute occurrence de # y est traitée littéralement (et non comme le début d’un commentaire) au sein d’une référence de variable ou d’un appel de fonction.
Les commentaires figurant dans une recette sont transmis au shell, comme tout autre texte de recette. C’est le shell qui décide comment les interpréter : qu’il s’agisse ou non d’un commentaire dépend du shell.
À l’intérieur d’une directive define, les commentaires ne sont pas ignorés pendant la définition de la variable ; ils sont au contraire conservés intacts dans la valeur de la variable. Lorsque la variable est développée, ils seront traités soit comme des commentaires make, soit comme du texte de recette, selon le contexte dans lequel la variable est évaluée.
Les makefiles utilisent une syntaxe « orientée ligne » dans laquelle le caractère de saut de ligne est spécial et marque la fin d’une instruction. GNU make n’impose aucune limite à la longueur d’une ligne d’instruction, jusqu’à concurrence de la mémoire disponible sur votre ordinateur.
Cependant, il est difficile de lire des lignes trop longues pour être affichées sans retour à la ligne ni défilement. Vous pouvez donc mettre en forme vos makefiles pour une meilleure lisibilité en insérant des sauts de ligne au milieu d’une instruction : cela se fait en échappant les sauts de ligne internes par une barre oblique inverse (\). Lorsqu’une distinction est nécessaire, nous appellerons « ligne physique » une ligne unique se terminant par un saut de ligne (qu’il soit échappé ou non), et « ligne logique » une instruction complète incluant tous les sauts de ligne échappés jusqu’au premier saut de ligne non échappé.
La manière dont les combinaisons barre oblique inverse/saut de ligne sont traitées dépend du fait que l’instruction soit une ligne de recette ou une ligne hors recette. Le traitement de barre oblique inverse/saut de ligne dans une ligne de recette est abordé plus loin (voir Diviser les lignes de recette).
En dehors des lignes de recette, les combinaisons barre oblique inverse/saut de ligne sont converties en un seul caractère d’espace. Cela fait, tous les espaces blancs autour de la barre oblique inverse/saut de ligne sont réduits à un seul espace : cela inclut tous les espaces précédant la barre oblique inverse, tous les espaces en début de la ligne suivant la barre oblique inverse/saut de ligne, ainsi que toute combinaison consécutive de barre oblique inverse/saut de ligne.
Si la cible spéciale .POSIX est définie, le traitement de barre oblique inverse/saut de ligne est légèrement modifié pour se conformer à POSIX.2 : premièrement, les espaces précédant une barre oblique inverse ne sont pas supprimés ; deuxièmement, les combinaisons consécutives de barre oblique inverse/saut de ligne ne sont pas réduites.
Si vous devez diviser une ligne mais que vous ne voulez pas ajouter d’espace, vous pouvez recourir à une astuce subtile : remplacez votre couple barre oblique inverse/saut de ligne par les trois caractères signe dollar, barre oblique inverse et saut de ligne :
var := one$\
word
Une fois que make a retiré la barre oblique inverse/saut de ligne et réduit la ligne suivante à un seul espace, cela équivaut à :
var := one$ word
Ensuite make effectue l’expansion des variables. La référence de variable « $ » fait référence à une variable dont le nom est le caractère unique « » (espace), qui n’existe pas ; elle est donc développée en chaîne vide, ce qui donne une affectation finale équivalente à :
var := oneword
Par défaut, lorsque make cherche le makefile, il essaie les noms suivants, dans cet ordre : GNUmakefile, makefile, puis Makefile.
Normalement, vous devriez nommer votre makefile soit makefile, soit Makefile. (Nous recommandons Makefile parce qu’il apparaît bien en vue près du début d’un listage de répertoire, juste à côté d’autres fichiers importants comme README.) Le premier nom essayé, GNUmakefile, n’est pas recommandé pour la plupart des makefiles. Vous ne devriez utiliser ce nom que si vous avez un makefile spécifique à GNU make, qui ne sera pas compris par d’autres versions de make. Les autres programmes make cherchent makefile et Makefile, mais pas GNUmakefile.
Si make ne trouve aucun de ces noms, il n’utilise aucun makefile. Vous devez alors spécifier un but à l’aide d’un argument de commande, et make tentera de déterminer comment le refaire en n’utilisant que ses règles implicites intégrées. Voir Utiliser les règles implicites.
Si vous voulez utiliser un nom non standard pour votre makefile, vous pouvez le spécifier avec l’option « -f » ou « --file ». Les arguments « -f name » ou « --file=name » indiquent à make de lire le fichier name comme makefile. Si vous utilisez plusieurs fois l’option « -f » ou « --file », vous pouvez spécifier plusieurs makefiles. Tous les makefiles sont, en pratique, concaténés dans l’ordre spécifié. Les noms de makefile par défaut GNUmakefile, makefile et Makefile ne sont pas recherchés automatiquement si vous spécifiez « -f » ou « --file ».
La directive include indique à make de suspendre la lecture du makefile courant et de lire un ou plusieurs autres makefiles avant de continuer. Cette directive est une ligne du makefile qui ressemble à ceci :
include filenames…
filenames peut contenir des motifs de noms de fichiers du shell. Si filenames est vide, rien n’est inclus et aucune erreur n’est affichée.
Des espaces supplémentaires sont autorisés et ignorés en début de ligne, mais le premier caractère ne doit pas être une tabulation (ou la valeur de .RECIPEPREFIX) : si la ligne commence par une tabulation, elle sera considérée comme une ligne de recette. Un espace blanc est requis entre include et les noms de fichiers, ainsi qu’entre les noms de fichiers ; tout espace supplémentaire y est ignoré, de même qu’à la fin de la directive. Un commentaire commençant par « # » est autorisé en fin de ligne. Si les noms de fichiers contiennent des références de variables ou de fonctions, elles sont développées. Voir Comment utiliser les variables.
Par exemple, si vous avez trois fichiers .mk, a.mk, b.mk et c.mk, et que $(bar) est développé en bish bash, alors l’expression suivante
include foo *.mk $(bar)
équivaut à
include foo a.mk b.mk c.mk bish bash
Lorsque make traite une directive include, il suspend la lecture du makefile qui la contient et lit tour à tour chaque fichier énuméré. Une fois cela terminé, make reprend la lecture du makefile dans lequel la directive est apparue.
Une situation où l’on utilise les directives include est lorsque plusieurs programmes, gérés par des makefiles distincts dans divers répertoires, ont besoin d’utiliser un ensemble commun de définitions de variables (voir Affecter des variables) ou de règles de motif (voir Définir et redéfinir des règles de motif).
Une autre situation est lorsque vous voulez générer automatiquement les prérequis à partir des fichiers sources ; les prérequis peuvent être placés dans un fichier inclus par le makefile principal. Cette pratique est en général plus propre que celle qui consiste à ajouter d’une manière ou d’une autre les prérequis à la fin du makefile principal, comme cela se faisait traditionnellement avec d’autres versions de make. Voir Générer les prérequis automatiquement.
Si le nom spécifié ne commence pas par une barre oblique (ou par une lettre de lecteur suivie d’un deux-points lorsque GNU Make est compilé avec la prise en charge des chemins MS-DOS / MS-Windows) et que le fichier n’est pas trouvé dans le répertoire courant, plusieurs autres répertoires sont parcourus. D’abord, tous les répertoires que vous avez spécifiés avec les options « -I » ou « --include-dir » sont parcourus (voir Résumé des options). Ensuite, les répertoires suivants (s’ils existent) sont parcourus, dans cet ordre : prefix/include (normalement /usr/local/include 1), /usr/gnu/include, /usr/local/include, /usr/include.
La variable .INCLUDE_DIRS contient la liste actuelle des répertoires dans lesquels make recherche les fichiers inclus. Voir Autres variables spéciales.
Vous pouvez éviter la recherche dans ces répertoires par défaut en ajoutant à la ligne de commande l’option -I avec la valeur spéciale - (par exemple -I-). Cela fait oublier à make tous les répertoires d’inclusion déjà définis, y compris les répertoires par défaut.
Si un makefile inclus ne peut être trouvé dans aucun de ces répertoires, ce n’est pas une erreur immédiatement fatale ; le traitement du makefile contenant le include se poursuit. Une fois la lecture des makefiles terminée, make tentera de refaire tout makefile périmé ou inexistant. Voir Comment les makefiles sont refaits. Ce n’est qu’après avoir échoué à trouver une règle pour refaire le makefile, ou bien après avoir trouvé une règle dont la recette a échoué, que make signalera le makefile manquant comme une erreur fatale.
Si vous voulez que make ignore simplement un makefile qui n’existe pas ou ne peut être refait, sans message d’erreur, utilisez la directive -include au lieu de include, comme ceci :
-include filenames…
Cette directive se comporte en tout point comme include, à ceci près qu’il n’y a aucune erreur (pas même un avertissement) si l’un des filenames (ou l’un des prérequis de l’un des filenames) n’existe pas ou ne peut être refait.
Pour la compatibilité avec certaines autres implémentations de make, sinclude est un autre nom pour -include.
MAKEFILES
Si la variable d’environnement MAKEFILES est définie, make considère sa valeur comme une liste de noms (séparés par des espaces blancs) de makefiles additionnels à lire avant les autres. Cela fonctionne à peu près comme la directive include : divers répertoires sont parcourus à la recherche de ces fichiers (voir Inclure d’autres makefiles). De plus, le but par défaut n’est jamais pris dans l’un de ces makefiles (ni dans un makefile inclus par eux) et ce n’est pas une erreur si les fichiers énumérés dans MAKEFILES ne sont pas trouvés.
L’usage principal de MAKEFILES est la communication entre invocations récursives de make (voir Utilisation récursive de make). Il n’est généralement pas souhaitable de définir cette variable d’environnement avant une invocation de make de niveau supérieur, car il vaut généralement mieux ne pas toucher à un makefile depuis l’extérieur. Cependant, si vous exécutez make sans makefile particulier, un makefile dans MAKEFILES peut faire des choses utiles pour aider les règles implicites intégrées à mieux fonctionner, comme définir des chemins de recherche (voir Recherche de répertoires pour les prérequis).
Certains utilisateurs sont tentés de définir automatiquement MAKEFILES dans l’environnement à la connexion, et d’écrire leurs makefiles en supposant que c’est le cas. C’est une très mauvaise idée, car de tels makefiles cesseront de fonctionner s’ils sont exécutés par quelqu’un d’autre. Il vaut bien mieux écrire des directives include explicites dans les makefiles. Voir Inclure d’autres makefiles.
Il arrive que les makefiles puissent être refaits à partir d’autres fichiers, comme des fichiers RCS ou SCCS. Si un makefile peut être refait à partir d’autres fichiers, vous voulez probablement que make dispose d’une version à jour du makefile à lire.
À cette fin, après avoir lu tous les makefiles, make considère chacun d’eux comme une cible-but, dans l’ordre où ils ont été traités, et tente de le mettre à jour. Si les constructions parallèles (voir Exécution parallèle) sont activées, les makefiles sont eux aussi reconstruits en parallèle.
Si un makefile possède une règle indiquant comment le mettre à jour (qu’elle se trouve dans ce makefile même ou dans un autre), ou si une règle implicite lui est applicable (voir Utiliser les règles implicites), il sera mis à jour si nécessaire. Après que tous les makefiles ont été vérifiés, si l’un d’eux a effectivement changé, make repart de zéro et relit tous les makefiles depuis le début. (Il tentera aussi de mettre à jour chacun d’eux à nouveau, mais normalement cela ne les modifiera plus, puisqu’ils sont déjà à jour.) Chaque redémarrage met à jour la variable spéciale MAKE_RESTARTS (voir Autres variables spéciales).
Si vous savez qu’un ou plusieurs de vos makefiles ne peuvent être refaits et que vous voulez empêcher make d’y effectuer une recherche de règle implicite, peut-être pour des raisons d’efficacité, vous pouvez recourir à n’importe quelle méthode habituelle pour empêcher la recherche de règle implicite. Par exemple, vous pouvez écrire une règle explicite ayant le makefile pour cible et une recette vide (voir Utiliser des recettes vides).
Si les makefiles spécifient une règle à double deux-points pour refaire un fichier avec une recette mais sans prérequis, ce fichier sera toujours refait (voir Règles à double deux-points). Dans le cas des makefiles, un makefile qui possède une règle à double deux-points avec une recette mais sans prérequis sera refait à chaque exécution de make, puis de nouveau après que make a tout recommencé et relu les makefiles. Cela provoquerait une boucle infinie : make referait constamment le makefile et redémarrerait, sans jamais rien faire d’autre. Aussi, pour éviter cela, make ne tentera pas de refaire les makefiles spécifiés comme cibles d’une règle à double deux-points avec une recette mais sans prérequis.
Les cibles factices (phony) (voir Cibles factices) ont le même effet : elles ne sont jamais considérées comme à jour, de sorte qu’un fichier inclus marqué comme factice ferait redémarrer make sans cesse. Pour éviter cela, make ne tentera pas de refaire les makefiles marqués comme factices.
Vous pouvez tirer parti de cela pour optimiser le temps de démarrage : si vous savez que votre Makefile n’a pas besoin d’être refait, vous pouvez empêcher make d’essayer de le refaire en ajoutant l’une des deux lignes suivantes :
.PHONY: Makefile
ou :
Makefile:: ;
Si vous ne spécifiez aucun makefile à lire avec les options « -f » ou « --file », make essaiera les noms de makefile par défaut ; voir Quel nom donner à votre makefile. Contrairement aux makefiles explicitement demandés avec les options « -f » ou « --file », make n’est pas certain que ces makefiles doivent exister. Cependant, si un makefile par défaut n’existe pas mais peut être créé en exécutant des règles make, vous voulez probablement que les règles soient exécutées afin que le makefile puisse être utilisé.
Par conséquent, si aucun des makefiles par défaut n’existe, make tentera de fabriquer chacun d’eux jusqu’à ce qu’il réussisse à en fabriquer un, ou qu’il n’ait plus de noms à essayer. Notez que ce n’est pas une erreur si make ne peut trouver ni fabriquer aucun makefile ; un makefile n’est pas toujours nécessaire.
Lorsque vous utilisez l’option « -t » ou « --touch » (voir Au lieu d’exécuter les recettes), vous ne voudriez pas vous appuyer sur un makefile périmé pour décider quelles cibles toucher. Ainsi, l’option « -t » n’a aucun effet sur la mise à jour des makefiles ; ils sont réellement mis à jour même si « -t » est spécifié. De même, « -q » (ou « --question ») et « -n » (ou « --just-print ») n’empêchent pas la mise à jour des makefiles, parce qu’un makefile périmé donnerait une sortie incorrecte pour les autres cibles. Ainsi, « make -f mfile -n foo » met à jour mfile, le lit, puis affiche, sans l’exécuter, la recette pour mettre à jour foo et ses prérequis. La recette affichée pour foo sera celle spécifiée par le contenu mis à jour de mfile.
Cependant, il se peut que, à l’occasion, vous souhaitiez réellement empêcher la mise à jour des makefiles eux-mêmes. Vous pouvez le faire en spécifiant les makefiles comme buts sur la ligne de commande, en plus de les spécifier comme makefiles. Lorsque le nom du makefile est explicitement spécifié comme but, les options « -t » et autres s’y appliquent.
Ainsi, « make -f mfile -n mfile foo » lirait le makefile mfile, afficherait sans l’exécuter la recette nécessaire pour le mettre à jour, puis afficherait sans l’exécuter la recette nécessaire pour mettre à jour foo. La recette pour foo sera celle spécifiée par le contenu existant de mfile.
Il est parfois utile d’avoir un makefile presque identique à un autre makefile. Souvent, vous pouvez utiliser la directive « include » pour inclure l’un dans l’autre, et ajouter d’autres cibles ou définitions de variables. Cependant, il est interdit que deux makefiles donnent des recettes différentes pour la même cible. Mais il existe un autre moyen.
Dans le makefile contenant (celui qui veut inclure l’autre), vous pouvez utiliser une règle de motif « qui correspond à tout » pour indiquer que, pour refaire toute cible ne pouvant être fabriquée à partir des informations du makefile contenant, make doit consulter un autre makefile. Pour plus d’informations sur les règles de motif, voir Définir et redéfinir des règles de motif.
Par exemple, si vous avez un makefile appelé Makefile qui indique comment fabriquer la cible « foo » (et d’autres cibles), vous pouvez écrire un makefile appelé GNUmakefile contenant :
foo:
frobnicate > foo
%: force
@$(MAKE) -f Makefile $@
force: ;
Si vous tapez « make foo », make trouve GNUmakefile, le lit, et voit que pour fabriquer foo il doit exécuter la recette « frobnicate > foo ». Si vous tapez « make bar », make ne trouve aucun moyen de fabriquer bar dans GNUmakefile, il utilise donc la recette de la règle de motif : « make -f Makefile bar ». Si Makefile fournit une règle pour mettre à jour bar, make appliquera cette règle. Et de même pour toute autre cible dont GNUmakefile n’indique pas comment la fabriquer.
Cela fonctionne parce que le motif de la règle de motif est simplement « % », qui correspond donc à n’importe quelle cible. La règle spécifie un prérequis force, afin de garantir que la recette sera exécutée même si le fichier cible existe déjà. Nous donnons à la cible force une recette vide pour empêcher make de chercher une règle implicite pour la fabriquer ; sinon, il appliquerait la même règle « qui correspond à tout » à force elle-même et créerait une boucle de prérequis !
make lit un makefile
GNU make effectue son travail en deux phases distinctes. Pendant la première phase, il lit tous les makefiles, les makefiles inclus, etc., et internalise toutes les variables et leurs valeurs ainsi que les règles implicites et explicites, et construit un graphe de dépendances de toutes les cibles et de leurs prérequis. Pendant la seconde phase, make utilise ces données internalisées pour déterminer quelles cibles doivent être mises à jour et exécute les recettes nécessaires à leur mise à jour.
Il est important de comprendre cette approche en deux phases, car elle a un impact direct sur le moment où se produit l’expansion des variables et des fonctions ; c’est souvent une source de confusion lors de l’écriture des makefiles. Vous trouverez ci-dessous un résumé des différentes constructions pouvant apparaître dans un makefile, et de la phase au cours de laquelle l’expansion se produit pour chaque partie de la construction.
On dit qu’une expansion est immédiate (immediate) si elle se produit pendant la première phase : make développe cette partie de la construction au fur et à mesure que le makefile est analysé. On dit qu’une expansion est différée (deferred) si elle n’est pas immédiate. L’expansion d’une partie de construction différée est repoussée jusqu’à ce que l’expansion soit utilisée : soit lorsqu’elle est référencée dans un contexte immédiat, soit lorsqu’elle est requise pendant la seconde phase.
Vous n’êtes peut-être pas encore familier de certaines de ces constructions. Vous pourrez vous reporter à cette section à mesure que vous vous familiariserez avec elles, dans les chapitres ultérieurs.
Les définitions de variables sont analysées comme suit :
immediate = deferred immediate ?= deferred immediate := immediate immediate ::= immediate immediate :::= immediate-with-escape immediate += deferred or immediate immediate != immediate define immediate deferred endef define immediate = deferred endef define immediate ?= deferred endef define immediate := immediate endef define immediate ::= immediate endef define immediate :::= immediate-with-escape endef define immediate += deferred or immediate endef define immediate != immediate endef
Pour l’opérateur d’ajout « += », le membre de droite est considéré comme immédiat si la variable avait été précédemment définie comme une variable simple (« := » ou « ::= »), et comme différé sinon.
Pour l’opérateur immédiat-avec-échappement « :::= », la valeur du membre de droite est développée immédiatement, puis échappée (c’est-à-dire que toutes les occurrences de $ dans le résultat de l’expansion sont remplacées par $$).
Pour l’opérateur d’affectation par le shell « != », le membre de droite est évalué immédiatement et transmis au shell. Le résultat est stocké dans la variable nommée à gauche, et cette variable est considérée comme une variable à expansion récursive (et sera donc réévaluée à chaque référence).
Les directives conditionnelles sont analysées immédiatement. Cela signifie par exemple que l’on ne peut pas utiliser de variables automatiques dans les directives conditionnelles, car les variables automatiques ne sont définies qu’au moment où la recette de cette règle est invoquée. Si vous devez utiliser des variables automatiques dans une directive conditionnelle, vous devez déplacer la condition dans la recette et utiliser à la place la syntaxe conditionnelle du shell.
Une règle est toujours développée de la même manière, quelle que soit sa forme :
immediate : immediate ; deferred
deferred
Autrement dit, les parties cible et prérequis sont développées immédiatement, tandis que la recette utilisée pour construire la cible est toujours différée. Cela vaut pour les règles explicites, les règles de motif, les règles de suffixe, les règles de motif statiques et les simples définitions de prérequis.
GNU make analyse les makefiles ligne par ligne. L’analyse procède selon les étapes suivantes :
make lit un makefile).
Une conséquence importante en découle : une macro peut être développée en une règle entière, si elle tient sur une seule ligne. L’exemple suivant fonctionne :
myrule = target : ; echo built $(myrule)
En revanche, l’exemple suivant ne fonctionne pas, car make ne redécoupe pas les lignes après les avoir développées :
define myrule
target:
echo built
endef
$(myrule)
Le makefile ci-dessus aboutit à la définition d’une cible « target » ayant pour prérequis « echo » et « built », comme si le makefile contenait target: echo built, plutôt qu’à une règle avec une recette. Les sauts de ligne encore présents dans une ligne une fois l’expansion terminée sont ignorés comme des espaces blancs ordinaires.
Pour développer correctement une macro multi-lignes, vous devez utiliser la fonction eval : elle fait exécuter l’analyseur de make sur le résultat de la macro développée (voir La fonction eval).
Nous avons appris précédemment que GNU make fonctionne en deux phases distinctes : une phase de lecture et une phase de mise à jour des cibles (voir Comment make lit un makefile). GNU Make a aussi la capacité d’activer une seconde expansion des prérequis (uniquement) pour une partie ou la totalité des cibles définies dans le makefile. Pour que cette seconde expansion ait lieu, la cible spéciale .SECONDEXPANSION doit être définie avant la première liste de prérequis qui fait usage de cette fonctionnalité.
Si .SECONDEXPANSION est définie, alors lorsque GNU make a besoin de vérifier les prérequis d’une cible, ces prérequis sont développés une seconde fois. Dans la plupart des cas, cette expansion secondaire n’a aucun effet, puisque toutes les références de variables et de fonctions auront été développées lors de l’analyse initiale des makefiles. Pour tirer parti de la phase d’expansion secondaire de l’analyseur, il faut donc échapper la référence de variable ou de fonction dans le makefile. Dans ce cas, la première expansion ne fait que dés-échapper la référence sans la développer, et l’expansion est laissée à la phase d’expansion secondaire. Par exemple, considérez ce makefile :
.SECONDEXPANSION: ONEVAR = onefile TWOVAR = twofile myfile: $(ONEVAR) $$(TWOVAR)
Après la première phase d’expansion, la liste de prérequis de la cible myfile sera onefile et $(TWOVAR) ; la première référence de variable (non échappée) à ONEVAR est développée, tandis que la seconde référence de variable (échappée) est simplement dés-échappée, sans être reconnue comme une référence de variable. Puis, pendant l’expansion secondaire, le premier mot est de nouveau développé, mais comme il ne contient aucune référence de variable ni de fonction, il reste la valeur onefile, tandis que le second mot est désormais une référence normale à la variable TWOVAR, qui est développée en la valeur twofile. Le résultat final est qu’il y a deux prérequis, onefile et twofile.
De toute évidence, ce cas n’est pas très intéressant, puisque le même résultat aurait pu être obtenu plus facilement en faisant simplement figurer les deux variables, non échappées, dans la liste de prérequis. Une différence apparaît si les variables sont redéfinies ; considérez cet exemple :
.SECONDEXPANSION: AVAR = top onefile: $(AVAR) twofile: $$(AVAR) AVAR = bottom
Ici, le prérequis de onefile est développé immédiatement et se résout à la valeur top, tandis que le prérequis de twofile n’est pas complètement développé avant l’expansion secondaire et donne la valeur bottom.
C’est un peu plus intéressant, mais la véritable puissance de cette fonctionnalité n’apparaît que lorsqu’on découvre que les expansions secondaires ont toujours lieu dans la portée des variables automatiques de cette cible. Cela signifie que vous pouvez utiliser des variables telles que $@, $*, etc. pendant la seconde expansion, et qu’elles auront les valeurs attendues, exactement comme dans la recette. Tout ce que vous avez à faire est de différer l’expansion en échappant le $. De plus, l’expansion secondaire se produit aussi bien pour les règles explicites que pour les règles implicites (de motif). Sachant cela, les usages possibles de cette fonctionnalité augmentent considérablement. Par exemple :
.SECONDEXPANSION: main_OBJS := main.o try.o test.o lib_OBJS := lib.o api.o main lib: $$($$@_OBJS)
Ici, après l’expansion initiale, les prérequis des cibles main et lib seront tous deux $($@_OBJS). Pendant l’expansion secondaire, la variable $@ est définie au nom de la cible, de sorte que l’expansion pour la cible main donnera $(main_OBJS), soit main.o try.o test.o, tandis que l’expansion secondaire pour la cible lib donnera $(lib_OBJS), soit lib.o api.o.
Vous pouvez aussi y mêler des fonctions, à condition qu’elles soient correctement échappées :
main_SRCS := main.c try.c test.c lib_SRCS := lib.c api.c .SECONDEXPANSION: main lib: $$(patsubst %.c,%.o,$$($$@_SRCS))
Cette version permet aux utilisateurs de spécifier des fichiers sources plutôt que des fichiers objets, mais donne la même liste de prérequis résultante que l’exemple précédent.
L’évaluation des variables automatiques pendant la phase d’expansion secondaire, en particulier celle de la variable du nom de cible $$@, se comporte de manière similaire à l’évaluation au sein des recettes. Il existe toutefois quelques différences subtiles et « cas particuliers » qui entrent en jeu pour les différents types de définitions de règles que make reconnaît. Les subtilités liées à l’utilisation des différentes variables automatiques sont décrites ci-dessous.
Lors de l’expansion secondaire des règles explicites, $$@ et $$% s’évaluent respectivement en le nom de fichier de la cible et, lorsque la cible est un membre d’archive, en le nom du membre cible. La variable $$< s’évalue en le premier prérequis de la première règle pour cette cible. $$^ et $$+ s’évaluent en la liste de tous les prérequis des règles déjà apparues pour la même cible ($$+ avec répétitions et $$^ sans). L’exemple suivant aidera à illustrer ces comportements :
.SECONDEXPANSION: foo: foo.1 bar.1 $$< $$^ $$+ # ligne #1 foo: foo.2 bar.2 $$< $$^ $$+ # ligne #2 foo: foo.3 bar.3 $$< $$^ $$+ # ligne #3
Dans la première liste de prérequis, les trois variables ($$<, $$^ et $$+) se développent toutes en la chaîne vide. Dans la deuxième, elles auront pour valeurs respectives foo.1, foo.1 bar.1 et foo.1 bar.1. Dans la troisième, elles auront pour valeurs respectives foo.1, foo.1 bar.1 foo.2 bar.2 et foo.1 bar.1 foo.2 bar.2 foo.1 foo.1 bar.1 foo.1 bar.1.
Les règles subissent l’expansion secondaire dans l’ordre du makefile, à ceci près que la règle portant la recette est toujours évaluée en dernier.
Les variables $$? et $$* ne sont pas disponibles et se développent en la chaîne vide.
Les règles d’expansion secondaire des règles de motif statiques sont identiques à celles des règles explicites ci-dessus, à une exception près : pour les règles de motif statiques, la variable $$* est définie au radical (stem) du motif. Comme pour les règles explicites, $$? n’est pas disponible et se développe en la chaîne vide.
Lorsque make recherche une règle implicite, il substitue le radical (stem) puis effectue l’expansion secondaire pour toute règle dont le motif de cible correspond. La valeur des variables automatiques est dérivée de la même façon que pour les règles de motif statiques. Par exemple :
.SECONDEXPANSION: foo: bar foo foz: fo%: bo% %oo: $$< $$^ $$+ $$*
Lorsque cette règle implicite est essayée pour la cible foo, $$< se développe en bar, $$^ se développe en bar boo, $$+ se développe également en bar boo, et $$* se développe en f.
Notez que le préfixe de répertoire (D), tel que décrit dans Algorithme de recherche des règles implicites, est ajouté (après expansion) à tous les motifs de la liste de prérequis. Par exemple :
.SECONDEXPANSION:
/tmp/foo.o:
%.o: $$(addsuffix /%.c,foo bar) foo.h
@echo $^
La liste de prérequis affichée, après l’expansion secondaire et la reconstruction du préfixe de répertoire, sera /tmp/foo/foo.c /tmp/bar/foo.c foo.h. Si cette reconstruction ne vous intéresse pas, vous pouvez utiliser $$* au lieu de % dans la liste de prérequis.