1 makefile 實際上是一個單行shell腳本,用make來調用。 2 makefile 使用的命令解釋器由SHELL這個變量所控制,make 啟動時會從用戶環(huán) 保導入所有的變量經作為make有變量,但不包括SHELL變量。 3 每個命令必須以跳格符(tab)開頭在工作目標之后,凡是第一個字符為跳格的文本行一律被視為命令。@放在要執(zhí)行的命令前,執(zhí)行時不打印這個命令。 4 $ 符號跟開括號,函數(shù)名,空格后跟一列由逗號分隔的參數(shù),最后用關括號結 束。例如 SOURCES = $(wildcard *.c) 這行會產生一個所有以 '.c' 結尾的文 件的列表,然后存入變量 SOURCES 里。 5 常用函數(shù)。 A, wildcard, 它有一個參數(shù),功能是展開成一列所有符合由其參數(shù)描述的文 件名,文件間以空格間隔。 $(wildcard *.c)產生一個所有以 '.c' 結尾的文件 的列表. B, patsubst, 它需要3個參數(shù)——第一個是一個需要匹配的式樣,第二個表示用什 么來替換它,第三個是一個需要被處理的由空格分隔的字列. OBJS = $(patsubst %.c,%.o,$(SOURCES)) 處理所有在 SOURCES 字列中的字(一列文件名),如果它 的 結尾是 '.c' ,就用 '.o' 把 '.c'取代. C.notdir 去除路徑。 6 規(guī)則分為三部分,A 工作目標, B 它的必要條件。 C 所要執(zhí)行的命令。 工作目標是一個必須建造的文件或進行的事情,必要條件或依存對像是工作目標得以被成功創(chuàng)建之前,必須事先存在的那些文件,而所要執(zhí)行的命令是必要條件成立時將會創(chuàng)建工作目標的那些shell命令。 7 -l<NAME>,形式的必要條件出現(xiàn)時,make 會搜索libNAME.so文件,如果沒有會再找libNAME.a文件,找到后就進行最后的動作----鏈接。 8 make 被調用時會自動編譯其所找到的第一個工作目標,要想更改工作目標,請在命令行上指定工作目標的名稱。 9 可以用反斜線平延續(xù)過長的文本行。 10 具體規(guī)則 A 工作目標與必要條件是一種依存關系,不是說必要條件一定用在編譯工作目標的文件夾中。如:a.o 只是編譯a.c時生成,但b.c有改動時要重新編a.o a.o: a.c a.o: b.c /*當b.c變化時要重編a.o*/ B 通配符。 模式出現(xiàn)在工作目標或必要條件時,是由make進行能配符的的擴展,不模式出現(xiàn)在命令中時,是由subshell進行擴展的動作。 C 假想工作目標。(1)總是標記為未更新,并且認make知道不應該像處理一般規(guī)則一樣,從源文件平建立工作目標為名的文件。(2)可以經假想目標作為另一個工作目標的必要條件,可以讓make在進行實際的工作目標之前調用假想工作目標。在假想工作目標加上打印,可以輸出相應的打印信息。(3)標準的假想目標有以下:all,install, chean, distclean, TAGS, info, check.. D 空工作目標。(參見《GNU MAKE項目管理》25頁) E 變量。用$(variable-name)來擴展變量。變量名是一單字符時不用圓括號。自動變量有以下幾個: (1)$@ ,工作目標的文件名。 (2)$< 第一個必要條件。 (3)$? 時間戳在工作目標之后的所有必要條件,以空格隔開。 (4)$^ 所有必要條件,以空格隔開,文件名不重復。 (5)$+ 如同$^,但包括重名的文件。 (6)$* 工作目標主文件名。不能在模式規(guī)則以外使用這個變量。
F 模式規(guī)則, 所有內置規(guī)則都是模式規(guī)則的實例。模式規(guī)則中%等同于unix中的shell.可以通過make –print-data-base查看當前的makefile有哪些默認規(guī)則和變量。 (1).%.o:%.c $(CC) –c –o $@ $< 這個內置規(guī)則是把.c編譯成.o (2) %: %.o $(CC ) –o $@ $< G 靜態(tài)模式規(guī)則,可以認為是限定模式規(guī)則的應用中在特定的文件上。 $(OBJ):%.o:%.c $(CC) –c –o $@ $< %.o模式會匹配$(OBJ)中所列出的每個文件,并取出其主干部分,分別構成各個.o文件,這樣就產生了工作目標和必要條件。 H 隱含規(guī)則 A 隱含規(guī)則不是模式規(guī)則就是后綴規(guī)則。當目標加入makefile只要不寫要執(zhí)得的腳本就可以使勝隱含規(guī)則了。 B 一個沒有指定腳本的模式規(guī)則將會從make的規(guī)則庫中刪除相應的規(guī)則。 12 文件查找。 以VPATH和vpath來查找文件。 A .VPATH變量的內容是一份目錄列表,可供make搜索其所用的文件,注意這個目錄列表只是用來搜索工作目標以及必要條件,但不包括命令腳本中所提到的文件。 B. 如果多個目錄中出現(xiàn)同名的文件,make只會取第三者個被找到的文件。 C. 可以用vpath指令在特定目錄搜索特定文件。 vpath pattern directory-list vpath %.c src 在src目錄下搜索.c 文件。 D.個人覺得小項目中可用這個功能,大項目中盡量不用,否則會導制難以發(fā)現(xiàn)的問題。 13 自動產生依存關系。(還要完善) (1)一個C文件可能include多個頭文件,手動去分析這些關系不太可能,所以要自動去分析。 gcc –M test.c這個命令可以打印出test.c 依賴什么頭文件。 這樣就可以編寫一個腳本加入這些自動產生的腳本。但這樣不最好的選擇。 (2)第二種方法是為make加入一個include指令。 編寫一個makefile工作目標,此工作目標的動作就是以-M選項對所有源文件執(zhí)行gcc,并將結果存入一個依存文件中,然后重新執(zhí)行make以便把剛才所產生的依存文件引入makefile,這樣就可以觸發(fā)所要的更新動作。 例: depend: file_a.c file_b.c file_c.c $(CC) –M $(CPPFLAGS) $^ > $@ include depend 但還有問題,就是對源文件加入或移除依存關系時通常不會產生depend文件,這會造成無法重新編譯源文件,整個工作又會變得一團亂。 (3)GNU make中可以使用一個或能和一個簡單的算法來解決上面的問題。 算法:如果我們?yōu)槊總€源文件產生依存關系,將之存入相應的依存文件(擴展名為.d的文件)并以該.d文件為工作目標來加入此依存規(guī)則,這樣,當源文件被改變時,make就會知道要更新該.d文件以及目標文件。 如:test.o test.d: test.c test.h filea.h 程序中可以使用如下模式規(guī)則用命令腳本。 %d: %c $(CC) –M $(CPPFLAGS) $< > $@.$$$$; \ sed ‘s,\($*\)\.o[ :]*,\1.o $@ : , g’ < $@$$$$ > $@; \ rm –f $@.$$$$
14 管理程序庫 程序庫可簡單的認為把相關的目標文件聚集在一起。 (1), ar rv libtest.a test1.o test2.o test3.o r選頂表示要以指定的目標文件替換程序庫里的成員。 v 選項表示ar要詳細打印出它所做的動作。 (2),兩種方法引用程序庫。 A,在命令行上直接指定這個程序庫全名。 gcc litest.a test3.o –o test.elf B,使用-l選項。 gcc -ltest test3.o –o test.elf 盡量使用每二種,因為-l選項告訴gcc去搜索相應的目錄。-L選項可以指定搜索路徑,用于所有和程序庫的搜索上. (3),程序庫會為它所包含的符號提供索引,較新的ar程序會在新成員加入時自動管理此索引,舊的ar不會這樣做,所以對于舊的ar要用另外一個程序來修建或更新索引. ranlib libtest.a (4),程序庫有可能有相互依賴關系,如:A中用到B, B里又用到A,但A要放在B前面,這樣在用這些程序庫時應這樣. –lA –lB –lA 15 變量與宏. (1), 要取一個變量的值應這樣$(var),或${var}注意在shell中不能用括號. (2), 給變量賦值時注意,賦值號右邊字特串前導空格會被刪除但字符串后面的空格會被保留,這有時會導致問題,所以不要在后面加空格。變量主要有三種:常量,內部變量,函數(shù)。 (3) 簡單擴展變量:在make從makefile讀進該變量的定放語句,賦值運算符右邊的部分會立刻擴展。 CC :=gcc MAKE_DEPEND := $(CC) –M 注意如果CC沒有定義那么會被擴展成 <space>-M MKDIR :=mkdir –p (4) 遞歸擴展變量:擴展動作會被延遲到該變量被使用時才進行擴展,有可能出現(xiàn)令人意外的結果。 source = *.c objects = $(subst .c, .o, $(source)) (5) 條件賦值:在變量值尚不存在的情況下進行變量的賦值的動作。 CC ?= gcc (6) 附加運算符:將文本附加到變量里。 VAR += test.c (7) 封裝命令序列,也可以稱為宏 define create-jar …. endef (8) 函數(shù)變量 maybe-make-dir = $(if $(wildcard $1), , $(MKDIR) $1) (9)何時擴展變量 make運行時分兩個階段:A 讀進makefile以及被引入的其它makefile和規(guī)則文件,其中所定義的變量和規(guī)則會被加載進make的內部數(shù)據(jù)庫,而且依存圖也建立起來。B make分析依存圖并且判斷要更新的工作目標,然后完成相應的動作。 16 變量來自何處 (1) 文件中,定義在makefile或是被makefile引入的文件。 (2) 命令行。可以在命令行上定義或重新定義變量。命令行上的變量值將會覆蓋掉環(huán)境變量以及makefile文件中的賦值結果。 (3) 環(huán)境。 (4) 自動創(chuàng)建。make會在執(zhí)行一個規(guī)則的命令腳本前立刻創(chuàng)建自動變量。 17 條件指令與引入指令 (1) 條件指令用來控制makefile文件中使用哪些部分,省略哪些部分。 if-condition ……. endif 或 if-condition ……. else ……. endif ifcondition可以是以下之一, ifdef variable-name ifndef variable-name ifeq test ifneq test 進行條件測試時不應該以$()括住variable-name,可以如下表示。 ifeq‘var1 var (2) 注意條件指令前不能以一個跳格符開始,如果以跳格符開始的話 (3)include指令 makefile可以用include指令引入其它文件,這個功能通常會用來引入make頭文件中所存放的共同的make定義,或是用來自動產生依存關系信息。 18 命令 (1)每個命令會在它自己的shell中執(zhí)行,所以要讓一系列shell命令一起執(zhí)行,要特別處理。簡單的說就是把命令放在一行進行處理。 .INTERMEDIATE:file_list file_list: for d in logic ui; \ do \ echo $$d/*.java: \ done > $@ 19 命令修飾符 (1)@ 不要輸出命令 可以這樣用 QUIET=@ script: $(QUIET) cmd 這樣只用控制QUIET變量就可以控制命令輸出了。 (2)- 破折號前綴,忽略命令中的錯誤。 (3)+加號修飾符, 要求make執(zhí)行命令就算用戶以—just-print命令來執(zhí)行。 20 錯誤與中斷 (1)make 每執(zhí)行一個命令就會返回一個狀態(tài)碼,零表示成功,非零表示出錯。常見的有如下: A shell的if 結構沒有使用else當走else分支時會返回錯誤。 B rm , mkdir等命令。rm –f , mkdir –P (2)如查用戶要求腳本中的兩條語句在同一個subshell中執(zhí)行,那長必須用語句連接符來隔開它們(;或&&). 21 大型項目管理 現(xiàn)有兩種方法:遞歸式和單一makefile從每個組件目錄引入信息。 (1)遞歸makefile A 要使用make來編譯一個大型項目時,可以為每一個目錄編寫一個簡單的,各自獨立的makefile,然后分別地執(zhí)行它們。有以下兩種進入下層的目錄。 make –directory=subdir make –C subdir B 假設一個項目有三個庫,UI, DECODE, DATABASE, 一個可執(zhí)行程序PLAYER,它們分別放在不同的目錄里。 可以這樣寫makefile UI_DIR DECODE_DIR DATABASE_DIR PLAYER_DIR SUB_DIR=$(UI_DIR) $(DECODE_DIR) $(DATABASE_DIR) $(PLAYER_DIR) for dir in $(SUB_DIR) do make –C $$dir done |
|