我們現在所看到的 makefile 範例已經有點長, 如果這是一支僅包含十幾個或更少檔案的小型程式, 我們可能並不擔心, 但是如果這是一支包含成千上萬檔案的大型程式, 手動指定每個工作目標, 必要條件以及命令稿將會變得不切實際! 此外在我們的 makefile 中, 這些命令稿代表著重複的程式碼. 如果這些命令稿包含了一個瑕疵或曾經修改過, 我們必須更新所有相關的規則. 這將會造成很大的維護問題!
許多程式在讀進檔案以及輸出檔案時都會依照慣例. 例如 C 編譯器都會假定檔案若是以 .c 為副檔名, 其所包含的就是 C 的原始碼, 把副檔名從 .c 置換成 .o (對 Windows 上某些編譯器來說是 .obj) 就可以得到目的檔的檔名. 這些慣例讓 make 得以透過檔名樣式比對來簡化規則的建立, 以及提供內建規則來處理它們. 舉例來說, 透過這些內建規則我們可以把之前的 makefile 縮減成 7 列 (原先為 17列):
- Makefile3371_1
- VPATH = src include
- CPPFLAGS = -I include
- count_words: counter.o lexer.o -lfl
- count_words.o: counter.h
- counter.o: counter.h lexer.h
- lexer.o: lexer.h
- %.o: %.c
- $(COMPILE.c) $(OUTPUT_OPTION) $<
- %.c: %.l
- @$(RM) $@
- $(LEX.l) $< > $@
- %: %.c
- $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
首先 make 會讀取 makefile 並且將預定目標 (default goal) 設置成 count_words, 因為命令列之上並未指定任何工作目標故以預定目標進行檢視. 此時 make 發現了四個必要條件:count_words.o (makefile 並未指定這個必要條件, 它是由內定規則提供的!), counter.o, lexer.o 以及 -lfl. 接著 make 會試著依序更新每個必要條件.
當 make 檢查第一個條件 count_words.o 時, 並未發現可以處理它的自訂規則 (explicit rule), 不過有找到內定規則 (implicit rule). 檢視當前目錄 make 並未找到原始檔, 所以它會開始搜尋 VPATH, 並且在 src 目錄中找到一個相符的原始檔. 因為 src/count_words.c 沒有其他必要條件, make 可以自由更新 count_words.o, 所以它會執行這個內定規則. counter.o 也是類式的情況, 當 make 檢查 lexer.o 的時候, 並未找到相應的原始檔 (即使是在 src 目錄中), 所以 make 會假定這 (不存在的原始檔) 是一個中間檔, 並且會去尋找 "從其他原始檔建立 lexer.c 檔" 的方法. make 找到了一個 "從 .l 檔建立 .c 檔" 的規則, 並且注意到 lexer.l 的存在. 因為不需要進行 lexer.l 的更新, 所以 make 會執行更新 lexer.c 的命令. 像這樣一連串的規則來更新一個工作目標的動作稱為 規則鏈結 (rule chaining)!
接下來 make 會檢查程式庫描述 -lfl, 它會搜尋檔案系統的標準程式庫並且找到 /lib/libfl.a. 現在 make 已經找到更新 count_words 時所需要的每個必要條件, 所以它會執行最後一道 gcc 命令. 結束後 make 發現自己建造了一個不必必保存的中間檔, 所以對它進行清除的工作.
你可以透過命令稿中更改變數的值來定製內建規則. 一個典型的規則會包括一群變數, 它的開頭是所要執行的程式, 並且包含用來設定主要命令列選項 (比如 輸出檔案, 進行優化, 進行除錯 等等) 的變數. 你可以透過執行 make --print-data-base 列出 make 具有那些預定規則 (和變數).
樣式
樣式規則中的百分比字符 (%) 大體上等於 Unix Shell 中的星號 (*), 它可以代表任何數目的任何字符. 百分比字符可以擺在樣式中任何地方, 不過只能出現一次 (第二次出現的 % 將被視為一般的百分比字符)! 檔名中, 百分比以外的字符, 會找字面進行比對. 一個樣式可以包含一個前綴 (prefix) 或一個後綴 (suffix), 或者是此兩者. 當 make 搜尋所要使用的樣式規則時, 它會先尋找相符的樣式規則的工作目標 (pattern rule target). 樣式規則工作目標必須以前綴開頭並且以後綴結尾 (如果它們存在). 如果找到相符的樣式規則工作目標, 則前綴與後綴之間的字符會被當成檔名的字幹 (stem), 接著 make 會檢視該樣式規則的必要條件, 並且將該字幹替換到必要條件樣式 (prerequisite pattern) 中. 如果所產生的檔名存在, 或是可以應用另一項規則進行建造的工作, 則會進行比對及應用規則的動作. 字幹至少必須包含一個字符.
事實上你還有可能只用到一個百分比字符的樣式. 此樣式最常被用來建造 Unix 可執行程式. 例如下面就是 GNU make 用來建造程式的若干樣式規則:
- %: %.mod
- $(COMPILE.mod) -o $@ -e $@ $^
- %: %.cpp
- $(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@
- %: %.sh
- cat $< > $@
- chmod a+x $@
靜態樣式規則
靜態樣式規則 (static pattern rule) 只能應用在特定的工作目標. 其語法如下:
- targets …: target-pattern: prereq-patterns …
- recipe
- …
- $(OBJECTS): %.o: %.c
- $(CC) -c $(CFLAGS) $< -o $@
如果明確列出工作目標檔案較容易進行副檔名或其他樣式的比對, 可以考慮靜態樣式規則.
後綴規則
後綴規則 (suffix rule) 是用來定義規則的最初 (也是過時的) 方法. 舊版的 make 可能不支援 GNU make 的樣式規則語法, 因此你仍然會在許多 makefile 檔中看到後綴規則, 所以你最好能夠了解它的語法. 儘管為目標系統 (target system) 編譯 GNU make 可以解決 makefile 移植性, 但是在一些罕見的情況下你可能仍舊需要使用後綴規則.
後綴規則中的工作目標, 可以是一個副檔名或兩個被銜接再一起的副檔名:
- .c.o:
- $(COMPILE.c) $(OUTPUT_OPTION) $<
- %.o: %.c
- $(COMPILE.c) $(OUTPUT_OPTION) $<
上面的後綴規則就是所謂的 雙後綴規則 (double-suffix rule). 因為它包含了兩個副檔名. 你還是可以使用單後綴規則 (single-suffix rule), 它只包含一個副檔名, 也就是原始檔的副檔名. 這個規則可用來建造執行檔, 因為 Unix 上的執行檔不需要副檔名:
- .p:
- $(LINK.p) $^ $(LOADLIBES) $(LDLIBS) -o $@
- %: %.p
- $(LINK.p) $^ $(LOADLIBES) $(LDLIBS) -o $@
- .SUFFIXES: .out .a .ln .o .c .cc .C .cpp .p .f .F .r .y .l
Supplement
* GNU Make Doc - Defining and Redefining Pattern Rules
* GNU Make Doc - Static Pattern Rules
* GNU Make Doc - Old-Fashioned Suffix Rules
沒有留言:
張貼留言