如何用GNU Make在_build目录保留源码结构编译目标文件?
解决GNU Make跨目录编译并按需创建子目录的问题
问题背景
现有项目结构:
Makefile main.cpp subfolder/ a.cpp b.cpp etc...
需求是将所有.cpp文件编译到_build目录中对应的.o文件,保留原目录结构,编译后结构如下:
Makefile _build final_binary main.o subfolder/ a.o b.o main.cpp subfolder/ a.cpp b.cpp
问题分析
你之前的写法核心问题在于对GNU Make中%模式匹配的理解偏差:
- 第一种写法里的
$(OBJS) $(TARGET): %: | $(dir %),%会匹配完整路径(如_build/subfolder/a.o),但模式规则的匹配逻辑要求目标和依赖的%位置对应,这种写法无法正确触发目录创建。 - 第二种写法里的
| $(dir _build/%.o)错误,因为$(dir)是Make函数,展开时%未被模式替换,最终只会得到_build/,无法创建子目录。
正确解决方案
方案一:编译时直接创建目录(简单直观)
这是最直接的实现方式,在编译每个.o文件前,自动创建其所在的目录:
# 匹配所有源文件 SRCS := main.cpp $(wildcard subfolder/*.cpp) # 生成对应的目标对象文件路径 OBJS := $(patsubst %.cpp,_build/%.o,$(SRCS)) TARGET := _build/final_binary # 链接生成最终可执行文件 $(TARGET): $(OBJS) $(CXX) $(CXXFLAGS) $^ $(LIBS) -o $@ $(LDPATHS) # 编译规则:将.cpp文件编译到_build目录对应路径,按需创建目录 _build/%.o: %.cpp @mkdir -p $(@D) $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ # 可选:清理生成文件 clean: rm -rf _build
关键说明:
$(patsubst %.cpp,_build/%.o,$(SRCS)):正确将每个源文件路径映射到_build下的对应路径,比如subfolder/a.cpp会被转换为_build/subfolder/a.o。$(@D):是Make的自动变量,代表目标文件($@)所在的目录,比如_build/subfolder/a.o对应的$(@D)是_build/subfolder/。@mkdir -p $(@D):-p参数会自动创建所有不存在的父目录,@用来屏蔽命令本身的输出,仅在编译该.o文件时执行,完全实现按需创建目录。
方案二:使用仅序依赖(符合Make依赖逻辑)
如果希望严格遵循Make的依赖管理规则,将目录创建作为独立的仅序依赖(目录更新不会触发重新编译),可以用以下写法:
SRCS := main.cpp $(wildcard subfolder/*.cpp) OBJS := $(patsubst %.cpp,_build/%.o,$(SRCS)) TARGET := _build/final_binary $(TARGET): $(OBJS) $(CXX) $(CXXFLAGS) $^ $(LIBS) -o $@ $(LDPATHS) # 为每个_build/%.o添加目录仅序依赖 _build/%.o: %.cpp | $(@D) $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ # 模式规则:匹配任意目录,按需创建 %/: @mkdir -p $@
关键说明:
| $(@D):仅序依赖,Make会先确保目录$(@D)存在,再执行编译命令,但目录的时间戳更新不会触发.o文件的重新编译。%/::通用模式规则,匹配任意目录路径,当需要某个目录时,Make会自动调用这个规则创建它,实现按需创建。
内容的提问来源于stack exchange,提问作者ABu
相关产品推荐
相关产品推荐

