【问题标题】:Complex pattern rule in MakefileMakefile 中的复杂模式规则
【发布时间】:2014-10-31 10:13:28
【问题描述】:

我有以下生成文件,用于从一些模板生成文件,生成的文件有两个可能的扩展名:

%.tex: %.tex*_tpl
    ./generate $@_tpl -o $@

%.xml: %.xml*_tpl
    ./generate $@_tpl -o $@

此处的依赖项列表将匹配 a.tex_tpla.tex-subpart1_tpla.tex-subpart2_tpl 等内容。

虽然这可行,但有没有办法避免重复?例如通过匹配规则名称中的*.{tex,xml} 并在依赖项列表中使用整个匹配的名称?看起来像这样的东西:

%.{tex,xml}: $@_tpl
    ./generate $< -o $@

(虽然我知道%.{tex,xml} 不是一个有效的规则名称,你不能在依赖列表中使用$@

或任何其他(更清洁的?)方式。

【问题讨论】:

  • 我想不出有什么能让这个更干净的东西,尽管这取决于你所说的“更干净”是什么意思。您可以将配方放入变量中:GENERATE = ./generate $@_tpl -o $@ 然后在每个命令中使用$(GENERATE) 变量;这将减少您需要更新食谱的地方的数量。
  • 确实更好,谢谢!我真的不知道我该叫什么“干净”,我只能想到一些模糊的描述,比如“简短而不是 hacky”。​​

标签: makefile


【解决方案1】:

在我看来,这可以满足您的需求:

#
# I've assumed that files of the form:
#
#  a.xml_tpl
#  b.tex_tpl
#
# determine what targets you want to build
#
TARGETS:=$(patsubst %_tpl,%,$(wildcard *.xml_tpl *.tex_tpl))

.PHONY: all
all: $(TARGETS)

.SECONDEXPANSION:
$(TARGETS): %: $$(wildcard %*_tpl)
    ./generate $^ -o $@

关键是使用.SECONDEXPANSION 允许在第二个扩展阶段评估$$(wildcard %*_tpl)。顺便说一句,双 $ 不是错字;它可以防止表达式在第一次展开时被计算。

如果我用这些文件填充目录:

a.tex-subpart1_tpl
a.tex_tpl
a.xml-subpart1_tpl
a.xml-subpart2_tpl
a.xml_tpl

然后运行make -n,我在控制台上得到了这个:

./generate a.xml_tpl a.xml-subpart1_tpl a.xml-subpart2_tpl -o a.xml
./generate a.tex_tpl a.tex-subpart1_tpl -o a.tex

为什么要进行第二次扩展?

如果不进行第二次扩展,则必须在依赖项中添加 $(wildcard %*_tpl),因为使用 $$,通配符函数将永远不会执行。相反,make 会将$$(wildcard..) 字面意思 视为依赖项,这显然是错误的。

好的,所以$(wildcard %*_tpl) 将在 make first 运行穿过那条线时进行评估(这是“第一次扩展”)。 那时% 还没有任何价值,所以wildcard 大概会在命令行中执行类似于ls %*_tpl 的操作。

出于速度的原因,make 默认情况下不会让您有机会在第一次扩展期间进行任何评估。如果您想要稍后的机会,您必须指定.SECONDEXPANSION,这将打开第二个扩展处理。 Make 仍然像往常一样执行第一次扩展。这就是为什么你需要$$(wildcard:在第一次扩展期间它被转换为$(wildcard。在第二次扩展时,make 看到 $(wildcard %*_tpl)% 替换为实际的词干,然后使用实际的词干而不是文字 % 执行 wildcard 函数。

为什么在模式规则中使用$(TARGETS)

模式规则可以写成:

%: $$(wildcard %*_tpl)
    ./generate $^ -o $@

没有$(TARGETS)。然而,这条规则不会做任何事情,因为它将是一个"match-anything rule"。基本上,如果 make 表面上采用这样的规则,那么计算成本将是巨大的,而且很可能Makefile 的作者真的 打算将此规则应用于任何文件。所以这样的规则是有限制的,在这里的 Makefile 中就没有用了。

添加$(TARGETS) 使其成为static pattern rule,这不是匹配任何规则。在目标模式前面添加$(TARGETS) 表明该规则仅适用于这些 目标,而不适用于其他目标。

【讨论】:

  • 这可能是“hacky” :D 但听起来很棒!我今天没有更多时间测试它,但我会尽快回复您,谢谢!编辑:你的makefile开头的评论是正确的,如果我不清楚,对不起。唯一不同的是generate 脚本只接受一个文件($@_tpl)作为输入,并且这个文件有指令(jinja 的include)包含其他文件(-subpart_tpl 的),但这不应该是个问题。
  • 这不是 cleaner 除非是 GNU Make 大师。
  • 我真的很喜欢它,尤其是它通过查看_tpl 文件在那里生成可用目标列表的事实。顺便说一下,我不需要将这些目标限制为 xml 和 tex 文件,因此我什至可以将其缩短为 TARGETS:=$(patsubst %_tpl,%,$(wildcard *_tpl))。不过我有两个问题:1) 是什么让.SECONDEXPANSION 成为强制性的? wildcard 函数必须在 % 扩展之后扩展吗? 2) 我在$(TARGETS) 规则中找不到关于额外: %: 的文档,它是什么? (我猜它会从目标列表中创建一个模式规则?)。
  • @Dettorer 如果没有第二次扩展,您将输入$(wildcard %*_tpl),因为使用$$wildcard 函数将永远不会执行。现在 $(wildcard %*_tpl) 将在 make first 运行该行时进行评估。 那时 % 还没有价值 所以wildcard 大概会在命令行中执行类似于ls %*_tpl 的操作。在第二次扩展时,% 被替换为实际的词干,then wildcard 被执行。
  • @Louis 谢谢,我(模糊地)是这么想的。 '$(TARGETS):' 之后的额外 '%:' 是什么?它记录在哪里? (或者这个概念的名称是什么?)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多