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


105

我正在尝试执行以下操作。有一个名为的程序,它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不知道如何仅处理数据文件的一小部分。


1
Automake对此具有相关文档:gnu.org/software/automake/manual/html_node/…–
Frank

Answers:


12

我将解决如下:

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不会执行任何操作,因此无需花费时间。


16
实际上,这是有问题的。如果file-b.out按照说明创建,然后被神秘删除,make将无法重新创建它,因为它file-a.out仍然存在。有什么想法吗?
makeaurus 2010年

如果您神秘地删除了,file-a.out那么上述解决方案将非常有效。这表明存在黑客行为:当仅存在a或b时,应对依赖项进行排序,以使丢失的文件显示为的输出input.in。几个$(widcard...)S,$(filter...)s和$(filter-out...)小号等,应做的伎俩。啊。
bobbogo 2011年

3
PS foo-bin显然会首先创建一个文件(使用微秒分辨率),因此,当两个文件都存在时,必须确保具有正确的依赖关系排序。
bobbogo 2011年

2
@bobbogo或者,或者touch file-a.outfoo-bin调用后添加为第二个命令。
康纳·哈里斯

这是一个可能的解决方法:touch file-b.out在第一条规则中添加为第二条命令,并在第二条规则中复制命令。如果一个文件丢失或早于input.in该文件,则该命令将只执行一次,并用file-b.outfile-a.out
chqrlie

128

诀窍是使用具有多个目标的模式规则。在这种情况下,make将假定通过一次调用命令即可创建两个目标。

全部:file-a.out file-b.out
文件a%out文件b%out:input.in
    foo-bin input.in文件-a $ * out文件-b $ * out

模式规则和常规规则在解释上的差异并不完全有意义,但对于此类情况很有用,并且已在手册中进行了说明。

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


2
这实际上是不正确的。您的规则指定使用指定的命令从input.in生成与这些模式匹配的文件,但没有任何地方说它们是同时创建的。如果实际上是并行make运行,则将同时运行同一命令两次。
makeaurus

47
请先尝试一下,然后再说不起作用;-) GNU make手册说:“模式规则可能有多个目标。与普通规则不同,它不具有相同的先决条件和命令而充当许多不同的规则。如果一个模式规则有多个目标,知道规则的命令负责建立所有目标。这些命令只执行一次就可以构成所有目标。” gnu.org/software/make/manual/make.html#Pattern-Intro
slowdog 2010年

4
但是,如果我输入的内容完全不同怎么办?,说foo.in和bar.src吗?模式规则是否支持空模式匹配?
grwlf

2
@dcow:输出文件仅需要共享一个子字符串(即使是单个字符,例如“。”也足够),而不必共享前缀。如果没有通用的子字符串,则可以使用richard和deemer描述的中间目标。@grwlf:嘿,这意味着可以完成“ foo.in”和“ bar.src”:foo%in bar%src: input.in:-)
慢狗

1
如果将各个输出文件保存在变量中,则配方可以写为:($(subst .,%,$(varA) $(varB)): input.in使用GNU make 4.1进行测试)。
stefanct

55

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和其他指出这一点的人)


7
不错,像.INTERMEDIATE这样的工具看起来不错。另请参见 描述问题的Automake文章(但他们尝试以可移植的方式解决问题)。
grwlf13年

3
这是我看到的最好的答案。其他特殊目标也可能引起关注:gnu.org/software/make/manual/html_node/Special-Targets.html
Arne Babenhauserheide

2
@deemer这不适用于OSX。(GNU Make 3.81)
Jorge Bucaran 2015年

2
我一直在尝试,并且我已经注意到并行构建的细微问题---依赖关系不一定总是在中间目标之间正确传播(尽管-j1构建一切都很好)。同样,如果makefile被打断并且留下input.in.intermediate文件,则它会很混乱。
大卫

3
这个答案有错误。可以使并行构建完美地运行。您只需更改file-a.out file-b.out: input.in.intermediatefile-a.out file-b.out: input.in.intermediate ;请参阅stackoverflow.com/questions/37873522/…即可了解更多详细信息。
siulkilulki '18

10

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

8

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

新功能:分组显式目标

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


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:查看先决条件,常规先决条件是最新的,次要先决条件缺失,因此被忽略。


1
这已破了。如果删除,file-b.outmake将无法重新创建它。
bobbogo 2011年

添加所有内容:file-a.out file-b.out作为第一条规则。就像在命令行上删除了file-b.out并在没有目标的情况下运行make一样,它不会重建它。
ctrl-alt-delor

@bobbogo固定:请参见上面的评论。
ctrl-alt-delor 2014年

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扩展为零,因此功能的完全扩展只是唯一的目标。

必须归功于此功能的灵感来自GNU Make中的Atomic Rules


0

要防止带有多个输出的规则并行执行多个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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.