程式扎記: [GNU Make] 規則 : 樣式規則

標籤

2015年11月25日 星期三

[GNU Make] 規則 : 樣式規則

前言 
我們現在所看到的 makefile 範例已經有點長, 如果這是一支僅包含十幾個或更少檔案的小型程式, 我們可能並不擔心, 但是如果這是一支包含成千上萬檔案的大型程式, 手動指定每個工作目標, 必要條件以及命令稿將會變得不切實際! 此外在我們的 makefile 中, 這些命令稿代表著重複的程式碼. 如果這些命令稿包含了一個瑕疵或曾經修改過, 我們必須更新所有相關的規則. 這將會造成很大的維護問題! 

許多程式在讀進檔案以及輸出檔案時都會依照慣例. 例如 C 編譯器都會假定檔案若是以 .c 為副檔名, 其所包含的就是 C 的原始碼, 把副檔名從 .c 置換成 .o (對 Windows 上某些編譯器來說是 .obj) 就可以得到目的檔的檔名. 這些慣例讓 make 得以透過檔名樣式比對來簡化規則的建立, 以及提供內建規則來處理它們. 舉例來說, 透過這些內建規則我們可以把之前的 makefile 縮減成 7 列 (原先為 17列): 
- Makefile3371_1 
  1. VPATH = src include  
  2. CPPFLAGS = -I include  
  3.   
  4. count_words: counter.o lexer.o -lfl  
  5. count_words.o: counter.h  
  6. counter.o: counter.h lexer.h  
  7. lexer.o: lexer.h  
所有內建規則 (built-in rule) 都是樣式規則 (pattern rule) 的實例. 一個樣式規則看起來就像之前你所見過的一般規則, 只是主檔名會被表示成 % 字符. 上面這個 makefile 之所以行得通是因為 make 裡存在著三項內建規則. 第一項規則描述了如何從一個 .c 檔編譯出一個 .o 檔: 
  1. %.o: %.c  
  2.     $(COMPILE.c) $(OUTPUT_OPTION) $<  
第二項規則描述了如何從 .l 檔建立出一個 .c 檔: 
  1. %.c: %.l  
  2.     @$(RM) $@  
  3.     $(LEX.l) $< > $@  
最後一項特殊規則描述了如何從 .c 檔產生出一個不具副檔名 (經常是一個可執行) 的檔案: 
  1. %: %.c  
  2.     $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@  
我們將進一步探討這個語法的細節, 不過首先讓我們檢視 make 的輸出, 看看它是如何應用這些內建規則. 當我們對上面的 makefile 執行 make 時會看到如下的輸出: 
# make -f Makefile3371_1
cc -I include -c -o count_words.o count_words.c
cc -I include -c -o counter.o counter.c
lex -t lexer.l > lexer.c
cc -I include -c -o lexer.o lexer.c
cc count_words.o counter.o lexer.o /usr/lib64/libfl.a -o count_words
rm lexer.c

首先 make 會讀取 makefile 並且將預定目標 (default goal) 設置成 count_words, 因為命令列之上並未指定任何工作目標故以預定目標進行檢視. 此時 make 發現了四個必要條件:count_words.o (makefile 並未指定這個必要條件, 它是由內定規則提供的!), counter.olexer.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 用來建造程式的若干樣式規則: 
  1. %: %.mod  
  2.     $(COMPILE.mod) -o $@ -e $@ $^  
  3.   
  4. %: %.cpp  
  5.     $(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@  
  6.   
  7. %: %.sh  
  8.     cat $< > $@  
  9.     chmod a+x $@  
這些樣式會依序從 Modula 原始檔, 經過預處理的 C 原始檔和 Bourne shell 命令稿產生可執行檔. 

靜態樣式規則 
靜態樣式規則 (static pattern rule) 只能應用在特定的工作目標. 其語法如下: 
  1. targets …: target-pattern: prereq-patterns …  
  2.         recipe  
  3.         …  
一個簡單範例如下: 
  1. $(OBJECTS): %.o: %.c  
  2.     $(CC) -c $(CFLAGS) $< -o $@  
此規則與一般樣式規則的唯一差別是, 開頭的 $(OBJECTS): 描述. 這將使得該項規則只能應用在 $(OBJECTS) 變數中所列舉的檔案上. 此規則與樣式規則十分類似. %.o 樣式會比對$(OBJECTS) 中所列舉的每個目的檔, 並且取出其字幹. 然後該字幹會被替換進 %.c 樣式, 以產生工作目標的必要條件. 如果工作目標樣式不存在, 則 make 會發出警告. 

如果明確列出工作目標檔案較容易進行副檔名或其他樣式的比對, 可以考慮靜態樣式規則. 

後綴規則 
後綴規則 (suffix rule) 是用來定義規則的最初 (也是過時的) 方法. 舊版的 make 可能不支援 GNU make 的樣式規則語法, 因此你仍然會在許多 makefile 檔中看到後綴規則, 所以你最好能夠了解它的語法. 儘管為目標系統 (target system) 編譯 GNU make 可以解決 makefile 移植性, 但是在一些罕見的情況下你可能仍舊需要使用後綴規則. 

後綴規則中的工作目標, 可以是一個副檔名或兩個被銜接再一起的副檔名: 
  1. .c.o:  
  2.     $(COMPILE.c) $(OUTPUT_OPTION) $<  
這有點令人混淆, 因為必要條件的副檔名被擺到開頭, 而工作目標退居第二位. 這個規則所比對的工作目標以及必要條件跟下面的規則一樣: 
  1. %.o: %.c  
  2.     $(COMPILE.c) $(OUTPUT_OPTION) $<  
後綴規則會透過移除工作目標的副檔名已形成檔案的主檔名, 以及藉由將工作目標的副檔名位置替換成必要條件的副檔名已形成必要條件. make 只會在這兩個 (被銜接再一起的) 副檔名都列在已知副檔名清單中時, 將之視為後綴規則. 

上面的後綴規則就是所謂的 雙後綴規則 (double-suffix rule). 因為它包含了兩個副檔名. 你還是可以使用單後綴規則 (single-suffix rule), 它只包含一個副檔名, 也就是原始檔的副檔名. 這個規則可用來建造執行檔, 因為 Unix 上的執行檔不需要副檔名: 
  1. .p:  
  2.     $(LINK.p) $^ $(LOADLIBES) $(LDLIBS) -o $@  
這個規則將會從 Pascal 原始檔中產生出可執行映像 (executable image). 這個規則作用等同下面的樣式規則: 
  1. %: %.p  
  2.     $(LINK.p) $^ $(LOADLIBES) $(LDLIBS) -o $@  
已知副檔名清單 (known suffix list) 是語法中最奇特的部分, 你可以使用 .SUFFIXES 這個特別的工作目標來設定已知的副檔名. 下面是 .SUFFIXES 知預設值的第一個部分: 
  1. .SUFFIXES: .out .a .ln .o .c .cc .C .cpp .p .f .F .r .y .l  
你只要在 makefile 檔中加入 .SUFFIXES 規則, 就可以將自己的副檔名加入此清單. 如果要刪除所有已知的副檔名 (因為它們干擾到你的副檔名), 你只要加入此規則時不指定必要條件即可. 你還可以使用命令列選項 --no-builtin-rules ( -r) 完成. 

Supplement 
GNU Make Doc - Defining and Redefining Pattern Rules 
GNU Make Doc - Static Pattern Rules 
GNU Make Doc - Old-Fashioned Suffix Rules

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!