在大型项目(如 U-Boot 或 Linux 内核)的开发中,通常支持两种编译模式:一种是将编译生成的目标文件(.o、.bin 等)与源代码混放在一起;另一种是将所有生成文件存放到指定的独立目录中。后者的优势在于能保持源码树的整洁,便于版本管理和多配置并行编译。
本文将通过一个简化的实验环境,演示如何编写一套支持"源码与目标文件分离"构建逻辑的 Makefile。
1. 核心构建逻辑设计
实现该功能的核心思路是:
- 检测用户是否指定了外部输出目录。
- 计算当前执行路径相对于源码根路径的偏移。
- 在输出目录下创建对应的镜像目录结构。
- 重定向编译器的输出路径。
2. 顶层 Makefile 实现
顶层 Makefile 负责初始化全局路径变量,并递归调用子目录的编译指令。
# 检查是否定义了输出目录 OUT_DIR
ifneq ($(OUT_DIR),)
# 尝试创建输出目录
$(shell [ -d $(OUT_DIR) ] || mkdir -p $(OUT_DIR))
# 获取输出目录的绝对路径
OBJ_ROOT := $(shell cd $(OUT_DIR) && /bin/pwd)
endif
# 定义源码根目录与目标文件根目录
SRC_ROOT := $(CURDIR)
OBJ_ROOT ?= $(SRC_ROOT)
export SRC_ROOT OBJ_ROOT
# 包含路径定义配置文件
include $(SRC_ROOT)/env_config.mk
# 定义需要构建的目标子模块
SUB_MODULES = math/calc.o core/main.o
# 为目标文件添加输出前缀
TARGET_OBJS := $(addprefix $(obj),$(SUB_MODULES))
.PHONY: all clean $(TARGET_OBJS)
all: $(obj)my_program
$(obj)my_program: $(TARGET_OBJS)
gcc $^ -o $@
$(TARGET_OBJS):
@# 提取子目录路径并进入该目录执行 make
$(MAKE) -C $(dir $(subst $(obj),,$@))
clean:
rm -rf $(OBJ_ROOT)/math $(OBJ_ROOT)/core $(obj)my_program
3. 环境路径配置文件 (env_config.mk)
该文件是实现路径重定向的关键。它被顶层和子目录 Makefile 共同引用,动态计算当前编译动作应当指向的输出位置。
# 判断是否为"分离编译"模式
ifneq ($(OBJ_ROOT),$(SRC_ROOT))
# 计算当前目录相对于源码根目录的相对路径
ifeq ($(CURDIR),$(SRC_ROOT))
CURRENT_REL_PATH :=
else
CURRENT_REL_PATH := $(subst $(SRC_ROOT)/,,$(CURDIR))
endif
# 定义当前模块的目标文件存放路径
obj := $(if $(CURRENT_REL_PATH),$(OBJ_ROOT)/$(CURRENT_REL_PATH)/,$(OBJ_ROOT)/)
# 定义当前模块的源码引用路径
src := $(if $(CURRENT_REL_PATH),$(SRC_ROOT)/$(CURRENT_REL_PATH)/,$(SRC_ROOT)/)
# 自动创建目标文件所需的物理目录结构
$(shell [ -d $(obj) ] || mkdir -p $(obj))
else
# 原地编译模式下,路径前缀为空
obj :=
src :=
endif
4. 子目录 Makefile 示例
以
math/Makefile 为例,其内部规则需通过
$(obj) 变量来标记输出文件的位置。
# 包含全局路径配置
include $(SRC_ROOT)/env_config.mk
# 定义该模块生成的目标
$(obj)calc.o: calc.c
gcc -c $< -I../include -o $@
clean:
rm -f $(obj)*.o
5. 编译实验操作
通过上述配置,用户可以灵活选择编译方式:
方式 A:原地构建(源码与目标文件混合)
在项目根目录下直接运行:
make
此时,
.o 文件会直接生成在各个子源码目录中。
方式 B:分离构建(指定输出目录)
在项目根目录下通过命令行变量指定输出路径:
make OUT_DIR=/tmp/build_output
或者先设置环境变量再执行:
export OUT_DIR=~/project/bin
make
执行后,所有的
.o 文件以及最终的可执行文件都会被整理到指定的文件夹内,而源码目录保持原始状态。这种设计模式大大增强了 Makefile 的灵活性和可维护性。