12

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

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

我不打算在这里发布我的实际(大)代码,但我已经能够构建一个看起来展示相同行为的小示例。

Makefile 看起来像这样:

输出.concat:输出5输出4输出3输出2输出1
    猫 $^ > $@

输出1 输出2 输出3 输出4 输出5:输入
    ./frob 输入

干净的:
    rm -rf 输出*

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

#!/bin/bash

对于 {1..5} 中的 i;做
    {
    echo "这是输出 ${i},从 ${1} 生成。输入是:"
    猫 ${1}
    } > 输出${i}
完毕

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

$使-j2
./frob 输入
./frob 输入
cat output5 output4 output3 output2 output1 > output.concat
$

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

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

4

6 回答 6

18

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

foo bar baz : boz ; $(BUILDIT)

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

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

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

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

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

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

于 2013-11-06T21:03:24.507 回答
12

从输入文件a b с并行正确生成和更新多个目标:make -ji1 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配方。

  • 在“John Graham-Cumming - GNU Make Book”p.92-96 一书中针对多个目标的一些其他方法。

于 2017-12-23T09:53:00.797 回答
8

@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 配方中,我们会触及除第一个文件之外的所有输出文件,因此我们可以保证第一个文件的时间戳比其他所有文件都旧。

于 2013-11-07T02:15:09.387 回答
2

make 4.3(2020 年 1 月)开始,make 允许grouped targets. 根据文档,如果任何目标丢失或过时,以下内容将仅更新所有目标一次:

foo bar biz &: baz boz
        echo $^ > foo
        echo $^ > bar
        echo $^ > biz
于 2021-02-17T10:26:47.027 回答
1

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'.
于 2019-01-03T16:05:44.443 回答
-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 $@
于 2020-01-21T18:43:30.093 回答