使用變量
————shell
在Makefile中的定義的變量,就像是C/C++語言中的宏同樣,他表明了一個文本字串,在Makefile中執行的時候其會自動原模原樣地展 開在所使用的地方。其與C/C++所不一樣的是,你能夠在Makefile中改變其值。在Makefile中,變量可使用在「目標」,「依賴目標」,「命 令」或是Makefile的其它部分中。安全
變量的命名字能夠包含字符、數字,下劃線(能夠是數字開頭),但不該該含有「:」、「#」、「=」或是空字符(空格、回車等)。變量是大小寫敏感 的,「foo」、「Foo」和「FOO」是三個不一樣的變量名。傳統的Makefile的變量名是全大寫的命名方式,但我推薦使用大小寫搭配的變量名, 如:MakeFlags。這樣能夠避免和系統的變量衝突,而發生意外的事情。ide
有一些變量是很奇怪字串,如「$<」、「$@」等,這些是自動化變量,我會在後面介紹。函數
1、變量的基礎spa
變量在聲明時須要給予初值,而在使用時,須要給在變量名前加上「$」符號,但最好用小括號「()」或是大括號「{}」把變量給包括起來。若是你要使用真實的「$」字符,那麼你須要用「$$」來表示。命令行
變量可使用在許多地方,如規則中的「目標」、「依賴」、「命令」以及新的變量中。先看一個例子:繼承
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)遞歸
$(objects) : defs.hip
變量會在使用它的地方精確地展開,就像C/C++中的宏同樣,例如:string
foo = c
prog.o : prog.$(foo)
$(foo)$(foo) -$(foo) prog.$(foo)
展開後獲得:
prog.o : prog.c
cc -c prog.c
固然,千萬不要在你的Makefile中這樣幹,這裏只是舉個例子來代表Makefile中的變量在使用處展開的真實樣子。可見其就是一個「替代」的原理。
另外,給變量加上括號徹底是爲了更加安全地使用這個變量,在上面的例子中,若是你不想給變量加上括號,那也能夠,但我仍是強烈建議你給變量加上括號。
2、變量中的變量
在定義變量的值時,咱們可使用其它變量來構造變量的值,在Makefile中有兩種方式來在用變量定義變量的值。
先看第一種方式,也就是簡單的使用「=」號,在「=」左側是變量,右側是變量的值,右側變量的值能夠定義在文件的任何一處,也就是說,右側中的變量不必定非要是已定義好的值,其也可使用後面定義的值。如:
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
echo $(foo)
咱們執行「make all」將會打出變量$(foo)的值是「Huh?」( $(foo)的值是$(bar),$(bar)的值是$(ugh),$(ugh)的值是「Huh?」)可見,變量是可使用後面的變量來定義的。
這個功能有好的地方,也有很差的地方,好的地方是,咱們能夠把變量的真實值推到後面來定義,如:
CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar
當「CFLAGS」在命令中被展開時,會是「-Ifoo -Ibar -O」。但這種形式也有很差的地方,那就是遞歸定義,如:
CFLAGS = $(CFLAGS) -O
或:
A = $(B)
B = $(A)
這會讓make陷入無限的變量展開過程當中去,固然,咱們的make是有能力檢測這樣的定義,並會報錯。還有就是若是在變量中使用函數,那麼,這種方 式會讓咱們的make運行時很是慢,更糟糕的是,他會使用得兩個make的函數「wildcard」和「shell」發生不可預知的錯誤。由於你不會知道 這兩個函數會被調用多少次。
爲了不上面的這種方法,咱們可使用make中的另外一種用變量來定義變量的方法。這種方法使用的是「:=」操做符,如:
x := foo
y := $(x) bar
x := later
其等價於:
y := foo bar
x := later
值得一提的是,這種方法,前面的變量不能使用後面的變量,只能使用前面已定義好了的變量。若是是這樣:
y := $(x) bar
x := foo
那麼,y的值是「bar」,而不是「foo bar」。
上面都是一些比較簡單的變量使用了,讓咱們來看一個複雜的例子,其中包括了make的函數、條件表達式和一個系統變量「MAKELEVEL」的使用:
ifeq (0,${MAKELEVEL})
cur-dir := $(shell pwd)
whoami := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif
關於條件表達式和函數,咱們在後面再說,對於系統變量「MAKELEVEL」,其意思是,若是咱們的make有一個嵌套執行的動做(參見前面的「嵌套使用make」),那麼,這個變量會記錄了咱們的當前Makefile的調用層數。
下面再介紹兩個定義變量時咱們須要知道的,請先看一個例子,若是咱們要定義一個變量,其值是一個空格,那麼咱們能夠這樣來:
nullstring :=
space := $(nullstring) # end of the line
nullstring是一個Empty變量,其中什麼也沒有,而咱們的space的值是一個空格。由於在操做符的右邊是很難描述一個空格的,這裏採 用的技術很管用,先用一個Empty變量來標明變量的值開始了,然後面採用「#」註釋符來表示變量定義的終止,這樣,咱們能夠定義出其值是一個空格的變 量。請注意這裏關於「#」的使用,註釋符「#」的這種特性值得咱們注意,若是咱們這樣定義一個變量:
dir := /foo/bar # directory to put the frobs in
dir這個變量的值是「/foo/bar」,後面還跟了4個空格,若是咱們這樣使用這樣變量來指定別的目錄——「$(dir)/file」那麼就完蛋了。
還有一個比較有用的操做符是「?=」,先看示例:
FOO ?= bar
其含義是,若是FOO沒有被定義過,那麼變量FOO的值就是「bar」,若是FOO先前被定義過,那麼這條語將什麼也不作,其等價於:
ifeq ($(origin FOO), undefined)
FOO = bar
endif
3、變量高級用法
這裏介紹兩種變量的高級使用方法,第一種是變量值的替換。
咱們能夠替換變量中的共有的部分,其格式是「$(var:a=b)」或是「${var:a=b}」,其意思是,把變量「var」中全部以「a」字串「結尾」的「a」替換成「b」字串。這裏的「結尾」意思是「空格」或是「結束符」。
仍是看一個示例吧:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
這個示例中,咱們先定義了一個「$(foo)」變量,而第二行的意思是把「$(foo)」中全部以「.o」字串「結尾」所有替換成「.c」,因此咱們的「$(bar)」的值就是「a.c b.c c.c」。
另一種變量替換的技術是以「靜態模式」(參見前面章節)定義的,如:
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
這依賴於被替換字串中的有相同的模式,模式中必須包含一個「%」字符,這個例子一樣讓$(bar)變量的值爲「a.c b.c c.c」。
第二種高級用法是——「把變量的值再當成變量」。先看一個例子:
x = y
y = z
a := $($(x))
在這個例子中,$(x)的值是「y」,因此$($(x))就是$(y),因而$(a)的值就是「z」。(注意,是「x=y」,而不是「x=$(y)」)
咱們還可使用更多的層次:
x = y
y = z
z = u
a := $($($(x)))
這裏的$(a)的值是「u」,相關的推導留給讀者本身去作吧。
讓咱們再複雜一點,使用上「在變量定義中使用變量」的第一個方式,來看一個例子:
x = $(y)
y = z
z = Hello
a := $($(x))
這裏的$($(x))被替換成了$($(y)),由於$(y)值是「z」,因此,最終結果是:a:=$(z),也就是「Hello」。
再複雜一點,咱們再加上函數:
x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))
這個例子中,「$($($(z)))」擴展爲「$($(y))」,而其再次被擴展爲「$($(subst 1,2,$(x)))」。$(x)的值是「variable1」,subst函數把「variable1」中的全部「1」字串替換成「2」字串,於 是,「variable1」變成「variable2」,再取其值,因此,最終,$(a)的值就是$(variable2)的值——「Hello」。 (喔,好不容易)
在這種方式中,或要可使用多個變量來組成一個變量的名字,而後再取其值:
first_second = Hello
a = first
b = second
all = $($a_$b)
這裏的「$a_$b」組成了「first_second」,因而,$(all)的值就是「Hello」。
再來看看結合第一種技術的例子:
a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
sources := $($(a1)_objects:.o=.c)
這個例子中,若是$(a1)的值是「a」的話,那麼,$(sources)的值就是「a.c b.c c.c」;若是$(a1)的值是「1」,那麼$(sources)的值是「1.c 2.c 3.c」。
再來看一個這種技術和「函數」與「條件語句」一同使用的例子:
ifdef do_sort
func := sort
else
func := strip
endif
bar := a d b g q c
foo := $($(func) $(bar))
這個示例中,若是定義了「do_sort」,那麼:foo := $(sort a d b g q c),因而$(foo)的值就是「a b c d g q」,而若是沒有定義「do_sort」,那麼:foo := $(sort a d b g q c),調用的就是strip函數。
固然,「把變量的值再當成變量」這種技術,一樣能夠用在操做符的左邊:
dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
lpr $($(dir)_sources)
endef
這個例子中定義了三個變量:「dir」,「foo_sources」和「foo_print」。
4、追加變量值
咱們可使用「+=」操做符給變量追加值,如:
objects = main.o foo.o bar.o utils.o
objects += another.o
因而,咱們的$(objects)值變成:「main.o foo.o bar.o utils.o another.o」(another.o被追加進去了)
使用「+=」操做符,能夠模擬爲下面的這種例子:
objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o
所不一樣的是,用「+=」更爲簡潔。
若是變量以前沒有定義過,那麼,「+=」會自動變成「=」,若是前面有變量定義,那麼「+=」會繼承於前次操做的賦值符。若是前一次的是「:=」,那麼「+=」會以「:=」做爲其賦值符,如:
variable := value
variable += more
等價於:
variable := value
variable := $(variable) more
但若是是這種狀況:
variable = value
variable += more
因爲前次的賦值符是「=」,因此「+=」也會以「=」來作爲賦值,那麼豈不會發生變量的遞補歸定義,這是很很差的,因此make會自動爲咱們解決這個問題,咱們沒必要擔憂這個問題。
5、override 指示符
若是有變量是一般make的命令行參數設置的,那麼Makefile中對這個變量的賦值會被忽略。若是你想在Makefile中設置這類參數的值,那麼,你可使用「override」指示符。其語法是:
override <variable> = <value>
override <variable> := <value>
固然,你還能夠追加:
override <variable> += <more text>
對於多行的變量定義,咱們用define指示符,在define指示符前,也一樣可使用ovveride指示符,如:
override define foo
bar
endef
6、多行變量
還有一種設置變量值的方法是使用define關鍵字。使用define關鍵字設置變量的值能夠有換行,這有利於定義一系列的命令(前面咱們講過「命令包」的技術就是利用這個關鍵字)。
define指示符後面跟的是變量的名字,而重起一行定義變量的值,定義是以endef關鍵字結束。其工做方式和「=」操做符同樣。變量的值能夠包 含函數、命令、文字,或是其它變量。由於命令須要以[Tab]鍵開頭,因此若是你用define定義的命令變量中沒有以[Tab]鍵開頭,那麼make就 不會把其認爲是命令。
下面的這個示例展現了define的用法:
define two-lines
echo foo
echo $(bar)
endef
7、環境變量
make運行時的系統環境變量能夠在make開始運行時被載入到Makefile文件中,可是若是Makefile中已定義了這個變量,或是這個變 量由make命令行帶入,那麼系統的環境變量的值將被覆蓋。(若是make指定了「-e」參數,那麼,系統環境變量將覆蓋Makefile中定義的變量)
所以,若是咱們在環境變量中設置了「CFLAGS」環境變量,那麼咱們就能夠在全部的Makefile中使用這個變量了。這對於咱們使用統一的編譯 參數有比較大的好處。若是Makefile中定義了CFLAGS,那麼則會使用Makefile中的這個變量,若是沒有定義則使用系統環境變量的值,一個 共性和個性的統一,很像「全局變量」和「局部變量」的特性。
當make嵌套調用時(參見前面的「嵌套調用」章節),上層Makefile中定義的變量會以系統環境變量的方式傳遞到下層的Makefile中。 固然,默認狀況下,只有經過命令行設置的變量會被傳遞。而定義在文件中的變量,若是要向下層Makefile傳遞,則須要使用exprot關鍵字來聲明。 (參見前面章節)
固然,我並不推薦把許多的變量都定義在系統環境中,這樣,在咱們執行不用的Makefile時,擁有的是同一套系統變量,這可能會帶來更多的麻煩。