GNUMakefile 规则从单个源文件生成几个目标

我正在尝试执行以下操作。有一个程序,称为 foo-bin,它接受一个输入文件并生成两个输出文件。对于这个问题,一个愚蠢的 Makefile 规则是:

file-a.out file-b.out: input.in
foo-bin input.in file-a.out file-b.out

However, this does not tell make in any way that both targets will be generated simultaneously. That is fine when running make in serial, but will likely cause trouble if one tries make -j16 or something equally crazy.

问题是是否存在为这种情况编写适当的 Makefile 规则的方法?显然,它会生成一个 DAG,但是 GNU make 手册并没有指定如何处理这种情况。

Running the same code twice and generating only one result is out of the question, because the computation takes time (think: hours). Outputting only one file would also be rather difficult, because frequently it is used as an input to GNUPLOT which doesn't know how to handle only a fraction of a data file.

29828 次浏览

我会解决如下问题:

file-a.out: input.in
foo-bin input.in file-a.out file-b.out


file-b.out: file-a.out
#do nothing
noop

在这种情况下,并行 make 将“序列化”创建 a 和 b,但是因为创建 b 不做任何事情,所以不需要花费时间。

诀窍是对多个目标使用模式规则。在这种情况下,make 将假定这两个目标都是通过单次调用该命令创建的。

all: file-a.out file-b.out
file-a%out file-b%out: input.in
foo-bin input.in file-a$*out file-b$*out

This difference in interpretation between pattern rules and normal rules doesn't exactly make sense, but it's useful for cases like this, and it is documented in the manual.

这个技巧可以用于任意数量的输出文件,只要它们的名称有一些公共的子字符串供 %匹配。(在本例中,公共子字符串是“ .”)

我就是这么做的。首先,我总是把先决条件和食谱分开。然后在这种情况下,一个新的目标来做菜谱。

all: file-a.out file-b.out #first rule


file-a.out file-b.out: input.in


file-a.out file-b.out: dummy-a-and-b.out


.SECONDARY:dummy-a-and-b.out
dummy-a-and-b.out:
echo creating: file-a.out file-b.out
touch file-a.out file-b.out

The first time:
1. 我们尝试构建 file-a. out,但是首先需要执行哑 a-and-b. out,因此 make 运行哑 a-and-b. out 配方。
2. 我们尝试建立文件 b. out,哑巴 a-and-b. out 是最新的。

第二次及以后的时间:
1.我们尝试建立文件 A。Out: make 查看先决条件,正常先决条件是最新的,次要先决条件丢失,因此忽略。
2.我们尝试建立文件 B。Out: make 查看先决条件,正常先决条件是最新的,次要先决条件丢失,因此忽略。

Make 没有任何直观的方法来做到这一点,但有两个不错的解决方案。

首先,如果所涉及的目标有一个共同的主干,您可以使用一个前缀规则(使用 GNUmake)。也就是说,如果你想修正以下规则:

object.out1 object.out2: object.input
foo-bin object.input object.out1 object.out2

你可以这样写:

%.out1 %.out2: %.input
foo-bin $*.input $*.out1 $*.out2

(使用 pattern-rule 变量 $* ,它代表模式的匹配部分)

如果你想要可移植到非 GNU Make 实现,或者你的文件不能被命名来匹配模式规则,还有另一种方法:

file-a.out file-b.out: input.in.intermediate ;


.INTERMEDIATE: input.in.intermediate
input.in.intermediate: input.in
foo-bin input.in file-a.out file-b.out

This tells make that input.in.intermediate won't exist before make is run, so its absence (or its timestamp) won't cause foo-bin to be run spuriously. And whether either file-a.out or file-b.out or both are out-of-date (relative to input.in), foo-bin will be only run once. You can use .SECONDARY instead of .INTERMEDIATE, which will instruct make NOT to delete a hypothetical file name input.in.intermediate. This method is also safe for parallel make builds.

第一行的分号很重要。它为该规则创建了一个空菜谱,这样 Make 就知道我们实际上是在更新 file-a。出去,然后归档。(谢谢@siulkiulki 和其他指出这一点的人)

这是基于@deemer 的第二个答案,它不依赖于模式规则,并且它修复了我在使用变通方法时遇到的一个问题。

file-a.out file-b.out: input.in.intermediate
@# Empty recipe to propagate "newness" from the intermediate to final targets


.INTERMEDIATE: input.in.intermediate
input.in.intermediate: input.in
foo-bin input.in file-a.out file-b.out

我本想在@deemer 的回答中添加这个评论,但是我不能,因为我刚刚创建了这个帐户,而且没有任何声誉。

说明: 空菜谱是必要的,以便让 Make 做适当的簿记,以标记 file-a.outfile-b.out已重建。如果你还有一个依赖于 file-a.out的中间目标,那么 Make 会选择不构建外部中间目标,声称:

No recipe for 'file-a.out' and no prerequisites actually changed.
No need to remake target 'file-a.out'.

作为@deemer 答案的一个扩展,我把它推广为一个函数。

sp :=
sp +=
inter = .inter.$(subst $(sp),_,$(subst /,_,$1))


ATOMIC=\
$(eval s1=$(strip $1)) \
$(eval target=$(call inter,$(s1))) \
$(eval $(s1): $(target) ;) \
$(eval .INTERMEDIATE: $(target) ) \
$(target)


$(call ATOMIC, file-a.out file-b.out): input.in
foo-bin input.in file-a.out file-b.out

细目:

$(eval s1=$(strip $1))

从第一个参数中去掉任何前/后的空格

$(eval target=$(call inter,$(s1)))

将变量目标创建为唯一值以用作中间目标。对于本例,值将为。Inter.file-a.Out _ file-b.出去。

$(eval $(s1): $(target) ;)

为输出创建一个空配方,并将唯一目标作为依赖项。

$(eval .INTERMEDIATE: $(target) )

将唯一目标声明为中间件。

$(target)

使用对唯一目标的引用完成,这样该函数可以直接在配方中使用。

还要注意的是,这里使用 eval 是因为 eval 扩展为空,所以函数的完全扩展只是唯一的目标。

这个功能的灵感来源于 GNU Make 中的原子规则

在 GNU Make 4.3(2020年1月19日)之后,您可以使用“分组显式目标”。

file-a.out file-b.out &: input.in
foo-bin input.in file-a.out file-b.out

GNU Make 的新闻文件显示:

新特性: 显式目标分组

模式规则总是能够通过一次对配方的调用生成多个目标。现在可以声明显式规则通过一次调用生成多个目标。若要使用此方法,请将规则中的“ :”标记替换为“ & :”。中的“分组目标”检测此特性搜索。特性特殊变量。执行工作由 Kaz Kylheku 提供 & lt; Kaz@Kylheku.com & gt;

若要防止使用 make -jN并行多次执行具有多个输出的规则,请使用 .NOTPARALLEL: outputs。 就你而言:

.NOTPARALLEL: file-a.out file-b.out


file-a.out file-b.out: input.in
foo-bin input.in file-a.out file-b.out

在4.3版本中,GNU make 支持所谓的“分组目标”:

  • 新特性: 显式目标分组

    模式规则始终具有生成多个目标的能力 现在可以声明一个 显式规则通过一次调用生成多个目标 使用此方法,将规则中的“ :”标记替换为“ & :” 在. FEATURES 特别功能中搜索“分组目标” 变量。

    实现由 Kaz Kylheku 地址@hide 提供

Release notes: https://lists.gnu.org/archive/html/make-w32/2020-01/msg00001.html

文件: https://www.gnu.org/software/make/manual/make.html#Multiple-Targets

DR: 使用 &:而不是 :