You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Makefile模式规则中wildcards无法匹配目录文件的问题

Makefile中wildcard与模式规则%结合失效问题

我试图用Make从特定文件和共享文件构建一组Markdown文档,原Makefile代码如下:

PKGS := foo bar baz app
DEFAULT := DEPENDENCIES.md RULES.md

BUILD := .build
VPATH := .:packages

$(BUILD)/%.history.md: $(DEFAULT) $(wildcard %/RULES.*.md) $(foreach f,$(DEFAULT),%/$(f))
        echo "!! BUILDING NOW $@"
        echo $^
        echo

对应的目录结构:

.
├── ABSTRACT.md
├── DEPENDENCIES.md
├── IMPLEMENTATION.md
├── Makefile
├── packages
│   ├── foo
│   │   ├── ABSTRACT.md
│   │   ├── DEPENDENCIES.md
│   │   ├── IMPLEMENTATION.md
│   │   ├── RULES.0.md -> ../COMMON.md
│   │   └── RULES.md
│   ├── COMMON.md
│   ├── bar
│   │   ├── ABSTRACT.md
│   │   ├── DEPENDENCIES.md
│   │   ├── IMPLEMENTATION.md
│   │   └── RULES.md
│   ├── baz
│   │   ├── ABSTRACT.md
│   │   ├── DEPENDENCIES.md
│   │   ├── IMPLEMENTATION.md
│   │   └── RULES.md
│   └── app
│       ├── ABSTRACT.md
│       ├── DEPENDENCIES.md
│       ├── IMPLEMENTATION.md
│       ├── RULES.0.md -> ../COMMON.md
│       └── RULES.md
└── RULES.md

问题现象

$(wildcard %/RULES.*.md)始终展开为空列表,执行make .build/foo.history.md时,预期依赖项包含packages/foo/RULES.0.md,但实际输出中缺少该文件。

为排查问题,我添加了测试目标:

# 模式规则中%作为目录的情况正常工作
%.static: %/RULES.0.md
        echo $@ -> $^

%.semi: %/RULES.*.md
        echo $@ -> $^

%.x: $(wildcard packages/%/RULES.*.md)
        echo $@ -> $^

# 单独使用*通配符正常工作
app.y: $(wildcard packages/app/RULES.*.md)
        echo $@ -> $^

# 两者结合失效:预期目标为foo.z时,依赖展开为foo/RULES.*.md(通过VPATH查找)
%.z: $(wildcard %/RULES.*.md)
        echo $@ -> $^

测试结果:

$ make app.static --just-print
echo app.static -> packages/app/RULES.0.md
$ make app.semi --just-print
make: *** No rule to make target 'app.semi'.  Stop.
$ make app.x --just-print
echo app.x ->
$ make app.y --just-print
echo app.y -> packages/app/RULES.0.md
$ make app.z --just-print
echo app.z ->

我尝试过.SECONDEXPANSION但未成功,且看到有人不推荐使用该方法。另外,$(foreach f,$(DEFAULT),%/$(f))能正常展开为packages/app/DEPENDENCIES.md packages/app/RULES.md,这更让我困惑为何$(wildcard)无法正常工作,特此求助解决方法。


问题原因与解决方案

核心原因

wildcard函数是在Makefile解析阶段执行的,此时模式规则中的%还只是一个通配符字符,并未被替换为实际的包名(比如foo或app),所以$(wildcard %/RULES.*.md)会去查找名为%的目录下的RULES.*.md文件,自然找不到任何内容。

而$(foreach f,$(DEFAULT),%/$(f))能正常工作,是因为这个表达式的展开是在目标匹配阶段完成的:当Make匹配到具体目标(比如.build/app.history.md)后,会先把%替换为app,再执行foreach展开,此时路径是合法的。

解决方案1:正确使用.SECONDEXPANSION

之前尝试失败大概率是转义处理不当,开启二次扩展后,需要用$$(wildcard ...)来延迟wildcard的执行时机,让%先被替换为实际包名,再执行通配符匹配:

.SECONDEXPANSION:
$(BUILD)/%.history.md: $(DEFAULT) $$(wildcard %/RULES.*.md) $(foreach f,$(DEFAULT),%/$(f))
        echo "!! BUILDING NOW $@"
        echo $^
        echo

这里的$$是为了转义:第一次解析时$$会变成$,二次扩展时$(wildcard %/RULES.*.md)中的%已经被替换为实际包名,此时执行wildcard就能正确找到目标文件。

解决方案2:预生成所有依赖文件列表(无需二次扩展)

先提前扫描所有包的RULES.*.md文件,再通过模式过滤对应到目标:

PKGS := foo bar baz app
DEFAULT := DEPENDENCIES.md RULES.md

BUILD := .build
VPATH := .:packages

# 预扫描所有包的RULES.*.md文件
ALL_RULES_VARIANTS := $(wildcard packages/*/RULES.*.md)
# 为每个目标匹配对应的RULES变体文件
$(BUILD)/%.history.md: $(DEFAULT) $(filter packages/%/RULES.*.md,$(ALL_RULES_VARIANTS)) $(foreach f,$(DEFAULT),%/$(f))
        echo "!! BUILDING NOW $@"
        echo $^
        echo

这种方式通过filter函数,把预扫描到的文件中对应当前%包的部分筛选出来,作为依赖项。


内容的提问来源于stack exchange,提问作者Kevin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 04:05:54