Linux環境下的程序員如果不會使用GNU make來構建和管理自己的工程,應該不能算是一個合格的專業程序員,至少不能稱得上是Linux程序員。在Linux環境下使用GNU的make工具能夠比較容易的構建一個屬於你自己的工程,整個工程的編譯只需要一個命令就可以完成編譯、連接以至於最後的執行。不過這需要我們投入一些時間去完成一個或者多個稱之爲Makefile文件的編寫。此文件正是make正常工作的基礎。
所要完成的Makefile文件描述了整個工程的編譯、連接等規則。其中包括:工程中的哪些源文件需要編譯以及如何編譯、需要創建那些庫文件以及如何創建這些庫文件、如何最後產生我們想要得可執行文件。儘管看起來可能是很複雜的事情,但是爲工程編寫Makefile的好處是能夠使用一行命令來完成“自動化編譯”,一旦提供一個(多個)正確的Makefile。編譯整個工程你所要做的唯一的一件事就是在shell提示符下輸入make命令。整個工程完全自動編譯,極大提高了效率。
makefile的高級用法
1 變量高級用法
這裏介紹兩種變量的高級使用方法,
1.1 第一種是變量值的替換。
我們可以替換變量中的共有的部分,其格式是“$(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”。
③ 其實上面的用法均是函數patsubst的簡稱使用:
函數原型: $(patsubst PATTERN,REPLACEMENT,TEXT)
用法:$(patsubst %.o,%.c,$foo)
1.2 第二種高級用法是——“把變量的值再當成變量”。
①先看一個例子:
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 := $(strip 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”。
2、追加變量值
我們可以使用“+=”操作符給變量追加值,如:
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會自動爲我們解決這個問題,我們不必擔心這個問題。
二、Makefile的條件執行
條件語句可以根據一個變量的值來控制對Makefile的執行,執行或者忽略Makefile的特定部分。條件語句可以是兩個不同變量、或者變量和常量值得比較。需要注意的是:條件語句只能用於控制make實際執行的makefile文件部分,它不能控制規則的shell命令執行過程。Makefile中使用條件控制可以做到處理的靈活性和高效性。
Ifeq ($a, $b)
Else
endif
3、看一個例子:
libs_for_gcc = -lgnu
normal_libs =
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
foo: $(objects)
$(CC) -o foo $(objects) $(libs)
解析:
在上例中,條件語句中使用到了三個關鍵字:“ifeq”、“else”和“endif”。其中:
1. “ifeq”表示條件語句的開始,並指定了一個比較條件(相等)。之後是用圓括號括包 圍的、使用逗號“,”分割的兩個參數,和關鍵字“ifeq”用空格分開。參數中的變量引用在 進行變 量值比較時被展開。“ifeq”之後就是當條件滿足make需要執行的,條件不滿 足時忽略。
2. “else”之後就是當條件不滿足時的執行部分。不是所有的條件語句都需要此部分。
3. “endif”表示一個條件語句的結束,任何一個條件表達式都必須以“endif”結束。
通過上邊的例子我們可以瞭解到。Makefile中,條件表達式是工作文本級別的,條件的解析是由make程序來完成的。make是在解析Makefile時根據條件表達式忽略條件表達式中的某一個文本行,解析完成後保留的只有表達式滿足條件所需要執行的文本行。對於上例,make處理這個條件時:
② ifdef & ifndef
Ⅰ 關鍵字是“ifdef”用來判斷一個變量是否定義。格式爲:
bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif
Ⅱ 關鍵字“ifndef”實現的功能和“ifdef”相反。格式爲:
bar =
foo = $(bar)
ifndef foo
frobozz = yes
else
frobozz = no
endif
Makefile中,可能需要書寫一些規則來描述一個.o目標文件和頭文件的依賴關係。例如,如果在main.c中使用“#include defs.h”,那麼我們可能需要如下那樣的一個規則來描述當頭文件“defs.h”被修改以後執行make,目標“main.o”應該被重建。
只需執行:gcc -M main.c
輸出:maim.o:main.c defs.h
在新版本的make中,推薦的方式是爲每一個源文件產生一個描述其依賴關係的makefile文件。對於一個源文件“NAME.c”,對應的這個makefile文件爲“NAME.d”。“NAME.d”中描述了文件“NAME.o”所要依賴的所有頭文件。採用這種方式,只有源文件在修改之後纔會重新使用命令生成新的依賴關係描述文件“NAME.o”。
我們可以使用如下的模式規則來自動生成每一個.c文件對應的.d文件:
%.d: %.c
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
此規則的含義是:所有的.d文件依賴於同名的.c文件。
第一行;使用c編譯器自自動生成依賴文件($<)的頭文件的依賴關係,並輸出成爲一個臨時文件,“$$$$”表示當前進程號。如果$(CC)爲GNU的C編譯工具,產生的依賴關係的規則中,依賴頭文件包括了所有的使用的系統頭文件和用戶定義的頭文件。如果需要生成的依賴描述文件不包含系統頭文件,可使用“-MM”代替“-M”。
第二行;使用sed處理第二行已產生的那個臨時文件並生成此規則的目標文件。這裏sed完成了如下的轉換過程。例如對已一個.c源文件。將編譯器產生的依賴關係:
main.o : main.c defs.h
轉成:
main.o main.d : main.c defs.h
這樣就將.d加入到了規則的目標中,其和對應的.o文件文件一樣依賴於對應的.c源文件和源文件所包含的頭文件。當.c源文件或者頭文件被改變之後規則將會被執行,相應的.d文件同樣會被更新。
第三行;刪除臨時文件。
使用上例的規則就可以建立一個描述目標文件依賴關係的.d文件。我們可以在Makefile中使用include指示符將描述將這個文件包含進來。在執行make時,Makefile所包含的所有.d文件就會被自動創建或者更新。Makefile中對當前目錄下.d文件處理可以參考如下:
sources = foo.c bar.c
sinclude $(sources:.c=.d)
瞭解更多請關注:
粵嵌教育:http://www.gec-edu.org
粵嵌新浪微博:http://e.weibo.com/gecedu
粵嵌騰訊微博:http://t.qq.com/gec-edu