makefile很重要
什麼是makefile?或許不少Winodws的程序員都不知道這個東西,由於那些Windows的IDE都爲你作了這個工做,但我以爲要做一個好的和professional的程序員,makefile仍是要懂。這就好像如今有這麼多的HTML的編輯器,但若是你想成爲一個專業人士,你仍是要了解HTML的標識的含義。特別在Unix下的軟件編譯,你就不能不本身寫makefile了,會不會寫makefile,從一個側面說明了一我的是否具有完成大型工程的能力。由於,makefile關係到了整個工程的編譯規則。一個工程中的源文件不計數,其按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的規則來指定,哪些文件須要先編譯,哪些文件須要後編譯,哪些文件須要從新編譯,甚至於進行更復雜的功能操做,由於makefile就像一個Shell腳本同樣,其中也能夠執行操做系統的命令。makefile帶來的好處就是——「自動化編譯」,一旦寫好,只須要一個make命令,整個工程徹底自動編譯,極大的提升了軟件開發的效率。make是一個命令工具,是一個解釋makefile中指令的命令工具,通常來講,大多數的IDE都有這個命令,好比:Delphi的make,Visual C++的nmake,Linux下GNU的make。可見,makefile都成爲了一種在工程方面的編譯方法。linux
如今講述如何寫makefile的文章比較少,這是我想寫這篇文章的緣由。固然,不一樣產商的make各不相同,也有不一樣的語法,但其本質都是在「文件依賴性」上作文章,這裏,我僅對GNU的make進行講述,個人環境是RedHat Linux 8.0,make的版本是3.80。必竟,這個make是應用最爲普遍的,也是用得最多的。並且其仍是最遵循於IEEE 1003.2-1992 標準的(POSIX.2)。程序員
在這篇文檔中,將以C/C++的源碼做爲咱們基礎,因此必然涉及一些關於C/C++的編譯的知識,相關於這方面的內容,還請各位查看相關的編譯器的文檔。這裏所默認的編譯器是UNIX下的GCC和CC。編程
在此,我想多說關於程序編譯的一些規範和方法,通常來講,不管是C、C++、仍是pas,首先要把源文件編譯成中間代碼文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,這個動做叫作編譯(compile)。而後再把大量的Object File合成執行文件,這個動做叫做連接(link)。
編譯時,編譯器須要的是語法的正確,函數與變量的聲明的正確。對於後者,一般是你須要告訴編譯器頭文件的所在位置(頭文件中應該只是聲明,而定義應該放在C/C++文件中),只要全部的語法正確,編譯器就能夠編譯出中間目標文件。通常來講,每一個源文件都應該對應於一箇中間目標文件(O文件或是OBJ文件)。
連接時,主要是連接函數和全局變量,因此,咱們可使用這些中間目標文件(O文件或是OBJ文件)來連接咱們的應用程序。連接器並無論函數所在的源文件,只管函數的中間目標文件(Object File),在大多數時候,因爲源文件太多,編譯生成的中間目標文件太多,而在連接時須要明顯地指出中間目標文件名,這對於編譯很不方便,因此,咱們要給中間目標文件打個包,在Windows下這種包叫「庫文件」(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。編輯器
總結一下,源文件首先會生成中間目標文件,再由中間目標文件生成執行文件。在編譯時,編譯器只檢測程序語法,和函數、變量是否被聲明。若是函數未被聲明,編譯器會給出一個警告,但能夠生成Object File。而在連接程序時,連接器會在全部的Object File中找尋函數的實現,若是找不到,那到就會報連接錯誤碼(Linker Error),在VC下,這種錯誤通常是:Link 2001錯誤,意思說是說,連接器未能找到函數的實現。你須要指定函數的ObjectFile.
好,言歸正傳,GNU的make有許多的內容,閒言少敘,仍是讓咱們開始吧。函數
make命令執行時,須要一個 Makefile 文件,以告訴make命令須要怎麼樣的去編譯和連接程序。工具
首先,咱們用一個示例來講明Makefile的書寫規則。以便給你們一個感興認識。這個示例來源於GNU的make使用手冊,在這個示例中,咱們的工程有8個C文件,和3個頭文件,咱們要寫一個Makefile來告訴make命令如何編譯和連接這幾個文件。咱們的規則是:ui
1.若是這個工程沒有編譯過,那麼咱們的全部C文件都要編譯並被連接。atom
2.若是這個工程的某幾個C文件被修改,那麼咱們只編譯被修改的C文件,並連接目標程序。spa
3.若是這個工程的頭文件被改變了,那麼咱們須要編譯引用了這幾個頭文件的C文件,並連接目標程序。
只要咱們的Makefile寫得夠好,全部的這一切,咱們只用一個make命令就能夠完成,make命令會自動智能地根據當前的文件修改的狀況來肯定哪些文件須要重編譯,從而本身編譯所須要的文件和連接目標程序。
在講述這個Makefile以前,仍是讓咱們先來粗略地看一看Makefile的規則。
target... : prerequisites ...
command
...
...
-------------------------------------------------------------------------------
target也就是一個目標文件,能夠是Object File,也能夠是執行文件。還能夠是一個標籤(Label),對於標籤這種特性,在後續的「僞目標」章節中會有敘述。
prerequisites就是,要生成那個target所須要的文件或是目標。
command也就是make須要執行的命令。(任意的Shell命令)
這是一個文件的依賴關係,也就是說,target這一個或多個的目標文件依賴於prerequisites中的文件,其生成規則定義在command中。說白一點就是說,prerequisites中若是有一個以上的文件比target文件要新的話,command所定義的命令就會被執行。這就是Makefile的規則。也就是Makefile中最核心的內容。
說到底,Makefile的東西就是這樣一點,好像個人這篇文檔也該結束了。呵呵。還不盡然,這是Makefile的主線和核心,但要寫好一個Makefile還不夠,我會之後面一點一點地結合個人工做經驗給你慢慢到來。內容還多着呢。:)
正如前面所說的,若是一個工程有3個頭文件,和8個C文件,咱們爲了完成前面所述的那三個規則,咱們的Makefile應該是下面的這個樣子的。
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
反斜槓(\)是換行符的意思。這樣比較便於Makefile的易讀。咱們能夠把這個內容保存在文件爲「Makefile」或「makefile」的文件中,而後在該目錄下直接輸入命令「make」就能夠生成執行文件edit。若是要刪除執行文件和全部的中間目標文件,那麼,只要簡單地執行一下「make clean」就能夠了。
在這個makefile中,目標文件(target)包含:執行文件edit和中間目標文件(*.o),依賴文件(prerequisites)就是冒號後面的那些 .c 文件和 .h文件。每個 .o 文件都有一組依賴文件,而這些 .o 文件又是執行文件 edit 的依賴文件。依賴關係的實質上就是說明了目標文件是由哪些文件生成的,換言之,目標文件是哪些文件更新的。
在定義好依賴關係後,後續的那一行定義瞭如何生成目標文件的操做系統命令,必定要以一個Tab鍵做爲開頭。記住,make並無論命令是怎麼工做的,他只管執行所定義的命令。make會比較targets文件和prerequisites文件的修改日期,若是prerequisites文件的日期要比targets文件的日期要新,或者target不存在的話,那麼,make就會執行後續定義的命令。
這裏要說明一點的是,clean不是一個文件,它只不過是一個動做名字,有點像C語言中的lable同樣,其冒號後什麼也沒有,那麼,make就不會自動去找文件的依賴性,也就不會自動執行其後所定義的命令。要執行其後的命令,就要在make命令後明顯得指出這個lable的名字。這樣的方法很是有用,咱們能夠在一個makefile中定義不用的編譯或是和編譯無關的命令,好比程序的打包,程序的備份,等等。
在默認的方式下,也就是咱們只輸入make命令。那麼,
這就是整個make的依賴性,make會一層又一層地去找文件的依賴關係,直到最終編譯出第一個目標文件。在找尋的過程當中,若是出現錯誤,好比最後被依賴的文件找不到,那麼make就會直接退出,並報錯,而對於所定義的命令的錯誤,或是編譯不成功,make根本不理。make只管文件的依賴性,即,若是在我找了依賴關係以後,冒號後面的文件仍是不在,那麼對不起,我就不工做啦。
經過上述分析,咱們知道,像clean這種,沒有被第一個目標文件直接或間接關聯,那麼它後面所定義的命令將不會被自動執行,不過,咱們能夠顯示要make執行。即命令——「make clean」,以此來清除全部的目標文件,以便重編譯。
因而在咱們編程中,若是這個工程已被編譯過了,當咱們修改了其中一個源文件,好比file.c,那麼根據咱們的依賴性,咱們的目標file.o會被重編譯(也就是在這個依性關係後面所定義的命令),因而file.o的文件也是最新的啦,因而file.o的文件修改時間要比edit要新,因此edit也會被從新連接了(詳見edit目標文件後定義的命令)。
而若是咱們改變了「command.h」,那麼,kdb.o、command.o和files.o都會被重編譯,而且,edit會被重連接。
在上面的例子中,先讓咱們看看edit的規則:
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
咱們能夠看到[.o]文件的字符串被重複了兩次,若是咱們的工程須要加入一個新的[.o]文件,那麼咱們須要在兩個地方加(應該是三個地方,還有一個地方在clean中)。固然,咱們的makefile並不複雜,因此在兩個地方加也不累,但若是makefile變得複雜,那麼咱們就有可能會忘掉一個須要加入的地方,而致使編譯失敗。因此,爲了makefile的易維護,在makefile中咱們可使用變量。makefile的變量也就是一個字符串,理解成C語言中的宏可能會更好。
好比,咱們聲明一個變量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正無論什麼啦,只要可以表示obj文件就好了。咱們在makefile一開始就這樣定義:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
因而,咱們就能夠很方便地在咱們的makefile中以「$(objects)」的方式來使用這個變量了,因而咱們的改良版makefile就變成下面這個樣子:
objects = main.o kbd.o command.o display.o \ insert.osearch.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 變量就能夠了。
關於變量更多的話題,我會在後續給你一一道來。
GNU的make很強大,它能夠自動推導文件以及文件依賴關係後面的命令,因而咱們就不必去在每個[.o]文件後都寫上相似的命令,由於,咱們的make會自動識別,並本身推導命令。
只要make看到一個[.o]文件,它就會自動的把[.c]文件加在依賴關係中,若是make找到一個whatever.o,那麼whatever.c,就會是whatever.o的依賴文件。而且 cc -c whatever.c 也會被推導出來,因而,咱們的makefile不再用寫得這麼複雜。咱們的是新的makefile又出爐了。
objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o edit : $(objects) cc -o edit $(objects) main.o : defs.h kbd.o : defs.h command.h command.o : defs.h command.h display.o : defs.h buffer.h insert.o : defs.h buffer.h search.o : defs.h buffer.h files.o : defs.h buffer.h command.h utils.o : defs.h .PHONY : clean clean : rm edit $(objects)
這種方法,也就是make的「隱晦規則」。上面文件內容中,「.PHONY」表示,clean是個僞目標文件。
關於更爲詳細的「隱晦規則」和「僞目標文件」,我會在後續給你一一道來。
即然咱們的make能夠自動推導命令,那麼我看到那堆[.o]和[.h]的依賴就有點不爽,那麼多的重複的[.h],能不能把其收攏起來,好吧,沒有問題,這個對於make來講很容易,誰叫它提供了自動推導命令和文件的功能呢?來看看最新風格的makefile吧。
objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o edit : $(objects) cc -o edit $(objects) $(objects) : defs.h kbd.o command.o files.o : command.h display.o insert.o search.o files.o : buffer.h .PHONY : clean clean : rm edit $(objects)
這種風格,讓咱們的makefile變得很簡單,但咱們的文件依賴關係就顯得有點凌亂了。魚和熊掌不可兼得。還看你的喜愛了。我是不喜歡這種風格的,一是文件的依賴關係看不清楚,二是若是文件一多,要加入幾個新的.o文件,那就理不清楚了。
每一個Makefile中都應該寫一個清空目標文件(.o和執行文件)的規則,這不只便於重編譯,也很利於保持文件的清潔。這是一個「修養」(呵呵,還記得個人《編程修養》嗎)。通常的風格都是:
clean:
rm edit $(objects)
更爲穩健的作法是:
.PHONY : clean
clean :
-rm edit $(objects)
前面說過,.PHONY意思表示clean是一個「僞目標」,。而在rm命令前面加了一個小減號的意思就是,也許某些文件出現問題,但不要管,繼續作後面的事。固然,clean的規則不要放在文件的開頭,否則,這就會變成make的默認目標,相信誰也不肯意這樣。不成文的規矩是——「clean歷來都是放在文件的最後」。
上面就是一個makefile的概貌,也是makefile的基礎,下面還有不少makefile的相關細節,準備好了嗎?準備好了就來。
Makefile裏主要包含了五個東西:顯式規則、隱晦規則、變量定義、文件指示和註釋。
- 顯式規則。顯式規則說明了,如何生成一個或多的的目標文件。這是由Makefile的書寫者明顯指出,要生成的文件,文件的依賴文件,生成的命令。
- 隱晦規則。因爲咱們的make有自動推導的功能,因此隱晦的規則可讓咱們比較粗糙地簡略地書寫Makefile,這是由make所支持的。
- 變量的定義。在Makefile中咱們要定義一系列的變量,變量通常都是字符串,這個有點你C語言中的宏,當Makefile被執行時,其中的變量都會被擴展到相應的引用位置上。
- 文件指示。其包括了三個部分,一個是在一個Makefile中引用另外一個Makefile,就像C語言中的include同樣;另外一個是指根據某些狀況指定Makefile中的有效部分,就像C語言中的預編譯#if同樣;還有就是定義一個多行的命令。有關這一部分的內容,我會在後續的部分中講述。
- 註釋。Makefile中只有行註釋,和UNIX的Shell腳本同樣,其註釋是用「#」字符,這個就像C/C++中的「//」同樣。若是你要在你的Makefile中使用「#」字符,能夠用反斜框進行轉義,如:「\#」。
最後,還值得一提的是,在Makefile中的命令,必需要以[Tab]鍵開始。
默認的狀況下,make命令會在當前目錄下按順序找尋文件名爲「GNUmakefile」、「makefile」、「Makefile」的文件,找到了解釋這個文件。在這三個文件名中,最好使用「Makefile」這個文件名,由於,這個文件名第一個字符爲大寫,這樣有一種顯目的感受。最好不要用「GNUmakefile」,這個文件是GNU的make識別的。有另一些make只對全小寫的「makefile」文件名敏感,可是基本上來講,大多數的make都支持「makefile」和「Makefile」這兩種默認文件名。
固然,你可使用別的文件名來書寫Makefile,好比:「Make.Linux」,「Make.Solaris」,「Make.AIX」等,若是要指定特定的Makefile,你可使用make的「-f」和「--file」參數,如:make -f Make.Linux或make --file Make.AIX。
在Makefile使用include關鍵字能夠把別的Makefile包含進來,這很像C語言的#include,被包含的文件會原模原樣的放在當前文件的包含位置。include的語法是:
include<filename>filename能夠是當前操做系統Shell的文件模式(能夠保含路徑和通配符)
在include前面能夠有一些空字符,可是毫不能是[Tab]鍵開始。include和能夠用一個或多個空格隔開。舉個例子,你有這樣幾個Makefile:a.mk、b.mk、c.mk,還有一個文件叫foo.make,以及一個變量$(bar),其包含了e.mk和f.mk,那麼,下面的語句:
include foo.make *.mk $(bar)
等價於:
include foo.make a.mk b.mk c.mk e.mk f.mk
make命令開始時,會把找尋include所指出的其它Makefile,並把其內容安置在當前的位置。就好像C/C++的#include指令同樣。若是文件都沒有指定絕對路徑或是相對路徑的話,make會在當前目錄下首先尋找,若是當前目錄下沒有找到,那麼,make還會在下面的幾個目錄下找:
1.若是make執行時,有「-I」或「--include-dir」參數,那麼make就會在這個參數所指定的目錄下去尋找。2.若是目錄/include(通常是:/usr/local/bin或/usr/include)存在的話,make也會去找。
若是有文件沒有找到的話,make會生成一條警告信息,但不會立刻出現致命錯誤。它會繼續載入其它的文件,一旦完成makefile的讀取,make會再重試這些沒有找到,或是不能讀取的文件,若是仍是不行,make纔會出現一條致命信息。若是你想讓make不理那些沒法讀取的文件,而繼續執行,你能夠在include前加一個減號「-」。如:
-include<filename>
其表示,不管include過程當中出現什麼錯誤,都不要報錯繼續執行。和其它版本make兼容的相關命令是sinclude,其做用和這一個是同樣的。
若是你的當前環境中定義了環境變量MAKEFILES,那麼,make會把這個變量中的值作一個相似於include的動做。這個變量中的值是其它的Makefile,用空格分隔。只是,它和include不一樣的是,從這個環境變中引入的Makefile的「目標」不會起做用,若是環境變量中定義的文件發現錯誤,make也會不理。
可是在這裏我仍是建議不要使用這個環境變量,由於只要這個變量一被定義,那麼當你使用make時,全部的Makefile都會受到它的影響,這毫不是你想看到的。在這裏提這個事,只是爲了告訴你們,也許有時候你的Makefile出現了怪事,那麼你能夠看看當前環境中有沒有定義這個變量。
GNU的make工做時的執行步驟入下:(想來其它的make也是相似)
1. 讀入全部的Makefile。
2. 讀入被include的其它Makefile。
3. 初始化文件中的變量。
4. 推導隱晦規則,並分析全部規則。
5. 爲全部的目標文件建立依賴關係鏈。
6. 根據依賴關係,決定哪些目標要從新生成。
7. 執行生成命令。
1-5步爲第一個階段,6-7爲第二個階段。第一個階段中,若是定義的變量被使用了,那麼,make會把其展開在使用的位置。但make並不會徹底立刻展開,make使用的是拖延戰術,若是變量出如今依賴關係的規則中,那麼僅當這條依賴被決定要使用了,變量纔會在其內部展開。
固然,這個工做方式你不必定要清楚,可是知道這個方式你也會對make更爲熟悉。有了這個基礎,後續部分也就容易看懂了。
規則包含兩個部分,一個是依賴關係,一個是生成目標的方法。
在Makefile中,規則的順序是很重要的,由於,Makefile中只應該有一個最終目標,其它的目標都是被這個目標所連帶出來的,因此必定要讓make知道你的最終目標是什麼。通常來講,定義在Makefile中的目標可能會有不少,可是第一條規則中的目標將被確立爲最終的目標。若是第一條規則中的目標有不少個,那麼,第一個目標會成爲最終的目標。make所完成的也就是這個目標。
好了,仍是讓咱們來看一看如何書寫規則。
foo.o: foo.c defs.h # foo模塊
cc -c -g foo.c
看到這個例子,各位應該不是很陌生了,前面也已說過,foo.o是咱們的目標,foo.c和defs.h是目標所依賴的源文件,而只有一個命令「cc -c -g foo.c」(以Tab鍵開頭)。這個規則告訴咱們兩件事:
1. 文件的依賴關係,foo.o依賴於foo.c和defs.h的文件,若是foo.c和defs.h的文件日期要比foo.o文件日期要新,或是foo.o不存在,那麼依賴關係發生。
2. 若是生成(或更新)foo.o文件。也就是那個cc命令,其說明了,如何生成foo.o這個文件。(固然foo.c文件include了defs.h文件)
targets : prerequisites
command
...
或是這樣:
targets : prerequisites ; command
command
...
targets是文件名,以空格分開,可使用通配符。通常來講,咱們的目標基本上是一個文件,但也有多是多個文件。
command是命令行,若是其不與「target:prerequisites」在一行,那麼,必須以[Tab鍵]開頭,若是和prerequisites在一行,那麼能夠用分號作爲分隔。(見上)
prerequisites也就是目標所依賴的文件(或依賴目標)。若是其中的某個文件要比目標文件要新,那麼,目標就被認爲是「過期的」,被認爲是須要重生成的。這個在前面已經講過了。
若是命令太長,你可使用反斜框(‘\’)做爲換行符。make對一行上有多少個字符沒有限制。規則告訴make兩件事,文件的依賴關係和如何成成目標文件。
通常來講,make會以UNIX的標準Shell,也就是/bin/sh來執行命令。
若是咱們想定義一系列比較相似的文件,咱們很天然地就想起使用通配符。make支持三各通配符:「*」,「?」和「[...]」。這是和Unix的B-Shell是相同的。
"~"
波浪號(「~」)字符在文件名中也有比較特殊的用途。若是是「~/test」,這就表示當前用戶的$HOME目錄下的test目錄。而「~hchen/test」則表示用戶hchen的宿主目錄下的test目錄。(這些都是Unix下的小知識了,make也支持)而在Windows或是MS-DOS下,用戶沒有宿主目錄,那麼波浪號所指的目錄則根據環境變量「HOME」而定。
"*"
通配符代替了你一系列的文件,如「*.c」表示因此後綴爲c的文件。一個須要咱們注意的是,若是咱們的文件名中有通配符,如:「*」,那麼能夠用轉義字符「\」,如「\*」來表示真實的「*」字符,而不是任意長度的字符串。
好吧,仍是先來看幾個例子吧:
clean:
rm -f *.o
上面這個例子我不很少說了,這是操做系統Shell所支持的通配符。這是在命令中的通配符。
print: *.c
lpr -p $?
touch print
上面這個例子說明了通配符也能夠在咱們的規則中,目標print依賴於全部的[.c]文件。其中的「$?」是一個自動化變量,我會在後面給你講述。
objects = *.o
上面這個例子,表示了,通符一樣能夠用在變量中。並非說[*.o]會展開,不!objects的值就是「*.o」。Makefile中的變量其實就是C/C++中的宏。若是你要讓通配符在變量中展開,也就是讓objects的值是全部[.o]的文件名的集合,那麼,你能夠這樣:
objects := $(wildcard *.o)
這種用法由關鍵字「wildcard」指出,關於Makefile的關鍵字,咱們將在後面討論。
在一些大的工程中,有大量的源文件,咱們一般的作法是把這許多的源文件分類,並存放在不一樣的目錄中。因此,當make須要去找尋文件的依賴關係時,你能夠在文件前加上路徑,但最好的方法是把一個路徑告訴make,讓make在自動去找。
Makefile文件中的特殊變量「VPATH」就是完成這個功能的,若是沒有指明這個變量,make只會在當前的目錄中去找尋依賴文件和目標文件。若是定義了這個變量,那麼,make就會在噹噹前目錄找不到的狀況下,到所指定的目錄中去找尋文件了。
VPATH = src:../headers
上面的的定義指定兩個目錄,「src」和「../headers」,make會按照這個順序進行搜索。目錄由「冒號」分隔。(固然,當前目錄永遠是最高優先搜索的地方)
另外一個設置文件搜索路徑的方法是使用make的「vpath」關鍵字(注意,它是全小寫的),這不是變量,這是一個make的關鍵字,這和上面提到的那個VPATH變量很相似,可是它更爲靈活。它能夠指定不一樣的文件在不一樣的搜索目錄中。這是一個很靈活的功能。它的使用方法有三種:
1. vpath < pattern> < directories> 爲符合模式< pattern>的文件指定搜索目錄<directories>。
2. vpath < pattern> 清除符合模式< pattern>的文件的搜索目錄。
3. vpath 清除全部已被設置好了的文件搜索目錄。
vapth使用方法中的< pattern>須要包含「%」字符。「%」的意思是匹配零或若干字符,例如,「%.h」表示全部以「.h」結尾的文件。< pattern>指定了要搜索的文件集,而< directories>則指定了的文件集的搜索的目錄。例如:
vpath %.h ../headers
該語句表示,要求make在「../headers」目錄下搜索全部以「.h」結尾的文件。(若是某文件在當前目錄沒有找到的話)
咱們能夠連續地使用vpath語句,以指定不一樣搜索策略。若是連續的vpath語句中出現了相同的< pattern>,或是被重複了的< pattern>,那麼,make會按照vpath語句的前後順序來執行搜索。如:
vpath %.c foo
vpath % blish
vpath %.c bar
其表示「.c」結尾的文件,先在「foo」目錄,而後是「blish」,最後是「bar」目錄。
vpath %.c foo:bar
vpath % blish
而上面的語句則表示「.c」結尾的文件,先在「foo」目錄,而後是「bar」目錄,最後纔是「blish」目錄。
最先先的一個例子中,咱們提到過一個「clean」的目標,這是一個「僞目標」,
clean:
rm *.o temp
正像咱們前面例子中的「clean」同樣,即然咱們生成了許多文件編譯文件,咱們也應該提供一個清除它們的「目標」以備完整地重編譯而用。 (以「make clean」來使用該目標)
由於,咱們並不生成「clean」這個文件。「僞目標」並非一個文件,只是一個標籤,因爲「僞目標」不是文件,因此make沒法生成它的依賴關係和決定它是否要執行。咱們只有經過顯示地指明這個「目標」才能讓其生效。固然,「僞目標」的取名不能和文件名重名,否則其就失去了「僞目標」的意義了。
固然,爲了不和文件重名的這種狀況,咱們可使用一個特殊的標記「.PHONY」來顯示地指明一個目標是「僞目標」,向make說明,無論是否有這個文件,這個目標就是「僞目標」。
.PHONY : clean
只要有這個聲明,無論是否有「clean」文件,要運行「clean」這個目標,只有「make clean」這樣。因而整個過程能夠這樣寫:
.PHONY: clean
clean:
rm *.o temp
僞目標通常沒有依賴的文件。可是,咱們也能夠爲僞目標指定所依賴的文件。僞目標一樣能夠做爲「默認目標」,只要將其放在第一個。一個示例就是,若是你的Makefile須要一口氣生成若干個可執行文件,但你只想簡單地敲一個make完事,而且,全部的目標文件都寫在一個Makefile中,那麼你可使用「僞目標」這個特性:
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
咱們知道,Makefile中的第一個目標會被做爲其默認目標。咱們聲明瞭一個「all」的僞目標,其依賴於其它三個目標。因爲僞目標的特性是,老是被執行的,因此其依賴的那三個目標就老是不如「all」這個目標新。因此,其它三個目標的規則老是會被決議。也就達到了咱們一口氣生成多個目標的目的。「.PHONY : all」聲明瞭「all」這個目標爲「僞目標」。
隨便提一句,從上面的例子咱們能夠看出,目標也能夠成爲依賴。因此,僞目標一樣也可成爲依賴。看下面的例子:
.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
「makeclean」將清除全部要被清除的文件。「cleanobj」和「cleandiff」這兩個僞目標有點像「子程序」的意思。咱們能夠輸入「makecleanall」和「make cleanobj」和「makecleandiff」命令來達到清除不一樣種類文件的目的
Makefile的規則中的目標能夠不止一個,其支持多目標,有可能咱們的多個目標同時依賴於一個文件,而且其生成的命令大致相似。因而咱們就能把其合併起來。固然,多個目標的生成規則的執行命令是同一個,這可能會可咱們帶來麻煩,不過好在咱們的可使用一個自動化變量「$@」(關於自動化變量,將在後面講述),這個變量表示着目前規則中全部的目標的集合,這樣說可能很抽象,仍是看一個例子吧。
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
上述規則等價於:
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
其中,-$(subst output,,$@)中的「$」表示執行一個Makefile的函數,函數名爲subst,後面的爲參數。關於函數,將在後面講述。這裏的這個函數是截取字符串的意思,「$@」表示目標的集合,就像一個數組,「$@」依次取出目標,並執於命令。
靜態模式能夠更加容易地定義多目標的規則,可讓咱們的規則變得更加的有彈性和靈活。咱們仍是先來看一下語法:
<targets...>: <target-pattern>: <prereq-patterns ...>
<commands>
...
targets定義了一系列的目標文件,能夠有通配符。是目標的一個集合。
target-parrtern是指明瞭targets的模式,也就是的目標集模式。
prereq-parrterns是目標的依賴模式,它對target-parrtern造成的模式再進行一次依賴目標的定義。
這樣描述這三個東西,可能仍是沒有說清楚,仍是舉個例子來講明一下吧。若是咱們的<target-parrtern>定義成「%.o」,意思是咱們的集合中都是以「.o」結尾的,而若是咱們的<prereq-parrterns>定義成「%.c」,意思是對<target-parrtern>所造成的目標集進行二次定義,其計算方法是,取<target-parrtern>模式中的「%」(也就是去掉了[.o]這個結尾),併爲其加上[.c]這個結尾,造成的新集合。
因此,咱們的「目標模式」或是「依賴模式」中都應該有「%」這個字符,若是你的文件名中有「%」那麼你可使用反斜槓「\」進行轉義,來標明真實的「%」字符。
看一個例子:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
上面的例子中,指明瞭咱們的目標從$object中獲取,「%.o」代表要全部以「.o」結尾的目標,也就是「foo.o bar.o」,也就是變量$object集合的模式,而依賴模式「%.c」則取模式「%.o」的「%」,也就是「foobar」,併爲其加下「.c」的後綴,因而,咱們的依賴目標就是「foo.cbar.c」。而命令中的「$<」和「$@」則是自動化變量,「$<」表示全部的依賴目標集(也就是「foo.c bar.c」),「$@」表示目標集(也褪恰癴oo.o bar.o」)。因而,上面的規則展開後等價於下面的規則:
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
試想,若是咱們的「%.o」有幾百個,那種咱們只要用這種很簡單的「靜態模式規則」就能夠寫完一堆規則,實在是太有效率了。「靜態模式規則」的用法很靈活,若是用得好,那會一個很強大的功能。再看一個例子:
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
$(filter%.o,$(files))表示調用Makefile的filter函數,過濾「$filter」集,只要其中模式爲「%.o」的內容。其的它內容,我就不用多說了吧。這個例字展現了Makefile中更大的彈性。
在Makefile中,咱們的依賴關係可能會須要包含一系列的頭文件,好比,若是咱們的main.c中有一句「#include "defs.h"」,那麼咱們的依賴關係應該是:
main.o : main.c defs.h
可是,若是是一個比較大型的工程,你必需清楚哪些C文件包含了哪些頭文件,而且,你在加入或刪除頭文件時,也須要當心地修改Makefile,這是一個很沒有維護性的工做。爲了不這種繁重而又容易出錯的事情,咱們可使用C/C++編譯的一個功能。大多數的C/C++編譯器都支持一個「-M」的選項,即自動找尋源文件中包含的頭文件,並生成一個依賴關係。例如,若是咱們執行下面的命令:
cc -M main.c
其輸出是:
main.o : main.c defs.h
因而由編譯器自動生成的依賴關係,這樣一來,你就沒必要再手動書寫若干文件的依賴關係,而由編譯器自動生成了。須要提醒一句的是,若是你使用GNU的C/C++編譯器,你得用「-MM」參數,否則,「-M」參數會把一些標準庫的頭文件也包含進來。
gcc-M main.c的輸出是:
main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
/usr/include/bits/sched.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/include/bits/wchar.h /usr/include/gconv.h \
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
/usr/include/bits/stdio_lim.h
gcc-MM main.c的輸出則是:
main.o: main.c defs.h
那麼,編譯器的這個功能如何與咱們的Makefile聯繫在一塊兒呢。由於這樣一來,咱們的Makefile也要根據這些源文件從新生成,讓Makefile自已依賴於源文件?這個功能並不現實,不過咱們能夠有其它手段來迂迴地實現這一功能。GNU組織建議把編譯器爲每個源文件的自動生成的依賴關係放到一個文件中,爲每個「name.c」的文件都生成一個「name.d」的Makefile文件,[.d]文件中就存放對應[.c]文件的依賴關係。
因而,咱們能夠寫出[.c]文件和[.d]文件的依賴關係,並讓make自動更新或自成[.d]文件,並把其包含在咱們的主Makefile中,這樣,咱們就能夠自動化地生成每一個文件的依賴關係了。
這裏,咱們給出了一個模式規則來產生[.d]文件:
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.
; \
sed 's,$∗\.o[ :]*,\1.o $@ : ,g' < $@.
> $@; \
rm -f $@.
這個規則的意思是,全部的[.d]文件依賴於[.c]文件,「rm-f $@」的意思是刪除全部的目標,也就是[.d]文件,第二行的意思是,爲每一個依賴文件「$<」,也就是[.c]文件生成依賴文件,「$@」表示模式「%.d」文件,若是有一個C文件是name.c,那麼「%」就是「name」,「
」意爲一個隨機編號,第二行生成的文件有多是「name.d.12345」,第三行使用sed命令作了一個替換,關於sed命令的用法請參看相關的使用文檔。第四行就是刪除臨時文件。
總而言之,這個模式要作的事就是在編譯器生成的依賴關係中加入[.d]文件的依賴,即把依賴關係:
main.o : main.c defs.h
轉成:
main.o main.d : main.c defs.h
因而,咱們的[.d]文件也會自動更新了,並會自動生成了,固然,你還能夠在這個[.d]文件中加入的不僅是依賴關係,包括生成的命令也可一併加入,讓每一個[.d]文件都包含一個完賴的規則。一旦咱們完成這個工做,接下來,咱們就要把這些自動生成的規則放進咱們的主Makefile中。咱們可使用Makefile的「include」命令,來引入別的Makefile文件(前面講過),例如:
sources = foo.c bar.c
include $(sources:.c=.d)
上述語句中的「$(sources:.c=.d)」中的「.c=.d」的意思是作一個替換,把變量$(sources)全部[.c]的字串都替換成[.d],關於這個「替換」的內容,在後面我會有更爲詳細的講述。固然,你得注意次序,由於include是按次來載入文件,最早載入的[.d]文件中的目標會成爲默認目標
每條規則中的命令和操做系統Shell的命令行是一致的。make會一按順序一條一條的執行命令,每條命令的開頭必須以[Tab]鍵開頭,除非,命令是緊跟在依賴規則後面的分號後的。在命令行之間中的空格或是空行會被忽略,可是若是該空格或空行是以Tab鍵開頭的,那麼make會認爲其是一個空命令。
咱們在UNIX下可能會使用不一樣的Shell,可是make的命令默認是被「/bin/sh」——UNIX的標準Shell解釋執行的。除非你特別指定一個其它的Shell。Makefile中,「#」是註釋符,很像C/C++中的「//」,其後的本行字符都被註釋。
一般,make會把其要執行的命令行在命令執行前輸出到屏幕上。當咱們用「@」字符在命令行前,那麼,這個命令將不被make顯示出來,最具表明性的例子是,咱們用這個功能來像屏幕顯示一些信息。如:
@echo 正在編譯XXX模塊......
當make執行時,會輸出「正在編譯XXX模塊......」字串,但不會輸出命令,若是沒有「@」,那麼,make將輸出:
echo 正在編譯XXX模塊......
正在編譯XXX模塊......
若是make執行時,帶入make參數「-n」或「--just-print」,那麼其只是顯示命令,但不會執行命令,這個功能頗有利於咱們調試咱們的Makefile,看看咱們書寫的命令是執行起來是什麼樣子的或是什麼順序的。
而make參數「-s」或「--slient」則是全面禁止命令的顯示。
當依賴目標新於目標時,也就是當規則的目標須要被更新時,make會一條一條的執行其後的命令。須要注意的是,若是你要讓上一條命令的結果應用在下一條命令時,你應該使用分號分隔這兩條命令。好比你的第一條命令是cd命令,你但願第二條命令得在cd以後的基礎上運行,那麼你就不能把這兩條命令寫在兩行上,而應該把這兩條命令寫在一行上,用分號分隔。如:
示例一:
exec:
cd /home/hchen
pwd
示例二:
exec:
cd /home/hchen; pwd
當咱們執行「make exec」時,第一個例子中的cd沒有做用,pwd會打印出當前的Makefile目錄,而第二個例子中,cd就起做用了,pwd會打印出「/home/hchen」。
make通常是使用環境變量SHELL中所定義的系統Shell來執行命令,默認狀況下使用UNIX的標準Shell——/bin/sh來執行命令。但在MS-DOS下有點特殊,由於MS-DOS下沒有SHELL環境變量,固然你也能夠指定。若是你指定了UNIX風格的目錄形式,首先,make會在SHELL所指定的路徑中找尋命令解釋器,若是找不到,其會在當前盤符中的當前目錄中尋找,若是再找不到,其會在PATH環境變量中所定義的全部路徑中尋找。MS-DOS中,若是你定義的命令解釋器沒有找到,其會給你的命令解釋器加上諸如「.exe」、「.com」、「.bat」、「.sh」等後綴。
每當命令運行完後,make會檢測每一個命令的返回碼,若是命令返回成功,那麼make會執行下一條命令,當規則中全部的命令成功返回後,這個規則就算是成功完成了。若是一個規則中的某個命令出錯了(命令退出碼非零),那麼make就會終止執行當前規則,這將有可能終止全部規則的執行。
有些時候,命令的出錯並不表示就是錯誤的。例如mkdir命令,咱們必定須要創建一個目錄,若是目錄不存在,那麼mkdir就成功執行,萬事大吉,若是目錄存在,那麼就出錯了。咱們之因此使用mkdir的意思就是必定要有這樣的一個目錄,因而咱們就不但願mkdir出錯而終止規則的運行。
爲了作到這一點,忽略命令的出錯,咱們能夠在Makefile的命令行前加一個減號「-」(在Tab鍵以後),標記爲無論命令出不出錯都認爲是成功的。如:
clean:
-rm -f *.o
還有一個全局的辦法是,給make加上「-i」或是「--ignore-errors」參數,那麼,Makefile中全部命令都會忽略錯誤。而若是一個規則是以「.IGNORE」做爲目標的,那麼這個規則中的全部命令將會忽略錯誤。這些是不一樣級別的防止命令出錯的方法,你能夠根據你的不一樣喜歡設置。
還有一個要提一下的make的參數的是「-k」或是「--keep-going」,這個參數的意思是,若是某規則中的命令出錯了,那麼就終目該規則的執行,但繼續執行其它規則。
在一些大的工程中,咱們會把咱們不一樣模塊或是不一樣功能的源文件放在不一樣的目錄中,咱們能夠在每一個目錄中都書寫一個該目錄的Makefile,這有利於讓咱們的Makefile變得更加地簡潔,而不至於把全部的東西所有寫在一個Makefile中,這樣會很難維護咱們的Makefile,這個技術對於咱們模塊編譯和分段編譯有着很是大的好處。
例如,咱們有一個子目錄叫subdir,這個目錄下有個Makefile文件,來指明瞭這個目錄下文件的編譯規則。那麼咱們總控的Makefile能夠這樣書寫:
subsystem:
cd subdir && $(MAKE)
其等價於:
subsystem:
$(MAKE) -C subdir
定義$(MAKE)宏變量的意思是,也許咱們的make須要一些參數,因此定義成一個變量比較利於維護。這兩個例子的意思都是先進入「subdir」目錄,而後執行make命令。
咱們把這個Makefile叫作「總控Makefile」,總控Makefile的變量能夠傳遞到下級的Makefile中(若是你顯示的聲明),可是不會覆蓋下層的Makefile中所定義的變量,除非指定了「-e」參數。
若是你要傳遞變量到下級Makefile中,那麼你可使用這樣的聲明:
export<variable ...>
若是你不想讓某些變量傳遞到下級Makefile中,那麼你能夠這樣聲明:
unexport<variable ...>
如:
示例一:
export variable = value
其等價於:
variable = value
export variable
其等價於:
export variable := value
其等價於:
variable := value
export variable
示例二:
export variable += value
其等價於:
variable += value
export variable
若是你要傳遞全部的變量,那麼,只要一個export就好了。後面什麼也不用跟,表示傳遞全部的變量。
須要注意的是,有兩個變量,一個是SHELL,一個是MAKEFLAGS,這兩個變量無論你是否export,其老是要傳遞到下層Makefile中,特別是MAKEFILES變量,其中包含了make的參數信息,若是咱們執行「總控Makefile」時有make參數或是在上層Makefile中定義了這個變量,那麼MAKEFILES變量將會是這些參數,並會傳遞到下層Makefile中,這是一個系統級的環境變量。
可是make命令中的有幾個參數並不往下傳遞,它們是「-C」,「-f」,「-h」「-o」和「-W」(有關Makefile參數的細節將在後面說明),若是你不想往下層傳遞參數,那麼,你能夠這樣來:
subsystem:
cd subdir && $(MAKE) MAKEFLAGS=
若是你定義了環境變量MAKEFLAGS,那麼你得確信其中的選項是你們都會用到的,若是其中有「-t」,「-n」,和「-q」參數,那麼將會有讓你意想不到的結果,或許會讓你異常地恐慌。
還有一個在「嵌套執行」中比較有用的參數,「-w」或是「--print-directory」會在make的過程當中輸出一些信息,讓你看到目前的工做目錄。好比,若是咱們的下級make目錄是「/home/hchen/gnu/make」,若是咱們使用「make -w」來執行,那麼當進入該目錄時,咱們會看到:
make: Entering directory `/home/hchen/gnu/make'.
而在完成下層make後離開目錄時,咱們會看到:
make: Leaving directory `/home/hchen/gnu/make'
當你使用「-C」參數來指定make下層Makefile時,「-w」會被自動打開的。若是參數中有「-s」(「--slient」)或是「--no-print-directory」,那麼,「-w」老是失效的。
若是Makefile中出現一些相同命令序列,那麼咱們能夠爲這些相同的命令序列定義一個變量。定義這種命令序列的語法以「define」開始,以「endef」結束,如:
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
這裏,「run-yacc」是這個命令包的名字,其不要和Makefile中的變量重名。在「define」和「endef」中的兩行就是命令序列。這個命令包中的第一個命令是運行Yacc程序,由於Yacc程序老是生成「y.tab.c」的文件,因此第二行的命令就是把這個文件改更名字。仍是把這個命令包放到一個示例中來看看吧。
foo.c : foo.y
$(run-yacc)
咱們能夠看見,要使用這個命令包,咱們就好像使用變量同樣。在這個命令包的使用中,命令包「run-yacc」中的「$^」就是「foo.y」,「$@」就是「foo.c」(有關這種以「$」開頭的特殊變量,咱們會在後面介紹),make在執行命令包時,命令包中的每一個命令會被依次獨立執行。
———— 在 Makefile中的定義的變量,就像是C/C++語言中的宏同樣,他表明了一個文本字串,在Makefile中執行的時候其會自動原模原樣地展開在所使用的地方。其與C/C++所不一樣的是,你能夠在Makefile中改變其值。在Makefile中,變量可使用在「目標」,「依賴目標」,「命令」或是 Makefile的其它部分中。變量的命名字能夠包含字符、數字,下劃線(能夠是數字開頭),但不該該含有「:」、「#」、「=」或是空字符(空格、回車等)。變量是大小寫敏感的,「foo」、「Foo」和「FOO」是三個不一樣的變量名。傳統的Makefile的變量名是全大寫的命名方式,但我推薦使用大小寫搭配的變量名,如:MakeFlags。這樣能夠避免和系統的變量衝突,而發生意外的事情。有一些變量是很奇怪字串,如「$<」、「$@」等,這些是自動化變量,我會在後面介紹。