一個Makefile的進化(二)
代碼分離與通用Makefile
這一篇接著對Makefile進行修改
目的:
- 將代碼與生成文件進行分離
- 使得Makefile具有更廣泛的通用性
- 使Makefile看起來更牛逼(哈哈哈)
涉及知識:
- makefile的include
- 修改makefile的隱含規(guī)則
- makefile中的函數(shù)使用
涉及的測試文件:
string@asus:~/Projects/makefile$ tree
.
├── include
│ └── test.h
├── Makefile
└── src
├── Makefile
├── test.c
└── test_fun1.c
主Makefile
CPPFLAGS:=-Iinclude
OUTDIR:=out
SRCDIR:=src
PROJECT:= $(notdir $(SRCDIR))
include $(SRCDIR)/Makefile
OBJS=$(addprefix $(OUTDIR)/,$(OBJ))
$(OUTDIR)/%.o:$(SRCDIR)/%.c
$(CC) -c $(CPPFLAGS) $< -o $@
all:$(OUTDIR) .build.stamp
$(OUTDIR):
mkdir $@
.build.stamp: $(OBJS)
gcc $^ -o $(OUTDIR)/$(PROJECT)
touch $@
clean:
$(RM) .build.stamp
rm -rf $(OUTDIR)
.PHONY : clean
子目錄的Makefile
PROJECT=test
OBJ+= test.o test_fun1.o
代碼解釋:
完成代碼與代碼的分離需要修改隱含的規(guī)則,因為默認的規(guī)則會到目標.o對應的目錄去尋找.c,為了將其進行分離重新進行規(guī)則設(shè)計,對應代碼中的下面兩行
$(OUTDIR)/%.o:$(SRCDIR)/%.c
$(CC) -c $(CPPFLAGS) $< -o $@
單獨摘出來應該很容易理解了,這兩行的目的就是當有需要OUTDIR目錄下的.o的時候就去SRCDIR的目錄尋找對應的.c
下面兩行代碼需要解釋一下
OBJS=$(addprefix $(OUTDIR)/,$(OBJ))
PROJECT:= $(notdir $(SRCDIR))
第一行使用了Makefile的一個函數(shù),addprefix,這個函數(shù)是增加前綴函數(shù),作用是在$(OBJ)這個變量的每一個字符串前面加上$(OUTDIR),
示例中OUTDIR為:OBJ+= test.o test_fun1.o,
這個函數(shù)調(diào)用后:OBJS=out/test.o out/test_fun1.o
第二行使用了Makefile的取文件名函數(shù),雖然在這里不是文件名,但是我們利用Makefile的特性獲得一個項目名稱該行執(zhí)行后PROJECT=src,雖然在這里看起來沒有作用,這樣設(shè)計是有原因的,后面會講到
以上Makefile最終會展開成下面的代碼
out/%.o:src/%.c
$(CC) -c -Iinclude $< -o $@
all:out .build.stamp
out:
mkdir $@
.build.stamp: out/test.o out/test_fun1.o
gcc $^ -o out/src
touch $@
clean:
$(RM) .build.stamp
rm -rf out
.PHONY : clean
展開以后的Makefile是不是很容易理解了,純手工展開,實在是太麻煩了,這部分工作還是交給Makefile去做吧
但是展開后的代碼只能完成我們本文的第一個目標:代碼分離
為什么Makefile要寫成那個沒有展開的Makefile,寫著也有點費力,理解也費力,但是這么寫終歸是有原因的,下面我們見證以下如何完成本文的第二個目標,第二個目標完成后你就會覺得很多Makefile的寫法瞬間理解了
注意一下子目錄的Makefile是不是寫的很簡單,但是該Makefile不能單獨使用,需要使用主目錄包含后才能起作用,如果我們擁有很多個類似的項目此時就不需要每一個項目寫一個麻煩的Makefile,只需要仿照子目錄下的那個Makefile寫下簡單的幾行就可以了,下面我們做一下測試
- 首先復制一份src的代碼用于測試,復制到serial
- 修改src的復制代碼的Makefile,在此只修改PROJECT
- 執(zhí)行make SRCDIR=serial查看測試結(jié)果
serial修改后的Makefile如下
PROJECT=serial
OBJ+= test.o test_fun1.o
執(zhí)行結(jié)果如下
string@asus:~/Projects/makefile$ make SRCDIR=serial
mkdir out
cc -c -Iinclude serial/test.c -o out/test.o
cc -c -Iinclude serial/test_fun1.c -o out/test_fun1.o
gcc out/test.o out/test_fun1.o -o out/serial
touch .build.stamp
請注意一下編譯的文件是serial/test.c serial/test_fun1.c這說明了我們編譯的是serial目錄下的文件生成的目標為serial,在此說明一下:serial下的Makefile中的PROJECT可以不賦值,會得到同樣的結(jié)果,這時由于在主Makefile下有語句
PROJECT:= (notdir(SRCDIR))
這一句會使用目錄SRCDIR目錄名作為項目名稱賦值給PROJECT
至此本文已經(jīng)解決完兩個目標,至于第三個目標(呵呵),解決完前兩個問題第三個還是問題嗎 哈哈哈
這個Makefile只作為簡單理解Makefile中的一些機制,在linux,openwrt,buildroot,busybox等開源軟件中都有大量使用這些規(guī)則,如果想更深入的理解Makefile還需要在實際工作中去學習