Makefile 隨記

foo.o : foo.c defs.h # module for twiddling the frobs 
cc -c -g foo.c 
上面的規則告訴我們了兩件事:
1.  如何確定目標文件是否過期(需要重建目標),過期是指目標文件不存在或者目標文件“foo.o”在時間戳上比依賴文件中的任何一個(“foo.c”或者“defs.h”)“老”。
2.  如何重建目標文件“foo.o”。這個規則中使用cc編譯器。規則的命令中沒有明確的使用到依賴文件“defs.h”。我們假設在源文件“foo.c”中已經包含了此頭文件。這也是爲什麼它作爲目標依賴出現的原因。




規則語法
通常規則的語法格式如下:
TARGETS : PREREQUISITES 
COMMAND 
... 
或者:
TARGETS : PREREQUISITES ; COMMAND 
COMMAND 
... 
1.  規則的命令部分有兩種書寫方式:
a. 命令可以和目標:依賴描述放在同一行。命令在依賴文件列表後並使用分號(;)和依賴文件列表分開。
b. 命令在目標:依賴的描述的下一行,作爲獨立的命令行。當作爲獨立的命令行時此行必須以[Tab]字符開始。在Makefile中,在第一個規則之後出現的所有以[Tab]字符開始的行都會被當作命令來處理。
2. Makefile中符號“$”有特殊的含義(表示變量或者函數的引用),在規則中需要使用符號“$”的地方,需要書寫兩個連續的(“$$”)。
3.  前邊已提到過,對於Makefile中一個較長的行,我們可以使用反斜線“\”將其書寫到幾個獨立的物理行上。雖然make對Makefile文本行的最大長度是沒有限制的,但還是建議這樣做。不僅書寫方便而且更有利於別人的閱讀(這也是一個程序員修養的體現)。
規則的中心思想是:目標文件的內容是由依賴文件文件決定,依賴文件的任何一處改動,將導致目前已經存在的目標文件的內容過期。規則的命令爲重建目標提供了方法。這些命令運行在系統shell之上。


有時,需要定義一個這樣的規則,在更新目標(目標文件已經存在)時只需要根據依賴文件中的部分來決定目標是否需要被重建,而不是在依賴文件的任何一個被修改後都重建目標。爲了實現這一目的,相應的就需要對規則的依賴進行分類,一類是在這些依賴文件被更新後,需要更新規則的目標;另一類是更新這些依賴的,可不需要更新規則的目標。我們把第二類稱爲:“order-only”依賴。書寫規則時,“order-only”依賴使用管道符號“|”開始,作爲目標的一個依賴文件。規則依賴列表中管道符號“|”左邊的是常規依賴,管道符號右邊的就是“order-only”依賴。這樣的規則書寫格式如下:
TARGETS : NORMAL-PREREQUISITES | ORDER-ONLY-PREREQUISITES 
這樣的規則中常規依賴文件可以是空;同樣也可以對一個目標進行多次追加依賴。需要注意:規則依賴文件列表中如果一個文件同時出現在常規列表和“order-only”列表中,那麼此文件被作爲常規依賴處理(因爲常規依賴所實現的動作是“order-only”依賴所實現的動作的一個超集)。
LIBS = libtest.a 
foo : foo.c | $(LIBS) 
 $(CC) $(CFLAGS) $< -o $@ $(LIBS) 
make在執行這個規則時,如果目標文件“foo”已經存在。當“foo.c”被修改以後,目標“foo”將會被重建,但是當“libtest.a”被修改以後。將不執行規則的命令來重建目標“foo”。就是說,規則中依賴文件$(LIBS)只有在目標文件不存在的情況下,纔會參與規則的執行。當目標文件存在時此依賴不會參與規則的執行過程。




4.4 文件名使用通配符
Maekfile中表示文件名時可使用通配符。可使用的通配符有:“*”、“?”和“[…]”。在Makefile中通配符的用法和含義和Linux(unix)的Bourne shell完全相同。例如,“*.c”代表了當前工作目錄下所有的以“.c”結尾的文件等。但是在Makefile中這些統配符並不是可以用在任何地方,Makefile中統配符可以出現在以下兩種場合:
1.  可以用在規則的目標、依賴中,make在讀取Makefile時會自動對其進行匹配處理(通配符展開);
2.  可出現在規則的命令中,通配符的通配處理是在shell在執行此命令時完成的。除這兩種情況之外的其它上下文中,不能直接使用通配符。而是需要通過函數“wildcard”(可參考 8.3 文件名處理函數 一節)來實現。
如果規則的一個文件名包含統配字符(“*”、“.”等字符),在使用這樣的文件時需要對文件名中的統配字符使用反斜線(\)進行轉義處理。例如“foo\*bar”,在Makefile中它表示了文件“foo*bar”。Makefile中對一些特殊字符的轉移和B-SHELL以及C語言中的基本上相同。
另外需要注意:在Linux(unix)中,以波浪線“~”開始的文件名有特殊含義。單獨使用它或者其後跟一個斜線(~/),代表了當前用戶的宿主目錄(在shell下可以通過命令“echo ~(~\)”來查看)。例如“~/bin”代表“/home/username/bin/”(當前用戶宿主目錄下的bin目錄)。波浪線之後跟一個單詞(~word),代表由這個“word”所指定的用戶的宿主目錄。例如“~john/bin”就是代表用戶john 的宿主目錄下的bin目錄。在一些系統中(像MS-DOS和MS-Windows),用戶沒有各自的宿主目錄,此情況下可通過設置環境變量“HOME”來模擬。




clean: 
rm *.o temp 
規則中“rm”不是創建文件“clean”的命令,而是刪除當前目錄下的所有.o文件和temp文件。當工作目錄下不存在“clean”這個文件時,我們輸入“make clean”,“rm *.o temp”總會被執行。這是我們的初衷。但是如果在當前工作目錄下存在文件“clean”,情況就不一樣了,同樣我們輸入“make clean”,由於這個規則沒有任何依賴文件,所以目標被認爲是最新的而不去執行規則所定義的命令,因此命令“rm”將不會被執行。這並不是我們的初衷。爲了解決這個問題,我們需要將目標“clean”聲明爲僞目標。將一個目標聲明爲僞目標的方法是將它作爲特殊目標.PHONY”的依賴。如下:
.PHONY : clean 
這樣目標“clean”就被聲明爲一個僞目標,無論在當前目錄下是否存在“clean”這個文件。我們輸入“make clean”之後。“rm”命令都會被執行。而且,當一個目標被聲明爲僞目標後,make在執行此規則時不會去試圖去查找隱含規則來創建它。這樣也提高了make的執行效率,同時也不用擔心由於目標和文件名重名而使我們的期望失敗。
在書寫僞目標規則時,首先需要聲明目標是一個僞目標,之後纔是僞目標的規則定義。
目標“clean”的完整書寫格式應該如下:
.PHONY: clean 
clean: 
rm *.o temp 




在Makefile中,一個僞目標可以有自己的依賴(可以是一個或者多個文件、一個或者多個僞目標)。在一個目錄下如果需要創建多個可執行程序,我們可以將所有程序的重建規則在一個Makefile中描述。因爲Makefile中第一個目標是“終極目標”,約定的做法是使用一個稱爲“all”的僞目標來作爲終極目標,它的依賴文件就是那些需要創建的程序。下邊就是一個例子:
#sample Makefile 
all : prog1 prog2 prog3 
.PHONY : all 
prog1 : prog1.o utils.o 
cc -o prog1 prog1.o utils.o 
prog2 : prog2.o 
cc -o prog2 prog2.o 
prog3 : prog3.o sort.o utils.o 
cc -o prog3 prog3.o sort.o utils.o 
執行make時,目標“all”被作爲終極目標。爲了完成對它的更新,make會創建(不存在)或者重建(已存在)目標“all”的所有依賴文件(prog1、prog2和prog3)。當需要單獨更新某一個程序時,我們可以通過make的命令行選項來明確指定需要重建的程序。(例如:“make prog1”)。當一個僞目標作爲另外一個僞目標依賴時,make將其作爲另外一個僞目標的子例程來處理(可以這樣理解:其作爲另外一個僞目標的必須執行的部分,就行C語言中的函數調用一樣)。下邊的例子就是這種用法:
.PHONY: cleanall cleanobj cleandiff 
cleanall : cleanobj cleandiff 
rm program 
cleanobj : 
rm *.o 
cleandiff : 
rm *.diff 
“cleanobj”和“cleandiff”這兩個僞目標有點像“子程序”的意思(執行目標“clearall時會觸發它們所定義的命令被執行”)。我們可以輸入“make cleanall”和“make cleanobj”和“make cleandiff”命令來達到清除不同種類文件的目的。例子首先通過特殊目標“.PHONY”聲明瞭多個僞目標,它們之間使用空各分割,之後纔是各個僞目標的規則定義。
說明:
通常在清除文件的僞目標所定義的命令中“rm”使用選項“–f”(--force)來防止在缺少刪除文件時出錯並退出,使“make clean”過程失敗。也可以在“rm”之前加上“-”來防止“rm”錯誤退出,這種方式時make會提示錯誤信息但不會退出。爲了不看到這些討厭的信息,需要使用上述的第一種方式。

另外make存在一個內嵌隱含變量“RM”,它被定義爲:“RM = rm –f”。因此在書寫“clean”規則的命令行時可以使用變量“$(RM)”來代替“rm”,這樣可以免出現一些不必要的麻煩!這是我們推薦的用法。



:在一個規則的命令中,命令行“cd”
改變目錄不會對其後的命令的執行產生影響。就是說其後的命令執行的工作目錄不會是之前使用“cd”進入的那個目錄。如果要實現這個目的,就不能把“cd”和其後的命令放在兩行來書寫。而應該把這兩條命令寫在一行上,用分號分隔。這樣它們纔是一個完整的shell命令行。如:
foo : bar/lose 
cd bar; gobble lose > ../foo 


個不帶任何參數的指示符“export”指示符:
export 
含義是將此Makefile中定義的所有變量傳遞給子make過程。如果不需要傳遞其中的某一個變量,可以單獨使用指示符“unexport”來聲明這個變量。使用“export”將所有定義的變量傳遞給子Makefile時,那些名字中包含其它字符(除字母、數字和下劃線以外的字符)的變量可能不會被傳遞給子make,對這類特殊命名的變量傳遞需要明確的使用“export”指示符對它進行聲明。雖然不帶任何參數的“export”指示符具有特殊的含義,但是一個不帶任何參數的“unexport”指示符卻是沒有任何意義的,它不會對make的執行過程(變量的傳遞)產生任何影響。


變量的替換引用
對於一個已經定義的變量,可以使用“替換引用”將其值中的後綴字符(串)使用指定的字符(字符串)替換。格式爲“$(VAR:A=B)”(或者“${VAR:A=B}”),意思是,替換變量“VAR”中所有“A”字符結尾的字爲“B”結尾的字。“結尾”的含義是空格之前(變量值多個字之間使用空格分開)。而對於變量其它部分的“A”字符不進行替換。例如:
foo := a.o b.o c.o 
bar := $(foo:.o=.c) 
在這個定義中,變量“bar”的值就爲“a.c b.c c.c”。使用變量的替換引用將變量“foo”以空格分開的值中的所有的字的尾字符“o”替換爲“c”,其他部分不變。如果在變量“foo”中如果存在“o.o”時,那麼變量“bar”的值爲“a.c b.c c.c o.c”而不是“a.c b.c c.c c.c”。
變量的替換引用其實是函數“patsubst”(參考 8.2 文本處理函數 一節)的一個簡化實現。在GNU make中同時提供了這兩種方式來實現同樣的目的,以兼容其它版本make。
另外一種引用替換的技術使用功能更強大的“patsubst”函數。它的格式和上面“$(VAR:A=B)”的格式相類似,不過需要在“A”和“B”中需要包含模式字符“%”。這時它和“$(patsubst A,B $(VAR))”(可參考 8.2 make的文本處理函數 一小節)所實現功能相同。例如:
foo := a.o b.o c.o 
bar := $(foo:%.o=%.c) 
這個例子同樣使變量“bar”的值爲“a.c b.c c.c”。這種格式的替換引用方式比第一種方式更通用。




1.  “ifeq”表示條件語句的開始,並指定了一個比較條件(相等)。之後是用圓括號括包圍的、使用逗號“,”分割的兩個參數,和關鍵字“ifeq”用空格分開。參數中的變量引用在進行變量值比較時被展開。“ifeq”之後就是當條件滿足make需要執行的,條件不滿足時忽略。
2.  “else”之後就是當條件不滿足時的執行部分。不是所有的條件語句都需要此部分。
3.  “endif”表示一個條件語句的結束,任何一個條件表達式都必須以“endif”結束。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章