• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            posts - 297,  comments - 15,  trackbacks - 0
            概述
            ——

            什么是makefile?或許很多Winodws的程序員都不知道這個東西,因為那些Windows的IDE都為你做了這個工作,但我覺得要作一 個好的和professional的程序員,makefile還是要懂。這就好像現(xiàn)在有這么多的HTML的編輯器,但如果你想成為一個專業(yè)人士,你還是要 了解HTML的標(biāo)識的含義。特別在Unix下的軟件編譯,你就不能不自己寫makefile了,會不會寫makefile,從一個側(cè)面說明了一個人是否具 備完成大型工程的能力。

            因為,makefile關(guān)系到了整個工程的編譯規(guī)則。一個工程中的源文件不計數(shù),其按類型、功能、模塊分別放在若干個目錄中,makefile定 義了一系列的規(guī)則來指定,哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯,甚至于進(jìn)行更復(fù)雜的功能操作,因為makefile就像一個 Shell腳本一樣,其中也可以執(zhí)行操作系統(tǒng)的命令。

            makefile帶來的好處就是——“自動化編譯”,一旦寫好,只需要一個make命令,整個工程完全自動編譯,極大的提高了軟件開發(fā)的效率。 make是一個命令工具,是一個解釋makefile中指令的命令工具,一般來說,大多數(shù)的IDE都有這個命令,比如:Delphi的 make,Visual C++的nmake,Linux下GNU的make。可見,makefile都成為了一種在工程方面的編譯方法。

            現(xiàn)在講述如何寫makefile的文章比較少,這是我想寫這篇文章的原因。當(dāng)然,不同產(chǎn)商的make各不相同,也有不同的語法,但其本質(zhì)都是在 “文件依賴性”上做文章,這里,我僅對GNU的make進(jìn)行講述,我的環(huán)境是RedHat Linux 8.0,make的版本是3.80。必竟,這個 make是應(yīng)用最為廣泛的,也是用得最多的。而且其還是最遵循于IEEE 1003.2-1992 標(biāo)準(zhǔn)的(POSIX.2)。

            在這篇文檔中,將以C/C++的源碼作為我們基礎(chǔ),所以必然涉及一些關(guān)于C/C++的編譯的知識,相關(guān)于這方面的內(nèi)容,還請各位查看相關(guān)的編譯器 的文檔。這里所默認(rèn)的編譯器是UNIX下的GCC和CC。

             

            關(guān)于程序的編譯和鏈接
            ——————————

            在此,我想多說關(guān)于程序編譯的一些規(guī)范和方法,一般來說,無論是C、C++、還是pas,首先要把源文件編譯成中間代碼文件,在Windows下 也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,這個動作叫做編譯(compile)。然后再把大量的 Object File合成執(zhí)行文件,這個動作叫作鏈接(link)。

            編譯時,編譯器需要的是語法的正確,函數(shù)與變量的聲明的正確。對于后者,通常是你需要告訴編譯器頭文件的所在位置(頭文件中應(yīng)該只是聲明,而定義 應(yīng)該放在C/C++文件中),只要所有的語法正確,編譯器就可以編譯出中間目標(biāo)文件。一般來說,每個源文件都應(yīng)該對應(yīng)于一個中間目標(biāo)文件(O文件或是 OBJ文件)。

            鏈接時,主要是鏈接函數(shù)和全局變量,所以,我們可以使用這些中間目標(biāo)文件(O文件或是OBJ文件)來鏈接我們的應(yīng)用程序。鏈接器并不管函數(shù)所在的 源文件,只管函數(shù)的中間目標(biāo)文件(Object File),在大多數(shù)時候,由于源文件太多,編譯生成的中間目標(biāo)文件太多,而在鏈接時需要明顯地指出中間 目標(biāo)文件名,這對于編譯很不方便,所以,我們要給中間目標(biāo)文件打個包,在Windows下這種包叫“庫文件”(Library File),也就 是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。

            總結(jié)一下,源文件首先會生成中間目標(biāo)文件,再由中間目標(biāo)文件生成執(zhí)行文件。在編譯時,編譯器只檢測程序語法,和函數(shù)、變量是否被聲明。如果函數(shù)未 被聲明,編譯器會給出一個警告,但可以生成Object File。而在鏈接程序時,鏈接器會在所有的Object File中找尋函數(shù)的實現(xiàn),如果找不 到,那到就會報鏈接錯誤碼(Linker Error),在VC下,這種錯誤一般是:Link 2001錯誤,意思說是說,鏈接器未能找到函數(shù)的實現(xiàn)。你 需要指定函數(shù)的Object File.

            好,言歸正傳,GNU的make有許多的內(nèi)容,閑言少敘,還是讓我們開始吧。

             

            Makefile 介紹
            ———————

            make命令執(zhí)行時,需要一個 Makefile 文件,以告訴make命令需要怎么樣的去編譯和鏈接程序。

            首先,我們用一個示例來說明Makefile的書寫規(guī)則。以便給大家一個感興認(rèn)識。這個示例來源于GNU的make使用手冊,在這個示例中,我們 的工程有8個C文件,和3個頭文件,我們要寫一個Makefile來告訴make命令如何編譯和鏈接這幾個文件。我們的規(guī)則是:
                1)如果這個工程沒有編譯過,那么我們的所有C文件都要編譯并被鏈接。
                2)如果這個工程的某幾個C文件被修改,那么我們只編譯被修改的C文件,并鏈接目標(biāo)程序。
                3)如果這個工程的頭文件被改變了,那么我們需要編譯引用了這幾個頭文件的C文件,并鏈接目標(biāo)程序。

            只要我們的Makefile寫得夠好,所有的這一切,我們只用一個make命令就可以完成,make命令會自動智能地根據(jù)當(dāng)前的文件修改的情況來 確定哪些文件需要重編譯,從而自己編譯所需要的文件和鏈接目標(biāo)程序。


            一、Makefile的規(guī)則

            在講述這個Makefile之前,還是讓我們先來粗略地看一看Makefile的規(guī)則。

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

                target也就是一個目標(biāo)文件,可以是Object File,也可以是執(zhí)行文件。還可以是一個標(biāo)簽(Label),對于標(biāo)簽這種特 性,在后續(xù)的“偽目標(biāo)”章節(jié)中會有敘述。

                prerequisites就是,要生成那個target所需要的文件或是目標(biāo)。

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

            這是一個文件的依賴關(guān)系,也就是說,target這一個或多個的目標(biāo)文件依賴于prerequisites中的文件,其生成規(guī)則定義在 command中。說白一點(diǎn)就是說,prerequisites中如果有一個以上的文件比target文件要新的話,command所定義的命令就會被執(zhí) 行。這就是Makefile的規(guī)則。也就是Makefile中最核心的內(nèi)容。

            說到底,Makefile的東西就是這樣一點(diǎn),好像我的這篇文檔也該結(jié)束了。呵呵。還不盡然,這是Makefile的主線和核心,但要寫好一個 Makefile還不夠,我會以后面一點(diǎn)一點(diǎn)地結(jié)合我的工作經(jīng)驗給你慢慢到來。內(nèi)容還多著呢。:)


            二、一個示例

            正如前面所說的,如果一個工程有3個頭文件,和8個C文件,我們?yōu)榱送瓿汕懊嫠龅哪侨齻€規(guī)則,我們的Makefile應(yīng)該是下面的這個樣子的。

                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的易讀。我們可以把這個內(nèi)容保存在文件為“Makefile”或“makefile” 的文件中,然后在該目錄下直接輸入命令“make”就可以生成執(zhí)行文件edit。如果要刪除執(zhí)行文件和所有的中間目標(biāo)文件,那么,只要簡單地執(zhí)行一下 “make clean”就可以了。

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

            在定義好依賴關(guān)系后,后續(xù)的那一行定義了如何生成目標(biāo)文件的操作系統(tǒng)命令,一定要以一個Tab鍵作為開頭。記住,make并不管命令是怎么工作 的,他只管執(zhí)行所定義的命令。make會比較targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期 要比targets文件的日期要新,或者target不存在的話,那么,make就會執(zhí)行后續(xù)定義的命令。

            這里要說明一點(diǎn)的是,clean不是一個文件,它只不過是一個動作名字,有點(diǎn)像C語言中的lable一樣,其冒號后什么也沒有,那么,make就 不會自動去找文件的依賴性,也就不會自動執(zhí)行其后所定義的命令。要執(zhí)行其后的命令,就要在make命令后明顯得指出這個lable的名字。這樣的方法非常 有用,我們可以在一個makefile中定義不用的編譯或是和編譯無關(guān)的命令,比如程序的打包,程序的備份,等等。
            三、make是如何工作的

            在默認(rèn)的方式下,也就是我們只輸入make命令。那么,

                1、make會在當(dāng)前目錄下找名字叫“Makefile”或“makefile”的文件。
                2、如果找到,它會找文件中的第一個目標(biāo)文件(target),在上面的例子中,他會找到“edit”這個文件,并把這個文件作為最終的 目標(biāo)文件。
                3、如果edit文件不存在,或是edit所依賴的后面的 .o 文件的文件修改時間要比edit這個文件新,那么,他就會執(zhí)行后面所定 義的命令來生成edit這個文件。
                4、如果edit所依賴的.o文件也存在,那么make會在當(dāng)前文件中找目標(biāo)為.o文件的依賴性,如果找到則再根據(jù)那一個規(guī)則生成.o文 件。(這有點(diǎn)像一個堆棧的過程)
                5、當(dāng)然,你的C文件和H文件是存在的啦,于是make會生成 .o 文件,然后再用 .o 文件生命make的終極任務(wù),也就是執(zhí)行文 件edit了。

            這就是整個make的依賴性,make會一層又一層地去找文件的依賴關(guān)系,直到最終編譯出第一個目標(biāo)文件。在找尋的過程中,如果出現(xiàn)錯誤,比如最 后被依賴的文件找不到,那么make就會直接退出,并報錯,而對于所定義的命令的錯誤,或是編譯不成功,make根本不理。make只管文件的依賴性, 即,如果在我找了依賴關(guān)系之后,冒號后面的文件還是不在,那么對不起,我就不工作啦。

            通過上述分析,我們知道,像clean這種,沒有被第一個目標(biāo)文件直接或間接關(guān)聯(lián),那么它后面所定義的命令將不會被自動執(zhí)行,不過,我們可以顯示 要make執(zhí)行。即命令——“make clean”,以此來清除所有的目標(biāo)文件,以便重編譯。

            于是在我們編程中,如果這個工程已被編譯過了,當(dāng)我們修改了其中一個源文件,比如file.c,那么根據(jù)我們的依賴性,我們的目標(biāo)file.o會 被重編譯(也就是在這個依性關(guān)系后面所定義的命令),于是file.o的文件也是最新的啦,于是file.o的文件修改時間要比edit要新,所以 edit也會被重新鏈接了(詳見edit目標(biāo)文件后定義的命令)。

            而如果我們改變了“command.h”,那么,kdb.o、command.o和files.o都會被重編譯,并且,edit會被重鏈接。


            四、makefile中使用變量

            在上面的例子中,先讓我們看看edit的規(guī)則:

                  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]文件的字符串被重復(fù)了兩次,如果我們的工程需要加入一個新的[.o]文件,那么我們需要在兩個地方加(應(yīng)該是三個地方,還有 一個地方在clean中)。當(dāng)然,我們的makefile并不復(fù)雜,所以在兩個地方加也不累,但如果makefile變得復(fù)雜,那么我們就有可能會忘掉一 個需要加入的地方,而導(dǎo)致編譯失敗。所以,為了makefile的易維護(hù),在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 變量就可以了。

            關(guān)于變量更多的話題,我會在后續(xù)給你一一道來。


            五、讓make自動推導(dǎo)

            GNU的make很強(qiáng)大,它可以自動推導(dǎo)文件以及文件依賴關(guān)系后面的命令,于是我們就沒必要去在每一個[.o]文件后都寫上類似的命令,因為,我 們的make會自動識別,并自己推導(dǎo)命令。

            只要make看到一個[.o]文件,它就會自動的把[.c]文件加在依賴關(guān)系中,如果make找到一個whatever.o,那么 whatever.c,就會是whatever.o的依賴文件。并且 cc -c whatever.c 也會被推導(dǎo)出來,于是,我們的makefile 再也不用寫得這么復(fù)雜。我們的是新的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的“隱晦規(guī)則”。上面文件內(nèi)容中,“.PHONY”表示,clean是個偽目標(biāo)文件。

            關(guān)于更為詳細(xì)的“隱晦規(guī)則”和“偽目標(biāo)文件”,我會在后續(xù)給你一一道來。


            六、另類風(fēng)格的makefile

            即然我們的make可以自動推導(dǎo)命令,那么我看到那堆[.o]和[.h]的依賴就有點(diǎn)不爽,那么多的重復(fù)的[.h],能不能把其收攏起來,好吧, 沒有問題,這個對于make來說很容易,誰叫它提供了自動推導(dǎo)命令和文件的功能呢?來看看最新風(fēng)格的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)

            這種風(fēng)格,讓我們的makefile變得很簡單,但我們的文件依賴關(guān)系就顯得有點(diǎn)凌亂了。魚和熊掌不可兼得。還看你的喜好了。我是不喜歡這種風(fēng)格 的,一是文件的依賴關(guān)系看不清楚,二是如果文件一多,要加入幾個新的.o文件,那就理不清楚了。


            七、清空目標(biāo)文件的規(guī)則

            每個Makefile中都應(yīng)該寫一個清空目標(biāo)文件(.o和執(zhí)行文件)的規(guī)則,這不僅便于重編譯,也很利于保持文件的清潔。這是一個“修養(yǎng)”(呵 呵,還記得我的《編程修養(yǎng)》嗎)。一般的風(fēng)格都是:

                    clean:
                        rm edit $(objects)

            更為穩(wěn)健的做法是:

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

            前面說過,.PHONY意思表示clean是一個“偽目標(biāo)”,。而在rm命令前面加了一個小減號的意思就是,也許某些文件出現(xiàn)問題,但不要管,繼 續(xù)做后面的事。當(dāng)然,clean的規(guī)則不要放在文件的開頭,不然,這就會變成make的默認(rèn)目標(biāo),相信誰也不愿意這樣。不成文的規(guī)矩是——“clean從 來都是放在文件的最后”。


            上面就是一個makefile的概貌,也是makefile的基礎(chǔ),下面還有很多makefile的相關(guān)細(xì)節(jié),準(zhǔn)備好了嗎?準(zhǔn)備好了就來。


            一、Makefile里有什么?

            Makefile里主要包含了五個東西:顯式規(guī)則、隱晦規(guī)則、變量定義、文件指示和注釋。

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

            2、隱晦規(guī)則。由于我們的make有自動推導(dǎo)的功能,所以隱晦的規(guī)則可以讓我們比較粗糙地簡略地書寫Makefile,這是由make所支持的。

            3、變量的定義。在Makefile中我們要定義一系列的變量,變量一般都是字符串,這個有點(diǎn)你C語言中的宏,當(dāng)Makefile被執(zhí)行時,其中 的變量都會被擴(kuò)展到相應(yīng)的引用位置上。

            4、文件指示。其包括了三個部分,一個是在一個Makefile中引用另一個Makefile,就像C語言中的include一樣;另一個是指根 據(jù)某些情況指定Makefile中的有效部分,就像C語言中的預(yù)編譯#if一樣;還有就是定義一個多行的命令。有關(guān)這一部分的內(nèi)容,我會在后續(xù)的部分中講 述。

            5、注釋。Makefile中只有行注釋,和UNIX的Shell腳本一樣,其注釋是用“#”字符,這個就像C/C++中的“//”一樣。如果你 要在你的Makefile中使用“#”字符,可以用反斜框進(jìn)行轉(zhuǎn)義,如:“\#”。

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


            二、Makefile的文件名
            默認(rèn)的情況下,make命令會在當(dāng)前目錄下按順序找尋文件名為“GNUmakefile”、“makefile”、“Makefile”的文件, 找到了解釋這個文件。在這三個文件名中,最好使用“Makefile”這個文件名,因為,這個文件名第一個字符為大寫,這樣有一種顯目的感覺。最好不要用 “GNUmakefile”,這個文件是GNU的make識別的。有另外一些make只對全小寫的“makefile”文件名敏感,但是基本上來說,大多 數(shù)的make都支持“makefile”和“Makefile”這兩種默認(rèn)文件名。

            當(dāng)然,你可以使用別的文件名來書寫Makefile,比如:“Make.Linux”,“Make.Solaris”,“Make.AIX”等, 如果要指定特定的Makefile,你可以使用make的“-f”和“--file”參數(shù),如:make -f Make.Linux或 make --file Make.AIX。

            三、引用其它的Makefile

            在Makefile使用include關(guān)鍵字可以把別的Makefile包含進(jìn)來,這很像C語言的#include,被包含的文件會原模原樣的放 在當(dāng)前文件的包含位置。include的語法是:

                include <filename>;

                filename可以是當(dāng)前操作系統(tǒng)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,并把其內(nèi)容安置在當(dāng)前的位置。就好像C/C++的#include指 令一樣。如果文件都沒有指定絕對路徑或是相對路徑的話,make會在當(dāng)前目錄下首先尋找,如果當(dāng)前目錄下沒有找到,那么,make還會在下面的幾個目錄下 找:

                1、如果make執(zhí)行時,有“-I”或“--include-dir”參數(shù),那么make就會在這個參數(shù)所指定的目錄下去尋找。
                2、如果目錄<prefix>;/include(一般是:/usr/local/bin或/usr/include)存在 的話,make也會去找。

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

                -include <filename>;
                其表示,無論include過程中出現(xiàn)什么錯誤,都不要報錯繼續(xù)執(zhí)行。和其它版本make兼容的相關(guān)命令是sinclude,其作用和這 一個是一樣的。


            四、環(huán)境變量 MAKEFILES 

            如果你的當(dāng)前環(huán)境中定義了環(huán)境變量MAKEFILES,那么,make會把這個變量中的值做一個類似于include的動作。這個變量中的值是其 它的Makefile,用空格分隔。只是,它和include不同的是,從這個環(huán)境變中引入的Makefile的“目標(biāo)”不會起作用,如果環(huán)境變量中定義 的文件發(fā)現(xiàn)錯誤,make也會不理。

            但是在這里我還是建議不要使用這個環(huán)境變量,因為只要這個變量一被定義,那么當(dāng)你使用make時,所有的Makefile都會受到它的影響,這絕 不是你想看到的。在這里提這個事,只是為了告訴大家,也許有時候你的Makefile出現(xiàn)了怪事,那么你可以看看當(dāng)前環(huán)境中有沒有定義這個變量。


            五、make的工作方式

            GNU的make工作時的執(zhí)行步驟入下:(想來其它的make也是類似)

                1、讀入所有的Makefile。
                2、讀入被include的其它Makefile。
                3、初始化文件中的變量。
                4、推導(dǎo)隱晦規(guī)則,并分析所有規(guī)則。
                5、為所有的目標(biāo)文件創(chuàng)建依賴關(guān)系鏈。
                6、根據(jù)依賴關(guān)系,決定哪些目標(biāo)要重新生成。
                7、執(zhí)行生成命令。


            1-5步為第一個階段,6-7為第二個階段。第一個階段中,如果定義的變量被使用了,那么,make會把其展開在使用的位置。但make并不會完全馬上展開,make使用的是拖延戰(zhàn)術(shù),如果變量出現(xiàn)在依賴關(guān)系的規(guī)則中,那么僅當(dāng)這條依賴被決定要使用了,變量才會在其內(nèi)部展開。

            當(dāng)然,這個工作方式你不一定要清楚,但是知道這個方式你也會對make更為熟悉。有了這個基礎(chǔ),后續(xù)部分也就容易看懂了。

            書寫規(guī)則
            ————

            規(guī)則包含兩個部分,一個是依賴關(guān)系,一個是生成目標(biāo)的方法。

            在Makefile中,規(guī)則的順序是很重要的,因為,Makefile中只應(yīng)該有一個最終目標(biāo),其它的目標(biāo)都是被這個目標(biāo)所連帶出來的,所以一定 要讓make知道你的最終目標(biāo)是什么。一般來說,定義在Makefile中的目標(biāo)可能會有很多,但是第一條規(guī)則中的目標(biāo)將被確立為最終的目標(biāo)。如果第一條 規(guī)則中的目標(biāo)有很多個,那么,第一個目標(biāo)會成為最終的目標(biāo)。make所完成的也就是這個目標(biāo)。

            好了,還是讓我們來看一看如何書寫規(guī)則。


            一、規(guī)則舉例

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

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

                1、文件的依賴關(guān)系,foo.o依賴于foo.c和defs.h的文件,如果foo.c和defs.h的文件日期要比foo.o文件日期 要新,或是foo.o不存在,那么依賴關(guān)系發(fā)生。
                2、如果生成(或更新)foo.o文件。也就是那個cc命令,其說明了,如何生成foo.o這個文件。(當(dāng)然foo.c文件 include了defs.h文件)


            二、規(guī)則的語法

                  targets : prerequisites
                    command
                    ...

                  或是這樣: 

                  targets : prerequisites ; command
                        command
                        ...

            targets是文件名,以空格分開,可以使用通配符。一般來說,我們的目標(biāo)基本上是一個文件,但也有可能是多個文件。

            command是命令行,如果其不與“target:prerequisites”在一行,那么,必須以[Tab鍵]開頭,如果和 prerequisites在一行,那么可以用分號做為分隔。(見上)

            prerequisites也就是目標(biāo)所依賴的文件(或依賴目標(biāo))。如果其中的某個文件要比目標(biāo)文件要新,那么,目標(biāo)就被認(rèn)為是“過時的”,被認(rèn) 為是需要重生成的。這個在前面已經(jīng)講過了。

            如果命令太長,你可以使用反斜框(‘\’)作為換行符。make對一行上有多少個字符沒有限制。規(guī)則告訴make兩件事,文件的依賴關(guān)系和如何成 成目標(biāo)文件。

            一般來說,make會以UNIX的標(biāo)準(zhǔn)Shell,也就是/bin/sh來執(zhí)行命令。


            三、在規(guī)則中使用通配符

            如果我們想定義一系列比較類似的文件,我們很自然地就想起使用通配符。make支持三各通配符:“*”,“?”和“[...]”。這是和Unix 的B-Shell是相同的。

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

            通配符代替了你一系列的文件,如“*.c”表示所以后綴為c的文件。一個需要我們注意的是,如果我們的文件名中有通配符,如:“*”,那么可以用 轉(zhuǎn)義字符“\”,如“\*”來表示真實的“*”字符,而不是任意長度的字符串。

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

                clean:
                     rm -f *.o

                上面這個例子我不不多說了,這是操作系統(tǒng)Shell所支持的通配符。這是在命令中的通配符。

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

                上面這個例子說明了通配符也可以在我們的規(guī)則中,目標(biāo)print依賴于所有的[.c]文件。其中的“$?”是一個自動化變量,我會在后面 給你講述。

                objects = *.o

                上面這個例子,表示了,通符同樣可以用在變量中。并不是說[*.o]會展開,不!objects的值就是“*.o”。Makefile中 的變量其實就是C/C++中的宏。如果你要讓通配符在變量中展開,也就是讓objects的值是所有[.o]的文件名的集合,那么,你可以這樣:

                objects := $(wildcard *.o)

            這種用法由關(guān)鍵字“wildcard”指出,關(guān)于Makefile的關(guān)鍵字,我們將在后面討論。


            四、文件搜尋

            在一些大的工程中,有大量的源文件,我們通常的做法是把這許多的源文件分類,并存放在不同的目錄中。所以,當(dāng)make需要去找尋文件的依賴關(guān)系 時,你可以在文件前加上路徑,但最好的方法是把一個路徑告訴make,讓make在自動去找。

            Makefile文件中的特殊變量“VPATH”就是完成這個功能的,如果沒有指明這個變量,make只會在當(dāng)前的目錄中去找尋依賴文件和目標(biāo)文 件。如果定義了這個變量,那么,make就會在當(dāng)當(dāng)前目錄找不到的情況下,到所指定的目錄中去找尋文件了。

                VPATH = src:../headers

            上面的的定義指定兩個目錄,“src”和“../headers”make會按照這個順序進(jìn)行搜索。目錄由“冒號”分隔(當(dāng)然,當(dāng)前目錄永遠(yuǎn) 是最高優(yōu)先搜索的地方)

            另一個設(shè)置文件搜索路徑的方法是使用make的“vpath”關(guān)鍵字(注意,它是全小寫的),這不是變量,這是一個make的關(guān)鍵字,這和上面提 到的那個VPATH變量很類似,但是它更為靈活。它可以指定不同的文件在不同的搜索目錄中。這是一個很靈活的功能。它的使用方法有三種:

                1、vpath <pattern>; <directories>;

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

                2、vpath <pattern>;

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

                3、vpath

                清除所有已被設(shè)置好了的文件搜索目錄。

            vapth使用方法中的<pattern>;需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以 “.h”結(jié)尾的文件。<pattern>;指定了要搜索的文件集,而<directories>;則指定 了<pattern>;的文件集的搜索的目錄。例如:

                vpath %.h ../headers

            該語句表示,要求make在“../headers”目錄下搜索所有以“.h”結(jié)尾的文件。(如果某文件在當(dāng)前目錄沒有找到的話)

            我們可以連續(xù)地使用vpath語句,以指定不同搜索策略。如果連續(xù)的vpath語句中出現(xiàn)了相同的<pattern>;,或是被重復(fù) 了的<pattern>;,那么,make會按照vpath語句的先后順序來執(zhí)行搜索。如:

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

            其表示“.c”結(jié)尾的文件,先在“foo”目錄,然后是“blish”,最后是“bar”目錄。

                vpath %.c foo:bar
                vpath %   blish

            而上面的語句則表示“.c”結(jié)尾的文件,先在“foo”目錄,然后是“bar”目錄,最后才是“blish”目錄。


            五、偽目標(biāo)

            最早先的一個例子中,我們提到過一個“clean”的目標(biāo),這是一個“偽目標(biāo)”,

                clean:
                        rm *.o temp

            正像我們前面例子中的“clean”一樣,即然我們生成了許多文件編譯文件,我們也應(yīng)該提供一個清除它們的“目標(biāo)”以備完整地重編譯而用。 (以 “make clean”來使用該目標(biāo))

            因為,我們并不生成“clean”這個文件。“偽目標(biāo)”并不是一個文件,只是一個標(biāo)簽,由于“偽目標(biāo)”不是文件,所以make無法生成它的依賴關(guān) 系和決定它是否要執(zhí)行。我們只有通過顯示地指明這個“目標(biāo)”才能讓其生效。當(dāng)然,“偽目標(biāo)”的取名不能和文件名重名,不然其就失去了“偽目標(biāo)”的意義了。

            當(dāng)然,為了避免和文件重名的這種情況,我們可以使用一個特殊的標(biāo)記“.PHONY”來顯示地指明一個目標(biāo)是“偽目標(biāo)”,向make說明,不管是否 有這個文件,這個目標(biāo)就是“偽目標(biāo)”。

                .PHONY : clean

            只要有這個聲明,不管是否有“clean”文件,要運(yùn)行“clean”這個目標(biāo),只有“make clean”這樣。于是整個過程可以這樣寫:

                 .PHONY: clean
                clean:
                        rm *.o temp

            偽目標(biāo)一般沒有依賴的文件。但是,我們也可以為偽目標(biāo)指定所依賴的文件。偽目標(biāo)同樣可以作為“默認(rèn)目標(biāo)”,只要將其放在第一個。一個示例就是,如 果你的Makefile需要一口氣生成若干個可執(zhí)行文件,但你只想簡單地敲一個make完事,并且,所有的目標(biāo)文件都寫在一個Makefile中,那么你 可以使用“偽目標(biāo)”這個特性:

                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中的第一個目標(biāo)會被作為其默認(rèn)目標(biāo)。我們聲明了一個“all”的偽目標(biāo),其依賴于其它三個目標(biāo)。由于偽目標(biāo)的特性是, 總是被執(zhí)行的,所以其依賴的那三個目標(biāo)就總是不如“all”這個目標(biāo)新。所以,其它三個目標(biāo)的規(guī)則總是會被決議。也就達(dá)到了我們一口氣生成多個目標(biāo)的目 的。“.PHONY : all”聲明了“all”這個目標(biāo)為“偽目標(biāo)”。

            隨便提一句,從上面的例子我們可以看出,目標(biāo)也可以成為依賴。所以,偽目標(biāo)同樣也可成為依賴。看下面的例子:

                .PHONY: cleanall cleanobj cleandiff

                cleanall : cleanobj cleandiff
                        rm program

                cleanobj :
                        rm *.o

                cleandiff :
                        rm *.diff

            “make clean”將清除所有要被清除的文件。“cleanobj”和“cleandiff”這兩個偽目標(biāo)有點(diǎn)像“子程序”的意思。我們可 以輸入“make cleanall”和“make cleanobj”和“make cleandiff”命令來達(dá)到清除不同種類文件的目的。



            六、多目標(biāo)

            Makefile的規(guī)則中的目標(biāo)可以不止一個,其支持多目標(biāo),有可能我們的多個目標(biāo)同時依賴于一個文件,并且其生成的命令大體類似。于是我們就能 把其合并起來。當(dāng)然,多個目標(biāo)的生成規(guī)則的執(zhí)行命令是同一個,這可能會可我們帶來麻煩,不過好在我們的可以使用一個自動化變量“$@”(關(guān)于自動化變量, 將在后面講述),這個變量表示著目前規(guī)則中所有的目標(biāo)的集合,這樣說可能很抽象,還是看一個例子吧。

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

                上述規(guī)則等價于:

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

                其中,-$(subst output,,$@)中的“$”表示執(zhí)行一個Makefile的函數(shù),函數(shù)名為subst,后面的為參數(shù)。關(guān) 于函數(shù),將在后面講述。這里的這個函數(shù)是截取字符串的意思,“$@”表示目標(biāo)的集合,就像一個數(shù)組,“$@”依次取出目標(biāo),并執(zhí)于命令。


            七、靜態(tài)模式

            靜態(tài)模式可以更加容易地定義多目標(biāo)的規(guī)則,可以讓我們的規(guī)則變得更加的有彈性和靈活。我們還是先來看一下語法:

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


                targets定義了一系列的目標(biāo)文件,可以有通配符。是目標(biāo)的一個集合。

                target-parrtern是指明了targets的模式,也就是的目標(biāo)集模式。

                prereq-parrterns是目標(biāo)的依賴模式,它對target-parrtern形成的模式再進(jìn)行一次依賴目標(biāo)的定義。

            這樣描述這三個東西,可能還是沒有說清楚,還是舉個例子來說明一下吧。如果我們的<target-parrtern>;定義成 “%.o”,意思是我們的<target>;集合中都是以“.o”結(jié)尾的,而如果我們的<prereq-parrterns>; 定義成“%.c”,意思是對<target-parrtern>;所形成的目標(biāo)集進(jìn)行二次定義,其計算方法是,取<target- parrtern>;模式中的“%”(也就是去掉了[.o]這個結(jié)尾),并為其加上[.c]這個結(jié)尾,形成的新集合。

            所以,我們的“目標(biāo)模式”或是“依賴模式”中都應(yīng)該有“%”這個字符,如果你的文件名中有“%”那么你可以使用反斜杠“\”進(jìn)行轉(zhuǎn)義,來標(biāo)明真實 的“%”字符。

            看一個例子:

                objects = foo.o bar.o

                all: $(objects)

                $(objects): %.o: %.c
                        $(CC) -c $(CFLAGS) $< -o $@
            上面這幾行怎么寫的這么亂啊,暫時沒看出頭緒來,又是.o 又是.c的,又是-c又是-o的。過幾天要再看看這里,寫一個這個東西實踐一下。
            實踐了一下,上面的模式可以實現(xiàn),但是只能生成.o ,在想是不是有方法可以直接生成可執(zhí)行文件呢.下面這個就可以了:
             $(OBJC): %: %.c
                 $(CC) -ggdb -Wall -o $@ $<

            上面的例子中,指明了我們的目標(biāo)從$object中獲取,“%.o”表明要所有以“.o”結(jié)尾的目標(biāo),也就是“foo.o bar.o”,也就是 變量$object集合的模式,而依賴模式“%.c”則取模式“%.o”的“%”,也就是“foo bar”,并為其加下“.c”的后綴,于是,我們的依 賴目標(biāo)就是“foo.c bar.c”。而命令中的“$<”和“$@”則是自動化變量,“$<”表示所有的依賴目標(biāo)集(也就是 “foo.c bar.c”),“$@”表示目標(biāo)集(也就是“foo.o bar.o”)。于是,上面的規(guī)則展開后等價于下面的規(guī)則:

                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”有幾百個,那種我們只要用這種很簡單的“靜態(tài)模式規(guī)則”就可以寫完一堆規(guī)則,實在是太有效率了。“靜態(tài)模式規(guī)則”的用 法很靈活,如果用得好,那會一個很強(qiáng)大的功能。再看一個例子:


                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))表示調(diào)用Makefile的filter函數(shù),過濾“$filter”集,只要其中模式為 “%.o”的內(nèi)容。其的它內(nèi)容,我就不用多說了吧。這個例字展示了Makefile中更大的彈性。


            八、自動生成依賴性

            在Makefile中,我們的依賴關(guān)系可能會需要包含一系列的頭文件,比如,如果我們的main.c中有一句 “#include "defs.h"”,那么我們的依賴關(guān)系應(yīng)該是:

                main.o : main.c defs.h

            但是,如果是一個比較大型的工程,你必需清楚哪些C文件包含了哪些頭文件,并且,你在加入或刪除頭文件時,也需要小心地修改Makefile,這 是一個很沒有維護(hù)性的工作。為了避免這種繁重而又容易出錯的事情,我們可以使用C/C++編譯的一個功能。大多數(shù)的C/C++編譯器都支持一個“-M”的 選項,即自動找尋源文件中包含的頭文件,并生成一個依賴關(guān)系。例如,如果我們執(zhí)行下面的命令:

                cc -M main.c

            其輸出是:

                main.o : main.c defs.h

            于是由編譯器自動生成的依賴關(guān)系,這樣一來,你就不必再手動書寫若干文件的依賴關(guān)系,而由編譯器自動生成了。需要提醒一句的是,如果你使用GNU 的C/C++編譯器,你得用“-MM”參數(shù),不然,“-M”參數(shù)會把一些標(biāo)準(zhǔn)庫的頭文件也包含進(jìn)來。

                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聯(lián)系在一起呢。因為這樣一來,我們的Makefile也要根據(jù)這些源文件重新生成,讓 Makefile自已依賴于源文件?這個功能并不現(xiàn)實,不過我們可以有其它手段來迂回地實現(xiàn)這一功能。GNU組織建議把編譯器為每一個源文件的自動生成的 依賴關(guān)系放到一個文件中,為每一個“name.c”的文件都生成一個“name.d”的Makefile文件,[.d]文件中就存放對應(yīng)[.c]文件的依 賴關(guān)系。

            于是,我們可以寫出[.c]文件和[.d]文件的依賴關(guān)系,并讓make自動更新或自成[.d]文件,并把其包含在我們的主Makefile中, 這樣,我們就可以自動化地生成每個文件的依賴關(guān)系了。

            這里,我們給出了一個模式規(guī)則來產(chǎn)生[.d]文件:
            ///記得以后要來再溫習(xí)下,不是太清晰感覺,我為什么不現(xiàn)實??????
                %.d: %.c
                        @set -e; rm -f $@; \
                         $(CC) -M $(CPPFLAGS) $< >; $@.$$$$; \
                         sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ >; $@; \
                         rm -f $@.$$$$


            這個規(guī)則的意思是,所有的[.d]文件依賴于[.c]文件,“rm -f $@”的意思是刪除所有的目標(biāo),也就是[.d]文件,第二行的意思是, 為每個依賴文件“$<”,也就是[.c]文件生成依賴文件,“$@”表示模式“%.d”文件,如果有一個C文件是name.c,那么“%”就是 “name”,“$$$$”意為一個隨機(jī)編號,第二行生成的文件有可能是“name.d.12345”,第三行使用sed命令做了一個替換,關(guān)于sed命 令的用法請參看相關(guān)的使用文檔。第四行就是刪除臨時文件。

            總而言之,這個模式要做的事就是在編譯器生成的依賴關(guān)系中加入[.d]文件的依賴,即把依賴關(guān)系:

                main.o : main.c defs.h

            轉(zhuǎn)成:

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

            于是,我們的[.d]文件也會自動更新了,并會自動生成了,當(dāng)然,你還可以在這個[.d]文件中加入的不只是依賴關(guān)系,包括生成的命令也可一并加 入,讓每個[.d]文件都包含一個完賴的規(guī)則。一旦我們完成這個工作,接下來,我們就要把這些自動生成的規(guī)則放進(jìn)我們的主Makefile中。我們可以使 用Makefile的“include”命令,來引入別的Makefile文件(前面講過),例如:

                sources = foo.c bar.c

                include $(sources:.c=.d)

            上述語句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一個替換,把變量$(sources)所有[.c]的字串都替換 成[.d],關(guān)于這個“替換”的內(nèi)容,在后面我會有更為詳細(xì)的講述。當(dāng)然,你得注意次序,因為include是按次來載入文件,最先載入的[.d]文件中 的目標(biāo)會成為默認(rèn)目標(biāo)。

            書寫命令
            ————

            每條規(guī)則中的命令和操作系統(tǒng)Shell的命令行是一致的。make會一按順序一條一條的執(zhí)行命令,每條命令的開頭必須以[Tab]鍵開頭,除非, 命令是緊跟在依賴規(guī)則后面的分號后的。在命令行之間中的空格或是空行會被忽略,但是如果該空格或空行是以Tab鍵開頭的,那么make會認(rèn)為其是一個空命 令。

            我們在UNIX下可能會使用不同的Shell,但是make的命令默認(rèn)是被“/bin/sh”——UNIX的標(biāo)準(zhǔn)Shell解釋執(zhí)行的。除非你特 別指定一個其它的Shell。Makefile中,“#”是注釋符,很像C/C++中的“//”,其后的本行字符都被注釋。

            一、顯示命令

            通常,make會把其要執(zhí)行的命令行在命令執(zhí)行前輸出到屏幕上。當(dāng)我們用“@”字符在命令行前,那么,這個命令將不被make顯示出來,最具代表 性的例子是,我們用這個功能來像屏幕顯示一些信息。如:

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

            當(dāng)make執(zhí)行時,會輸出“正在編譯XXX模塊......”字串,但不會輸出命令,如果沒有“@”,那么,make將輸出:

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

            如果make執(zhí)行時,帶入make參數(shù)“-n”或“--just-print”,那么其只是顯示命令,但不會執(zhí)行命令,這個功能很有利于我們調(diào)試 我們的Makefile,看看我們書寫的命令是執(zhí)行起來是什么樣子的或是什么順序的。

            而make參數(shù)“-s”或“--slient”則是全面禁止命令的顯示。

             

            二、命令執(zhí)行

            當(dāng)依賴目標(biāo)新于目標(biāo)時,也就是當(dāng)規(guī)則的目標(biāo)需要被更新時,make會一條一條的執(zhí)行其后的命令。需要注意的是,如果你要讓上一條命令的結(jié)果應(yīng)用在 下一條命令時,你應(yīng)該使用分號分隔這兩條命令。比如你的第一條命令是cd命令,你希望第二條命令得在cd之后的基礎(chǔ)上運(yùn)行,那么你就不能把這兩條命令寫在 兩行上,而應(yīng)該把這兩條命令寫在一行上,用分號分隔。如:

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

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

            當(dāng)我們執(zhí)行“make exec”時,第一個例子中的cd沒有作用,pwd會打印出當(dāng)前的Makefile目錄,而第二個例子中,cd就起作用 了,pwd會打印出“/home/hchen”。

            make一般是使用環(huán)境變量SHELL中所定義的系統(tǒng)Shell來執(zhí)行命令,默認(rèn)情況下使用UNIX的標(biāo)準(zhǔn)Shell——/bin/sh來執(zhí)行命令。但在MS-DOS下有點(diǎn)特殊,因為MS-DOS下沒有SHELL環(huán)境變量,當(dāng)然你也可以指定。如果你指定了UNIX風(fēng)格的目錄形式,首先,make會 在SHELL所指定的路徑中找尋命令解釋器,如果找不到,其會在當(dāng)前盤符中的當(dāng)前目錄中尋找,如果再找不到,其會在PATH環(huán)境變量中所定義的所有路徑中 尋找。MS-DOS中,如果你定義的命令解釋器沒有找到,其會給你的命令解釋器加上諸如“.exe”、“.com”、“.bat”、“.sh”等后綴。



            三、命令出錯

            每當(dāng)命令運(yùn)行完后,make會檢測每個命令的返回碼,如果命令返回成功,那么make會執(zhí)行下一條命令,當(dāng)規(guī)則中所有的命令成功返回后,這個規(guī)則 就算是成功完成了。如果一個規(guī)則中的某個命令出錯了(命令退出碼非零),那么make就會終止執(zhí)行當(dāng)前規(guī)則,這將有可能終止所有規(guī)則的執(zhí)行。

            有些時候,命令的出錯并不表示就是錯誤的。例如mkdir命令,我們一定需要建立一個目錄,如果目錄不存在,那么mkdir就成功執(zhí)行,萬事大 吉,如果目錄存在,那么就出錯了。我們之所以使用mkdir的意思就是一定要有這樣的一個目錄,于是我們就不希望mkdir出錯而終止規(guī)則的運(yùn)行。

            為了做到這一點(diǎn),忽略命令的出錯,我們可以在Makefile的命令行前加一個減號“-”(在Tab鍵之后),標(biāo)記為不管命令出不出錯都認(rèn)為是成 功的。如:

               clean:
                        -rm -f *.o

            還有一個全局的辦法是,給make加上“-i”或是“--ignore-errors”參數(shù),那么,Makefile中所有命令都會忽略錯誤。而 如果一個規(guī)則是以“.IGNORE”作為目標(biāo)的,那么這個規(guī)則中的所有命令將會忽略錯誤。這些是不同級別的防止命令出錯的方法,你可以根據(jù)你的不同喜歡設(shè) 置。

            還有一個要提一下的make的參數(shù)的是“-k”或是“--keep-going”,這個參數(shù)的意思是,如果某規(guī)則中的命令出錯了,那么就終目該規(guī) 則的執(zhí)行,但繼續(xù)執(zhí)行其它規(guī)則。



            四、嵌套執(zhí)行make

            在一些大的工程中,我們會把我們不同模塊或是不同功能的源文件放在不同的目錄中,我們可以在每個目錄中都書寫一個該目錄的Makefile,這有 利于讓我們的Makefile變得更加地簡潔,而不至于把所有的東西全部寫在一個Makefile中,這樣會很難維護(hù)我們的Makefile,這個技術(shù)對 于我們模塊編譯和分段編譯有著非常大的好處。

            例如,我們有一個子目錄叫subdir,這個目錄下有個Makefile文件,來指明了這個目錄下文件的編譯規(guī)則。那么我們總控的 Makefile可以這樣書寫:

                subsystem:
                        cd subdir && $(MAKE)

            其等價于:

                subsystem:
                        $(MAKE) -C subdir

            定義$(MAKE)宏變量的意思是,也許我們的make需要一些參數(shù),所以定義成一個變量比較利于維護(hù)。這兩個例子的意思都是先進(jìn)入 “subdir”目錄,然后執(zhí)行make命令。

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

            如果你要傳遞變量到下級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的參數(shù)信息,如果我們執(zhí)行“總控Makefile”時有make參數(shù)或是在上層 Makefile中定義了這個變量,那么MAKEFILES變量將會是這些參數(shù),并會傳遞到下層Makefile中,這是一個系統(tǒng)級的環(huán)境變量。

            但是make命令中的有幾個參數(shù)并不往下傳遞,它們是“-C”,“-f”,“-h”“-o”和“-W”(有關(guān)Makefile參數(shù)的細(xì)節(jié)將在后面 說明),如果你不想往下層傳遞參數(shù),那么,你可以這樣來:

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

            如果你定義了環(huán)境變量MAKEFLAGS,那么你得確信其中的選項是大家都會用到的,如果其中有“-t”,“-n”,和“-q”參數(shù),那么將會有 讓你意想不到的結(jié)果,或許會讓你異常地恐慌。

            還有一個在“嵌套執(zhí)行”中比較有用的參數(shù),“-w”或是“--print-directory”會在make的過程中輸出一些信息,讓你看到目前 的工作目錄。比如,如果我們的下級make目錄是“/home/hchen/gnu/make”,如果我們使用“make -w”來執(zhí)行,那么當(dāng)進(jìn)入該目 錄時,我們會看到:

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

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

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

            當(dāng)你使用“-C”參數(shù)來指定make下層Makefile時,“-w”會被自動打開的。如果參數(shù)中有“-s”(“--slient”)或是 “--no-print-directory”,那么,“-w”總是失效的。



            五、定義命令包

            如果Makefile中出現(xiàn)一些相同命令序列,那么我們可以為這些相同的命令序列定義一個變量。定義這種命令序列的語法以“define”開始, 以“endef”結(jié)束,如:

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

            這里,“run-yacc”是這個命令包的名字,其不要和Makefile中的變量重名。“define”和“endef”中的兩行就是命令序 列。這個命令包中的第一個命令是運(yùn)行Yacc程序,因為Yacc程序總是生成“y.tab.c”的文件,所以第二行的命令就是把這個文件改改名字。還是把 這個命令包放到一個示例中來看看吧。

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

            我們可以看見,要使用這個命令包,我們就好像使用變量一樣。在這個命令包的使用中,命令包“run-yacc”中的“$^”就是 “foo.y”,“$@”就是“foo.c”(有關(guān)這種以“$”開頭的特殊變量,我們會在后面介紹),make在執(zhí)行命令包時,命令包中的每個命令會被依 次獨(dú)立執(zhí)行。

            使用變量
            ————
            在Makefile中的定義的變量,就像是C/C++語言中的宏一樣,他代表了一個文本字串,在Makefile中執(zhí)行的時候其會自動原模原樣地 展開在所使用的地方。其與C/C++所不同的是,你可以在Makefile中改變其值。在Makefile中,變量可以使用在“目標(biāo)”,“依賴目標(biāo)”, “命令”或是Makefile的其它部分中。

            變量的命名字可以包含字符、數(shù)字,下劃線(可以是數(shù)字開頭),但不應(yīng)該含有“:”、“#”、“=”或是空字符(空格、回車等)。變量是大小寫敏感 的,“foo”、“Foo”和“FOO”是三個不同的變量名。傳統(tǒng)的Makefile的變量名是全大寫的命名方式,但我推薦使用大小寫搭配的變量名, 如:MakeFlags。這樣可以避免和系統(tǒng)的變量沖突,而發(fā)生意外的事情。

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

            一、變量的基礎(chǔ)

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

            變量可以使用在許多地方,如規(guī)則中的“目標(biāo)”、“依賴”、“命令”以及新的變量中。先看一個例子:

                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

            當(dāng)然,千萬不要在你的Makefile中這樣干,這里只是舉個例子來表明Makefile中的變量在使用處展開的真實樣子。可見其就是一個“替 代”的原理。

            另外,給變量加上括號完全是為了更加安全地使用這個變量,在上面的例子中,如果你不想給變量加上括號,那也可以,但我還是強(qiáng)烈建議你給變量加上括 號。


            二、變量中的變量

            在定義變量的值時,我們可以使用其它變量來構(gòu)造變量的值,在Makefile中有兩種方式來在用變量定義變量的值。

            先看第一種方式,也就是簡單的使用“=”號,在“=”左側(cè)是變量,右側(cè)是變量的值,右側(cè)變量的值可以定義在文件的任何一處,也就是說,右側(cè)中的變 量不一定非要是已定義好的值,其也可以使用后面定義的值。如:

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

                all:
                        echo $(foo)

            我們執(zhí)行“make all”將會打出變量$(foo)的值是“Huh?”( $(foo)的值是$(bar),$(bar)的值 是$(ugh),$(ugh)的值是“Huh?”)可見,變量是可以使用后面的變量來定義的。

            這個功能有好的地方,也有不好的地方,好的地方是,我們可以把變量的真實值推到后面來定義,如:

                CFLAGS = $(include_dirs) -O
                include_dirs = -Ifoo -Ibar

            當(dāng)“CFLAGS”在命令中被展開時,會是“-Ifoo -Ibar -O”。但這種形式也有不好的地方,那就是遞歸定義,如:

                CFLAGS = $(CFLAGS) -O

                或:

                A = $(B)
                B = $(A)

            這會讓make陷入無限的變量展開過程中去,當(dāng)然,我們的make是有能力檢測這樣的定義,并會報錯。還有就是如果在變量中使用函數(shù),那么,這種 方式會讓我們的make運(yùn)行時非常慢,更糟糕的是,他會使用得兩個make的函數(shù)“wildcard”和“shell”發(fā)生不可預(yù)知的錯誤。因為你不會知 道這兩個函數(shù)會被調(diào)用多少次。

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

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

            其等價于:

                y := foo bar
                x := later

            值得一提的是這種方法,前面的變量不能使用后面的變量,只能使用前面已定義好了的變量。如果是這樣:

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

            那么,y的值是“bar”,而不是“foo bar”。

            上面都是一些比較簡單的變量使用了,讓我們來看一個復(fù)雜的例子,其中包括了make的函數(shù)、條件表達(dá)式和一個系統(tǒng)變量“MAKELEVEL”的使 用:

                ifeq (0,${MAKELEVEL})
                cur-dir   := $(shell pwd)
                whoami    := $(shell whoami)
                host-type := $(shell arch)
                MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
                endif

            關(guān)于條件表達(dá)式和函數(shù),我們在后面再說,對于系統(tǒng)變量“MAKELEVEL”,其意思是,如果我們的make有一個嵌套執(zhí)行的動作(參見前面的 “嵌套使用make”),那么,這個變量會記錄了我們的當(dāng)前Makefile的調(diào)用層數(shù)。

            下面再介紹兩個定義變量時我們需要知道的,請先看一個例子,如果我們要定義一個變量,其值是一個空格,那么我們可以這樣來:

                nullstring :=
                space := $(nullstring) # end of the line
            nullstring是一個Empty變量,其中什么也沒有,而我們的space的值是一個空格。因為在操作符的右邊是很難描述一個空格的,這里 采用的技術(shù)很管用,先用一個Empty變量來標(biāo)明變量的值開始了,而后面采用“#”注釋符來表示變量定義的終止,這樣,我們可以定義出其值是一個空格的變 量。請注意這里關(guān)于“#”的使用,注釋符“#”的這種特性值得我們注意,如果我們這樣定義一個變量:

                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


            三、變量高級用法

            這里介紹兩種變量的高級使用方法,第一種是變量值的替換。

            我們可以替換變量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把變量“var”中所有以“a”字 串“結(jié)尾”的“a”替換成“b”字串。這里的“結(jié)尾”意思是“空格”或是“結(jié)束符”。

            還是看一個示例吧:

                foo := a.o b.o c.o
                bar := $(foo:.o=.c)

            這個示例中,我們先定義了一個“$(foo)”變量,而第二行的意思是把“$(foo)”中所有以“.o”字串“結(jié)尾”全部替換成“.c”,所以 我們的“$(bar)”的值就是“a.c b.c c.c”。

            另外一種變量替換的技術(shù)是以“靜態(tài)模式”(參見前面章節(jié))定義的,如:

                foo := a.o b.o c.o
                bar := $(foo:%.o=%.c)

            這依賴于被替換字串中的有相同的模式,模式中必須包含一個“%”字符,這個例子同樣讓$(bar)變量的值為“a.c b.c c.c”。 

            第二種高級用法是——“把變量的值再當(dāng)成變量”。先看一個例子:

                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”,相關(guān)的推導(dǎo)留給讀者自己去做吧。

            讓我們再復(fù)雜一點(diǎn),使用上“在變量定義中使用變量”的第一個方式,來看一個例子:

                x = $(y)
                y = z
                z = Hello
                a := $($(x))

            這里的$($(x))被替換成了$($(y)),因為$(y)值是“z”,所以,最終結(jié)果是:a:=$(z),也就是“Hello”。

            再復(fù)雜一點(diǎn),我們再加上函數(shù):

                x = variable1
                variable2 := Hello
                y = $(subst 1,2,$(x))
                z = y
                a := $($($(z)))

            這個例子中,“$($($(z)))”擴(kuò)展為“$($(y))”,而其再次被擴(kuò)展為“$($(subst 1,2,$(x)))”。$(x)的值 是“variable1”,subst函數(shù)把“variable1”中的所有“1”字串替換成“2”字串,于是,“variable1”變成 “variable2”,再取其值,所以,最終,$(a)的值就是$(variable2)的值——“Hello”。(喔,好不容易)

            在這種方式中,或要可以使用多個變量來組成一個變量的名字,然后再取其值:

                first_second = Hello
                a = first
                b = second
                all = $($a_$b)

            這里的“$a_$b”組成了“first_second”,于是,$(all)的值就是“Hello”。

            再來看看結(jié)合第一種技術(shù)的例子:

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

            再來看一個這種技術(shù)和“函數(shù)”與“條件語句”一同使用的例子:

                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),調(diào)用的就是strip函 數(shù)。

            當(dāng)然,“把變量的值再當(dāng)成變量”這種技術(shù),同樣可以用在操作符的左邊:

                dir = foo
                $(dir)_sources := $(wildcard $(dir)/*.c)
                define $(dir)_print
                lpr $($(dir)_sources)
                endef

            這個例子中定義了三個變量:“dir”,“foo_sources”和“foo_print”。


            四、追加變量值

            我們可以使用“+=”操作符給變量追加值,如:

                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 被追加進(jìn)去了)

            使用“+=”操作符,可以模擬為下面的這種例子:

                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

            由于前次的賦值符是“=”,所以“+=”也會以“=”來做為賦值,那么豈不會發(fā)生變量的遞補(bǔ)歸定義,這是很不好的,所以make會自動為我們解決 這個問題,我們不必?fù)?dān)心這個問題。


            五、override 指示符

            如果有變量是通常make的命令行參數(shù)設(shè)置的,那么Makefile中對這個變量的賦值會被忽略。如果你想在Makefile中設(shè)置這類參數(shù)的 值,那么,你可以使用“override”指示符。其語法是:

                override <variable>; = <value>;

                override <variable>; := <value>;

            當(dāng)然,你還可以追加:

                override <variable>; += <more text>;

            對于多行的變量定義,我們用define指示符,在define指示符前,也同樣可以使用ovveride指示符,如:

                override define foo
                bar
                endef


             gunguymadman 回復(fù)于:2004-09-16 12:22:51

            六、多行變量
             
            還有一種設(shè)置變量值的方法是使用define關(guān)鍵字。使用define關(guān)鍵字設(shè)置變量的值可以有換行,這有利于定義一系列的命令(前面我們講過 “命令包”的技術(shù)就是利用這個關(guān)鍵字)。

            define指示符后面跟的是變量的名字,而重起一行定義變量的值,定義是以endef關(guān)鍵字結(jié)束。其工作方式和“=”操作符一樣。變量的值可以 包含函數(shù)、命令、文字,或是其它變量。因為命令需要以[Tab]鍵開頭,所以如果你用define定義的命令變量中沒有以[Tab]鍵開頭,那么make 就不會把其認(rèn)為是命令。

            下面的這個示例展示了define的用法:

                define two-lines
                echo foo
                echo $(bar)
                endef


            七、環(huán)境變量

            make運(yùn)行時的系統(tǒng)環(huán)境變量可以在make開始運(yùn)行時被載入到Makefile文件中,但是如果Makefile中已定義了這個變量,或是這個 變量由make命令行帶入,那么系統(tǒng)的環(huán)境變量的值將被覆蓋。(如果make指定了“-e”參數(shù),那么,系統(tǒng)環(huán)境變量將覆蓋Makefile中定義的變 量)

            因此,如果我們在環(huán)境變量中設(shè)置了“CFLAGS”環(huán)境變量,那么我們就可以在所有的Makefile中使用這個變量了。這對于我們使用統(tǒng)一的編 譯參數(shù)有比較大的好處。如果Makefile中定義了CFLAGS,那么則會使用Makefile中的這個變量,如果沒有定義則使用系統(tǒng)環(huán)境變量的值,一 個共性和個性的統(tǒng)一,很像“全局變量”和“局部變量”的特性。

            當(dāng)make嵌套調(diào)用時(參見前面的“嵌套調(diào)用”章節(jié)),上層Makefile中定義的變量會以系統(tǒng)環(huán)境變量的方式傳遞到下層的Makefile 中。當(dāng)然,默認(rèn)情況下,只有通過命令行設(shè)置的變量會被傳遞。而定義在文件中的變量,如果要向下層Makefile傳遞,則需要使用exprot關(guān)鍵字來聲 明。(參見前面章節(jié))

            當(dāng)然,我并不推薦把許多的變量都定義在系統(tǒng)環(huán)境中,這樣,在我們執(zhí)行不用的Makefile時,擁有的是同一套系統(tǒng)變量,這可能會帶來更多的麻 煩。


            八、目標(biāo)變量

            前面我們所講的在Makefile中定義的變量都是“全局變量”,在整個文件,我們都可以訪問這些變量。當(dāng)然,“自動化變量”除外,如 “$<”等這種類量的自動化變量就屬于“規(guī)則型變量”,這種變量的值依賴于規(guī)則的目標(biāo)和依賴目標(biāo)的定義。

            當(dāng)然,我樣同樣可以為某個目標(biāo)設(shè)置局部變量,這種變量被稱為“Target-specific Variable”,它可以和“全局變量”同名, 因為它的作用范圍只在這條規(guī)則以及連帶規(guī)則中,所以其值也只在作用范圍內(nèi)有效。而不會影響規(guī)則鏈以外的全局變量的值。

            其語法是:

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

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

            <variable-assignment>;可以是前面講過的各種賦值表達(dá)式,如“=”、“:=”、“+=”或是“?=”。第二個 語法是針對于make命令行帶入的變量,或是系統(tǒng)環(huán)境變量。

            這個特性非常的有用,當(dāng)我們設(shè)置了這樣一個變量,這個變量會作用到由這個目標(biāo)所引發(fā)的所有的規(guī)則中去。如:

                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目標(biāo),以及其所引發(fā)的所有規(guī)則中(prog.o foo.o bar.o的規(guī) 則),$(CFLAGS)的值都是“-g”


            九、模式變量

            在GNU的make中,還支持模式變量(Pattern-specific Variable),通過上面的目標(biāo)變量中,我們知道,變量可以定義 在某個目標(biāo)上。模式變量的好處就是,我們可以給定一種“模式”,可以把變量定義在符合這種模式的所有目標(biāo)上。

            我們知道,make的“模式”一般是至少含有一個“%”的,所以,我們可以以如下方式給所有以[.o]結(jié)尾的目標(biāo)定義目標(biāo)變量:

                %.o : CFLAGS = -O

            同樣,模式變量的語法和“目標(biāo)變量”一樣:

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

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

            override同樣是針對于系統(tǒng)環(huán)境傳入的變量,或是make命令行指定的變量。
             


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

            使用條件判斷,可以讓make根據(jù)運(yùn)行時的不同情況選擇不同的執(zhí)行分支。條件表達(dá)式可以是比較變量的值,或是比較變量和常量的值。

            一、示例

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

                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

            可見,在上面示例的這個規(guī)則中,目標(biāo)“foo”可以根據(jù)變量“$(CC)”值來選取不同的函數(shù)庫來編譯程序。

            我們可以從上面的示例中看到三個關(guān)鍵字:ifeq、else和endif。ifeq的意思表示條件語句的開始,并指定一個條件表達(dá)式,表達(dá)式包含 兩個參數(shù),以逗號分隔,表達(dá)式以圓括號括起。else表示條件表達(dá)式為假的情況。endif表示一個條件語句的結(jié)束,任何一個條件表達(dá)式都應(yīng)該以 endif結(jié)束。

            當(dāng)我們的變量$(CC)值是“gcc”時,目標(biāo)foo的規(guī)則是:

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

            而當(dāng)我們的變量$(CC)值不是“gcc”時(比如“cc”),目標(biāo)foo的規(guī)則是:

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

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

                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)


            二、語法

            條件表達(dá)式的語法為:

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

            以及:

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

            其中<conditional-directive>;表示條件關(guān)鍵字,如“ifeq”。這個關(guān)鍵字有四個。

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

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

            比較參數(shù)“arg1”和“arg2”的值是否相同。當(dāng)然,參數(shù)中我們還可以使用make的函數(shù)。如:

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

            這個示例中使用了“strip”函數(shù),如果這個函數(shù)的返回值是空(Empty),那么<text-if-empty>;就生效。

            第二個條件關(guān)鍵字是“ifneq”。語法是:

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

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

            第三個條件關(guān)鍵字是“ifdef”。語法是:

                ifdef <variable-name>; 

            如果變量<variable-name>;的值非空,那到表達(dá)式為真。否則,表達(dá)式為假。當(dāng)然,<variable- name>;同樣可以是一個函數(shù)的返回值。注意,ifdef只是測試一個變量是否有值,其并不會把變量擴(kuò)展到當(dāng)前位置。還是來看兩個例子:

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

                示例二:
                foo =
                ifdef foo
                frobozz = yes
                else
                frobozz = no
                endif

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

            第四個條件關(guān)鍵字是“ifndef”。其語法是:

                ifndef <variable-name>;

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

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

            特別注意的是,make是在讀取Makefile時就計算條件表達(dá)式的值,并根據(jù)條件表達(dá)式的值來選擇語句,所以,你最好不要把自動化變量(如 “$@”等)放入條件表達(dá)式中,因為自動化變量是在運(yùn)行時才有的。

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


             gunguymadman 回復(fù)于:2004-09-16 12:23:29

            使用函數(shù)
            ————

            在Makefile中可以使用函數(shù)來處理變量,從而讓我們的命令或是規(guī)則更為的靈活和具有智能。make所支持的函數(shù)也不算很多,不過已經(jīng)足夠我 們的操作了。函數(shù)調(diào)用后,函數(shù)的返回值可以當(dāng)做變量來使用。


            一、函數(shù)的調(diào)用語法

            函數(shù)調(diào)用,很像變量的使用,也是以“$”來標(biāo)識的,其語法如下:

                $(<function>; <arguments>;)

            或是

                ${<function>; <arguments>;}

            這里,<function>;就是函數(shù)名,make支持的函數(shù)不多。<arguments>;是函數(shù)的參數(shù),參數(shù)間以逗 號“,”分隔,而函數(shù)名和參數(shù)之間以“空格”分隔。函數(shù)調(diào)用以“$”開頭,以圓括號或花括號把函數(shù)名和參數(shù)括起。感覺很像一個變量,是不是?函數(shù)中的參數(shù) 可以使用變量,為了風(fēng)格的統(tǒng)一,函數(shù)和變量的括號最好一樣,如使用“$(subst a,b,$(x))”這樣的形式,而不是 “$(subst a,b,${x})”的形式。因為統(tǒng)一會更清楚,也會減少一些不必要的麻煩。

            還是來看一個示例:

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

            在這個示例中,$(comma)的值是一個逗號。$(space)使用了$(empty)定義了一個空格,$(foo)的值是 “a b c”,$(bar)的定義用,調(diào)用了函數(shù)“subst”,這是一個替換函數(shù),這個函數(shù)有三個參數(shù),第一個參數(shù)是被替換字串,第二個參數(shù)是替換字 串,第三個參數(shù)是替換操作作用的字串。這個函數(shù)也就是把$(foo)中的空格替換成逗號,所以$(bar)的值是“a,b,c”。


            二、字符串處理函數(shù)

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

                名稱:字符串替換函數(shù)——subst。
                功能:把字串<text>;中的<from>;字符串替換成<to>;。
                返回:函數(shù)返回被替換過后的字符串。

                示例:
                    
                    $(subst ee,EE,feet on the street),
                    
                    把“feet on the street”中的“ee”替換成“EE”,返回結(jié)果是 “fEEt on the strEEt”。


            $(patsubst <pattern>;,<replacement>;,<text>;) 

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

                示例:

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

                    把字串“x.c.c bar.c”符合模式[%.c]的單詞替換成[%.o],返回結(jié)果是“x.c.o bar.o”

                備注:

                    這和我們前面“變量章節(jié)”說過的相關(guān)知識有點(diǎn)相似。如:

                    “$(var:<pattern>;=<replacement>;)”
                     相當(dāng)于
                    “$(patsubst <pattern>;,<replacement>;,$(var))”,

                     而“$(var: <suffix>;=<replacement>;)”
                     則相當(dāng)于
                     “$(patsubst %<suffix>;,%<replacement>;,$(var))”。

                     例如有:objects = foo.o bar.o baz.o,
                     那么,“$(objects:.o=.c)”和“$(patsubst %.o,%.c,$(objects))”是一樣的。

            $(strip <string>;)

                名稱:去空格函數(shù)——strip。
                功能:去掉<string>;字串中開頭和結(jié)尾的空字符。
                返回:返回被去掉空格的字符串值。
                示例:
                    
                    $(strip a b c )

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

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

                名稱:查找字符串函數(shù)——findstring。
                功能:在字串<in>;中查找<find>;字串。
                返回:如果找到,那么返回<find>;,否則返回空字符串。
                示例:

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

                    第一個函數(shù)返回“a”字符串,第二個返回“”字符串(空字符串)

            $(filter <pattern...>;,<text>;)

                名稱:過濾函數(shù)——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>;)

                名稱:反過濾函數(shù)——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>;)

                名稱:排序函數(shù)——sort。
                功能:給字符串<list>;中的單詞排序(升序)。
                返回:返回排序后的字符串。
                示例:$(sort foo bar lose)返回“bar foo lose” 。
                備注:sort函數(shù)會去掉<list>;中相同的單詞。

            $(word <n>;,<text>;)

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

            $(wordlist <s>;,<e>;,<text>;)  

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

            $(words <text>;)

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

            $(firstword <text>;)

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

            以上,是所有的字符串操作函數(shù),如果搭配混合使用,可以完成比較復(fù)雜的功能。這里,舉一個現(xiàn)實中應(yīng)用的例子。我們知道,make使用 “VPATH”變量來指定“依賴文件”的搜索路徑。于是,我們可以利用這個搜索路徑來指定編譯器對頭文件的搜索路徑參數(shù)CFLAGS,如:

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

                如果我們的“$(VPATH)”值是“src:../headers”,那么 “$(patsubst %,-I%,$(subst :, ,$(VPATH)))”將返回“-Isrc -I../headers”,這正是cc或 gcc搜索頭文件路徑的參數(shù)。


            三、文件名操作函數(shù)

            下面我們要介紹的函數(shù)主要是處理文件名的。每個函數(shù)的參數(shù)字符串都會被當(dāng)做一個或是一系列的文件名來對待。

            $(dir <names...>;) 

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

            $(notdir <names...>;) 

                名稱:取文件函數(shù)——notdir。
                功能:從文件名序列<names>;中取出非目錄部分。非目錄部分是指最后一個反斜杠(“/”)之后的部分。
                返回:返回文件名序列<names>;的非目錄部分。
                示例: $(notdir src/foo.c hacks)返回值是“foo.c hacks”。
             
            $(suffix <names...>;) 
                
                名稱:取后綴函數(shù)——suffix。
                功能:從文件名序列<names>;中取出各個文件名的后綴。
                返回:返回文件名序列<names>;的后綴序列,如果文件沒有后綴,則返回空字串。
                示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是“.c .c”。

            $(basename <names...>;)

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

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

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

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

                名稱:加前綴函數(shù)——addprefix。
                功能:把前綴<prefix>;加到<names>;中的每個單詞后面。
                返回:返回加過前綴的文件名序列。
                示例:$(addprefix src/,foo bar)返回值是“src/foo src/bar”。

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

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


             gunguymadman 回復(fù)于:2004-09-16 12:24:08

            四、foreach 函數(shù)
             

            foreach函數(shù)和別的函數(shù)非常的不一樣。因為這個函數(shù)是用來做循環(huán)用的,Makefile中的foreach函數(shù)幾乎是仿照于Unix標(biāo)準(zhǔn) Shell(/bin/sh)中的for語句,或是C-Shell(/bin/csh)中的foreach語句而構(gòu)建的。它的語法是:

             

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

             

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

             

            所以,<var>;最好是一個變量名,<list>;可以是一個表達(dá)式,而<text>;中一般會使 用<var>;這個參數(shù)來依次枚舉<list>;中的單詞。舉個例子:

             

                names := a b c d

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

             

            上面的例子中,$(name)中的單詞會被挨個取出,并存到變量“n”中,“$(n).o”每次根據(jù)“$(n)”計算出一個值,這些值以空格分 隔,最后作為foreach函數(shù)的返回,所以,$(files)的值是“a.o b.o c.o d.o”。

             

            注意,foreach中的<var>;參數(shù)是一個臨時的局部變量,foreach函數(shù)執(zhí)行完后,參數(shù)<var>;的變量 將不在作用,其作用域只在foreach函數(shù)當(dāng)中。

             

             

            五、if 函數(shù)
             

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

             

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

             

            或是

             

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

             

            可見,if函數(shù)可以包含“else”部分,或是不含。即if函數(shù)的參數(shù)可以是兩個,也可以是三個。<condition>;參數(shù)是 if的表達(dá)式,如果其返回的為非空字符串,那么這個表達(dá)式就相當(dāng)于返回真,于是,<then-part>;會被計算,否則<else- part>;會被計算。

             

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

             

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

             

             

            六、call函數(shù)
             

            call函數(shù)是唯一一個可以用來創(chuàng)建新的參數(shù)化的函數(shù)。你可以寫一個非常復(fù)雜的表達(dá)式,這個表達(dá)式中,你可以定義許多參數(shù),然后你可以用call 函數(shù)來向這個表達(dá)式傳遞參數(shù)。其語法是:

             

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

             

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

                reverse =  $(1) $(2)

                foo = $(call reverse,a,b)



            那么,foo的值就是“a b”。當(dāng)然,參數(shù)的次序是可以自定義的,不一定是順序的,如:

             

                reverse =  $(2) $(1)

                foo = $(call reverse,a,b)



            此時的foo的值就是“b a”。

             

             

            七、origin函數(shù)
            origin函數(shù)不像其它的函數(shù),他并不操作變量的值,他只是告訴你你的這個變量是哪里來的?其語法是:

             

                $(origin <variable>;)

             

            注意,<variable>;是變量的名字,不應(yīng)該是引用。所以你最好不要在<variable>;中使用“$”字符。 Origin函數(shù)會以其返回值來告訴你這個變量的“出生情況”,下面,是origin函數(shù)的返回值:

             

            “undefined”

                  如果<variable>;從來沒有定義過,origin函數(shù)返回這個值“undefined”。

             

            “default”

                  如果<variable>;是一個默認(rèn)的定義,比如“CC”這個變量,這種變量我們將在后面講述。

             

            “environment”

                  如果<variable>;是一個環(huán)境變量,并且當(dāng)Makefile被執(zhí)行時,“-e”參數(shù)沒有被打開。

             

            “file”

                  如果<variable>;這個變量被定義在Makefile中。

             

            “command line”

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

             

            “override”

                  如果<variable>;是被override指示符重新定義的。

             

            “automatic”

                  如果<variable>;是一個命令運(yùn)行中的自動化變量。關(guān)于自動化變量將在后面講述。

             

            這些信息對于我們編寫Makefile是非常有用的,例如,假設(shè)我們有一個Makefile其包了一個定義文件Make.def,在 Make.def中定義了一個變量“bletch”,而我們的環(huán)境中也有一個環(huán)境變量“bletch”,此時,我們想判斷一下,如果變量來源于環(huán)境,那么 我們就把之重定義了,如果來源于Make.def或是命令行等非環(huán)境的,那么我們就不重新定義它。于是,在我們的Makefile中,我們可以這樣寫:

             

                ifdef bletch

                ifeq "$(origin bletch)" "environment"

                bletch = barf, gag, etc.

                endif

                endif

             

            當(dāng)然,你也許會說,使用override關(guān)鍵字不就可以重新定義環(huán)境中的變量了嗎?為什么需要使用這樣的步驟?是的,我們用override是可 以達(dá)到這樣的效果,可是override過于粗暴,它同時會把從命令行定義的變量也覆蓋了,而我們只想重新定義環(huán)境傳來的,而不想重新定義命令行傳來的。

             

             

            八、shell函數(shù)
             

            shell函數(shù)也不像其它的函數(shù)。顧名思義,它的參數(shù)應(yīng)該就是操作系統(tǒng)Shell的命令。它和反引號“`”是相同的功能。這就是說,shell函 數(shù)把執(zhí)行操作系統(tǒng)命令后的輸出作為函數(shù)返回。于是,我們可以用操作系統(tǒng)命令以及字符串處理命令awk,sed等等命令來生成一個變量,如:

             

                contents := $(shell cat foo)

             

                files := $(shell echo *.c)

             

            注意,這個函數(shù)會新生成一個Shell程序來執(zhí)行命令,所以你要注意其運(yùn)行性能,如果你的Makefile中有一些比較復(fù)雜的規(guī)則,并大量使用了 這個函數(shù),那么對于你的系統(tǒng)性能是有害的。特別是Makefile的隱晦的規(guī)則可能會讓你的shell函數(shù)執(zhí)行的次數(shù)比你想像的多得多。

             

             

            九、控制make的函數(shù)
             

            make提供了一些函數(shù)來控制make的運(yùn)行。通常,你需要檢測一些運(yùn)行Makefile時的運(yùn)行時信息,并且根據(jù)這些信息來決定,你是讓 make繼續(xù)執(zhí)行,還是停止。

             

            $(error <text ...>;)

             

                產(chǎn)生一個致命的錯誤,<text ...>;是錯誤信息。注意,error函數(shù)不會在一被使用就會產(chǎn)生錯誤信息,所以如果你 把其定義在某個變量中,并在后續(xù)的腳本中使用這個變量,那么也是可以的。例如:

             

                示例一:

                ifdef ERROR_001

                $(error error is $(ERROR_001))

                endif

             

                示例二:

                ERR = $(error found an error!)

                .PHONY: err

                err: ; $(ERR)

             

                示例一會在變量ERROR_001定義了后執(zhí)行時產(chǎn)生error調(diào)用,而示例二則在目錄err被執(zhí)行時才發(fā)生error調(diào)用。

             

            $(warning <text ...>;)

             

                 這個函數(shù)很像error函數(shù),只是它并不會讓make退出,只是輸出一段警告信息,而make繼續(xù)執(zhí)行。


             gunguymadman 回復(fù)于:2004-09-16 12:25:00

            make 的運(yùn)行
            ——————

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

            一、make的退出碼

            make命令執(zhí)行后有三個退出碼:

                0 —— 表示成功執(zhí)行。
                1 —— 如果make運(yùn)行時出現(xiàn)任何錯誤,其返回1。
                2 —— 如果你使用了make的“-q”選項,并且make使得一些目標(biāo)不需要更新,那么返回2。

            Make的相關(guān)參數(shù)我們會在后續(xù)章節(jié)中講述。


            二、指定Makefile

            前面我們說過,GNU make找尋默認(rèn)的Makefile的規(guī)則是在當(dāng)前目錄下依次找三個文件——“GNUmakefile”、 “makefile”和“Makefile”。其按順序找這三個文件,一旦找到,就開始讀取這個文件并執(zhí)行。

            當(dāng)前,我們也可以給make命令指定一個特殊名字的Makefile。要達(dá)到這個功能,我們要使用make的“-f”或是“--file”參數(shù) (“--makefile”參數(shù)也行)。例如,我們有個makefile的名字是“hchen.mk”,那么,我們可以這樣來讓make來執(zhí)行這個文件:

                make –f hchen.mk

            如果在make的命令行是,你不只一次地使用了“-f”參數(shù),那么,所有指定的makefile將會被連在一起傳遞給make執(zhí)行。


            三、指定目標(biāo)

            一般來說,make的最終目標(biāo)是makefile中的第一個目標(biāo),而其它目標(biāo)一般是由這個目標(biāo)連帶出來的。這是make的默認(rèn)行為。當(dāng)然,一般來 說,你的makefile中的第一個目標(biāo)是由許多個目標(biāo)組成,你可以指示make,讓其完成你所指定的目標(biāo)。要達(dá)到這一目的很簡單,需在make命令后直 接跟目標(biāo)的名字就可以完成(如前面提到的“make clean”形式)

            任何在makefile中的目標(biāo)都可以被指定成終極目標(biāo),但是除了以“-”打頭,或是包含了“=”的目標(biāo),因為有這些字符的目標(biāo),會被解析成命令 行參數(shù)或是變量。甚至沒有被我們明確寫出來的目標(biāo)也可以成為make的終極目標(biāo),也就是說,只要make可以找到其隱含規(guī)則推導(dǎo)規(guī)則,那么這個隱含目標(biāo)同 樣可以被指定成終極目標(biāo)。

            有一個make的環(huán)境變量叫“MAKECMDGOALS”,這個變量中會存放你所指定的終極目標(biāo)的列表,如果在命令行上,你沒有指定目標(biāo),那么, 這個變量是空值。這個變量可以讓你使用在一些比較特殊的情形下。比如下面的例子:

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

            基于上面的這個例子,只要我們輸入的命令不是“make clean”,那么makefile會自動包含“foo.d”和“bar.d”這兩個 makefile。

            使用指定終極目標(biāo)的方法可以很方便地讓我們編譯我們的程序,例如下面這個例子:

                .PHONY: all
                all: prog1 prog2 prog3 prog4

            從這個例子中,我們可以看到,這個makefile中有四個需要編譯的程序——“prog1”, “prog2”, “prog3” 和 “prog4”,我們可以使用“make all”命令來編譯所有的目標(biāo)(如果把a(bǔ)ll置成第一個目標(biāo),那么只需執(zhí)行“make”),我們也可以使用 “make prog2”來單獨(dú)編譯目標(biāo)“prog2”。

            即然make可以指定所有makefile中的目標(biāo),那么也包括“偽目標(biāo)”,于是我們可以根據(jù)這種性質(zhì)來讓我們的makefile根據(jù)指定的不同 的目標(biāo)來完成不同的事。在Unix世界中,軟件發(fā)布時,特別是GNU這種開源軟件的發(fā)布時,其makefile都包含了編譯、安裝、打包等功能。我們可以 參照這種規(guī)則來書寫我們的makefile中的目標(biāo)。

                 “all”
                    這個偽目標(biāo)是所有目標(biāo)的目標(biāo),其功能一般是編譯所有的目標(biāo)。
                 “clean”
                    這個偽目標(biāo)功能是刪除所有被make創(chuàng)建的文件。
                 “install”
                    這個偽目標(biāo)功能是安裝已編譯好的程序,其實就是把目標(biāo)執(zhí)行文件拷貝到指定的目標(biāo)中去。
                 “print”
                    這個偽目標(biāo)的功能是例出改變過的源文件。
                 “tar”
                    這個偽目標(biāo)功能是把源程序打包備份。也就是一個tar文件。
                 “dist”
                    這個偽目標(biāo)功能是創(chuàng)建一個壓縮文件,一般是把tar文件壓成Z文件。或是gz文件。
                 “TAGS”
                    這個偽目標(biāo)功能是更新所有的目標(biāo),以備完整地重編譯使用。
                 “check”和“test”
                    這兩個偽目標(biāo)一般用來測試makefile的流程。

            當(dāng)然一個項目的makefile中也不一定要書寫這樣的目標(biāo),這些東西都是GNU的東西,但是我想,GNU搞出這些東西一定有其可取之處(等你的 UNIX下的程序文件一多時你就會發(fā)現(xiàn)這些功能很有用了),這里只不過是說明了,如果你要書寫這種功能,最好使用這種名字命名你的目標(biāo),這樣規(guī)范一些,規(guī) 范的好處就是——不用解釋,大家都明白。而且如果你的makefile中有這些功能,一是很實用,二是可以顯得你的makefile很專業(yè)(不是那種初學(xué) 者的作品)。


            四、檢查規(guī)則

            有時候,我們不想讓我們的makefile中的規(guī)則執(zhí)行起來,我們只想檢查一下我們的命令,或是執(zhí)行的序列。于是我們可以使用make命令的下述 參數(shù):

                “-n”
                “--just-print”
                “--dry-run”
                “--recon”
                不執(zhí)行參數(shù),這些參數(shù)只是打印命令,不管目標(biāo)是否更新,把規(guī)則和連帶規(guī)則下的命令打印出來,但不執(zhí)行,這些參數(shù)對于我們調(diào)試 makefile很有用處。

                “-t”
                “--touch”
                這個參數(shù)的意思就是把目標(biāo)文件的時間更新,但不更改目標(biāo)文件。也就是說,make假裝編譯目標(biāo),但不是真正的編譯目標(biāo),只是把目標(biāo)變成已 編譯過的狀態(tài)。

                “-q”
                “--question”
                這個參數(shù)的行為是找目標(biāo)的意思,也就是說,如果目標(biāo)存在,那么其什么也不會輸出,當(dāng)然也不會執(zhí)行編譯,如果目標(biāo)不存在,其會打印出一條出 錯信息。

                “-W <file>;”
                “--what-if=<file>;”
                “--assume-new=<file>;”
                “--new-file=<file>;”
                這個參數(shù)需要指定一個文件。一般是是源文件(或依賴文件),Make會根據(jù)規(guī)則推導(dǎo)來運(yùn)行依賴于這個文件的命令,一般來說,可以和 “-n”參數(shù)一同使用,來查看這個依賴文件所發(fā)生的規(guī)則命令。

            另外一個很有意思的用法是結(jié)合“-p”和“-v”來輸出makefile被執(zhí)行時的信息(這個將在后面講述)。


            五、make的參數(shù)

            下面列舉了所有GNU make 3.80版的參數(shù)定義。其它版本和產(chǎn)商的make大同小異,不過其它產(chǎn)商的make的具體參數(shù)還是請參考各自的 產(chǎn)品文檔。

            “-b”
            “-m”
            這兩個參數(shù)的作用是忽略和其它版本make的兼容性。

            “-B”
            “--always-make”
            認(rèn)為所有的目標(biāo)都需要更新(重編譯)。

            “-C <dir>;”
            “--directory=<dir>;”
            指定讀取makefile的目錄。如果有多個“-C”參數(shù),make的解釋是后面的路徑以前面的作為相對路徑,并以最后的目錄作為被指定目錄。 如:“make –C ~hchen/test –C prog”等價于“make –C ~hchen/test/prog”。

            “—debug[=<options>;]”
            輸出make的調(diào)試信息。它有幾種不同的級別可供選擇,如果沒有參數(shù),那就是輸出最簡單的調(diào)試信息。下面是<options>;的取 值:
                a —— 也就是all,輸出所有的調(diào)試信息。(會非常的多)
                b —— 也就是basic,只輸出簡單的調(diào)試信息。即輸出不需要重編譯的目標(biāo)。
                v —— 也就是verbose,在b選項的級別之上。輸出的信息包括哪個makefile被解析,不需要被重編譯的依賴文件(或是依賴 目標(biāo))等。
                i —— 也就是implicit,輸出所以的隱含規(guī)則。
                j —— 也就是jobs,輸出執(zhí)行規(guī)則中命令的詳細(xì)信息,如命令的PID、返回碼等。
                m —— 也就是makefile,輸出make讀取makefile,更新makefile,執(zhí)行makefile的信息。

            “-d”
            相當(dāng)于“--debug=a”。

            “-e”
            “--environment-overrides”
            指明環(huán)境變量的值覆蓋makefile中定義的變量的值。

            “-f=<file>;”
            “--file=<file>;”
            “--makefile=<file>;”
            指定需要執(zhí)行的makefile。

            “-h”
            “--help”
            顯示幫助信息。

            “-i”
            “--ignore-errors”
            在執(zhí)行時忽略所有的錯誤。

            “-I <dir>;”
            “--include-dir=<dir>;”
            指定一個被包含makefile的搜索目標(biāo)。可以使用多個“-I”參數(shù)來指定多個目錄。

            “-j [<jobsnum>;]”
            “--jobs[=<jobsnum>;]”
            指同時運(yùn)行命令的個數(shù)。如果沒有這個參數(shù),make運(yùn)行命令時能運(yùn)行多少就運(yùn)行多少。如果有一個以上的“-j”參數(shù),那么僅最后一個“-j”才是 有效的。(注意這個參數(shù)在MS-DOS中是無用的)

            “-k”
            “--keep-going”
            出錯也不停止運(yùn)行。如果生成一個目標(biāo)失敗了,那么依賴于其上的目標(biāo)就不會被執(zhí)行了。

            “-l <load>;”
            “--load-average[=<load]”
            “—max-load[=<load>;]”
            指定make運(yùn)行命令的負(fù)載。

            “-n”
            “--just-print”
            “--dry-run”
            “--recon”
            僅輸出執(zhí)行過程中的命令序列,但并不執(zhí)行。

            “-o <file>;”
            “--old-file=<file>;”
            “--assume-old=<file>;”
            不重新生成的指定的<file>;,即使這個目標(biāo)的依賴文件新于它。

            “-p”
            “--print-data-base”
            輸出makefile中的所有數(shù)據(jù),包括所有的規(guī)則和變量。這個參數(shù)會讓一個簡單的makefile都會輸出一堆信息。如果你只是想輸出信息而不 想執(zhí)行makefile,你可以使用“make -qp”命令。如果你想查看執(zhí)行makefile前的預(yù)設(shè)變量和規(guī)則,你可以使用 “make –p –f /dev/null”。這個參數(shù)輸出的信息會包含著你的makefile文件的文件名和行號,所以,用這個參數(shù)來調(diào)試你的 makefile會是很有用的,特別是當(dāng)你的環(huán)境變量很復(fù)雜的時候。

            “-q”
            “--question”
            不運(yùn)行命令,也不輸出。僅僅是檢查所指定的目標(biāo)是否需要更新。如果是0則說明要更新,如果是2則說明有錯誤發(fā)生。

            “-r”
            “--no-builtin-rules”
            禁止make使用任何隱含規(guī)則。

            “-R”
            “--no-builtin-variabes”
            禁止make使用任何作用于變量上的隱含規(guī)則。

            “-s”
            “--silent”
            “--quiet”
            在命令運(yùn)行時不輸出命令的輸出。

            “-S”
            “--no-keep-going”
            “--stop”
            取消“-k”選項的作用。因為有些時候,make的選項是從環(huán)境變量“MAKEFLAGS”中繼承下來的。所以你可以在命令行中使用這個參數(shù)來讓 環(huán)境變量中的“-k”選項失效。

            “-t”
            “--touch”
            相當(dāng)于UNIX的touch命令,只是把目標(biāo)的修改日期變成最新的,也就是阻止生成目標(biāo)的命令運(yùn)行。

            “-v”
            “--version”
            輸出make程序的版本、版權(quán)等關(guān)于make的信息。

            “-w”
            “--print-directory”
            輸出運(yùn)行makefile之前和之后的信息。這個參數(shù)對于跟蹤嵌套式調(diào)用make時很有用。

            “--no-print-directory”
            禁止“-w”選項。

            “-W <file>;”
            “--what-if=<file>;”
            “--new-file=<file>;”
            “--assume-file=<file>;”
            假定目標(biāo)<file>;需要更新,如果和“-n”選項使用,那么這個參數(shù)會輸出該目標(biāo)更新時的運(yùn)行動作。如果沒有“-n”那么就像運(yùn) 行UNIX的“touch”命令一樣,使得<file>;的修改時間為當(dāng)前時間。

            “--warn-undefined-variables”
            只要make發(fā)現(xiàn)有未定義的變量,那么就輸出警告信息。


             gunguymadman 回復(fù)于:2004-09-16 12:25:37

            隱含規(guī)則
            ————

            在我們使用Makefile時,有一些我們會經(jīng)常使用,而且使用頻率非常高的東西,比如,我們編譯C/C++的源程序為中間目標(biāo)文件(Unix下 是[.o]文件,Windows下是[.obj]文件)。本章講述的就是一些在Makefile中的“隱含的”,早先約定了的,不需要我們再寫出來的規(guī) 則。

            “隱含規(guī)則”也就是一種慣例,make會按照這種“慣例”心照不喧地來運(yùn)行,那怕我們的Makefile中沒有書寫這樣的規(guī)則。例如,把[.c] 文件編譯成[.o]文件這一規(guī)則,你根本就不用寫出來,make會自動推導(dǎo)出這種規(guī)則,并生成我們需要的[.o]文件。

            “隱含規(guī)則”會使用一些我們系統(tǒng)變量,我們可以改變這些系統(tǒng)變量的值來定制隱含規(guī)則的運(yùn)行時的參數(shù)。如系統(tǒng)變量“CFLAGS”可以控制編譯時的 編譯器參數(shù)。

            我們還可以通過“模式規(guī)則”的方式寫下自己的隱含規(guī)則。用“后綴規(guī)則”來定義隱含規(guī)則會有許多的限制。使用“模式規(guī)則”會更回得智能和清楚,但 “后綴規(guī)則”可以用來保證我們Makefile的兼容性。
            我們了解了“隱含規(guī)則”,可以讓其為我們更好的服務(wù),也會讓我們知道一些“約定俗成”了的東西,而不至于使得我們在運(yùn)行Makefile時出現(xiàn)一 些我們覺得莫名其妙的東西。當(dāng)然,任何事物都是矛盾的,水能載舟,亦可覆舟,所以,有時候“隱含規(guī)則”也會給我們造成不小的麻煩。只有了解了它,我們才能 更好地使用它。


            一、使用隱含規(guī)則

            如果要使用隱含規(guī)則生成你需要的目標(biāo),你所需要做的就是不要寫出這個目標(biāo)的規(guī)則。那么,make會試圖去自動推導(dǎo)產(chǎn)生這個目標(biāo)的規(guī)則和命令,如果 make可以自動推導(dǎo)生成這個目標(biāo)的規(guī)則和命令,那么這個行為就是隱含規(guī)則的自動推導(dǎo)。當(dāng)然,隱含規(guī)則是make事先約定好的一些東西。例如,我們有下面 的一個Makefile:

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

            我們可以注意到,這個Makefile中并沒有寫下如何生成foo.o和bar.o這兩目標(biāo)的規(guī)則和命令。因為make的“隱含規(guī)則”功能會自動 為我們自動去推導(dǎo)這兩個目標(biāo)的依賴目標(biāo)和生成命令。

            make會在自己的“隱含規(guī)則”庫中尋找可以用的規(guī)則,如果找到,那么就會使用。如果找不到,那么就會報錯。在上面的那個例子中,make調(diào)用的 隱含規(guī)則是,把[.o]的目標(biāo)的依賴文件置成[.c],并使用C的編譯命令“cc –c $(CFLAGS) [.c]”來生成[.o]的目標(biāo)。也就是 說,我們完全沒有必要寫下下面的兩條規(guī)則:

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

            因為,這已經(jīng)是“約定”好了的事了,make和我們約定好了用C編譯器“cc”生成[.o]文件的規(guī)則,這就是隱含規(guī)則。

            當(dāng)然,如果我們?yōu)閇.o]文件書寫了自己的規(guī)則,那么make就不會自動推導(dǎo)并調(diào)用隱含規(guī)則,它會按照我們寫好的規(guī)則忠實地執(zhí)行。

            還有,在make的“隱含規(guī)則庫”中,每一條隱含規(guī)則都在庫中有其順序,越靠前的則是越被經(jīng)常使用的,所以,這會導(dǎo)致我們有些時候即使我們顯示地 指定了目標(biāo)依賴,make也不會管。如下面這條規(guī)則(沒有命令):

                foo.o : foo.p

            依賴文件“foo.p”(Pascal程序的源文件)有可能變得沒有意義。如果目錄下存在了“foo.c”文件,那么我們的隱含規(guī)則一樣會生效, 并會通過“foo.c”調(diào)用C的編譯器生成foo.o文件。因為,在隱含規(guī)則中,Pascal的規(guī)則出現(xiàn)在C的規(guī)則之后,所以,make找到可以生成 foo.o的C的規(guī)則就不再尋找下一條規(guī)則了。如果你確實不希望任何隱含規(guī)則推導(dǎo),那么,你就不要只寫出“依賴規(guī)則”,而不寫命令。


            二、隱含規(guī)則一覽

            這里我們將講述所有預(yù)先設(shè)置(也就是make內(nèi)建)的隱含規(guī)則,如果我們不明確地寫下規(guī)則,那么,make就會在這些規(guī)則中尋找所需要規(guī)則和命 令。當(dāng)然,我們也可以使用make的參數(shù)“-r”或“--no-builtin-rules”選項來取消所有的預(yù)設(shè)置的隱含規(guī)則。

            當(dāng)然,即使是我們指定了“-r”參數(shù),某些隱含規(guī)則還是會生效,因為有許多的隱含規(guī)則都是使用了“后綴規(guī)則”來定義的,所以,只要隱含規(guī)則中有 “后綴列表”(也就一系統(tǒng)定義在目標(biāo).SUFFIXES的依賴目標(biāo)),那么隱含規(guī)則就會生效。默認(rèn)的后綴列表 是:.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。 具體的細(xì)節(jié),我們會在后面講述。

            還是先來看一看常用的隱含規(guī)則吧。

            1、編譯C程序的隱含規(guī)則。
            “<n>;.o”的目標(biāo)的依賴目標(biāo)會自動推導(dǎo)為“<n>;.c”,并且其生成命令是 “$(CC) –c $(CPPFLAGS) $(CFLAGS)”

            2、編譯C++程序的隱含規(guī)則。
            “<n>;.o”的目標(biāo)的依賴目標(biāo)會自動推導(dǎo)為“<n>;.cc”或是“<n>;.C”,并且其生成命令是 “$(CXX) –c $(CPPFLAGS) $(CFLAGS)”。(建議使用“.cc”作為C++源文件的后綴,而不是“.C”)

            3、編譯Pascal程序的隱含規(guī)則。
            “<n>;.o”的目標(biāo)的依賴目標(biāo)會自動推導(dǎo)為“<n>;.p”,并且其生成命令是 “$(PC) –c  $(PFLAGS)”。

            4、編譯Fortran/Ratfor程序的隱含規(guī)則。
            “<n>;.o”的目標(biāo)的依賴目標(biāo)會自動推導(dǎo)為“<n>;.r”或“<n>;.F”或 “<n>;.f”,并且其生成命令是:
                “.f”  “$(FC) –c  $(FFLAGS)”
                “.F”  “$(FC) –c  $(FFLAGS) $(CPPFLAGS)”
                “.f”  “$(FC) –c  $(FFLAGS) $(RFLAGS)”

            5、預(yù)處理Fortran/Ratfor程序的隱含規(guī)則。
            “<n>;.f”的目標(biāo)的依賴目標(biāo)會自動推導(dǎo)為“<n>;.r”或“<n>;.F”。這個規(guī)則只是轉(zhuǎn)換 Ratfor或有預(yù)處理的Fortran程序到一個標(biāo)準(zhǔn)的Fortran程序。其使用的命令是:
                “.F”  “$(FC) –F $(CPPFLAGS) $(FFLAGS)”
                “.r”  “$(FC) –F $(FFLAGS) $(RFLAGS)”

            6、編譯Modula-2程序的隱含規(guī)則。
            “<n>;.sym”的目標(biāo)的依賴目標(biāo)會自動推導(dǎo)為“<n>;.def”,并且其生成命令 是:“$(M2C) $(M2FLAGS) $(DEFFLAGS)”。“<n.o>;” 的目標(biāo)的依賴目標(biāo)會自動推導(dǎo)為 “<n>;.mod”,并且其生成命令是:“$(M2C) $(M2FLAGS) $(MODFLAGS)”。

            7、匯編和匯編預(yù)處理的隱含規(guī)則。
            “<n>;.o” 的目標(biāo)的依賴目標(biāo)會自動推導(dǎo)為“<n>;.s”,默認(rèn)使用編譯品“as”,并且其生成命令 是:“$(AS) $(ASFLAGS)”。“<n>;.s” 的目標(biāo)的依賴目標(biāo)會自動推導(dǎo)為“<n>;.S”,默認(rèn)使用C預(yù)編 譯器“cpp”,并且其生成命令是:“$(AS) $(ASFLAGS)”。

            8、鏈接Object文件的隱含規(guī)則。
            “<n>;”目標(biāo)依賴于“<n>;.o”,通過運(yùn)行C的編譯器來運(yùn)行鏈接程序生成(一般是“ld”),其生成命令 是:“$(CC) $(LDFLAGS) <n>;.o $(LOADLIBES) $(LDLIBS)”。這個規(guī)則對于只有一個源文件的工 程有效,同時也對多個Object文件(由不同的源文件生成)的也有效。例如如下規(guī)則:

                x : y.o z.o

            并且“x.c”、“y.c”和“z.c”都存在時,隱含規(guī)則將執(zhí)行如下命令:

                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)和你的目標(biāo)名字(如上例中的x)相關(guān)聯(lián),那么,你最好寫出自己的生成規(guī)則,不然,隱含規(guī)則會報錯的。

            9、Yacc C程序時的隱含規(guī)則。
            “<n>;.c”的依賴文件被自動推導(dǎo)為“n.y”(Yacc生成的文件),其生成命令 是:“$(YACC) $(YFALGS)”。(“Yacc”是一個語法分析器,關(guān)于其細(xì)節(jié)請查看相關(guān)資料)

            10、Lex C程序時的隱含規(guī)則。
            “<n>;.c”的依賴文件被自動推導(dǎo)為“n.l”(Lex生成的文件),其生成命令 是:“$(LEX) $(LFALGS)”。(關(guān)于“Lex”的細(xì)節(jié)請查看相關(guān)資料)

            11、Lex Ratfor程序時的隱含規(guī)則。
            “<n>;.r”的依賴文件被自動推導(dǎo)為“n.l”(Lex生成的文件),其生成命令是:“$(LEX) $(LFALGS)”。

            12、從C程序、Yacc文件或Lex文件創(chuàng)建Lint庫的隱含規(guī)則。
            “<n>;.ln” (lint生成的文件)的依賴文件被自動推導(dǎo)為“n.c”,其生成命令 是:“$(LINT) $(LINTFALGS) $(CPPFLAGS) -i”。對于“<n>;.y”和“<n>;.l”也 是同樣的規(guī)則。


            三、隱含規(guī)則使用的變量

            在隱含規(guī)則中的命令中,基本上都是使用了一些預(yù)先設(shè)置的變量。你可以在你的makefile中改變這些變量的值,或是在make的命令行中傳入這 些值,或是在你的環(huán)境變量中設(shè)置這些值,無論怎么樣,只要設(shè)置了這些特定的變量,那么其就會對隱含規(guī)則起作用。當(dāng)然,你也可以利用make的“-R”或 “--no–builtin-variables”參數(shù)來取消你所定義的變量對隱含規(guī)則的作用。

            例如,第一條隱含規(guī)則——編譯C程序的隱含規(guī)則的命令是“$(CC) –c $(CFLAGS) $(CPPFLAGS)”。Make默認(rèn)的編譯 命令是“cc”,如果你把變量“$(CC)”重定義成“gcc”,把變量“$(CFLAGS)”重定義成“-g”,那么,隱含規(guī)則中的命令全部會以 “gcc –c -g $(CPPFLAGS)”的樣子來執(zhí)行了。

            我們可以把隱含規(guī)則中使用的變量分成兩種:一種是命令相關(guān)的,如“CC”;一種是參數(shù)相的關(guān),如“CFLAGS”。下面是所有隱含規(guī)則中會用到的 變量:

            1、關(guān)于命令的變量。

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

            2、關(guān)于命令參數(shù)的變量

            下面的這些變量都是相關(guān)上面的命令的參數(shù)。如果沒有指明其默認(rèn)值,那么其默認(rèn)值都是空。

            ARFLAGS 
                函數(shù)庫打包程序AR命令的參數(shù)。默認(rèn)值是“rv”。
            ASFLAGS 
                匯編語言編譯器參數(shù)。(當(dāng)明顯地調(diào)用“.s”或“.S”文件時)。 
            CFLAGS 
                C語言編譯器參數(shù)。
            CXXFLAGS 
                C++語言編譯器參數(shù)。
            COFLAGS 
                RCS命令參數(shù)。 
            CPPFLAGS 
                C預(yù)處理器參數(shù)。( C 和 Fortran 編譯器也會用到)。
            FFLAGS 
                Fortran語言編譯器參數(shù)。
            GFLAGS 
                SCCS “get”程序參數(shù)。
            LDFLAGS 
                鏈接器參數(shù)。(如:“ld”)
            LFLAGS 
                Lex文法分析器參數(shù)。
            PFLAGS 
                Pascal語言編譯器參數(shù)。
            RFLAGS 
                Ratfor 程序的Fortran 編譯器參數(shù)。
            YFLAGS 
                Yacc文法分析器參數(shù)。 


            四、隱含規(guī)則鏈

            有些時候,一個目標(biāo)可能被一系列的隱含規(guī)則所作用。例如,一個[.o]的文件生成,可能會是先被Yacc的[.y]文件先成[.c],然后再被C 的編譯器生成。我們把這一系列的隱含規(guī)則叫做“隱含規(guī)則鏈”。

            在上面的例子中,如果文件[.c]存在,那么就直接調(diào)用C的編譯器的隱含規(guī)則,如果沒有[.c]文件,但有一個[.y]文件,那么Yacc的隱含 規(guī)則會被調(diào)用,生成[.c]文件,然后,再調(diào)用C編譯的隱含規(guī)則最終由[.c]生成[.o]文件,達(dá)到目標(biāo)。

            我們把這種[.c]的文件(或是目標(biāo)),叫做中間目標(biāo)。不管怎么樣,make會努力自動推導(dǎo)生成目標(biāo)的一切方法,不管中間目標(biāo)有多少,其都會執(zhí)著 地把所有的隱含規(guī)則和你書寫的規(guī)則全部合起來分析,努力達(dá)到目標(biāo),所以,有些時候,可能會讓你覺得奇怪,怎么我的目標(biāo)會這樣生成?怎么我的 makefile發(fā)瘋了?

            在默認(rèn)情況下,對于中間目標(biāo),它和一般的目標(biāo)有兩個地方所不同:第一個不同是除非中間的目標(biāo)不存在,才會引發(fā)中間規(guī)則。第二個不同的是,只要目標(biāo) 成功產(chǎn)生,那么,產(chǎn)生最終目標(biāo)過程中,所產(chǎn)生的中間目標(biāo)文件會被以“rm -f”刪除。

            通常,一個被makefile指定成目標(biāo)或是依賴目標(biāo)的文件不能被當(dāng)作中介。然而,你可以明顯地說明一個文件或是目標(biāo)是中介目標(biāo),你可以使用偽目 標(biāo)“.INTERMEDIATE”來強(qiáng)制聲明。(如:.INTERMEDIATE : mid )

            你也可以阻止make自動刪除中間目標(biāo),要做到這一點(diǎn),你可以使用偽目標(biāo)“.SECONDARY”來強(qiáng)制聲明 (如:.SECONDARY : sec)。你還可以把你的目標(biāo),以模式的方式來指定(如:%.o)成偽目標(biāo)“.PRECIOUS”的依賴目標(biāo),以保存被 隱含規(guī)則所生成的中間文件。

            在“隱含規(guī)則鏈”中,禁止同一個目標(biāo)出現(xiàn)兩次或兩次以上,這樣一來,就可防止在make自動推導(dǎo)時出現(xiàn)無限遞歸的情況。

            Make會優(yōu)化一些特殊的隱含規(guī)則,而不生成中間文件。如,從文件“foo.c”生成目標(biāo)程序“foo”,按道理,make會編譯生成中間文件 “foo.o”,然后鏈接成“foo”,但在實際情況下,這一動作可以被一條“cc”的命令完成(cc –o foo foo.c),于是優(yōu)化過的規(guī)則就 不會生成中間文件。


             gunguymadman 回復(fù)于:2004-09-16 12:26:11

            五、定義模式規(guī)則

            你可以使用模式規(guī)則來定義一個隱含規(guī)則。一個模式規(guī)則就好像一個一般的規(guī)則,只是在規(guī)則中,目標(biāo)的定義需要有"%"字符。"%"的意思是表示一個 或多個任意字符。在依賴目標(biāo)中同樣可以使用"%",只是依賴目標(biāo)中的"%"的取值,取決于其目標(biāo)。

            有一點(diǎn)需要注意的是,"%"的展開發(fā)生在變量和函數(shù)的展開之后,變量和函數(shù)的展開發(fā)生在make載入Makefile時,而模式規(guī)則中的"%"則 發(fā)生在運(yùn)行時。


            1、模式規(guī)則介紹

            模式規(guī)則中,至少在規(guī)則的目標(biāo)定義中要包含"%",否則,就是一般的規(guī)則。目標(biāo)中的"%"定義表示對文件名的匹配,"%"表示長度任意的非空字符 串。例如:"%.c"表示以".c"結(jié)尾的文件名(文件名的長度至少為3),而"s.%.c"則表示以"s."開頭,".c"結(jié)尾的文件名(文件名的長度 至少為5)。

            如果"%"定義在目標(biāo)中,那么,目標(biāo)中的"%"的值決定了依賴目標(biāo)中的"%"的值,也就是說,目標(biāo)中的模式的"%"決定了依賴目標(biāo)中"%"的樣 子。例如有一個模式規(guī)則如下:

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

            其含義是,指出了怎么從所有的[.c]文件生成相應(yīng)的[.o]文件的規(guī)則。如果要生成的目標(biāo)是"a.o b.o",那么"%c"就 是"a.c b.c"。

            一旦依賴目標(biāo)中的"%"模式被確定,那么,make會被要求去匹配當(dāng)前目錄下所有的文件名,一旦找到,make就會規(guī)則下的命令,所以,在模式規(guī) 則中,目標(biāo)可能會是多個的,如果有模式匹配出多個目標(biāo),make就會產(chǎn)生所有的模式目標(biāo),此時,make關(guān)心的是依賴的文件名和生成目標(biāo)的命令這兩件事。


            2、模式規(guī)則示例

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

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

            其中,"$@"表示所有的目標(biāo)的挨個值,"$<"表示了所有依賴目標(biāo)的挨個值。這些奇怪的變量我們叫"自動化變量",后面會詳細(xì)講述。

            下面的這個例子中有兩個目標(biāo)是模式的:

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

            這條規(guī)則告訴make把所有的[.y]文件都以"bison -d <n>;.y"執(zhí)行,然后生 成"<n>;.tab.c"和"<n>;.tab.h"文件。(其中,"<n>;"表示一個任意字符串)。如果我們 的執(zhí)行程序"foo"依賴于文件"parse.tab.o"和"scan.o",并且文件"scan.o"依賴于文件"parse.tab.h",如 果"parse.y"文件被更新了,那么根據(jù)上述的規(guī)則,"bison -d parse.y"就會被執(zhí)行一次,于是,"parse.tab.o" 和"scan.o"的依賴文件就齊了。(假設(shè),"parse.tab.o"由"parse.tab.c"生成,和"scan.o"由"scan.c"生 成,而"foo"由"parse.tab.o"和"scan.o"鏈接生成,而且foo和其[.o]文件的依賴關(guān)系也寫好,那么,所有的目標(biāo)都會得到滿 足)


            3、自動化變量

            在上述的模式規(guī)則中,目標(biāo)和依賴文件都是一系例的文件,那么我們?nèi)绾螘鴮懸粋€命令來完成從不同的依賴文件生成相應(yīng)的目標(biāo)?因為在每一次的對模式規(guī) 則的解析時,都會是不同的目標(biāo)和依賴文件。

            自動化變量就是完成這個功能的。在前面,我們已經(jīng)對自動化變量有所提涉,相信你看到這里已對它有一個感性認(rèn)識了。所謂自動化變量,就是這種變量會 把模式中所定義的一系列的文件自動地挨個取出,直至所有的符合模式的文件都取完了。這種自動化變量只應(yīng)出現(xiàn)在規(guī)則的命令中。

            下面是所有的自動化變量及其說明:

            $@
                表示規(guī)則中的目標(biāo)文件集。在模式規(guī)則中,如果有多個目標(biāo),那么,"$@"就是匹配于目標(biāo)中模式定義的集合。

            $%
                僅當(dāng)目標(biāo)是函數(shù)庫文件中,表示規(guī)則中的目標(biāo)成員名。例如,如果一個目標(biāo)是"foo.a(bar.o)",那么,"$%"就 是"bar.o","$@"就是"foo.a"。如果目標(biāo)不是函數(shù)庫文件(Unix下是[.a],Windows下是[.lib]),那么,其值為空。

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

            $?
                所有比目標(biāo)新的依賴目標(biāo)的集合。以空格分隔。

            $^
                所有的依賴目標(biāo)的集合。以空格分隔。如果在依賴目標(biāo)中有多個重復(fù)的,那個這個變量會去除重復(fù)的依賴目標(biāo),只保留一份。

            $+
                這個變量很像"$^",也是所有依賴目標(biāo)的集合。只是它不去除重復(fù)的依賴目標(biāo)。

            $* 
               這個變量表示目標(biāo)模式中"%"及其之前的部分。如果目標(biāo)是"dir/a.foo.b",并且目標(biāo)的模式是"a.%.b",那么,"$*"的 值就是"dir/a.foo"。這個變量對于構(gòu)造有關(guān)聯(lián)的文件名是比較有較。如果目標(biāo)中沒有模式的定義,那么"$*"也就不能被推導(dǎo)出,但是,如果目標(biāo)文 件的后綴是make所識別的,那么"$*"就是除了后綴的那一部分。例如:如果目標(biāo)是"foo.c",因為".c"是make所能識別的后綴名,所 以,"$*"的值就是"foo"。這個特性是GNU make的,很有可能不兼容于其它版本的make,所以,你應(yīng)該盡量避免使用"$*",除非是在隱含 規(guī)則或是靜態(tài)模式中。如果目標(biāo)中的后綴是make所不能識別的,那么"$*"就是空值。

            當(dāng)你希望只對更新過的依賴文件進(jìn)行操作時,"$?"在顯式規(guī)則中很有用,例如,假設(shè)有一個函數(shù)庫文件叫"lib",其由其它幾個object文件 更新。那么把object文件打包的比較有效率的Makefile規(guī)則是:

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

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

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

            $(@D)
                表示"$@"的目錄部分(不以斜杠作為結(jié)尾),如果"$@"值是"dir/foo.o",那么"$(@D)"就是"dir",而如 果"$@"中沒有包含斜杠的話,其值就是"."(當(dāng)前目錄)。

            $(@F)
                表示"$@"的文件部分,如果"$@"值是"dir/foo.o",那么"$(@F)"就是"foo.o","$(@F)"相當(dāng)于函 數(shù)"$(notdir $@)"。

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

            "$(%D)"
            "$(%F)"
                分別表示了函數(shù)包文件成員的目錄部分和文件部分。這對于形同"archive(member)"形式的目標(biāo)中的"member"中包含了 不同的目錄很有用。

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

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

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

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

            最后想提醒一下的是,對于"$<",為了避免產(chǎn)生不必要的麻煩,我們最好給$后面的那個特定字符都加上圓括號,比如,"$(<)"就 要比"$<"要好一些。

            還得要注意的是,這些變量只使用在規(guī)則的命令中,而且一般都是"顯式規(guī)則"和"靜態(tài)模式規(guī)則"(參見前面"書寫規(guī)則"一章)。其在隱含規(guī)則中并沒 有意義。

            4、模式的匹配

            一般來說,一個目標(biāo)的模式有一個有前綴或是后綴的"%",或是沒有前后綴,直接就是一個"%"。因為"%"代表一個或多個字符,所以在定義好了的 模式中,我們把"%"所匹配的內(nèi)容叫做"莖",例如"%.c"所匹配的文件"test.c"中"test"就是"莖"。因為在目標(biāo)和依賴目標(biāo)中同時 有"%"時,依賴目標(biāo)的"莖"會傳給目標(biāo),當(dāng)做目標(biāo)中的"莖"。

            當(dāng)一個模式匹配包含有斜杠(實際也不經(jīng)常包含)的文件時,那么在進(jìn)行模式匹配時,目錄部分會首先被移開,然后進(jìn)行匹配,成功后,再把目錄加回去。 在進(jìn)行"莖"的傳遞時,我們需要知道這個步驟。例如有一個模式"e%t",文件"src/eat"匹配于該模式,于是"src/a"就是其"莖",如果這 個模式定義在依賴目標(biāo)中,而被依賴于這個模式的目標(biāo)中又有個模式"c%r",那么,目標(biāo)就是"src/car"。("莖"被傳遞)


            5、重載內(nèi)建隱含規(guī)則

            你可以重載內(nèi)建的隱含規(guī)則(或是定義一個全新的),例如你可以重新構(gòu)造和內(nèi)建隱含規(guī)則不同的命令,如:

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

            你可以取消內(nèi)建的隱含規(guī)則,只要不在后面寫命令就行。如:

                %.o : %.s

            同樣,你也可以重新定義一個全新的隱含規(guī)則,其在隱含規(guī)則中的位置取決于你在哪里寫下這個規(guī)則。朝前的位置就靠前。


            六、老式風(fēng)格的"后綴規(guī)則"

            后綴規(guī)則是一個比較老式的定義隱含規(guī)則的方法。后綴規(guī)則會被模式規(guī)則逐步地取代。因為模式規(guī)則更強(qiáng)更清晰。為了和老版本的Makefile兼 容,GNU make同樣兼容于這些東西。后綴規(guī)則有兩種方式:"雙后綴"和"單后綴"。

            雙后綴規(guī)則定義了一對后綴:目標(biāo)文件的后綴和依賴目標(biāo)(源文件)的后綴。如".c.o"相當(dāng)于"%o : %c"。單后綴規(guī)則只定義一個后綴,也 就是源文件的后綴。如".c"相當(dāng)于"% : %.c"。

            后綴規(guī)則中所定義的后綴應(yīng)該是make所認(rèn)識的,如果一個后綴是make所認(rèn)識的,那么這個規(guī)則就是單后綴規(guī)則,而如果兩個連在一起的后綴都被 make所認(rèn)識,那就是雙后綴規(guī)則。例如:".c"和".o"都是make所知道。因而,如果你定義了一個規(guī)則是".c.o"那么其就是雙后綴規(guī)則,意義 就是".c"是源文件的后綴,".o"是目標(biāo)文件的后綴。如下示例:

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

            后綴規(guī)則不允許任何的依賴文件,如果有依賴文件的話,那就不是后綴規(guī)則,那些后綴統(tǒng)統(tǒng)被認(rèn)為是文件名,如:

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

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

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

            后綴規(guī)則中,如果沒有命令,那是毫無意義的。因為他也不會移去內(nèi)建的隱含規(guī)則。

            而要讓make知道一些特定的后綴,我們可以使用偽目標(biāo)".SUFFIXES"來定義或是刪除,如:

                .SUFFIXES: .hack .win

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

                .SUFFIXES:              # 刪除默認(rèn)的后綴
                .SUFFIXES: .c .o .h   # 定義自己的后綴

            先清楚默認(rèn)后綴,后定義自己的后綴列表。

            make的參數(shù)"-r"或"-no-builtin-rules"也會使用得默認(rèn)的后綴列表為空。而變量"SUFFIXE"被用來定義默認(rèn)的后綴 列表,你可以用".SUFFIXES"來改變后綴列表,但請不要改變變量"SUFFIXE"的值。


            七、隱含規(guī)則搜索算法

            比如我們有一個目標(biāo)叫 T。下面是搜索目標(biāo)T的規(guī)則的算法。請注意,在下面,我們沒有提到后綴規(guī)則,原因是,所有的后綴規(guī)則在Makefile被 載入內(nèi)存時,會被轉(zhuǎn)換成模式規(guī)則。如果目標(biāo)是"archive(member)"的函數(shù)庫文件模式,那么這個算法會被運(yùn)行兩次,第一次是找目標(biāo)T,如果沒 有找到的話,那么進(jìn)入第二次,第二次會把"member"當(dāng)作T來搜索。

            1、把T的目錄部分分離出來。叫D,而剩余部分叫N。(如:如果T是"src/foo.o",那么,D就是"src/",N就是"foo.o")

            2、創(chuàng)建所有匹配于T或是N的模式規(guī)則列表。

            3、如果在模式規(guī)則列表中有匹配所有文件的模式,如"%",那么從列表中移除其它的模式。

            4、移除列表中沒有命令的規(guī)則。

            5、對于第一個在列表中的模式規(guī)則:
                1)推導(dǎo)其"莖"S,S應(yīng)該是T或是N匹配于模式中"%"非空的部分。
                2)計算依賴文件。把依賴文件中的"%"都替換成"莖"S。如果目標(biāo)模式中沒有包含斜框字符,而把D加在第一個依賴文件的開頭。
            3)測試是否所有的依賴文件都存在或是理當(dāng)存在。(如果有一個文件被定義成另外一個規(guī)則的目標(biāo)文件,或者是一個顯式規(guī)則的依賴文件,那么這個文件 就叫"理當(dāng)存在")
                4)如果所有的依賴文件存在或是理當(dāng)存在,或是就沒有依賴文件。那么這條規(guī)則將被采用,退出該算法。

            6、如果經(jīng)過第5步,沒有模式規(guī)則被找到,那么就做更進(jìn)一步的搜索。對于存在于列表中的第一個模式規(guī)則:
                1)如果規(guī)則是終止規(guī)則,那就忽略它,繼續(xù)下一條模式規(guī)則。
            2)計算依賴文件。(同第5步)
            3)測試所有的依賴文件是否存在或是理當(dāng)存在。
            4)對于不存在的依賴文件,遞歸調(diào)用這個算法查找他是否可以被隱含規(guī)則找到。
            5)如果所有的依賴文件存在或是理當(dāng)存在,或是就根本沒有依賴文件。那么這條規(guī)則被采用,退出該算法。

            7、如果沒有隱含規(guī)則可以使用,查看".DEFAULT"規(guī)則,如果有,采用,把".DEFAULT"的命令給T使用。

            一旦規(guī)則被找到,就會執(zhí)行其相當(dāng)?shù)拿睿藭r,我們的自動化變量的值才會生成。


             gunguymadman 回復(fù)于:2004-09-16 12:26:44

            使用make更新函數(shù)庫文件
            ———————————

            函數(shù)庫文件也就是對Object文件(程序編譯的中間文件)的打包文件。在Unix下,一般是由命令"ar"來完成打包工作。

            一、函數(shù)庫文件的成員

            一個函數(shù)庫文件由多個文件組成。你可以以如下格式指定函數(shù)庫文件及其組成:

                archive(member)

            這個不是一個命令,而一個目標(biāo)和依賴的定義。一般來說,這種用法基本上就是為了"ar"命令來服務(wù)的。如:

                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)


            二、函數(shù)庫成員的隱含規(guī)則

            當(dāng)make搜索一個目標(biāo)的隱含規(guī)則時,一個特殊的特性是,如果這個目標(biāo)是"a(m)"形式的,其會把目標(biāo)變成"(m)"。于是,如果我們的成員 是"%.o"的模式定義,并且如果我們使用"make foo.a(bar.o)"的形式調(diào)用Makefile時,隱含規(guī)則會去找"bar.o"的規(guī)則, 如果沒有定義bar.o的規(guī)則,那么內(nèi)建隱含規(guī)則生效,make會去找bar.c文件來生成bar.o,如果找得到的話,make執(zhí)行的命令大致如下:

                cc -c bar.c -o bar.o
                ar r foo.a bar.o
                rm -f bar.o

            還有一個變量要注意的是"$%",這是專屬函數(shù)庫文件的自動化變量,有關(guān)其說明請參見"自動化變量"一節(jié)。


            三、函數(shù)庫文件的后綴規(guī)則

            你可以使用"后綴規(guī)則"和"隱含規(guī)則"來生成函數(shù)庫打包文件,如:

                .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


            四、注意事項

            在進(jìn)行函數(shù)庫打包文件生成時,請小心使用make的并行機(jī)制("-j"參數(shù))。如果多個ar命令在同一時間運(yùn)行在同一個函數(shù)庫打包文件上,就很有 可以損壞這個函數(shù)庫文件。所以,在make未來的版本中,應(yīng)該提供一種機(jī)制來避免并行操作發(fā)生在函數(shù)打包文件上。

            但就目前而言,你還是應(yīng)該不要盡量不要使用"-j"參數(shù)。

             

            后序
            ——

            終于到寫結(jié)束語的時候了,以上基本上就是GNU make的Makefile的所有細(xì)節(jié)了。其它的產(chǎn)商的make基本上也就是這樣的,無論什么樣 的make,都是以文件的依賴性為基礎(chǔ)的,其基本是都是遵循一個標(biāo)準(zhǔn)的。這篇文檔中80%的技術(shù)細(xì)節(jié)都適用于任何的make,我猜測"函數(shù)"那一章的內(nèi)容 可能不是其它make所支持的,而隱含規(guī)則方面,我想不同的make會有不同的實現(xiàn),我沒有精力來查看GNU的make和VC的nmake、BCB的 make,或是別的UNIX下的make有些什么樣的差別,一是時間精力不夠,二是因為我基本上都是在Unix下使用make,以前在SCO Unix和 IBM的AIX,現(xiàn)在在Linux、Solaris、HP-UX、AIX和Alpha下使用,Linux和Solaris下更多一點(diǎn)。不過,我可以肯定的 是,在Unix下的make,無論是哪種平臺,幾乎都使用了Richard Stallman開發(fā)的make和cc/gcc的編譯器,而且,基本上都是 GNU的make(公司里所有的UNIX機(jī)器上都被裝上了GNU的東西,所以,使用GNU的程序也就多了一些)。GNU的東西還是很不錯的,特別是使用得 深了以后,越來越覺得GNU的軟件的強(qiáng)大,也越來越覺得GNU的在操作系統(tǒng)中(主要是Unix,甚至Windows)"殺傷力"。

            對于上述所有的make的細(xì)節(jié),我們不但可以利用make這個工具來編譯我們的程序,還可以利用make來完成其它的工作,因為規(guī)則中的命令可以 是任何Shell之下的命令,所以,在Unix下,你不一定只是使用程序語言的編譯器,你還可以在Makefile中書寫其它的命令,如:tar、 awk、mail、sed、cvs、compress、ls、rm、yacc、rpm、ftp……等等,等等,來完成諸如"程序打包"、"程序備份"、" 制作程序安裝包"、"提交代碼"、"使用程序模板"、"合并文件"等等五花八門的功能,文件操作,文件管理,編程開發(fā)設(shè)計,或是其它一些異想天開的東西。 比如,以前在書寫銀行交易程序時,由于銀行的交易程序基本一樣,就見到有人書寫了一些交易的通用程序模板,在該模板中把一些網(wǎng)絡(luò)通訊、數(shù)據(jù)庫操作的、業(yè)務(wù) 操作共性的東西寫在一個文件中,在這些文件中用些諸如"@@@N、###N"奇怪字串標(biāo)注一些位置,然后書寫交易時,只需按照一種特定的規(guī)則書寫特定的處 理,最后在make時,使用awk和sed,把模板中的"@@@N、###N"等字串替代成特定的程序,形成C文件,然后再編譯。這個動作很像數(shù)據(jù)庫的" 擴(kuò)展C"語言(即在C語言中用"EXEC SQL"的樣子執(zhí)行SQL語句,在用cc/gcc編譯之前,需要使用"擴(kuò)展C"的翻譯程序,如cpre,把其翻 譯成標(biāo)準(zhǔn)C)。如果你在使用make時有一些更為絕妙的方法,請記得告訴我啊。

            回頭看看整篇文檔,不覺記起幾年前剛剛開始在Unix下做開發(fā)的時候,有人問我會不會寫Makefile時,我兩眼發(fā)直,根本不知道在說什么。一 開始看到別人在vi中寫完程序后輸入"!make"時,還以為是vi的功能,后來才知道有一個Makefile在作怪,于是上網(wǎng)查啊查,那時又不愿意看英 文,發(fā)現(xiàn)就根本沒有中文的文檔介紹Makefile,只得看別人寫的Makefile,自己瞎碰瞎搞才積累了一點(diǎn)知識,但在很多地方完全是知其然不知所以 然。后來開始從事UNIX下產(chǎn)品軟件的開發(fā),看到一個400人年,近200萬行代碼的大工程,發(fā)現(xiàn)要編譯這樣一個龐然大物,如果沒有Makefile,那 會是多么恐怖的一樣事啊。于是橫下心來,狠命地讀了一堆英文文檔,才覺得對其掌握了。但發(fā)現(xiàn)目前網(wǎng)上對Makefile介紹的文章還是少得那么的可憐,所 以想寫這樣一篇文章,共享給大家,希望能對各位有所幫助。

            現(xiàn)在我終于寫完了,看了看文件的創(chuàng)建時間,這篇技術(shù)文檔也寫了兩個多月了。發(fā)現(xiàn),自己知道是一回事,要寫下來,跟別人講述又是另外一回事,而且, 現(xiàn)在越來越?jīng)]有時間專研技術(shù)細(xì)節(jié),所以在寫作時,發(fā)現(xiàn)在闡述一些細(xì)節(jié)問題時很難做到嚴(yán)謹(jǐn)和精練,而且對先講什么后講什么不是很清楚,所以,還是參考了一些 國外站點(diǎn)上的資料和題綱,以及一些技術(shù)書籍的語言風(fēng)格,才得以完成。整篇文檔的提綱是基于GNU的Makefile技術(shù)手冊的提綱來書寫的,并結(jié)合了自己 的工作經(jīng)驗,以及自己的學(xué)習(xí)歷程。因為從來沒有寫過這么長,這么細(xì)的文檔,所以一定會有很多地方存在表達(dá)問題,語言歧義或是錯誤。因些,我迫切地得等待各 位給我指證和建議,以及任何的反饋。

            最后,還是利用這個后序,介紹一下自己。我目前從事于所有Unix平臺下的軟件研發(fā),主要是做分布式計算/網(wǎng)格計算方面的系統(tǒng)產(chǎn)品軟件,并且我對 于下一代的計算機(jī)革命——網(wǎng)格計算非常地感興趣,對于分布式計算、P2P、Web Service、J2EE技術(shù)方向也很感興趣,同時,對于項目實施、團(tuán) 隊管理、項目管理也小有心得,希望同樣和我戰(zhàn)斗在“技術(shù)和管理并重”的陣線上的年輕一代,能夠和我多多地交流。我的MSN 是:haoel@hotmail.com(常用),QQ是:753640(不常用)。(注:請勿給我MSN的郵箱發(fā)信,由于hotmail的垃圾郵件導(dǎo)致 我拒收這個郵箱的所有來信)

            我歡迎任何形式的交流,無論是討論技術(shù)還是管理,或是其它海闊天空的東西。除了政治和娛樂新聞我不關(guān)心,其它只要積極向上的東西我都?xì)g迎!

            最最后,我還想介紹一下make程序的設(shè)計開發(fā)者。

             

            首當(dāng)其沖的是: Richard Stallman  

            開源軟件的領(lǐng)袖和先驅(qū),從來沒有領(lǐng)過一天工資,從來沒有使用過Windows操作系統(tǒng)。對于他的事跡和他的軟件以及他的思想,我無需說過多的話, 相信大家對這個人并不比我陌生,這是他的主頁:http://www.stallman.org/ 。這里只貼上一張他的近照:





            原文鏈接:http://bbs.chinaunix.net/viewthread.php?tid=408225
            轉(zhuǎn)載請注明作者名及原文出處


            posted on 2010-05-14 15:02 chatler 閱讀(479) 評論(0)  編輯 收藏 引用 所屬分類: makefile
            <2010年7月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567

            常用鏈接

            留言簿(10)

            隨筆分類(307)

            隨筆檔案(297)

            algorithm

            Books_Free_Online

            C++

            database

            Linux

            Linux shell

            linux socket

            misce

            • cloudward
            • 感覺這個博客還是不錯,雖然做的東西和我不大相關(guān),覺得看看還是有好處的

            network

            OSS

            • Google Android
            • Android is a software stack for mobile devices that includes an operating system, middleware and key applications. This early look at the Android SDK provides the tools and APIs necessary to begin developing applications on the Android platform using the Java programming language.
            • os161 file list

            overall

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            久久久91精品国产一区二区三区| 99久久国产综合精品网成人影院| 中文字幕精品久久久久人妻| 久久精品国产99久久丝袜| 狠狠色丁香婷婷综合久久来| 国产精品9999久久久久| 久久婷婷国产麻豆91天堂| 久久精品人妻一区二区三区| 久久精品一区二区影院 | 日本免费久久久久久久网站| 久久精品一区二区三区不卡| 久久国产精品一区| 无码任你躁久久久久久老妇| 亚洲色大成网站www久久九| 2020久久精品国产免费| 精品多毛少妇人妻AV免费久久| 一本综合久久国产二区| 亚洲av伊人久久综合密臀性色| 精品久久久久久综合日本| 国内精品久久久久久久久| 午夜精品久久久久9999高清| 亚洲AV无码久久精品色欲| 色综合久久中文综合网| 尹人香蕉久久99天天拍| 狠狠久久亚洲欧美专区| 一极黄色视频久久网站| 久久99精品久久久久久久不卡| 久久精品国产黑森林| 久久亚洲AV成人无码电影| 国产一区二区精品久久凹凸| 久久人做人爽一区二区三区| 久久精品国产99国产电影网 | 久久精品国产影库免费看| 久久国产精品视频| 精品久久亚洲中文无码| 99久久精品国产综合一区| 亚洲综合日韩久久成人AV| 久久国产精品免费| 2020久久精品国产免费| 国产亚洲精品久久久久秋霞 | 天堂久久天堂AV色综合|