一个配方和并行执行的多个目标

时间:2013-11-06 20:44:31

标签: parallel-processing makefile gnu-make

我有一个包含代码生成器的项目,该代码生成器只需一次调用代码生成器就可以从一个输入文件生成几个.c和.h文件。我有一个规则,其中.c和.h文件作为多个目标,输入文件作为先决条件,配方是代码生成器的调用。然后我有进一步的规则来编译和链接生成的.c文件。

这适用于-j因子为1,但如果我增加j因子,我发现我得到了代码生成器的多次调用,直到-j因子或预期目标文件的数量,以最小者为准。这很糟糕,因为代码生成器的多次调用可能会导致由于生成的代码被多次写入而导致失败。

我不会在这里发布我的实际(大)代码,但我能够构建一个似乎表现出相同行为的小例子。

Makefile看起来像这样:

output.concat: output5 output4 output3 output2 output1
    cat $^ > $@

output1 output2 output3 output4 output5: input
    ./frob input

clean:
    rm -rf output*

在本例中,我编写了一个简单的shell脚本frob,而不是代码生成器,它从一个输入文件生成多个输出文件:

#!/bin/bash

for i in {1..5}; do
    {
    echo "This is output${i}, generated from ${1}. input was:"
    cat ${1}
    } > output${i}
done

当我使用非统一-j因子运行此Makefile时,我得到以下输出:

$ make -j2 
./frob input
./frob input
cat output5 output4 output3 output2 output1 > output.concat
$

我们在这里看到./frob被调用了两次,这很糟糕。有没有什么方法可以构造这个规则,使得配方只被调用一次,即使使用非统一-j因子?

我考虑过更改规则,以便只有一个预期的输出文件是目标,然后添加另一个没有配方的规则,使其目标是剩余的预期输出文件,先决条件是第一个预期的输出文件。但我不确定这会起作用,因为我不知道我是否可以保证生成文件的顺序,因此可能最终会产生循环依赖。

6 个答案:

答案 0 :(得分:16)

这就是定义make的工作方式。像这样的规则:

foo bar baz : boz ; $(BUILDIT)

完全等同于编写这三条规则:

foo : boz ; $(BUILDIT)
bar : boz ; $(BUILDIT)
baz : boz ; $(BUILDIT)

无法(在GNU make中)定义具有所需特征的显式规则;也就是说,一次调用配方将构建所有三个目标。

但是,如果您的输出文件和输入文件共享一个共同基础,您可以编写如下的模式规则:

%.foo %.bar %.baz : %.boz ; $(BUILDIT)

奇怪的是,对于具有多个目标的隐式规则,GNU make假定对配方的单个调用将构建所有目标,并且它将完全按照您的需要运行。

答案 1 :(得分:7)

@MadScientist的回答是有希望的 - 我想我可以使用它。与此同时,我一直在玩这个,并提出了一个不同的可能解决方案,正如问题所暗示的那样。我可以将规则分为两部分:

INPUT_FILE = input
OUTPUT_FILES = output5 output4 output3 output2 output1
OUTPUT_FILE1 = $(firstword $(OUTPUT_FILES))
OUTPUT_FILES_REST = $(wordlist 2,$(words $(OUTPUT_FILES)),$(OUTPUT_FILES))

$(OUTPUT_FILE1): $(INPUT_FILE)
    ./frob $<
    touch $(OUTPUT_FILES_REST)

$(OUTPUT_FILES_REST): $(OUTPUT_FILE1)

只提供一个输出文件作为目标可以解决可能存在的并行问题。然后我们将这个输出文件作为其余输出文件的先决条件。重要的是,在frob配方中,除了第一个输出文件外,我们触摸所有输出文件,因此我们保证第一个输出文件的时间戳比其他所有文件都要长。

答案 2 :(得分:7)

从输入文件a b с正确生成并更新并行make -j中的多个目标i1 i2

all: a b c
.INTERMEDIATE: d
a: d
b: d
c: d
d: i1 i2
    cat i1 i2 > a 
    cat i1 i2 > b
    cat i1 i2 > c
  • 如果缺少任何a,b,c,则会重新生成伪目标d。永远不会创建文件d; d的单一规则避免了对配方的几次并行调用。

  • .INTERMEDIATE确保丢失的文件d不会触发d食谱。

  • 书中多个目标的其他一些方法&#34; John Graham-Cumming - GNU Make Book&#34; p.92-96。

答案 3 :(得分:1)

make 4.3(2020 年 1 月)起,make 允许 grouped targets。根据 docs,如果任何目标丢失或过时,以下内容只会更新所有目标一次:

foo bar biz &: baz boz
        echo $^ > foo
        echo $^ > bar
        echo $^ > biz

答案 4 :(得分:0)

Ivan Zaentsev的答案几乎为我工作,但以下问题除外。仅在运行并行make(-j2或更高版本)时,当更改生成文件的先决条件时,才可以成功地重新生成生成的文件,但是,不会重建依赖于生成文件的后续目标。

除了对中间目标(d)的依赖性之外,我发现的解决方法是为生成的文件提供配方(简单的复制命令):

d: i1 i2
    cat i1 i2 > a.gen 
    cat i1 i2 > b.gen
    cat i1 i2 > c.gen
.INTERMEDIATE: d
a.gen : d
b.gen : d
c.gen : d

a: a.gen d
    cp $< $@
b: b.gen d
    cp $< $@
c: c.gen d
    cp $< $@

e: a b c
    some_command $@ $^

线索是在没有解决方法的情况下运行时,make的调试输出(尽管重建了a,b,c时,make -j2并未重建'e'):

       Finished prerequisites of target file `a'.
       Prerequisite `d' of target `a' does not exist.
      No recipe for `a' and no prerequisites actually changed.
      No need to remake target `a'.

答案 5 :(得分:-1)

这似乎是对我有用的解决方案(主要解决方案使用@Ivan Zaentsev,而指出问题的使用@alexei)。它与原始方法类似,但有一个重大变化。与其生成临时文件(如建议的*.gen),它只是touch依赖于INTERMEDIATE文件的文件。 :

default: f

.INTERMEDIATE: temp
a b c: temp
    touch $@

temp: i1 i2
    echo "BUILD: a b c"
    cat i1 i2 > a
    cat i1 i2 > b
    cat i1 i2 > c

e: a b c
    echo "BUILD: e"
    touch $@

f: e
    echo "BUILD: f"
    touch $@