【问题标题】:GNU Make: obtain list of primary prerequisites of a ruleGNU Make:获取规则的主要先决条件列表
【发布时间】:2014-12-10 16:55:16
【问题描述】:

考虑以下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 之后的附加先决条件在其他地方被分解,通常是由工具生成的完全独立的依赖生成文件,可以完全删除,而不会影响从头开始进行完整构建的能力。

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

这应该可以通过通用方式实现,无需任何硬编码。例如,如果我将一些配方行定义为宏,它们可以在多个规则中重复使用。

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

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

我不想为此向 RULE_BODY 传递参数,因为它应该“只知道”,就像它知道目标和总先决条件一样。

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

【问题讨论】:

  • 这种区别只存在于您的脑海中。 Make 没有这样的区别。
  • @EtanReisner 感谢您的意见。在我看来,区别在Makefile 的语法中进行了编码,因为模式规则%.o: %.c 的右侧是%.c,并且仅匹配foo.c,而不匹配foo.h。在某些时候,程序知道这一点。所以,它不仅在我的脑海里。
  • 您正在假设一种非常特定的方式来编写makefile,并且只有特定类型的模式规则。您也没有考虑 make 结合先决条件的方式。我并不是建议让无法知道这种区别。我建议 make 不会保存此信息,并且不会像您一样考虑这种区别。
  • 我同意构建使用与新鲜使用先决条件的想法是一个潜在有用的想法(尽管使用可能仅限于像这样的编译案例)但这种区别在 make 中并不存在.唯一的先决条件类型区分是针对仅订单的先决条件,这些先决条件不能满足您的要求。
  • @EtanReisner 你能通过引用 GNU make 源代码来支持这一点吗?如果做不到,知道这将是有用的。我必须为此写一个补丁吗?说一个新的自动变量,如$~ 或其他什么?我知道 o-o 先决条件并使用过它们。补丁不会那么有用,因为它必须被上游化,并且它会产生对前沿 GNU Make 的依赖。在 3.81 中工作的东西会很好。没有人愿意发布一个需要最新 gmake 的开源程序,该程序必须从 GNU ftp 服务器获得并从头开始构建。 :)

标签: makefile gnu-make


【解决方案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 makefile 被触及。我们不能只这样做:

$(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整个事情就像@ 987654338@短信。

当上面的 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 行。也就是说,按照以下思路生成文件:

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)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-01-03
    • 2021-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多