关于makefile:GNU make中的高级变量继承

Advanced variable inheritance in GNU make

我正在阅读GNU make手册,并对变量继承机制感到困惑。首先让我讲一下基础知识。

我引用了手册章节6.10来自环境的变量:

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.

想象一下,我打开一个外壳(称为"外壳1")并定义了两个变量。然后,我用两个选项" op1"和" op2"启动make。该程序使自身读取makefile并构造第三个变量,称为" varC"。我们得到如下图所示的情况:

enter image description here

我继续引用手册中的一句话:

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

这就是我现在要做的。将执行目标的第一条配方行,为此make将打开一个临时外壳(称为"外壳2")。我假设所有变量" varA"," varB"和" varC"都存在于此外壳中,因此可以由配方行使用。虽然我不确定100%。

enter image description here

本手册将继续介绍配方递归调用的情况:

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.

下一条配方行是递归$(MAKE)调用。顶层make打开一个临时shell(称为" shell 3")来运行此sub-make实例。因为varC没有显式导出,所以我认为Shell 3和子make中都不存在它。我对么?

enter image description here

我发布了此主题,以从经验丰富的Makefile编写者中获得澄清。我是该主题的新手,但是我正在努力研究手册,并在此之后开始使用。非常感谢所有帮助:-)

PS:如果您发布答案,请说明您的答案是否适用于Linux和/或Windows。


我认为您过分剖析了以下段落:

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.

所有这些结合在一起,因此仅将在传入环境或命令行中设置的环境变量,或在Makefile中显式"导出"的环境变量置于调用的命令的环境中(无论该命令是否为$(MAKE)或者是其他东西)。

一个有趣的特殊情况是在传入环境和Makefile中设置变量(但未显式导出)。然后,Makefile值将覆盖传入的环境值,并且还导出AND(因为它在传入的环境中,尽管具有不同的值)。

生成文件:

1
2
3
TEST = test
default:
        @echo TEST=""$$TEST""

结果:

1
2
3
4
5
6
$ make
TEST=""
$ TEST=xx make
TEST="test"
$ make TEST=xx
TEST="xx"

我将仅针对Windows回答,因为我这里没有Unix环境。它应该已经很好地说明了它在GNU make中的工作方式。

首先,我将假定您所谈论的环境变量具有与正在运行的Shell的生命周期相关联的生命周期,因此它不是系统环境变量。

在Windows上,有两个程序可以设置变量:SETSETX。可能还有更多的微妙之处,但为简单起见,SET将仅为当前shell及其子进程设置一个变量,而SETX将设置一个系统环境变量。我只使用SET,因为我不想处理系统环境变量。

我会给出一个经验性的答案。我已经对此设置进行了测试:

1
2
3
4
5
6
7
8
\---level1
    |   Makefile
    |  
    \---level2
        |   Makefile
        |  
        \---level3
                Makefile

级别1-Makefile

1
2
3
4
5
6
7
8
9
10
11
12
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

级别2-Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

3级-Makefile

1
2
3
4
5
6
7
8
9
10
11
12
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)

在测试开始时,我在level1文件夹中打开一个外壳程序(Windows命令提示符)。我创建一个值为11的变量varA

SET varA=11

然后,我调用第一个Makefile,后者将调用第二个,而后者将调用第三个。

make foo

这是输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

这样我们可以看到:

  • 可以从make的所有子调用中访问shell变量
  • 可以从该Makefile的make的所有子调用中访问导出的Makefile变量。
  • 无法从此Makefile的make子调用中访问未导出的Makefile变量

您可以轻松地重现此示例以执行更多测试。

请注意,您实际上可以包含另一个Makefile并因此获取其变量和规则,请参见GNU make:Include。如果您不十分注意发生的情况,我不建议使用此方法,因为如果规则在包含的Makefile中与包含在其中的规则具有相同的名称,则可以覆盖规则。


要注意的一件事是,$(shell)的规则似乎与在配方中执行命令时的规则略有不同。

在克里斯·多德(Chris Dodd)的"有趣的极端案例"中,使用$(shell)时似乎没有传递修改。用的makefile

1
2
3
4
5
IN_ENV = hello
$(info $(shell echo IN_ENV is $$IN_ENV))

all:
  @echo IN_ENV is $$IN_ENV

如果您运行:

1
2
export IN_ENV=goodbye
make

您得到以下输出:

1
2
IN_ENV is goodbye
IN_ENV is hello