Makefile variable initialization and export

后端 未结 4 1777
野趣味
野趣味 2021-02-02 08:00
somevar := apple
export somevar
update := $(shell echo \"v=$$somevar\")

all:
    @echo $(update)

I was hoping to apple as output of command, however i

相关标签:
4条回答
  • 2021-02-02 08:34

    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"'
    
    0 讨论(0)
  • 2021-02-02 08:47

    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)

    0 讨论(0)
  • 2021-02-02 08:48

    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 讨论(0)
  • 2021-02-02 08:52

    @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.

    0 讨论(0)
提交回复
热议问题