2011年12月7日 星期三

Makefile用法(變數傳遞)

兩種風格的變量定義 
GNU有兩種定義變量的方式,它們的不同體現在定義它們的風格和他們被展開的方式。 
第一類叫做遞歸展開變量(Recursively Expanded Variable)。用=define關鍵字都 
可以定義這種變量,如果變量的定義引用了其它的變量,那麼引用會一直展開下去, 
直到找到被引用的變量的最新的定義,並以此作為改變量的值返回。例如: 
代碼:
foo = $(bar)
bar = $(ugh)
ugh = Huh?
$(foo)的值究竟是什麼呢? $(foo)被展開成$(bar)$(bar)被展開成$(ugh),最終$(ugh)
被展開成「Huh?」,那麼$(foo)的值就是「Huh?」這種類型的變量是所有其他make工具支持 
的變量類型。它有著自己的有點和缺點: 
優點: 
它可以向後引用變量 
缺點: 
1).你不能對該變量進行任何擴展,例如 
CFLAGS=$(CFLAGS) -O
會造成無限循環展開,所有對於遞歸展開變量,這樣做是不允許的。 
2).如果makefile中的某個變量調用了某些函數,那麼在變量被展開的每一次,函數都會被調用, 
說的好些,無非是make慢點,更壞的情況是一些 shell函數和通配符的重複調用的結果往往 
是難以預知的。 
最後,用一個例子說明上面的問題。用這個makefile試一下就明白了(分別開啟和註銷掉第三行, 
你就能看到結果)
代碼:
bar = test
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all: ;echo foo
為了解決這些問題,GNU定義了另一中其它風格的變量,叫做簡單擴展變量(Simply Expanded Variables)
簡單擴展變量用符號:=來定義,用這種方式定義的變量,會在變量的定義點,按照被引用的變量的當前值 
進行展開。同樣寫個例子,測試一下,這東西用文字表達太困難了 
代碼:
m := mm
x := $(m)
y := $(x) bar
x := later
all:;echo $(x) $(y)

這種定義變量的方式更適合在大的編程項目中使用,因為它更像我們一般的編程語言。 
其實,GNU還對makefile中定義變量的方式進行了第三種擴展,用?=定義變量,它的含義是如果變量還沒定義 
就定義它,例如 FOO ?= Am I defined?,那麼,它相當於下面的代碼片斷 
代碼:
ifeq ($(origin FOO), undefined)
FOO = Am I defined?
endif
這種寫法可以用於為用戶提供默認的選項,即如果用戶不定義,就提供給他默認的。另外,要說明的是把變量 
定義成NULl,也是定義了變量,此時?=將不再起作用。 
注意: 
最後要說的是,無論我們使用那種風格定義變量,都不要在定義變量的行後面加上隨機數目的空格,之後寫註釋, 
這個我們想像的是不一樣的,例如 
ml = magic_linux
和 
ml = magic_linux # My favourite linux distribution
這是兩個完全不同的變量,其中第一個ml的值是magic_linux,而後一個的值是『magic_linux 』,所以除非 
你有意要定義末尾帶有空格的變量,否則不要在定義變量行末尾隨便添加空格和註釋。 
高級的變量引用方法 
有兩種方法: 1. 替換變量引用 2. 計算變量的值(更確切的說是推導)
第一種 
定義方法: $(var: a=b) ${var: a=b}
意義:把其變量a的值中,每一個詞的最後一個字幕換成b
例如: 
代碼:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
all:;echo $(bar)
這時,bar的值等於a.c b.c c.c
另外,我們還可以用bar := $(foo:%.o=%.c)的形式 
第二種 
這是一種高級的makefile編程技術,一般我們很少使用它,但是它並不難,先看個例子 
代碼:
x = y
y = z
a := $($(x))
此時,a的值是什麼呢?答案是z。因為內層的$(x)=y,而外層的$(y)=z,也就是說a的值是通過$(x)計算出來的,所以 
叫做計算變量的值。當然嵌套可是有很多層,自己試試吧,不多說了。在嵌套變量引用的時候,還可以包含函數調用, 
例如: 
代碼:
x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))
一番推導,a的值等於Hello
整個變量的推導過程中可以涉及到多個變量,這樣,一個變量就可以有多個字面值,例如: 
代碼:
a_dirs := dira dirb
1_dirs := dir1 dir2
a_files := filea fileb
1_files := file1 file2
ifeq "$(use_a)" "yes"
a1 := a
else
a1 := 1
endif
ifeq "$(use_dirs)" "yes"
df := dirs
else
df := files
endif
dirs := $($(a1)_$(df))
根據usa_ause_dirs這兩個兩個變量的值,dirs可以有不同的值 
另外,推導變量值還可以用在替換變量引用中,例如: 
代碼:
a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
sources := $($(a1)_objects:.o=.c)
根據上面a1值的不同,source可以等於a.c b.c c.c1.c 2.c 3.c
這種內嵌變量的限制是你不能把函數調用作為定義變量的一種方式,舉個例子 
代碼:
ifdef do_sort
func := sort
else
func := strip
endif
bar := a d b g q c
foo := $($(func) $(bar))
foo的是什麼呢?它是sort a d b g q cstrip a d b g q c,而並不是我們想像的把a d b g q c作為參數 
傳遞給sortstrip
你還可以把推導出來的變量名作為變量的左值 
代碼:
dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
最後要說明的是,要注意把遞歸擴展變量和嵌套推廣變量區分開。 
變量得到值的辦法 

1) 在你運行make的時候覆蓋變量的值,例如make CFLAGS='-g -O'
2) makefile中用上面說的方式為變量賦值 
3) 設定的環境變量可以在makefile中成為變量 
4) GNU定義的一些自動變量,詳見http://www.gnu.org/software/make/manual/html_chapter/make_10.html#SEC111
5) 一些變量有固定的初始值,詳見http://www.gnu.org/software/make/manual/html_chapter/make_10.html#SEC106
為變量添加值 
你可以通過+=為已定義的變量添加新的值,例如 
代碼:
objects = main.o foo.o bar.o utils.o
objects += another.o
看上去,+=有些和下面的代碼類似 
代碼:
objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o
但是,它們還是存在這一些不同 
當變量從前沒有被定義過, +==是一樣的,它定義一個遞歸展開的變量,但是,當變量已經有定義的時候,+=只是簡單 
的進行字符的添加工作。 
如果起初你用:=定義變量,那麼+=只是利用變量的當前值進行添加,這和我們的直覺是一樣的,例如: 
variable := magic_
variable += linux
此時variable的值就是magic_linux
如果起初用=定義變量,+=的行為就變得有些古怪,它並不會在使用+=的地方馬上進行變量展開,而是會把展開工作推後, 
直到它找到最後變量的定義,這和=定義變量的行為是類似的,但是總覺得不合直覺,例如: 
代碼:
var = I love
variable = linux
var += $(variable)
variable = magic
all:;echo $(var)
當你使用var := I love和上面的情況作對比的時候,這種差別就明顯了。這種定義方式在當變量中引用了其他的變量時是 
很有用的。例如: 
代碼:
CFLAGS = $(includes) -O
...
CFLAGS += -pg # enable profiling
由於CFLAGS是遞歸擴展的,所以make處理CFLAGS時,並不會對其進行擴展,所以只要在使用CFLAGS前,把include定義就好了。 
看似我們可以用CFLAGS := $(CFLAGS) -O來完成上面的任務,但是他們之間仍有差別,這會使CFLAGS變成一個簡單擴展型的變 
量,如果此時include尚未定義,整個CFLAGS就會變成-O -pg,而我們用+=是想把CFLAGS設定成$(include) -O-pg,這顯然和 
我們想像的有差距。 
使用override關鍵字 
一般情況下,如果你是用命令行參數定義一個變量的話,在makefile中的定義就會被忽略,如果你想讓你的定義仍然有效,就要 
使用override關鍵字,向下面這樣: 
override variable = magic 或 override variable := magic
此時,用戶在命令行中指定的值就會被忽略,如果你想在用戶指定的命令行後面添加上自己的文字,用下面的方法 
override variable += magic
這個東西的使用動機就是,你可以為用戶指定一些你默認想讓他們使用的選項,例如: 
override CFLAGS += -g

使用define定義變量 
define可以定義一個代表多行指令的變量,例如 
代碼:
define two-lines
echo foo
echo $(bar)
endef
它相當於 
代碼:
two-lines = echo foo; echo $(bar)
為特定目標的指定變量值 
我們可以在編譯特定的目標文件的時候,為變量設定特殊的值,例如: 
代碼:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
在這個依賴關係中,編譯prog時,就會為編譯參數加上-g選項。當然你也可以使用override關鍵字 
代碼:
prog : override CFLAGS = -g
prog : prog.o foo.o bar.o
十一為特定樣式的文件指定變量值 
我們還可以為生成特定各式的文件指定特殊的編譯參數,例如 
%.o : CFLAGS = -O
這樣,所有生成.o的編譯參數都會被加上-O的選項。

沒有留言: