1. 程式人生 > >makefile 入門知識備忘

makefile 入門知識備忘

前言

在windows下,編譯、連結工作就是一個按鈕的事情,IDE幫你把大部分工作都做了。這當然非常方便,但是如果你對背後的工作原理不瞭解,就經常會出現一些自己無法解決的、莫名其妙的編譯、連結錯誤。

在linux下,離開了IDE,要編譯一個大型工程,就需要藉助makefile了。makefile定義了一系列的規則來指定,哪些檔案需要先編譯,哪些檔案需要後編譯,哪些檔案需要重新編譯,甚至於進行更復雜的功能操作,makefile就像一個Shell指令碼一樣,其中也可以執行作業系統的命令。

makefile帶來的好處就是——“自動化編譯”,一旦寫好,只需要一個make命令,整個工程完全自動編譯,極大的提高了軟體開發的效率。make是一個命令工具,是一個解釋makefile中指令的命令工具,一般來說,大多數的IDE都有這個命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。

關於編譯連結

編譯、連結這一塊在之前的文章中也提過不少,因此這裡僅簡單再回顧下。

我們常說的編譯,事實上包括了編譯和連結兩大步。

首先,編譯器將原始檔編譯成中間程式碼檔案,Windows下是 .obj 檔案,UNIX下是 .o 檔案,即 Object File,這個動作叫做編譯(compile)。然後連結器再把大量的Object File合成執行檔案,這個動作叫作連結(link)。

編譯時,編譯器需要的是語法的正確,函式與變數的宣告的正確。對於後者,通常是你需要告訴編譯器標頭檔案的所在位置(標頭檔案中應該只是宣告,而定義應該放在C/C++檔案中),只要所有的語法正確,編譯器就可以編譯出中間目標檔案。一般來說,每個原始檔都應該對應於一箇中間目標檔案(O檔案或是OBJ檔案)。

連結時,主要是連結函式和全域性變數,所以,我們可以使用這些中間目標檔案(O檔案或是OBJ檔案)來連結我們的應用程式。連結器並不管函式所在的原始檔,只管函式的中間目標檔案(Object File),在大多數時候,由於原始檔太多,編譯生成的中間目標檔案太多,而在連結時需要明顯地指出中間目標檔名,這對於編譯很不方便,所以,我們要給中間目標檔案打個包,在Windows下這種包叫“庫檔案”(Library File),也就是 .lib 檔案,在UNIX下,是Archive File,也就是 .a 檔案。

總結一下,原始檔首先會生成中間目標檔案,再由中間目標檔案生成執行檔案。在編譯時,編譯器只檢測程式語法,和函式、變數是否被宣告。如果函式未被宣告,編譯器會給出一個警告,但可以生成Object File。而在連結程式時,連結器會在所有的Object File中找尋函式的實現,如果找不到,那到就會報連結錯誤碼(Linker Error)。

Makefile知識備忘

首先說明一下,本文不對基礎知識做詳細地介紹,只作為個人備忘。

詳細入門知識請參考:

1. make命令

我們通常會編寫一個名字叫makefile或者Makefile的檔案,然後在編譯的時候執行命令make。那麼make命令和makefile檔案到底是什麼關係?

在預設的方式下,也就是我們只輸入以下命令:

$ make

那麼,

  1. make命令會在當前目錄下找名字叫“Makefile”或“makefile”的檔案。
  2. 如果找到,它會找檔案中的第一個目標檔案(target),並把這個檔案作為最終的目標檔案,我們假設該目標檔案是edit。
  3. 如果edit檔案不存在,或是edit所依賴的後面的 .o 檔案的檔案修改時間要比edit這個檔案新,那麼,他就會執行後面所定義的命令來生成edit這個檔案。
  4. 如果edit所依賴的.o檔案也不存在,那麼make會在當前檔案中找該.o檔案的依賴性,如果找到則再根據那一個規則生成.o檔案。(這有點像一個堆疊的過程)
  5. 根據你的C檔案和H檔案,make會生成 .o 檔案,然後再用 .o 檔案生成make的終極任務,也就是執行檔案edit了。

第一點的意思是,make是一個命令,在沒有指定makefile檔案的情況下,它會預設找當前目錄下名為[Mm]akefile的檔案作為makefile檔案。如果你想指定makefile檔案,則可通過-f引數指定:

$ make -f makefile.myfile

第二點的意思是,在沒有指定目標的情況下,他會找到檔案中第一個目標作為最終目標。如果你想指定目標,則在命令列中指定,比如想指定目標clean:

$ make clean

後面幾點的意思是,make會一層又一層地去找檔案的依賴關係,直到最終編譯出第一個目標檔案。在找尋的過程中,如果出現錯誤,比如最後被依賴的檔案找不到,那麼make就會直接退出,並報錯,而對於所定義的命令的錯誤,或是編譯不成功,make根本不理。make只管檔案的依賴性,即,如果在我找了依賴關係之後,冒號後面的檔案還是不在,那麼對不起,我就不工作啦。

於是在我們程式設計中,如果這個工程已被編譯過了,當我們修改了其中一個原始檔,比如file.c,那麼根據我們的依賴性,我們的目標file.o會被重編譯(也就是在這個依性關係後面所定義的命令),於是file.o的檔案也是最新的啦,於是file.o的檔案修改時間要比edit要新,所以edit也會被重新連結了。

2. makefile規則示例

    objects = main.o kbd.o command.o display.o \ 
              insert.o search.o files.o utils.o 

    # objects 是一個變數,反斜槓(\)是換行符的意思

    edit : $(objects) 
            cc -o edit $(objects) 

    # 執行檔案edit 是第一個目標(target),因此被當作最終的目標
    # edit 依賴objects變數的值中的.o檔案
    # 下面一行是根據依賴的.o目標檔案來生成最終的edit的命令,必須以TAB開頭

    main.o : main.c defs.h 
            cc -c main.c 
    kbd.o : kbd.c defs.h command.h 
            cc -c kbd.c 
    command.o : command.c defs.h command.h 
            cc -c command.c 
    display.o : display.c defs.h buffer.h 
            cc -c display.c 
    insert.o : insert.c defs.h buffer.h 
            cc -c insert.c 
    search.o : search.c defs.h buffer.h 
            cc -c search.c 
    files.o : files.c defs.h buffer.h command.h 
            cc -c files.c 
    utils.o : utils.c defs.h 
            cc -c utils.c 

    clean : 
            rm edit $(objects) 

    # 以上是非第一個的目標,因此不一定會用到
    # make會從edit開始,根據依賴關係來一層層地執行所需要的目標

假設我們鍵入如下命令:

$ make

則make先找到目錄下的makefile檔案,然後找到第一個目標edit。第一個目標依賴變數object的值即目標main.o kbd.o command.o display.o insert.o search.o files.o utils.o,因此會先執行這些目標,比如main.o,它又依賴main.c defs.h,這兩個檔案是原始檔已經存在了,於是會執行命令cc -c main.c,根據main.c defs.h來生成main.o,當所有.o都生成了,再通過命令cc -o edit $(objects)生成最終目標edit。

依賴關係的實質上就是說明了目標檔案是由哪些檔案生成的,換言之,目標檔案是通過哪些檔案更新的。

在定義好依賴關係後,後續的那一行定義瞭如何生成目標檔案的作業系統命令,一定要以一個Tab鍵作為開頭。make並不管命令是怎麼工作的,他只管執行所定義的命令。make會比較targets檔案和其依賴檔案的修改日期,如果依賴檔案的日期要比targets檔案的日期要新,或者target不存在的話,那麼,make就會執行後續定義的命令。

3. make自動推導

只要make看到一個[.o]檔案,它就會自動的把[.c]檔案加在依賴關係中,如果make找到一個whatever.o,那麼whatever.c,就會是whatever.o的依賴檔案。並且 cc -c whatever.c 也會被推匯出來。

因此以下寫法可以簡化:

    main.o : main.c defs.h 
        cc -c main.c 

–>

    main.o : defs.h 

4. 偽目標

每個Makefile中都應該寫一個清空目標檔案(.o和執行檔案)的規則,這不僅便於重編譯,也很利於保持檔案的清潔。一般的風格都是:

    clean: 
        rm edit $(objects) 

更為穩健的做法是:

    .PHONY : clean 
    clean : 
         -rm edit $(objects) 

.PHONY表示clean是一個“偽目標”。而在rm命令前面加了一個小減號的意思就是,也許某些檔案出現問題,但不要管,繼續做後面的事。當然,clean的規則不要放在檔案的開頭,不然,這就會變成make的預設目標,相信誰也不願意這樣。

5. 引用其他的makefile

類似C語言的#include,Makefile使用include關鍵字把別的Makefile包含進來,被包含的檔案會原樣展開。include的語法是:

include filename; 

filename可以是當前作業系統Shell的檔案模式(可包含路徑和萬用字元)

在include前面可以有一些空字元,但是絕不能是[Tab]鍵開始。include和filename可以用一個或多個空格隔開。舉個例子,你有這樣幾個Makefile:a.mk、b.mk、c.mk,還有一個檔案叫foo.make,以及一個變數$(bar),其包含了e.mk和f.mk,那麼,下面的語句:

include foo.make *.mk $(bar) 

等價於: 

include foo.make a.mk b.mk c.mk e.mk f.mk 

make命令開始時,會先把include的檔案內容展開。如果檔案都沒有指定絕對路徑或是相對路徑的話,make會在當前目錄下首先尋找,如果當前目錄下沒有找到,那麼,make還會在下面的幾個目錄下找:

1、如果make執行時,有“-I”或“–include-dir”引數,那麼make就會在這個引數所指定的目錄下去尋找。
2、如果目錄include(一般是:/usr/local/bin或/usr/include)存在的話,make也會去找。

如果有檔案沒有找到的話,make會生成一條警告資訊,但不會馬上出現致命錯誤。它會繼續載入其它的檔案,一旦完成makefile的讀取,make會再重試這些沒有找到,或是不能讀取的檔案,如果還是不行,make才會出現一條致命資訊。如果你想讓make不理那些無法讀取的檔案,而繼續執行,你可以在include前加一個減號“-”。如:

-include <filename>; 

其表示,無論include過程中出現什麼錯誤,都不要報錯繼續執行。

6. 萬用字元

例子1:

    clean: 
         rm -f *.o 

以上的萬用字元是命令中的萬用字元,由shell所支援。

例子2:

    print: *.c 
         lpr -p $? 
         touch print 

以上的萬用字元在規則中,屬於make所支援的,表示目標print依賴於所有的[.c]檔案。其中的“$?”是一個自動化變數。

需要注意的是,以下的寫法:

    objects = *.o 

上面的寫法中,* 並不作為萬用字元,而是表示objects的值就是字串“*.o”,即變數中的萬用字元不會被展開

**如果想讓objects的值是所有[.o]的檔名的集合,那麼需要這樣寫:

    objects := $(wildcard *.o) 

7. 多目標

Makefile的規則支援多目標,有可能我們的多個目標同時依賴於一個檔案,並且其生成的命令大體類似。於是我們就能把其合併起來,我們可以使用自動化變數“[email protected]”,這個變量表示著目前規則中所有的目標的集合。

    bigoutput littleoutput : text.g 
            generate text.g -$(subst output,,[email protected]) >; [email protected] 
上述規則等價於: 
    bigoutput : text.g 
            generate text.g -big >; bigoutput 
    littleoutput : text.g 
            generate text.g -little >; littleoutput 
其中,-$(subst output,,[email protected])中的“$”表示執行一個Makefile的函式,函式名為subst,後面的為引數。這個函式是擷取字串的意思,“[email protected]”表示目標的集合,就像一個數組,“[email protected]”依次取出目標,並執行命令。 

8. 靜態模式

例子1:

    objects = foo.o bar.o 

    all: $(objects) 

    $(objects): %.o: %.c 
            $(CC) -c $(CFLAGS) $< -o [email protected] 

上面的例子中,指明瞭我們的目標從 $object 中獲取(即目標是foo.o和bar.o),“% .o”表示所有以“.o”結尾的目標,也就是“foo.o bar.o”。

“%.o: %.c ”表示取“%.o”的“%”,也就是“foo bar”,併為其加上“.c”字尾,於是,我們的依賴目標就是“foo.c bar.c”。

而命令中的“$<”和“$@”則是自動化變數,$<”表示所有的依賴目標集(也就是“foo.c bar.c”),“$@”表示目標集(也就是“foo.o bar.o”)。於是,上面的規則展開後等價於下面的規則:

    foo.o : foo.c 
            $(CC) -c $(CFLAGS) foo.c -o foo.o 
    bar.o : bar.c 
            $(CC) -c $(CFLAGS) bar.c -o bar.o 

例子2:

    files = foo.elc bar.o lose.o 

    $(filter %.o,$(files)): %.o: %.c 
            $(CC) -c $(CFLAGS) $< -o [email protected] 
    $(filter %.elc,$(files)): %.elc: %.el 
            emacs -f batch-byte-compile $< 

$(filter %.o,$(files))表示呼叫Makefile的filter函式,過濾“$files”集,只要其中模式為“%.o”的內容作為目標集,然後再通過靜態模式將.o替換為.c字尾作為依賴集。

9. 變數

變數在宣告時需要給予初值,而在使用時,需要給在變數名前加上“$”符號,最好用小括號“()”或是大括號“{}”把變數給包括起來。如果你要使用真實的“$”字元,那麼你需要用“$$”來表示。

變數的值也可以作為變數:

foo = $(bar) 
bar = $(ugh) 
ugh = Huh? 

# “=”左側是變數,右側是變數的值,右側變數的值可以定義在檔案的**任何**一處

all: 
        echo $(foo) 

我們執行“make all”將會打出變數$(foo)的值是“Huh?”( $(foo)的值是$(bar),$(bar)的值是$(ugh),$(ugh)的值是“Huh?”)可見,變數是可以使用後面的變數來定義的。這種情況下,make會將整個makefile展開後,再決定變數的值。也就是說,變數的值將會是整個makefile中最後被指定的值。

為了避免上面的方法出現遞迴定義,我們可以使用make中的另一種用變數來定義變數的方法。這種方法使用的是“:=”操作符,如:

    x := foo 
    y := $(x) bar 
    x := later 

其等價於:

y := foo bar 
x := later 

這種方法,前面的變數不能使用後面的變數,只能使用前面已定義好了的變數。如果是這樣:

    y := $(x) bar 
    x := foo 

那麼,y的值是“bar”,而不是“foo bar”。這種情況下,變數的值決定於它在makefile中的位置,而不是整個makefile展開後的最終值。

注意註釋符“#”的使用,如果我們這樣定義一個變數:

dir := /foo/bar    # directory to put the frobs in 

dir這個變數的值是“/foo/bar”,後面還跟了4個空格,如果我們使用這樣的變數來指定別的目錄——“$(dir)/file”那麼就完蛋了,空格會算在dir裡面。

還有一個比較有用的操作符是“?=”:

FOO ?= bar 

其含義是,如果FOO沒有被定義過,那麼變數FOO的值就是“bar”,如果FOO先前被定義過,那麼這條語將什麼也不做,其等價於:

    ifeq ($(origin FOO), undefined) 
      FOO = bar 
    endif 

再看一個例子:

VAR = MY_VAR="$(MY_VAR)"
MY_VAR = TRUE

上面的最終結果是,變數MY_VAR的值為字串“TRUE”,變數VAR的值為字串“MY_VAR=”TRUE””。

10. 巢狀執行make

當我們的工程比較大時,為清晰起見,我們會把不同模組放在不同目錄中,然後為每個目錄書寫一個makefile,讓我們的Makefile變得更加地簡潔,而不至於把所有的東西全部寫在一個Makefile中,這樣會很難維護我們的Makefile,這個技術對於我們模組編譯和分段編譯有著非常大的好處。

例如,我們有一個子目錄叫subdir,這個目錄下有個Makefile檔案,來指明瞭這個目錄下檔案的編譯規則。那麼我們總控的Makefile可以這樣書寫:

    subsystem: 
            cd subdir && $(MAKE) 

其等價於:

    subsystem: 
            $(MAKE) -C subdir 

定義$(MAKE)巨集變數的意思是,也許我們的make需要一些引數,所以定義成一個變數比較利於維護。這兩個例子的意思都是先進入“subdir”目錄,然後執行make命令。

我們把這個Makefile叫做“總控Makefile”,總控Makefile的變數可以傳遞到下級的Makefile中(如果你顯示的宣告),但是不會覆蓋下層的Makefile中所定義的變數,除非指定了“-e”引數。

如果你要傳遞變數到下級Makefile中,那麼你可以使用這樣的宣告:

export <variable ...>; 

如果你不想讓某些變數傳遞到下級Makefile中,那麼你可以這樣宣告:

unexport <variable ...>; 

如:

    示例一: 

        export variable = value 

        其等價於: 

        variable = value 
        export variable 

        其等價於: 

        export variable := value 

        其等價於: 

        variable := value 
        export variable 

    示例二: 

        export variable += value 

        其等價於: 

        variable += value 
        export variable 

如果你要傳遞所有的變數,那麼,只要一個export就行了。後面什麼也不用跟,表示傳遞所有的變數。

需要注意的是,有兩個變數,一個是SHELL,一個是MAKEFLAGS,這兩個變數不管你是否export,其總是要傳遞到下層Makefile中,特別是MAKEFILES變數,其中包含了make的引數資訊,如果我們執行“總控Makefile”時有make引數或是在上層Makefile中定義了這個變數,那麼MAKEFILES變數將會是這些引數,並會傳遞到下層Makefile中,這是一個系統級的環境變數。

但是make命令中的有幾個引數並不往下傳遞,它們是“-C”,“-f”,“-h”“-o”和“-W”(有關Makefile引數的細節將在後面說明),如果你不想往下層傳遞引數,那麼,你可以這樣來:

subsystem: 
        cd subdir && $(MAKE) MAKEFLAGS= 

如果你定義了環境變數MAKEFLAGS,那麼你得確信其中的選項是大家都會用到的,如果其中有“-t”,“-n”,和“-q”引數,那麼將會有讓你意想不到的結果,或許會讓你異常地恐慌。

還有一個在“巢狀執行”中比較有用的引數,“-w”或是“–print-directory”會在make的過程中輸出一些資訊,讓你看到目前的工作目錄。比如,如果我們的下級make目錄是“/home/hchen/gnu/make”,如果我們使用“make -w”來執行,那麼當進入該目錄時,我們會看到:

    make: Entering directory `/home/hchen/gnu/make'. 

而在完成下層make後離開目錄時,我們會看到:

    make: Leaving directory `/home/hchen/gnu/make' 

當你使用“-C”引數來指定make下層Makefile時,“-w”會被自動開啟的。如果引數中有“-s”(“–slient”)或是“–no-print-directory”,那麼,“-w”總是失效的。

另外一種傳遞引數的方式,就是在make命令之後進行賦值:

    $(MAKE) PARAM=$(param)

不過有一些小區別,第二種方式傳遞的引數,是一個常量而不是變數,也就是說,你不能修改PARAM的值。

例子:

#父makefile
MAKE=make
VAR=Hello
export VAR

mytest:
    -$(MAKE) -C ./makeTest VAR2=HI

#子makefile,位於目錄./makeTest下
VAR += HI 
VAR2 += HELLO

target:
    echo $(VAR);echo $(VAR2);

執行結果:

make -C ./makeTest VAR2=HI
make[1]: Entering directory '/home/jiange/makeTest'
echo Hello HI ;echo HI;
Hello HI
HI
make[1]: Leaving directory '/home/jiange/makeTest'

另外,如果變數中包含有空格字元,那麼需要額外小心,具體情況遇到自己分析下,這裡我就不具體展開了。

11. 條件判斷

使用條件判斷,可以讓make根據執行時的不同情況選擇不同的執行分支。條件表示式可以是比較變數的值,或是比較變數和常量的值。

下面的例子,判斷$(CC)變數是否“gcc”,如果是的話,則使用GNU函式編譯目標。

    libs_for_gcc = -lgnu 
    normal_libs = 

    foo: $(objects) 
    ifeq ($(CC),gcc) 
            $(CC) -o foo $(objects) $(libs_for_gcc) 
    else 
            $(CC) -o foo $(objects) $(normal_libs) 
    endif 

可見,在上面示例的這個規則中,目標“foo”可以根據變數“$(CC)”值來選取不同的函式庫來編譯程式。

我們可以從上面的示例中看到三個關鍵字:ifeq、else和endif。
ifeq的意思表示條件語句的開始,並指定一個條件表示式,表示式包含兩個引數,以逗號分隔,表示式以圓括號括起。
else表示條件表示式為假的情況。
endif表示一個條件語句的結束,任何一個條件表示式都應該以endif結束。

當然,我們還可以把上面的那個例子寫得更簡潔一些:

    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) 

條件表示式的語法為:

    <conditional-directive>; 
    <text-if-true>; 
    endif 

以及:

    <conditional-directive>; 
    <text-if-true>; 
    else 
    <text-if-false>; 
    endif 

其中;表示條件關鍵字,如“ifeq”。這個關鍵字有四個。

第一個是我們前面所見過的“ifeq”

    ifeq (<arg1>;, <arg2>;)  
    ifeq '<arg1>;' '<arg2>;'  
    ifeq "<arg1>;" "<arg2>;"  
    ifeq "<arg1>;" '<arg2>;'  
    ifeq '<arg1>;' "<arg2>;"  

比較引數“arg1”和“arg2”的值是否相同。當然,引數中我們還可以使用make的函式。如:

    ifeq ($(strip $(foo)),) 
    <text-if-empty>; 
    endif 

這個示例中使用了“strip”函式,如果這個函式的返回值是空(Empty),那麼;就生效。

第二個條件關鍵字是“ifneq”。語法是:

    ifneq (<arg1>;, <arg2>;)  
    ifneq '<arg1>;' '<arg2>;'  
    ifneq "<arg1>;" "<arg2>;"  
    ifneq "<arg1>;" '<arg2>;'  
    ifneq '<arg1>;' "<arg2>;"  

其比較引數“arg1”和“arg2”的值是否相同,如果不同,則為真。和“ifeq”類似。

第三個條件關鍵字是“ifdef”。語法是:

    ifdef <variable-name>;  

如果變數;的值非空,那到表示式為真。否則,表示式為假。當然,;同樣可以是一個函式的返回值。注意,ifdef只是測試一個變數是否有值,其並不會把變數擴充套件到當前位置。

    示例一: 
    bar = 
    foo = $(bar) 
    ifdef foo 
    frobozz = yes 
    else 
    frobozz = no 
    endif 

    示例二:
    #變數宣告一定要賦值,沒有賦值相當於未宣告 
    foo = 
    ifdef foo 
    frobozz = yes 
    else 
    frobozz = no 
    endif 

第一個例子中,“$(frobozz)”值是“yes”,第二個則是“no”。

第四個條件關鍵字是“ifndef”。其語法是:

ifndef <variable-name>; 

這個我就不多說了,和“ifdef”是相反的意思。

在;這一行上,多餘的空格是被允許的,但是不能以[Tab]鍵做為開始(不然就被認為是命令)。而註釋符“#”同樣也是安全的。“else”和“endif”也一樣,只要不是以[Tab]鍵開始就行了。

特別注意的是,make是在讀取Makefile時就計算條件表示式的值,並根據條件表示式的值來選擇語句,所以,你最好不要把自動化變數(如“[email protected]”等)放入條件表示式中,因為自動化變數是在執行時才有的。

而且,為了避免混亂,make不允許把整個條件語句分成兩部分放在不同的檔案中。

12.函式

在Makefile中可以使用函式來處理變數,函式呼叫後,函式的返回值可以當做變數來使用。

函式呼叫,很像變數的使用,也是以“$”來標識的,其語法如下:

$(<function> <arguments>) 

或是

${<function> <arguments>} 

這裡,< function >就是函式名,make支援的函式不多。< arguments >是函式的引數,引數間以逗號“,”分隔,而函式名和引數之間以“空格”分隔。函式呼叫以“ $ ”開頭,以圓括號或花括號把函式名和引數括起。函式中的引數可以使用變數,為了風格的統一,函式和變數的括號最好一樣。

示例:

    comma:= , 
    empty:= 
    space:= $(empty) $(empty) 
    foo:= a b c 
    bar:= $(subst $(space),$(comma),$(foo)) 

在這個示例中,$(comma)的值是一個逗號。$(space)使用了$(empty)定義了一個空格,$(foo)的值是“a b c”,$(bar)的定義呼叫了函式“subst”,這是一個替換函式,這個函式有三個引數,第一個引數是被替換字串,第二個引數是替換字串,第三個引數是替換操作作用的字串。這個函式也就是把(foo)(bar)的值是“a,b,c”。

字串處理函式

$(subst <from>;,<to>;,<text>;)  

    名稱:字串替換函式——subst。 
    功能:把字串<text>;中的<from>;字串替換成<to>;。 
    返回:函式返回被替換過後的字串。 

    示例: 

        $(subst ee,EE,feet on the street), 

        把“feet on the street”中的“ee”替換成“EE”,返回結果是“fEEt on the strEEt”。 
$(patsubst <pattern>;,<replacement>;,<text>;)  

    名稱:模式字串替換函式——patsubst。 
    功能:查詢<text>;中的單詞(單詞以“空格”、“Tab”或“回車”“換行”分隔)是否符合模式<pattern>;,如果匹配的話,則以<replacement>;替換。這裡,<pattern>;可以包括萬用字元“%”,表示任意長度的字串。如果<replacement>;中也包含“%”,那麼,<replacement>;中的這個“%”將是<pattern>;中的那個“%”所代表的字串。(可以用“\”來轉義,以“\%”來表示真實含義的“%”字元) 
    返回:函式返回被替換過後的字串。 

    示例: 

        $(patsubst %.c,%.o,x.c.c bar.c) 

        把字串“x.c.c bar.c”符合模式[%.c]的單詞替換成[%.o],返回結果是“x.c.o bar.o” 
$(strip <string>;) 

    名稱:去空格函式——strip。 
    功能:去掉<string>;字串中開頭和結尾的空字元。 
    返回:返回被去掉空格的字串值。 
    示例: 

        $(strip a b c ) 

        把字串“a b c ”去到開頭和結尾的空格,結果是“a b c”。 

$(findstring <find>;,<in>;) 

    名稱:查詢字串函式——findstring。 
    功能:在字串<in>;中查詢<find>;字串。 
    返回:如果找到,那麼返回<find>;,否則返回空字串。 
    示例: 

        $(findstring a,a b c) 
        $(findstring a,b c) 

        第一個函式返回“a”字串,第二個返回“”字串(空字串) 
$(filter <pattern...>;,<text>;) 

    名稱:過濾函式——filter。 
    功能:以<pattern>;模式過濾<text>;字串中的單詞,保留符合模式<pattern>;的單詞。可以有多個模式。 
    返回:返回符合模式<pattern>;的字串。 
    示例: 

        sources := foo.c bar.c baz.s ugh.h 
        foo: $(sources) 
                cc $(filter %.c %.s,$(sources)) -o foo 

        $(filter %.c %.s,$(sources))返回的值是“foo.c bar.c baz.s”。 
$(filter-out <pattern...>;,<text>;) 

    名稱:反過濾函式——filter-out。 
    功能:以<pattern>;模式過濾<text>;字串中的單詞,去除符合模式<pattern>;的單詞。可以有多個模式。 
    返回:返回不符合模式<pattern>;的字串。 
    示例: 

        objects=main1.o foo.o main2.o bar.o 
        mains=main1.o main2.o 

        $(filter-out $(mains),$(objects)) 返回值是“foo.o bar.o”。 
$(sort <list>;) 

    名稱:排序函式——sort。 
    功能:給字串<list>;中的單詞排序(升序)。 
    返回:返回排序後的字串。 
    示例:$(sort foo bar lose)返回“bar foo lose” 。 
    備註:sort函式會去掉<list>;中相同的單詞。 
$(word <n>;,<text>;) 

    名稱:取單詞函式——word。 
    功能:取字串<text>;中第<n>;個單詞。(從一開始) 
    返回:返回字串<text>;中第<n>;個單詞。如果<n>;比<text>;中的單詞數要大,那麼返回空字串。 
    示例:$(word 2, foo bar baz)返回值是“bar”。 
$(wordlist <s>;,<e>;,<text>;)   

    名稱:取單詞串函式——wordlist。 
    功能:從字串<text>;中取從<s>;開始到<e>;的單詞串。<s>;和<e>;是一個數字。 
    返回:返回字串<text>;中從<s>;到<e>;的單詞字串。如果<s>;比<text>;中的單詞數要大,那麼返回空字串。如果<e>;大於<text>;的單詞數,那麼返回從<s>;開始,到<text>;結束的單詞串。 
    示例: $(wordlist 2, 3, foo bar baz)返回值是“bar baz”。 
$(words <text>;) 

    名稱:單詞個數統計函式——words。 
    功能:統計<text>;中字串中的單詞個數。 
    返回:返回<text>;中的單詞數。 
    示例:$(words, foo bar baz)返回值是“3”。 
    備註:如果我們要取<text>;中最後的一個單詞,我們可以這樣:$(word $(words <text>;),<text>;)。 
$(firstword <text>;) 

    名稱:首單詞函式——firstword。 
    功能:取字串<text>;中的第一個單詞。 
    返回:返回字串<text>;的第一個單詞。 
    示例:$(firstword foo bar)返回值是“foo”。 
    備註:這個函式可以用word函式來實現:$(word 1,<text>;)。 

以上,是所有的字串操作函式,如果搭配混合使用,可以完成比較複雜的功能。

舉個例子:我們知道,make使用“VPATH”變數來指定“依賴檔案”的搜尋路徑。於是,我們可以利用這個搜尋路徑來指定編譯器對標頭檔案的搜尋路徑引數CFLAGS,如:

    override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH))) 

如果我們的“(VPATH)src:../headers(patsubst %,-I%,(subst:,,(VPATH)))”將返回“-Isrc -I../headers”,這正是cc或gcc搜尋標頭檔案路徑的引數。

檔名操作函式

下面我們要介紹的函式主要是處理檔名的。每個函式的引數字串都會被當做一個或是一系列的檔名來對待。

$(dir <names...>;)  

    名稱:取目錄函式——dir。 
    功能:從檔名序列<names>;中取出目錄部分。目錄部分是指最後一個反斜槓(“/”)之前的部分。如果沒有反斜槓,那麼返回“./”。 
    返回:返回檔名序列<names>;的目錄部分。 
    示例: $(dir src/foo.c hacks)返回值是“src/ ./”。 

$(notdir <names...>;)  

    名稱:取檔案函式——notdir。 
    功能:從檔名序列<names>;中取出非目錄部分。非目錄部分是指最後一個反斜槓(“/”)之後的部分。 
    返回:返回檔名序列<names>;的非目錄部分。 
    示例: $(notdir src/foo.c hacks)返回值是“foo.c hacks”。 

$(suffix <names...>;)  

    名稱:取字尾函式——suffix。 
    功能:從檔名序列<names>;中取出各個檔名的字尾。 
    返回:返回檔名序列<names>;的字尾序列,如果檔案沒有後綴,則返回空字串。 
    示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是“.c .c”。 

$(basename <names...>;) 

    名稱:取字首函式——basename。 
    功能:從檔名序列<names>;中取出各個檔名的字首部分。 
    返回:返回檔名序列<names>;的字首序列,如果檔案沒有字首,則返回空字串。 
    示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/bar hacks”。 

$(addsuffix <suffix>;,<names...>;)  

    名稱:加字尾函式——addsuffix。 
    功能:把字尾<suffix>;加到<names>;中的每個單詞後面。 
    返回:返回加過後綴的檔名序列。 
    示例:$(addsuffix .c,foo bar)返回值是“foo.c bar.c”。 

$(addprefix <prefix>;,<names...>;)  

    名稱:加字首函式——addprefix。 
    功能:把字首<prefix>;加到<names>;中的每個單詞後面。 
    返回:返回加過字首的檔名序列。 
    示例:$(addprefix src/,foo bar)返回值是“src/foo src/bar”。 

$(join <list1>;,<list2>;) 

    名稱:連線函式——join。 
    功能:把<list2>;中的單詞對應地加到<list1>;的單詞後面。如果<list1>;的單詞個數要比<list2>;的多,那麼,<list1>;中的多出來的單詞將保持原樣。如果<list2>;的單詞個數要比<list1>;多,那麼,<list2>;多出來的單詞將被複制到<list2>;中。 
    返回:返回連線過後的字串。 
    示例:$(join aaa bbb , 111 222 333)返回值是“aaa111 bbb222 333”。

origin函式

origin函式不像其它的函式,他並不操作變數的值,他只是告訴你你的這個變數是哪裡來的?其語法是:

$(origin <variable>;) 

注意,;是變數的名字,不應該是引用。所以你最好不要在;中使用“$”字元。Origin函式會以其返回值來告訴你這個變數的“出生情況”,下面,是origin函式的返回值:

“undefined”:如果;從來沒有定義過,origin函式返回這個值“undefined”。

“default”:如果;是一個預設的定義,比如“CC”這個變數。

“environment”: 如果;是一個環境變數,並且當Makefile被執行時,“-e”引數沒有被開啟。

“file”:如果;這個變數被定義在Makefile中。

“command line”: 如果;這個變數是被命令列定義的。

“override”:如果;是被override指示符重新定義的。

“automatic”:如果;是一個命令執行中的自動化變數。

這些資訊對於我們編寫Makefile是非常有用的,例如,假設我們有一個Makefile其包了一個定義檔案Make.def,在Make.def中定義了一個變數“bletch”,而我們的環境中也有一個環境變數“bletch”,此時,我們想判斷一下,如果變數來源於環境,那麼我們就把之重定義了,如果來源於Make.def或是命令列等非環境的,那麼我們就不重新定義它。於是,在我們的Makefile中,我們可以這樣寫:

    ifdef bletch 

    ifeq "$(