如何编写Makefile pattern rule实现多子目录源文件生成单一目标
Makefile模式规则实现多源文件合并生成单个目标文件
需求
需要编写Makefile模式规则,将多个源文件合并生成单个目标文件。与常规单源文件场景不同,源文件存放在与目标同名的文件夹中(如目标lib1.so对应lib1/文件夹),源文件路径为src/[可变目录]/[库名]/形式,需忽略公共前缀src/及中间的可变目录,最终生成bin/[库名].so目标。
目录结构
需要生成的目标文件:
bin/lib1.so bin/lib2.so bin/lib3.so
源目录结构:
- src/
- foo/
- lib1/
- a.c
- b.c
- lib1/
- bar/
- lib2/
- d.c
- e.c
- lib3/
- f.c
- g.c
- lib2/
- foo/
现有Makefile问题
当前通过make all构建,显式指定目标(实际使用shell find自动查找,此处简化):
all: bin/lib1.so bin/lib2.so bin/lib3.so
尝试编写的模式规则无法正常工作:
bin/%.so: $$(wildcard src/*/%/*.c) # 测试规则: echo $< > $@
问题核心:现有方案仅适配1:1文件映射场景,无法处理当前多源文件对应单个目标的n:1映射,且规则中的通配符展开时机错误,导致无法匹配到对应源文件。
期望结果
测试规则时,每个目标文件需记录对应所有源文件路径:
- bin/lib1.so内容:
src/foo/lib1/a.c src/foo/lib1/b.c
- bin/lib2.so内容:
src/bar/lib2/d.c src/bar/lib2/e.c
- bin/lib3.so内容:
src/bar/lib3/f.c src/bar/lib3/g.c
解决方案
方案1:使用二次展开模式规则
利用Make的.SECONDEXPANSION特性,让依赖中的通配符在匹配到具体目标后再展开,确保每个%能正确替换为库名:
# 启用二次展开,让规则依赖在模式匹配后再解析 .SECONDEXPANSION: all: bin/lib1.so bin/lib2.so bin/lib3.so # 模式规则:匹配bin/%.so,依赖为对应库目录下的所有.c文件 bin/%.so: $$(wildcard src/*/%/*.c) # 将所有依赖源文件列表写入目标,用tr替换空格为换行 echo $^ | tr ' ' '\n' > $@ # 确保bin目录存在,作为所有目标的前置条件 $(all): | bin bin: mkdir -p bin
方案2:自动收集目标+静态模式规则
适合需要自动发现所有库目标的场景,无需手动指定all中的目标:
# 自动查找src下所有以lib开头的目录 LIB_SRCDIRS := $(shell find src -type d -name "lib*") # 将源目录路径转换为bin下的目标文件路径 LIB_TARGETS := $(patsubst src/%/lib%, bin/lib%.so, $(LIB_SRCDIRS)) all: $(LIB_TARGETS) # 静态模式规则:为每个目标匹配对应的源文件 bin/lib%.so: # 查找当前库对应的所有.c文件 $(eval SRCS := $(shell find src -path "*lib$*/*.c")) # 将源文件列表写入目标 echo $(SRCS) | tr ' ' '\n' > $@ # 确保bin目录存在 $(LIB_TARGETS): | bin bin: mkdir -p bin
说明
- 方案1中,
$^代表规则的所有依赖文件列表,通过tr ' ' '\n'将空格分隔的路径转换为每行一个,符合期望格式。 - 方案2通过
find自动发现所有库目录,适合库数量较多或目录结构动态变化的场景,无需手动维护all目标列表。
内容的提问来源于stack exchange,提问作者user66554
相关产品推荐
相关产品推荐

