程式扎記: [GNU Make] 變數與巨集 : 變數來自何處

標籤

2010年10月15日 星期五

[GNU Make] 變數與巨集 : 變數來自何處

前言 : 
到目前為止, 我們所看到的大部分變數都會明確的定義在 makefile 檔中, 其實變數的使用可以更複雜. 舉例來說, 我們曾看到過變數被定義在 make 命令列之上, 事實上 make 的變數可以有以下底下幾種來源 : 
- 檔案 : 
當然變數可以定義在 makefile 中, 或是被 makefile 引入, 如 include 指令.

- 命令列 : 
你可以在make 命令列上定義或重新定義變數 :
linux-tl0r:~/gccprac # make CFLAGS=-g CPPFLAGS='-DBSD -DDEBUG'

每個命令引數中所包含的等號 (=), 就是一個變數的賦值運算符. 命列上每個變數賦值運算符右邊必須是一個單獨的 shell 引數. 如果變數的值包含空格, 則必須為引數加上括號, 或是規避空格. 命令列上變數的賦值結果將會蓋掉環境變數以及 makefile 檔中的賦值結果, 你可以使用 := 或 = 賦值運算符將命令列引數設成簡單或是遞回變數. 此外如果使用 override 指令, 你還可以要求 make 採用 makefile 的賦值結果, 而不要採用命令列的賦值結果. 當然你只應該在非常緊迫情況下, 忽略使用者所要求賦值動作. 

環境 : 
當 make 啟動時, 所有來自環境的變數都會被自動定義成 make 的變數. 這些變數具有非常低的優先權, 所以來自 makefile 檔或是命令列引數的賦值動作將會覆蓋環境變數的值. 不過你可以使用 --environment-overrides (或是 -e命令列選項, 讓環境變數覆蓋定相對應的 makefile 變數

當 makefile 被遞回調用時, 將有若干來自上層 make 變數會透過環境變數傳遞給下層的 make. 預設上只有原先就來自環境變數會被匯到下層環境中. 不過你只要使用 export 指令就可以讓任何變數被匯出到環境之中. 條件賦值運算符與環境變數互動良好. 假如你已經在 makefile 檔中設定了一個預設的輸出目錄, 但是你希望使用者輕易能改寫此預設值, 此時條件賦值將會是最佳解決方案 : 
* Makefile 內容 : 
  1. # Presume Output path to be $(PROJECT_DIR)/out  
  2. PROJECT_DIR=.  
  3. OUTPUT_DIR?= $(PROJECT_DIR)/out  
  4. hello: hello.c  
  5.         gcc hello.c -o $(OUTPUT_DIR)/hello  
執行結果 : 
linux-tl0r:~/gccprac # make <因為命令列未指定 OUTPUT_DIR 變數, 故以 makefile 中定義為準>
gcc hello.c -o ./out/hello
linux-tl0r:~/gccprac # OUTPUT_DIR=. make <在命令列指定 OUTPUT_DIR 變數為當前路徑>
gcc hello.c -o ./hello

這樣 make 只會在變數 OUTPUT_DIR 尚未定義的狀況下進行賦值動作. 此外, 使用如下較冗長方式也可以得到幾乎一樣的結果 : 
* Makefile2 內容: 
  1. # Presume Output path to be $(PROJECT_DIR)/out  
  2. PROJECT_DIR=.  
  3. ifndef OUTPUT_DIR  
  4.         OUTPUT_DIR?= $(PROJECT_DIR)/out  
  5. endif  
  6. hello: hello.c  
  7.         gcc hello.c -o $(OUTPUT_DIR)/hello  
執行結果 : 
linux-tl0r:~/gccprac # make -f Makefile2 <變數 OUTPUT_DIR 未被定義, 故在 makefile 中定義>
gcc hello.c -o ./out/hello
linux-tl0r:~/gccprac # OUTPUT_DIR=. make -f Makefile2 <在命令列定義 OUTPUT_DIR 且有值, 故忽略 makefile 對變數 OUTPUT_DIR 定義>
gcc hello.c -o ./hello
linux-tl0r:~/gccprac # OUTPUT_DIR="" <令變數 OUTPUT_DIR 為空值>
linux-tl0r:~/gccprac # make -f Makefile2 <即使變數 OUTPUT_DIR 已被定義, 但為空值故 ifndef 仍然有效>
gcc hello.c -o ./out/hello

觀察上面執行結果可知, 如果變數的值已經設定, 即使是空值, 條件賦值符便會跳過賦值動作, 而 ifdef/ifndef 只會測試 "非空值". 因此我們會使用條件賦值符來取代 ifdef/ifndef 來對 OUTPUT_DIR= 賦值. 最後切記過度使用環境變數將會大大降低你的 makefile 的移植性, 因為其他使用者不太可能設定跟你完全一樣的環境變數. 

自動建立 : 
最後 make 會在執行第一個規則的命令稿之前就會建立自動變數. 傳統上環境變數可協助開發者管理機器之間的差異. 常見的作法就是根據 makefile 檔中所參照的環境變數來建立開發環境 (原始檔樹, 二元輸出樹, 以及程式庫). makefile 將會以環境變數指向每個目錄樹的根目錄. 如果能夠從 PROJECT_SRC 變數參照原始檔樹, 從 PROJECT_BIN 參照二元輸出樹以及從PROJECT_LIB 參照程式庫, 那麼開發者就可以依照需要將這些目錄擺到適當的地方. 

這麼做有一個潛在的問題, 如果這些指向根目錄的變數, 並未進行設定, 將會導致錯誤! 一個解決的辦法就是在 makefile 檔中以條件賦值運算 ?= 提供預設值: 
  1. PROJECT_SRC ?= /dev/$(USER)/src  
  2. PROJECT_BIN ?= $(patsubst %/src, %/bin, $(PROJECT_SRC))  
  3. PROJECT_LIB ?= /net/server/project/lib  
使用這些變數來存取專案裡的元件, 就能建立一個可以適應不同機器的開發環境. 然而請小心並不要過度使用環境變數. 以避免不同機器使用不同環境變數造成難以 debugging 的錯誤! 

沒有留言:

張貼留言

網誌存檔

關於我自己

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