GNU Make:获取规则的主要先决条件列表

3
请看以下 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中,该makefile由工具生成,并且可以完全删除,而不会影响从头开始进行完整构建的能力。

问题是:我们如何仅获得主要先决条件的列表,而不包括次要先决条件?

这应该可以以一种通用的方式完成,而没有任何硬编码。例如,如果我定义了一些作为宏的配方行,它们可以在多个规则中重复使用。

define RULE_BODY
@printf "the primary prerequisites of target %s are %s\n" $@ [what goes here?]
endef

%.o: %.c
    $(call RULE_BODY)

我不希望将参数传递给RULE_BODY,因为它应该“自己知道”,就像它知道目标和全部先决条件一样。

请注意,使用模式规则是一个红鲱鱼:我们可以用foo.o: foo.c替换%.o: %.c


2
这种区别只存在于你的头脑中。Make 没有这样的区分。 - Etan Reisner
@EtanReisner,你能否提供GNU make源代码的引用来支持这个观点?如果无法做到,那么了解这一点将是有用的。我需要为此编写一个补丁吗?比如说一个新的自动变量像$~或其他什么?我知道关于o-o先决条件并已经使用过它们。一个补丁可能没有那么有用,因为它必须被上游采纳,并且它会在GNU Make的最新版本中创建依赖性。在3.81中工作的东西会很好。没有人想要发布一个需要从GNU ftp服务器获取并从头构建的最新gmake的开源程序。 :) - Kaz
外部进程的问题在于知道哪些文件与模式规则匹配。您可能需要将完整构建的“make -d”输出解析与手动解析makefile(或可能是“-p”输出,因为您最好要考虑“eval”创建的目标)相结合。 - Etan Reisner
如果你想使用 foo.o: $(DEPS_$*),那么这将需要进行二次扩展(我认为),但如果你不这样做,那么你的定义可以使用 $(DEPS_$*) 来从 $^ 中过滤出次要先决条件。是的,我假设 $^ 在这些情况下不能“只是”做你想要的事情? - Etan Reisner
只是出于好奇,你为什么想以这种方式拆分你的依赖关系? - bobbogo
显示剩余10条评论
1个回答

2

一种可能的解决方案是添加一个中间依赖节点,它捕获次要先决条件并将它们表示为单个先决条件。这个虚假的先决条件具有特定的可识别的词汇形式,可以根据它来过滤出来:

概念证明,紧密基于问题中的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

缺点是任何额外的依赖关系必须插入到变量中,而不是通过普通规则进行断言。例如,假设我们想要在触摸config.make makefile时重新编译所有$(OBJS)。我们不能仅仅这样做:
$(OBJS): config.make   # Oops, config.make is now considered primary

相反,我们坚持使用DEP_变量方案,并像这样执行:

$(eval $(foreach obj,$(OBJS),DEP_$(obj) += config.make$(NL)))

换句话说,循环遍历$(OBJS),并为每个DEP_变量生成一个+=变量分配,其中添加了config.make,后跟一个换行符,并eval整个内容,就好像它是Makefile文本一样。
当上述eval插入到我们的Makefile中(在现有eval之前而不是之后),输出显示已将config.make添加为foo.obar.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

这是一个可行的解决方案,避免了临时文件,但对于Makefile维护者来说更具挑战性。
另外请注意,由于GNU Make允许变量名中包含句点和斜杠,像以下这样的内容并不会出现问题:
DEP_libs/parser/scan.o := config.h libs/parser/parser.h ...

在一个规则中,libs/parser/scan.o$@ 目标,$(DEP_$@) 很好地给了我们 config.h libs/parser/parser.h ...
最后,请注意,依赖关系生成器可以不使用 eval 行,而是将代码生成并粘贴到依赖关系 makefile 中。也就是说,按照以下方式生成文件:
DEP_foo.o := foo.h bar.h xyzzy.h config.make  # config.make tacked on
foo.o: $(DEP_foo.o)                           # also generated

DEP_bar.o := ... # and so forth
bar.o: $(DEP_bar.o)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接