1

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

背景

为了进一步阅读,而不是我复制太多现有材料,请参阅这个较早的问题,它很好地涵盖了基础,并且在构建这个系统时帮助了我。

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

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

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

有关 GNU make 不同类型变量的信息,请参阅手册。总而言之:简单扩展变量(SEV)随着生成文件的读取而扩展,表现出类似于命令式编程语言的行为;在读取所有 makefile 之后,递归扩展变量 (REV) 在 make 的第二阶段进行扩展。

问题

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

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

规则.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 定义,但是因为我在 SO 上的其他任何地方都找不到这个问题,所以我认为值得为了未来的搜索者,无论如何都要问这个问题,另外我想听听更有经验的用户对此或任何其他方法的想法。

4

1 回答 1

1

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

只有一种方法可以推迟先决条件的扩展,那就是使用辅助扩展功能。您必须执行以下操作:

$(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
$1 : $($1_src) $(foreach dep, $($1_dependencies), $($(dep)_src))
        @echo $$^
endif

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

如果你对你的变量名很小心,你甚至可以去掉这些设置,方法target是在下面添加这样的东西rules.mk

targets := $(patsubst %_c,%,$(filter %_c,$(.VARIABLES)))

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

于 2017-01-01T17:11:25.820 回答