Makefile中的复杂模式规则

4
我有以下makefile,用于从一些模板生成文件,生成的文件有两个可能的扩展名:
%.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}不是有效的规则名称,而且在依赖项列表中不能使用$@,但我希望你能够以任何其他(更干净?)的方式实现。

3
我无法想到任何能够使这个更加“清洁”的方法,但这取决于你对“清洁”的定义。你可以将食谱放入一个变量中:GENERATE = ./generate $@_tpl -o $@,然后在每个命令中使用$(GENERATE)变量;这将减少需要更新食谱的地方数量。 - MadScientist
确实更好,谢谢!我不太知道该怎么称呼“更干净”,我只能想到一些模糊的描述,比如“简短而不粗糙”。 - Dettorer
1个回答

5

在我看来,这似乎是你要寻找的功能:

#
# 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第一次遍历该行时(即“第一次展开”)进行评估。此时%尚未具有值,因此wildcard将大致执行类似于在命令行上执行 ls %*_tpl 的操作。

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

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

模式规则可以写成:

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

没有 $(TARGETS) 的话,这个规则什么也做不了,因为它将成为一个“匹配任何内容的规则”。基本上,如果make直接采取这样的规则,那么计算成本将是巨大的,很可能撰写Makefile的作者并不真正意味着将此规则应用于任何文件。因此,这种规则带有限制,在这个Makefile中使其毫无用处。
添加$(TARGETS)会将其变成一个静态模式规则,而不是一个匹配任何内容的规则。在目标模式前添加$(TARGETS)告诉make该规则仅适用于这些目标,而不是其他任何目标。

可能算是“hacky”的做法:D,但听起来很不错!今天我已经没有时间测试了,但一旦测试完我会尽快回复你的,谢谢!编辑:您makefile开头的评论是正确的,如果我表述不清楚请原谅。唯一不同的是generate脚本只接受一个文件($@_tpl)作为输入,而这个文件有包含其他文件的指令(jinja的include),但这不应该成为问题。 - Dettorer
我实际上非常喜欢它,特别是它通过查看 _tpl 文件来生成可用目标列表的功能。顺便说一下,我不需要将这些目标限制为 xml 和 tex 文件,因此我甚至可以将其缩短为 TARGETS:=$(patsubst %_tpl,%,$(wildcard *_tpl))。但我有两个问题:1)是什么使 .SECONDEXPANSION 成为必需项?是 wildcard 函数必须在 % 扩展之后才能扩展吗?2)我找不到关于 $(TARGETS) 规则中额外的 :%: 的文档,这是什么?(我猜它从目标列表创建了一个模式规则?)。 - Dettorer
1
@Dettorer 如果没有第二次扩展,你会放置 $(wildcard %*_tpl),因为使用 $$wildcard 函数将永远不会执行。现在 $(wildcard %*_tpl) 将在 make 第一次运行该行时进行评估。此时 % 没有值,所以 wildcard 大致上会像在命令行中执行 ls %*_tpl 一样。在第二次扩展时,% 被替换为实际的词干,然后执行 wildcard - Louis
@Louis 谢谢,这正是我(模糊地)想的。但是在“$(TARGETS):”后面的“%:”是什么意思?它在哪里有记录?(或者这个概念的名称是什么?) - Dettorer
啊,我找到了,一个静态模式规则 - Dettorer
显示剩余3条评论

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