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

Makefile GNU Make:获取规则的主要先决条件列表,makefile,gnu-make,Makefile,Gnu Make,考虑以下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 正确地说,自动变量$^为我们提供了

考虑以下
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
结构中。我们通常不会使用以下规则编写
makefile

foo.o: foo.c foo.h bar.h xyzzy.h
    # commands
foo.c
之后的附加先决条件在其他地方被分解,通常被分解成一个完全独立的依赖项生成文件,该文件由工具生成,可以完全删除,而不会影响从头开始进行完整构建的能力

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

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

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
,一种可能的解决方案是添加一个中间依赖节点,它捕获次要先决条件,并将它们表示为单个先决条件。虚假先决条件具有某种可识别的词汇形式,根据这种形式可以将其过滤掉:

概念证明,紧密基于问题中的
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
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
不幸的是,构建目录中充斥着这些中间文件。即使以其他方式处理次要先决条件的传播,
secondary\u 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)))
缺点是任何附加的依赖项都必须插入到变量中,而不是通过普通规则断言。例如,假设我们想要重新编译所有
$(OBJS)
,如果触摸了
config.make
makefile。我们不能这样做:

$(OBJS): config.make   # Oops, config.make is now considered primary
相反,我们坚持使用
DEP\uu
变量方案,如下所示:

$(eval $(foreach obj,$(OBJS),DEP_$(obj) += config.make$(NL)))
换句话说,循环
$(OBJS)
,并为每个
DEP\uu
变量生成一个
+=
变量赋值,该变量添加
config.make
,后跟一个换行符,
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
这是一个可行的解决方案,它避免了临时文件,但对于
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
行。也就是说,沿着以下几行生成文件:

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)

这种区别只存在于你的头脑中。Make没有这样的区别。@EtanReisner感谢您的输入。在我看来,这种区别是通过
Makefile
的语法编码的,因为模式规则
%.o:%.c
的右侧是
%.c
,它只匹配
foo.c
,而不匹配
foo.h
。在某种程度上,程序知道这一点。所以,这不仅仅是在我的头脑中,你在假设一种非常特殊的方式来编写一个makefile,并且只使用特定类型的模式规则。您也没有考虑如何使组合成为先决条件。我并不是说make不知道这种区别。我建议,制作不保存这些信息,也不考虑与你做的同样的区别。我同意使用构建和使用新鲜度的前提条件是一个潜在的有用的(虽然使用可能局限于这样的编译案例),但是这种区别在制作中根本不存在。make唯一的先决条件类型区别是只针对订单的先决条件,这些先决条件不能满足您的要求。@EtanReisner您可以引用GNU make源代码来证明这一点吗?如果不能做到这一点,了解这一点将是有益的。我必须为此编写补丁吗?说一个新的自动变量,比如
$~
或其他什么?我知道o-o的先决条件,并使用过它们。补丁不会那么有用,因为它必须是上游的,并且会对最前沿的GNU Make产生依赖。一些