Initialisation et exportation de variables Makefile

somevar := apple
export somevar
update := $(shell echo "v=$$somevar")

all:
    @echo $(update)

J'espérais apple comme sortie de commande, mais il est vide, ce qui me fait penser à l'exportation et à l'expansion variable := se déroulant sur différentes phases. comment surmonter cela?

27
demandé sur Pablo 2010-05-15 06:46:45

4 réponses

Le problème est que export exporte la variable vers les sous-shell utilisés par les commandes; elle n'est pas disponible pour l'expansion dans d'autres affectations. Alors n'essayez pas de l'obtenir de l'environnement en dehors d'une règle.

somevar := apple
export somevar

update1 := $(shell perl -e 'print "method 1 $$ENV{somevar}\n"')
# Make runs the shell command, the shell does not know somevar, so update1 is "method 1 ".

update2 := perl -e 'print "method 2 $$ENV{somevar}\n"'
# Now update2 is perl -e 'print "method 2 $$ENV{somevar}\n"'

# Lest we forget:
update3 := method 3 $(somevar)

all:
    echo $(update1)
    $(update2)
    echo $(update3)
    perl -e 'print method 4 "$$ENV{somevar}\n"'
26
répondu Beta 2018-01-12 12:45:25

@la réponse de Beta contient le pointeur crucial: avec GNU make, les variables marquées avec export ne sont disponibles que pour [les shells lancés pour] commandes de recette (qui font partie des règles), malheureusement PAS aux invocations de $(shell ...) (ils ne voient que l'environnement que make lui-même a vu quand il a été lancé).

Il existe une solution de contournement , cependant: transmet explicitement la variable exportée en tant qu'environnement variable à la fonction shell:

update := $(shell somevar='$(somevar)' perl -e 'print "$$ENV{somevar}"')

En ajoutant la commande shell à <var>=<val>, cette définition est ajoutée en tant que variable d'environnement à l'environnement que la commande voit - il s'agit d'une fonctionnalité shell Générique.

Mise en garde: @Kaz souligne dans un commentaire que cette méthode se comporte mal si $(somevar) contient certains caractères., parce que l'expansion de la variable est verbatim (pas d'échappement), ce qui peut casser la commande shell résultante, et suggère ce qui suit variante pour fonctionner également avec les instances ' intégrées (décompose la valeur d'entrée en sous-chaînes entre guillemets simples avec guillemets ' spliced in):

update := $(shell somevar='$(subst ','\'',$(somevar))' perl -e 'print "$$ENV{somevar}"')

Cela devrait fonctionner avec toutes les valeurs sauf multi-lignes (qui sont rares; il n'y a pas de solution de contournement pour les valeurs multi-lignes que je connais).

Sur une note de côté, caractères littéraux $. dans les valeurs doivent être représentées comme $$, sinon make les interprétera comme des références à ses propres variable.

Notez que je n'ai délibérément pas choisi l'instruction original de L'OP, update := $(shell echo "v=$$somevar"), pour la démonstration, car elle contient un piège qui embrouille le problème: en raison de la façon dont le shell évalue une ligne de commande, somevar=apple echo v=$somevar n'évalue pas à v=apple, car la référence $somevar est développée avant somevar=apple prend effet. Pour obtenir l'effet désiré dans ce cas, vous auriez à utiliser 2 déclarations: update := $(shell export somevar="$(somevar)"; echo "v=$$somevar")


Quant à la bug-vs.-débat de fonctionnalités:

Bien que l'on puisse faire valoir que la fonction shell devrait voir le même environnement que les commandes de recette, la documentation ne fait pas une telle promesse-voir http://www.gnu.org/software/make/manual/make.html#Shell-Function . inversement, http://www.gnu.org/software/make/manual/make.html#Variables_002fRecursion ne mentionne que la mise à disposition des variables exportées dans les commandes recipe.

11
répondu mklement0 2017-05-23 11:47:17

Exécution du makefile

foo:=apple
export foo
all:
        @echo ">"$(shell echo "$$foo")
        @echo ">""$$foo"

Donne pour moi (avec foo undefined dans l'environnement)

$ make
>
>apple

$ make foo=bar
>
>apple

$ export foo=bar; make
>bar
>apple

$ export foo=bar; make foo=bar
>bar
>bar

Essayez d'utiliser le formulaire entre guillemets (update := "v=$$somevar") et laissez le shell gérer l'expansion lorsqu'une commande est exécutée (vous aurez toujours besoin de l'exportation)

9
répondu Scott Wales 2010-05-15 03:05:04

Bien que export ne joue pas bien avec $(shell ...), il existe une solution simple. Nous pouvons passer les données au script shell via la ligne de commande.

Maintenant, bien sûr, le passage de l'environnement est robuste contre les problèmes d'échappement et de citation. Cependant, le langage shell a une méthode de citation unique '...' qui gère tout. Le seul problème est qu'il n'y a aucun moyen d'obtenir une seule citation; mais bien sûr, cela est résolu en terminant la citation, en échappant à la barre oblique inverse citation unique nécessaire et commencer une nouvelle citation: en d'autres termes:

ab'cd -> 'ab'\''cd'

Dans le script shell exécuté par $(shell ...), nous générons simplement une affectation de variable de la forme var='$(...)', où $(...) est une expression de marque qui interpole le matériau correctement échappé. Ainsi, Makefile:

somevar := apple with 'quoted' "stuff" and dollar $$signs

shell_escape = $(subst ','\'',$(1))

update := $(shell v='$(call shell_escape,$(somevar))'; echo $$v > file.txt)

.phony: all

all:
    cat file.txt

Exemple d'exécution:

$ make
cat file.txt
apple with 'quoted' "stuff" and dollar $signs

Si nous voulons communiquer une variable d'environnement à une commande, nous pouvons le faire en utilisant la syntaxe shell VAR0=val0 VAR1=val1 ... VARn=valn command arg .... Cela peut être illustré par quelques modifications mineures à la au-dessus de Makefile:

somevar := apple with 'quoted' "stuff" and dollar $$signs

shell_escape = $(subst ','\'',$(1))

update := $(shell somevar='$(call shell_escape,$(somevar))' env > file.txt)

.phony: all

all:
        grep somevar file.txt

Exécuter:

$ make
grep somevar file.txt
somevar=apple with 'quoted' "stuff" and dollar $signs

file.txt contient un vidage de variables d'environnement, où l'on peut voir somevar. Si export dans GNU Make avait fait la bonne chose, nous aurions pu faire:

export somevar
update := $(shell env > file.txt)

, Mais le résultat final est le même.

Puisque le résultat final que vous voulez est de echo $(update), Vous shell_escape de toute façon, même si GNU Make a passé des variables exportées à $(shell ...). C'est-à-dire, regardez un de plus Makefile:

somevar := apple with 'quoted' "stuff" and dollar $$signs

shell_escape = $(subst ','\'',$(1))

update := $(shell v='$(call shell_escape,$(somevar))'; echo $$v)

.phony: all

all:
    @echo '$(call shell_escape,$(update))'
    @echo $(update)

Sortie:

apple with 'quoted' "stuff" and dollar $signs
apple with quoted stuff and dollar
1
répondu Kaz 2015-01-17 06:23:40