考虑以下简单示例:
ARGS := -a -b
$(info args = $(ARGS))
DIR := foo
ARGS += ../$(DIR)/lib.a
$(info args = $(ARGS))
DIR := bar
$(info args = $(ARGS))
此输出(
make
在当前目录中)(如我所料):
args = -a -b
args = -a -b ../foo/lib.a
args = -a -b ../foo/lib.a
make: *** No targets. Stop.
也就是说,
ARGS
是一个简单扩展变量,所以没有重新替换。它没有发现 DIR
从“foo”更改为“bar”。到目前为止,一切都很好。
但是,现在我尝试使用递归 makefile 来完成它。在父 makefile 中,我有第一个块,其中包含
export
命令(将所有变量导出到子级)和递归调用:
ARGS := -a -b
$(info args = $(ARGS))
export
default:
$(MAKE) -C child/
在子 makefile 中,我有其余部分,未修改:
DIR := foo
ARGS += ../$(DIR)/lib.a
$(info args = $(ARGS))
DIR := bar
$(info args = $(ARGS))
但是,现在的输出是:
args = -a -b
make -C child/
make[1]: Entering directory '[...]/child'
args = -a -b ../foo/lib.a
args = -a -b ../bar/lib.a
make[1]: *** No targets. Stop.
make[1]: Leaving directory '[...]/child'
make: *** [Makefile:14: default] Error 2
现在,它确实拾取
DIR
从“foo”更改为“bar”,重新替换。也就是说,不知何故,export
命令更改了ARGS
的类型(在子进程中) )到递归扩展变量!
export
(再次,这里)的描述似乎表明这是不可能的。具体来说,它将 export :=
与 export =
进行了对比,并表示它们具有不同的效果。我们还可以通过用非常明确的内容替换父 makefile 中的代码来直接测试:
export ARGS := -a -b
default:
$(MAKE) -C child/
然而,这会在子进程中产生与以前相同的不需要的递归扩展!
make --version
将自己报告为“GNU Make 4.3”。
发生了什么事以及如何导出变量以便它们在子级中保持简单扩展?
您对手册读得太多了。说明书上说:
export foo := bar
与以下行为相同:
foo := bar
export foo
确实如此。但这绝对没有说明子品牌中
foo
的行为。
实际上所有导出的 make 变量都是通过标准 POSIX 环境导出的,作为标准 POSIX 环境变量。环境变量中不存在“简单”变量与“递归”变量之类的东西,只有变量。
当 make 准备好调用配方中的命令时(无论该命令是编译器还是 make 的另一个实例或其他任何东西:这只是一个新的子进程),它将遍历所有导出的变量,展开它们,并将它们添加到子流程的环境中,然后调用子流程。
如果子进程是 make 的另一个实例,那么 make 将检索所有环境变量(一如既往)并将它们作为递归变量导入,并使用它们作为 make 变量具有的任何值。 Make 不知道父实例使用了简单变量,父实例也无法通过环境告诉子实例。
如果你想强制变量的简单性,你可以使用赋值而不是附加:
ARGS := $(ARGS) ../$(DIR)/lib.a