前面的 simple 和 complicated 项目都是采用了单一的目录结构,但大型的项目往往用多个目录来存放不同的模块。下面我们通过 huge 项目来模拟一个更加专业的编译环境。
下图说明了 huge 项目将采用的目录结构
从图中:
- huge 最上层有两个目录: build 和 code
- build 目录用于存放个 Makefile 文件间的共享文件 make.rule ,以及编译整个项目的 Makefile,在 build 还会自动生成 libs 和 exes 两个子目录 (1)libs : 用于存放编译出来的目标文件 (2)exes:用于存放编译出来的可执行文件
- code 目录用于存放项目的源程序文件,在 code 中还会创建 foo 库和 huge 主程序两个子目录
- 对于每个软件模块子目录,分为用于存放 .c 文件的 src 子目录和用于存放 .h 文件的 inc 子目录。当进行项目编译时,我们希望 make 在 src 目录下面创建 deps 和 objs 目录。
- 在每一个 src 目录中都会有一个 Makefile ,用于构建所在目录中的源程序文件,可以推测,在 build 目录下的 Makefile ,将调用每一个软件模块中 src 子目录内的 Makefile 。
我们采用以下命令来完成这些目录的创建工作:
$mkdir -p build code/foo/src code/foo/inc code/huge/src
huge / code / foo / src / Makefile
.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAG = -rf
CC = gcc
AR = ar
ARFLAG = crs
DIR_OBJS = objs
DIR_EXES = ../../../build/exes
DIR_DEPS = deps
DIR_LIBS = ../../../build/libs
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)
EXE =
ifneq("$(EXE)", "")
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
RMS += $(EXE)
endif
LIB = libfoo.a
ifneq("$(LIB)", "")
LIB := $(addprefix $(DIR_LIBS)/, $(LIB))
RMS += $(LIB)
endif
SRCS = $(wildcard *.c)
OBJS = $(SRCS :.c = .o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS :.c = .dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
ifeq("$(wildcard $(DIR_OBJS))", "")
DEP_DIR_OBJS := $(DIR_OBJS)
endif
ifeq("$(wildcard $(DIR_EXES))", "")
DEP_DIR_EXES := $(DIR_EXES)
endif
ifeq("$(wildcard $(DIR_DEPS))", "")
DEP_DIR_DEPS := $(DIR_DEPS)
endif
ifeq("$(wildcard $(DIR_LIBS))", "")
DEP_DIR_LIBS := $(DIR_LIBS)
endif
all : $(EXE) $(LIB)
ifneq($(MAKECMDGOALS), clean)
include $(DEPS)
endif
$(DIRS) :
$(MKDIR) $@
$(EXE) : $(DEP_DIR_EXES) $(OBJS)
$(CC) -o $@ $(filter %.o, $^)
$(LIB) : $(DEP_DIR_LIBS) $(OBJS)
$(AR) $(ARFLAG) $@ $(filter %.o, $^)
$(DIR_OBJS) / %.o : $(DEP_DIR_OBJS) %.c
$(CC) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS) / %.dep : $(DEP_DIR_DEPS) %.c
@echo "Creating $@ ..."
@set -e;\
$(RM) $(RMFLAG) $@.tmp;\
$(CC) -E -MM $(filter %.c, $^) > $@.tmp;\
sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@;\
$(RM) $(RMFLAG) $@.tmp
clean :
$(RM) $(RMFLAG) $(RMS)
其中更改如下:
- 增加了 AR 和 ARFLAG 两个变量,用于创建静态库
- 将 exes 目录的实际位置以相对路径的形式赋值给 DIR_EXES 变量
- 增加了 DIR_LIBS 变量以记录 libs 目录的实际位置,同样采用相对路径的形式
- 在 DIRS 变量中增加了 DIR_LIBS 变量的值,以便创建 build / libs 目录
- 新增了 RMS 变量用于表示需要删除的目录和(或)文件。由于这个 Makefile 只是针对构建 libfoo.a 库的,所以当运行 “make clean” 时,不应将位于 build 目录下的 exes 和 libs 目录全部删除。
- 清除了对 EXE 变量所赋值的 complicated,同时增加了 ifneq 条件语句用于判断 EXE 变量的值是否为空。只有当 EXE 不为空时才需要为 EXE 变量的值增加目录前缀并将 $(EXE) 加入到 RMS 变量中,以便在调用 “make clean” 时清除它
- 新增了 LIB 变量,用于存放最终生成库的名字,同样使用条件语法来决定是否需要为 LIB 变量中的值增加目录前缀
- 为 all 目标增加 $(LIB) 先决条件
- 增加了一条用于生成库的规则,使用 ar 工具来生成库
- 在 clean 目标命令中,采用删除 RMS 变量中的内容而不是 DIRS 变量中的内容
运行结果:
/Makefile / huge / code / foo / src
$ touch foo.c
$ make
mkdir deps
Creating deps / foo.dep ...
mkdir objs
gcc -o objs / foo.o -c foo.c
ar crs ../../../build/libs/libfoo.a objs/foo.o
$ ls
Makefile deps/ foo.c objs/
$ ls ../../../build/libs/
libfoo.a
$ make clean
rm -rf objs deps ../../../build/libs/libfoo.a
$ ls ../../../build/libs/
从运行结果:
确实在 build / libs 目录下面生成了 libfoo.a 库文件,运行了 “make clean” ,并没有将 build / libs 目录删除,只是删除了 libfoo.a 文件
下面要做的是将这个 Makefile 运用到 code / huge / src 目录。
参考文献:《专业嵌入式软件开发》李云·著
2016年7月5日,星期二