前面的 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日,星期二