1 makefile實際上是一個單行shell腳本,用make來調(diào)用。
2 makefile使用的命令解釋器由SHELL這個變量所控制,make 啟動時會從用戶環(huán)
保導(dǎo)入所有的變量經(jīng)作為make有變量,但不包括SHELL變量。
3 每個命令必須以跳格符(tab)開頭在工作目標之后,凡是第一個字符為跳格的文本行一律被視為命令。@放在要執(zhí)行的命令前,執(zhí)行時不打印這個命令。
4 $符號跟開括號,函數(shù)名,空格后跟一列由逗號分隔的參數(shù),最后用關(guān)括號結(jié)
束。例如 SOURCES = $(wildcard *.c) 這行會產(chǎn)生一個所有以 '.c' 結(jié)尾的文
件的列表,然后存入變量 SOURCES 里。
5 常用函數(shù)。
A, wildcard, 它有一個參數(shù),功能是展開成一列所有符合由其參數(shù)描述的文
件名,文件間以空格間隔。 $(wildcard *.c)產(chǎn)生一個所有以 '.c' 結(jié)尾的文件
的列表.
B, patsubst, 它需要3個參數(shù)——第一個是一個需要匹配的式樣,第二個表示用什
么來替換它,第三個是一個需要被處理的由空格分隔的字列. OBJS =$(patsubst
%.c,%.o,$(SOURCES)) 處理所有在 SOURCES 字列中的字(一列文件名),如果它
的 結(jié)尾是 '.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 被調(diào)用時會自動編譯其所找到的第一個工作目標,要想更改工作目標,請在命令行上指定工作目標的名稱。
9 可以用反斜線平延續(xù)過長的文本行。
10 具體規(guī)則
A 工作目標與必要條件是一種依存關(guān)系,不是說必要條件一定用在編譯工作目標的文件夾中。如: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知道不應(yīng)該像處理一般規(guī)則一樣,從源文件平建立工作目標為名的文件。(2)可以經(jīng)假想目標作為另一個工作目標的必要條件,可以讓make在進行實際的工作目標之前調(diào)用假想工作目標。在假想工作目標加上打印,可以輸出相應(yīng)的打印信息。(3)標準的假想目標有以下:all,install,chean, distclean, TAGS, info, check..
D 空工作目標。(參見《GNU MAKE項目管理》25頁)
E 變量。用$(variable-name)來擴展變量。變量名是一單字符時不用圓括號。自動變量有以下幾個:
(1)$@ ,工作目標的文件名。
(2)$< 第一個必要條件。
(3)$? 時間戳在工作目標之后的所有必要條件,以空格隔開。
(4)$^ 所有必要條件,以空格隔開,文件名不重復(fù)。
(5)$+ 如同$^,但包括重名的文件。
(6)$* 工作目標主文件名。不能在模式規(guī)則以外使用這個變量。
F 模式規(guī)則, 所有內(nèi)置規(guī)則都是模式規(guī)則的實例。模式規(guī)則中%等同于unix中的shell.可以通過make –print-data-base查看當前的makefile有哪些默認規(guī)則和變量。
(1).%.o:%.c
$(CC)–c –o $@ $<
這個內(nèi)置規(guī)則是把.c編譯成.o
(2) %: %.o
$(CC ) –o $@$<
G靜態(tài)模式規(guī)則,可以認為是限定模式規(guī)則的應(yīng)用中在特定的文件上。
$(OBJ):%.o:%.c
$(CC)–c –o $@ $<
%.o模式會匹配$(OBJ)中所列出的每個文件,并取出其主干部分,分別構(gòu)成各個.o文件,這樣就產(chǎn)生了工作目標和必要條件。
H 隱含規(guī)則
A 隱含規(guī)則不是模式規(guī)則就是后綴規(guī)則。當目標加入makefile只要不寫要執(zhí)得的腳本就可以使勝隱含規(guī)則了。
B 一個沒有指定腳本的模式規(guī)則將會從make的規(guī)則庫中刪除相應(yīng)的規(guī)則。
12 文件查找。
以VPATH和vpath來查找文件。
A .VPATH變量的內(nèi)容是一份目錄列表,可供make搜索其所用的文件,注意這個目錄列表只是用來搜索工作目標以及必要條件,但不包括命令腳本中所提到的文件。
B. 如果多個目錄中出現(xiàn)同名的文件,make只會取第三者個被找到的文件。
C. 可以用vpath指令在特定目錄搜索特定文件。
vpath patterndirectory-list
vpath %.c src 在src目錄下搜索.c 文件。
D.個人覺得小項目中可用這個功能,大項目中盡量不用,否則會導(dǎo)制難以發(fā)現(xiàn)的問題。
13 自動產(chǎn)生依存關(guān)系。(還要完善)
(1)一個C文件可能include多個頭文件,手動去分析這些關(guān)系不太可能,所以要自動去分析。
gcc –M test.c這個命令可以打印出test.c 依賴什么頭文件。
這樣就可以編寫一個腳本加入這些自動產(chǎn)生的腳本。但這樣不最好的選擇。
(2)第二種方法是為make加入一個include指令。
編寫一個makefile工作目標,此工作目標的動作就是以-M選項對所有源文件執(zhí)行gcc,并將結(jié)果存入一個依存文件中,然后重新執(zhí)行make以便把剛才所產(chǎn)生的依存文件引入makefile,這樣就可以觸發(fā)所要的更新動作。
例:
depend: file_a.cfile_b.c file_c.c
$(CC) –M $(CPPFLAGS) $^ > $@
include depend
但還有問題,就是對源文件加入或移除依存關(guān)系時通常不會產(chǎn)生depend文件,這會造成無法重新編譯源文件,整個工作又會變得一團亂。
(3)GNU make中可以使用一個或能和一個簡單的算法來解決上面的問題。
算法:如果我們?yōu)槊總€源文件產(chǎn)生依存關(guān)系,將之存入相應(yīng)的依存文件(擴展名為.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 管理程序庫
程序庫可簡單的認為把相關(guān)的目標文件聚集在一起。
(1),ar rv libtest.a test1.o test2.o test3.o
r選頂表示要以指定的目標文件替換程序庫里的成員。
v 選項表示ar要詳細打印出它所做的動作。
(2),兩種方法引用程序庫。
A,在命令行上直接指定這個程序庫全名。
gcc litest.a test3.o –otest.elf
B,使用-l選項。
gcc -ltest test3.o –o test.elf
盡量使用每二種,因為-l選項告訴gcc去搜索相應(yīng)的目錄。-L選項可以指定搜索路徑,用于所有和程序庫的搜索上.
(3),程序庫會為它所包含的符號提供索引,較新的ar程序會在新成員加入時自動管理此索引,舊的ar不會這樣做,所以對于舊的ar要用另外一個程序來修建或更新索引.
ranlib libtest.a
(4),程序庫有可能有相互依賴關(guān)系,如:A中用到B, B里又用到A,但A要放在B前面,這樣在用這些程序庫時應(yīng)這樣.
–lA –lB –lA
15 變量與宏.
(1), 要取一個變量的值應(yīng)這樣$(var),或${var}注意在shell中不能用括號.
(2), 給變量賦值時注意,賦值號右邊字特串前導(dǎo)空格會被刪除但字符串后面的空格會被保留,這有時會導(dǎo)致問題,所以不要在后面加空格。變量主要有三種:常量,內(nèi)部變量,函數(shù)。
(3)簡單擴展變量:在make從makefile讀進該變量的定放語句,賦值運算符右邊的部分會立刻擴展。
CC :=gcc
MAKE_DEPEND := $(CC) –M 注意如果CC沒有定義那么會被擴展成 <space>-M
MKDIR :=mkdir –p
(4)遞歸擴展變量:擴展動作會被延遲到該變量被使用時才進行擴展,有可能出現(xiàn)令人意外的結(jié)果。
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的內(nèi)部數(shù)據(jù)庫,而且依存圖也建立起來。B make分析依存圖并且判斷要更新的工作目標,然后完成相應(yīng)的動作。
16 變量來自何處
(1)文件中,定義在makefile或是被makefile引入的文件。
(2)命令行??梢栽诿钚猩隙x或重新定義變量。命令行上的變量值將會覆蓋掉環(huán)境變量以及makefile文件中的賦值結(jié)果。
(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
進行條件測試時不應(yīng)該以$()括住variable-name,可以如下表示。
ifeq‘var1 var
(2)注意條件指令前不能以一個跳格符開始,如果以跳格符開始的話
(3)include指令
makefile可以用include指令引入其它文件,這個功能通常會用來引入make頭文件中所存放的共同的make定義,或是用來自動產(chǎn)生依存關(guān)系信息。
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)碼,零表示成功,非零表示出錯。常見的有如下:
Ashell的if 結(jié)構(gòu)沒有使用else當走else分支時會返回錯誤。
Brm , 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 假設(shè)一個項目有三個庫,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