使用名称从简单扩展变量计算的变量定义递归扩展变量

Define a recursively expanded variable using a variable whose name is computed from a simply expanded variable

我 运行 在为非递归 make 系统定义通用规则时遇到困难。

背景

为了进一步阅读,而不是我复制太多现有的 material,请参阅 this earlier question,它涵盖了很好的基础,并且以前在构建这个系统时帮助了我。

对于我正在构建的 make 系统,我想定义系统组件之间的依赖关系 - 例如组件 A 依赖于组件 B - 然后离开 make 系统以确保在 A 的构建步骤需要它们之前构建 B 构建过程的任何产品。由于粒度(可能会构建一些不需要的中间体),这有点浪费),但对于我的用例,它在易用性和构建性能之间达到了一个舒适的平衡点。

系统必须处理的一个困难是无法控制makefile 加载的顺序- 事实上,应该无关紧要。然而,正因为如此,在早期加载的 makefile 中定义的组件可能依赖于在尚未读取的 makefile 中定义的组件。

为了允许在所有定义组件的 makefile 中应用通用模式,每个组件都使用变量,例如:$(component_name)_SRC。这是非递归(但递归包含)make 系统的常见解决方案。

有关 GNU make 不同类型变量的信息,请参阅 the manual。总结:简单扩展变量 (SEV) 在读取 makefile 时被扩展,表现出类似于命令式编程语言的行为;在读取所有 makefile 后,递归扩展变量 (REV) 在 make 的第二阶段扩展。

问题

在尝试将依赖组件列表转换为这些组件代表的文件列表时会出现特定问题。

我已经将我的代码提炼成这个 运行 可行的示例,它省略了真实系统的很多细节。我认为这很简单,可以在不失去实质内容的情况下证明这个问题。

rules.mk:

$(c)_src              := $(src)
$(c)_dependencies     := $(dependencies)

### This is the interesting line:
$(c)_dependencies_src := $(foreach dep, $($(c)_dependencies), $($(dep)_src))

$(c) : $($(c)_src) $($(c)_dependencies_src)
        @echo $^

生成文件:

.PHONY: foo_a.txt foo_b.txt bar_a.txt hoge_a.txt

### Bar
c            := bar
src          := bar_a.txt
dependencies :=

include rules.mk

### Foo
c            := foo
src          := foo_a.txt foo_b.txt
dependencies := bar hoge

include rules.mk

### Hoge
c            := hoge
src          := hoge_a.txt
dependencies := bar

include rules.mk

这些将 运行 给出:

$ make foo
foo_a.txt foo_b.txt bar_a.txt
$

hoge_a.txt 未包含在输出中,因为在 foo_dependencies 被定义为 SEV 时,hoge_src 尚不存在。

读取所有 makefile 后的扩展是 REV 应该能够解决的问题,我之前确实尝试将 $(c)_dependencies_src 定义为 REV,但这也不起作用,因为 $(c)然后在替换时扩展,而不是定义时,因此它不再具有正确的值。

如果有人想知道为什么我不使用特定于目标的变量,我担心将变量应用于手册中描述的目标的所有先决条件会导致不同规则之间不必要的交互组件。

我想知道:

  1. 这个具体问题有解决方案吗? (也就是说,有没有一种简单的方法可以使这条线达到我想要的效果?)
  2. 是否有更典型的方法来构建这样的 make 系统? (即单个 make 实例,从多个 makefile 加载组件并定义这些组件之间的依赖关系。)
  3. 如果有多个解决方案,它们之间的取舍是什么?

最后的评论:在我写下我的问题时,我开始意识到可能有一个解决方案可以使用 eval 来构建 REV 定义,但是因为我在其他任何地方都找不到这个问题所以我认为为了未来的搜索者无论如何问这个问题都是值得的,另外我想听听更多有经验的用户对此或任何其他方法的想法。

简短的回答是您提出的问题没有好的解决方案。不可能中途停止变量的扩展并将其推迟到以后。不仅如此,而且因为您在先决条件列表中使用了变量,即使您可以获得 $(c)_dependencies_src 变量的值以仅包含您想要的变量引用,在下一行中它们将作为一部分完全展开先决条件列表,因此它不会为您带来任何好处。

只有一种方法可以推迟先决条件的扩展,那就是使用 secondary expansion 功能。你必须做类似的事情:

$(c)_src              := $(src)
$(c)_dependencies     := $(dependencies)

.SECONDEXPANSION
$(c) : $($(c)_src) $$(foreach dep, $$($$@_dependencies), $$($$(dep)_src))
        @echo $^

(未经测试)。这回避了 $(c)_dependencies_src 的问题,只是根本不定义它并将其直接放入先决条件列表中,而是作为二次扩展。

不过,正如我在上面的评论中所写,我个人不会设计这样一个系统。我更喜欢这样的系统,其中所有变量都是使用命名空间预先创建的(通常在目标名称前面),然后在最后,在定义所有变量之后,包括单个 "rules.mk" 或任何将使用的变量所有这些变量来构建规则,很可能(除非你所有的食谱都非常简单)使用 eval.

所以,类似于:

targets :=

### Bar
targets += bar
bar_c            := bar
bar_src          := bar_a.txt
bar_dependencies :=

### Foo
targets += foo
foo_c            := foo
foo_src          := foo_a.txt foo_b.txt
foo_dependencies := bar hoge

### Hoge
targets += hoge
hoge_c            := hoge
hoge_src          := hoge_a.txt
hoge_dependencies := bar

# Now build all the rules
include rules.mk

然后在 rules.mk 你会看到类似这样的东西:

define make_c
 : $(_src) $(foreach dep, $(_dependencies), $($(dep)_src))
        @echo $$^
endif

$(foreach T,$(targets),$(eval $(call make_c,$T)))

如果您注意变量名,您甚至可以去掉 target 的设置,方法是在 rules.mk:

中添加类似这样的内容
targets := $(patsubst %_c,%,$(filter %_c,$(.VARIABLES)))

为了在不同的目标中允许相同的组件,您只需要向命名空间添加更多内容以区分两个不同的组件。