(轉)跟我一塊兒寫MAKEFILE

概述 
—— 

什麼是makefile?或許不少Winodws的程序員都不知道這個東西,由於那些Windows的IDE都爲你作了這個工做,但我以爲要做一個好的和professional的程序員,makefile仍是要懂。這就好像如今有這麼多的HTML的編輯器,但若是你想成爲一個專業人士,你仍是要了解HTML的標識的含義。特別在Unix下的軟件編譯,你就不能不本身寫makefile了,會不會寫makefile,從一個側面說明了一我的是否具有完成大型工程的能力。 

由於,makefile關係到了整個工程的編譯規則。一個工程中的源文件不計數,其按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的規則來指定,哪些文件須要先編譯,哪些文件須要後編譯,哪些文件須要從新編譯,甚至於進行更復雜的功能操做,由於makefile就像一個Shell腳本同樣,其中也能夠執行操做系統的命令。 

makefile帶來的好處就是——「自動化編譯」,一旦寫好,只須要一個make命令,整個工程徹底自動編譯,極大的提升了軟件開發的效率。make是一個命令工具,是一個解釋makefile中指令的命令工具,通常來講,大多數的IDE都有這個命令,好比:Delphi的make,Visual C++的nmake,Linux下GNU的make。可見,makefile都成爲了一種在工程方面的編譯方法。 

如今講述如何寫makefile的文章比較少,這是我想寫這篇文章的緣由。固然,不一樣產商的make各不相同,也有不一樣的語法,但其本質都是在「文件依賴性」上作文章,這裏,我僅對GNU的make進行講述,個人環境是RedHat Linux 8.0,make的版本是3.80。必竟,這個make是應用最爲普遍的,也是用得最多的。並且其仍是最遵循於IEEE 1003.2-1992 標準的(POSIX.2)。 

在這篇文檔中,將以C/C++的源碼做爲咱們基礎,因此必然涉及一些關於C/C++的編譯的知識,相關於這方面的內容,還請各位查看相關的編譯器的文檔。這裏所默認的編譯器是UNIX下的GCC和CC。 

  

關於程序的編譯和連接 
—————————— 

在此,我想多說關於程序編譯的一些規範和方法,通常來講,不管是C、C++、仍是pas,首先要把源文件編譯成中間代碼文件,在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),在VC下,這種錯誤通常是:Link 2001錯誤,意思說是說,連接器未能找到函數的實現。你須要指定函數的Object File. 

好,言歸正傳,GNU的make有許多的內容,閒言少敘,仍是讓咱們開始吧。 

  

Makefile 介紹 
——————— 

make命令執行時,須要一個 Makefile 文件,以告訴make命令須要怎麼樣的去編譯和連接程序。 

首先,咱們用一個示例來講明Makefile的書寫規則。以便給你們一個感興認識。這個示例來源於GNU的make使用手冊,在這個示例中,咱們的工程有8個C文件,和3個頭文件,咱們要寫一個Makefile來告訴make命令如何編譯和連接這幾個文件。咱們的規則是: 
    1)若是這個工程沒有編譯過,那麼咱們的全部C文件都要編譯並被連接。 
    2)若是這個工程的某幾個C文件被修改,那麼咱們只編譯被修改的C文件,並連接目標程序。 
    3)若是這個工程的頭文件被改變了,那麼咱們須要編譯引用了這幾個頭文件的C文件,並連接目標程序。 

只要咱們的Makefile寫得夠好,全部的這一切,咱們只用一個make命令就能夠完成,make命令會自動智能地根據當前的文件修改的狀況來肯定哪些文件須要重編譯,從而本身編譯所須要的文件和連接目標程序。 


1、Makefile的規則 

在講述這個Makefile以前,仍是讓咱們先來粗略地看一看Makefile的規則。 

    target ... : prerequisites ... 
            command 
            ... 
            ... 

    target也就是一個目標文件,能夠是Object File,也能夠是執行文件。還能夠是一個標籤(Label),對於標籤這種特性,在後續的「僞目標」章節中會有敘述。 

    prerequisites就是,要生成那個target所須要的文件或是目標。 

    command也就是make須要執行的命令。(任意的Shell命令) 

這是一個文件的依賴關係,也就是說,target這一個或多個的目標文件依賴於prerequisites中的文件,其生成規則定義在command中。說白一點就是說,prerequisites中若是有一個以上的文件比target文件要新的話,command所定義的命令就會被執行。這就是Makefile的規則。也就是Makefile中最核心的內容。 

說到底,Makefile的東西就是這樣一點,好像個人這篇文檔也該結束了。呵呵。還不盡然,這是Makefile的主線和核心,但要寫好一個Makefile還不夠,我會之後面一點一點地結合個人工做經驗給你慢慢到來。內容還多着呢。:) 


2、一個示例 

正如前面所說的,若是一個工程有3個頭文件,和8個C文件,咱們爲了完成前面所述的那三個規則,咱們的Makefile應該是下面的這個樣子的。 

    edit : main.o kbd.o command.o display.o \ 
           insert.o search.o files.o utils.o 
            cc -o edit 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 
    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 main.o kbd.o command.o display.o \ 
               insert.o search.o files.o utils.o 

反斜槓(\)是換行符的意思。這樣比較便於Makefile的易讀。咱們能夠把這個內容保存在文件爲「Makefile」或「makefile」的文件中,而後在該目錄下直接輸入命令「make」就能夠生成執行文件edit。若是要刪除執行文件和全部的中間目標文件,那麼,只要簡單地執行一下「make clean」就能夠了。 

在這個makefile中,目標文件(target)包含:執行文件edit和中間目標文件(*.o),依賴文件(prerequisites)就是冒號後面的那些 .c 文件和 .h文件。每個 .o 文件都有一組依賴文件,而這些 .o 文件又是執行文件 edit 的依賴文件。依賴關係的實質上就是說明了目標文件是由哪些文件生成的,換言之,目標文件是哪些文件更新的。 

在定義好依賴關係後,後續的那一行定義瞭如何生成目標文件的操做系統命令,必定要以一個Tab鍵做爲開頭。記住,make並無論命令是怎麼工做的,他只管執行所定義的命令。make會比較targets文件和prerequisites文件的修改日期,若是prerequisites文件的日期要比targets文件的日期要新,或者target不存在的話,那麼,make就會執行後續定義的命令。 

這裏要說明一點的是,clean不是一個文件,它只不過是一個動做名字,有點像C語言中的lable同樣,其冒號後什麼也沒有,那麼,make就不會自動去找文件的依賴性,也就不會自動執行其後所定義的命令。要執行其後的命令,就要在make命令後明顯得指出這個lable的名字。這樣的方法很是有用,咱們能夠在一個makefile中定義不用的編譯或是和編譯無關的命令,好比程序的打包,程序的備份,等等。linux

3、make是如何工做的 

在默認的方式下,也就是咱們只輸入make命令。那麼, 

    一、make會在當前目錄下找名字叫「Makefile」或「makefile」的文件。 
    二、若是找到,它會找文件中的第一個目標文件(target),在上面的例子中,他會找到「edit」這個文件,並把這個文件做爲最終的目標文件。 
    三、若是edit文件不存在,或是edit所依賴的後面的 .o 文件的文件修改時間要比edit這個文件新,那麼,他就會執行後面所定義的命令來生成edit這個文件。 
    四、若是edit所依賴的.o文件也存在,那麼make會在當前文件中找目標爲.o文件的依賴性,若是找到則再根據那一個規則生成.o文件。(這有點像一個堆棧的過程) 
    五、固然,你的C文件和H文件是存在的啦,因而make會生成 .o 文件,而後再用 .o 文件生命make的終極任務,也就是執行文件edit了。 

這就是整個make的依賴性,make會一層又一層地去找文件的依賴關係,直到最終編譯出第一個目標文件。在找尋的過程當中,若是出現錯誤,好比最後被依賴的文件找不到,那麼make就會直接退出,並報錯,而對於所定義的命令的錯誤,或是編譯不成功,make根本不理。make只管文件的依賴性,即,若是在我找了依賴關係以後,冒號後面的文件仍是不在,那麼對不起,我就不工做啦。 

經過上述分析,咱們知道,像clean這種,沒有被第一個目標文件直接或間接關聯,那麼它後面所定義的命令將不會被自動執行,不過,咱們能夠顯示要make執行。即命令——「make clean」,以此來清除全部的目標文件,以便重編譯。 

因而在咱們編程中,若是這個工程已被編譯過了,當咱們修改了其中一個源文件,好比file.c,那麼根據咱們的依賴性,咱們的目標file.o會被重編譯(也就是在這個依性關係後面所定義的命令),因而file.o的文件也是最新的啦,因而file.o的文件修改時間要比edit要新,因此edit也會被從新連接了(詳見edit目標文件後定義的命令)。 

而若是咱們改變了「command.h」,那麼,kdb.o、command.o和files.o都會被重編譯,而且,edit會被重連接。 


4、makefile中使用變量 

在上面的例子中,先讓咱們看看edit的規則: 

      edit : main.o kbd.o command.o display.o \ 
                  insert.o search.o files.o utils.o 
            cc -o edit main.o kbd.o command.o display.o \ 
                       insert.o search.o files.o utils.o 

咱們能夠看到[.o]文件的字符串被重複了兩次,若是咱們的工程須要加入一個新的[.o]文件,那麼咱們須要在兩個地方加(應該是三個地方,還有一個地方在clean中)。固然,咱們的makefile並不複雜,因此在兩個地方加也不累,但若是makefile變得複雜,那麼咱們就有可能會忘掉一個須要加入的地方,而致使編譯失敗。因此,爲了makefile的易維護,在makefile中咱們能夠使用變量。makefile的變量也就是一個字符串,理解成C語言中的宏可能會更好。 

好比,咱們聲明一個變量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正無論什麼啦,只要可以表示obj文件就好了。咱們在makefile一開始就這樣定義: 

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

因而,咱們就能夠很方便地在咱們的makefile中以「$(objects)」的方式來使用這個變量了,因而咱們的改良版makefile就變成下面這個樣子: 

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

    edit : $(objects) 
            cc -o edit $(objects) 
    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) 


因而若是有新的 .o 文件加入,咱們只需簡單地修改一下 objects 變量就能夠了。 

關於變量更多的話題,我會在後續給你一一道來。 


5、讓make自動推導 

GNU的make很強大,它能夠自動推導文件以及文件依賴關係後面的命令,因而咱們就不必去在每個[.o]文件後都寫上相似的命令,由於,咱們的make會自動識別,並本身推導命令。 

只要make看到一個[.o]文件,它就會自動的把[.c]文件加在依賴關係中,若是make找到一個whatever.o,那麼whatever.c,就會是whatever.o的依賴文件。而且 cc -c whatever.c 也會被推導出來,因而,咱們的makefile不再用寫得這麼複雜。咱們的是新的makefile又出爐了。 


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

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

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

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

這種方法,也就是make的「隱晦規則」。上面文件內容中,「.PHONY」表示,clean是個僞目標文件。 

關於更爲詳細的「隱晦規則」和「僞目標文件」,我會在後續給你一一道來。 


6、另類風格的makefile 

即然咱們的make能夠自動推導命令,那麼我看到那堆[.o]和[.h]的依賴就有點不爽,那麼多的重複的[.h],能不能把其收攏起來,好吧,沒有問題,這個對於make來講很容易,誰叫它提供了自動推導命令和文件的功能呢?來看看最新風格的makefile吧。 

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

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

    $(objects) : defs.h 
    kbd.o command.o files.o : command.h 
    display.o insert.o search.o files.o : buffer.h 

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

這種風格,讓咱們的makefile變得很簡單,但咱們的文件依賴關係就顯得有點凌亂了。魚和熊掌不可兼得。還看你的喜愛了。我是不喜歡這種風格的,一是文件的依賴關係看不清楚,二是若是文件一多,要加入幾個新的.o文件,那就理不清楚了。 


7、清空目標文件的規則 

每一個Makefile中都應該寫一個清空目標文件(.o和執行文件)的規則,這不只便於重編譯,也很利於保持文件的清潔。這是一個「修養」(呵呵,還記得個人《編程修養》嗎)。通常的風格都是: 

        clean: 
            rm edit $(objects) 

更爲穩健的作法是: 

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

前面說過,.PHONY意思表示clean是一個「僞目標」,。而在rm命令前面加了一個小減號的意思就是,也許某些文件出現問題,但不要管,繼續作後面的事。固然,clean的規則不要放在文件的開頭,否則,這就會變成make的默認目標,相信誰也不肯意這樣。不成文的規矩是——「clean歷來都是放在文件的最後」。 


上面就是一個makefile的概貌,也是makefile的基礎,下面還有不少makefile的相關細節,準備好了嗎?準備好了就來。 

<-上程序員


 
1、Makefile裏有什麼? 

Makefile裏主要包含了五個東西:顯式規則、隱晦規則、變量定義、文件指示和註釋。 

一、顯式規則。顯式規則說明了,如何生成一個或多的的目標文件。這是由Makefile的書寫者明顯指出,要生成的文件,文件的依賴文件,生成的命令。 

二、隱晦規則。因爲咱們的make有自動推導的功能,因此隱晦的規則可讓咱們比較粗糙地簡略地書寫Makefile,這是由make所支持的。 

三、變量的定義。在Makefile中咱們要定義一系列的變量,變量通常都是字符串,這個有點你C語言中的宏,當Makefile被執行時,其中的變量都會被擴展到相應的引用位置上。 

四、文件指示。其包括了三個部分,一個是在一個Makefile中引用另外一個Makefile,就像C語言中的include同樣;另外一個是指根據某些狀況指定Makefile中的有效部分,就像C語言中的預編譯#if同樣;還有就是定義一個多行的命令。有關這一部分的內容,我會在後續的部分中講述。 

五、註釋。Makefile中只有行註釋,和UNIX的Shell腳本同樣,其註釋是用「#」字符,這個就像C/C++中的「//」同樣。若是你要在你的Makefile中使用「#」字符,能夠用反斜框進行轉義,如:「\#」。 

最後,還值得一提的是,在Makefile中的命令,必需要以[Tab]鍵開始。 


2、Makefile的文件名 

默認的狀況下,make命令會在當前目錄下按順序找尋文件名爲「GNUmakefile」、「makefile」、「Makefile」的文件,找到了解釋這個文件。在這三個文件名中,最好使用「Makefile」這個文件名,由於,這個文件名第一個字符爲大寫,這樣有一種顯目的感受。最好不要用「GNUmakefile」,這個文件是GNU的make識別的。有另一些make只對全小寫的「makefile」文件名敏感,可是基本上來講,大多數的make都支持「makefile」和「Makefile」這兩種默認文件名。 

固然,你能夠使用別的文件名來書寫Makefile,好比:「Make.Linux」,「Make.Solaris」,「Make.AIX」等,若是要指定特定的Makefile,你能夠使用make的「-f」和「--file」參數,如:make -f Make.Linux或make --file Make.AIX。 


3、引用其它的Makefile 

在Makefile使用include關鍵字能夠把別的Makefile包含進來,這很像C語言的#include,被包含的文件會原模原樣的放在當前文件的包含位置。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所指出的其它Makefile,並把其內容安置在當前的位置。就好像C/C++的#include指令同樣。若是文件都沒有指定絕對路徑或是相對路徑的話,make會在當前目錄下首先尋找,若是當前目錄下沒有找到,那麼,make還會在下面的幾個目錄下找: 

    一、若是make執行時,有「-I」或「--include-dir」參數,那麼make就會在這個參數所指定的目錄下去尋找。 
    二、若是目錄<prefix>;/include(通常是:/usr/local/bin或/usr/include)存在的話,make也會去找。 

若是有文件沒有找到的話,make會生成一條警告信息,但不會立刻出現致命錯誤。它會繼續載入其它的文件,一旦完成makefile的讀取,make會再重試這些沒有找到,或是不能讀取的文件,若是仍是不行,make纔會出現一條致命信息。若是你想讓make不理那些沒法讀取的文件,而繼續執行,你能夠在include前加一個減號「-」。如: 

    -include <filename>; 
    其表示,不管include過程當中出現什麼錯誤,都不要報錯繼續執行。和其它版本make兼容的相關命令是sinclude,其做用和這一個是同樣的。 


4、環境變量 MAKEFILES  

若是你的當前環境中定義了環境變量MAKEFILES,那麼,make會把這個變量中的值作一個相似於include的動做。這個變量中的值是其它的Makefile,用空格分隔。只是,它和include不一樣的是,從這個環境變中引入的Makefile的「目標」不會起做用,若是環境變量中定義的文件發現錯誤,make也會不理。 

可是在這裏我仍是建議不要使用這個環境變量,由於只要這個變量一被定義,那麼當你使用make時,全部的Makefile都會受到它的影響,這毫不是你想看到的。在這裏提這個事,只是爲了告訴你們,也許有時候你的Makefile出現了怪事,那麼你能夠看看當前環境中有沒有定義這個變量。 


5、make的工做方式 

GNU的make工做時的執行步驟入下:(想來其它的make也是相似) 

    一、讀入全部的Makefile。 
    二、讀入被include的其它Makefile。 
    三、初始化文件中的變量。 
    四、推導隱晦規則,並分析全部規則。 
    五、爲全部的目標文件建立依賴關係鏈。 
    六、根據依賴關係,決定哪些目標要從新生成。 
    七、執行生成命令。 

1-5步爲第一個階段,6-7爲第二個階段。第一個階段中,若是定義的變量被使用了,那麼,make會把其展開在使用的位置。但make並不會徹底立刻展開,make使用的是拖延戰術,若是變量出如今依賴關係的規則中,那麼僅當這條依賴被決定要使用了,變量纔會在其內部展開。 

固然,這個工做方式你不必定要清楚,可是知道這個方式你也會對make更爲熟悉。有了這個基礎,後續部分也就容易看懂了。web


 
書寫規則 
———— 

規則包含兩個部分,一個是依賴關係,一個是生成目標的方法。 

在Makefile中,規則的順序是很重要的,由於,Makefile中只應該有一個最終目標,其它的目標都是被這個目標所連帶出來的,因此必定要讓make知道你的最終目標是什麼。通常來講,定義在Makefile中的目標可能會有不少,可是第一條規則中的目標將被確立爲最終的目標。若是第一條規則中的目標有不少個,那麼,第一個目標會成爲最終的目標。make所完成的也就是這個目標。 

好了,仍是讓咱們來看一看如何書寫規則。 


1、規則舉例 

    foo.o : foo.c defs.h       # foo模塊 
            cc -c -g foo.c 

看到這個例子,各位應該不是很陌生了,前面也已說過,foo.o是咱們的目標,foo.c和defs.h是目標所依賴的源文件,而只有一個命令「cc -c -g foo.c」(以Tab鍵開頭)。這個規則告訴咱們兩件事: 

    一、文件的依賴關係,foo.o依賴於foo.c和defs.h的文件,若是foo.c和defs.h的文件日期要比foo.o文件日期要新,或是foo.o不存在,那麼依賴關係發生。 
    二、若是生成(或更新)foo.o文件。也就是那個cc命令,其說明了,如何生成foo.o這個文件。(固然foo.c文件include了defs.h文件) 


2、規則的語法 

      targets : prerequisites 
        command 
        ... 

      或是這樣:  

      targets : prerequisites ; command 
            command 
            ... 

targets是文件名,以空格分開,能夠使用通配符。通常來講,咱們的目標基本上是一個文件,但也有多是多個文件。 

command是命令行,若是其不與「target:prerequisites」在一行,那麼,必須以[Tab鍵]開頭,若是和prerequisites在一行,那麼能夠用分號作爲分隔。(見上) 

prerequisites也就是目標所依賴的文件(或依賴目標)。若是其中的某個文件要比目標文件要新,那麼,目標就被認爲是「過期的」,被認爲是須要重生成的。這個在前面已經講過了。 

若是命令太長,你能夠使用反斜框(‘\’)做爲換行符。make對一行上有多少個字符沒有限制。規則告訴make兩件事,文件的依賴關係和如何成成目標文件。 

通常來講,make會以UNIX的標準Shell,也就是/bin/sh來執行命令。 


3、在規則中使用通配符 

若是咱們想定義一系列比較相似的文件,咱們很天然地就想起使用通配符。make支持三各通配符:「*」,「?」和「[...]」。這是和Unix的B-Shell是相同的。 

波浪號(「~」)字符在文件名中也有比較特殊的用途。若是是「~/test」,這就表示當前用戶的$HOME目錄下的test目錄。而「~hchen/test」則表示用戶hchen的宿主目錄下的test目錄。(這些都是Unix下的小知識了,make也支持)而在Windows或是MS-DOS下,用戶沒有宿主目錄,那麼波浪號所指的目錄則根據環境變量「HOME」而定。 

通配符代替了你一系列的文件,如「*.c」表示因此後綴爲c的文件。一個須要咱們注意的是,若是咱們的文件名中有通配符,如:「*」,那麼能夠用轉義字符「\」,如「\*」來表示真實的「*」字符,而不是任意長度的字符串。 

好吧,仍是先來看幾個例子吧: 

    clean: 
         rm -f *.o 

    上面這個例子我不很少說了,這是操做系統Shell所支持的通配符。這是在命令中的通配符。 

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

    上面這個例子說明了通配符也能夠在咱們的規則中,目標print依賴於全部的[.c]文件。其中的「$?」是一個自動化變量,我會在後面給你講述。 

    objects = *.o 

    上面這個例子,表示了,通符一樣能夠用在變量中。並非說[*.o]會展開,不!objects的值就是「*.o」。Makefile中的變量其實就是C/C++中的宏。若是你要讓通配符在變量中展開,也就是讓objects的值是全部[.o]的文件名的集合,那麼,你能夠這樣: 

    objects := $(wildcard *.o) 

這種用法由關鍵字「wildcard」指出,關於Makefile的關鍵字,咱們將在後面討論。 


4、文件搜尋 

在一些大的工程中,有大量的源文件,咱們一般的作法是把這許多的源文件分類,並存放在不一樣的目錄中。因此,當make須要去找尋文件的依賴關係時,你能夠在文件前加上路徑,但最好的方法是把一個路徑告訴make,讓make在自動去找。 

Makefile文件中的特殊變量「VPATH」就是完成這個功能的,若是沒有指明這個變量,make只會在當前的目錄中去找尋依賴文件和目標文件。若是定義了這個變量,那麼,make就會在噹噹前目錄找不到的狀況下,到所指定的目錄中去找尋文件了。 

    VPATH = src:../headers 

上面的的定義指定兩個目錄,「src」和「../headers」,make會按照這個順序進行搜索。目錄由「冒號」分隔。(固然,當前目錄永遠是最高優先搜索的地方) 

另外一個設置文件搜索路徑的方法是使用make的「vpath」關鍵字(注意,它是全小寫的),這不是變量,這是一個make的關鍵字,這和上面提到的那個VPATH變量很相似,可是它更爲靈活。它能夠指定不一樣的文件在不一樣的搜索目錄中。這是一個很靈活的功能。它的使用方法有三種: 

    一、vpath <pattern>; <directories>; 

    爲符合模式<pattern>;的文件指定搜索目錄<directories>;。 

    二、vpath <pattern>; 

    清除符合模式<pattern>;的文件的搜索目錄。 

    三、vpath 

    清除全部已被設置好了的文件搜索目錄。 

vapth使用方法中的<pattern>;須要包含「%」字符。「%」的意思是匹配零或若干字符,例如,「%.h」表示全部以「.h」結尾的文件。<pattern>;指定了要搜索的文件集,而<directories>;則指定了<pattern>;的文件集的搜索的目錄。例如: 

    vpath %.h ../headers 

該語句表示,要求make在「../headers」目錄下搜索全部以「.h」結尾的文件。(若是某文件在當前目錄沒有找到的話) 

咱們能夠連續地使用vpath語句,以指定不一樣搜索策略。若是連續的vpath語句中出現了相同的<pattern>;,或是被重複了的<pattern>;,那麼,make會按照vpath語句的前後順序來執行搜索。如: 

    vpath %.c foo 
    vpath %   blish 
    vpath %.c bar 

其表示「.c」結尾的文件,先在「foo」目錄,而後是「blish」,最後是「bar」目錄。 

    vpath %.c foo:bar 
    vpath %   blish 

而上面的語句則表示「.c」結尾的文件,先在「foo」目錄,而後是「bar」目錄,最後纔是「blish」目錄。 


5、僞目標 

最先先的一個例子中,咱們提到過一個「clean」的目標,這是一個「僞目標」, 

    clean: 
            rm *.o temp 

正像咱們前面例子中的「clean」同樣,即然咱們生成了許多文件編譯文件,咱們也應該提供一個清除它們的「目標」以備完整地重編譯而用。 (以「make clean」來使用該目標) 

由於,咱們並不生成「clean」這個文件。「僞目標」並非一個文件,只是一個標籤,因爲「僞目標」不是文件,因此make沒法生成它的依賴關係和決定它是否要執行。咱們只有經過顯示地指明這個「目標」才能讓其生效。固然,「僞目標」的取名不能和文件名重名,否則其就失去了「僞目標」的意義了。 

固然,爲了不和文件重名的這種狀況,咱們能夠使用一個特殊的標記「.PHONY」來顯示地指明一個目標是「僞目標」,向make說明,無論是否有這個文件,這個目標就是「僞目標」。 

    .PHONY : clean 

只要有這個聲明,無論是否有「clean」文件,要運行「clean」這個目標,只有「make clean」這樣。因而整個過程能夠這樣寫: 

     .PHONY: clean 
    clean: 
            rm *.o temp 

僞目標通常沒有依賴的文件。可是,咱們也能夠爲僞目標指定所依賴的文件。僞目標一樣能夠做爲「默認目標」,只要將其放在第一個。一個示例就是,若是你的Makefile須要一口氣生成若干個可執行文件,但你只想簡單地敲一個make完事,而且,全部的目標文件都寫在一個Makefile中,那麼你能夠使用「僞目標」這個特性: 

    all : prog1 prog2 prog3 
    .PHONY : all 

    prog1 : prog1.o utils.o 
            cc -o prog1 prog1.o utils.o 

    prog2 : prog2.o 
            cc -o prog2 prog2.o 

    prog3 : prog3.o sort.o utils.o 
            cc -o prog3 prog3.o sort.o utils.o 

咱們知道,Makefile中的第一個目標會被做爲其默認目標。咱們聲明瞭一個「all」的僞目標,其依賴於其它三個目標。因爲僞目標的特性是,老是被執行的,因此其依賴的那三個目標就老是不如「all」這個目標新。因此,其它三個目標的規則老是會被決議。也就達到了咱們一口氣生成多個目標的目的。「.PHONY : all」聲明瞭「all」這個目標爲「僞目標」。 

隨便提一句,從上面的例子咱們能夠看出,目標也能夠成爲依賴。因此,僞目標一樣也可成爲依賴。看下面的例子: 

    .PHONY: cleanall cleanobj cleandiff 

    cleanall : cleanobj cleandiff 
            rm program 

    cleanobj : 
            rm *.o 

    cleandiff : 
            rm *.diff 

「make clean」將清除全部要被清除的文件。「cleanobj」和「cleandiff」這兩個僞目標有點像「子程序」的意思。咱們能夠輸入「make cleanall」和「make cleanobj」和「make cleandiff」命令來達到清除不一樣種類文件的目的。算法


 
6、多目標 

Makefile的規則中的目標能夠不止一個,其支持多目標,有可能咱們的多個目標同時依賴於一個文件,而且其生成的命令大致相似。因而咱們就能把其合併起來。固然,多個目標的生成規則的執行命令是同一個,這可能會可咱們帶來麻煩,不過好在咱們的能夠使用一個自動化變量「$@」(關於自動化變量,將在後面講述),這個變量表示着目前規則中全部的目標的集合,這樣說可能很抽象,仍是看一個例子吧。 

    bigoutput littleoutput : text.g 
            generate text.g -$(subst output,,$@) >; $@ 

    上述規則等價於: 

    bigoutput : text.g 
            generate text.g -big >; bigoutput 
    littleoutput : text.g 
            generate text.g -little >; littleoutput 

    其中,-$(subst output,,$@)中的「$」表示執行一個Makefile的函數,函數名爲subst,後面的爲參數。關於函數,將在後面講述。這裏的這個函數是截取字符串的意思,「$@」表示目標的集合,就像一個數組,「$@」依次取出目標,並執於命令。 


7、靜態模式 

靜態模式能夠更加容易地定義多目標的規則,可讓咱們的規則變得更加的有彈性和靈活。咱們仍是先來看一下語法: 

    <targets ...>;: <target-pattern>;: <prereq-patterns ...>; 
            <commands>; 
            ... 


    targets定義了一系列的目標文件,能夠有通配符。是目標的一個集合。 

    target-parrtern是指明瞭targets的模式,也就是的目標集模式。 

    prereq-parrterns是目標的依賴模式,它對target-parrtern造成的模式再進行一次依賴目標的定義。 

這樣描述這三個東西,可能仍是沒有說清楚,仍是舉個例子來講明一下吧。若是咱們的<target-parrtern>;定義成「%.o」,意思是咱們的<target>;集合中都是以「.o」結尾的,而若是咱們的<prereq-parrterns>;定義成「%.c」,意思是對<target-parrtern>;所造成的目標集進行二次定義,其計算方法是,取<target-parrtern>;模式中的「%」(也就是去掉了[.o]這個結尾),併爲其加上[.c]這個結尾,造成的新集合。 

因此,咱們的「目標模式」或是「依賴模式」中都應該有「%」這個字符,若是你的文件名中有「%」那麼你能夠使用反斜槓「\」進行轉義,來標明真實的「%」字符。 

看一個例子: 

    objects = foo.o bar.o 

    all: $(objects) 

    $(objects): %.o: %.c 
            $(CC) -c $(CFLAGS) $< -o $@ 


上面的例子中,指明瞭咱們的目標從$object中獲取,「%.o」代表要全部以「.o」結尾的目標,也就是「foo.o bar.o」,也就是變量$object集合的模式,而依賴模式「%.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 

試想,若是咱們的「%.o」有幾百個,那種咱們只要用這種很簡單的「靜態模式規則」就能夠寫完一堆規則,實在是太有效率了。「靜態模式規則」的用法很靈活,若是用得好,那會一個很強大的功能。再看一個例子: 


    files = foo.elc bar.o lose.o 

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


$(filter %.o,$(files))表示調用Makefile的filter函數,過濾「$filter」集,只要其中模式爲「%.o」的內容。其的它內容,我就不用多說了吧。這個例字展現了Makefile中更大的彈性。 


8、自動生成依賴性 

在Makefile中,咱們的依賴關係可能會須要包含一系列的頭文件,好比,若是咱們的main.c中有一句「#include "defs.h"」,那麼咱們的依賴關係應該是: 

    main.o : main.c defs.h 

可是,若是是一個比較大型的工程,你必需清楚哪些C文件包含了哪些頭文件,而且,你在加入或刪除頭文件時,也須要當心地修改Makefile,這是一個很沒有維護性的工做。爲了不這種繁重而又容易出錯的事情,咱們能夠使用C/C++編譯的一個功能。大多數的C/C++編譯器都支持一個「-M」的選項,即自動找尋源文件中包含的頭文件,並生成一個依賴關係。例如,若是咱們執行下面的命令: 

    cc -M main.c 

其輸出是: 

    main.o : main.c defs.h 

因而由編譯器自動生成的依賴關係,這樣一來,你就沒必要再手動書寫若干文件的依賴關係,而由編譯器自動生成了。須要提醒一句的是,若是你使用GNU的C/C++編譯器,你得用「-MM」參數,否則,「-M」參數會把一些標準庫的頭文件也包含進來。 

    gcc -M main.c的輸出是: 

    main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \ 
         /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \ 
         /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \ 
         /usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \ 
         /usr/include/bits/sched.h /usr/include/libio.h \ 
         /usr/include/_G_config.h /usr/include/wchar.h \ 
         /usr/include/bits/wchar.h /usr/include/gconv.h \ 
         /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \ 
         /usr/include/bits/stdio_lim.h 


    gcc -MM main.c的輸出則是: 

    main.o: main.c defs.h 

那麼,編譯器的這個功能如何與咱們的Makefile聯繫在一塊兒呢。由於這樣一來,咱們的Makefile也要根據這些源文件從新生成,讓Makefile自已依賴於源文件?這個功能並不現實,不過咱們能夠有其它手段來迂迴地實現這一功能。GNU組織建議把編譯器爲每個源文件的自動生成的依賴關係放到一個文件中,爲每個「name.c」的文件都生成一個「name.d」的Makefile文件,[.d]文件中就存放對應[.c]文件的依賴關係。 

因而,咱們能夠寫出[.c]文件和[.d]文件的依賴關係,並讓make自動更新或自成[.d]文件,並把其包含在咱們的主Makefile中,這樣,咱們就能夠自動化地生成每一個文件的依賴關係了。 

這裏,咱們給出了一個模式規則來產生[.d]文件: 

    %.d: %.c 
            @set -e; rm -f $@; \ 
             $(CC) -M $(CPPFLAGS) $< >; $@.$$$$; \ 
             sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ >; $@; \ 
             rm -f $@.$$$$ 


這個規則的意思是,全部的[.d]文件依賴於[.c]文件,「rm -f $@」的意思是刪除全部的目標,也就是[.d]文件,第二行的意思是,爲每一個依賴文件「$<」,也就是[.c]文件生成依賴文件,「$@」表示模式「%.d」文件,若是有一個C文件是name.c,那麼「%」就是「name」,「$$$$」意爲一個隨機編號,第二行生成的文件有多是「name.d.12345」,第三行使用sed命令作了一個替換,關於sed命令的用法請參看相關的使用文檔。第四行就是刪除臨時文件。 

總而言之,這個模式要作的事就是在編譯器生成的依賴關係中加入[.d]文件的依賴,即把依賴關係: 

    main.o : main.c defs.h 

轉成: 

    main.o main.d : main.c defs.h 

因而,咱們的[.d]文件也會自動更新了,並會自動生成了,固然,你還能夠在這個[.d]文件中加入的不僅是依賴關係,包括生成的命令也可一併加入,讓每一個[.d]文件都包含一個完賴的規則。一旦咱們完成這個工做,接下來,咱們就要把這些自動生成的規則放進咱們的主Makefile中。咱們能夠使用Makefile的「include」命令,來引入別的Makefile文件(前面講過),例如: 

    sources = foo.c bar.c 

    include $(sources:.c=.d) 

上述語句中的「$(sources:.c=.d)」中的「.c=.d」的意思是作一個替換,把變量$(sources)全部[.c]的字串都替換成[.d],關於這個「替換」的內容,在後面我會有更爲詳細的講述。固然,你得注意次序,由於include是按次來載入文件,最早載入的[.d]文件中的目標會成爲默認目標。shell


 
書寫命令 
———— 

每條規則中的命令和操做系統Shell的命令行是一致的。make會一按順序一條一條的執行命令,每條命令的開頭必須以[Tab]鍵開頭,除非,命令是緊跟在依賴規則後面的分號後的。在命令行之間中的空格或是空行會被忽略,可是若是該空格或空行是以Tab鍵開頭的,那麼make會認爲其是一個空命令。 

咱們在UNIX下可能會使用不一樣的Shell,可是make的命令默認是被「/bin/sh」——UNIX的標準Shell解釋執行的。除非你特別指定一個其它的Shell。Makefile中,「#」是註釋符,很像C/C++中的「//」,其後的本行字符都被註釋。 

1、顯示命令 

一般,make會把其要執行的命令行在命令執行前輸出到屏幕上。當咱們用「@」字符在命令行前,那麼,這個命令將不被make顯示出來,最具表明性的例子是,咱們用這個功能來像屏幕顯示一些信息。如: 

    @echo 正在編譯XXX模塊...... 

當make執行時,會輸出「正在編譯XXX模塊......」字串,但不會輸出命令,若是沒有「@」,那麼,make將輸出: 

    echo 正在編譯XXX模塊...... 
    正在編譯XXX模塊...... 

若是make執行時,帶入make參數「-n」或「--just-print」,那麼其只是顯示命令,但不會執行命令,這個功能頗有利於咱們調試咱們的Makefile,看看咱們書寫的命令是執行起來是什麼樣子的或是什麼順序的。 

而make參數「-s」或「--slient」則是全面禁止命令的顯示。 

  

2、命令執行 

當依賴目標新於目標時,也就是當規則的目標須要被更新時,make會一條一條的執行其後的命令。須要注意的是,若是你要讓上一條命令的結果應用在下一條命令時,你應該使用分號分隔這兩條命令。好比你的第一條命令是cd命令,你但願第二條命令得在cd以後的基礎上運行,那麼你就不能把這兩條命令寫在兩行上,而應該把這兩條命令寫在一行上,用分號分隔。如: 

    示例一: 
        exec: 
                cd /home/hchen 
                pwd 

    示例二: 
        exec: 
                cd /home/hchen; pwd 

當咱們執行「make exec」時,第一個例子中的cd沒有做用,pwd會打印出當前的Makefile目錄,而第二個例子中,cd就起做用了,pwd會打印出「/home/hchen」。 

make通常是使用環境變量SHELL中所定義的系統Shell來執行命令,默認狀況下使用UNIX的標準Shell——/bin/sh來執行命令。但在MS-DOS下有點特殊,由於MS-DOS下沒有SHELL環境變量,固然你也能夠指定。若是你指定了UNIX風格的目錄形式,首先,make會在SHELL所指定的路徑中找尋命令解釋器,若是找不到,其會在當前盤符中的當前目錄中尋找,若是再找不到,其會在PATH環境變量中所定義的全部路徑中尋找。MS-DOS中,若是你定義的命令解釋器沒有找到,其會給你的命令解釋器加上諸如「.exe」、「.com」、「.bat」、「.sh」等後綴。 



3、命令出錯 

每當命令運行完後,make會檢測每一個命令的返回碼,若是命令返回成功,那麼make會執行下一條命令,當規則中全部的命令成功返回後,這個規則就算是成功完成了。若是一個規則中的某個命令出錯了(命令退出碼非零),那麼make就會終止執行當前規則,這將有可能終止全部規則的執行。 

有些時候,命令的出錯並不表示就是錯誤的。例如mkdir命令,咱們必定須要創建一個目錄,若是目錄不存在,那麼mkdir就成功執行,萬事大吉,若是目錄存在,那麼就出錯了。咱們之因此使用mkdir的意思就是必定要有這樣的一個目錄,因而咱們就不但願mkdir出錯而終止規則的運行。 

爲了作到這一點,忽略命令的出錯,咱們能夠在Makefile的命令行前加一個減號「-」(在Tab鍵以後),標記爲無論命令出不出錯都認爲是成功的。如: 

   clean: 
            -rm -f *.o 

還有一個全局的辦法是,給make加上「-i」或是「--ignore-errors」參數,那麼,Makefile中全部命令都會忽略錯誤。而若是一個規則是以「.IGNORE」做爲目標的,那麼這個規則中的全部命令將會忽略錯誤。這些是不一樣級別的防止命令出錯的方法,你能夠根據你的不一樣喜歡設置。 

還有一個要提一下的make的參數的是「-k」或是「--keep-going」,這個參數的意思是,若是某規則中的命令出錯了,那麼就終目該規則的執行,但繼續執行其它規則。 



4、嵌套執行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」老是失效的。 



5、定義命令包 

若是Makefile中出現一些相同命令序列,那麼咱們能夠爲這些相同的命令序列定義一個變量。定義這種命令序列的語法以「define」開始,以「endef」結束,如: 

    define run-yacc 
    yacc $(firstword $^) 
    mv y.tab.c $@ 
    endef 

這裏,「run-yacc」是這個命令包的名字,其不要和Makefile中的變量重名。在「define」和「endef」中的兩行就是命令序列。這個命令包中的第一個命令是運行Yacc程序,由於Yacc程序老是生成「y.tab.c」的文件,因此第二行的命令就是把這個文件改更名字。仍是把這個命令包放到一個示例中來看看吧。 

    foo.c : foo.y 
            $(run-yacc) 

咱們能夠看見,要使用這個命令包,咱們就好像使用變量同樣。在這個命令包的使用中,命令包「run-yacc」中的「$^」就是「foo.y」,「$@」就是「foo.c」(有關這種以「$」開頭的特殊變量,咱們會在後面介紹),make在執行命令包時,命令包中的每一個命令會被依次獨立執行。數據庫


 
使用變量 
———— 

在Makefile中的定義的變量,就像是C/C++語言中的宏同樣,他表明了一個文本字串,在Makefile中執行的時候其會自動原模原樣地展開在所使用的地方。其與C/C++所不一樣的是,你能夠在Makefile中改變其值。在Makefile中,變量能夠使用在「目標」,「依賴目標」,「命令」或是Makefile的其它部分中。 

變量的命名字能夠包含字符、數字,下劃線(能夠是數字開頭),但不該該含有「:」、「#」、「=」或是空字符(空格、回車等)。變量是大小寫敏感的,「foo」、「Foo」和「FOO」是三個不一樣的變量名。傳統的Makefile的變量名是全大寫的命名方式,但我推薦使用大小寫搭配的變量名,如:MakeFlags。這樣能夠避免和系統的變量衝突,而發生意外的事情。 

有一些變量是很奇怪字串,如「$<」、「$@」等,這些是自動化變量,我會在後面介紹。 

1、變量的基礎 

變量在聲明時須要給予初值,而在使用時,須要給在變量名前加上「$」符號,但最好用小括號「()」或是大括號「{}」把變量給包括起來。若是你要使用真實的「$」字符,那麼你須要用「$$」來表示。 

變量能夠使用在許多地方,如規則中的「目標」、「依賴」、「命令」以及新的變量中。先看一個例子: 

    objects = program.o foo.o utils.o 
    program : $(objects) 
            cc -o program $(objects) 

    $(objects) : defs.h 

變量會在使用它的地方精確地展開,就像C/C++中的宏同樣,例如: 

    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 
    endefexpress


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時,擁有的是同一套系統變量,這可能會帶來更多的麻煩。 


8、目標變量 

前面咱們所講的在Makefile中定義的變量都是「全局變量」,在整個文件,咱們均可以訪問這些變量。固然,「自動化變量」除外,如「$<」等這種類量的自動化變量就屬於「規則型變量」,這種變量的值依賴於規則的目標和依賴目標的定義。 

固然,我樣一樣能夠爲某個目標設置局部變量,這種變量被稱爲「Target-specific Variable」,它能夠和「全局變量」同名,由於它的做用範圍只在這條規則以及連帶規則中,因此其值也只在做用範圍內有效。而不會影響規則鏈之外的全局變量的值。 

其語法是: 

    <target ...>; : <variable-assignment>; 

    <target ...>; : overide <variable-assignment>; 

<variable-assignment>;能夠是前面講過的各類賦值表達式,如「=」、「:=」、「+=」或是「?=」。第二個語法是針對於make命令行帶入的變量,或是系統環境變量。 

這個特性很是的有用,當咱們設置了這樣一個變量,這個變量會做用到由這個目標所引起的全部的規則中去。如: 

    prog : CFLAGS = -g 
    prog : prog.o foo.o bar.o 
            $(CC) $(CFLAGS) prog.o foo.o bar.o 

    prog.o : prog.c 
            $(CC) $(CFLAGS) prog.c 

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

    bar.o : bar.c 
            $(CC) $(CFLAGS) bar.c 
  
在這個示例中,無論全局的$(CFLAGS)的值是什麼,在prog目標,以及其所引起的全部規則中(prog.o foo.o bar.o的規則),$(CFLAGS)的值都是「-g」 


9、模式變量 

在GNU的make中,還支持模式變量(Pattern-specific Variable),經過上面的目標變量中,咱們知道,變量能夠定義在某個目標上。模式變量的好處就是,咱們能夠給定一種「模式」,能夠把變量定義在符合這種模式的全部目標上。 

咱們知道,make的「模式」通常是至少含有一個「%」的,因此,咱們能夠以以下方式給全部以[.o]結尾的目標定義目標變量: 

    %.o : CFLAGS = -O 

一樣,模式變量的語法和「目標變量」同樣: 

    <pattern ...>; : <variable-assignment>; 

    <pattern ...>; : override <variable-assignment>; 

override一樣是針對於系統環境傳入的變量,或是make命令行指定的變量。 
  


使用條件判斷 
—————— 

使用條件判斷,可讓make根據運行時的不一樣狀況選擇不一樣的執行分支。條件表達式能夠是比較變量的值,或是比較變量和常量的值。 

1、示例 

下面的例子,判斷$(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結束。 

當咱們的變量$(CC)值是「gcc」時,目標foo的規則是: 

    foo: $(objects) 
            $(CC) -o foo $(objects) $(libs_for_gcc) 

而當咱們的變量$(CC)值不是「gcc」時(好比「cc」),目標foo的規則是: 

    foo: $(objects) 
            $(CC) -o foo $(objects) $(normal_libs) 

固然,咱們還能夠把上面的那個例子寫得更簡潔一些: 

    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) 


2、語法 

條件表達式的語法爲: 

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

以及: 

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

其中<conditional-directive>;表示條件關鍵字,如「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),那麼<text-if-empty>;就生效。 

第二個條件關鍵字是「ifneq」。語法是: 

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

其比較參數「arg1」和「arg2」的值是否相同,若是不一樣,則爲真。和「ifeq」相似。 

第三個條件關鍵字是「ifdef」。語法是: 

    ifdef <variable-name>;  

若是變量<variable-name>;的值非空,那到表達式爲真。不然,表達式爲假。固然,<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」是相反的意思。 

在<conditional-directive>;這一行上,多餘的空格是被容許的,可是不能以[Tab]鍵作爲開始(否則就被認爲是命令)。而註釋符「#」一樣也是安全的。「else」和「endif」也同樣,只要不是以[Tab]鍵開始就好了。 

特別注意的是,make是在讀取Makefile時就計算條件表達式的值,並根據條件表達式的值來選擇語句,因此,你最好不要把自動化變量(如「$@」等)放入條件表達式中,由於自動化變量是在運行時纔有的。 

並且,爲了不混亂,make不容許把整個條件語句分紅兩部分放在不一樣的文件中。編程


 使用函數 
———— 

在Makefile中能夠使用函數來處理變量,從而讓咱們的命令或是規則更爲的靈活和具備智能。make所支持的函數也不算不少,不過已經足夠咱們的操做了。函數調用後,函數的返回值能夠當作變量來使用。 


1、函數的調用語法 

函數調用,很像變量的使用,也是以「$」來標識的,其語法以下: 

    $(<function>; <arguments>;) 

或是 

    ${<function>; <arguments>;} 

這裏,<function>;就是函數名,make支持的函數很少。<arguments>;是函數的參數,參數間以逗號「,」分隔,而函數名和參數之間以「空格」分隔。函數調用以「$」開頭,以圓括號或花括號把函數名和參數括起。感受很像一個變量,是否是?函數中的參數能夠使用變量,爲了風格的統一,函數和變量的括號最好同樣,如使用「$(subst a,b,$(x))」這樣的形式,而不是「$(subst a,b,${x})」的形式。由於統一會更清楚,也會減小一些沒必要要的麻煩。 

仍是來看一個示例: 

    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」。 


2、字符串處理函數 

$(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」 

    備註: 

        這和咱們前面「變量章節」說過的相關知識有點類似。如: 

        「$(var:<pattern>;=<replacement>;)」 
         至關於 
        「$(patsubst <pattern>;,<replacement>;,$(var))」, 

         而「$(var: <suffix>;=<replacement>;)」 
         則至關於 
         「$(patsubst %<suffix>;,%<replacement>;,$(var))」。 

         例若有:objects = foo.o bar.o baz.o, 
         那麼,「$(objects:.o=.c)」和「$(patsubst %.o,%.c,$(objects))」是同樣的。 

$(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搜索頭文件路徑的參數。 


3、文件名操做函數 

下面咱們要介紹的函數主要是處理文件名的。每一個函數的參數字符串都會被當作一個或是一系列的文件名來對待。 

$(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」。數組



4、foreach 函數 
  

foreach函數和別的函數很是的不同。由於這個函數是用來作循環用的,Makefile中的foreach函數幾乎是仿照於Unix標準Shell(/bin/sh)中的for語句,或是C-Shell(/bin/csh)中的foreach語句而構建的。它的語法是: 

  

    $(foreach <var>;,<list>;,<text>;) 

  

這個函數的意思是,把參數<list>;中的單詞逐一取出放到參數<var>;所指定的變量中,而後再執行<text>;所包含的表達式。每一次<text>;會返回一個字符串,循環過程當中,<text>;的所返回的每一個字符串會以空格分隔,最後當整個循環結束時,<text>;所返回的每一個字符串所組成的整個字符串(以空格分隔)將會是foreach函數的返回值。 

  

因此,<var>;最好是一個變量名,<list>;能夠是一個表達式,而<text>;中通常會使用<var>;這個參數來依次枚舉<list>;中的單詞。舉個例子: 

  

    names := a b c d 

    files := $(foreach n,$(names),$(n).o) 

  

上面的例子中,$(name)中的單詞會被挨個取出,並存到變量「n」中,「$(n).o」每次根據「$(n)」計算出一個值,這些值以空格分隔,最後做爲foreach函數的返回,因此,$(files)的值是「a.o b.o c.o d.o」。 

  

注意,foreach中的<var>;參數是一個臨時的局部變量,foreach函數執行完後,參數<var>;的變量將不在做用,其做用域只在foreach函數當中。 

  

  

5、if 函數 
  

if函數很像GNU的make所支持的條件語句——ifeq(參見前面所述的章節),if函數的語法是: 

  

    $(if <condition>;,<then-part>;)  

  

或是 

  

    $(if <condition>;,<then-part>;,<else-part>;) 

  

可見,if函數能夠包含「else」部分,或是不含。即if函數的參數能夠是兩個,也能夠是三個。<condition>;參數是if的表達式,若是其返回的爲非空字符串,那麼這個表達式就至關於返回真,因而,<then-part>;會被計算,不然<else-part>;會被計算。 

  

而if函數的返回值是,若是<condition>;爲真(非空字符串),那個<then-part>;會是整個函數的返回值,若是<condition>;爲假(空字符串),那麼<else-part>;會是整個函數的返回值,此時若是<else-part>;沒有被定義,那麼,整個函數返回空字串。 

  

因此,<then-part>;和<else-part>;只會有一個被計算。 

  

  

6、call函數 
  

call函數是惟一一個能夠用來建立新的參數化的函數。你能夠寫一個很是複雜的表達式,這個表達式中,你能夠定義許多參數,而後你能夠用call函數來向這個表達式傳遞參數。其語法是: 

  

    $(call <expression>;,<parm1>;,<parm2>;,<parm3>;...) 

  

當make執行這個函數時,<expression>;參數中的變量,如$(1),$(2),$(3)等,會被參數<parm1>;,<parm2>;,<parm3>;依次取代。而<expression>;的返回值就是call函數的返回值。例如: 

    reverse =  $(1) $(2) 

    foo = $(call reverse,a,b) 



那麼,foo的值就是「a b」。固然,參數的次序是能夠自定義的,不必定是順序的,如: 

  

    reverse =  $(2) $(1) 

    foo = $(call reverse,a,b) 



此時的foo的值就是「b a」。 

  

  

7、origin函數 
origin函數不像其它的函數,他並不操做變量的值,他只是告訴你你的這個變量是哪裏來的?其語法是: 

  

    $(origin <variable>;) 

  

注意,<variable>;是變量的名字,不該該是引用。因此你最好不要在<variable>;中使用「$」字符。Origin函數會以其返回值來告訴你這個變量的「出生狀況」,下面,是origin函數的返回值: 

  

「undefined」 

      若是<variable>;歷來沒有定義過,origin函數返回這個值「undefined」。 

  

「default」 

      若是<variable>;是一個默認的定義,好比「CC」這個變量,這種變量咱們將在後面講述。 

  

「environment」 

      若是<variable>;是一個環境變量,而且當Makefile被執行時,「-e」參數沒有被打開。 

  

「file」 

      若是<variable>;這個變量被定義在Makefile中。 

  

「command line」 

      若是<variable>;這個變量是被命令行定義的。 

  

「override」 

      若是<variable>;是被override指示符從新定義的。 

  

「automatic」 

      若是<variable>;是一個命令運行中的自動化變量。關於自動化變量將在後面講述。 

  

這些信息對於咱們編寫Makefile是很是有用的,例如,假設咱們有一個Makefile其包了一個定義文件Make.def,在Make.def中定義了一個變量「bletch」,而咱們的環境中也有一個環境變量「bletch」,此時,咱們想判斷一下,若是變量來源於環境,那麼咱們就把之重定義了,若是來源於Make.def或是命令行等非環境的,那麼咱們就不從新定義它。因而,在咱們的Makefile中,咱們能夠這樣寫: 

  

    ifdef bletch 

    ifeq "$(origin bletch)" "environment" 

    bletch = barf, gag, etc. 

    endif 

    endif 

  

固然,你也許會說,使用override關鍵字不就能夠從新定義環境中的變量了嗎?爲何須要使用這樣的步驟?是的,咱們用override是能夠達到這樣的效果,但是override過於粗暴,它同時會把從命令行定義的變量也覆蓋了,而咱們只想從新定義環境傳來的,而不想從新定義命令行傳來的。 

  

  

8、shell函數 
  

shell函數也不像其它的函數。顧名思義,它的參數應該就是操做系統Shell的命令。它和反引號「`」是相同的功能。這就是說,shell函數把執行操做系統命令後的輸出做爲函數返回。因而,咱們能夠用操做系統命令以及字符串處理命令awk,sed等等命令來生成一個變量,如: 

  

    contents := $(shell cat foo) 

  

    files := $(shell echo *.c) 

  

注意,這個函數會新生成一個Shell程序來執行命令,因此你要注意其運行性能,若是你的Makefile中有一些比較複雜的規則,並大量使用了這個函數,那麼對於你的系統性能是有害的。特別是Makefile的隱晦的規則可能會讓你的shell函數執行的次數比你想像的多得多。 

  

  

9、控制make的函數 
  

make提供了一些函數來控制make的運行。一般,你須要檢測一些運行Makefile時的運行時信息,而且根據這些信息來決定,你是讓make繼續執行,仍是中止。 

  

$(error <text ...>;) 

  

    產生一個致命的錯誤,<text ...>;是錯誤信息。注意,error函數不會在一被使用就會產生錯誤信息,因此若是你把其定義在某個變量中,並在後續的腳本中使用這個變量,那麼也是能夠的。例如: 

  

    示例一: 

    ifdef ERROR_001 

    $(error error is $(ERROR_001)) 

    endif 

  

    示例二: 

    ERR = $(error found an error!) 

    .PHONY: err 

    err: ; $(ERR) 

  

    示例一會在變量ERROR_001定義了後執行時產生error調用,而示例二則在目錄err被執行時才發生error調用。 

  

$(warning <text ...>;) 

  

     這個函數很像error函數,只是它並不會讓make退出,只是輸出一段警告信息,而make繼續執行。安全



make 的運行 
—————— 

通常來講,最簡單的就是直接在命令行下輸入make命令,make命令會找當前目錄的makefile來執行,一切都是自動的。但也有時你也許只想讓make重編譯某些文件,而不是整個工程,而又有的時候你有幾套編譯規則,你想在不一樣的時候使用不一樣的編譯規則,等等。本章節就是講述如何使用make命令的。 

1、make的退出碼 

make命令執行後有三個退出碼: 

    0 —— 表示成功執行。 
    1 —— 若是make運行時出現任何錯誤,其返回1。 
    2 —— 若是你使用了make的「-q」選項,而且make使得一些目標不須要更新,那麼返回2。 

Make的相關參數咱們會在後續章節中講述。 


2、指定Makefile 

前面咱們說過,GNU make找尋默認的Makefile的規則是在當前目錄下依次找三個文件——「GNUmakefile」、「makefile」和「Makefile」。其按順序找這三個文件,一旦找到,就開始讀取這個文件並執行。 

當前,咱們也能夠給make命令指定一個特殊名字的Makefile。要達到這個功能,咱們要使用make的「-f」或是「--file」參數(「--makefile」參數也行)。例如,咱們有個makefile的名字是「hchen.mk」,那麼,咱們能夠這樣來讓make來執行這個文件: 

    make –f hchen.mk 

若是在make的命令行是,你不僅一次地使用了「-f」參數,那麼,全部指定的makefile將會被連在一塊兒傳遞給make執行。 


3、指定目標 

通常來講,make的最終目標是makefile中的第一個目標,而其它目標通常是由這個目標連帶出來的。這是make的默認行爲。固然,通常來講,你的makefile中的第一個目標是由許多個目標組成,你能夠指示make,讓其完成你所指定的目標。要達到這一目的很簡單,需在make命令後直接跟目標的名字就能夠完成(如前面提到的「make clean」形式) 

任何在makefile中的目標均可以被指定成終極目標,可是除了以「-」打頭,或是包含了「=」的目標,由於有這些字符的目標,會被解析成命令行參數或是變量。甚至沒有被咱們明確寫出來的目標也能夠成爲make的終極目標,也就是說,只要make能夠找到其隱含規則推導規則,那麼這個隱含目標一樣能夠被指定成終極目標。 

有一個make的環境變量叫「MAKECMDGOALS」,這個變量中會存放你所指定的終極目標的列表,若是在命令行上,你沒有指定目標,那麼,這個變量是空值。這個變量可讓你使用在一些比較特殊的情形下。好比下面的例子: 

    sources = foo.c bar.c 
    ifneq ( $(MAKECMDGOALS),clean) 
    include $(sources:.c=.d) 
    endif 

基於上面的這個例子,只要咱們輸入的命令不是「make clean」,那麼makefile會自動包含「foo.d」和「bar.d」這兩個makefile。 

使用指定終極目標的方法能夠很方便地讓咱們編譯咱們的程序,例以下面這個例子: 

    .PHONY: all 
    all: prog1 prog2 prog3 prog4 

從這個例子中,咱們能夠看到,這個makefile中有四個須要編譯的程序——「prog1」, 「prog2」, 「prog3」和 「prog4」,咱們能夠使用「make all」命令來編譯全部的目標(若是把all置成第一個目標,那麼只需執行「make」),咱們也能夠使用「make prog2」來單獨編譯目標「prog2」。 

即然make能夠指定全部makefile中的目標,那麼也包括「僞目標」,因而咱們能夠根據這種性質來讓咱們的makefile根據指定的不一樣的目標來完成不一樣的事。在Unix世界中,軟件發佈時,特別是GNU這種開源軟件的發佈時,其makefile都包含了編譯、安裝、打包等功能。咱們能夠參照這種規則來書寫咱們的makefile中的目標。 

     「all」 
        這個僞目標是全部目標的目標,其功能通常是編譯全部的目標。 
     「clean」 
        這個僞目標功能是刪除全部被make建立的文件。 
     「install」 
        這個僞目標功能是安裝已編譯好的程序,其實就是把目標執行文件拷貝到指定的目標中去。 
     「print」 
        這個僞目標的功能是例出改變過的源文件。 
     「tar」 
        這個僞目標功能是把源程序打包備份。也就是一個tar文件。 
     「dist」 
        這個僞目標功能是建立一個壓縮文件,通常是把tar文件壓成Z文件。或是gz文件。 
     「TAGS」 
        這個僞目標功能是更新全部的目標,以備完整地重編譯使用。 
     「check」和「test」 
        這兩個僞目標通常用來測試makefile的流程。 

固然一個項目的makefile中也不必定要書寫這樣的目標,這些東西都是GNU的東西,可是我想,GNU搞出這些東西必定有其可取之處(等你的UNIX下的程序文件一多時你就會發現這些功能頗有用了),這裏只不過是說明了,若是你要書寫這種功能,最好使用這種名字命名你的目標,這樣規範一些,規範的好處就是——不用解釋,你們都明白。並且若是你的makefile中有這些功能,一是很實用,二是能夠顯得你的makefile很專業(不是那種初學者的做品)。 


4、檢查規則 

有時候,咱們不想讓咱們的makefile中的規則執行起來,咱們只想檢查一下咱們的命令,或是執行的序列。因而咱們能夠使用make命令的下述參數: 

    「-n」 
    「--just-print」 
    「--dry-run」 
    「--recon」 
    不執行參數,這些參數只是打印命令,無論目標是否更新,把規則和連帶規則下的命令打印出來,但不執行,這些參數對於咱們調試makefile頗有用處。 

    「-t」 
    「--touch」 
    這個參數的意思就是把目標文件的時間更新,但不更改目標文件。也就是說,make僞裝編譯目標,但不是真正的編譯目標,只是把目標變成已編譯過的狀態。 

    「-q」 
    「--question」 
    這個參數的行爲是找目標的意思,也就是說,若是目標存在,那麼其什麼也不會輸出,固然也不會執行編譯,若是目標不存在,其會打印出一條出錯信息。 

    「-W <file>;」 
    「--what-if=<file>;」 
    「--assume-new=<file>;」 
    「--new-file=<file>;」 
    這個參數須要指定一個文件。通常是是源文件(或依賴文件),Make會根據規則推導來運行依賴於這個文件的命令,通常來講,能夠和「-n」參數一同使用,來查看這個依賴文件所發生的規則命令。 

另一個頗有意思的用法是結合「-p」和「-v」來輸出makefile被執行時的信息(這個將在後面講述)。 


5、make的參數 

下面列舉了全部GNU make 3.80版的參數定義。其它版本和產商的make大同小異,不過其它產商的make的具體參數仍是請參考各自的產品文檔。 

「-b」 
「-m」 
這兩個參數的做用是忽略和其它版本make的兼容性。 

「-B」 
「--always-make」 
認爲全部的目標都須要更新(重編譯)。 

「-C <dir>;」 
「--directory=<dir>;」 
指定讀取makefile的目錄。若是有多個「-C」參數,make的解釋是後面的路徑之前面的做爲相對路徑,並以最後的目錄做爲被指定目錄。如:「make –C ~hchen/test –C prog」等價於「make –C ~hchen/test/prog」。 

「—debug[=<options>;]」 
輸出make的調試信息。它有幾種不一樣的級別可供選擇,若是沒有參數,那就是輸出最簡單的調試信息。下面是<options>;的取值: 
    a —— 也就是all,輸出全部的調試信息。(會很是的多) 
    b —— 也就是basic,只輸出簡單的調試信息。即輸出不須要重編譯的目標。 
    v —— 也就是verbose,在b選項的級別之上。輸出的信息包括哪一個makefile被解析,不須要被重編譯的依賴文件(或是依賴目標)等。 
    i —— 也就是implicit,輸出因此的隱含規則。 
    j —— 也就是jobs,輸出執行規則中命令的詳細信息,如命令的PID、返回碼等。 
    m —— 也就是makefile,輸出make讀取makefile,更新makefile,執行makefile的信息。 

「-d」 
至關於「--debug=a」。 

「-e」 
「--environment-overrides」 
指明環境變量的值覆蓋makefile中定義的變量的值。 

「-f=<file>;」 
「--file=<file>;」 
「--makefile=<file>;」 
指定須要執行的makefile。 

「-h」 
「--help」 
顯示幫助信息。 

「-i」 
「--ignore-errors」 
在執行時忽略全部的錯誤。 

「-I <dir>;」 
「--include-dir=<dir>;」 
指定一個被包含makefile的搜索目標。能夠使用多個「-I」參數來指定多個目錄。 

「-j [<jobsnum>;]」 
「--jobs[=<jobsnum>;]」 
指同時運行命令的個數。若是沒有這個參數,make運行命令時能運行多少就運行多少。若是有一個以上的「-j」參數,那麼僅最後一個「-j」纔是有效的。(注意這個參數在MS-DOS中是無用的) 

「-k」 
「--keep-going」 
出錯也不中止運行。若是生成一個目標失敗了,那麼依賴於其上的目標就不會被執行了。 

「-l <load>;」 
「--load-average[=<load]」 
「—max-load[=<load>;]」 
指定make運行命令的負載。 

「-n」 
「--just-print」 
「--dry-run」 
「--recon」 
僅輸出執行過程當中的命令序列,但並不執行。 

「-o <file>;」 
「--old-file=<file>;」 
「--assume-old=<file>;」 
不從新生成的指定的<file>;,即便這個目標的依賴文件新於它。 

「-p」 
「--print-data-base」 
輸出makefile中的全部數據,包括全部的規則和變量。這個參數會讓一個簡單的makefile都會輸出一堆信息。若是你只是想輸出信息而不想執行makefile,你能夠使用「make -qp」命令。若是你想查看執行makefile前的預設變量和規則,你能夠使用「make –p –f /dev/null」。這個參數輸出的信息會包含着你的makefile文件的文件名和行號,因此,用這個參數來調試你的makefile會是頗有用的,特別是當你的環境變量很複雜的時候。 

「-q」 
「--question」 
不運行命令,也不輸出。僅僅是檢查所指定的目標是否須要更新。若是是0則說明要更新,若是是2則說明有錯誤發生。 

「-r」 
「--no-builtin-rules」 
禁止make使用任何隱含規則。 

「-R」 
「--no-builtin-variabes」 
禁止make使用任何做用於變量上的隱含規則。 

「-s」 
「--silent」 
「--quiet」 
在命令運行時不輸出命令的輸出。 

「-S」 
「--no-keep-going」 
「--stop」 
取消「-k」選項的做用。由於有些時候,make的選項是從環境變量「MAKEFLAGS」中繼承下來的。因此你能夠在命令行中使用這個參數來讓環境變量中的「-k」選項失效。 

「-t」 
「--touch」 
至關於UNIX的touch命令,只是把目標的修改日期變成最新的,也就是阻止生成目標的命令運行。 

「-v」 
「--version」 
輸出make程序的版本、版權等關於make的信息。 

「-w」 
「--print-directory」 
輸出運行makefile以前和以後的信息。這個參數對於跟蹤嵌套式調用make時頗有用。 

「--no-print-directory」 
禁止「-w」選項。 

「-W <file>;」 
「--what-if=<file>;」 
「--new-file=<file>;」 
「--assume-file=<file>;」 
假定目標<file>;須要更新,若是和「-n」選項使用,那麼這個參數會輸出該目標更新時的運行動做。若是沒有「-n」那麼就像運行UNIX的「touch」命令同樣,使得<file>;的修改時間爲當前時間。 

「--warn-undefined-variables」 
只要make發現有未定義的變量,那麼就輸出警告信息。


 
隱含規則 
———— 

在咱們使用Makefile時,有一些咱們會常用,並且使用頻率很是高的東西,好比,咱們編譯C/C++的源程序爲中間目標文件(Unix下是[.o]文件,Windows下是[.obj]文件)。本章講述的就是一些在Makefile中的「隱含的」,早先約定了的,不須要咱們再寫出來的規則。 

「隱含規則」也就是一種慣例,make會按照這種「慣例」心照不喧地來運行,那怕咱們的Makefile中沒有書寫這樣的規則。例如,把[.c]文件編譯成[.o]文件這一規則,你根本就不用寫出來,make會自動推導出這種規則,並生成咱們須要的[.o]文件。 

「隱含規則」會使用一些咱們系統變量,咱們能夠改變這些系統變量的值來定製隱含規則的運行時的參數。如系統變量「CFLAGS」能夠控制編譯時的編譯器參數。 

咱們還能夠經過「模式規則」的方式寫下本身的隱含規則。用「後綴規則」來定義隱含規則會有許多的限制。使用「模式規則」會更回得智能和清楚,但「後綴規則」能夠用來保證咱們Makefile的兼容性。 
咱們瞭解了「隱含規則」,可讓其爲咱們更好的服務,也會讓咱們知道一些「約定俗成」了的東西,而不至於使得咱們在運行Makefile時出現一些咱們以爲莫名其妙的東西。固然,任何事物都是矛盾的,水能載舟,亦可覆舟,因此,有時候「隱含規則」也會給咱們形成不小的麻煩。只有瞭解了它,咱們才能更好地使用它。 


1、使用隱含規則 

若是要使用隱含規則生成你須要的目標,你所須要作的就是不要寫出這個目標的規則。那麼,make會試圖去自動推導產生這個目標的規則和命令,若是make能夠自動推導生成這個目標的規則和命令,那麼這個行爲就是隱含規則的自動推導。固然,隱含規則是make事先約定好的一些東西。例如,咱們有下面的一個Makefile: 

    foo : foo.o bar.o 
            cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS) 

咱們能夠注意到,這個Makefile中並無寫下如何生成foo.o和bar.o這兩目標的規則和命令。由於make的「隱含規則」功能會自動爲咱們自動去推導這兩個目標的依賴目標和生成命令。 

make會在本身的「隱含規則」庫中尋找能夠用的規則,若是找到,那麼就會使用。若是找不到,那麼就會報錯。在上面的那個例子中,make調用的隱含規則是,把[.o]的目標的依賴文件置成[.c],並使用C的編譯命令「cc –c $(CFLAGS) [.c]」來生成[.o]的目標。也就是說,咱們徹底沒有必要寫下下面的兩條規則: 

    foo.o : foo.c 
            cc –c foo.c $(CFLAGS) 
    bar.o : bar.c 
        cc –c bar.c $(CFLAGS) 

由於,這已是「約定」好了的事了,make和咱們約定好了用C編譯器「cc」生成[.o]文件的規則,這就是隱含規則。 

固然,若是咱們爲[.o]文件書寫了本身的規則,那麼make就不會自動推導並調用隱含規則,它會按照咱們寫好的規則忠實地執行。 

還有,在make的「隱含規則庫」中,每一條隱含規則都在庫中有其順序,越靠前的則是越被常用的,因此,這會致使咱們有些時候即便咱們顯示地指定了目標依賴,make也不會管。以下面這條規則(沒有命令): 

    foo.o : foo.p 

依賴文件「foo.p」(Pascal程序的源文件)有可能變得沒有意義。若是目錄下存在了「foo.c」文件,那麼咱們的隱含規則同樣會生效,並會經過「foo.c」調用C的編譯器生成foo.o文件。由於,在隱含規則中,Pascal的規則出如今C的規則以後,因此,make找到能夠生成foo.o的C的規則就再也不尋找下一條規則了。若是你確實不但願任何隱含規則推導,那麼,你就不要只寫出「依賴規則」,而不寫命令。 


2、隱含規則一覽 

這裏咱們將講述全部預先設置(也就是make內建)的隱含規則,若是咱們不明確地寫下規則,那麼,make就會在這些規則中尋找所須要規則和命令。固然,咱們也能夠使用make的參數「-r」或「--no-builtin-rules」選項來取消全部的預設置的隱含規則。 

固然,即便是咱們指定了「-r」參數,某些隱含規則仍是會生效,由於有許多的隱含規則都是使用了「後綴規則」來定義的,因此,只要隱含規則中有「後綴列表」(也就一系統定義在目標.SUFFIXES的依賴目標),那麼隱含規則就會生效。默認的後綴列表是:.out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el。具體的細節,咱們會在後面講述。 

仍是先來看一看經常使用的隱含規則吧。 

一、編譯C程序的隱含規則。 
「<n>;.o」的目標的依賴目標會自動推導爲「<n>;.c」,而且其生成命令是「$(CC) –c $(CPPFLAGS) $(CFLAGS)」 

二、編譯C++程序的隱含規則。 
「<n>;.o」的目標的依賴目標會自動推導爲「<n>;.cc」或是「<n>;.C」,而且其生成命令是「$(CXX) –c $(CPPFLAGS) $(CFLAGS)」。(建議使用「.cc」做爲C++源文件的後綴,而不是「.C」) 

三、編譯Pascal程序的隱含規則。 
「<n>;.o」的目標的依賴目標會自動推導爲「<n>;.p」,而且其生成命令是「$(PC) –c  $(PFLAGS)」。 

四、編譯Fortran/Ratfor程序的隱含規則。 
「<n>;.o」的目標的依賴目標會自動推導爲「<n>;.r」或「<n>;.F」或「<n>;.f」,而且其生成命令是: 
    「.f」  「$(FC) –c  $(FFLAGS)」 
    「.F」  「$(FC) –c  $(FFLAGS) $(CPPFLAGS)」 
    「.f」  「$(FC) –c  $(FFLAGS) $(RFLAGS)」 

五、預處理Fortran/Ratfor程序的隱含規則。 
「<n>;.f」的目標的依賴目標會自動推導爲「<n>;.r」或「<n>;.F」。這個規則只是轉換Ratfor或有預處理的Fortran程序到一個標準的Fortran程序。其使用的命令是: 
    「.F」  「$(FC) –F $(CPPFLAGS) $(FFLAGS)」 
    「.r」  「$(FC) –F $(FFLAGS) $(RFLAGS)」 

六、編譯Modula-2程序的隱含規則。 
「<n>;.sym」的目標的依賴目標會自動推導爲「<n>;.def」,而且其生成命令是:「$(M2C) $(M2FLAGS) $(DEFFLAGS)」。「<n.o>;」 的目標的依賴目標會自動推導爲「<n>;.mod」,而且其生成命令是:「$(M2C) $(M2FLAGS) $(MODFLAGS)」。 

七、彙編和彙編預處理的隱含規則。 
「<n>;.o」 的目標的依賴目標會自動推導爲「<n>;.s」,默認使用編譯品「as」,而且其生成命令是:「$(AS) $(ASFLAGS)」。「<n>;.s」 的目標的依賴目標會自動推導爲「<n>;.S」,默認使用C預編譯器「cpp」,而且其生成命令是:「$(AS) $(ASFLAGS)」。 

八、連接Object文件的隱含規則。 
「<n>;」目標依賴於「<n>;.o」,經過運行C的編譯器來運行連接程序生成(通常是「ld」),其生成命令是:「$(CC) $(LDFLAGS) <n>;.o $(LOADLIBES) $(LDLIBS)」。這個規則對於只有一個源文件的工程有效,同時也對多個Object文件(由不一樣的源文件生成)的也有效。例如以下規則: 

    x : y.o z.o 

而且「x.c」、「y.c」和「z.c」都存在時,隱含規則將執行以下命令: 

    cc -c x.c -o x.o 
    cc -c y.c -o y.o 
    cc -c z.c -o z.o 
    cc x.o y.o z.o -o x 
    rm -f x.o 
    rm -f y.o 
    rm -f z.o 

若是沒有一個源文件(如上例中的x.c)和你的目標名字(如上例中的x)相關聯,那麼,你最好寫出本身的生成規則,否則,隱含規則會報錯的。 

九、Yacc C程序時的隱含規則。 
「<n>;.c」的依賴文件被自動推導爲「n.y」(Yacc生成的文件),其生成命令是:「$(YACC) $(YFALGS)」。(「Yacc」是一個語法分析器,關於其細節請查看相關資料) 

十、Lex C程序時的隱含規則。 
「<n>;.c」的依賴文件被自動推導爲「n.l」(Lex生成的文件),其生成命令是:「$(LEX) $(LFALGS)」。(關於「Lex」的細節請查看相關資料) 

十一、Lex Ratfor程序時的隱含規則。 
「<n>;.r」的依賴文件被自動推導爲「n.l」(Lex生成的文件),其生成命令是:「$(LEX) $(LFALGS)」。 

十二、從C程序、Yacc文件或Lex文件建立Lint庫的隱含規則。 
「<n>;.ln」 (lint生成的文件)的依賴文件被自動推導爲「n.c」,其生成命令是:「$(LINT) $(LINTFALGS) $(CPPFLAGS) -i」。對於「<n>;.y」和「<n>;.l」也是一樣的規則。 


3、隱含規則使用的變量 

在隱含規則中的命令中,基本上都是使用了一些預先設置的變量。你能夠在你的makefile中改變這些變量的值,或是在make的命令行中傳入這些值,或是在你的環境變量中設置這些值,不管怎麼樣,只要設置了這些特定的變量,那麼其就會對隱含規則起做用。固然,你也能夠利用make的「-R」或「--no–builtin-variables」參數來取消你所定義的變量對隱含規則的做用。 

例如,第一條隱含規則——編譯C程序的隱含規則的命令是「$(CC) –c $(CFLAGS) $(CPPFLAGS)」。Make默認的編譯命令是「cc」,若是你把變量「$(CC)」重定義成「gcc」,把變量「$(CFLAGS)」重定義成「-g」,那麼,隱含規則中的命令所有會以「gcc –c -g $(CPPFLAGS)」的樣子來執行了。 

咱們能夠把隱含規則中使用的變量分紅兩種:一種是命令相關的,如「CC」;一種是參數相的關,如「CFLAGS」。下面是全部隱含規則中會用到的變量: 

一、關於命令的變量。 

AR  
    函數庫打包程序。默認命令是「ar」。  
AS  
    彙編語言編譯程序。默認命令是「as」。 
CC  
    C語言編譯程序。默認命令是「cc」。 
CXX  
    C++語言編譯程序。默認命令是「g++」。 
CO  
    從 RCS文件中擴展文件程序。默認命令是「co」。 
CPP  
    C程序的預處理器(輸出是標準輸出設備)。默認命令是「$(CC) –E」。 
FC  
    Fortran 和 Ratfor 的編譯器和預處理程序。默認命令是「f77」。 
GET  
    從SCCS文件中擴展文件的程序。默認命令是「get」。  
LEX  
    Lex方法分析器程序(針對於C或Ratfor)。默認命令是「lex」。 
PC  
    Pascal語言編譯程序。默認命令是「pc」。 
YACC  
    Yacc文法分析器(針對於C程序)。默認命令是「yacc」。 
YACCR  
    Yacc文法分析器(針對於Ratfor程序)。默認命令是「yacc –r」。 
MAKEINFO  
    轉換Texinfo源文件(.texi)到Info文件程序。默認命令是「makeinfo」。 
TEX  
    從TeX源文件建立TeX DVI文件的程序。默認命令是「tex」。 
TEXI2DVI  
    從Texinfo源文件建立軍TeX DVI 文件的程序。默認命令是「texi2dvi」。 
WEAVE  
    轉換Web到TeX的程序。默認命令是「weave」。 
CWEAVE  
    轉換C Web 到 TeX的程序。默認命令是「cweave」。 
TANGLE  
    轉換Web到Pascal語言的程序。默認命令是「tangle」。 
CTANGLE  
    轉換C Web 到 C。默認命令是「ctangle」。 
RM  
    刪除文件命令。默認命令是「rm –f」。 

二、關於命令參數的變量 

下面的這些變量都是相關上面的命令的參數。若是沒有指明其默認值,那麼其默認值都是空。 

ARFLAGS  
    函數庫打包程序AR命令的參數。默認值是「rv」。 
ASFLAGS  
    彙編語言編譯器參數。(當明顯地調用「.s」或「.S」文件時)。  
CFLAGS  
    C語言編譯器參數。 
CXXFLAGS  
    C++語言編譯器參數。 
COFLAGS  
    RCS命令參數。  
CPPFLAGS  
    C預處理器參數。( C 和 Fortran 編譯器也會用到)。 
FFLAGS  
    Fortran語言編譯器參數。 
GFLAGS  
    SCCS 「get」程序參數。 
LDFLAGS  
    連接器參數。(如:「ld」) 
LFLAGS  
    Lex文法分析器參數。 
PFLAGS  
    Pascal語言編譯器參數。 
RFLAGS  
    Ratfor 程序的Fortran 編譯器參數。 
YFLAGS  
    Yacc文法分析器參數。  


4、隱含規則鏈 

有些時候,一個目標可能被一系列的隱含規則所做用。例如,一個[.o]的文件生成,可能會是先被Yacc的[.y]文件先成[.c],而後再被C的編譯器生成。咱們把這一系列的隱含規則叫作「隱含規則鏈」。 

在上面的例子中,若是文件[.c]存在,那麼就直接調用C的編譯器的隱含規則,若是沒有[.c]文件,但有一個[.y]文件,那麼Yacc的隱含規則會被調用,生成[.c]文件,而後,再調用C編譯的隱含規則最終由[.c]生成[.o]文件,達到目標。 

咱們把這種[.c]的文件(或是目標),叫作中間目標。無論怎麼樣,make會努力自動推導生成目標的一切方法,無論中間目標有多少,其都會執着地把全部的隱含規則和你書寫的規則所有合起來分析,努力達到目標,因此,有些時候,可能會讓你以爲奇怪,怎麼個人目標會這樣生成?怎麼個人makefile發瘋了? 

在默認狀況下,對於中間目標,它和通常的目標有兩個地方所不一樣:第一個不一樣是除非中間的目標不存在,纔會引起中間規則。第二個不一樣的是,只要目標成功產生,那麼,產生最終目標過程當中,所產生的中間目標文件會被以「rm -f」刪除。 

一般,一個被makefile指定成目標或是依賴目標的文件不能被看成中介。然而,你能夠明顯地說明一個文件或是目標是中介目標,你能夠使用僞目標「.INTERMEDIATE」來強制聲明。(如:.INTERMEDIATE : mid ) 

你也能夠阻止make自動刪除中間目標,要作到這一點,你能夠使用僞目標「.SECONDARY」來強制聲明(如:.SECONDARY : sec)。你還能夠把你的目標,以模式的方式來指定(如:%.o)成僞目標「.PRECIOUS」的依賴目標,以保存被隱含規則所生成的中間文件。 

在「隱含規則鏈」中,禁止同一個目標出現兩次或兩次以上,這樣一來,就可防止在make自動推導時出現無限遞歸的狀況。 

Make會優化一些特殊的隱含規則,而不生成中間文件。如,從文件「foo.c」生成目標程序「foo」,按道理,make會編譯生成中間文件「foo.o」,而後連接成「foo」,但在實際狀況下,這一動做能夠被一條「cc」的命令完成(cc –o foo foo.c),因而優化過的規則就不會生成中間文件。



5、定義模式規則 

你能夠使用模式規則來定義一個隱含規則。一個模式規則就好像一個通常的規則,只是在規則中,目標的定義須要有"%"字符。"%"的意思是表示一個或多個任意字符。在依賴目標中一樣能夠使用"%",只是依賴目標中的"%"的取值,取決於其目標。 

有一點須要注意的是,"%"的展開發生在變量和函數的展開以後,變量和函數的展開發生在make載入Makefile時,而模式規則中的"%"則發生在運行時。 


一、模式規則介紹 

模式規則中,至少在規則的目標定義中要包含"%",不然,就是通常的規則。目標中的"%"定義表示對文件名的匹配,"%"表示長度任意的非空字符串。例如:"%.c"表示以".c"結尾的文件名(文件名的長度至少爲3),而"s.%.c"則表示以"s."開頭,".c"結尾的文件名(文件名的長度至少爲5)。 

若是"%"定義在目標中,那麼,目標中的"%"的值決定了依賴目標中的"%"的值,也就是說,目標中的模式的"%"決定了依賴目標中"%"的樣子。例若有一個模式規則以下: 

    %.o : %.c ; <command ......>; 

其含義是,指出了怎麼從全部的[.c]文件生成相應的[.o]文件的規則。若是要生成的目標是"a.o b.o",那麼"%c"就是"a.c b.c"。 

一旦依賴目標中的"%"模式被肯定,那麼,make會被要求去匹配當前目錄下全部的文件名,一旦找到,make就會規則下的命令,因此,在模式規則中,目標可能會是多個的,若是有模式匹配出多個目標,make就會產生全部的模式目標,此時,make關心的是依賴的文件名和生成目標的命令這兩件事。 


二、模式規則示例 

下面這個例子表示了,把全部的[.c]文件都編譯成[.o]文件. 

    %.o : %.c 
            $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ 

其中,"$@"表示全部的目標的挨個值,"$<"表示了全部依賴目標的挨個值。這些奇怪的變量咱們叫"自動化變量",後面會詳細講述。 

下面的這個例子中有兩個目標是模式的: 

    %.tab.c %.tab.h: %.y 
            bison -d $< 

這條規則告訴make把全部的[.y]文件都以"bison -d <n>;.y"執行,而後生成"<n>;.tab.c"和"<n>;.tab.h"文件。(其中,"<n>;"表示一個任意字符串)。若是咱們的執行程序"foo"依賴於文件"parse.tab.o"和"scan.o",而且文件"scan.o"依賴於文件"parse.tab.h",若是"parse.y"文件被更新了,那麼根據上述的規則,"bison -d parse.y"就會被執行一次,因而,"parse.tab.o"和"scan.o"的依賴文件就齊了。(假設,"parse.tab.o"由"parse.tab.c"生成,和"scan.o"由"scan.c"生成,而"foo"由"parse.tab.o"和"scan.o"連接生成,並且foo和其[.o]文件的依賴關係也寫好,那麼,全部的目標都會獲得知足) 


三、自動化變量 

在上述的模式規則中,目標和依賴文件都是一系例的文件,那麼咱們如何書寫一個命令來完成從不一樣的依賴文件生成相應的目標?由於在每一次的對模式規則的解析時,都會是不一樣的目標和依賴文件。 

自動化變量就是完成這個功能的。在前面,咱們已經對自動化變量有所提涉,相信你看到這裏已對它有一個感性認識了。所謂自動化變量,就是這種變量會把模式中所定義的一系列的文件自動地挨個取出,直至全部的符合模式的文件都取完了。這種自動化變量只應出如今規則的命令中。 

下面是全部的自動化變量及其說明: 

$@ 
    表示規則中的目標文件集。在模式規則中,若是有多個目標,那麼,"$@"就是匹配於目標中模式定義的集合。 

$% 
    僅當目標是函數庫文件中,表示規則中的目標成員名。例如,若是一個目標是"foo.a(bar.o)",那麼,"$%"就是"bar.o","$@"就是"foo.a"。若是目標不是函數庫文件(Unix下是[.a],Windows下是[.lib]),那麼,其值爲空。 

$< 
    依賴目標中的第一個目標名字。若是依賴目標是以模式(即"%")定義的,那麼"$<"將是符合模式的一系列的文件集。注意,其是一個一個取出來的。 

$? 
    全部比目標新的依賴目標的集合。以空格分隔。 

$^ 
    全部的依賴目標的集合。以空格分隔。若是在依賴目標中有多個重複的,那個這個變量會去除重複的依賴目標,只保留一份。 

$+ 
    這個變量很像"$^",也是全部依賴目標的集合。只是它不去除重複的依賴目標。 

$*  
   這個變量表示目標模式中"%"及其以前的部分。若是目標是"dir/a.foo.b",而且目標的模式是"a.%.b",那麼,"$*"的值就是"dir/a.foo"。這個變量對於構造有關聯的文件名是比較有較。若是目標中沒有模式的定義,那麼"$*"也就不能被推導出,可是,若是目標文件的後綴是make所識別的,那麼"$*"就是除了後綴的那一部分。例如:若是目標是"foo.c",由於".c"是make所能識別的後綴名,因此,"$*"的值就是"foo"。這個特性是GNU make的,頗有可能不兼容於其它版本的make,因此,你應該儘可能避免使用"$*",除非是在隱含規則或是靜態模式中。若是目標中的後綴是make所不能識別的,那麼"$*"就是空值。 

當你但願只對更新過的依賴文件進行操做時,"$?"在顯式規則中頗有用,例如,假設有一個函數庫文件叫"lib",其由其它幾個object文件更新。那麼把object文件打包的比較有效率的Makefile規則是: 

    lib : foo.o bar.o lose.o win.o 
            ar r lib $? 

在上述所列出來的自動量變量中。四個變量($@、$<、$%、$*)在擴展時只會有一個文件,而另三個的值是一個文件列表。這七個自動化變量還能夠取得文件的目錄名或是在當前目錄下的符合模式的文件名,只須要搭配上"D"或"F"字樣。這是GNU make中老版本的特性,在新版本中,咱們使用函數"dir"或"notdir"就能夠作到了。"D"的含義就是Directory,就是目錄,"F"的含義就是File,就是文件。 

下面是對於上面的七個變量分別加上"D"或是"F"的含義: 

$(@D) 
    表示"$@"的目錄部分(不以斜槓做爲結尾),若是"$@"值是"dir/foo.o",那麼"$(@D)"就是"dir",而若是"$@"中沒有包含斜槓的話,其值就是"."(當前目錄)。 

$(@F) 
    表示"$@"的文件部分,若是"$@"值是"dir/foo.o",那麼"$(@F)"就是"foo.o","$(@F)"至關於函數"$(notdir $@)"。 

"$(*D)" 
"$(*F)" 
    和上面所述的同理,也是取文件的目錄部分和文件部分。對於上面的那個例子,"$(*D)"返回"dir",而"$(*F)"返回"foo" 

"$(%D)" 
"$(%F)" 
    分別表示了函數包文件成員的目錄部分和文件部分。這對於形同"archive(member)"形式的目標中的"member"中包含了不一樣的目錄頗有用。 

"$(<D)" 
"$(<F)" 
    分別表示依賴文件的目錄部分和文件部分。 

"$(^D)" 
"$(^F)" 
    分別表示全部依賴文件的目錄部分和文件部分。(無相同的) 

"$(+D)" 
"$(+F)" 
    分別表示全部依賴文件的目錄部分和文件部分。(能夠有相同的) 

"$(?D)" 
"$(?F)" 
    分別表示被更新的依賴文件的目錄部分和文件部分。 

最後想提醒一下的是,對於"$<",爲了不產生沒必要要的麻煩,咱們最好給$後面的那個特定字符都加上圓括號,好比,"$(<)"就要比"$<"要好一些。 

還得要注意的是,這些變量只使用在規則的命令中,並且通常都是"顯式規則"和"靜態模式規則"(參見前面"書寫規則"一章)。其在隱含規則中並無意義。 

四、模式的匹配 

通常來講,一個目標的模式有一個有前綴或是後綴的"%",或是沒有先後綴,直接就是一個"%"。由於"%"表明一個或多個字符,因此在定義好了的模式中,咱們把"%"所匹配的內容叫作"莖",例如"%.c"所匹配的文件"test.c"中"test"就是"莖"。由於在目標和依賴目標中同時有"%"時,依賴目標的"莖"會傳給目標,當作目標中的"莖"。 

當一個模式匹配包含有斜槓(實際也不常常包含)的文件時,那麼在進行模式匹配時,目錄部分會首先被移開,而後進行匹配,成功後,再把目錄加回去。在進行"莖"的傳遞時,咱們須要知道這個步驟。例若有一個模式"e%t",文件"src/eat"匹配於該模式,因而"src/a"就是其"莖",若是這個模式定義在依賴目標中,而被依賴於這個模式的目標中又有個模式"c%r",那麼,目標就是"src/car"。("莖"被傳遞) 


五、重載內建隱含規則 

你能夠重載內建的隱含規則(或是定義一個全新的),例如你能夠從新構造和內建隱含規則不一樣的命令,如: 

    %.o : %.c 
            $(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date) 

你能夠取消內建的隱含規則,只要不在後面寫命令就行。如: 

    %.o : %.s 

一樣,你也能夠從新定義一個全新的隱含規則,其在隱含規則中的位置取決於你在哪裏寫下這個規則。朝前的位置就靠前。 


6、老式風格的"後綴規則" 

後綴規則是一個比較老式的定義隱含規則的方法。後綴規則會被模式規則逐步地取代。由於模式規則更強更清晰。爲了和老版本的Makefile兼容,GNU make一樣兼容於這些東西。後綴規則有兩種方式:"雙後綴"和"單後綴"。 

雙後綴規則定義了一對後綴:目標文件的後綴和依賴目標(源文件)的後綴。如".c.o"至關於"%o : %c"。單後綴規則只定義一個後綴,也就是源文件的後綴。如".c"至關於"% : %.c"。 

後綴規則中所定義的後綴應該是make所認識的,若是一個後綴是make所認識的,那麼這個規則就是單後綴規則,而若是兩個連在一塊兒的後綴都被make所認識,那就是雙後綴規則。例如:".c"和".o"都是make所知道。於是,若是你定義了一個規則是".c.o"那麼其就是雙後綴規則,意義就是".c"是源文件的後綴,".o"是目標文件的後綴。以下示例: 

    .c.o: 
            $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $< 

後綴規則不容許任何的依賴文件,若是有依賴文件的話,那就不是後綴規則,那些後綴通通被認爲是文件名,如: 

    .c.o: foo.h 
            $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $< 

這個例子,就是說,文件".c.o"依賴於文件"foo.h",而不是咱們想要的這樣: 

    %.o: %.c foo.h 
            $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $< 

後綴規則中,若是沒有命令,那是毫無心義的。由於他也不會移去內建的隱含規則。 

而要讓make知道一些特定的後綴,咱們能夠使用僞目標".SUFFIXES"來定義或是刪除,如: 

    .SUFFIXES: .hack .win 

把後綴.hack和.win加入後綴列表中的末尾。 

    .SUFFIXES:              # 刪除默認的後綴 
    .SUFFIXES: .c .o .h   # 定義本身的後綴 

先清楚默認後綴,後定義本身的後綴列表。 

make的參數"-r"或"-no-builtin-rules"也會使用得默認的後綴列表爲空。而變量"SUFFIXE"被用來定義默認的後綴列表,你能夠用".SUFFIXES"來改變後綴列表,但請不要改變變量"SUFFIXE"的值。 


7、隱含規則搜索算法 

好比咱們有一個目標叫 T。下面是搜索目標T的規則的算法。請注意,在下面,咱們沒有提到後綴規則,緣由是,全部的後綴規則在Makefile被載入內存時,會被轉換成模式規則。若是目標是"archive(member)"的函數庫文件模式,那麼這個算法會被運行兩次,第一次是找目標T,若是沒有找到的話,那麼進入第二次,第二次會把"member"看成T來搜索。 

一、把T的目錄部分分離出來。叫D,而剩餘部分叫N。(如:若是T是"src/foo.o",那麼,D就是"src/",N就是"foo.o") 

二、建立全部匹配於T或是N的模式規則列表。 

三、若是在模式規則列表中有匹配全部文件的模式,如"%",那麼從列表中移除其它的模式。 

四、移除列表中沒有命令的規則。 

五、對於第一個在列表中的模式規則: 
    1)推導其"莖"S,S應該是T或是N匹配於模式中"%"非空的部分。 
    2)計算依賴文件。把依賴文件中的"%"都替換成"莖"S。若是目標模式中沒有包含斜框字符,而把D加在第一個依賴文件的開頭。 
3)測試是否全部的依賴文件都存在或是理當存在。(若是有一個文件被定義成另一個規則的目標文件,或者是一個顯式規則的依賴文件,那麼這個文件就叫"理當存在") 
    4)若是全部的依賴文件存在或是理當存在,或是就沒有依賴文件。那麼這條規則將被採用,退出該算法。 

六、若是通過第5步,沒有模式規則被找到,那麼就作更進一步的搜索。對於存在於列表中的第一個模式規則: 
    1)若是規則是終止規則,那就忽略它,繼續下一條模式規則。 
2)計算依賴文件。(同第5步) 
3)測試全部的依賴文件是否存在或是理當存在。 
4)對於不存在的依賴文件,遞歸調用這個算法查找他是否能夠被隱含規則找到。 
5)若是全部的依賴文件存在或是理當存在,或是就根本沒有依賴文件。那麼這條規則被採用,退出該算法。 

七、若是沒有隱含規則能夠使用,查看".DEFAULT"規則,若是有,採用,把".DEFAULT"的命令給T使用。 

一旦規則被找到,就會執行其至關的命令,而此時,咱們的自動化變量的值纔會生成。


使用make更新函數庫文件 ——————————— 函數庫文件也就是對Object文件(程序編譯的中間文件)的打包文件。在Unix下,通常是由命令"ar"來完成打包工做。 1、函數庫文件的成員 一個函數庫文件由多個文件組成。你能夠以以下格式指定函數庫文件及其組成:     archive(member) 這個不是一個命令,而一個目標和依賴的定義。通常來講,這種用法基本上就是爲了"ar"命令來服務的。如:     foolib(hack.o) : hack.o             ar cr foolib hack.o 若是要指定多個member,那就以空格分開,如:     foolib(hack.o kludge.o) 其等價於:     foolib(hack.o) foolib(kludge.o) 你還能夠使用Shell的文件通配符來定義,如:     foolib(*.o) 2、函數庫成員的隱含規則 當make搜索一個目標的隱含規則時,一個特殊的特性是,若是這個目標是"a(m)"形式的,其會把目標變成"(m)"。因而,若是咱們的成員是"%.o"的模式定義,而且若是咱們使用"make foo.a(bar.o)"的形式調用Makefile時,隱含規則會去找"bar.o"的規則,若是沒有定義bar.o的規則,那麼內建隱含規則生效,make會去找bar.c文件來生成bar.o,若是找獲得的話,make執行的命令大體以下:     cc -c bar.c -o bar.o     ar r foo.a bar.o     rm -f bar.o 還有一個變量要注意的是"$%",這是專屬函數庫文件的自動化變量,有關其說明請參見"自動化變量"一節。 3、函數庫文件的後綴規則 你能夠使用"後綴規則"和"隱含規則"來生成函數庫打包文件,如:     .c.a:             $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o             $(AR) r $@ $*.o             $(RM) $*.o 其等效於:     (%.o) : %.c             $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o             $(AR) r $@ $*.o             $(RM) $*.o 4、注意事項 在進行函數庫打包文件生成時,請當心使用make的並行機制("-j"參數)。若是多個ar命令在同一時間運行在同一個函數庫打包文件上,就頗有能夠損壞這個函數庫文件。因此,在make將來的版本中,應該提供一種機制來避免並行操做發生在函數打包文件上。 但就目前而言,你仍是應該不要儘可能不要使用"-j"參數。   後序 —— 終於到寫結束語的時候了,以上基本上就是GNU make的Makefile的全部細節了。其它的產商的make基本上也就是這樣的,不管什麼樣的make,都是以文件的依賴性爲基礎的,其基本是都是遵循一個標準的。這篇文檔中80%的技術細節都適用於任何的make,我猜想"函數"那一章的內容可能不是其它make所支持的,而隱含規則方面,我想不一樣的make會有不一樣的實現,我沒有精力來查看GNU的make和VC的nmake、BCB的make,或是別的UNIX下的make有些什麼樣的差異,一是時間精力不夠,二是由於我基本上都是在Unix下使用make,之前在SCO Unix和IBM的AIX,如今在Linux、Solaris、HP-UX、AIX和Alpha下使用,Linux和Solaris下更多一點。不過,我能夠確定的是,在Unix下的make,不管是哪一種平臺,幾乎都使用了Richard Stallman開發的make和cc/gcc的編譯器,並且,基本上都是GNU的make(公司裏全部的UNIX機器上都被裝上了GNU的東西,因此,使用GNU的程序也就多了一些)。GNU的東西仍是很不錯的,特別是使用得深了之後,愈來愈以爲GNU的軟件的強大,也愈來愈以爲GNU的在操做系統中(主要是Unix,甚至Windows)"殺傷力"。 對於上述全部的make的細節,咱們不但能夠利用make這個工具來編譯咱們的程序,還能夠利用make來完成其它的工做,由於規則中的命令能夠是任何Shell之下的命令,因此,在Unix下,你不必定只是使用程序語言的編譯器,你還能夠在Makefile中書寫其它的命令,如:tar、awk、mail、sed、cvs、compress、ls、rm、yacc、rpm、ftp……等等,等等,來完成諸如"程序打包"、"程序備份"、"製做程序安裝包"、"提交代碼"、"使用程序模板"、"合併文件"等等五花八門的功能,文件操做,文件管理,編程開發設計,或是其它一些異想天開的東西。好比,之前在書寫銀行交易程序時,因爲銀行的交易程序基本同樣,就見到有人書寫了一些交易的通用程序模板,在該模板中把一些網絡通信、數據庫操做的、業務操做共性的東西寫在一個文件中,在這些文件中用些諸如"@@@N、###N"奇怪字串標註一些位置,而後書寫交易時,只需按照一種特定的規則書寫特定的處理,最後在make時,使用awk和sed,把模板中的"@@@N、###N"等字串替代成特定的程序,造成C文件,而後再編譯。這個動做很像數據庫的"擴展C"語言(即在C語言中用"EXEC SQL"的樣子執行SQL語句,在用cc/gcc編譯以前,須要使用"擴展C"的翻譯程序,如cpre,把其翻譯成標準C)。若是你在使用make時有一些更爲絕妙的方法,請記得告訴我啊。 回頭看看整篇文檔,不覺記起幾年前剛剛開始在Unix下作開發的時候,有人問我會不會寫Makefile時,我兩眼發直,根本不知道在說什麼。一開始看到別人在vi中寫完程序後輸入"!make"時,還覺得是vi的功能,後來才知道有一個Makefile在做怪,因而上網查啊查,那時又不肯意看英文,發現就根本沒有中文的文檔介紹Makefile,只得看別人寫的Makefile,本身瞎碰瞎搞才積累了一點知識,但在不少地方徹底是知其然不知因此然。後來開始從事UNIX下產品軟件的開發,看到一個400人年,近200萬行代碼的大工程,發現要編譯這樣一個龐然大物,若是沒有Makefile,那會是多麼恐怖的同樣事啊。因而橫下心來,狠命地讀了一堆英文文檔,才以爲對其掌握了。但發現目前網上對Makefile介紹的文章仍是少得那麼的可憐,因此想寫這樣一篇文章,共享給你們,但願能對各位有所幫助。 如今我終於寫完了,看了看文件的建立時間,這篇技術文檔也寫了兩個多月了。發現,本身知道是一回事,要寫下來,跟別人講述又是另一回事,並且,如今愈來愈沒有時間專研技術細節,因此在寫做時,發如今闡述一些細節問題時很難作到嚴謹和精練,並且對先講什麼後講什麼不是很清楚,因此,仍是參考了一些國外站點上的資料和題綱,以及一些技術書籍的語言風格,才得以完成。整篇文檔的提綱是基於GNU的Makefile技術手冊的提綱來書寫的,並結合了本身的工做經驗,以及本身的學習歷程。由於歷來沒有寫過這麼長,這麼細的文檔,因此必定會有不少地方存在表達問題,語言歧義或是錯誤。因些,我迫切地得等待各位給我指證和建議,以及任何的反饋。 最後,仍是利用這個後序,介紹一下本身。我目前從事於全部Unix平臺下的軟件研發,主要是作分佈式計算/網格計算方面的系統產品軟件,而且我對於下一代的計算機革命——網格計算很是地感興趣,對於分佈式計算、P2P、Web Service、J2EE技術方向也很感興趣,同時,對於項目實施、團隊管理、項目管理也小有心得,但願一樣和我戰鬥在「技術和管理並重」的陣線上的年輕一代,可以和我多多地交流。個人MSN是:haoel@hotmail.com(經常使用),QQ是:753640(不經常使用)。(注:請勿給我MSN的郵箱發信,因爲hotmail的垃圾郵件致使我拒收這個郵箱的全部來信) 

相關文章
相關標籤/搜索