轉載出處:http://blog.csdn.net/whuslei/article/details/7179892
作者博客:http://blog.csdn.net/whuslei
作爲一個初學者,第一次自己動手寫makefile,雖然參照了不少資料,但是實踐過程中還是遇到了很多問題。希望給後來者一個參考。
爲什麼要寫makefile?之前學C語言,用的都是IDE工具,基本都是在windows下進行的。現在轉到linux下了,開發的方式發生了改變。要在linux下開發C語言程序,有三樣很基礎的東西一定要熟悉:VI、Shell、Makefile。用VI快速編輯,用shell寫一些配置腳本、用Makefile來簡化構建C語言項目。所以,我之前好好看了下VI手冊,推薦大家好好看下。有人說,不寫Makefile我也能開發C語言,比如用Eclipse等IDE工具,可以自動生成Makefile。我個人認爲,現在有很多自動化構建的工具幫我們實現了,我們依然有必要知其所以然,自己動手實踐。另外,Makefile可以簡化我們編譯項目的難度,有了它,我們不再需要逐個地寫GCC命令了。
怎麼學着寫Makefile?網上類似的文章很多,各色各樣的,很容易讓我們這樣的菜鳥失去目標。我建議按照如下三個步驟來:1) 參考《Linux程序設計 第4版》中有關makefile的章節。老外寫書,沒得話說。這部分內容很少,很簡練,適合我們對Makefile有個直觀的瞭解。 2) 網上的教程 《跟我一起寫Makefile》陳皓的大作,娓娓道來,值得一看。 3) 參考《GNUmake_v3.80-zh_CN_html》 GNU的make參考手冊,比較詳細,有中文版了。 作爲初學者,應該先對一個陌生的東西來個直觀的瞭解,然後再逐步深入地學習。
Makefile怎麼寫,資料就太多了,需要讀者先去了解下。我下面只是把寫Makefile過程中容易出問題的地方列出。
【理解make的執行過程】
本部分摘自makefile手冊。
GUN make的執行過程分爲兩個階段。
第一階段:讀取所有的makefile文件(包括“MAKIFILES”變量指定的、指示符“include”指定的、以及命令行選項“-f(--file)”指定的makefile文件),內建所有的變量、明確規則和隱含規則,並建立所有目標和依賴之間的依賴關係結構鏈表。
第二階段:根據第一階段已經建立的依賴關係結構鏈表決定哪些目標需要更新,並使用對應的規則來重建這些目標。
理解make執行過程的兩個階段是很重要的。它能幫助我們更深入的瞭解執行過程中變量以及函數是如何被展開的。變量和函數的展開問題是書寫Makefile時容易犯錯和引起大家迷惑的地方之一。
具體的執行過程如下:
1. 依次讀取變量“MAKEFILES”定義的makefile文件列表
2. 讀取工作目錄下的makefile文件(根據命名的查找順序“GNUmakefile”,“makefile”,“Makefile”,首先找到那個就讀取那個)
3. 依次讀取工作目錄makefile文件中使用指示符“include”包含的文件
4. 查找重建所有已讀取的makefile文件的規則(如果存在一個目標是當前讀取的某一個makefile文件,則執行此規則重建此makefile文件,完成以後從第一步開始重新執行)
5. 初始化變量值並展開那些需要立即展開的變量和函數並根據預設條件確定執行分支
6. 根據“終極目標”以及其他目標的依賴關係建立依賴關係鏈表
7. 執行除“終極目標”以外的所有的目標的規則(規則中如果依賴文件中任一個文件的時間戳比目標文件新,則使用規則所定義的命令重建目標文件)
8. 執行“終極目標”所在的規則
【理解"依賴"】
Makefile 其實和 Maven有點類似,都是通過依賴來實現一系列功能的。因此,一定要好好理解"依賴"。寫Makefile過程中,用到最多的就是 " 目標文件 : 依賴文件 ; 命令 " 。意思是說,要想生成"目標文件",必須先生成"依賴文件",然後再對"依賴文件"-->執行--->"命令"-->生成-->"目標文件"。這樣的關係就構成了一系列依賴!
舉個例子 testa.c、testa.h、 testb.c、 testb.h、 main.c,其中testb引用了testa.h,main引用了testb.h
- all : Mains
- Mains : main.o testb.o testa.o
- gcc -o $@ $^
- %.o : %.c
- gcc -c $< -o %@ #此處的"-o"表示的是:指定.o文件的名字或者路徑!
- .PHONY:all clean
- clean:
- @echo "i will clean..."
- -rm -rf *.o Mains
- @echo "ok,i have cleaned!"
類似上面這樣的代碼就是稍微複雜點的makefile了,用到了自動變量、僞目標和隱含依賴。這也是Makefile裏最常用到的。這裏想說明的是,"all"和"clean"是兩種約定俗成的標記。通常我們把他們放在僞目標裏。注意:makefile中是以第一個規則爲出發點的,換句話說,我們要把all放在前面(除變量和include外)。這樣保證Makefile的依賴是我們所想要的那樣!!可能,有時候Include的東西會導致只生成了列表中的第一個.o文件!!比如,include進來的是一種依賴關係 ,如 "testb.o : testb.c testb.h"。如果這樣一句依賴出現在all依賴之前,那麼make會誤以爲testb是終極目標!這種錯誤不好發現!因爲include的東西先被執行!
爲什麼專門提到這個?因爲實踐過程中曾經出現過一個問題。比如某些C源碼,我不想把它編譯鏈接爲exe,只是想編譯爲.o文件。那麼我們怎麼寫依賴呢?依賴的起始可以這麼寫:
- all : testa.o testb.o
- %.o : %.c
- gcc -c $<;
"all" 對應的依賴規則不需要做任何處理,這樣就是告訴make,我需要生成.o文件,然後再根據下一條規則,將.c文件編譯爲.o文件!第一條規則可以不寫成"all",但是這條規則一定要有!
附:什麼是僞目標。僞目標是這樣一個目標:當使用make命令行指定此目標時,這個目標所在規則定義的命令、無論目標文件是否存在都會被無條件執行。
另附(再羅嗦一句):在通過建立.d文件實現"自動產生依賴"的過程中,需要注意的是include指示符的書寫順序,因爲在這些.d文件中已經存在規則。當一個Makefile使用指示符include這些.d文件時,應該注意它應該出現在終極目標之後,以免.d文件中的規則被是Makefile的終極規則
【多目錄結構】
程序中不可避免出現多目錄,劃分模塊。每個模塊內也有makefile,怎麼聯繫起來呢?我的做法是:在最外層弄一個Makefile,它主要是負責別的makefile文件的調用順序!類似與下面這種:
- all:
- @for subdir in $(make_subdir); do\
- echo "making $$subdir";\
- $(MAKE) -C $$subdir ;\
- done;
【字符串處理函數】
如 notdir, subst, strip, wildcard(準確地說這個不屬於次列)等,需要注意的一點是:makefile中字符串的表示和Shell有很大不同。比如 am := xy abc 相當於shell中的 am="xy abc"。變量的表示也不一樣,在Makfile中 "$(am)" 和shell中 "$am"或者"${am}"是一個意思!!不要忽略這個。
【規則中的"%"】
比如 "%.o:%.c",意思是說,xxx.o 需要xxx.c,注意,如果他們不在一個路徑下,記得改爲"%.o:$(path)%.c",這樣才能找到.c代碼。換句話說,"%"匹配的是這個文件的名字,並不包括路徑!
【自動化變量】
$@ : 規則中的目標文件
$< : 規則中的第一個依賴文件名
$^ : 規則中的所有依賴文件列表,以空格分隔
注意:這些自動化變量是和上下文環境相關聯的。
【"@" 和 "-" 】
在依賴對應的命令中,可以用"@"來表示該條命令本身不輸出,僅輸出結果;用"-"來表示該命令執行如果不成功也繼續執行!需要注意的一點是:它們都只能用在一個命令的開頭。例如
- # 正確的寫法。連續執行三條命令,並非同一個shell
- clean:
- @echo "start..."
- -rm rf *.o
- @echo "over..."
- # 錯誤的寫法。用";\"放在末尾表示用一個shell來執行所有的命令
- clean:
- @echo "start";\
- -rm rf *.o;\
- @echo "over..."
【調試】
可以在make時加上參數:如 make -n --just-print等。或者在makefile文件中加入 $(warning xxxx) ,這個語句可以加到變量前,make過程中就會輸出!
【實例1】一個工程一個makefile
目錄結構如右所示: ,insert.c調用了file.c,而main.c又調用了insert.c ,這就是他們的關係。
Makefile的內容是
- SRCPATH:=../src/
- #得到所有.c文件的名稱,去除路徑
- SRC:=$(wildcard $(SRCPATH)*.c)
- SRC:=$(notdir $(SRC))
- #得到即將要被生成的.o文件名稱
- OBJS:=$(SRC:.c=.o)
- CC = gcc
- INCLUDE = .
- CFLAGS = -g -Wall
- # makefile 的程序入口!
- all : main
- @echo "enter regular: all..."
- #引入.d文件,.d文件中包含了.c文件中頭文件的依賴關係!
- include $(OBJS:.o=.d)
- main : $(OBJS)
- gcc -o $@ $^
- %.d: $(SRCPATH)%.c
- @set -e;rm -f $@;\
-
$(CC) -MM $(CFLAGS) $< > $@.
-
sed 's,$∗\.o[ :]*,\1.o $@ : ,g' < $@.
-
rm -f $@.
- %.o : $(SRCPATH)%.c
- $(CC) -I$(INCLUDE) $(CFLAGS) -c $< -o $@
- .PHONY:all clean print
- clean:
- @echo "i will clean..."
- -rm -rf *.o *.d main
- @echo "ok,i have cleaned!"
- print:
- @echo $(OBJS)
上面這個makefile的執行過程是這樣的。首先初始化變量、引入include中的內容。發現" xxx.d "文件不存在,於是查找是否有相應的規則來生成這種文件。如果找不到,則報錯,文件不存在;如果找到了,比如本例,可以根據" %.d: $(SRCPATH)%.c "對應的命令,生成" xxx.d "文件。生成文件後,也就是include完成!注意:include書寫的位置就是新增內容所要放置的位置。make開始建立依賴,此時它看到的其實是這樣的(可以這麼設想):
- all : main
- main : main.o file.o insert.o
- gcc -o $@ $^
- # 此時 xxx.d 文件已經包含進來,且是最新的了,但此時還沒有xxx.o文件
- # 所以會執行下面 " %.o : $(SRCPATH)%.c " 對應的命令
- file.o file.d : ../src/file.c ../src/../include/file.h
- insert.o insert.d : ../src/insert.c ../src/../include/insert.h \
- ../src/../include/file.h
- main.o main.d : ../src/main.c ../src/../include/insert.h ../src/../include/file.h
- %.o : $(SRCPATH)%.c
- $(CC) -I$(INCLUDE) $(CFLAGS) -c $< -o $@
依賴就可以看得很清楚了,因爲缺xx,所以要根據xx來生成xx。於是就構成了一個依賴鏈。可以順利地編譯鏈接爲可執行文件。
【實例2】三個makefile
目錄結構如下圖所示:
這三個makefile的關係是:"1號" 是總的makefile,負責按照順序調用"2號和3號";"2號"是內部模塊的makefile文件,負責編譯爲xxx.o;"3號"是外部模塊,負責編譯鏈接生成可執行文件。
其中," stack.c " 調用了 " array.c "," main.c " 調用了 " stack.c "
ok,上代碼。
"1號 makefile " 的內容是:
- make_subdir := ./src/util/maker/ ./maker/
- all:
- @for subdir in $(make_subdir); do\
- echo "making $$subdir";\
- $(MAKE) -C $$subdir ;\
- done;
- .PHONY:clean
- clean:
- @echo "send clean order..."
- @for subdir in $$(make_subdir); do\
- $(MAKE) -C $$subdir clean;\
- done;
- @echo "receive singal of clean over!"
" 2號 makefile "的內容是(和上面的基本一樣):
- SRCPATH:=../
- #得到所有.c文件的名稱,去除路徑
- SRC:=$(wildcard $(SRCPATH)*.c)
- SRC:=$(notdir $(SRC))
- #得到即將要被生成的.o文件名稱
- OBJS:=$(SRC:.c=.o)
- CC = gcc
- INCLUDE = .
- CFLAGS = -g -Wall
- # makefile 的程序入口!
- all : $(OBJS)
- #引入.d文件,.d文件中包含了.c文件中頭文件的依賴關係!
- include $(OBJS:.o=.d)
- %.d: $(SRCPATH)%.c
- @set -e;rm -f $@;\
-
$(CC) -MM $(CFLAGS) $< > $@.
-
sed 's,$∗\.o[ :]*,\1.o $@ : ,g' < $@.
-
rm -f $@.
- %.o : $(SRCPATH)%.c
- $(CC) -I$(INCLUDE) $(CFLAGS) -c $< -o $@
- .PHONY:all clean print
- clean:
- @echo "i will clean..."
- -rm -rf *.o *.d main
- @echo "ok,i have cleaned!"
- print:
- @echo $(OBJS)
3 號 makefile " 的內容是:
- SRCPATH:=../src/
- SRCINNER:=
- #得到所有.c文件的名稱,去除路徑
- SRC:=$(wildcard $(SRCPATH)*.c)
- SRC:=$(notdir $(SRC))
- OBJSINNER:=../src/util/maker/
- OBJSINNER:=$(wildcard $(OBJSINNER)*.o)#-----------新增的代碼
- #得到即將要被生成的.o文件名稱
- OBJS:=$(SRC:.c=.o)
- CC = gcc
- INCLUDE = .
- CFLAGS = -g -Wall
- # makefile 的程序入口!
- all : main
- @echo "enter regular: all..."
- #引入.d文件,.d文件中包含了.c文件中頭文件的依賴關係!
- include $(OBJS:.o=.d)
- main : $(OBJS) $(OBJSINNER)#---------新增代碼
- gcc -o $@ $^
- %.d: $(SRCPATH)%.c
- set -e;rm -f $@;\
-
$(CC) -MM $(CFLAGS) $< > $@.
-
sed 's,$∗\.o[ :]*,\1.o $@ : ,g' < $@.
-
rm -f $@.
- %.o : $(SRCPATH)%.c
- $(CC) -I$(INCLUDE) $(CFLAGS) -c $< -o $@
- .PHONY:all clean print
- clean:
- @echo "i will clean..."
- -rm -rf *.o *.d main
- @echo "ok,i have cleaned!"
- print:
- @echo $(OBJS)
全文完。如有轉載,請註明blog.csdn.net/whuslei
示例代碼下載地址:http://download.csdn.net/detail/whuslei/4003960
Makefile經典教程(掌握這些足夠):http://blog.csdn.net/ruglcc/article/details/7814546/