2
votes

I'm reading the GNU make manual, and got confused about the variable inheritance mechanism. Let me go through the basics first.


I quote from the manual chapter 6.10 Variables from the Environment :

Variables in make can come from the environment in which make is run. Every environment variable that make sees when it starts up is transformed into a make variable with the same name and value.

So imagine that I open a shell (call it "shell 1") and I define two variables. Then I startup make with two options, "op1" and "op2". The program make itself reads a makefile and constructs a third variable, called "varC". We get the situation as in the figure below:

enter image description here


I continue with a quote from the manual:

When make runs a recipe, variables defined in the makefile are placed into the environment of each shell.

So that's what I am going to do now. The first recipe line for the target gets executed, for which make opens a temporary shell (call it "shell 2"). I presume that all variables "varA", "varB" and "varC" are present in this shell, and thus can be used by the recipe line. Although I am not 100% sure.

enter image description here


The manual continues about the case where a recipe calls make recursively:

By default, only variables that came from the environment or the command line are passed to recursive invocations. You can use the export directive to pass other variables.

The next recipe line is a recursive $(MAKE) call. The toplevel make opens a temporary shell (call it "shell 3") to run this sub-make instance. Because varC was not explicitely exported, I believe that it is not present in shell 3, nor in the sub-make. Am I correct?

enter image description here

I posted this topic to get clarifications from experienced makefile writers. I am a newbie to this topic, but I'm doing my best to study the manual and get started after that. All help is greatly appreciated :-)

PS: if you post an answer, please mention if your answer applies to Linux, Windows, or both.

3

3 Answers

2
votes

I think you're overdissecting the paragraph:

When 'make' runs a command script, variables defined in the makefile are placed into the environment of that command. This allows you to pass values to sub-'make' invocations. By default, only variables that came from the environment or the command line are passed to recursive invocations. You can use the 'export' directive to pass other variables.

All of this goes together, so ONLY environment variables set in the incoming environment, or on the command line, or explicitly 'export'-ed in the Makefile are placed in the environment of commands that are invoked (whether that command is $(MAKE) or something else).

One interesting corner case is a variable set both in the incoming environment and in the Makefile (but not explicitly exported). The Makefile value then overrides the incoming environment value, AND is also exported (because it was in the incoming environment, albeit with a different value).

Makefile:

TEST = test
default:
        @echo TEST="\"$$TEST\""

result:

$ make
TEST=""
$ TEST=xx make
TEST="test"
$ make TEST=xx
TEST="xx"
1
votes

I will answer for Windows only since I do not have a Unix environment here. It should already give a good view of how it works in GNU make.

At first I will assume that the environment variables your are talking about have a life cycle linked with the life cycle of the running shell, so it's not a system environment variable.

On Windows there are two programs to set a variable : SET and SETX. There a probably more subtleties, but to keep it simple, SET will set a variable only for the current shell and its sub-processes, and SETX will set a system environment variable. I'll only use SET since I don't want to deal with system environment variables.

I will give an empiric answer. I have done the test on this setup :

\---level1
    |   Makefile
    |   
    \---level2
        |   Makefile
        |   
        \---level3
                Makefile

level1 - Makefile

LEVEL = LEVEL1
LEVEL1VAR = VAR1
varB = 12
export varB

.PHONY: foo

foo:
    @echo $(LEVEL) var level 1 : $(LEVEL1VAR)
    @echo $(LEVEL) varA is $(varA)
    @echo $(LEVEL) varB is $(varB)
    cd level2 & $(MAKE) foo

level2 - Makefile

LEVEL = LEVEL2
LEVEL2VAR = VAR2
MKID = MKID2

varC = 13
export varC

.PHONY: foo

foo:
    @echo $(LEVEL) var level 1 : $(LEVEL1VAR)
    @echo $(LEVEL) var level 2 : $(LEVEL2VAR)
    @echo $(LEVEL) varA is $(varA)
    @echo $(LEVEL) varB is $(varB)
    cd level3 & $(MAKE) foo

level3 - Makefile

LEVEL = LEVEL3
LEVEL3VAR = VAR3

.PHONY: foo

foo:
    @echo $(LEVEL) var level 1 : $(LEVEL1VAR)
    @echo $(LEVEL) var level 2 : $(LEVEL2VAR)
    @echo $(LEVEL) var level 3 : $(LEVEL3VAR)
    @echo $(LEVEL) varA is $(varA)
    @echo $(LEVEL) varB is $(varB)
    @echo $(LEVEL) varC is $(varC)

At the beginning of the test I open a shell (Windows command prompt) in the level1 folder. I create a variable varA with the value 11 :

SET varA=11

Then I call the first Makefile, which will call the second, which will call the third.

make foo

Here is the output :

LEVEL1 var level 1 : VAR1
LEVEL1 varA is 11
LEVEL1 varB is 12
cd level2 & make foo
LEVEL2 var level 1 : 
LEVEL2 var level 2 : VAR2
LEVEL2 varA is 11
LEVEL2 varB is 12
cd level3 & make foo
LEVEL3 var level 1 : 
LEVEL3 var level 2 : 
LEVEL3 var level 3 : VAR3
LEVEL3 varA is 11
LEVEL3 varB is 12
LEVEL3 varC is 13

So we can see that :

  • The shell variable can be accessed from all the sub-calls of make
  • An exported Makefile variable can be accessed from all the sub-calls of make of this Makefile
  • A non-exported Makefile variable cannot be accessed from the sub-calls of make of this Makefile

You can easily reproduce this example to perform more tests.

Note that you can actually include another Makefile and therefore acquire its variables and rules, see GNU make: Include. I don't recommend to use this if you don't pay a really close attention to what happens, because you can override rules if they have the same name in the included Makefile as in the one which includes.

0
votes

One thing to be careful about is that the rules for $(shell) appear to be slightly different from those when executing commands as part of a recipe.

In Chris Dodd's "interesting corner case" it would appear that modifications are not passed on when using $(shell). With a makefile of

IN_ENV = hello
$(info $(shell echo IN_ENV is $$IN_ENV))

all:
  @echo IN_ENV is $$IN_ENV

if you run:

export IN_ENV=goodbye
make

you get output of:

IN_ENV is goodbye
IN_ENV is hello