GNU Makefile规则从单个源文件生成一些目标

时间:2010-06-04 10:57:05

标签: makefile

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

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

但是,这并不能以任何方式告诉make两个目标将同时生成。在串行运行make时这很好,但如果尝试make -j16或同样疯狂的话,可能会造成麻烦。

问题是,是否存在为这种情况编写正确的Makefile规则的方法?显然,它会生成DAG,但不知何故,GNU make手册没有说明这种情况如何被处理。

运行相同的代码两次并仅生成一个结果是不可能的,因为计算需要时间(想想:小时)。仅输出一个文件也相当困难,因为它常常被用作GNUPLOT的输入,GNUPLOT不知道如何只处理数据文件的一小部分。

8 个答案:

答案 0 :(得分:122)

诀窍是使用具有多个目标的模式规则。在这种情况下,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

模式规则和普通规则之间的这种解释差异并不完全有意义,但它对于这样的情况很有用,并且在手册中有记录。

此技巧可用于任意数量的输出文件,只要它们的名称具有匹配的%的一些公共子字符串即可。 (在这种情况下,公共子串是“。”)

答案 1 :(得分:51)

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

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

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

你可以这样写:

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

(使用模式规则变量$ *,代表模式的匹配部分)

如果您希望可移植到非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

这告诉make在make运行之前不会存在input.in.intermediate,因此它的缺失(或它的时间戳)不会导致foo-bin虚假运行。无论file-a.out还是file-b.out或者两者都是过时的(相对于input.in),foo-bin只会运行一次。您可以使用.SECONDARY而不是.INTERMEDIATE,它将指示make NOT删除假设的文件名input.in.intermediate。这种方法对于并行make构建也是安全的。

第一行的分号很重要。它为该规则创建了一个空的配方,因此Make知道我们将真正更新file-a.out和file-b.out(感谢@siulkiulki和其他指出这一点的人)

答案 2 :(得分:14)

在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的NEWS文件说:

新功能:分组的显式目标

模式规则始终能够通过一次调用配方来生成多个目标。现在可以声明一个显式规则通过一次调用即可生成多个目标。要使用此功能,请在规则中将“:”标记替换为“&:”。要检测此功能,请在.FEATURES特殊变量中搜索“ grouped-target”。由Kaz Kylheku

贡献的实施

答案 3 :(得分:10)

我会按如下方式解决:

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将'serialize'创建a和b但是因为创建b不会做任何事情所以它会花费时间。

答案 4 :(得分:7)

这是基于@ 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'.

答案 5 :(得分:3)

我就是这样做的。首先,我总是将预先请求与食谱分开。然后在这种情况下一个新的目标来做配方。

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

第一次:
1.我们尝试构建file-a.out,但是dummy-a-and-b.out需要先做,所以make运行dummy-a-and-b.out配方。
2.我们尝试构建file-b.out,dummy-a-and-b.out是最新的。

第二次及以后的时间:
 1.我们尝试构建file-a.out:查看先决条件,正常先决条件是最新的,缺少次要先决条件,因此被忽略。
 2.我们尝试构建file-b.out:查看先决条件,正常的先决条件是最新的,缺少次要先决条件,因此被忽略。

答案 6 :(得分:0)

作为@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.out。

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

为输出创建一个空配方,以唯一目标为目标。

$(eval .INTERMEDIATE: $(target) ) 

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

$(target)

完成对唯一目标的引用,以便可以在配方中直接使用此功能。

还要注意,此处使用eval是因为eval扩展为零,因此功能的完全扩展只是唯一的目标。

必须归功于此功能的灵感来源Atomic Rules in GNU Make

答案 7 :(得分:-1)

要防止并行执行多个输出为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