Makefile 忽略模式中包含子目录的模式规则

Makefile ignores pattern rule with subdirectory in pattern

考虑以下目录结构:

.
  bar/
    foo/
      test.ext1
  _foo/

我用以下规则编写了一个 makefile:

_%.ext2: bar/%.ext1 # % should match foo/test
    echo $*

然而 运行 make _foo/test.ext2 给出:

make: *** No rule to make target '_foo/test.ext2'.  Stop.

当我查看 make -d _foo/test.ext2 时,它甚至没有考虑上述规则,即它直接跳转到尝试内置规则。我不明白这里发生了什么,有人可以向我解释一下吗?

编辑: 我希望能够从 bar 中的任意文件生成,例如:

保证_foo_baz存在。

规则 _%.ext2: bar/%.ext1,当在目标 _foo/test.ext2 上调用时,将匹配词干 (%) 等于 foo 的规则。要使规则生效,需要有一个文件(或规则)来创建 bar/%.ext1 = bar/foo.ext1.

根据 OP,输入文件是 bar/foo/test1.ext1.

尝试调整依赖关系:

_%/test.ext2: bar/%/test.ext1
    echo $*

我通过将以下内容添加到 makefile 中设法避免了这个问题:

TARGETS := $(patsubst bar/%,_%,$(patsubst %.ext1,%.ext2,$(wildcard bar/**/*.ext1)))

$(TARGETS): _%.ext2: bar/%.ext1 # % should match foo/test
    echo $*

这基本上是预先构建目标(从 bar/ 中可用的文件),然后将规则应用于它们。但是,我不确定为什么模式规则适用于这种情况而不适用于原始情况,所以我仍然希望得到解释。

这在manual中有解释:

When the target pattern does not contain a slash (and it usually does not), directory names in the file names are removed from the file name before it is compared with the target prefix and suffix.

When prerequisites are turned into file names, the directories from the stem are added at the front, while the rest of the stem is substituted for the %

因此,对于像 _%.ext2 这样的模式,下划线与文件名而不是目录名匹配。所以它不匹配 _foo/test.ext2 但它会匹配 foo/_test.ext2(并查找 foo/bar/test.ext1)。自己看看:

$ cat Makefile
_%.ext2: bar/%.ext1
        echo Making $@ from $<

$ make -dr _foo/test.ext2
...
Considering target file '_foo/test.ext2'.
 File '_foo/test.ext2' does not exist.
 Looking for an implicit rule for '_foo/test.ext2'.
 No implicit rule found for '_foo/test.ext2'.
 Finished prerequisites of target file '_foo/test.ext2'.
Must remake target '_foo/test.ext2'.
make: *** No rule to make target '_foo/test.ext2'.  Stop.

$ make -dr foo/_test.ext2
...
Considering target file 'foo/_test.ext2'.
 File 'foo/_test.ext2' does not exist.
 Looking for an implicit rule for 'foo/_test.ext2'.
 Trying pattern rule with stem 'test'.
 Trying implicit prerequisite 'foo/bar/test.ext1'.
 Trying pattern rule with stem 'test'.
 Trying implicit prerequisite 'foo/bar/test.ext1'.
 Looking for a rule with intermediate file 'foo/bar/test.ext1'.
  Avoiding implicit rule recursion.
 No implicit rule found for 'foo/_test.ext2'.
 Finished prerequisites of target file 'foo/_test.ext2'.
Must remake target 'foo/_test.ext2'.
make: *** No rule to make target 'foo/_test.ext2'.  Stop.

最简单的修复方法是让目标包含斜杠。如果你可以将输出移动到别处,你可以这样做:

$ cat Makefile
OBJDIR := obj
$(OBJDIR)/_%.ext2: bar/%.ext1
        echo Making $@ from $<

$ make obj/_foo/test.ext2
echo Making obj/_foo/test.ext2 from bar/foo/test.ext1
Making obj/_foo/test.ext2 from bar/foo/test.ext1

甚至

$ cat Makefile
_/%.ext2: bar/%.ext1
        echo Making $@ from $<

$ make _/foo/test.ext2
echo Making _/foo/test.ext2 from bar/foo/test.ext1
Making _/foo/test.ext2 from bar/foo/test.ext1

如果做不到,可以尝试全路径引用,即:

$ cat Makefile
$(CURDIR)/_%.ext2: bar/%.ext1
        echo Making $@ from $<

$ make $(pwd)/_foo/test.ext2
echo Making /path/to/curdir/_foo/test.ext2 from bar/foo/test.ext1
Making /path/to/curdir/_foo/test.ext2 from bar/foo/test.ext1

请注意,使用 ./ 不起作用,因为 GNU 从目标中生成前导 ./ 的条带,使模式再次不匹配:

$ cat Makefile
./_%.ext2: bar/%.ext1
        echo Making $@ from $<

$ make -dr ./_foo/test.ext2
...
Considering target file '_foo/test.ext2'.
 File '_foo/test.ext2' does not exist.
 Looking for an implicit rule for '_foo/test.ext2'.
 No implicit rule found for '_foo/test.ext2'.
 Finished prerequisites of target file '_foo/test.ext2'.
Must remake target '_foo/test.ext2'.
make: *** No rule to make target '_foo/test.ext2'.  Stop.

如果你想让你的同行评审同事紧张,你也可以使用一些二次扩展魔法来生成适当的先决条件名称,例如:

$ cat Makefile
percent := %
.SECONDEXPANSION:
%.ext2: bar/$$(patsubst _$$(percent),$$(percent),$$(@D))/$$*.ext1
        echo Making $@ from $<

$ make _foo/test.ext2
echo Making _foo/test.ext2 from bar/foo/test.ext1
Making _foo/test.ext2 from bar/foo/test.ext1

注意:% 必须通过 $(percent) 变量引用,否则 GNU make 解析器可能会抱怨或误解 patsubst 函数。

您的最终方法有效,因为您的最终解决方案是静态模式规则。它不应用相同的 'strip directory' 方法,而只是匹配字符串。