http://blog.chinaunix.net/u/21948/showart_292182.html
學習任務:Gnu Make
學習資料:GNU Make 3.80(中文版,徐海兵譯)、網路
學習目標:
1、能夠熟練編寫中小規模project的Makefile
2、能夠讀懂大規模project,比如Linux kernel的Makefile
3、學習automake以實現自動化工程管理
2007-05-05
1、make是什麼?Makefile又是什麼?
Gnu make是Linux環境下用來構建和管理工程的命令工具,然而單獨的make命令是無法工作的,它需要一個Makefile檔。這個檔描述了
整個工程的編譯、連接規則,Makefile有自己的書寫格式、命令、關鍵字。make讀取Makefile,然後對這些規則解釋執行,以完成工程管理。
可以進行類比理解:shell是一個命令解釋器,它可以讀取shell腳本檔,在解釋的同時執行(注意:這是解釋器和編譯器的不同之處)。
同樣,make類似一個命令解釋器(但是不是命令解釋器,只是類比而已),它可以讀取Makefile檔,進行解釋執行。
因為shell腳本和Makefile都有自己獨立的書寫格式、命令等,所以需要分別理解,在變數引用等方面需要進行區分,不要混淆。
從另一方面來看,Linux存在很多相通之處,就像一個生態環境。shell和Makefile可以結合使用,以更加通用。二者在正則運算式上很多地方
相同。在學習中,採用對比研究的方法比較合適。
2、make的基本工作原理是什麼?
make是通過比較對應檔(規則的目標和依賴)的最後修改時間,來決定哪些檔需要更新、那些檔不需要更新。對需要更新的檔,make就執行
資料庫中所記錄的相應命令(在make讀取Makefile後會建立一個編譯過程的描述資料庫。此資料庫中記錄了所有各個檔之間的相互關係,以及
它們的關係描述)來重建它,對於不需要重建的檔make什麼也不做。
3、Makefile編寫可讀性的注意事項
(1)在書寫時,一個較長行可以使用反斜線(\)分解為多行,這樣做可以使Makefile清晰、容易閱讀。注意:反斜線之後不能有空格!
(2)在實際工作中大家比較認同的方法是,使用一個變數"objects"、"OBJECTS"、"objs"、"OBJS"來作為所有的.o文件的列表的替代。
在使用到這些檔列表的地方,使用此變數來代替。比如說定義了變數"OBJS",那麼就可以在需要的地方使用"$(OBJS)"來表示它。
(3)書寫規則建議的方式是:單目標,多依賴。就是說儘量做到一個規則中只存在一個目標檔,可有多個依賴檔。儘量避免多目標,單依賴的方式。
這樣後期維護也會非常方便,而且Makefile會更加清晰明瞭。
(4)Makefile可以包含子Makefile,這樣方便工程的模組化管理。這可以利用關鍵字include實現,和c語言對頭檔的包含方式類似。
include指示符告訴make暫停讀取當前的Makefile,而轉去讀取include指定的一個或多個檔,完成以後再繼續當前Makefile的讀取。
Makefile指示符include書寫在獨立的一行,其形式如下:
include FILENAMES
FILENAMES是shell所支援的檔案名(可以使用通配符)。指示符include所在的行可以以一個或者多個空格(make在處理時將忽略這些空格)
開始,但是切記不能以【TAB】字元開始。
4、Makefile中如何創建多個可執行程式?
Makefile中,如果在一個目錄下如果需要創建多個可執行程式,我們可以將所有程式的重建規則在一個Makefile中描述。
因為Makefile中第一個目標是"終極目標",約定的做法是使用一個稱為"all"的偽目標來作為終極目標,它的依賴檔就是那些需要創建的程式。
#sample multi-targetall: prog1 prog2 prog3.PHONY: allprog1: prog1.o utils.o $(CC) $(CFLAGS) $^ -o $@prog2: prog2.o $(CC) $(CFLAGS) $^ -o $@prog3: prog3.o sort.o utils.o $(CC) $(CFLAGS) $^ -o $@
註:$^為前提清單,$@為目標,如果以紅色部分來展開,即為如下所示,也可以寫$(^),$(@)
Prog1: prog1.o utils.o
$(CC) $(CFLAGS) prog1.o utils.o –o prog1
當需要單獨更新某一個程式時,我們可以通過make的命令行選項來明確指定需要重建的程式。
5、Makefile中make clean的標準格式
make存在一個內嵌隱含變數“RM”,它被定義為:“RM=rm -f”,因此在書寫clean規則時的命令行可以使用$(RM)來代替rm,這樣可以避免
出現一個不必要的麻煩。這是推薦使用的做法。
注意:目標clean僅僅代表一個動作的標識,這個標識可以作為make命令的參數。它沒有任何依賴,Makefile中把這種沒有任何依賴,只有執行
動作的目標稱為“偽目標”(phony targets)。你可以用make clean來執行清理工作。
.PHONY:cleanclean: $(RM) *.o $(TARGET)
6、小技巧
(1)如果要執行的命令以字元"@"開始,則make在執行時這個命令就不會被回顯。典型的用法是我們在使用echo命令時輸出一些資訊時,如:
@echo "starting"
所以可以在書寫Makefile時,使用@來控制命令的回顯。
(2)規則中,當目標需要被重建時,此規則定義的命令將會被執行,如果是多行命令,那麼make就為每一行命令使用一個獨立的子shell去執行。
因此,多行命令之間的執行是相互獨立的,相互之間不存在依賴。而在Makefile中書寫在同一行的多個命令屬於一個完整的shell命令行,所以需要
注意:在一個規則的命令中,命令行cd改變目錄並不會對其後的命令的執行產生影響。就是說其後的命令執行的工作目錄不會是之前使用cd進入的那個目錄。如果要實現這個目的,就不能把cd和其後的命令放在兩行來書寫。而應該把這兩行命令寫在一行上,用分號隔開。這樣它們才是一個完整的shell命令行。如果希望把一個完整的shell命令行書寫在多行上,需要使用反斜杠來對處於多行的命令進行連接,表示它們是一個完整的shell命令行。
(3)make的遞迴調用
在Makefile中使用"make"作為一個命令來執行本身或者其他makefile檔。遞迴調用在一個存在多級子目錄的項目中非常有用。
subsystem:
cd subdir && $(MAKE)
等價於
subsystem:
$(MAKE) -C subdir
意思是進入子目錄,然後在子目錄下面執行make。
(4)make可以定制命令包
在書寫Makefile時,可能有多個規則會使用相同的一組命令,就像C語言程式中需要經常使用的函數printf。可以將一組命令進行類似c語言函數的封裝,以後在需要的地方可以通過它的名字來對這一組命令中進行引用。這樣就可以減少重複工作,提高了效率。在GNU make中,可以使用指示符define來完成這個功能,通常把define定義的一組命令成為一個命令包。定義一個命令包的語法以define開始,以endef結束。
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
這樣,run-yacc就是這個命令包的名字。在define和endef之間的命令就是命令包的主體。需要說明的是,使用define定義的命令包中,命令體中變數和函數的引用不會展開。命令體中所有的內容包括$等都是變數run-yacc的定義,它和C語言中巨集的使用方式一樣。
使用:
foo.c:foo.y
$(run-yacc)
(5)區分Makefile和shell中對變數應用的不同
Makefile單字元的引用可以為$x $(x) ${x}三種形式。多字元的引用$(xx) ${xx}.
而shell中變數的引用只有兩種形式${xx} $xx.
一般在書寫Makefile時,各部分變數引用的格式建議如下:
(i)make變數(Makefile中定義的變數或者是make的環境變數)的引用使用$(VAR)的格式,無論VAR是單字元變數名還是多字元變數名。
(ii)出現在規則命令行中shell變數(一般為執行命令過程中的臨時變數,它不屬於Makefile變數,而是一個shell變數)引用使用shell的$tmp格式。
(iii)對出現在命令行中的make變數同樣使用$(CMDVAR)格式來引用。
(6)儘量採用直接展開式變數,而不要用遞迴展開式變數。
(7)如果實現對一個之前沒有定義過的變數進行賦值,可以使用?=。如果想對一個通用變數的值進行追加,使用+=。
7、思想
作為一名優秀的程式師,在面對一個複雜問題時,應該是尋求一種盡可能簡單、直接並且高效的處理方式來解決,而不是將一個簡單問題在實現上複雜化。如果想在簡單問題上突出自己使用某種語言的熟練程度,是一種非常愚蠢且不成熟的行為