Makefile目标名称未知

时间:2016-12-15 19:06:40

标签: makefile

我试图编写一个Makefile,其中多个源文件(在我的情况下是markdown)创建多个目标文件(pdfs)。但是,生成的目标文件在文件名中有额外的字符无法预测(它恰好是在源代码中编码的版本号),但理想情况下Makefile不必读取源本身。

所以,例如:

file1.md => file1-v1.pdf
file2.md => file2-v2.pdf
...

我可以根据目标名称计算源名称(通过排除连字符后面的任何内容并添加.md),但无法计算给定源的目标名称。

是否可以编写一个仅构建源已更新的目标的Makefile?

3 个答案:

答案 0 :(得分:2)

这将是丑陋的,但它会起作用。

正如通常与Make一样,我们的问题分为两个问题:
1.构建目标列表
2.建立他们

假设我们有五个md文件映射到pdf文件(我们事先不知道其名称):

file1.md => file1-v1.pdf
file2.md => file2-v1.pdf
file3.md => file3-v1.pdf
file4.md => file4-v1.pdf
file5.md => file5-v1.pdf

我们不能将实际输出文件名用作目标,因为我们事先并不知道它们,但我们看到五个输入文件,并且知道我们必须为每个文件构建一个输出文件。 目前,虚拟目标名称将

file1-dummy.pdf: file1.md
    zap file1.md

当Make执行此规则时,它会生成文件file1-v1.pdf。它不会产生名为file1-dummy.pdf的文件这一事实令人不安,但并不是一个严重的问题。我们可以把它变成一个模式规则:

%-dummy.pdf: %.md
    zap $<

然后我们要做的就是将现有输入文件列表(file1.mdfile2.md,...)转换为虚拟目标列表(file1-dummy.pdf,{{1 },...),并构建它们。到目前为止,非常好。

但是假设某些输出文件已经存在。如果file2-dummy.pdf已经存在 - 并且比file2-v2.pdf更新 - 那么我们希望Make not rebuild it(通过尝试构建file2.md)。在这种情况下,我们希望file2-dummy.pdf位于目标列表中,其规则如下:

file2-v2.pdf

这个不容易变成模式规则,因为 Make不能很好地处理通配符,并且无法在单个短语中处理多个通配符,而不是没有很多笨拙。但是有一种方法可以编写一个涵盖这两种情况的规则。首先请注意,我们可以使用此kludge在连字符之前获取变量的一部分:

file2-v2.pdf: file2.md
    zap $<

有了这个,并且使用secondary expansion,我们可以编写一个适用于这两种情况的模式规则,并构建一个可以利用它的目标列表:

$(basename $(subst -,.,$(VAR)))

答案 1 :(得分:1)

Make&#39>算法始终以最终输出产品开始,然后向后移动到源文件,以查看需要更新的内容。

因此,您必须能够将最终输出产品枚举为目标名称,并将其与生成该输出的输入相关联,以使其生效。

这就是为什么make不是构建Java的好工具,例如,因为输出文件名不容易映射到输入文件名。

因此,您必须至少有一个目标/先决条件对,它是可派生的(对于隐式规则),或者是可状态的(对于显式规则) - 也就是说,在您编写makefile时已知。如果你没有,那么标记文件是你唯一的选择。请注意,除了已知的先决条件之外,您还可以添加额外生成的非衍生先决条件(例如,在编译器中,您可以添加头文件作为与源文件名无关的先决条件)。

答案 2 :(得分:1)

@ Beta的回答是有用且有帮助的,但我需要一个解决方案(使用GNU Make 4.1),当目标文件名与输入文件名没有相似之处时,例如,如果它是从其内容生成的。我想出了以下内容,它将每个文件与*.in匹配,并通过读取源文件的内容,附加.txt并将其用作要创建的文件名来创建文件。 (例如,如果test.in存在且包含foo,则makefile将创建foo.txt文件。)

SRCS := $(wildcard *.in)
.PHONY: all 

all: all_s

define TXT_template =
$(2).txt: $(1)
    touch $$@
ALL += $(2).txt
endef

$(foreach src,$(SRCS),$(eval $(call TXT_template, $(src), $(shell cat $(src)))))

.SECONDARY_EXPANSION:
all_s: $(ALL)

解释:

define块定义了从.in文件生成文本文件所需的配方。它是一个带两个参数的函数; $(1).in.文件,$(2)是其内容,或输出文件名的基础。将touch替换为输出的任何内容。我们必须使用$$@,因为eval会将所有内容扩展一次,但我们希望在此扩展后留下$@。由于我们必须收集所有生成的目标,因此我们知道所有的内容,ALL行将目标累积到一个变量中。 foreach行遍历每个源文件,使用源文件名和文件内容调用函数(即我们想要成为目标的名称,这里你通常使用生成的任何脚本所需的文件名),然后评估结果块,动态地将配方添加到make。感谢Beta解释.SECONDARY_EXPANSION;我需要它的原因并不完全清楚,但它有效(将all: $(ALL)置于顶部不起作用)。顶部的all:取决于底部all_s:的二次扩展,不知何故,这种魔法使其有效。欢迎评论。