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

发布于 2024-09-04 01:37:32 字数 523 浏览 4 评论 0原文

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

I am attempting to do the following. There is a program, call it foo-bin, that takes in a single input file and generates two output files. A dumb Makefile rule for this would be:

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

However, this does not tell make in any way that both targets will be generated simultaneously. That is fine when running make in serial, but will likely cause trouble if one tries make -j16 or something equally crazy.

The question is whether there exists a way to write a proper Makefile rule for such a case? Clearly, it would generate a DAG, but somehow the GNU make manual does not specify how this case could be handled.

Running the same code twice and generating only one result is out of the question, because the computation takes time (think: hours). Outputting only one file would also be rather difficult, because frequently it is used as an input to GNUPLOT which doesn't know how to handle only a fraction of a data file.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

雪落纷纷 2024-09-11 01:37:33

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

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

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

The trick is to use a pattern rule with multiple targets. In that case make will assume that both targets are created by a single invocation of the command.

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

This difference in interpretation between pattern rules and normal rules doesn't exactly make sense, but it's useful for cases like this, and it is documented in the manual.

This trick can be used for any number of output files as long as their names have some common substring for the % to match. (In this case the common substring is ".")

痴者 2024-09-11 01:37:33

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

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

Make doesn't have any intuitive way to do this, but there are two decent workarounds.

First, if the targets involved have a common stem, you can use a prefix rule (with GNU make). That is, if you wanted to fix the following rule:

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

You could write it this way:

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

(Using the pattern-rule variable $*, which stands for the matched part of the pattern)

If you want to be portable to non-GNU Make implementations or if your files can't be named to match a pattern rule, there is another way:

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

This tells make that input.in.intermediate won't exist before make is run, so its absence (or its timestamp) won't cause foo-bin to be run spuriously. And whether either file-a.out or file-b.out or both are out-of-date (relative to input.in), foo-bin will be only run once. You can use .SECONDARY instead of .INTERMEDIATE, which will instruct make NOT to delete a hypothetical file name input.in.intermediate. This method is also safe for parallel make builds.

The semicolon on the first line is important. It creates an empty recipe for that rule, so that Make knows we will really be updating file-a.out and file-b.out (thanks @siulkiulki and others who pointed this out)

一抹苦笑 2024-09-11 01:37:33

在 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 贡献的实现[电子邮件受保护]>

After GNU Make 4.3 (19th Jan. 2020) you can use “grouped explicit targets”. Replace : with &:.

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

The NEWS file of the GNU Make says:

New feature: Grouped explicit targets

Pattern rules have always had the ability to generate multiple targets with a single invocation of the recipe. It's now possible to declare that an explicit rule generates multiple targets with a single invocation. To use this, replace the ":" token with "&:" in the rule. To detect this feature search for 'grouped-target' in the .FEATURES special variable. Implementation contributed by Kaz Kylheku <[email protected]>

鹤舞 2024-09-11 01:37:33

我将按如下方式解决它:

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

I would solve it as follows :

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

file-b.out: file-a.out
    #do nothing
    noop

In this case parallel make will 'serialize' creating a and b but since creating b does not do anything it takes no time.

远山浅 2024-09-11 01:37:33

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

This is based on @deemer's second answer which does not rely on pattern rules, and it fixes an issue I was experiencing with nested uses of the workaround.

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

I would have added this as a comment to @deemer's answer, but I can't because I just created this account and don't have any reputation.

Explanation: The empty recipe is needed in order to allow Make to do the proper bookkeeping to mark file-a.out and file-b.out as having been rebuilt. If you have yet another intermediate target which depends on file-a.out, then Make will choose to not build the outer intermediate, claiming:

No recipe for 'file-a.out' and no prerequisites actually changed.
No need to remake target 'file-a.out'.
望笑 2024-09-11 01:37:33

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

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:make 查看先决条件,正常先决条件是最新的,次要先决条件丢失,因此被忽略。
2. 我们尝试构建 file-b.out:make 查看先决条件,正常先决条件是最新的,次要先决条件丢失,因此被忽略。

This is how I do it. First I always separate pre-requesits from the recipes. Then in this case a new target to do the recipe.

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

The first time:
1. We try to build file-a.out, but dummy-a-and-b.out needs doing first so make runs the dummy-a-and-b.out recipe.
2. We try to build file-b.out, dummy-a-and-b.out is up to date.

The second and subsequent time:
1. We try to build file-a.out: make looks at prerequisites, normal prerequisites are up to date, secondary prerequisites are missing so ignored.
2. We try to build file-b.out: make looks at prerequisites, normal prerequisites are up to date, secondary prerequisites are missing so ignored.

晨敛清荷 2024-09-11 01:37:33

从版本 4.3 开始,GNU make 支持所谓的“分组目标”:

  • 新功能:对显式目标进行分组

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

    由 Kaz Kylheku address@hidden 贡献的实现

发行说明:https ://lists.gnu.org/archive/html/make-w32/2020-01/msg00001.html

文档:https://www.gnu.org/software/make/manual/make.html#Multiple-Targets

TL;DR:使用 &: 而不是 :

As of version 4.3, GNU make supports so-called "grouped targets":

  • New feature: Grouped explicit targets

    Pattern rules have always had the ability to generate multiple targets with
    a single invocation of the recipe. It's now possible to declare that an
    explicit rule generates multiple targets with a single invocation. To
    use this, replace the ":" token with "&:" in the rule. To detect
    this feature search for 'grouped-target' in the .FEATURES special
    variable.

    Implementation contributed by Kaz Kylheku address@hidden

Release notes: https://lists.gnu.org/archive/html/make-w32/2020-01/msg00001.html

Documentation: https://www.gnu.org/software/make/manual/make.html#Multiple-Targets

TL;DR: use &: instead of :.

温馨耳语 2024-09-11 01:37:33

作为@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 中的原子规则从。

As an extension to @deemer's answer, I have generalised it into a function.

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

Breakdown:

$(eval s1=$(strip $1))

Strip any leading/trailing whitespace from the first argument

$(eval target=$(call inter,$(s1)))

Create a variable target to a unique value to use as the intermediate target. For this case, the value will be .inter.file-a.out_file-b.out.

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

Create an empty recipe for the outputs with the unique target as a depedency.

$(eval .INTERMEDIATE: $(target) ) 

Declare the unique target as an intermediate.

$(target)

Finish with a reference to the unique target so this function can be used directly in a recipe.

Also note the use of eval here is because eval expands to nothing so the full expansion of the function is just the unique target.

Must give credit to Atomic Rules in GNU Make which this function is inspired from.

萌酱 2024-09-11 01:37:33

多年来我一直在与 make 中的多个输出作斗争。复杂的 make 构建是一种糟糕的体验,而且充满了令人难以置信的陷阱;特别是如果您想要并行构建,特别是如果您不能使用通配符。

所以有点令人沮丧的是,我只阅读了 https: //www.gnu.org/software/automake/manual/html_node/Multiple-Outputs.html,它建议使用以下模式,该模式实际上似乎有效:

out1 out2 out3: input
    build output files here

out2: out1
out3: out1

其工作方式是第一条规则相当于this:

out1: input
    build output file here
out2: input
    build output file here
out3: input
    build output file here

这会导致在 DAG 中生成三个规则,每个文件一次。 但是,在每个规则之后它都会重新设置输入,因此如果您正在进行单线程构建,它只会运行一次 --- make 将看到 out2 和 out3 已构建并跳过再次运行规则。

但这一切都会随着并行构建而崩溃,因为您很可能同时运行三个规则,可能会导致文件损坏。这就是其他两个规则的用武之地:

out2: out1
out3: out1

这迫使序列化构建。尝试构建任何这些文件都会使 out1 首先构建,而构建 out2out3 则被迫等待,直到 的规则>out1 完成。 然后 make 重新统计文件,看到 out2out3 已经构建,并再次跳过规则。

重要提示:这些仅依赖关系的规则必须位于主规则之后,否则一切都会崩溃。我不明白为什么。

同样重要的是; 不要这样做:

# do not do this
out1: input
    build output file here
out2: out1
out3: out1

当您通过 ^Cing 中断构建时(或者如果规则失败),make 将尝试删除任何部分输出的文件。它通过查看规则输出来知道它们是什么,在本例中我们只告诉它 out1 是输出。这意味着它不会删除 out2out3,可能会导致您的构建中留下损坏的文件,其时间戳比其源更新......这将破坏您的构建。我自己也遇到过这个。

I've been fighting multiple outputs in make for years. Complicated builds in make is a wretched experience, and so incredibly full of gotchas; especially if you want parallel builds, and especially if you can't use wildcards.

So it's kinda frustrating that I only just read the writeup at https://www.gnu.org/software/automake/manual/html_node/Multiple-Outputs.html, which suggests the following pattern, which actually seems to work:

out1 out2 out3: input
    build output files here

out2: out1
out3: out1

The way this works is that the first rule is equivalent to this:

out1: input
    build output file here
out2: input
    build output file here
out3: input
    build output file here

This causes three rules to be generated in the DAG, once for each file. However, after each rule it'll restat the inputs, so if you're doing a single-threaded build it'll only get run once --- make will see that out2 and out3 have been built and skip running the rule again.

But this all falls down with parallel builds, because you'll be very likely to get the three rules running at once, probably resulting in corrupted files. This is where the other two rules come in:

out2: out1
out3: out1

This forces make to serialise the builds. Trying to build any of these files will make out1 get built first, and building out2 or out3 is forced to wait until the rule for out1 to complete. Then make restats the files, sees that out2 and out3 have been built, and skips the rule again.

Important: These dependency-only rules must go after the main rule, or else all hell breaks loose. I do not understand why.

Also important; do not do this:

# do not do this
out1: input
    build output file here
out2: out1
out3: out1

When you interrupt the build by ^Cing it (or if the rule fails) then make will try to delete any partially-outputted files. It knows what they are by looking at the rule outputs, and in this example we've only told it that out1 is output. This means it won't delete out2 or out3, potentially resulting in corrupted files left in your build with timestamps newer than their source... which will wreck your build. I just ran into this myself.

你怎么敢 2024-09-11 01:37:33

要防止使用 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

To prevent parallel multiple execution of a rule with multiple outputs with make -jN, use .NOTPARALLEL: outputs.
In your case :

.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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文