考虑以下Makefile
:
.SUFFIXES:
.SUFFIXES: .c.o
.PHONY: all
all: foo.o
foo.o: foo.h bar.h xyzzy.h
%.o: %.c
@printf "prerequisites of %s are %s\n" $@ "$^"
除了foo.o
之外,所有文件都存在,输出为:
prerequisites of foo.o are foo.c foo.h bar.h xyzzy.h
正确地说,自动变量$^
为我们提供了所有先决条件,包括从其他规则中规定的依赖关系中获得的先决条件。
让我们调用规则本身主要先决条件中给出的先决条件,以及来自其他依赖项辅助先决条件的先决条件。
以上,主要先决条件是:
foo.c
,次要的是:
foo.h bar.h xyzzy.h
类别很重要,因为主要先决条件是规则实际使用的对象,这些对象是构建程序所必需的。辅助先决条件仅涉及正确触发增量构建,而不是完整构建。即使我们删除了依赖项,从头开始的完整构建也会起作用:
foo.o: foo.h bar.h xyzzy.h
这反映在我们的Makefile
结构中。我们通常不会使用以下规则编写Makefiles
:
foo.o: foo.c foo.h bar.h xyzzy.h
# commands
foo.c
之后的其他先决条件在其他地方被考虑在内,通常是由工具生成的完全独立的依赖关系makefile,可以完全删除,而不会影响从头开始完成构建的能力。< / p>
问题是:我们如何才能获得主要先决条件的列表,而不包括辅助先决条件?
这应该是通用的,没有任何硬编码。例如,如果我将某些配方行定义为宏,则可以在多个规则中重复使用它们。
define RULE_BODY
@printf "the primary prerequisites of target %s are %s\n" $@ [what goes here?]
endef
%.o: %.c
$(call RULE_BODY)
我不想将参数传递给RULE_BODY,因为它应该&#34;只知道&#34;,就像它知道目标和总的先决条件一样。
请注意,使用模式规则是一个红色鲱鱼:我们可以将%.o: %.c
替换为foo.o: foo.c
。
答案 0 :(得分:1)
一种可能的解决方案是添加一个中间依赖关系节点,该节点捕获辅助先决条件,并将它们表示为单个先决条件。虚假的先决条件具有一定的可识别的词汇形式,可以过滤掉它:
概念证明,紧密基于问题中的Makefile
:
.SUFFIXES:
.SUFFIXES: .c.o
all: foo.o
secondary_foo.o: foo.h bar.h xyzzy.h
echo $^ > $@
foo.o: secondary_foo.o
define RULE_BODY
@printf "prerequisites of %s are %s\n" $@ "$^"
@printf "primary prerequisites of %s are %s\n" $@ "$(filter-out secondary_$@,$^)"
@printf "secondary prerequisites of %s are %s\n" $@ "$(shell cat secondary_$@)"
endef
%.o: %.c
$(call RULE_BODY)
touch $@
输出:
prerequisites of foo.o are foo.c secondary_foo.o
primary prerequisites of foo.o are foo.c
secondary prerequisites of foo.o are foo.h bar.h xyzzy.h
touch foo.o
不幸的是,构建目录中充斥着这些中间文件。即使以某种其他方式处理辅助先决条件的传播,secondary_foo.o
文件仍然不能成为假目标;至少它必须是一个空的时间戳文件。
以下替代解决方案更复杂,需要计算变量eval
,并使用技巧在变量中存储依赖关系,这些变量用于生成规则。但是,它的优点是它不会产生时间戳文件的扩散。
.SUFFIXES:
.SUFFIXES: .c.o
OBJS := foo.o bar.o
all: $(OBJS)
# These variables give secondary dependencies for the objectg files,
# in place of rules. These would typeically be "farmed out" to
# a machine-generated dependency makefile which is included:
DEP_foo.o := foo.h bar.h xyzzy.h
DEP_bar.o := bar.h xyzzy.h
define RULE_BODY
@printf "\n"
@printf "prerequisites of %s are %s\n" $@ "$^"
@printf "primary prerequisites of %s are %s\n" $@ "$(filter-out $(DEP_$@),$^)"
@printf "secondary prerequisites of %s are %s\n" $@ "$(DEP_$@)"
endef
%.o: %.c
$(call RULE_BODY)
# Now the trickery: generate the dependency rules from OBJS and DEP_ vars:
# $(NL) provides newline, so we can insert newline into eval expansions
define NL
endef
# For each object <obj>, generate the rule <obj>: $(DEP_<obj>)
$(eval $(foreach obj,$(OBJS),$(obj): $(DEP_$(obj))$(NL)))
输出:
prerequisites of foo.o are foo.c foo.h bar.h xyzzy.h
primary prerequisites of foo.o are foo.c
secondary prerequisites of foo.o are foo.h bar.h xyzzy.h
prerequisites of bar.o are bar.c bar.h xyzzy.h
primary prerequisites of bar.o are bar.c
secondary prerequisites of bar.o are bar.h xyzzy.h
缺点是必须将任何其他依赖项插入到变量中,而不是通过普通规则声明。例如,假设我们想要在触摸$(OBJS)
生成文件时重新编译所有config.make
。我们不能这样做:
$(OBJS): config.make # Oops, config.make is now considered primary
相反,我们坚持使用DEP_
变量方案,并按照以下方式执行:
$(eval $(foreach obj,$(OBJS),DEP_$(obj) += config.make$(NL)))
换句话说,循环遍历$(OBJS)
,并为每个+=
变量生成DEP_
变量赋值,该变量添加config.make
,后跟换行符,{{1整个事情好像是eval
文本。
当上述eval插入我们的Makefile
时(在现有Makefile
之前,而不是之后),输出显示eval
已添加到config.make
和foo.o
作为次要先决条件:
bar.o
这是一个可行的解决方案,它可以避免临时文件,但对prerequisites of foo.o are foo.c foo.h bar.h xyzzy.h config.make
primary prerequisites of foo.o are foo.c
secondary prerequisites of foo.o are foo.h bar.h xyzzy.h config.make
prerequisites of bar.o are bar.c bar.h xyzzy.h config.make
primary prerequisites of bar.o are bar.c
secondary prerequisites of bar.o are bar.h xyzzy.h config.make
维护者的理解更具挑战性。
另请注意,由于GNU Make允许使用变量名称中的句点和斜杠,因此类似以下内容不是问题:
Makefile
在规则中,DEP_libs/parser/scan.o := config.h libs/parser/parser.h ...
是libs/parser/scan.o
目标,$@
很好地为我们提供$(DEP_$@)
。
最后,请注意,依赖关系生成器可以只生成代码并将其粘贴到依赖关系makefile中,而不是config.h libs/parser/parser.h ...
行。也就是说,沿着这些行生成文件:
eval