變數適合用來儲存單列形式的值, 可是多列的值例如命令稿, 如果我們想在不同的地方執行它呢? 考慮下面例子從 Java 類別檔建立 Java 程式庫 (jar 檔) 的命令序列 :
- echo Creating $@...
- $(RM) $(TMP_JAR_DIR)
- $(MKDIR) $(TMP_JAR_DIR)
- $(CP) -r $^ $(TMP_JAR_DIR)
- cd $(TMP_JAR_DIR) && $(JAR) $(JARFLAGS) $@ .
- $(JAR) -ufm $@ $(MANIFEST)
- $(RM) $(TMP_JAR_DIR)
在 GNU make 中, 我們可以透過 define 指令以建立 "套裝命令序列" 的方式來解決此問題, 而我們簡稱它為巨集 (macro) . 雖然與變數容易搞混, 但是"變數" 字詞僅用來指稱由賦值符號所定義. 考慮下面 define 範例 :
- define create-jar
- @echo Creating $@...
- $(RM) $(TMP_JAR_DIR)
- $(MKDIR) $(TMP_JAR_DIR)
- $(CP) -r $^ $(TMP_JAR_DIR)
- cd $(TMP_JAR_DIR) && $(JAR) $(JARFLAGS) $@ .
- $(JAR) -ufm $@ $(MANIFEST)
- $(RM) $(TMP_JAR_DIR)
- endef
- $(UI_JAR): $(UI_CLASSES)
- $(create-jar)
- $(UI_JAR): $(UI_CLASSES)
- @$(create-jar)
何時展開變數 :
有關變數的展開過程, 多半取決於變數之前的定義方式以及定義位置. 即使 make 無法找到錯誤, 獲的預期以外的結果是常有的事. 所以你可能想知道展開變數的規則是什麼? 當 make 執行時, 它會以兩階段來完成它的工作. 第一階段 make 會讀進 makefile 以及被引入的任何其他 makefile . 這個時候其中所定義的變數與規則會被載入 make 的內部資料庫, 而且依存關係也會被建立起來. 第二個階段, make 會分析依存圖並且判斷需要更新的工作目標, 然後執行指令稿以完成所需要的更新動作.
當 make 在處理遞迴變數或 define 指令時, 會將變數裡的每一列或巨集的主體儲存起來, 包括換列符號但不會予以展開. 巨集定義的最後一個換列符號並不會儲存成巨集的一部分, 否則巨集被展開時 make 就會讀進一個額外的換列符號. 當巨集被展開, make 會立即掃描被展開的文字中是否存在巨集或是變數的參照, 如果存在就予以展開, 如此遞迴下去. 如果巨集是在命令稿中被展開, 巨集主體的每一列就會被插進一個前導的跳格字元 (Tab 鍵). 下面是用來處理 "makefile 中元素何時被展開" 的準則 :
下面整理成表格 :
一個通則是, 總是先定義變數和巨集然後再使用他們. 尤其是在工作目標或必要條件使用到變數時, 就需要先予以定義. 舉例說明如下, 假設我們要定義一個 free-space 巨集, 接下來會一次說明一個部分, 最後再組合在一起 :
- BIN := /bin
- PRINTF := /usr$(BIN)/printf
- DF := $(BIN)/df
- AWK := $(BIN)/awk
接著我們定義 free-space 巨集 :
- define free-space
- $(PRINTF) "Free disk space "
- $(DF) . | $(AWK) 'NR ==2 { print $$4 }'
- endef
- OUTPUT_DIR := /tmp
- $(OUTPUT_DIR)/very_big_file:
- $(free-space)
- AWK := $(BIN)/awk
- define free-space
- $(PRINTF) "Free disk space "
- $(DF) . | $(AWK) 'NR ==2 { print $$4 }'
- endef
- OUTPUT_DIR := /tmp
- $(OUTPUT_DIR)/very_big_file:
- $(free-space)
- BIN := /bin
- PRINTF := /usr$(BIN)/printf
- DF := $(BIN)/df
第二階段進行時候, 也就是 make 讀進 makefile 之後, make 會針對每項規則尋找工作目標, 進行分析依存關係以及執行動作. 此處只找到 $(OUTPUT_DIR)/very_big_file 這個工作目標, 因為此工作目標並未存在任何必要條件, 所以 make 會直接執行相應的指令稿 (假定工作目標所代表的檔案不存在). make 所要執行的指令稿就是 $(free-space) 這個命令稿. 所以 make 會將之展開 , 整個規則會變成下面這個樣子 :
- /tmp/very_big_file:
- /usr/bin/printf "Free disk space "
- /bin/df . | /bin/awk 'NR ==2 {print $$4 }'
最後請注意, 將 OUTPUT_DIR 和 BIN 的定義變成遞迴變數, 並不會對前面的次序問題有任何的影響. 重點在 $(OUTPUT_DIR)/very_big_file 工作目標以及 PRINTF, DF 與 AWK 的右邊部分是在何時展開, 因為它們會立即被展開, 所以在這之前該變數就必須先定義好.
Supplement
* Tutorialpoints - Unix Makefile Tutorial
沒有留言:
張貼留言