我正在尝试执行以下操作。有一个程序,称之为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不知道如何只处理数据文件的一小部分。
答案 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.out
和file-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