【问题标题】:GNU Makefile rule generating a few targets from a single source fileGNU Makefile 规则从单个源文件生成几个目标
【发布时间】:2011-02-27 17:49:37
【问题描述】:

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

【问题讨论】:

标签: makefile


【解决方案1】:

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

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

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

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

【讨论】:

  • 这实际上是不正确的。您的规则指定匹配这些模式的文件将使用指定的命令从 input.in 生成,但没有任何地方说它们是同时生成的。如果你真的并行运行它,make 将同时运行相同的命令两次。
  • 在你说它不起作用之前请先尝试一下 ;-) GNU make 手册说:“模式规则可能有多个目标。与普通规则不同,这不会像许多不同的规则一样起作用" gnu.org/software/make/manual/make.html#Pattern-Intro
  • 但是如果我有完全不同的输入呢?比如说 foo.in 和 bar.src?模式规则是否支持空模式匹配?
  • @dcow:输出文件只需要共享一个子字符串(即使是单个字符,例如“.”,也足够了)不一定是前缀。如果没有公共子字符串,您可以使用 Richard 和 deemer 所描述的中间目标。 @grwlf:嘿,这意味着可以完成“foo.in”和“bar.src”:foo%in bar%src: input.in :-)
  • 如果各自的输出文件保存在变量中,配方可以写成:$(subst .,%,$(varA) $(varB)): input.in(用 GNU make 4.1 测试)。
【解决方案2】:

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 NOT 删除假设的文件名 input.in.intermediate。此方法对于并行 make 构建也是安全的。

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

【讨论】:

  • 很好,看起来 .INTERMEDIATE 方法效果很好。另请参阅 Automake article 描述问题(但他们试图以可移植的方式解决它)。
  • 这是我看到的最好的答案。其他特殊目标也可能令人感兴趣:gnu.org/software/make/manual/html_node/Special-Targets.html
  • @deemer 这对我在 OSX 上不起作用。 (GNU Make 3.81)
  • 我一直在尝试这个,我注意到并行构建的细微问题 --- 依赖关系并不总是在中间目标上正确传播(尽管 -j1 构建一切都很好) .此外,如果 makefile 被中断并且它留下了 input.in.intermediate 文件,它会非常混乱。
  • 这个答案有错误。你可以让并行构建完美运行。您只需将file-a.out file-b.out: input.in.intermediate 更改为file-a.out file-b.out: input.in.intermediate ; 更多详细信息,请参阅stackoverflow.com/questions/37873522/…
【解决方案3】:

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

新功能:分组显式目标

模式规则始终能够通过单次调用配方生成多个目标。现在可以声明显式规则通过一次调用生成多个目标。要使用它,请将规则中的“:”标记替换为“&:”。要检测此功能,请在 .FEATURES 特殊变量中搜索“分组目标”。 Kaz Kylheku

贡献的实施

【讨论】:

    【解决方案4】:

    我会这样解决:

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

    【讨论】:

    • 其实这个是有问题的。如果file-b.out 是按照说明创建的,然后被神秘地删除,make 将无法重新创建它,因为file-a.out 仍然存在。有什么想法吗?
    • 如果你神秘地删除了file-a.out,那么上面的解决方案效果很好。这暗示了一个 hack:当只存在 a 或 b 中的一个时,应该对依赖项进行排序,以使丢失的文件显示为 input.in 的输出。一些$(widcard...) s、$(filter...) s、$(filter-out...) s 等,应该可以解决问题。呃。
    • P.S. foo-bin 显然会首先创建一个文件(使用微秒分辨率),因此当两个文件都存在时,您必须确保具有正确的依赖顺序。
    • @bobbogo 要么,要么在foo-bin 调用之后添加touch file-a.out 作为第二个命令。
    • 这里有一个潜在的解决方法:在第一条规则中添加touch file-b.out 作为第二条命令,并在第二条规则中复制这些命令。如果其中一个文件丢失或早于input.in,则该命令将只执行一次,并将重新生成两个文件,其中file-b.outfile-a.out 更新。
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      这就是我的做法。首先,我总是将预先请求与食谱分开。然后在这种情况下为新目标做配方。

      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查看先决条件,正常先决条件是最新的,次要先决条件缺失所以忽略。

      【讨论】:

      • 这个坏了。如果你删除了file-b.out,make就没有办法重新创建它。
      • 全部添加:file-a.out file-b.out 作为第一条规则。好像 file-b.out 被删除了,并且 make 在没有目标的情况下运行,在命令行上,它不会重建它。
      • @bobbogo 已修复:见上面的评论。
      【解决方案7】:

      作为@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 扩展为空,所以函数的完全扩展只是唯一的目标。

      必须感谢Atomic Rules in GNU Make,此函数的灵感来自于此。

      【讨论】:

        【解决方案8】:

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

        【讨论】:

        • .NOTPARALLEL 阻止所有目标的并行执行,无论其先决条件如何。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-31
        相关资源
        最近更新 更多