在linux開發中,應用程序的編譯基本都採用GNU的make工具,而make搭配Makefile來實現工程代碼的編譯,在越是大型複雜的項目中,make的強悍之處越是明顯。在使用了一段時間make後,對其用法進行分析。本文是在學習了陳皓的「跟我一塊兒學Makefile」文章後,對本身學習的小結。linux
如今,咱們有一個C++的項目須要進行編譯,項目包含3個頭文件,3個.cpp文件,分別是main.cpp,main.h,read.cpp,read.h,write.cpp,write.h,下面就是咱們的Makefile了:c++
咱們須要生成的目標是可執行文件test,test由中間目標文件.o連接生成,.o文件根據依賴關係由相應的頭文件和.cpp源文件編譯而成,僞目標clean用於清除目標文件和.o文件。函數
在個人Makefile中,咱們發現編譯工具cc即便不指明爲g++也能夠正常編譯,可是若是是在目標文件連接時不指明的話,會有提示找不到相關的庫,那關於這個cc究竟是根據什麼來選擇編譯工具的呢?經過查閱資料和測試發現,編譯程序cc通常默認的都是gcc,當咱們對源文件編譯時,對於.c文件,gcc和g++分別識別爲c和c++程序,對於.cpp文件,二者都會識別爲c++程序,因此在編譯時咱們徹底能夠用默認cc來編譯.c和.cpp文件(固然對於其餘平臺或嵌入式中仍是要指定對應的編譯工具,防止出錯)。可是連接時,須要對.o中間文件連接庫生成可執行程序,g++會連接c++的庫,gcc連接c的庫,因此若是連接時須要指明編譯器。在日常使用中,考慮到的易維護和防出錯,都是統一將編譯和連接的編譯器都指明爲g++。工具
好,經過這個Makefile實現了咱們的功能需求,可是咱們發現,這種Makefile雖然邏輯簡單,可是一旦當項目體積很是大時,寫Makefile會是一件很是痛苦的事,須要寫幾百個中間目標文件的依賴關係,OK,別擔憂,GNU的make很強大,它能夠經過隱晦規則進行自動推導。學習
GNU的make工具存在隱晦規則,只要make看到一個.o文件,他會默認把對應的.c或.cpp(後面討論時會都用.c文件來討論)文件加到依賴關係中,好比若是有一個main.o,那麼他會默認把main.c加到其依賴關係中,而且make會對其進行自動推導,包括文件的依賴關係和後面的命令。那麼,咱們又能夠獲得一個新的Makefile了。測試
這樣咱們獲得了一個看起來更簡潔的Makefile,咱們不用書寫每一箇中間目標文件的編譯指令了,由於make會自動推導,可是這樣貌似只是部分下降了咱們的勞動力,對於幾百個源文件的程序,咱們仍是須要寫出來每一個文件隱晦規則以外的依賴關係,雖然不用寫編譯指令,但仍是很是多,並且這樣的Makefile通用性並不高,若是咱們寫了個其餘的測試程序,這個Makefile咱們仍是須要更改許多,OK,make這個工具的靜態模式和自動變量能夠幫咱們解決這個問題。spa
Make中的靜態模式的語法以下:3d
<targets ...>: <target-pattern>: <prereq-patterns ...> blog
<commands> 接口
....
targets定義了一系列的目標文件,能夠有通配符。是目標的一個集合。
target-parrtern是指明瞭targets的模式,也就是的目標集模式。
prereq-parrterns是目標的依賴模式,它對target-parrtern造成的模式再進行一次依賴目標的定義。
舉個例子,對於前面的objects目標集合,<target-parrtern>定義成「%.o」,意思是咱們的<target>集合中都是以「.o」結尾的,而若是咱們的<prereq-parrterns>定義成「%.c」,意思是對<target-parrtern>所造成的目標集進行二次定義,其計算方法是,取<target-parrtern>模式中的「%」(也就是去掉了[.o]這個結尾),併爲其加上[.c]這個結尾,造成的新集合。如main.o經過模式取到的依賴目標就是main.c文件。靜態模式幫助咱們完成將幾百個源文件用一種通用的模式來表明,咱們能夠不用寫出全部中間文件的依賴關係,只要用一個靜態模式就OK了。
接下來有個問題,在上述的模式規則中,目標和依賴文件都是一系例的文件,那麼咱們如何書寫一個命令來完成從不一樣的依賴文件生成相應的目標?由於在每一次的對模式規則的解析時,都會是不一樣的目標和依賴文件。這就須要make 的自動化變量。
Make的自動化變量。 所謂自動化變量,就是這種變量會把模式中所定義的一系列的文件自動地挨個取出,直至全部的符合模式的文件都取完了。這種自動化變量只應出如今規則的命令中。
下面是全部的自動化變量及其說明:
$@
表示規則中的目標文件集。在模式規則中,若是有多個目標,那麼,"$@"就是匹配於
目標中模式定義的集合。
$%
僅當目標是函數庫文件中,表示規則中的目標成員名。例如,若是一個目標是"foo.a (bar.o)",那麼,"$%"就是"bar.o","$@"就是"foo.a"。若是目標不是函數庫文件(Unix下是[.a],Windows下是[.lib]),那麼,其值爲空。
$<
依賴目標中的第一個目標名字。若是依賴目標是以模式(即"%")定義的,那麼"$<"將是符合模式的一系列的文件集。注意,其是一個一個取出來的。
$?
全部比目標新的依賴目標的集合。以空格分隔。
$^
全部的依賴目標的集合。以空格分隔。若是在依賴目標中有多個重複的,那個這個變量會去除重複的依賴目標,只保留一份。
$+
這個變量很像"$^",也是全部依賴目標的集合。只是它不去除重複的依賴目標。
$*
這個變量表示目標模式中"%"及其以前的部分。若是目標是"dir/a.foo.b",而且目標的模式是"a.%.b",那麼,"$*"的值就是"dir/a.foo"。這個變量對於構造有關聯的文件名是比較有較。若是目標中沒有模式的定義,那麼"$*"也就不能被推導出,可是,若是目標文件的後綴是make所識別的,那麼"$*"就是除了後綴的那一部分。例如:若是目標是"foo.c",由於".c"是make所能識別的後綴名,因此,"$*"的值就是"foo"。這個特性是GNU make的, 頗有可能不兼容於其它版本的make,因此,你應該儘可能避免使用"$*",除非是在隱含規則或是靜態模式中。若是目標中的後綴是make所不能識別的,那麼"$*"就是空值。
更新後的Makefile以下:
OK,寫到這裏,咱們的Makefile已經很是很是簡潔了,可是這裏面存在兩個問題,第一個問題是,咱們全部的目標文件的依賴關係都只有相應的源文件,並無其餘所需頭文件的依賴關係,好比main.o的依賴關係包含了main.cpp,main.h,read.h,write.h,在首次編譯時,依賴關係會找到相應的相應的頭文件,可是以後若是修改了某個頭文件,則再次編譯頭文件並不會更新,因此這種方式中缺乏頭文件的依賴關係,一種解決方法是每次從新編譯時,先清空以前的編譯目標文件就OK了,還有一種方法是創建.c文件的依賴關係(下次再寫)。
第二個問題是,咱們發現,咱們仍是要寫出全部的目標文件集,一樣的,對於幾百個中間文件的項目來講,這也是很頭疼的事,咱們能夠經過通配符和一些函數來解決這個問題。
咱們的需求是讓make本身找到當前目錄下全部的源文件,而且對這些源文件根據依賴關係進行編譯處理,實現徹底的自動化。首先咱們須要用通配符*來找到全部的源文件,並對應生成全部源文件對應的中間目標文件,這主要經過patsubst函數來實現,該函數是模式字符串替換函數(對於全部的函數能夠查詢後文的函數表),幫咱們將依賴目標集的全部源文件對應生成.o文件。
Makefile寫到這裏,應該是簡潔性和通用性都比較高的了,可是,如今的makefile在實際中可用性不高,由於實際項目中源文件每每放置在多個文件夾中,並且還會設置許多編譯參數,因此下面將會寫一個體現源文件結構複雜度和編譯器參數的makefile。
如今,咱們假設,咱們的read.cpp,read.h在當前目錄的read目錄下,write.cpp和write.h在當前目錄下的write目錄下,那麼咱們新的makefile又出爐了。
原有的wildcard是匹配當前目錄下全部的源文件,可是有多個文件夾,就沒法匹配到了,因而在這裏咱們就可使用VPATH這個變量(這個make默認識別的變量用於在當前目錄下查找文件查找不到時,默認到VPATH路徑下查找),同時還須要使用一個函數forreach。因爲這裏只匹配了源文件,相應的頭文件的位置沒有包含,因此還須要包含一下頭文件。
OK,到這裏,貌似咱們已經寫出了一個比較好的Makefile,可是,實際的編譯參數咱們都還沒涉及,下面就具體分析一下,GNU下的編譯工具的編譯參數。
有時咱們須要連接第三方庫,那麼如何連接庫,如何使用庫中的函數?
首先,在源代碼中,咱們須要包含所使用的庫的接口函數的頭文件;
其次,在Makefile中,咱們須要在頭文件和庫的搜索路徑中分別加入所需頭文件和庫的路徑。假設庫和庫的頭文件在路徑/mnt/hgfs/share/miniupnp/miniupnpc-1.9.20160209路徑下,那麼包含方式
頭文件:-I/mnt/hgfs/share/miniupnp/miniupnpc-1.9.20160209 //加入頭文件路路徑
庫:-L/mnt/hgfs/share/miniupnp/miniupnpc-1.9.20160209 //加入庫文件路徑
若是庫的名字是libminiupnpc.a,那麼在連接時的參數就是 -lminiupnpc
相似於下圖:
結束。