开发者

Makefile variable initialization and export

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

all:
    @echo $(update)

I was hoping to apple as output of command, however it's em开发者_高级运维pty, which makes me think export and := variable expansion taking place on different phases. how to overcome this?


The problem is that export exports the variable to the subshells used by the commands; it is not available for expansion in other assignments. So don't try to get it from the environment outside a rule.

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"'

The output is:

echo method 1 
method 1
perl -e 'print "method 2 $ENV{somevar}\n"'
method 2 apple
echo method 3 apple
method 3 apple
perl -e 'print "method 4 $ENV{somevar}\n"'
method 4 apple


@Beta's answer contains the crucial pointer: with GNU make, variables marked with export are only available to [the shells launched for] recipe commands (commands that are part of rules), regrettably not to invocations of $(shell ...) (they only see the environment that make itself saw when it was launched).

There is a workaround, however: explicitly pass the exported variable as an environment variable to the shell function:

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

By prepending the shell command with <var>=<val>, that definition is added as an environment variable to the environment that the command sees - this is a generic shell feature.

Caveat: @Kaz points out in a comment that this method misbehaves if $(somevar) contains certain chars., because the variable expansion is verbatim (no escaping), which can break the resulting shell command, and suggests the following variant to also work with embedded ' instances (breaks the input value into single-quoted substrings with quoted ' spliced in):

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

This should work with all values except multi-line ones (which are rare; there is no workaround for multi-line values that I'm aware of).

On a side note, literal $ chars. in values must be represented as $$, otherwise make will interpret them as references to its own variables.

Note that I've deliberately NOT chosen the OP's original statement, update := $(shell echo "v=$$somevar"), for demonstration, because it contains a pitfall that muddles the issue: due to how the shell evaluates a command line, somevar=apple echo v=$somevar does NOT evaluate to v=apple, because the $somevar reference is expanded before somevar=apple takes effect. To achieve the desired effect in this case, you'd have to use 2 statements: update := $(shell export somevar="$(somevar)"; echo "v=$$somevar")


As for the bug-vs.-feature debate:

While it can be argued that the shell function should see the same environment as recipe commands, the documentation makes no such promise - see http://www.gnu.org/software/make/manual/make.html#Shell-Function. Conversely, http://www.gnu.org/software/make/manual/make.html#Variables_002fRecursion only mentions making exported variables available to recipe commands.


Running the makefile

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

gives for me (with foo undefined in the environment)

$ make
>
>apple

$ make foo=bar
>
>apple

$ export foo=bar; make
>bar
>apple

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

Try using the quoted form (update := "v=$$somevar") and let the shell handle expansion when a command is run (you'll still need the export)


Although export does not play nicely with $(shell ...), there is a simple workaround. We can pass the data to the shell script via the command line.

Now of course, environment passage is robust against issues of escaping and quoting. However, the shell language has a single quote quoting method '...' which handles everything. The only problem is that there is no way to get a single quote in there; but of course that is solved by terminating the quote, backslash-escaping the needed single quote and starting a new quote: In other words:

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

In the shell script executed by $(shell ...) we just generate a variable assignment of the form var='$(...)', where $(...) is some make expression that interpolates suitably escaped material. Thus, 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

Sample run:

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

If we want to communicate an environment variable to a command, we can do that using the shell syntax VAR0=val0 VAR1=val1 ... VARn=valn command arg .... This can be illustrated by some minor alterations to the above 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

Run:

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

file.txt contains a dump of environment variables, where we can see somevar. If export in GNU Make did the right thing, we would have been able to just do:

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

but the end result is the same.

Since the end result you want is to echo $(update), you would shell_escape anyway, even if GNU Make passed exported vars to $(shell ...). That is to say, look at one more 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)

Output:

apple with 'quoted' "stuff" and dollar $signs
apple with quoted stuff and dollar
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜