Makefile 为什么make要在先决条件之前运行配方?

Makefile 为什么make要在先决条件之前运行配方?,makefile,gnu-make,Makefile,Gnu Make,我有一个make文件,其中包含一些模式规则,作为另一个规则的先决条件。下面是一个最起码的例子,说明了我所困惑的症状: .PHONY: clean default one two default: clean one two clean: @rm -f {one,two}.{a,b} one two: %: %.a %.b @echo TARGET $@ PREREQUISITES $^ %.a %.b: @echo Prereq $@ @touch $@

我有一个make文件,其中包含一些模式规则,作为另一个规则的先决条件。下面是一个最起码的例子,说明了我所困惑的症状:

.PHONY: clean default one two

default: clean one two

clean:
    @rm -f {one,two}.{a,b}

one two: %: %.a %.b
    @echo TARGET $@ PREREQUISITES $^

%.a %.b:
    @echo Prereq $@
    @touch $@
运行此命令时,我期望的输出是:

预请求1.a
预请求1.b
目标一个先决条件一个.a一个.b
预请求2.a
预请求2.b
目标2先决条件2.a 2.b

相反,只有第一个先决条件得到构建,
make
给了我以下信息:

预请求1.a
目标一个先决条件一个.a一个.b
预请求2.a
目标2先决条件2.a 2.b

正如您所看到的,配方本身正确地解析了这些,并且知道它应该在两个先决条件之后构建,但是先决条件规则实际上还没有运行

顺便说一句,我在这里对第二项使用的是仅限订单的先决条件,但这并不重要:无论哪种方式都会出现相同的问题

如果我再次运行同一个目标,那么它将构建第二个先决条件。换句话说,两次传球最终让我得到了我需要的结果:

$ make clean
$ make one
Prereq one.a
TARGET one PREREQUISITES one.a one.b
$ make one
Prereq one.b
TARGET one PREREQUISITES one.a one.b
它第一次运行时,只有第一个先决条件存在,剩下的
一个已损坏。在第二次传递时,存在
one.a
,因此它决定构建
one.b
。既然这两个先决条件都存在,我的
one
将正确构建

如果我将先决条件目标变体拼写为两个单独的配方(分别对
%a:
%.b
模式重复相同的块),那么两个先决条件都将为每个目标构建。然而,这会使我的文件变得更加复杂,我想知道为什么会出现这种情况



<>这使文件有一些,但我已经把这个问题孤立到一个可重复的情况,所以我不认为它的其他特质是这里的问题。

GNUmake具有我们可以考虑的一点“怪癖”,在这个意义上,多个目标模式规则不能像几个规则一样工作。在这种情况下,规则仅由单个目标触发一次,Make希望规则同时生成所有目标。从:

模式规则可能有多个目标。与普通规则不同的是,这并不像许多具有相同先决条件和配方的不同规则那样起作用。如果模式规则有多个目标,make知道规则的配方负责生成所有目标。配方只执行一次以生成所有目标。[……]

因此,这里发生的是:

  • 检查
    one
    :从模式
    %:%.a%.b
    中查找
    one.a
    one.b
  • one.a
    one.b
    查找一个模式规则
  • 执行配方,目标(
    $@
    )设置为
    one.a
    (触发规则的目标)
  • one.a
    one.b
    标记为已更新,即使配方仅创建
    one.a
  • 认为对
    one
    的所有依赖性都已满足,并继续对
    two
    执行相同的操作
  • 再次调用时,为什么要触摸不同的文件,是因为现在
    one.a
    two.a
    是最新的。因此触发规则的目标变成
    one.b
    two.b
    。如果在第二次调用Make之前只删除
    one.a
    ,它将创建
    one.a
    two.b

    因此,您至少有三种可能的解决方案。一个是详细说明那些行为像你期望的那样的目标,你说这不是一个好的解决方案,但无论如何,这里值得一提。另一种方法是将
    %.a%.b
    分为两条规则,这两条规则的效果相同,但考虑到您的需要可能更容易。第三个是按照Make的期望,在同一配方中创建两个目标,例如,您可以在配方中使用stem:

    %.a %.b:
            @echo Processing target $@ from stem $*
            touch $*.a $*.b
    

    在研究这个问题时,我注意到的另一件事是,您的MCVE的
    clean
    坏了。默认情况下,Make将使用
    /bin/sh
    ,这不会扩展
    {a,b}
    之类的内容。您可以设置
    SHELL=/bin/bash
    来更改它。

    可能重复@giusti这是一个只需订单的先决条件问题。完全相同的结果只显示正常依赖项。抱歉。我错了。谢谢,这很有帮助。我理解你建议的第一和第三种选择。第一个是可行的,但令人讨厌,因为这是一个巨大的配方,有很多逻辑,我不想重复。第三个是不可能的,因为有些make运行将生成一个而不是另一个,而有些将同时生成两个(这里涉及测试、CI和部署)。并非所有的运行都能够同时为a和b提供先决条件。我完全不明白你的第二个选择。这与第一个选项有什么不同呢?为了避免重复,只需使用
    define
    将配方放入多行变量中即可
    endef
    然后您可以在所有规则的配方中使用该变量,而不是多次将其全部写出来。第一个建议是
    one.aOne.b2.a2.b:
    。如果你想扩展到
    three.a
    three.b
    ,你也需要把它们列为目标。第二个规则是一个用于
    %.a
    ,另一个用于
    %.b
    。您不需要修改任何东西来扩展
    three.{a,b}
    。当然,如果您有一个依赖于
    one.a
    two.a
    的规则,您将再次遇到相同的问题。因此,只有当您能够确保每个目标和规则的
    前缀%suffix
    只匹配一个依赖项时,此建议才适用。谢谢,这很有帮助。我不明白你的第一个选择。在我的例子中,我实际上可以这样做,因为我可以通过编程生成所有可能排列的列表。遗憾的是,我发现这并不能像我的计划那样解决问题