我在为非递归make系统定义通用规则时遇到了困难。
背景
为了进一步阅读,而不是我复制过多的现有材料,请参阅this earlier question,它很好地覆盖了地面,并且在构建此系统之前帮助了我。
对于我正在构建的make系统,我想定义系统组件之间的依赖关系 - 例如组件A依赖于组件B - 然后离开make系统以确保B构建过程的任何产品在A构建步骤需要之前构建。由于粒度(可能构建一些不需要的中间体)而略微浪费),但对于我的用例,它在易用性和构建性能之间达到了一个舒适的平衡点。
系统必须处理的一个难点是makefile加载的顺序无法控制 - 事实上,无所谓。但是正因为如此,早期加载的makefile中定义的组件可能依赖于尚未读取的makefile中定义的组件。
为了允许在所有组件定义的makefile中应用通用模式,每个组件都使用如下变量:$(component_name)_SRC
。这是非递归(但递归包含)make系统的常见解决方案。
有关GNU make的不同类型变量的信息,请参阅the manual。总结:简单地扩展变量(SEV)在读取makefile时被扩展,表现出与命令式编程语言类似的行为;在读取所有makefile之后,在make的第二阶段扩展递归扩展变量(REV)。
问题
当尝试将依赖组件列表转换为这些组件所代表的文件列表时,会出现特定问题。
我已将我的代码提炼到这个可运行的示例中,这省略了实际系统的许多细节。我认为这很容易证明这个问题而不会失去其实质内容。
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)
是在替换时间扩展,而不是定义时间,因此它不再保持正确的值。
如果有人想知道我为什么不使用特定于目标的变量,我担心将变量应用于手册中描述的目标的所有先决条件将导致不同组件的规则之间的不必要的交互。
我想知道:
最后的评论:当我写完我的问题时,我已经开始意识到可能有一个解决方案可能使用eval来构建一个REV定义,但是因为我无法在SO上的任何其他地方找到这个问题为了未来的搜索者,我认为值得为此问题提出问题,而且我希望听到更多有经验的用户对此或任何其他方法的看法。
答案 0 :(得分:0)
简短的回答是,对于你提出的问题,没有好的解决办法。它不可能在中途停止变量的扩展并将其推迟到以后。不仅如此,而且因为您在先决条件列表中使用该变量,即使您可以获得$(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
$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)))
为了允许不同目标中的相同组件,您只需要在命名空间中添加更多内容以区分这两个不同的组件。