程式扎記: [GNU Make] 函式 : 內建函式

標籤

2015年12月2日 星期三

[GNU Make] 函式 : 內建函式

前言 
當你開始以 make 變數取代簡單的常數之後, 你將發現自己越來越想以複雜的方式來操作變數以及它們的內容. GNU make 提供不少內建函式可用來操作變數和它們的內容. make 的函式可以分類為: 字串操作, 檔名操作, 流程控制, 使用者自訂函式 以及若干 (重要的雜項函式

但首先你應該多知道一些函式的語法, 所有的函式都會具備有如下的形式: 
$(function-name arg1[, argn])

$( 之後式內建函式的名稱, 接著是函式的引數. 第一個引數的前導空白會被刪除, 但是後續的任何引數若包含的前導 (當然也包含內嵌的和跟在後面的) 空白則都會被保留下來! 函式引數是以逗號為分隔符, 所以只有一個引數的函數並不需要使用逗號, 具有兩個引數的函式需要一個逗號並以此類推. 許多只接受一個引數的函式會把它的引數視為一串以空格隔開的單字. 對這些函數而言, 它們會以空白作為分隔符. 

作者喜歡空白, 因為它可以讓函式的描述較具可讀性並且容易維護. 然而有時空白在引數串列或變數定義中可能會讓 make 無法正確解讀我們所做的描述. 這個時候你沒有多少選擇, 只能移除有問題的空白! 在之前看過一個例子, 在該例子中一個結尾的空白會被意外地插入到 grep 命令的搜尋樣式裡. 後面還會看到更多例子, 屆時我們會指出問題在哪. 

make 有需多函式允許你以樣式為引數, 此樣式的語法如同樣式規則中所使用的樣式 (參見 2.4 節的樣式規則). 樣式之中可以包含一個 % 符號, 以及前導或跟在後面的字符 (或者 both). % 字符代表零或多個任何類型的字符. 進行工作目標字串比對時, 此樣式必須比對整個字串, 而不只是字串的子集. 稍後我們會舉例以說明. 在樣式中, % 字符是一個選項, 通常你可以適時予以省略. 

字串函式 
make 的內建函式大部分都可以操作兩種形式的文字, 不過有些函式具有特別強的字串處理能力, 這就是這裡要探討的內容. 在 make 中常見的字串操作就是從一份檔案清單選出一組檔案來. shell script 中之所以會常用到 grep 就是這個原因. 在 make 中我們有 filterfilter-out 和 findstring 等函式可以使用. 首先來看 filter
$(filter pattern ...,text)

filter 函式會將 text 視為一系列被空格隔開的單字, 與 pattern 比對之後, 接著會傳回相符者. 舉例來說, 為了建立使用者介面 (user-interface) 的程式庫, 我們可能只想從 ui 子目錄中選出目的檔. 接下來我們將會從檔名清單中取出開頭為 ui/ 而結尾為 .o 的檔案. % 字符將會相符其間任何數目的字符: 
  1. $(ui_library): $(filter ui/%.o,$(objects))  
  2.     $(AR) $(ARFLAGS) $@ $^  
filter 還可以接受多個 (被空格隔開的) 樣式. 正如之前所說, 樣式必須比對整個單字, 才可以將相符的單字擺到輸出清單. 所以如下面例子: 
- Make3370_1 
  1. words := he the hen other the%  
  2. get-the:  
  3.     @echo he matches: $(filter he, $(words))  
  4.     @echo %he matches: $(filter %he, $(words))  
  5.     @echo he% matches: $(filter he%, $(words))  
  6.     @echo %he% matches: $(filter %he%, $(words))  
執行後輸出如下: 
# make -f Make3370_1
he matches: he
%he matches: he the
he% matches: he hen
%he% matches: the%

第一個樣式只會相符單字 'he', 因為該樣式必須比對整個單字, 而不是單字的一部分. 樣式之中只能包含一個 % 字符. 如果樣式包含了額外的 % 字符, 第一個 % 字符除外, 其餘 % 字符都會被視為自面字符 (literal character). filter 無法在單字裡比對子字串而且只接受一個通配字符, 這看起來或許有些奇怪, 當你需要特殊比對的功能不存在, 可能會需要一些 tricky 的做法. 不過你可以透過 "迴圈" 和 "條件測試" 來實作你想要的比對. (稍後會介紹) 接著來看 filter-out 函數: 
$(filter-out pattern ...,text)

filter-out 函式所做的事剛好跟 filter 相反, 用來選出不相符的每個單字. 所以下面的例子可以從檔名清單中選出所有非 C 標頭檔的檔案: 
  1. all_source := count_words.c counter.c lexer.l counter.h lexer.h  
  2. to_compile := $(filter-out %.h, $(all_source))  
接著來看下一個函數 findstring
$(findstring string ...,text)

此函式會將 text 裡搜尋 string. 如果字串找到了, 此函式就會傳回 string; 否則它會傳回空值. 乍看之下此函式有點像字串搜尋函式 grep, 但並非如此, 首先且最重要的是此函數的回傳值只是 "搜尋字串", 而不是比對到的單字, 其次 "搜尋字串" 無法包含通配字符 (就算你在 "搜尋字串" 中使用 % 字符, 也是按照字面比對). 此函式通常會跟梢後討論的 if 函式一起使用. 不過下面狀況適合單獨使用此函式: 
- Make3370_2 
  1. # Figure out which path is contained by current working directory  
  2. find_tree:  
  3.         # PWD = $(PWD)  
  4.         # $(findstring /test/book/admin, $(PWD))  
  5.         # $(findstring /root, $(PWD))  
命令稿中的每列命令開頭是一個 Tab 與 Shell 的註解字符 '#', 所以每列命令就像其他命令一樣在自己的 subshell 中被執行. 與 @echo 相比, 此方法將簡單之 make 結構的展開動作列印出來較方便. 事實上使用較具移植性的 shell 運算符 ":" 也可以達到同樣效果, 不過遇到會進行轉向 "redirection" 的動作如 "> word" 時, 會產生建立 word 檔的副作用. 對此 makefile 執行會看到如下結果: 
# pwd
/root/tmp
# make -f Make3370_2
# PWD = /root/tmp
#
# /root

接著我們要來看兩個 "搜尋與代換" 的函式, 首先是 subst
$(subst search-string,replace-string,text)

這是一個不具通配符能力的 "搜尋與替換" 函式. 它最常被用在檔名清單中將一個副檔名替換成另一個副檔名: 
  1. sources := count_words.c counter.c lexer.c  
  2. objects := $(subst .c,.o,$(sources))   
請注意逗號之後不可以有空格, 如果我們將前面的描述作如下的改寫: 
- Make3370_3 
  1. sources := count_words.c counter.c lexer.c  
  2. objects := $(subst .c, .o, $(sources))  
  3.   
  4. showObjs:  
  5.         @echo ${objects}  
執行結果如下: 
# make -f Make3370_3
count_words .o counter .o lexer .o

這不是我們預期的結果! 問題出在 .o 引數之前的空格是 "代換字串" 的一部分, 所以擺在 .c 引數前的空格沒有問題, 因為第一個引數之前的任何空白符號都會被 make 移除. 事實上 $(sources) 之前的空格也是能避免則避免, 雖然在這個範例並無問題. 最後請注意 subst 並不知道什麼是副檔名, 它只知道字符所構成的字串, 所以只要 $(sources) 中只要出現 .c 字樣就會被替換掉. 例如檔名 car.cdr.c 將會被轉換成 car.cdr.o. 或許這不是你想要的結果. 接著來看 patsubst
$(patsubst search-pattern,replace-pattern,text)

這是一個具備通配符能力的 "搜尋和替換" 函式. 照例此處比對樣式只可包含一個 % 字符. replace-pattern 中的百分比符號會被展開與樣式相符的文字. 切記 search-pattern 必須比對 text 的整個值. 例如下面範例將只會刪除 text 裡結尾的斜線符號, 而不是每個 text 中的每個斜線符號: 
  1. strip-trailing-slash = $(patsubst %/,%,$(directory-path))  
對於相同的代換操作來說, 代換參照 (substitution reference) 是個具移植性的做法. 代換參照與法如下: 
$(variable:search=replace)

search 可以是一個簡單的字串; 如果是這樣只要該字串出現在一個單字的結尾 (亦即後面接著空白符號或變數值的結尾), 就會被替換成 replace. 此外 search 可以包含一個代表通配符 %: 如果是這樣, make 會依照 patsubst 的規則進行搜尋和替換的操作. 這個功能的語法相對 patsubst 來說可讀性較差. 一個測試範例如下: 
- Make3370_4 
  1. TEST_VAR := hellojohn  
  2. TEST_VAR2 := $(TEST_VAR:john=peter)  
  3.   
  4. ShowTest:  
  5.         @echo "TEST_VAR=$(TEST_VAR)"  
  6.         @echo "TEST_VAR2=$(TEST_VAR2)"  
執行結果如下: 
# make -f Make3370_4
TEST_VAR=hellojohn
TEST_VAR2=hello
peter

如我們先前所見, 變數通常會包含一串單字, 接下來我們看到可以從一份清單中選出所需要的單字的函式, 計算清單長度的函數等等. 如同所有其他的 make 函式, 單字清單中式以空白作為分隔符. 
$(words text)

此函式會回傳 text 中單字的數目. 一個使用範例如下: 
- Make3370_5 
  1. CURRENT_PATH := $(subst /, ,$(HOME))  
  2. words:  
  3.         @echo My HOME path has $(words $(CURRENT_PATH)) directories.  
範例執行結果: 
# pwd
/root/tmp
# make -f Make3370_5
My HOME path has 1 directories.

$(words n,text)

此函式會回傳 text 中第 n 個單字. 第一個單字的編號是 1. 如果 n 的值大於 text 中的單字個數, 此函式會傳回空值. 一個範例如下: 
- Make3370_6 
  1. VERSION_LIST := $(subst ., ,$(MAKE_VERSION))  
  2. MINOR_VERSION := $(word 2, $(VERSION_LIST))  
  3.   
  4. ShowVar:  
  5.         @echo VERSION_LIST=$(VERSION_LIST)  
  6.         @echo MINOR_VERSION=$(MINOR_VERSION)  
範例執行結果如下: 
# make -f Make3370_6
VERSION_LIST=3 82
MINOR_VERSION=82


$(firstword text)

此函式會傳回 text 中的第一個單字, 功能等同於 $(word 1,text). 一個使用範例如下: 
- Make3370_7 
  1. VERSION_LIST := $(subst ., ,$(MAKE_VERSION))  
  2. MAJOR_VERSION := $(firstword $(VERSION_LIST))  
  3.   
  4. ShowVar:  
  5.         @echo VERSION_LIST=$(VERSION_LIST)  
  6.         @echo MAJOR_VERSION=$(MAJOR_VERSION)  
範例執行結果如下: 
# make -f Make3370_7
VERSION_LIST=3 82
MAJOR_VERSION=3


$(wordlist start,end,text)

此函數傳回 text 中範圍從 start () 到 end () 的單字. 如同 word 函式, 第一個單字的編號是 1. 如果 start 的值大於單字個數, 則含式傳回空值. 如果 end 的值大於單字個數, 則函式回傳自 start 開始到最後的單字清單. 一個使用範例如下: 
- Make3370_8 
  1. USER_NAME := john  
  2. PGU = $(wordlist 3,4, \  
  3.             $(subst :, , \  
  4.               $(shell grep "^$1:" /etc/passwd)))  
  5. Show:  
  6.         @echo Demo:  
  7.         # $(call PGU,$(USER_NAME))  
範例執行結果如下: 
# make -f Make3370_8
Demo:
# 1000 1000

重要的雜項函數 
在我們使用這些函數來管理檔名之前, 讓我們先來了解兩個非常有用的函式: sort 和 shell 
$(sort list)

sort 函式會排序它的 list 引數並且移除重複的項目. 此函式執行之後會回傳依照字典排序之下的單字清單, 並且以空白作為分隔符. 此外此函式還會移除前導與接在後面的空格. 一個簡單範例如下: 
# make -f- <<< 'x:;@echo =$(sort d b s d t )='
=b d s t=

因為 sort 函式是由 make 直接實作, 所以它並不支援 sort 程式所提供的命令列參數. 

$(shell command)

shell 函式的引數會被展開 (就像所有其它的引數) 並傳遞給 subshell 執行. 然後 make 會讀取 command 的標準輸出, 並將之傳回成函式的值. 輸出中所出現一連串的換行符號會被縮減成單一空白符號. 任何接在最後面的換行符號都會被刪除. 標準錯誤以及任何程式的結束狀態都不會被傳回. 一個測式範例如下: 
- Make3370_9 
  1. STDOUT := $(shell echo normal message)  
  2. STDERR := $(shell echo error message 1>&2)  
  3. SHELL_VALUE:  
  4.         # $(STDOUT)  
  5.         # $(STDERR)  
測式執行結果: 
# make -f Make3370_9
error message
# normal message
#

因為 shell 函式可用來調用任何外部程式, 所以使用時應該注意小心. 特別是要考慮簡單變數與遞迴變數之間的差異. 考慮範例如下: 
  1. START_TIME := $(shell date)  
  2. CURRENT_TIME = $(shell date)  
START_TIME 變數會在被定義當下執行 date 命令一次; CURRENT_TIME 變數則是每次被使用時就會執行 date 命令一次. 

現在我們的工具箱已經能夠撰寫出極為有用的函式. 考慮下面函式 has-duplicate 可用來測試一個值是否包含重複的內容: 
- Make3370_10 
  1. has-duplicate = $(filter  \  
  2.                   $(words $1),\  
  3.                   $(words $(sort $1)))  
  4.   
  5. WORD_LIST = john peter mary  
  6. DWORD_LIST = john peter mary john  
  7.   
  8. Show:  
  9.         @echo WORD_LIST='$(WORD_LIST)' has duplicate? $(call has-duplicate,$(WORD_LIST))  
  10.         @echo DWORD_LIST='$(DWORD_LIST)' has duplicate? $(call has-duplicate,$(DWORD_LIST))  
範例執行結果如下: 
# make -f Make3370_10
WORD_LIST=john peter mary has duplicate? 3
DWORD_LIST=john peter mary john has duplicate?
 # 因為有 duplicate, 所以輸出為空值

另一個常見的用法是可以使用時間戳記產生檔名: 
  1. RELEASE_TAR := mpwm-$(shell date +%F).tar.gz  
或是你可以讓 date 多做一點事, 另一個寫法如下: 
  1. RELEASE_TAR := $(shell date +mpwm-%F.tar.gz)  
檔名函式 
makefile 的撰寫通常會花許多時間在檔案的處理上, 因此有許多相關的 make 函式提供來處理此類工作. 

$(wildcard pattern...)

之前提過通配符可以使用在工作目標, 必要條件以及命令稿等語境中, 但如果我們想將此功能用在其他語境, 例如變數定義, 該怎麼辦? 使用 subshell 來展開樣式可以達成目的, 但式執行起來會非常慢. 此時我們可以借用 wildcard 函式, 例如: 
- Make3370_11 
  1. SOURCES := $(wildcard *.c *.h)  
  2. SHOW:  
  3.         @echo SOURCES='$(SOURCES)'  
範例執行結果: 
# ls *.c *.h
a.c a.h b.c
# make -f Make3370_11
SOURCES=a.c b.c a.h


$(dir list...)

dir 函式會回傳 list 中每個單字代表路徑的目錄部分. 下面使用者自訂函式會傳回包含 C 原始檔的每個子目錄: 
  1. source-dirs = $(sort  \  
  2.                  $(dir  \  
  3.                    $(shell find . -name '*.c')))  
find 命令會傳回當前目錄中所有 C 原始檔的檔案路徑, 接著 dir 函式拿掉檔名的部分並保留目錄的部分, 最後由 sort 移除重複的項目. 

$(notdir name...)

此函式會傳回檔案路徑的檔名部分. 我們經常可以看到 dir 和 notdir 會被一起使用來產生必要的輸出. 舉例來說, 假設你必須在輸出檔所在的目錄執行自訂 shell 命令稿以產生輸出檔: 
  1. $(OUT)/myfile.out: $(SRC)/source1.in $(SRC)/source2.in  
  2.         cd $(dir $@); \  
  3.         generate-myfile $^ > $(notdir $@)  
事實上如果 $(OUT) 是一個絕對路徑, 此處就不需要 notdir 函數了, 不過上面寫法較具可讀性. 接著下面要來介紹新增, 刪除副檔名, 基本檔名等等的函式. 
$(suffix name...)

此函式會傳回引數中每個單字的後綴 (即檔案名稱的副檔名). 例如下面的使用者自訂函式會將測試清單中所有單字是否具備相同的後綴: 
  1. same-suffix = $(filter 1,$(words $(sort $(suffix $1))))  
條件句裡的 suffix 函式常會跟 findstring 一起使用. 

$(basename name...)

此函式是 suffix 函式的捕函式. basename 函式傳回的是不含後綴部分的檔案名稱. 呼叫 basename 之後任何前導的路徑元件都會被原封不動的保留下來. 一個簡單範例如下: 
- Make3370_12 
  1. FILES := /tmp/john.c g.c a.c d.h  
  2.   
  3. Show:  
  4.         @echo FILES='$(FILES)'  
  5.         @echo Suffix: $(suffix $(FILES))  
  6.         @echo Basename: $(basename $(FILES))  
範例執行結果如下: 
# make -f Make3370_12
FILES=/tmp/john.c g.c a.c d.h
Suffix: .c .c .c .h
Basename: /tmp/john g a d


$(addsuffix suffix,name...)

addsuffix 函式會將你所指定的 suffix 附加到 name 中所包含的每個單字之後. suffix 可以是任何值. 

$(addprefix prefix,name...)

addprefix 是 addsuffix 的捕函式. 下面的使用者自訂函式可用來測試一組檔案是否存在而且不為空: 
- Make3370_13 
  1. SRC_FILES := ./src/a.c ./src/b.c  
  2. SRC_FILES_TEST := ./src/a.c not_exist  
  3. valid-files = test -s . $(addprefix -a -s ,$1) && echo 'yes' || echo 'no'  
  4.   
  5. Show:  
  6.         @echo SRC_FILES='$(SRC_FILES)' all exist?  
  7.         $(call valid-files,$(SRC_FILES))  
  8.         @echo SRC_FILES_TEST='$(SRC_FILES_TEST)' all exist?  
  9.         $(call valid-files,$(SRC_FILES_TEST))  
範例執行輸出結果: 
# make -f Make3370_13
SRC_FILES=./src/a.c ./src/b.c all exist?
test -s . -a -s ./src/a.c -a -s ./src/b.c && echo 'yes' || echo 'no'
yes
SRC_FILES_TEST=./src/a.c not_exist all exist?
test -s . -a -s ./src/a.c -a -s not_exist && echo 'yes' || echo 'no'
no


$(join prefix-list,suffix-list)

join 是 dir 和 notdir 的捕函式. 此函數的引數是兩個串列: prefix-list 和 suffix-list, 它會把 prefix-list 的第一個元素與 suffix-list 的第一個元素銜接在一起, 然後把 prefix-list 的第二個元素與 suffix-list 的第二個元素銜接並以此類推. 

流程控制 
到目前為止我們所看到的函式有需多倍時做成針對串列 (清單) 進行處理, 所以即使不使用迴圈結構, 它們也能夠運作的很好. 然而如果不提供實際的迴圈運算符以及某種條件處理能力, make 的巨集語言將會受到非常大的限制! 還好 make 有支援以上所提到的兩種功能. 這一節還會談到 "無可挽回" 的 error 函式, 此函式顯然是流程控制中最極端的一種做法. 首先來看第一個流程控制 if 函式: 
$(if condition, then-part,else-part)

if 函式 (不要跟之前介紹的 ifeq, ifne, ifdef  ifndef 搞混) 會根據條件表示式 (conditional expression) 的求值結果, 從接下來的兩個巨集選一個出來進行展開的動作. 如果 condition 展開之後包含任何字符 (即使是空格), 那麼它的求值結果為 "真", 於是會對 then-part 進行展開的動作; 否則如果 condition 展開後空無一物, 那麼其求值結果為 "假", 於是會對 else-part 進行展開動作. 

一個使用範例是測試 makefile 是否在 Windows 上執行藉以設定路徑的分隔符為 ";" 或是 Unix/Linux 的 ":", 方法很簡單. 尋找 COMSPECT 環境變數就行了, 因為只有 Windows 會定義此環境變數: 
  1. PATH_SEP := $(if $(COMSPEC),;,:)  
另一個使用情境是當你要使用新版本的 Make 功能 (像是 eval), 應該檢查 make 的版本是否支援, 這種測試字串的功能常會用到 if 和 filter 函式: 
  1. $(if $(filter $(MAKE_VERSION),3.80),,$(error This makefile requires GNU make version 3.80.))  
現在當 make 的後續版本被釋出, 你還可以將其他支援的版本編號加入條件式: 
  1. $(if $(filter $(MAKE_VERSION),3.80 3.81 3.82),,$(error This makefile requires GNU make version -.))  
這個方法的缺點是當 make 有新的版本釋出, 你就必須對此描述進行更新的動作. 不過這不常發生就是了. 如上的測試動作通常會放在 makefile 的最上層, 如果表示式的求值結果為 "真", if 不會有任何動作, 否則會透過 error 函式 (Functions that control Make) 終止 make 的執行狀態! 接著來看 error 函式. 

$(error text)

error 函式可用來打印 "無可挽回" 的錯誤結果訊息 (fatal error message). 在此函式印出訊息後, make 將會以 2 這個結束狀態中止執行. 輸出中包含當前的 makefile 名稱, 當前的列數以及訊息文字. 接下來讓我們為 make 實作常見的 assert 編成結構: 
- Make3370_14 
  1. # $(call assert,condition,message)  
  2. define assert  
  3.         $(if $1,,$(error Assertion failed: $2))  
  4. endef  
  5. # $(call assert-file-exists,wildcard-pattern)  
  6. define assert-file-exists  
  7.         $(call assert,$(wildcard $1),$1 does not exist)  
  8. endef  
  9. # $(call assert-not-null,make-variable)  
  10. define assert-not-null  
  11.         $(call assert,$($1),The variable "$1" is null)  
  12. endef  
  13. err-exist:  
  14.         $(call assert-not-null,NOT_EXISTENT)  
測試執行輸出: 
# make -f Make3370_14
Make3370_14:17: *** Assertion failed: The variable "NOT_EXISTENT" is null. Stop.

warning 函式 可以打印跟 error 一樣的訊息, 但是它不會終止 make 的執行狀態. 接著來看迴圈相關的函式. 

$(foreach variable,list,body)

這個函式可讓你在反覆展開文字時, 將不同的值代換進去. 這跟你使用不同的引數反覆的執行函式的狀況不太一樣. 首先來看一個範例: 
- Make3370_15 
  1. letters := $(foreach letter,a b c d,$(letter))  
  2. show-words:  
  3.         # letters has $(words $(letters)) words: '$(letters)'  
# make -f Make3370_15
# letters has 4 words: 'a b c d'

當 foreach 函式被執行, 它會反覆展開迴圈主體並將值以變數名稱 letter 依序傳入清單的單字 "a", "b", "c" 與 "d". 每次展開得到的輸出會被累積起來並使用空格為分隔符連接起來. 接著來看第二個範例, 透過下面的自訂函式可用來測試一組變數是否有定義: 
- Make3370_16 
  1. VARIABLE_LIST := SOURCES OBJECTS NOT_EXIST  
  2. SOURCES := src  
  3. OBJECTS := obj  
  4. test_variables = $(foreach i,$(1),  \  
  5.   $(if $($i),,  \  
  6.     $(shell echo $i has no value > /dev/stderr)))  
  7.   
  8. Show:  
  9.         $(call test_variables,$(VARIABLE_LIST))  
執行結果: 
# make -f Make3370_16
NOT_EXIST has no value
make: `Show' is up to date.

稍早我們提到要使用迴圈與條件測試來從清單中找尋符合樣式的單字, 下面函式將從單字清單找出包含特定字串的所有單字來: 
- Make3370_17 
  1. # $(call grep-string, serch-string, word-list)  
  2. define grep-string  
  3.         $(strip                                 \  
  4.           $(foreach w,$2,                       \  
  5.             $(if $(findstring $1,$w),$w)))  
  6. endef  
  7.   
  8. words := count_words.c counter.c lexer.l lexer.h counter.h  
  9. find-src:  
  10.         @echo $(call grep-string,.c,$(words))  
執行結果: 
# make -f Make3370_17
count_words.c counter.c

變數何時該使用小括號 
之前提過單一字母形式的 make 變數不需要小括號. 例如所有基本變數其名稱都採用單一字母形式. 就像你在 GNU Make 文件上所看到的, 所有的自動變數都沒有加上小括號. 不過強烈建議在處理變數時盡量加上小括號以避免不可預期錯誤. 下面是使用 foreach 函式時候因為前述說明造成的常見錯誤 ($INCLUDE -> $(I)NCLUDE -> 因為變數 $I 為空, 故清單為 'NCLUDE'): 
  1. INCLUDE_DIRS := ...  
  2. INCLUDES := $(foreach i,$INCLUDE_DIRS,-I $i)  
  3. # INCLUDES 現在值為 "-I NCLUDE_DIRS" !!!!  
較不重要的其他函式 
最後還有一些較不重要的內建函式, 儘管它們使用頻率不如 foreach 或 call, 但是搭配起來使用還是相當方便: 
$(strip text)

strip 函式將會從 text 移除所有前導與接在後面的空白符號, 並以單一空白符號來置換所有內部的空格. 此函式常用來清理 "條件表示式" 中所使用的變數. 當變數和巨集的定義橫跨多列時, 可以透過此函式從中移除非必要的空白符號. 不過如果函式會受引數之前的前導空格影響, 為函式引數 $1, $2... 等加上 strip 也是個不錯的想法. 

$(origin variable)

origin 函式將回傳描述變數來自何處的字串. 這個函式可以協助你決定如何使用一個變數的值. 舉例來說: 如果變數來自環境, 或許你會想要忽略該變數的值; 如果變數來自於命令列, 你就不會這麼做. 而此函數可能的輸出如下: 
- undefined: 變數尚未定義
- default: 變數定義來自 make 的內建資料庫, 如果你改變了內建變數的值, origin 所傳回會是最近一次的操作結果.
- environment: 變數定義來自於環境 (並且未使用 --environment-overrides 選項).
- environment override: 變數定義來自於環境 (並且未使用 --environment-overrides 選項).
- file: 變數定義來自於 makefile.
- command line: 變數定義來自於命令列
- override: 變數定義來自於 override 指令.
- automatic: 這個變數是 make 所定義的自動變數.

讓我們來看一個比較具體的範例. 下面是一個新的斷言函式, 可用來測試一個變數是否有定義: 
- Make3370_18 
  1. # $(call assert,condition,message)  
  2. define assert  
  3.         $(if $(strip $1),,$(error Assertion failed: $2))  
  4. endef  
  5. # $(call assert-file-exists,wildcard-pattern)  
  6. define assert-file-exists  
  7.         $(call assert,$(wildcard $1),$1 does not exist)  
  8. endef  
  9. # $(call assert-not-null,make-variable)  
  10. define assert-not-null  
  11.         $(call assert,$($1),The variable "$1" is null)  
  12. endef  
  13.   
  14. # $(call assert-defined,variable-name)  
  15. define assert-defined  
  16.         $(call assert,                           \  
  17.           $(filter-out undefined,$(origin $1)),\  
  18.             '$1' is undefinied)  
  19. endef  
  20.   
  21. EXIST := TEST  
  22. err-exist:  
  23.         @echo === Test ===  
  24.         # origin of EXIST = $(origin EXIST)  
  25.         # origin of NOT_EXIST = $(origin NOT_EXIST)  
  26.         $(call assert-defined,EXIST)  
  27.         @echo === Done ===  
執行結果: 
# make -f Make3370_18
=== Test ===
# origin of EXIST = file
# origin of NOT_EXIST = undefined
=== Done ===

如果替換成 $(call assert-defined,NOT_EXIST) , 執行結果能偵測到未定義變數並輸出如下: 
# make -f Make3370_18
Make3370_18:26: *** Assertion failed: 'NOT_EXIST' is undefinied. Stop.


$(warning text)

warning 函式類似於 error 函式, 不過此函式不會導致 make 結束執行. 如同 error 函式, warning 的輸出包括當前的 makefile 檔名, 當前的列號以及所要顯示的訊息內容. 一個使用範例如下: 
  1. JAVAC := /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.91-2.6.2.1.el7_1.x86_64/bin/javac  
  2. $(if $(wildcard $(JAVAC)),,$(warning The java compiler variable, JAVAC ($(JAVAC)), is not properly set.))  
  3. ...  

Supplement 
GNU Make Doc - Functions for Transforming Text 
GNU Make Doc - Conditional Parts of Makefiles 
conditional directive causes part of a makefile to be obeyed or ignored depending on the values of variables. Conditionals can compare the value of one variable to another, or the value of a variable to a constant string. Conditionals control what make actually “sees” in the makefile, so they cannot be used to control recipes at the time of execution.
• Conditional Example: Example of a conditional
• Conditional Syntax: The syntax of conditionals.
• Testing Flags: Conditionals that test flags.

[GNU Make] 規則 : 自動變數 與 使用VPATH/vpath 來搜尋檔案 
GNU Make Doc - Functions for File Names

沒有留言:

張貼留言

網誌存檔

關於我自己

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