GNU Makefile 规则从单个源文件生成一些目标
我正在尝试执行以下操作。有一个名为 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
诀窍是使用具有多个目标的模式规则。在这种情况下,make 将假定两个目标都是通过命令的单次调用创建的。
模式规则和普通规则之间的这种解释差异并不完全有意义,但它对于这样的情况很有用,并且它记录在手册中。
此技巧可用于任意数量的输出文件,只要它们的名称具有一些公共子字符串供
%
匹配即可。 (在本例中,公共子字符串是“.”)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.
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 ".")Make 没有任何直观的方法来做到这一点,但有两个不错的解决方法。
首先,如果涉及的目标具有共同的词干,则可以使用前缀规则(使用 GNU make)。也就是说,如果你想修复以下规则:
你可以这样写:(
使用模式规则变量 $*,它代表模式的匹配部分)
如果你想移植到非 GNU Make实现或者如果您的文件无法命名以匹配模式规则,还有另一种方法:
这告诉 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:
You could write it this way:
(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:
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)
在 GNU Make 4.3(2020 年 1 月 19 日)之后,您可以使用“分组显式目标”。将
:
替换为&:
。GNU Make 的新闻文件说:
After GNU Make 4.3 (19th Jan. 2020) you can use “grouped explicit targets”. Replace
:
with&:
.The NEWS file of the GNU Make says:
我将按如下方式解决它:
在这种情况下,并行 make 将“序列化”创建 a 和 b,但由于创建 b 不执行任何操作,因此不需要时间。
I would solve it as follows :
In this case parallel make will 'serialize' creating a and b but since creating b does not do anything it takes no time.
这是基于 @deemer 的第二个答案,该答案不依赖于模式规则,它解决了我在解决方法的嵌套使用中遇到的问题。
我本来可以将此作为评论添加到@deemer的答案中,但我不能,因为我刚刚创建了这个帐户并且没有任何声誉。
说明:需要空配方,以便 Make 能够进行正确的记录,将
file-a.out
和file-b.out
标记为已重建。如果您还有另一个依赖于 file-a.out 的中间目标,那么 Make 将选择不构建外部中间目标,并声明: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.
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
andfile-b.out
as having been rebuilt. If you have yet another intermediate target which depends onfile-a.out
, then Make will choose to not build the outer intermediate, claiming:我就是这样做的。首先,我总是将预先请求与食谱分开。然后在这种情况下用一个新目标来执行配方。
第一次:
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.
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.
从版本 4.3 开始,GNU make 支持所谓的“分组目标”:
发行说明: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":
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:
.作为@deemer 答案的扩展,我将其概括为一个函数。
细分:
从第一个参数中去除任何前导/尾随空格
创建一个变量目标,将其设置为唯一值以用作中间目标。对于这种情况,该值将为.inter.file-a.out_file-b.out。
使用唯一目标作为依赖项,为输出创建一个空配方。
将唯一目标声明为中间体。
最后引用唯一目标,以便可以在配方中直接使用此函数。
另请注意,此处使用 eval 是因为 eval 扩展为空,因此函数的完整扩展只是唯一目标。
必须归功于此函数的灵感来源 GNU Make 中的原子规则从。
As an extension to @deemer's answer, I have generalised it into a function.
Breakdown:
Strip any leading/trailing whitespace from the first argument
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.
Create an empty recipe for the outputs with the unique target as a depedency.
Declare the unique target as an intermediate.
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.
多年来我一直在与 make 中的多个输出作斗争。复杂的 make 构建是一种糟糕的体验,而且充满了令人难以置信的陷阱;特别是如果您想要并行构建,特别是如果您不能使用通配符。
所以有点令人沮丧的是,我只阅读了 https: //www.gnu.org/software/automake/manual/html_node/Multiple-Outputs.html,它建议使用以下模式,该模式实际上似乎有效:
其工作方式是第一条规则相当于this:
这会导致在 DAG 中生成三个规则,每个文件一次。 但是,在每个规则之后它都会重新设置输入,因此如果您正在进行单线程构建,它只会运行一次 --- make 将看到
out2 和
out3
已构建并跳过再次运行规则。但这一切都会随着并行构建而崩溃,因为您很可能同时运行三个规则,可能会导致文件损坏。这就是其他两个规则的用武之地:
这迫使序列化构建。尝试构建任何这些文件都会使
out1
首先构建,而构建out2
或out3
则被迫等待,直到的规则>out1
完成。 然后 make 重新统计文件,看到out2
和out3
已经构建,并再次跳过规则。重要提示:这些仅依赖关系的规则必须位于主规则之后,否则一切都会崩溃。我不明白为什么。
同样重要的是; 不要这样做:
当您通过 ^Cing 中断构建时(或者如果规则失败),make 将尝试删除任何部分输出的文件。它通过查看规则输出来知道它们是什么,在本例中我们只告诉它
out1
是输出。这意味着它不会删除out2
或out3
,可能会导致您的构建中留下损坏的文件,其时间戳比其源更新......这将破坏您的构建。我自己也遇到过这个。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:
The way this works is that the first rule is equivalent to this:
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
andout3
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:
This forces make to serialise the builds. Trying to build any of these files will make
out1
get built first, and buildingout2
orout3
is forced to wait until the rule forout1
to complete. Then make restats the files, sees thatout2
andout3
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:
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 deleteout2
orout3
, 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.要防止使用
make -jN
并行多次执行具有多个输出的规则,请使用.NOTPARALLEL:outputs
。在你的情况下:
To prevent parallel multiple execution of a rule with multiple outputs with
make -jN
, use.NOTPARALLEL: outputs
.In your case :