GNU Makefile 多目录源文件编译中filter函数的必要性探究
核心认知偏差
你对patsubst函数的规则存在关键误解:patsubst并不会丢弃输入文本中不匹配指定模式的内容,它的实际处理逻辑是——对输入里所有空白分隔的单词逐次判断:匹配模式就按规则替换,不匹配就原样保留在输出结果中,不会自动过滤。
无filter时的实际执行过程
你修改后的Makefile里,foreach循环会依次遍历easy、medium两个源目录,每次循环会对全量SOURCES列表执行一遍patsubst转换,每一步的实际输出如下:
- 第一次循环,
dir = easy- 传入patsubst的输入是全量SOURCES:
easy/a.cpp medium/b.cpp - 匹配规则是
easy/%.cpp,替换规则是build/%.out - 逐词处理结果:
easy/a.cpp匹配规则,替换为build/a.outmedium/b.cpp不匹配当前规则,原样保留
- 本次循环输出:
build/a.out medium/b.cpp
- 传入patsubst的输入是全量SOURCES:
- 第二次循环,
dir = medium- 传入patsubst的输入还是全量SOURCES:
easy/a.cpp medium/b.cpp - 匹配规则切换为
medium/%.cpp,替换规则是build/%.out - 逐词处理结果:
medium/b.cpp匹配规则,替换为build/b.outeasy/a.cpp不匹配当前规则,原样保留
- 本次循环输出:
easy/a.cpp build/b.out
- 传入patsubst的输入还是全量SOURCES:
- 两次循环的输出直接拼接,最终得到错误的TARGETS:
build/a.out medium/b.cpp easy/a.cpp build/b.out,和你实际运行看到的结果完全吻合。
filter的作用逻辑
filter函数的功能是从输入文本中提前筛出符合指定模式的条目,直接删除所有不匹配的内容,从源头避免不匹配的文件流入patsubst的处理流程。
加上filter后,每次循环的处理逻辑会变成:
- 当
dir = easy时,先通过filter easy/%,$(SOURCES)从全量源文件里筛出所有属于easy目录的文件,输入到patsubst的文本就只剩easy/a.cpp,处理后输出build/a.out,没有其他残留文件 - 当
dir = medium时,先筛出所有属于medium目录的文件,输入到patsubst的文本只剩medium/b.cpp,处理后输出build/b.out - 两次循环的结果拼接后,就得到了完全符合预期的TARGETS列表:
build/a.out build/b.out
常见误区提醒
很多人初用Make内置函数都会踩这个坑:误以为patsubst会自动过滤输入里不匹配模式的条目,实际上它只会对匹配的项做规则替换,不匹配的项会原封不动留在输出结果里。在多目录批量路径转换的场景下,如果不提前用filter把不属于当前循环目录的文件筛掉,就会出现多余的源文件残留进目标列表的问题。
内容的提问来源于stack exchange,提问作者Zois Tasoulas
相关产品推荐
相关产品推荐

