1 跟我一塊兒寫 Makefile 2 /**/ 3 4 陳皓 (CSDN) 5 6 概述 7 —— 8 9 什麼是makefile?或許不少Winodws的程序員都不知道這個東西,由於那些Windows的IDE都爲你作了這個工做,但我以爲要做一個好的和professional的程序員,makefile仍是要懂。這就好像如今有這麼多的HTML的編輯器,但若是你想成爲一個專業人士,你仍是要了解HTML的標識的含義。特別在Unix下的軟件編譯,你就不能不本身寫makefile了,會不會寫makefile,從一個側面說明了一我的是否具有完成大型工程的能力。 10 11 由於,makefile關係到了整個工程的編譯規則。一個工程中的源文件不計數,其按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的規則來指定,哪些文件須要先編譯,哪些文件須要後編譯,哪些文件須要從新編譯,甚至於進行更復雜的功能操做,由於makefile就像一個Shell腳本同樣,其中也能夠執行操做系統的命令。 12 13 makefile帶來的好處就是——「自動化編譯」,一旦寫好,只須要一個make命令,整個工程徹底自動編譯,極大的提升了軟件開發的效率。make是一個命令工具,是一個解釋makefile中指令的命令工具,通常來講,大多數的IDE都有這個命令,好比:Delphi的make,Visual C++的nmake,Linux下GNU的make。可見,makefile都成爲了一種在工程方面的編譯方法。 14 15 如今講述如何寫makefile的文章比較少,這是我想寫這篇文章的緣由。固然,不一樣產商的make各不相同,也有不一樣的語法,但其本質都是在「文件依賴性」上作文章,這裏,我僅對GNU的make進行講述,個人環境是RedHat Linux 8.0,make的版本是3.80。必竟,這個make是應用最爲普遍的,也是用得最多的。並且其仍是最遵循於IEEE 1003.2-1992 標準的(POSIX.2)。 16 17 在這篇文檔中,將以C/C++的源碼做爲咱們基礎,因此必然涉及一些關於C/C++的編譯的知識,相關於這方面的內容,還請各位查看相關的編譯器的文檔。這裏所默認的編譯器是UNIX下的GCC和CC。 18 19 20 21 關於程序的編譯和連接 22 —————————— 23 24 在此,我想多說關於程序編譯的一些規範和方法,通常來講,不管是C、C++、仍是pas,首先要把源文件編譯成中間代碼文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,這個動做叫作編譯(compile)。而後再把大量的Object File合成執行文件,這個動做叫做連接(link)。 25 26 編譯時,編譯器須要的是語法的正確,函數與變量的聲明的正確。對於後者,一般是你須要告訴編譯器頭文件的所在位置(頭文件中應該只是聲明,而定義應該放在C/C++文件中),只要全部的語法正確,編譯器就能夠編譯出中間目標文件。通常來講,每一個源文件都應該對應於一箇中間目標文件(O文件或是OBJ文件)。 27 28 連接時,主要是連接函數和全局變量,因此,咱們能夠使用這些中間目標文件(O文件或是OBJ文件)來連接咱們的應用程序。連接器並無論函數所在的源文件,只管函數的中間目標文件(Object File),在大多數時候,因爲源文件太多,編譯生成的中間目標文件太多,而在連接時須要明顯地指出中間目標文件名,這對於編譯很不方便,因此,咱們要給中間目標文件打個包,在Windows下這種包叫「庫文件」(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。 29 30 總結一下,源文件首先會生成中間目標文件,再由中間目標文件生成執行文件。在編譯時,編譯器只檢測程序語法,和函數、變量是否被聲明。若是函數未被聲明,編譯器會給出一個警告,但能夠生成Object File。而在連接程序時,連接器會在全部的Object File中找尋函數的實現,若是找不到,那到就會報連接錯誤碼(Linker Error),在VC下,這種錯誤通常是:Link 2001錯誤,意思說是說,連接器未能找到函數的實現。你須要指定函數的Object File. 31 32 好,言歸正傳,GNU的make有許多的內容,閒言少敘,仍是讓咱們開始吧。 33 34 35 36 Makefile 介紹 37 ——————— 38 39 make命令執行時,須要一個 Makefile 文件,以告訴make命令須要怎麼樣的去編譯和連接程序。 40 41 首先,咱們用一個示例來講明Makefile的書寫規則。以便給你們一個感興認識。這個示例來源於GNU的make使用手冊,在這個示例中,咱們的工程有8個C文件,和3個頭文件,咱們要寫一個Makefile來告訴make命令如何編譯和連接這幾個文件。咱們的規則是: 42 1)若是這個工程沒有編譯過,那麼咱們的全部C文件都要編譯並被連接。 43 2)若是這個工程的某幾個C文件被修改,那麼咱們只編譯被修改的C文件,並連接目標程序。 44 3)若是這個工程的頭文件被改變了,那麼咱們須要編譯引用了這幾個頭文件的C文件,並連接目標程序。 45 46 只要咱們的Makefile寫得夠好,全部的這一切,咱們只用一個make命令就能夠完成,make命令會自動智能地根據當前的文件修改的狀況來肯定哪些文件須要重編譯,從而本身編譯所須要的文件和連接目標程序。 47 48 49 1、Makefile的規則 50 51 在講述這個Makefile以前,仍是讓咱們先來粗略地看一看Makefile的規則。 52 53 target ... : prerequisites ... 54 command 55 ... 56 ... 57 58 target也就是一個目標文件,能夠是Object File,也能夠是執行文件。還能夠是一個標籤(Label),對於標籤這種特性,在後續的「僞目標」章節中會有敘述。 59 60 prerequisites就是,要生成那個target所須要的文件或是目標。 61 62 command也就是make須要執行的命令。(任意的Shell命令) 63 64 這是一個文件的依賴關係,也就是說,target這一個或多個的目標文件依賴於prerequisites中的文件,其生成規則定義在command中。說白一點就是說,prerequisites中若是有一個以上的文件比target文件要新的話,command所定義的命令就會被執行。這就是Makefile的規則。也就是Makefile中最核心的內容。 65 66 說到底,Makefile的東西就是這樣一點,好像個人這篇文檔也該結束了。呵呵。還不盡然,這是Makefile的主線和核心,但要寫好一個Makefile還不夠,我會之後面一點一點地結合個人工做經驗給你慢慢到來。內容還多着呢。:) 67 68 69 2、一個示例 70 71 正如前面所說的,若是一個工程有3個頭文件,和8個C文件,咱們爲了完成前面所述的那三個規則,咱們的Makefile應該是下面的這個樣子的。 72 73 edit : main.o kbd.o command.o display.o / 74 insert.o search.o files.o utils.o 75 cc -o edit main.o kbd.o command.o display.o / 76 insert.o search.o files.o utils.o 77 78 main.o : main.c defs.h 79 cc -c main.c 80 kbd.o : kbd.c defs.h command.h 81 cc -c kbd.c 82 command.o : command.c defs.h command.h 83 cc -c command.c 84 display.o : display.c defs.h buffer.h 85 cc -c display.c 86 insert.o : insert.c defs.h buffer.h 87 cc -c insert.c 88 search.o : search.c defs.h buffer.h 89 cc -c search.c 90 files.o : files.c defs.h buffer.h command.h 91 cc -c files.c 92 utils.o : utils.c defs.h 93 cc -c utils.c 94 clean : 95 rm edit main.o kbd.o command.o display.o / 96 insert.o search.o files.o utils.o 97 98 反斜槓(/)是換行符的意思。這樣比較便於Makefile的易讀。咱們能夠把這個內容保存在文件爲「Makefile」或「makefile」的文件中,而後在該目錄下直接輸入命令「make」就能夠生成執行文件edit。若是要刪除執行文件和全部的中間目標文件,那麼,只要簡單地執行一下「make clean」就能夠了。 99 100 在這個makefile中,目標文件(target)包含:執行文件edit和中間目標文件(*.o),依賴文件(prerequisites)就是冒號後面的那些 .c 文件和 .h文件。每個 .o 文件都有一組依賴文件,而這些 .o 文件又是執行文件 edit 的依賴文件。依賴關係的實質上就是說明了目標文件是由哪些文件生成的,換言之,目標文件是哪些文件更新的。 101 102 在定義好依賴關係後,後續的那一行定義瞭如何生成目標文件的操做系統命令,必定要以一個Tab鍵做爲開頭。記住,make並無論命令是怎麼工做的,他只管執行所定義的命令。make會比較targets文件和prerequisites文件的修改日期,若是prerequisites文件的日期要比targets文件的日期要新,或者target不存在的話,那麼,make就會執行後續定義的命令。 103 104 這裏要說明一點的是,clean不是一個文件,它只不過是一個動做名字,有點像C語言中的lable同樣,其冒號後什麼也沒有,那麼,make就不會自動去找文件的依賴性,也就不會自動執行其後所定義的命令。要執行其後的命令,就要在make命令後明顯得指出這個lable的名字。這樣的方法很是有用,咱們能夠在一個makefile中定義不用的編譯或是和編譯無關的命令,好比程序的打包,程序的備份,等等。 105 106 107 108 3、make是如何工做的 109 110 在默認的方式下,也就是咱們只輸入make命令。那麼, 111 112 1、make會在當前目錄下找名字叫「Makefile」或「makefile」的文件。 113 2、若是找到,它會找文件中的第一個目標文件(target),在上面的例子中,他會找到「edit」這個文件,並把這個文件做爲最終的目標文件。 114 3、若是edit文件不存在,或是edit所依賴的後面的 .o 文件的文件修改時間要比edit這個文件新,那麼,他就會執行後面所定義的命令來生成edit這個文件。 115 4、若是edit所依賴的.o文件也不存在,那麼make會在當前文件中找目標爲.o文件的依賴性,若是找到則再根據那一個規則生成.o文件。(這有點像一個堆棧的過程) 116 5、固然,你的C文件和H文件是存在的啦,因而make會生成 .o 文件,而後再用 .o 文件生命make的終極任務,也就是執行文件edit了。 117 118 這就是整個make的依賴性,make會一層又一層地去找文件的依賴關係,直到最終編譯出第一個目標文件。在找尋的過程當中,若是出現錯誤,好比最後被依賴的文件找不到,那麼make就會直接退出,並報錯,而對於所定義的命令的錯誤,或是編譯不成功,make根本不理。make只管文件的依賴性,即,若是在我找了依賴關係以後,冒號後面的文件仍是不在,那麼對不起,我就不工做啦。 119 120 經過上述分析,咱們知道,像clean這種,沒有被第一個目標文件直接或間接關聯,那麼它後面所定義的命令將不會被自動執行,不過,咱們能夠顯示要make執行。即命令——「make clean」,以此來清除全部的目標文件,以便重編譯。 121 122 因而在咱們編程中,若是這個工程已被編譯過了,當咱們修改了其中一個源文件,好比file.c,那麼根據咱們的依賴性,咱們的目標file.o會被重編譯(也就是在這個依性關係後面所定義的命令),因而file.o的文件也是最新的啦,因而file.o的文件修改時間要比edit要新,因此edit也會被從新連接了(詳見edit目標文件後定義的命令)。 123 124 而若是咱們改變了「command.h」,那麼,kdb.o、command.o和files.o都會被重編譯,而且,edit會被重連接。 125 126 127 4、makefile中使用變量 128 129 在上面的例子中,先讓咱們看看edit的規則: 130 131 edit : main.o kbd.o command.o display.o / 132 insert.o search.o files.o utils.o 133 cc -o edit main.o kbd.o command.o display.o / 134 insert.o search.o files.o utils.o 135 136 咱們能夠看到[.o]文件的字符串被重複了兩次,若是咱們的工程須要加入一個新的[.o]文件,那麼咱們須要在兩個地方加(應該是三個地方,還有一個地方在clean中)。固然,咱們的makefile並不複雜,因此在兩個地方加也不累,但若是makefile變得複雜,那麼咱們就有可能會忘掉一個須要加入的地方,而致使編譯失敗。因此,爲了makefile的易維護,在makefile中咱們能夠使用變量。makefile的變量也就是一個字符串,理解成C語言中的宏可能會更好。 137 138 好比,咱們聲明一個變量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正無論什麼啦,只要可以表示obj文件就好了。咱們在makefile一開始就這樣定義: 139 140 objects = main.o kbd.o command.o display.o / 141 insert.o search.o files.o utils.o 142 143 因而,咱們就能夠很方便地在咱們的makefile中以「$(objects)」的方式來使用這個變量了,因而咱們的改良版makefile就變成下面這個樣子: 144 145 objects = main.o kbd.o command.o display.o / 146 insert.o search.o files.o utils.o 147 148 edit : $(objects) 149 cc -o edit $(objects) 150 main.o : main.c defs.h 151 cc -c main.c 152 kbd.o : kbd.c defs.h command.h 153 cc -c kbd.c 154 command.o : command.c defs.h command.h 155 cc -c command.c 156 display.o : display.c defs.h buffer.h 157 cc -c display.c 158 insert.o : insert.c defs.h buffer.h 159 cc -c insert.c 160 search.o : search.c defs.h buffer.h 161 cc -c search.c 162 files.o : files.c defs.h buffer.h command.h 163 cc -c files.c 164 utils.o : utils.c defs.h 165 cc -c utils.c 166 clean : 167 rm edit $(objects) 168 169 170 因而若是有新的 .o 文件加入,咱們只需簡單地修改一下 objects 變量就能夠了。 171 172 關於變量更多的話題,我會在後續給你一一道來。 173 174 175 5、讓make自動推導 176 177 GNU的make很強大,它能夠自動推導文件以及文件依賴關係後面的命令,因而咱們就不必去在每個[.o]文件後都寫上相似的命令,由於,咱們的make會自動識別,並本身推導命令。 178 179 只要make看到一個[.o]文件,它就會自動的把[.c]文件加在依賴關係中,若是make找到一個whatever.o,那麼whatever.c,就會是whatever.o的依賴文件。而且 cc -c whatever.c 也會被推導出來,因而,咱們的makefile不再用寫得這麼複雜。咱們的是新的makefile又出爐了。 180 181 182 objects = main.o kbd.o command.o display.o / 183 insert.o search.o files.o utils.o 184 185 edit : $(objects) 186 cc -o edit $(objects) 187 188 main.o : defs.h 189 kbd.o : defs.h command.h 190 command.o : defs.h command.h 191 display.o : defs.h buffer.h 192 insert.o : defs.h buffer.h 193 search.o : defs.h buffer.h 194 files.o : defs.h buffer.h command.h 195 utils.o : defs.h 196 197 .PHONY : clean 198 clean : 199 rm edit $(objects) 200 201 這種方法,也就是make的「隱晦規則」。上面文件內容中,「.PHONY」表示,clean是個僞目標文件。 202 203 關於更爲詳細的「隱晦規則」和「僞目標文件」,我會在後續給你一一道來。 204 205 206 6、另類風格的makefile 207 208 即然咱們的make能夠自動推導命令,那麼我看到那堆[.o]和[.h]的依賴就有點不爽,那麼多的重複的[.h],能不能把其收攏起來,好吧,沒有問題,這個對於make來講很容易,誰叫它提供了自動推導命令和文件的功能呢?來看看最新風格的makefile吧。 209 210 objects = main.o kbd.o command.o display.o / 211 insert.o search.o files.o utils.o 212 213 edit : $(objects) 214 cc -o edit $(objects) 215 216 $(objects) : defs.h 217 kbd.o command.o files.o : command.h 218 display.o insert.o search.o files.o : buffer.h 219 220 .PHONY : clean 221 clean : 222 rm edit $(objects) 223 224 這種風格,讓咱們的makefile變得很簡單,但咱們的文件依賴關係就顯得有點凌亂了。魚和熊掌不可兼得。還看你的喜愛了。我是不喜歡這種風格的,一是文件的依賴關係看不清楚,二是若是文件一多,要加入幾個新的.o文件,那就理不清楚了。 225 226 7、清空目標文件的規則 227 228 每一個Makefile中都應該寫一個清空目標文件(.o和執行文件)的規則,這不只便於重編譯,也很利於保持文件的清潔。這是一個「修養」(呵呵,還記得個人《編程修養》嗎)。通常的風格都是: 229 230 clean: 231 rm edit $(objects) 232 233 更爲穩健的作法是: 234 235 .PHONY : clean 236 clean : 237 -rm edit $(objects) 238 239 前面說過,.PHONY意思表示clean是一個「僞目標」,。而在rm命令前面加了一個小減號的意思就是,也許某些文件出現問題,但不要管,繼續作後面的事。固然,clean的規則不要放在文件的開頭,否則,這就會變成make的默認目標,相信誰也不肯意這樣。不成文的規矩是——「clean歷來都是放在文件的最後」。 240 241 242 上面就是一個makefile的概貌,也是makefile的基礎,下面還有不少makefile的相關細節,準備好了嗎?準備好了就來。 243 244 Makefile 總述 245 ——————— 246 247 1、Makefile裏有什麼? 248 249 Makefile裏主要包含了五個東西:顯式規則、隱晦規則、變量定義、文件指示和註釋。 250 251 1、顯式規則。顯式規則說明了,如何生成一個或多的的目標文件。這是由Makefile的書寫者明顯指出,要生成的文件,文件的依賴文件,生成的命令。 252 253 2、隱晦規則。因爲咱們的make有自動推導的功能,因此隱晦的規則可讓咱們比較粗糙地簡略地書寫Makefile,這是由make所支持的。 254 255 3、變量的定義。在Makefile中咱們要定義一系列的變量,變量通常都是字符串,這個有點你C語言中的宏,當Makefile被執行時,其中的變量都會被擴展到相應的引用位置上。 256 257 4、文件指示。其包括了三個部分,一個是在一個Makefile中引用另外一個Makefile,就像C語言中的include同樣;另外一個是指根據某些狀況指定Makefile中的有效部分,就像C語言中的預編譯#if同樣;還有就是定義一個多行的命令。有關這一部分的內容,我會在後續的部分中講述。 258 259 5、註釋。Makefile中只有行註釋,和UNIX的Shell腳本同樣,其註釋是用「#」字符,這個就像C/C++中的「//」同樣。若是你要在你的Makefile中使用「#」字符,能夠用反斜框進行轉義,如:「/#」。 260 261 最後,還值得一提的是,在Makefile中的命令,必需要以[Tab]鍵開始。 262 263 264 2、Makefile的文件名 265 266 默認的狀況下,make命令會在當前目錄下按順序找尋文件名爲「GNUmakefile」、「makefile」、「Makefile」的文件,找到了解釋這個文件。在這三個文件名中,最好使用「Makefile」這個文件名,由於,這個文件名第一個字符爲大寫,這樣有一種顯目的感受。最好不要用「GNUmakefile」,這個文件是GNU的make識別的。有另一些make只對全小寫的「makefile」文件名敏感,可是基本上來講,大多數的make都支持「makefile」和「Makefile」這兩種默認文件名。 267 268 固然,你能夠使用別的文件名來書寫Makefile,好比:「Make.Linux」,「Make.Solaris」,「Make.AIX」等,若是要指定特定的Makefile,你能夠使用make的「-f」和「--file」參數,如:make -f Make.Linux或make --file Make.AIX。 269 270 271 3、引用其它的Makefile 272 273 在Makefile使用include關鍵字能夠把別的Makefile包含進來,這很像C語言的#include,被包含的文件會原模原樣的放在當前文件的包含位置。include的語法是: 274 275 include <filename> 276 277 filename能夠是當前操做系統Shell的文件模式(能夠保含路徑和通配符) 278 279 在include前面能夠有一些空字符,可是毫不能是[Tab]鍵開始。include和<filename>能夠用一個或多個空格隔開。舉個例子,你有這樣幾個Makefile:a.mk、b.mk、c.mk,還有一個文件叫foo.make,以及一個變量$(bar),其包含了e.mk和f.mk,那麼,下面的語句: 280 281 include foo.make *.mk $(bar) 282 283 等價於: 284 285 include foo.make a.mk b.mk c.mk e.mk f.mk 286 287 make命令開始時,會把找尋include所指出的其它Makefile,並把其內容安置在當前的位置。就好像C/C++的#include指令同樣。若是文件都沒有指定絕對路徑或是相對路徑的話,make會在當前目錄下首先尋找,若是當前目錄下沒有找到,那麼,make還會在下面的幾個目錄下找: 288 289 1、若是make執行時,有「-I」或「--include-dir」參數,那麼make就會在這個參數所指定的目錄下去尋找。 290 2、若是目錄<prefix>/include(通常是:/usr/local/bin或/usr/include)存在的話,make也會去找。 291 292 若是有文件沒有找到的話,make會生成一條警告信息,但不會立刻出現致命錯誤。它會繼續載入其它的文件,一旦完成makefile的讀取,make會再重試這些沒有找到,或是不能讀取的文件,若是仍是不行,make纔會出現一條致命信息。若是你想讓make不理那些沒法讀取的文件,而繼續執行,你能夠在include前加一個減號「-」。如: 293 294 -include <filename> 295 其表示,不管include過程當中出現什麼錯誤,都不要報錯繼續執行。和其它版本make兼容的相關命令是sinclude,其做用和這一個是同樣的。 296 297 298 4、環境變量 MAKEFILES 299 300 若是你的當前環境中定義了環境變量MAKEFILES,那麼,make會把這個變量中的值作一個相似於include的動做。這個變量中的值是其它的Makefile,用空格分隔。只是,它和include不一樣的是,從這個環境變中引入的Makefile的「目標」不會起做用,若是環境變量中定義的文件發現錯誤,make也會不理。 301 302 可是在這裏我仍是建議不要使用這個環境變量,由於只要這個變量一被定義,那麼當你使用make時,全部的Makefile都會受到它的影響,這毫不是你想看到的。在這裏提這個事,只是爲了告訴你們,也許有時候你的Makefile出現了怪事,那麼你能夠看看當前環境中有沒有定義這個變量。 303 304 305 5、make的工做方式 306 307 GNU的make工做時的執行步驟入下:(想來其它的make也是相似) 308 309 1、讀入全部的Makefile。 310 2、讀入被include的其它Makefile。 311 3、初始化文件中的變量。 312 4、推導隱晦規則,並分析全部規則。 313 5、爲全部的目標文件建立依賴關係鏈。 314 6、根據依賴關係,決定哪些目標要從新生成。 315 7、執行生成命令。 316 317 1-5步爲第一個階段,6-7爲第二個階段。第一個階段中,若是定義的變量被使用了,那麼,make會把其展開在使用的位置。但make並不會徹底立刻展開,make使用的是拖延戰術,若是變量出如今依賴關係的規則中,那麼僅當這條依賴被決定要使用了,變量纔會在其內部展開。 318 319 固然,這個工做方式你不必定要清楚,可是知道這個方式你也會對make更爲熟悉。有了這個基礎,後續部分也就容易看懂了。 320 321 322 323 書寫規則 324 ———— 325 326 規則包含兩個部分,一個是依賴關係,一個是生成目標的方法。 327 328 在Makefile中,規則的順序是很重要的,由於,Makefile中只應該有一個最終目標,其它的目標都是被這個目標所連帶出來的,因此必定要讓make知道你的最終目標是什麼。通常來講,定義在Makefile中的目標可能會有不少,可是第一條規則中的目標將被確立爲最終的目標。若是第一條規則中的目標有不少個,那麼,第一個目標會成爲最終的目標。make所完成的也就是這個目標。 329 330 好了,仍是讓咱們來看一看如何書寫規則。 331 332 333 1、規則舉例 334 335 foo.o : foo.c defs.h # foo模塊 336 cc -c -g foo.c 337 338 看到這個例子,各位應該不是很陌生了,前面也已說過,foo.o是咱們的目標,foo.c和defs.h是目標所依賴的源文件,而只有一個命令「cc -c -g foo.c」(以Tab鍵開頭)。這個規則告訴咱們兩件事: 339 340 1、文件的依賴關係,foo.o依賴於foo.c和defs.h的文件,若是foo.c和defs.h的文件日期要比foo.o文件日期要新,或是foo.o不存在,那麼依賴關係發生。 341 2、若是生成(或更新)foo.o文件。也就是那個cc命令,其說明了,如何生成foo.o這個文件。(固然foo.c文件include了defs.h文件) 342 343 344 2、規則的語法 345 346 targets : prerequisites 347 command 348 ... 349 350 或是這樣: 351 352 targets : prerequisites ; command 353 command 354 ... 355 356 targets是文件名,以空格分開,能夠使用通配符。通常來講,咱們的目標基本上是一個文件,但也有多是多個文件。 357 358 command是命令行,若是其不與「target吐舌rerequisites」在一行,那麼,必須以[Tab鍵]開頭,若是和prerequisites在一行,那麼能夠用分號作爲分隔。(見上) 359 360 prerequisites也就是目標所依賴的文件(或依賴目標)。若是其中的某個文件要比目標文件要新,那麼,目標就被認爲是「過期的」,被認爲是須要重生成的。這個在前面已經講過了。 361 362 若是命令太長,你能夠使用反斜框(‘/’)做爲換行符。make對一行上有多少個字符沒有限制。規則告訴make兩件事,文件的依賴關係和如何成成目標文件。 363 364 通常來講,make會以UNIX的標準Shell,也就是/bin/sh來執行命令。 365 366 367 3、在規則中使用通配符 368 369 若是咱們想定義一系列比較相似的文件,咱們很天然地就想起使用通配符。make支持三各通配符:「*」,「?」和「[...]」。這是和Unix的B-Shell是相同的。 370 371 波浪號(「~」)字符在文件名中也有比較特殊的用途。若是是「~/test」,這就表示當前用戶的$HOME目錄下的test目錄。而「~hchen/test」則表示用戶hchen的宿主目錄下的test目錄。(這些都是Unix下的小知識了,make也支持)而在Windows或是MS-DOS下,用戶沒有宿主目錄,那麼波浪號所指的目錄則根據環境變量「HOME」而定。 372 373 通配符代替了你一系列的文件,如「*.c」表示因此後綴爲c的文件。一個須要咱們注意的是,若是咱們的文件名中有通配符,如:「*」,那麼能夠用轉義字符「/」,如「/*」來表示真實的「*」字符,而不是任意長度的字符串。 374 375 好吧,仍是先來看幾個例子吧: 376 377 clean: 378 rm -f *.o 379 380 上面這個例子我不很少說了,這是操做系統Shell所支持的通配符。這是在命令中的通配符。 381 382 print: *.c 383 lpr -p $? 384 touch print 385 386 上面這個例子說明了通配符也能夠在咱們的規則中,目標print依賴於全部的[.c]文件。其中的「$?」是一個自動化變量,我會在後面給你講述。 387 388 objects = *.o 389 390 上面這個例子,表示了,通符一樣能夠用在變量中。並非說[*.o]會展開,不!objects的值就是「*.o」。Makefile中的變量其實就是C/C++中的宏。若是你要讓通配符在變量中展開,也就是讓objects的值是全部[.o]的文件名的集合,那麼,你能夠這樣: 391 392 objects := $(wildcard *.o) 393 394 這種用法由關鍵字「wildcard」指出,關於Makefile的關鍵字,咱們將在後面討論。 395 396 397 4、文件搜尋 398 399 在一些大的工程中,有大量的源文件,咱們一般的作法是把這許多的源文件分類,並存放在不一樣的目錄中。因此,當make須要去找尋文件的依賴關係時,你能夠在文件前加上路徑,但最好的方法是把一個路徑告訴make,讓make在自動去找。 400 401 Makefile文件中的特殊變量「VPATH」就是完成這個功能的,若是沒有指明這個變量,make只會在當前的目錄中去找尋依賴文件和目標文件。若是定義了這個變量,那麼,make就會在噹噹前目錄找不到的狀況下,到所指定的目錄中去找尋文件了。 402 403 VPATH = src:../headers 404 405 上面的的定義指定兩個目錄,「src」和「../headers」,make會按照這個順序進行搜索。目錄由「冒號」分隔。(固然,當前目錄永遠是最高優先搜索的地方) 406 407 另外一個設置文件搜索路徑的方法是使用make的「vpath」關鍵字(注意,它是全小寫的),這不是變量,這是一個make的關鍵字,這和上面提到的那個VPATH變量很相似,可是它更爲靈活。它能夠指定不一樣的文件在不一樣的搜索目錄中。這是一個很靈活的功能。它的使用方法有三種: 408 409 一、vpath <pattern> <directories> 410 411 爲符合模式<pattern>的文件指定搜索目錄<directories>。 412 413 二、vpath <pattern> 414 415 清除符合模式<pattern>的文件的搜索目錄。 416 417 三、vpath 418 419 清除全部已被設置好了的文件搜索目錄。 420 421 vapth使用方法中的<pattern>須要包含「%」字符。「%」的意思是匹配零或若干字符,例如,「%.h」表示全部以「.h」結尾的文件。<pattern>指定了要搜索的文件集,而<directories>則指定了<pattern>的文件集的搜索的目錄。例如: 422 423 vpath %.h ../headers 424 425 該語句表示,要求make在「../headers」目錄下搜索全部以「.h」結尾的文件。(若是某文件在當前目錄沒有找到的話) 426 427 咱們能夠連續地使用vpath語句,以指定不一樣搜索策略。若是連續的vpath語句中出現了相同的<pattern>,或是被重複了的<pattern>,那麼,make會按照vpath語句的前後順序來執行搜索。如: 428 429 vpath %.c foo 430 vpath % blish 431 vpath %.c bar 432 433 其表示「.c」結尾的文件,先在「foo」目錄,而後是「blish」,最後是「bar」目錄。 434 435 vpath %.c foo:bar 436 vpath % blish 437 438 而上面的語句則表示「.c」結尾的文件,先在「foo」目錄,而後是「bar」目錄,最後纔是「blish」目錄。 439 440 441 5、僞目標 442 443 最先先的一個例子中,咱們提到過一個「clean」的目標,這是一個「僞目標」, 444 445 clean: 446 rm *.o temp 447 448 正像咱們前面例子中的「clean」同樣,即然咱們生成了許多文件編譯文件,咱們也應該提供一個清除它們的「目標」以備完整地重編譯而用。 (以「make clean」來使用該目標) 449 450 由於,咱們並不生成「clean」這個文件。「僞目標」並非一個文件,只是一個標籤,因爲「僞目標」不是文件,因此make沒法生成它的依賴關係和決定它是否要執行。咱們只有經過顯示地指明這個「目標」才能讓其生效。固然,「僞目標」的取名不能和文件名重名,否則其就失去了「僞目標」的意義了。 451 452 固然,爲了不和文件重名的這種狀況,咱們能夠使用一個特殊的標記「.PHONY」來顯示地指明一個目標是「僞目標」,向make說明,無論是否有這個文件,這個目標就是「僞目標」。 453 454 .PHONY : clean 455 456 只要有這個聲明,無論是否有「clean」文件,要運行「clean」這個目標,只有「make clean」這樣。因而整個過程能夠這樣寫: 457 458 .PHONY: clean 459 clean: 460 rm *.o temp 461 462 僞目標通常沒有依賴的文件。可是,咱們也能夠爲僞目標指定所依賴的文件。僞目標一樣能夠做爲「默認目標」,只要將其放在第一個。一個示例就是,若是你的Makefile須要一口氣生成若干個可執行文件,但你只想簡單地敲一個make完事,而且,全部的目標文件都寫在一個Makefile中,那麼你能夠使用「僞目標」這個特性: 463 464 all : prog1 prog2 prog3 465 .PHONY : all 466 467 prog1 : prog1.o utils.o 468 cc -o prog1 prog1.o utils.o 469 470 prog2 : prog2.o 471 cc -o prog2 prog2.o 472 473 prog3 : prog3.o sort.o utils.o 474 cc -o prog3 prog3.o sort.o utils.o 475 476 咱們知道,Makefile中的第一個目標會被做爲其默認目標。咱們聲明瞭一個「all」的僞目標,其依賴於其它三個目標。因爲僞目標的特性是,老是被執行的,因此其依賴的那三個目標就老是不如「all」這個目標新。因此,其它三個目標的規則老是會被決議。也就達到了咱們一口氣生成多個目標的目的。「.PHONY : all」聲明瞭「all」這個目標爲「僞目標」。 477 478 隨便提一句,從上面的例子咱們能夠看出,目標也能夠成爲依賴。因此,僞目標一樣也可成爲依賴。看下面的例子: 479 480 .PHONY: cleanall cleanobj cleandiff 481 482 cleanall : cleanobj cleandiff 483 rm program 484 485 cleanobj : 486 rm *.o 487 488 cleandiff : 489 rm *.diff 490 491 「make clean」將清除全部要被清除的文件。「cleanobj」和「cleandiff」這兩個僞目標有點像「子程序」的意思。咱們能夠輸入「make cleanall」和「make cleanobj」和「make cleandiff」命令來達到清除不一樣種類文件的目的。 492 493 6、多目標 494 495 Makefile的規則中的目標能夠不止一個,其支持多目標,有可能咱們的多個目標同時依賴於一個文件,而且其生成的命令大致相似。因而咱們就能把其合併起來。固然,多個目標的生成規則的執行命令是同一個,這可能會可咱們帶來麻煩,不過好在咱們的能夠使用一個自動化變量「$@」(關於自動化變量,將在後面講述),這個變量表示着目前規則中全部的目標的集合,這樣說可能很抽象,仍是看一個例子吧。 496 497 bigoutput littleoutput : text.g 498 generate text.g -$(subst output,,$@) > $@ 499 500 上述規則等價於: 501 502 bigoutput : text.g 503 generate text.g -big > bigoutput 504 littleoutput : text.g 505 generate text.g -little > littleoutput 506 507 其中,-$(subst output,,$@)中的「$」表示執行一個Makefile的函數,函數名爲subst,後面的爲參數。關於函數,將在後面講述。這裏的這個函數是截取字符串的意思,「$@」表示目標的集合,就像一個數組,「$@」依次取出目標,並執於命令。 508 509 510 7、靜態模式 511 512 靜態模式能夠更加容易地定義多目標的規則,可讓咱們的規則變得更加的有彈性和靈活。咱們仍是先來看一下語法: 513 514 <targets ...>: <target-pattern>: <prereq-patterns ...> 515 <commands> 516 ... 517 518 519 targets定義了一系列的目標文件,能夠有通配符。是目標的一個集合。 520 521 target-parrtern是指明瞭targets的模式,也就是的目標集模式。 522 523 prereq-parrterns是目標的依賴模式,它對target-parrtern造成的模式再進行一次依賴目標的定義。 524 525 這樣描述這三個東西,可能仍是沒有說清楚,仍是舉個例子來講明一下吧。若是咱們的<target-parrtern>定義成「%.o」,意思是咱們的<target>集合中都是以「.o」結尾的,而若是咱們的<prereq-parrterns>定義成「%.c」,意思是對<target-parrtern>所造成的目標集進行二次定義,其計算方法是,取<target-parrtern>模式中的「%」(也就是去掉了[.o]這個結尾),併爲其加上[.c]這個結尾,造成的新集合。 526 527 因此,咱們的「目標模式」或是「依賴模式」中都應該有「%」這個字符,若是你的文件名中有「%」那麼你能夠使用反斜槓「/」進行轉義,來標明真實的「%」字符。 528 529 看一個例子: 530 531 objects = foo.o bar.o 532 533 all: $(objects) 534 535 $(objects): %.o: %.c 536 $(CC) -c $(CFLAGS) $< -o $@ 537 538 539 上面的例子中,指明瞭咱們的目標從$object中獲取,「%.o」代表要全部以「.o」結尾的目標,也就是「foo.o bar.o」,也就是變量$object集合的模式,而依賴模式「%.c」則取模式「%.o」的「%」,也就是「foo bar」,併爲其加下「.c」的後綴,因而,咱們的依賴目標就是「foo.c bar.c」。而命令中的「$<」和「$@」則是自動化變量,「$<」表示全部的依賴目標集(也就是「foo.c bar.c」),「$@」表示目標集(也就是「foo.o bar.o」)。因而,上面的規則展開後等價於下面的規則: 540 541 foo.o : foo.c 542 $(CC) -c $(CFLAGS) foo.c -o foo.o 543 bar.o : bar.c 544 $(CC) -c $(CFLAGS) bar.c -o bar.o 545 546 試想,若是咱們的「%.o」有幾百個,那種咱們只要用這種很簡單的「靜態模式規則」就能夠寫完一堆規則,實在是太有效率了。「靜態模式規則」的用法很靈活,若是用得好,那會一個很強大的功能。再看一個例子: 547 548 549 files = foo.elc bar.o lose.o 550 551 $(filter %.o,$(files)): %.o: %.c 552 $(CC) -c $(CFLAGS) $< -o $@ 553 $(filter %.elc,$(files)): %.elc: %.el 554 emacs -f batch-byte-compile $< 555 556 557 $(filter %.o,$(files))表示調用Makefile的filter函數,過濾「$filter」集,只要其中模式爲「%.o」的內容。其的它內容,我就不用多說了吧。這個例字展現了Makefile中更大的彈性。 558 559 560 8、自動生成依賴性 561 562 在Makefile中,咱們的依賴關係可能會須要包含一系列的頭文件,好比,若是咱們的main.c中有一句「#include "defs.h"」,那麼咱們的依賴關係應該是: 563 564 main.o : main.c defs.h 565 566 可是,若是是一個比較大型的工程,你必需清楚哪些C文件包含了哪些頭文件,而且,你在加入或刪除頭文件時,也須要當心地修改Makefile,這是一個很沒有維護性的工做。爲了不這種繁重而又容易出錯的事情,咱們能夠使用C/C++編譯的一個功能。大多數的C/C++編譯器都支持一個「-M」的選項,即自動找尋源文件中包含的頭文件,並生成一個依賴關係。例如,若是咱們執行下面的命令: 567 568 cc -M main.c 569 570 其輸出是: 571 572 main.o : main.c defs.h 573 574 因而由編譯器自動生成的依賴關係,這樣一來,你就沒必要再手動書寫若干文件的依賴關係,而由編譯器自動生成了。須要提醒一句的是,若是你使用GNU的C/C++編譯器,你得用「-MM」參數,否則,「-M」參數會把一些標準庫的頭文件也包含進來。 575 576 gcc -M main.c的輸出是: 577 578 main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h / 579 /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h / 580 /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h / 581 /usr/include/bits/types.h /usr/include/bits/pthreadtypes.h / 582 /usr/include/bits/sched.h /usr/include/libio.h / 583 /usr/include/_G_config.h /usr/include/wchar.h / 584 /usr/include/bits/wchar.h /usr/include/gconv.h / 585 /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h / 586 /usr/include/bits/stdio_lim.h 587 588 589 gcc -MM main.c的輸出則是: 590 591 main.o: main.c defs.h 592 593 那麼,編譯器的這個功能如何與咱們的Makefile聯繫在一塊兒呢。由於這樣一來,咱們的Makefile也要根據這些源文件從新生成,讓Makefile自已依賴於源文件?這個功能並不現實,不過咱們能夠有其它手段來迂迴地實現這一功能。GNU組織建議把編譯器爲每個源文件的自動生成的依賴關係放到一個文件中,爲每個「name.c」的文件都生成一個「name.d」的Makefile文件,[.d]文件中就存放對應[.c]文件的依賴關係。 594 595 因而,咱們能夠寫出[.c]文件和[.d]文件的依賴關係,並讓make自動更新或自成[.d]文件,並把其包含在咱們的主Makefile中,這樣,咱們就能夠自動化地生成每一個文件的依賴關係了。 596 597 這裏,咱們給出了一個模式規則來產生[.d]文件: 598 599 %.d: %.c 600 @set -e; rm -f $@; / 601 $(CC) -M $(CPPFLAGS) $< > $@.$$$$; / 602 sed 's,/($*/)/.o[ :]*,/1.o $@ : ,g' < $@.$$$$ > $@; / 603 rm -f $@.$$$$ 604 605 606 這個規則的意思是,全部的[.d]文件依賴於[.c]文件,「rm -f $@」的意思是刪除全部的目標,也就是[.d]文件,第二行的意思是,爲每一個依賴文件「$<」,也就是[.c]文件生成依賴文件,「$@」表示模式「%.d」文件,若是有一個C文件是name.c,那麼「%」就是「name」,「$$$$」意爲一個隨機編號,第二行生成的文件有多是「name.d.12345」,第三行使用sed命令作了一個替換,關於sed命令的用法請參看相關的使用文檔。第四行就是刪除臨時文件。 607 608 總而言之,這個模式要作的事就是在編譯器生成的依賴關係中加入[.d]文件的依賴,即把依賴關係: 609 610 main.o : main.c defs.h 611 612 轉成: 613 614 main.o main.d : main.c defs.h 615 616 因而,咱們的[.d]文件也會自動更新了,並會自動生成了,固然,你還能夠在這個[.d]文件中加入的不僅是依賴關係,包括生成的命令也可一併加入,讓每一個[.d]文件都包含一個完賴的規則。一旦咱們完成這個工做,接下來,咱們就要把這些自動生成的規則放進咱們的主Makefile中。咱們能夠使用Makefile的「include」命令,來引入別的Makefile文件(前面講過),例如: 617 618 sources = foo.c bar.c 619 620 include $(sources:.c=.d) 621 622 上述語句中的「$(sources:.c=.d)」中的「.c=.d」的意思是作一個替換,把變量$(sources)全部[.c]的字串都替換成[.d],關於這個「替換」的內容,在後面我會有更爲詳細的講述。固然,你得注意次序,由於include是按次來載入文件,最早載入的[.d]文件中的目標會成爲默認目標。 623 624 625 626 書寫命令 627 ———— 628 629 每條規則中的命令和操做系統Shell的命令行是一致的。make會一按順序一條一條的執行命令,每條命令的開頭必須以[Tab]鍵開頭,除非,命令是緊跟在依賴規則後面的分號後的。在命令行之間中的空格或是空行會被忽略,可是若是該空格或空行是以Tab鍵開頭的,那麼make會認爲其是一個空命令。 630 631 咱們在UNIX下可能會使用不一樣的Shell,可是make的命令默認是被「/bin/sh」——UNIX的標準Shell解釋執行的。除非你特別指定一個其它的Shell。Makefile中,「#」是註釋符,很像C/C++中的「//」,其後的本行字符都被註釋。 632 633 1、顯示命令 634 635 一般,make會把其要執行的命令行在命令執行前輸出到屏幕上。當咱們用「@」字符在命令行前,那麼,這個命令將不被make顯示出來,最具表明性的例子是,咱們用這個功能來像屏幕顯示一些信息。如: 636 637 @echo 正在編譯XXX模塊...... 638 639 當make執行時,會輸出「正在編譯XXX模塊......」字串,但不會輸出命令,若是沒有「@」,那麼,make將輸出: 640 641 echo 正在編譯XXX模塊...... 642 正在編譯XXX模塊...... 643 644 若是make執行時,帶入make參數「-n」或「--just-print」,那麼其只是顯示命令,但不會執行命令,這個功能頗有利於咱們調試咱們的Makefile,看看咱們書寫的命令是執行起來是什麼樣子的或是什麼順序的。 645 646 而make參數「-s」或「--slient」則是全面禁止命令的顯示。 647 648 649 650 2、命令執行 651 652 當依賴目標新於目標時,也就是當規則的目標須要被更新時,make會一條一條的執行其後的命令。須要注意的是,若是你要讓上一條命令的結果應用在下一條命令時,你應該使用分號分隔這兩條命令。好比你的第一條命令是cd命令,你但願第二條命令得在cd以後的基礎上運行,那麼你就不能把這兩條命令寫在兩行上,而應該把這兩條命令寫在一行上,用分號分隔。如: 653 654 示例一: 655 exec: 656 cd /home/hchen 657 pwd 658 659 示例二: 660 exec: 661 cd /home/hchen; pwd 662 663 當咱們執行「make exec」時,第一個例子中的cd沒有做用,pwd會打印出當前的Makefile目錄,而第二個例子中,cd就起做用了,pwd會打印出「/home/hchen」。 664 665 make通常是使用環境變量SHELL中所定義的系統Shell來執行命令,默認狀況下使用UNIX的標準Shell——/bin/sh來執行命令。但在MS-DOS下有點特殊,由於MS-DOS下沒有SHELL環境變量,固然你也能夠指定。若是你指定了UNIX風格的目錄形式,首先,make會在SHELL所指定的路徑中找尋命令解釋器,若是找不到,其會在當前盤符中的當前目錄中尋找,若是再找不到,其會在PATH環境變量中所定義的全部路徑中尋找。MS-DOS中,若是你定義的命令解釋器沒有找到,其會給你的命令解釋器加上諸如「.exe」、「.com」、「.bat」、「.sh」等後綴。 666 667 668 669 3、命令出錯 670 671 每當命令運行完後,make會檢測每一個命令的返回碼,若是命令返回成功,那麼make會執行下一條命令,當規則中全部的命令成功返回後,這個規則就算是成功完成了。若是一個規則中的某個命令出錯了(命令退出碼非零),那麼make就會終止執行當前規則,這將有可能終止全部規則的執行。 672 673 有些時候,命令的出錯並不表示就是錯誤的。例如mkdir命令,咱們必定須要創建一個目錄,若是目錄不存在,那麼mkdir就成功執行,萬事大吉,若是目錄存在,那麼就出錯了。咱們之因此使用mkdir的意思就是必定要有這樣的一個目錄,因而咱們就不但願mkdir出錯而終止規則的運行。 674 675 爲了作到這一點,忽略命令的出錯,咱們能夠在Makefile的命令行前加一個減號「-」(在Tab鍵以後),標記爲無論命令出不出錯都認爲是成功的。如: 676 677 clean: 678 -rm -f *.o 679 680 還有一個全局的辦法是,給make加上「-i」或是「--ignore-errors」參數,那麼,Makefile中全部命令都會忽略錯誤。而若是一個規則是以「.IGNORE」做爲目標的,那麼這個規則中的全部命令將會忽略錯誤。這些是不一樣級別的防止命令出錯的方法,你能夠根據你的不一樣喜歡設置。 681 682 還有一個要提一下的make的參數的是「-k」或是「--keep-going」,這個參數的意思是,若是某規則中的命令出錯了,那麼就終目該規則的執行,但繼續執行其它規則。 683 684 685 686 4、嵌套執行make 687 688 在一些大的工程中,咱們會把咱們不一樣模塊或是不一樣功能的源文件放在不一樣的目錄中,咱們能夠在每一個目錄中都書寫一個該目錄的Makefile,這有利於讓咱們的Makefile變得更加地簡潔,而不至於把全部的東西所有寫在一個Makefile中,這樣會很難維護咱們的Makefile,這個技術對於咱們模塊編譯和分段編譯有着很是大的好處。 689 690 例如,咱們有一個子目錄叫subdir,這個目錄下有個Makefile文件,來指明瞭這個目錄下文件的編譯規則。那麼咱們總控的Makefile能夠這樣書寫: 691 692 subsystem: 693 cd subdir && $(MAKE) 694 695 其等價於: 696 697 subsystem: 698 $(MAKE) -C subdir 699 700 定義$(MAKE)宏變量的意思是,也許咱們的make須要一些參數,因此定義成一個變量比較利於維護。這兩個例子的意思都是先進入「subdir」目錄,而後執行make命令。 701 702 咱們把這個Makefile叫作「總控Makefile」,總控Makefile的變量能夠傳遞到下級的Makefile中(若是你顯示的聲明),可是不會覆蓋下層的Makefile中所定義的變量,除非指定了「-e」參數。 703 704 若是你要傳遞變量到下級Makefile中,那麼你能夠使用這樣的聲明: 705 706 export <variable ...> 707 708 若是你不想讓某些變量傳遞到下級Makefile中,那麼你能夠這樣聲明: 709 710 unexport <variable ...> 711 712 如: 713 714 示例一: 715 716 export variable = value 717 718 其等價於: 719 720 variable = value 721 export variable 722 723 其等價於: 724 725 export variable := value 726 727 其等價於: 728 729 variable := value 730 export variable 731 732 示例二: 733 734 export variable += value 735 736 其等價於: 737 738 variable += value 739 export variable 740 741 若是你要傳遞全部的變量,那麼,只要一個export就好了。後面什麼也不用跟,表示傳遞全部的變量。 742 743 須要注意的是,有兩個變量,一個是SHELL,一個是MAKEFLAGS,這兩個變量無論你是否export,其老是要傳遞到下層Makefile中,特別是MAKEFILES變量,其中包含了make的參數信息,若是咱們執行「總控Makefile」時有make參數或是在上層Makefile中定義了這個變量,那麼MAKEFILES變量將會是這些參數,並會傳遞到下層Makefile中,這是一個系統級的環境變量。 744 745 可是make命令中的有幾個參數並不往下傳遞,它們是「-C」,「-f」,「-h」「-o」和「-W」(有關Makefile參數的細節將在後面說明),若是你不想往下層傳遞參數,那麼,你能夠這樣來: 746 747 subsystem: 748 cd subdir && $(MAKE) MAKEFLAGS= 749 750 若是你定義了環境變量MAKEFLAGS,那麼你得確信其中的選項是你們都會用到的,若是其中有「-t」,「-n」,和「-q」參數,那麼將會有讓你意想不到的結果,或許會讓你異常地恐慌。 751 752 還有一個在「嵌套執行」中比較有用的參數,「-w」或是「--print-directory」會在make的過程當中輸出一些信息,讓你看到目前的工做目錄。好比,若是咱們的下級make目錄是「/home/hchen/gnu/make」,若是咱們使用「make -w」來執行,那麼當進入該目錄時,咱們會看到: 753 754 make: Entering directory `/home/hchen/gnu/make'. 755 756 而在完成下層make後離開目錄時,咱們會看到: 757 758 make: Leaving directory `/home/hchen/gnu/make' 759 760 當你使用「-C」參數來指定make下層Makefile時,「-w」會被自動打開的。若是參數中有「-s」(「--slient」)或是「--no-print-directory」,那麼,「-w」老是失效的。 761 762 763 764 5、定義命令包 765 766 若是Makefile中出現一些相同命令序列,那麼咱們能夠爲這些相同的命令序列定義一個變量。定義這種命令序列的語法以「define」開始,以「endef」結束,如: 767 768 define run-yacc 769 yacc $(firstword $^) 770 mv y.tab.c $@ 771 endef 772 773 這裏,「run-yacc」是這個命令包的名字,其不要和Makefile中的變量重名。在「define」和「endef」中的兩行就是命令序列。這個命令包中的第一個命令是運行Yacc程序,由於Yacc程序老是生成「y.tab.c」的文件,因此第二行的命令就是把這個文件改更名字。仍是把這個命令包放到一個示例中來看看吧。 774 775 foo.c : foo.y 776 $(run-yacc) 777 778 咱們能夠看見,要使用這個命令包,咱們就好像使用變量同樣。在這個命令包的使用中,命令包「run-yacc」中的「$^」就是「foo.y」,「$@」就是「foo.c」(有關這種以「$」開頭的特殊變量,咱們會在後面介紹),make在執行命令包時,命令包中的每一個命令會被依次獨立執行。 779 780 使用變量 781 ———— 782 783 在Makefile中的定義的變量,就像是C/C++語言中的宏同樣,他表明了一個文本字串,在Makefile中執行的時候其會自動原模原樣地展開在所使用的地方。其與C/C++所不一樣的是,你能夠在Makefile中改變其值。在Makefile中,變量能夠使用在「目標」,「依賴目標」,「命令」或是Makefile的其它部分中。 784 785 變量的命名字能夠包含字符、數字,下劃線(能夠是數字開頭),但不該該含有「:」、「#」、「=」或是空字符(空格、回車等)。變量是大小寫敏感的,「foo」、「Foo」和「FOO」是三個不一樣的變量名。傳統的Makefile的變量名是全大寫的命名方式,但我推薦使用大小寫搭配的變量名,如:MakeFlags。這樣能夠避免和系統的變量衝突,而發生意外的事情。 786 787 有一些變量是很奇怪字串,如「$<」、「$@」等,這些是自動化變量,我會在後面介紹。 788 789 1、變量的基礎 790 791 變量在聲明時須要給予初值,而在使用時,須要給在變量名前加上「$」符號,但最好用小括號「()」或是大括號「{}」把變量給包括起來。若是你要使用真實的「$」字符,那麼你須要用「$$」來表示。 792 793 變量能夠使用在許多地方,如規則中的「目標」、「依賴」、「命令」以及新的變量中。先看一個例子: 794 795 objects = program.o foo.o utils.o 796 program : $(objects) 797 cc -o program $(objects) 798 799 $(objects) : defs.h 800 801 變量會在使用它的地方精確地展開,就像C/C++中的宏同樣,例如: 802 803 foo = c 804 prog.o : prog.$(foo) 805 $(foo)$(foo) -$(foo) prog.$(foo) 806 807 展開後獲得: 808 809 prog.o : prog.c 810 cc -c prog.c 811 812 固然,千萬不要在你的Makefile中這樣幹,這裏只是舉個例子來代表Makefile中的變量在使用處展開的真實樣子。可見其就是一個「替代」的原理。 813 814 另外,給變量加上括號徹底是爲了更加安全地使用這個變量,在上面的例子中,若是你不想給變量加上括號,那也能夠,但我仍是強烈建議你給變量加上括號。 815 816 817 2、變量中的變量 818 819 在定義變量的值時,咱們能夠使用其它變量來構造變量的值,在Makefile中有兩種方式來在用變量定義變量的值。 820 821 先看第一種方式,也就是簡單的使用「=」號,在「=」左側是變量,右側是變量的值,右側變量的值能夠定義在文件的任何一處,也就是說,右側中的變量不必定非要是已定義好的值,其也能夠使用後面定義的值。如: 822 823 foo = $(bar) 824 bar = $(ugh) 825 ugh = Huh? 826 827 all: 828 echo $(foo) 829 830 咱們執行「make all」將會打出變量$(foo)的值是「Huh?」( $(foo)的值是$(bar),$(bar)的值是$(ugh),$(ugh)的值是「Huh?」)可見,變量是能夠使用後面的變量來定義的。 831 832 這個功能有好的地方,也有很差的地方,好的地方是,咱們能夠把變量的真實值推到後面來定義,如: 833 834 CFLAGS = $(include_dirs) -O 835 include_dirs = -Ifoo -Ibar 836 837 當「CFLAGS」在命令中被展開時,會是「-Ifoo -Ibar -O」。但這種形式也有很差的地方,那就是遞歸定義,如: 838 839 CFLAGS = $(CFLAGS) -O 840 841 或: 842 843 A = $(B) 844 B = $(A) 845 846 這會讓make陷入無限的變量展開過程當中去,固然,咱們的make是有能力檢測這樣的定義,並會報錯。還有就是若是在變量中使用函數,那麼,這種方式會讓咱們的make運行時很是慢,更糟糕的是,他會使用得兩個make的函數「wildcard」和「shell」發生不可預知的錯誤。由於你不會知道這兩個函數會被調用多少次。 847 848 爲了不上面的這種方法,咱們能夠使用make中的另外一種用變量來定義變量的方法。這種方法使用的是「:=」操做符,如: 849 850 x := foo 851 y := $(x) bar 852 x := later 853 854 其等價於: 855 856 y := foo bar 857 x := later 858 859 值得一提的是,這種方法,前面的變量不能使用後面的變量,只能使用前面已定義好了的變量。若是是這樣: 860 861 y := $(x) bar 862 x := foo 863 864 那麼,y的值是「bar」,而不是「foo bar」。 865 866 上面都是一些比較簡單的變量使用了,讓咱們來看一個複雜的例子,其中包括了make的函數、條件表達式和一個系統變量「MAKELEVEL」的使用: 867 868 ifeq (0,${MAKELEVEL}) 869 cur-dir := $(shell pwd) 870 whoami := $(shell whoami) 871 host-type := $(shell arch) 872 MAKE := ${MAKE} host-type=${host-type} whoami=${whoami} 873 endif 874 875 關於條件表達式和函數,咱們在後面再說,對於系統變量「MAKELEVEL」,其意思是,若是咱們的make有一個嵌套執行的動做(參見前面的「嵌套使用make」),那麼,這個變量會記錄了咱們的當前Makefile的調用層數。 876 877 下面再介紹兩個定義變量時咱們須要知道的,請先看一個例子,若是咱們要定義一個變量,其值是一個空格,那麼咱們能夠這樣來: 878 879 nullstring := 880 space := $(nullstring) # end of the line 881 882 nullstring是一個Empty變量,其中什麼也沒有,而咱們的space的值是一個空格。由於在操做符的右邊是很難描述一個空格的,這裏採用的技術很管用,先用一個Empty變量來標明變量的值開始了,然後面採用「#」註釋符來表示變量定義的終止,這樣,咱們能夠定義出其值是一個空格的變量。請注意這裏關於「#」的使用,註釋符「#」的這種特性值得咱們注意,若是咱們這樣定義一個變量: 883 884 dir := /foo/bar # directory to put the frobs in 885 886 dir這個變量的值是「/foo/bar」,後面還跟了4個空格,若是咱們這樣使用這樣變量來指定別的目錄——「$(dir)/file」那麼就完蛋了。 887 888 還有一個比較有用的操做符是「?=」,先看示例: 889 890 FOO ?= bar 891 892 其含義是,若是FOO沒有被定義過,那麼變量FOO的值就是「bar」,若是FOO先前被定義過,那麼這條語將什麼也不作,其等價於: 893 894 ifeq ($(origin FOO), undefined) 895 FOO = bar 896 endif 897 898 899 3、變量高級用法 900 901 這裏介紹兩種變量的高級使用方法,第一種是變量值的替換。 902 903 咱們能夠替換變量中的共有的部分,其格式是「$(var:a=b)」或是「${var:a=b}」,其意思是,把變量「var」中全部以「a」字串「結尾」的「a」替換成「b」字串。這裏的「結尾」意思是「空格」或是「結束符」。 904 905 仍是看一個示例吧: 906 907 foo := a.o b.o c.o 908 bar := $(foo:.o=.c) 909 910 這個示例中,咱們先定義了一個「$(foo)」變量,而第二行的意思是把「$(foo)」中全部以「.o」字串「結尾」所有替換成「.c」,因此咱們的「$(bar)」的值就是「a.c b.c c.c」。 911 912 另一種變量替換的技術是以「靜態模式」(參見前面章節)定義的,如: 913 914 foo := a.o b.o c.o 915 bar := $(foo:%.o=%.c) 916 917 這依賴於被替換字串中的有相同的模式,模式中必須包含一個「%」字符,這個例子一樣讓$(bar)變量的值爲「a.c b.c c.c」。 918 919 第二種高級用法是——「把變量的值再當成變量」。先看一個例子: 920 921 x = y 922 y = z 923 a := $($(x)) 924 925 在這個例子中,$(x)的值是「y」,因此$($(x))就是$(y),因而$(a)的值就是「z」。(注意,是「x=y」,而不是「x=$(y)」) 926 927 咱們還能夠使用更多的層次: 928 929 x = y 930 y = z 931 z = u 932 a := $($($(x))) 933 934 這裏的$(a)的值是「u」,相關的推導留給讀者本身去作吧。 935 936 讓咱們再複雜一點,使用上「在變量定義中使用變量」的第一個方式,來看一個例子: 937 938 x = $(y) 939 y = z 940 z = Hello 941 a := $($(x)) 942 943 這裏的$($(x))被替換成了$($(y)),由於$(y)值是「z」,因此,最終結果是:a:=$(z),也就是「Hello」。 944 945 再複雜一點,咱們再加上函數: 946 947 x = variable1 948 variable2 := Hello 949 y = $(subst 1,2,$(x)) 950 z = y 951 a := $($($(z))) 952 953 這個例子中,「$($($(z)))」擴展爲「$($(y))」,而其再次被擴展爲「$($(subst 1,2,$(x)))」。$(x)的值是「variable1」,subst函數把「variable1」中的全部「1」字串替換成「2」字串,因而,「variable1」變成「variable2」,再取其值,因此,最終,$(a)的值就是$(variable2)的值——「Hello」。(喔,好不容易) 954 955 在這種方式中,或要能夠使用多個變量來組成一個變量的名字,而後再取其值: 956 957 first_second = Hello 958 a = first 959 b = second 960 all = $($a_$b) 961 962 這裏的「$a_$b」組成了「first_second」,因而,$(all)的值就是「Hello」。 963 964 再來看看結合第一種技術的例子: 965 966 a_objects := a.o b.o c.o 967 1_objects := 1.o 2.o 3.o 968 969 sources := $($(a1)_objects:.o=.c) 970 971 這個例子中,若是$(a1)的值是「a」的話,那麼,$(sources)的值就是「a.c b.c c.c」;若是$(a1)的值是「1」,那麼$(sources)的值是「1.c 2.c 3.c」。 972 973 再來看一個這種技術和「函數」與「條件語句」一同使用的例子: 974 975 ifdef do_sort 976 func := sort 977 else 978 func := strip 979 endif 980 981 bar := a d b g q c 982 983 foo := $($(func) $(bar)) 984 985 這個示例中,若是定義了「do_sort」,那麼:foo := $(sort a d b g q c),因而$(foo)的值就是「a b c d g q」,而若是沒有定義「do_sort」,那麼:foo := $(sort a d b g q c),調用的就是strip函數。 986 987 固然,「把變量的值再當成變量」這種技術,一樣能夠用在操做符的左邊: 988 989 dir = foo 990 $(dir)_sources := $(wildcard $(dir)/*.c) 991 define $(dir)_print 992 lpr $($(dir)_sources) 993 endef 994 995 這個例子中定義了三個變量:「dir」,「foo_sources」和「foo_print」。 996 997 998 4、追加變量值 999 1000 咱們能夠使用「+=」操做符給變量追加值,如: 1001 1002 objects = main.o foo.o bar.o utils.o 1003 objects += another.o 1004 1005 因而,咱們的$(objects)值變成:「main.o foo.o bar.o utils.o another.o」(another.o被追加進去了) 1006 1007 使用「+=」操做符,能夠模擬爲下面的這種例子: 1008 1009 objects = main.o foo.o bar.o utils.o 1010 objects := $(objects) another.o 1011 1012 所不一樣的是,用「+=」更爲簡潔。 1013 1014 若是變量以前沒有定義過,那麼,「+=」會自動變成「=」,若是前面有變量定義,那麼「+=」會繼承於前次操做的賦值符。若是前一次的是「:=」,那麼「+=」會以「:=」做爲其賦值符,如: 1015 1016 variable := value 1017 variable += more 1018 1019 等價於: 1020 1021 variable := value 1022 variable := $(variable) more 1023 1024 但若是是這種狀況: 1025 1026 variable = value 1027 variable += more 1028 1029 因爲前次的賦值符是「=」,因此「+=」也會以「=」來作爲賦值,那麼豈不會發生變量的遞補歸定義,這是很很差的,因此make會自動爲咱們解決這個問題,咱們沒必要擔憂這個問題。 1030 1031 1032 5、override 指示符 1033 1034 若是有變量是一般make的命令行參數設置的,那麼Makefile中對這個變量的賦值會被忽略。若是你想在Makefile中設置這類參數的值,那麼,你能夠使用「override」指示符。其語法是: 1035 1036 override <variable> = <value> 1037 1038 override <variable> := <value> 1039 1040 固然,你還能夠追加: 1041 1042 override <variable> += <more text> 1043 1044 對於多行的變量定義,咱們用define指示符,在define指示符前,也一樣能夠使用ovveride指示符,如: 1045 1046 override define foo 1047 bar 1048 endef 1049 1050 6、多行變量 1051 1052 還有一種設置變量值的方法是使用define關鍵字。使用define關鍵字設置變量的值能夠有換行,這有利於定義一系列的命令(前面咱們講過「命令包」的技術就是利用這個關鍵字)。 1053 1054 define指示符後面跟的是變量的名字,而重起一行定義變量的值,定義是以endef關鍵字結束。其工做方式和「=」操做符同樣。變量的值能夠包含函數、命令、文字,或是其它變量。由於命令須要以[Tab]鍵開頭,因此若是你用define定義的命令變量中沒有以[Tab]鍵開頭,那麼make就不會把其認爲是命令。 1055 1056 下面的這個示例展現了define的用法: 1057 1058 define two-lines 1059 echo foo 1060 echo $(bar) 1061 endef 1062 1063 1064 7、環境變量 1065 1066 make運行時的系統環境變量能夠在make開始運行時被載入到Makefile文件中,可是若是Makefile中已定義了這個變量,或是這個變量由make命令行帶入,那麼系統的環境變量的值將被覆蓋。(若是make指定了「-e」參數,那麼,系統環境變量將覆蓋Makefile中定義的變量) 1067 1068 所以,若是咱們在環境變量中設置了「CFLAGS」環境變量,那麼咱們就能夠在全部的Makefile中使用這個變量了。這對於咱們使用統一的編譯參數有比較大的好處。若是Makefile中定義了CFLAGS,那麼則會使用Makefile中的這個變量,若是沒有定義則使用系統環境變量的值,一個共性和個性的統一,很像「全局變量」和「局部變量」的特性。 1069 1070 當make嵌套調用時(參見前面的「嵌套調用」章節),上層Makefile中定義的變量會以系統環境變量的方式傳遞到下層的Makefile中。固然,默認狀況下,只有經過命令行設置的變量會被傳遞。而定義在文件中的變量,若是要向下層Makefile傳遞,則須要使用exprot關鍵字來聲明。(參見前面章節) 1071 1072 固然,我並不推薦把許多的變量都定義在系統環境中,這樣,在咱們執行不用的Makefile時,擁有的是同一套系統變量,這可能會帶來更多的麻煩。 1073 1074 1075 8、目標變量 1076 1077 前面咱們所講的在Makefile中定義的變量都是「全局變量」,在整個文件,咱們均可以訪問這些變量。固然,「自動化變量」除外,如「$<」等這種類量的自動化變量就屬於「規則型變量」,這種變量的值依賴於規則的目標和依賴目標的定義。 1078 1079 固然,我樣一樣能夠爲某個目標設置局部變量,這種變量被稱爲「Target-specific Variable」,它能夠和「全局變量」同名,由於它的做用範圍只在這條規則以及連帶規則中,因此其值也只在做用範圍內有效。而不會影響規則鏈之外的全局變量的值。 1080 1081 其語法是: 1082 1083 <target ...> : <variable-assignment> 1084 1085 <target ...> : overide <variable-assignment> 1086 1087 <variable-assignment>能夠是前面講過的各類賦值表達式,如「=」、「:=」、「+=」或是「?=」。第二個語法是針對於make命令行帶入的變量,或是系統環境變量。 1088 1089 這個特性很是的有用,當咱們設置了這樣一個變量,這個變量會做用到由這個目標所引起的全部的規則中去。如: 1090 1091 prog : CFLAGS = -g 1092 prog : prog.o foo.o bar.o 1093 $(CC) $(CFLAGS) prog.o foo.o bar.o 1094 1095 prog.o : prog.c 1096 $(CC) $(CFLAGS) prog.c 1097 1098 foo.o : foo.c 1099 $(CC) $(CFLAGS) foo.c 1100 1101 bar.o : bar.c 1102 $(CC) $(CFLAGS) bar.c 1103 1104 在這個示例中,無論全局的$(CFLAGS)的值是什麼,在prog目標,以及其所引起的全部規則中(prog.o foo.o bar.o的規則),$(CFLAGS)的值都是「-g」 1105 1106 1107 9、模式變量 1108 1109 在GNU的make中,還支持模式變量(Pattern-specific Variable),經過上面的目標變量中,咱們知道,變量能夠定義在某個目標上。模式變量的好處就是,咱們能夠給定一種「模式」,能夠把變量定義在符合這種模式的全部目標上。 1110 1111 咱們知道,make的「模式」通常是至少含有一個「%」的,因此,咱們能夠以以下方式給全部以[.o]結尾的目標定義目標變量: 1112 1113 %.o : CFLAGS = -O 1114 1115 一樣,模式變量的語法和「目標變量」同樣: 1116 1117 <pattern ...> : <variable-assignment> 1118 1119 <pattern ...> : override <variable-assignment> 1120 1121 override一樣是針對於系統環境傳入的變量,或是make命令行指定的變量。 1122 1123 使用條件判斷 1124 —————— 1125 1126 使用條件判斷,可讓make根據運行時的不一樣狀況選擇不一樣的執行分支。條件表達式能夠是比較變量的值,或是比較變量和常量的值。 1127 1128 1、示例 1129 1130 下面的例子,判斷$(CC)變量是否「gcc」,若是是的話,則使用GNU函數編譯目標。 1131 1132 libs_for_gcc = -lgnu 1133 normal_libs = 1134 1135 foo: $(objects) 1136 ifeq ($(CC),gcc) 1137 $(CC) -o foo $(objects) $(libs_for_gcc) 1138 else 1139 $(CC) -o foo $(objects) $(normal_libs) 1140 endif 1141 1142 可見,在上面示例的這個規則中,目標「foo」能夠根據變量「$(CC)」值來選取不一樣的函數庫來編譯程序。 1143 1144 咱們能夠從上面的示例中看到三個關鍵字:ifeq、else和endif。ifeq的意思表示條件語句的開始,並指定一個條件表達式,表達式包含兩個參數,以逗號分隔,表達式以圓括號括起。else表示條件表達式爲假的狀況。endif表示一個條件語句的結束,任何一個條件表達式都應該以endif結束。 1145 1146 當咱們的變量$(CC)值是「gcc」時,目標foo的規則是: 1147 1148 foo: $(objects) 1149 $(CC) -o foo $(objects) $(libs_for_gcc) 1150 1151 而當咱們的變量$(CC)值不是「gcc」時(好比「cc」),目標foo的規則是: 1152 1153 foo: $(objects) 1154 $(CC) -o foo $(objects) $(normal_libs) 1155 1156 固然,咱們還能夠把上面的那個例子寫得更簡潔一些: 1157 1158 libs_for_gcc = -lgnu 1159 normal_libs = 1160 1161 ifeq ($(CC),gcc) 1162 libs=$(libs_for_gcc) 1163 else 1164 libs=$(normal_libs) 1165 endif 1166 1167 foo: $(objects) 1168 $(CC) -o foo $(objects) $(libs) 1169 1170 1171 2、語法 1172 1173 條件表達式的語法爲: 1174 1175 <conditional-directive> 1176 <text-if-true> 1177 endif 1178 1179 以及: 1180 1181 <conditional-directive> 1182 <text-if-true> 1183 else 1184 <text-if-false> 1185 endif 1186 1187 其中<conditional-directive>表示條件關鍵字,如「ifeq」。這個關鍵字有四個。 1188 1189 第一個是咱們前面所見過的「ifeq」 1190 1191 ifeq (<arg1>, <arg2> ) 1192 ifeq '<arg1>' '<arg2>' 1193 ifeq "<arg1>" "<arg2>" 1194 ifeq "<arg1>" '<arg2>' 1195 ifeq '<arg1>' "<arg2>" 1196 1197 比較參數「arg1」和「arg2」的值是否相同。固然,參數中咱們還能夠使用make的函數。如: 1198 1199 ifeq ($(strip $(foo)),) 1200 <text-if-empty> 1201 endif 1202 1203 這個示例中使用了「strip」函數,若是這個函數的返回值是空(Empty),那麼<text-if-empty>就生效。 1204 1205 第二個條件關鍵字是「ifneq」。語法是: 1206 1207 ifneq (<arg1>, <arg2> ) 1208 ifneq '<arg1>' '<arg2>' 1209 ifneq "<arg1>" "<arg2>" 1210 ifneq "<arg1>" '<arg2>' 1211 ifneq '<arg1>' "<arg2>" 1212 1213 其比較參數「arg1」和「arg2」的值是否相同,若是不一樣,則爲真。和「ifeq」相似。 1214 1215 第三個條件關鍵字是「ifdef」。語法是: 1216 1217 ifdef <variable-name> 1218 1219 若是變量<variable-name>的值非空,那到表達式爲真。不然,表達式爲假。固然,<variable-name>一樣能夠是一個函數的返回值。注意,ifdef只是測試一個變量是否有值,其並不會把變量擴展到當前位置。仍是來看兩個例子: 1220 1221 示例一: 1222 bar = 1223 foo = $(bar) 1224 ifdef foo 1225 frobozz = yes 1226 else 1227 frobozz = no 1228 endif 1229 1230 示例二: 1231 foo = 1232 ifdef foo 1233 frobozz = yes 1234 else 1235 frobozz = no 1236 endif 1237 1238 第一個例子中,「$(frobozz)」值是「yes」,第二個則是「no」。 1239 1240 第四個條件關鍵字是「ifndef」。其語法是: 1241 1242 ifndef <variable-name> 1243 1244 這個我就很少說了,和「ifdef」是相反的意思。 1245 1246 在<conditional-directive>這一行上,多餘的空格是被容許的,可是不能以[Tab]鍵作爲開始(否則就被認爲是命令)。而註釋符「#」一樣也是安全的。「else」和「endif」也同樣,只要不是以[Tab]鍵開始就好了。 1247 1248 特別注意的是,make是在讀取Makefile時就計算條件表達式的值,並根據條件表達式的值來選擇語句,因此,你最好不要把自動化變量(如「$@」等)放入條件表達式中,由於自動化變量是在運行時纔有的。 1249 1250 並且,爲了不混亂,make不容許把整個條件語句分紅兩部分放在不一樣的文件中。 1251 1252 1253 1254 使用函數 1255 ———— 1256 1257 在Makefile中能夠使用函數來處理變量,從而讓咱們的命令或是規則更爲的靈活和具備智能。make所支持的函數也不算不少,不過已經足夠咱們的操做了。函數調用後,函數的返回值能夠當作變量來使用。 1258 1259 1260 1、函數的調用語法 1261 1262 函數調用,很像變量的使用,也是以「$」來標識的,其語法以下: 1263 1264 $(<function> <arguments> ) 1265 1266 或是 1267 1268 ${<function> <arguments>} 1269 1270 這裏,<function>就是函數名,make支持的函數很少。<arguments>是函數的參數,參數間以逗號「,」分隔,而函數名和參數之間以「空格」分隔。函數調用以「$」開頭,以圓括號或花括號把函數名和參數括起。感受很像一個變量,是否是?函數中的參數能夠使用變量,爲了風格的統一,函數和變量的括號最好同樣,如使用「$(subst a,b,$(x))」這樣的形式,而不是「$(subst a,b,${x})」的形式。由於統一會更清楚,也會減小一些沒必要要的麻煩。 1271 1272 仍是來看一個示例: 1273 1274 comma:= , 1275 empty:= 1276 space:= $(empty) $(empty) 1277 foo:= a b c 1278 bar:= $(subst $(space),$(comma),$(foo)) 1279 1280 在這個示例中,$(comma)的值是一個逗號。$(space)使用了$(empty)定義了一個空格,$(foo)的值是「a b c」,$(bar)的定義用,調用了函數「subst」,這是一個替換函數,這個函數有三個參數,第一個參數是被替換字串,第二個參數是替換字串,第三個參數是替換操做做用的字串。這個函數也就是把$(foo)中的空格替換成逗號,因此$(bar)的值是「a,b,c」。 1281 1282 1283 2、字符串處理函數 1284 1285 $(subst <from>,<to>,<text> ) 1286 1287 名稱:字符串替換函數——subst。 1288 功能:把字串<text>中的<from>字符串替換成<to>。 1289 返回:函數返回被替換事後的字符串。 1290 1291 示例: 1292 1293 $(subst ee,EE,feet on the street), 1294 1295 把「feet on the street」中的「ee」替換成「EE」,返回結果是「fEEt on the strEEt」。 1296 1297 1298 $(patsubst <pattern>,<replacement>,<text> ) 1299 1300 名稱:模式字符串替換函數——patsubst。 1301 功能:查找<text>中的單詞(單詞以「空格」、「Tab」或「回車」「換行」分隔)是否符合模式<pattern>,若是匹配的話,則以<replacement>替換。這裏,<pattern>能夠包括通配符「%」,表示任意長度的字串。若是<replacement>中也包含「%」,那麼,<replacement>中的這個「%」將是<pattern>中的那個「%」所表明的字串。(能夠用「/」來轉義,以「/%」來表示真實含義的「%」字符) 1302 返回:函數返回被替換事後的字符串。 1303 1304 示例: 1305 1306 $(patsubst %.c,%.o,x.c.c bar.c) 1307 1308 把字串「x.c.c bar.c」符合模式[%.c]的單詞替換成[%.o],返回結果是「x.c.o bar.o」 1309 1310 備註: 1311 1312 這和咱們前面「變量章節」說過的相關知識有點類似。如: 1313 1314 「$(var:<pattern>=<replacement> )」 1315 至關於 1316 「$(patsubst <pattern>,<replacement>,$(var))」, 1317 1318 而「$(var: <suffix>=<replacement> )」 1319 則至關於 1320 「$(patsubst %<suffix>,%<replacement>,$(var))」。 1321 1322 例若有:objects = foo.o bar.o baz.o, 1323 那麼,「$(objects:.o=.c)」和「$(patsubst %.o,%.c,$(objects))」是同樣的。 1324 1325 $(strip <string> ) 1326 1327 名稱:去空格函數——strip。 1328 功能:去掉<string>字串中開頭和結尾的空字符。 1329 返回:返回被去掉空格的字符串值。 1330 示例: 1331 1332 $(strip a b c ) 1333 1334 把字串「a b c 」去到開頭和結尾的空格,結果是「a b c」。 1335 1336 $(findstring <find>,<in> ) 1337 1338 名稱:查找字符串函數——findstring。 1339 功能:在字串<in>中查找<find>字串。 1340 返回:若是找到,那麼返回<find>,不然返回空字符串。 1341 示例: 1342 1343 $(findstring a,a b c) 1344 $(findstring a,b c) 1345 1346 第一個函數返回「a」字符串,第二個返回「」字符串(空字符串) 1347 1348 $(filter <pattern...>,<text> ) 1349 1350 名稱:過濾函數——filter。 1351 功能:以<pattern>模式過濾<text>字符串中的單詞,保留符合模式<pattern>的單詞。能夠有多個模式。 1352 返回:返回符合模式<pattern>的字串。 1353 示例: 1354 1355 sources := foo.c bar.c baz.s ugh.h 1356 foo: $(sources) 1357 cc $(filter %.c %.s,$(sources)) -o foo 1358 1359 $(filter %.c %.s,$(sources))返回的值是「foo.c bar.c baz.s」。 1360 1361 $(filter-out <pattern...>,<text> ) 1362 1363 名稱:反過濾函數——filter-out。 1364 功能:以<pattern>模式過濾<text>字符串中的單詞,去除符合模式<pattern>的單詞。能夠有多個模式。 1365 返回:返回不符合模式<pattern>的字串。 1366 示例: 1367 1368 objects=main1.o foo.o main2.o bar.o 1369 mains=main1.o main2.o 1370 1371 $(filter-out $(mains),$(objects)) 返回值是「foo.o bar.o」。 1372 1373 $(sort <list> ) 1374 1375 名稱:排序函數——sort。 1376 功能:給字符串<list>中的單詞排序(升序)。 1377 返回:返回排序後的字符串。 1378 示例:$(sort foo bar lose)返回「bar foo lose」 。 1379 備註:sort函數會去掉<list>中相同的單詞。 1380 1381 $(word <n>,<text> ) 1382 1383 名稱:取單詞函數——word。 1384 功能:取字符串<text>中第<n>個單詞。(從一開始) 1385 返回:返回字符串<text>中第<n>個單詞。若是<n>比<text>中的單詞數要大,那麼返回空字符串。 1386 示例:$(word 2, foo bar baz)返回值是「bar」。 1387 1388 $(wordlist <s>,<e>,<text> ) 1389 1390 名稱:取單詞串函數——wordlist。 1391 功能:從字符串<text>中取從<s>開始到<e>的單詞串。<s>和<e>是一個數字。 1392 返回:返回字符串<text>中從<s>到<e>的單詞字串。若是<s>比<text>中的單詞數要大,那麼返回空字符串。若是<e>大於<text>的單詞數,那麼返回從<s>開始,到<text>結束的單詞串。 1393 示例: $(wordlist 2, 3, foo bar baz)返回值是「bar baz」。 1394 1395 $(words <text> ) 1396 1397 名稱:單詞個數統計函數——words。 1398 功能:統計<text>中字符串中的單詞個數。 1399 返回:返回<text>中的單詞數。 1400 示例:$(words, foo bar baz)返回值是「3」。 1401 備註:若是咱們要取<text>中最後的一個單詞,咱們能夠這樣:$(word $(words <text> ),<text> )。 1402 1403 $(firstword <text> ) 1404 1405 名稱:首單詞函數——firstword。 1406 功能:取字符串<text>中的第一個單詞。 1407 返回:返回字符串<text>的第一個單詞。 1408 示例:$(firstword foo bar)返回值是「foo」。 1409 備註:這個函數能夠用word函數來實現:$(word 1,<text> )。 1410 1411 以上,是全部的字符串操做函數,若是搭配混合使用,能夠完成比較複雜的功能。這裏,舉一個現實中應用的例子。咱們知道,make使用「VPATH」變量來指定「依賴文件」的搜索路徑。因而,咱們能夠利用這個搜索路徑來指定編譯器對頭文件的搜索路徑參數CFLAGS,如: 1412 1413 override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH))) 1414 1415 若是咱們的「$(VPATH)」值是「src:../headers」,那麼「$(patsubst %,-I%,$(subst :, ,$(VPATH)))」將返回「-Isrc -I../headers」,這正是cc或gcc搜索頭文件路徑的參數。 1416 1417 1418 3、文件名操做函數 1419 1420 下面咱們要介紹的函數主要是處理文件名的。每一個函數的參數字符串都會被當作一個或是一系列的文件名來對待。 1421 1422 $(dir <names...> ) 1423 1424 名稱:取目錄函數——dir。 1425 功能:從文件名序列<names>中取出目錄部分。目錄部分是指最後一個反斜槓(「/」)以前的部分。若是沒有反斜槓,那麼返回「./」。 1426 返回:返回文件名序列<names>的目錄部分。 1427 示例: $(dir src/foo.c hacks)返回值是「src/ ./」。 1428 1429 $(notdir <names...> ) 1430 1431 名稱:取文件函數——notdir。 1432 功能:從文件名序列<names>中取出非目錄部分。非目錄部分是指最後一個反斜槓(「/」)以後的部分。 1433 返回:返回文件名序列<names>的非目錄部分。 1434 示例: $(notdir src/foo.c hacks)返回值是「foo.c hacks」。 1435 1436 $(suffix <names...> ) 1437 1438 名稱:取後綴函數——suffix。 1439 功能:從文件名序列<names>中取出各個文件名的後綴。 1440 返回:返回文件名序列<names>的後綴序列,若是文件沒有後綴,則返回空字串。 1441 示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是「.c .c」。 1442 1443 $(basename <names...> ) 1444 1445 名稱:取前綴函數——basename。 1446 功能:從文件名序列<names>中取出各個文件名的前綴部分。 1447 返回:返回文件名序列<names>的前綴序列,若是文件沒有前綴,則返回空字串。 1448 示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是「src/foo src-1.0/bar hacks」。 1449 1450 $(addsuffix <suffix>,<names...> ) 1451 1452 名稱:加後綴函數——addsuffix。 1453 功能:把後綴<suffix>加到<names>中的每一個單詞後面。 1454 返回:返回加事後綴的文件名序列。 1455 示例:$(addsuffix .c,foo bar)返回值是「foo.c bar.c」。 1456 1457 $(addprefix <prefix>,<names...> ) 1458 1459 名稱:加前綴函數——addprefix。 1460 功能:把前綴<prefix>加到<names>中的每一個單詞後面。 1461 返回:返回加過前綴的文件名序列。 1462 示例:$(addprefix src/,foo bar)返回值是「src/foo src/bar」。 1463 1464 $(join <list1>,<list2> ) 1465 1466 名稱:鏈接函數——join。 1467 功能:把<list2>中的單詞對應地加到<list1>的單詞後面。若是<list1>的單詞個數要比<list2>的多,那麼,<list1>中的多出來的單詞將保持原樣。若是<list2>的單詞個數要比<list1>多,那麼,<list2>多出來的單詞將被複制到<list2>中。 1468 返回:返回鏈接事後的字符串。 1469 示例:$(join aaa bbb , 111 222 333)返回值是「aaa111 bbb222 333」。 1470 1471 1472 1473 4、foreach 函數 1474 1475 1476 foreach函數和別的函數很是的不同。由於這個函數是用來作循環用的,Makefile中的foreach函數幾乎是仿照於Unix標準Shell(/bin/sh)中的for語句,或是C-Shell(/bin/csh)中的foreach語句而構建的。它的語法是: 1477 1478 1479 1480 $(foreach <var>,<list>,<text> ) 1481 1482 1483 1484 這個函數的意思是,把參數<list>中的單詞逐一取出放到參數<var>所指定的變量中,而後再執行<text>所包含的表達式。每一次<text>會返回一個字符串,循環過程當中,<text>的所返回的每一個字符串會以空格分隔,最後當整個循環結束時,<text>所返回的每一個字符串所組成的整個字符串(以空格分隔)將會是foreach函數的返回值。 1485 1486 1487 1488 因此,<var>最好是一個變量名,<list>能夠是一個表達式,而<text>中通常會使用<var>這個參數來依次枚舉<list>中的單詞。舉個例子: 1489 1490 1491 1492 names := a b c d 1493 1494 files := $(foreach n,$(names),$(n).o) 1495 1496 1497 1498 上面的例子中,$(name)中的單詞會被挨個取出,並存到變量「n」中,「$(n).o」每次根據「$(n)」計算出一個值,這些值以空格分隔,最後做爲foreach函數的返回,因此,$(files)的值是「a.o b.o c.o d.o」。 1499 1500 1501 1502 注意,foreach中的<var>參數是一個臨時的局部變量,foreach函數執行完後,參數<var>的變量將不在做用,其做用域只在foreach函數當中。 1503 1504 1505 1506 1507 1508 5、if 函數 1509 1510 1511 if函數很像GNU的make所支持的條件語句——ifeq(參見前面所述的章節),if函數的語法是: 1512 1513 1514 1515 $(if <condition>,<then-part> ) 1516 1517 1518 1519 或是 1520 1521 1522 1523 $(if <condition>,<then-part>,<else-part> ) 1524 1525 1526 1527 可見,if函數能夠包含「else」部分,或是不含。即if函數的參數能夠是兩個,也能夠是三個。<condition>參數是if的表達式,若是其返回的爲非空字符串,那麼這個表達式就至關於返回真,因而,<then-part>會被計算,不然<else-part>會被計算。 1528 1529 1530 1531 而if函數的返回值是,若是<condition>爲真(非空字符串),那個<then-part>會是整個函數的返回值,若是<condition>爲假(空字符串),那麼<else-part>會是整個函數的返回值,此時若是<else-part>沒有被定義,那麼,整個函數返回空字串。 1532 1533 1534 1535 因此,<then-part>和<else-part>只會有一個被計算。 1536 1537 1538 1539 1540 1541 6、call函數 1542 1543 1544 call函數是惟一一個能夠用來建立新的參數化的函數。你能夠寫一個很是複雜的表達式,這個表達式中,你能夠定義許多參數,而後你能夠用call函數來向這個表達式傳遞參數。其語法是: 1545 1546 1547 1548 $(call <expression>,<parm1>,<parm2>,<parm3>...) 1549 1550 1551 1552 當make執行這個函數時,<expression>參數中的變量,如$(1),$(2),$(3)等,會被參數<parm1>,<parm2>,<parm3>依次取代。而<expression>的返回值就是call函數的返回值。例如: 1553 1554 reverse = $(1) $(2) 1555 1556 foo = $(call reverse,a,b) 1557 1558 1559 1560 那麼,foo的值就是「a b」。固然,參數的次序是能夠自定義的,不必定是順序的,如: 1561 1562 1563 1564 reverse = $(2) $(1) 1565 1566 foo = $(call reverse,a,b) 1567 1568 1569 1570 此時的foo的值就是「b a」。 1571 1572 1573 1574 1575 1576 7、origin函數 1577 origin函數不像其它的函數,他並不操做變量的值,他只是告訴你你的這個變量是哪裏來的?其語法是: 1578 1579 1580 1581 $(origin <variable> ) 1582 1583 1584 1585 注意,<variable>是變量的名字,不該該是引用。因此你最好不要在<variable>中使用「$」字符。Origin函數會以其返回值來告訴你這個變量的「出生狀況」,下面,是origin函數的返回值: 1586 1587 1588 1589 「undefined」 1590 1591 若是<variable>歷來沒有定義過,origin函數返回這個值「undefined」。 1592 1593 1594 1595 「default」 1596 1597 若是<variable>是一個默認的定義,好比「CC」這個變量,這種變量咱們將在後面講述。 1598 1599 1600 1601 「environment」 1602 1603 若是<variable>是一個環境變量,而且當Makefile被執行時,「-e」參數沒有被打開。 1604 1605 1606 1607 「file」 1608 1609 若是<variable>這個變量被定義在Makefile中。 1610 1611 1612 1613 「command line」 1614 1615 若是<variable>這個變量是被命令行定義的。 1616 1617 1618 1619 「override」 1620 1621 若是<variable>是被override指示符從新定義的。 1622 1623 1624 1625 「automatic」 1626 1627 若是<variable>是一個命令運行中的自動化變量。關於自動化變量將在後面講述。 1628 1629 1630 1631 這些信息對於咱們編寫Makefile是很是有用的,例如,假設咱們有一個Makefile其包了一個定義文件Make.def,在Make.def中定義了一個變量「bletch」,而咱們的環境中也有一個環境變量「bletch」,此時,咱們想判斷一下,若是變量來源於環境,那麼咱們就把之重定義了,若是來源於Make.def或是命令行等非環境的,那麼咱們就不從新定義它。因而,在咱們的Makefile中,咱們能夠這樣寫: 1632 1633 1634 1635 ifdef bletch 1636 1637 ifeq "$(origin bletch)" "environment" 1638 1639 bletch = barf, gag, etc. 1640 1641 endif 1642 1643 endif 1644 1645 1646 1647 固然,你也許會說,使用override關鍵字不就能夠從新定義環境中的變量了嗎?爲何須要使用這樣的步驟?是的,咱們用override是能夠達到這樣的效果,但是override過於粗暴,它同時會把從命令行定義的變量也覆蓋了,而咱們只想從新定義環境傳來的,而不想從新定義命令行傳來的。 1648 1649 1650 1651 1652 1653 8、shell函數 1654 1655 1656 shell函數也不像其它的函數。顧名思義,它的參數應該就是操做系統Shell的命令。它和反引號「`」是相同的功能。這就是說,shell函數把執行操做系統命令後的輸出做爲函數返回。因而,咱們能夠用操做系統命令以及字符串處理命令awk,sed等等命令來生成一個變量,如: 1657 1658 1659 1660 contents := $(shell cat foo) 1661 1662 1663 1664 files := $(shell echo *.c) 1665 1666 1667 1668 注意,這個函數會新生成一個Shell程序來執行命令,因此你要注意其運行性能,若是你的Makefile中有一些比較複雜的規則,並大量使用了這個函數,那麼對於你的系統性能是有害的。特別是Makefile的隱晦的規則可能會讓你的shell函數執行的次數比你想像的多得多。 1669 1670 1671 1672 1673 1674 9、控制make的函數 1675 1676 1677 make提供了一些函數來控制make的運行。一般,你須要檢測一些運行Makefile時的運行時信息,而且根據這些信息來決定,你是讓make繼續執行,仍是中止。 1678 1679 1680 1681 $(error <text ...> ) 1682 1683 1684 1685 產生一個致命的錯誤,<text ...>是錯誤信息。注意,error函數不會在一被使用就會產生錯誤信息,因此若是你把其定義在某個變量中,並在後續的腳本中使用這個變量,那麼也是能夠的。例如: 1686 1687 1688 1689 示例一: 1690 1691 ifdef ERROR_001 1692 1693 $(error error is $(ERROR_001)) 1694 1695 endif 1696 1697 1698 1699 示例二: 1700 1701 ERR = $(error found an error!) 1702 1703 .PHONY: err 1704 1705 err: ; $(ERR) 1706 1707 1708 1709 示例一會在變量ERROR_001定義了後執行時產生error調用,而示例二則在目錄err被執行時才發生error調用。 1710 1711 1712 1713 $(warning <text ...> ) 1714 1715 1716 1717 這個函數很像error函數,只是它並不會讓make退出,只是輸出一段警告信息,而make繼續執行。 1718 1719 make 的運行 1720 —————— 1721 1722 通常來講,最簡單的就是直接在命令行下輸入make命令,make命令會找當前目錄的makefile來執行,一切都是自動的。但也有時你也許只想讓make重編譯某些文件,而不是整個工程,而又有的時候你有幾套編譯規則,你想在不一樣的時候使用不一樣的編譯規則,等等。本章節就是講述如何使用make命令的。 1723 1724 1、make的退出碼 1725 1726 make命令執行後有三個退出碼: 1727 1728 0 —— 表示成功執行。 1729 1 —— 若是make運行時出現任何錯誤,其返回1。 1730 2 —— 若是你使用了make的「-q」選項,而且make使得一些目標不須要更新,那麼返回2。 1731 1732 Make的相關參數咱們會在後續章節中講述。 1733 1734 1735 2、指定Makefile 1736 1737 前面咱們說過,GNU make找尋默認的Makefile的規則是在當前目錄下依次找三個文件——「GNUmakefile」、「makefile」和「Makefile」。其按順序找這三個文件,一旦找到,就開始讀取這個文件並執行。 1738 1739 當前,咱們也能夠給make命令指定一個特殊名字的Makefile。要達到這個功能,咱們要使用make的「-f」或是「--file」參數(「--makefile」參數也行)。例如,咱們有個makefile的名字是「hchen.mk」,那麼,咱們能夠這樣來讓make來執行這個文件: 1740 1741 make –f hchen.mk 1742 1743 若是在make的命令行是,你不僅一次地使用了「-f」參數,那麼,全部指定的makefile將會被連在一塊兒傳遞給make執行。 1744 1745 1746 3、指定目標 1747 1748 通常來講,make的最終目標是makefile中的第一個目標,而其它目標通常是由這個目標連帶出來的。這是make的默認行爲。固然,通常來講,你的makefile中的第一個目標是由許多個目標組成,你能夠指示make,讓其完成你所指定的目標。要達到這一目的很簡單,需在make命令後直接跟目標的名字就能夠完成(如前面提到的「make clean」形式) 1749 1750 任何在makefile中的目標均可以被指定成終極目標,可是除了以「-」打頭,或是包含了「=」的目標,由於有這些字符的目標,會被解析成命令行參數或是變量。甚至沒有被咱們明確寫出來的目標也能夠成爲make的終極目標,也就是說,只要make能夠找到其隱含規則推導規則,那麼這個隱含目標一樣能夠被指定成終極目標。 1751 1752 有一個make的環境變量叫「MAKECMDGOALS」,這個變量中會存放你所指定的終極目標的列表,若是在命令行上,你沒有指定目標,那麼,這個變量是空值。這個變量可讓你使用在一些比較特殊的情形下。好比下面的例子: 1753 1754 sources = foo.c bar.c 1755 ifneq ( $(MAKECMDGOALS),clean) 1756 include $(sources:.c=.d) 1757 endif 1758 1759 基於上面的這個例子,只要咱們輸入的命令不是「make clean」,那麼makefile會自動包含「foo.d」和「bar.d」這兩個makefile。 1760 1761 使用指定終極目標的方法能夠很方便地讓咱們編譯咱們的程序,例以下面這個例子: 1762 1763 .PHONY: all 1764 all: prog1 prog2 prog3 prog4 1765 1766 從這個例子中,咱們能夠看到,這個makefile中有四個須要編譯的程序——「prog1」, 「prog2」, 「prog3」和 「prog4」,咱們能夠使用「make all」命令來編譯全部的目標(若是把all置成第一個目標,那麼只需執行「make」),咱們也能夠使用「make prog2」來單獨編譯目標「prog2」。 1767 1768 即然make能夠指定全部makefile中的目標,那麼也包括「僞目標」,因而咱們能夠根據這種性質來讓咱們的makefile根據指定的不一樣的目標來完成不一樣的事。在Unix世界中,軟件發佈時,特別是GNU這種開源軟件的發佈時,其makefile都包含了編譯、安裝、打包等功能。咱們能夠參照這種規則來書寫咱們的makefile中的目標。 1769 1770 「all」 1771 這個僞目標是全部目標的目標,其功能通常是編譯全部的目標。 1772 「clean」 1773 這個僞目標功能是刪除全部被make建立的文件。 1774 「install」 1775 這個僞目標功能是安裝已編譯好的程序,其實就是把目標執行文件拷貝到指定的目標中去。 1776 「print」 1777 這個僞目標的功能是例出改變過的源文件。 1778 「tar」 1779 這個僞目標功能是把源程序打包備份。也就是一個tar文件。 1780 「dist」 1781 這個僞目標功能是建立一個壓縮文件,通常是把tar文件壓成Z文件。或是gz文件。 1782 「TAGS」 1783 這個僞目標功能是更新全部的目標,以備完整地重編譯使用。 1784 「check」和「test」 1785 這兩個僞目標通常用來測試makefile的流程。 1786 1787 固然一個項目的makefile中也不必定要書寫這樣的目標,這些東西都是GNU的東西,可是我想,GNU搞出這些東西必定有其可取之處(等你的UNIX下的程序文件一多時你就會發現這些功能頗有用了),這裏只不過是說明了,若是你要書寫這種功能,最好使用這種名字命名你的目標,這樣規範一些,規範的好處就是——不用解釋,你們都明白。並且若是你的makefile中有這些功能,一是很實用,二是能夠顯得你的makefile很專業(不是那種初學者的做品)。 1788 1789 1790 4、檢查規則 1791 1792 有時候,咱們不想讓咱們的makefile中的規則執行起來,咱們只想檢查一下咱們的命令,或是執行的序列。因而咱們能夠使用make命令的下述參數: 1793 1794 「-n」 1795 「--just-print」 1796 「--dry-run」 1797 「--recon」 1798 不執行參數,這些參數只是打印命令,無論目標是否更新,把規則和連帶規則下的命令打印出來,但不執行,這些參數對於咱們調試makefile頗有用處。 1799 1800 「-t」 1801 「--touch」 1802 這個參數的意思就是把目標文件的時間更新,但不更改目標文件。也就是說,make僞裝編譯目標,但不是真正的編譯目標,只是把目標變成已編譯過的狀態。 1803 1804 「-q」 1805 「--question」 1806 這個參數的行爲是找目標的意思,也就是說,若是目標存在,那麼其什麼也不會輸出,固然也不會執行編譯,若是目標不存在,其會打印出一條出錯信息。 1807 1808 「-W <file>」 1809 「--what-if=<file>」 1810 「--assume-new=<file>」 1811 「--new-file=<file>」 1812 這個參數須要指定一個文件。通常是是源文件(或依賴文件),Make會根據規則推導來運行依賴於這個文件的命令,通常來講,能夠和「-n」參數一同使用,來查看這個依賴文件所發生的規則命令。 1813 1814 另一個頗有意思的用法是結合「-p」和「-v」來輸出makefile被執行時的信息(這個將在後面講述)。 1815 1816 1817 5、make的參數 1818 1819 下面列舉了全部GNU make 3.80版的參數定義。其它版本和產商的make大同小異,不過其它產商的make的具體參數仍是請參考各自的產品文檔。 1820 1821 「-b」 1822 「-m」 1823 這兩個參數的做用是忽略和其它版本make的兼容性。 1824 1825 「-B」 1826 「--always-make」 1827 認爲全部的目標都須要更新(重編譯)。 1828 1829 「-C <dir>」 1830 「--directory=<dir>」 1831 指定讀取makefile的目錄。若是有多個「-C」參數,make的解釋是後面的路徑之前面的做爲相對路徑,並以最後的目錄做爲被指定目錄。如:「make –C ~hchen/test –C prog」等價於「make –C ~hchen/test/prog」。 1832 1833 「—debug[=<options>]」 1834 輸出make的調試信息。它有幾種不一樣的級別可供選擇,若是沒有參數,那就是輸出最簡單的調試信息。下面是<options>的取值: 1835 a —— 也就是all,輸出全部的調試信息。(會很是的多) 1836 b —— 也就是basic,只輸出簡單的調試信息。即輸出不須要重編譯的目標。 1837 v —— 也就是verbose,在b選項的級別之上。輸出的信息包括哪一個makefile被解析,不須要被重編譯的依賴文件(或是依賴目標)等。 1838 i —— 也就是implicit,輸出因此的隱含規則。 1839 j —— 也就是jobs,輸出執行規則中命令的詳細信息,如命令的PID、返回碼等。 1840 m —— 也就是makefile,輸出make讀取makefile,更新makefile,執行makefile的信息。 1841 1842 「-d」 1843 至關於「--debug=a」。 1844 1845 「-e」 1846 「--environment-overrides」 1847 指明環境變量的值覆蓋makefile中定義的變量的值。 1848 1849 「-f=<file>」 1850 「--file=<file>」 1851 「--makefile=<file>」 1852 指定須要執行的makefile。 1853 1854 「-h」 1855 「--help」 1856 顯示幫助信息。 1857 1858 「-i」 1859 「--ignore-errors」 1860 在執行時忽略全部的錯誤。 1861 1862 「-I <dir>」 1863 「--include-dir=<dir>」 1864 指定一個被包含makefile的搜索目標。能夠使用多個「-I」參數來指定多個目錄。 1865 1866 「-j [<jobsnum>]」 1867 「--jobs[=<jobsnum>]」 1868 指同時運行命令的個數。若是沒有這個參數,make運行命令時能運行多少就運行多少。若是有一個以上的「-j」參數,那麼僅最後一個「-j」纔是有效的。(注意這個參數在MS-DOS中是無用的) 1869 1870 「-k」 1871 「--keep-going」 1872 出錯也不中止運行。若是生成一個目標失敗了,那麼依賴於其上的目標就不會被執行了。 1873 1874 「-l <load>」 1875 「--load-average[=<load]」 1876 「—max-load[=<load>]」 1877 指定make運行命令的負載。 1878 1879 「-n」 1880 「--just-print」 1881 「--dry-run」 1882 「--recon」 1883 僅輸出執行過程當中的命令序列,但並不執行。 1884 1885 「-o <file>」 1886 「--old-file=<file>」 1887 「--assume-old=<file>」 1888 不從新生成的指定的<file>,即便這個目標的依賴文件新於它。 1889 1890 「-p」 1891 「--print-data-base」 1892 輸出makefile中的全部數據,包括全部的規則和變量。這個參數會讓一個簡單的makefile都會輸出一堆信息。若是你只是想輸出信息而不想執行makefile,你能夠使用「make -qp」命令。若是你想查看執行makefile前的預設變量和規則,你能夠使用「make –p –f /dev/null」。這個參數輸出的信息會包含着你的makefile文件的文件名和行號,因此,用這個參數來調試你的makefile會是頗有用的,特別是當你的環境變量很複雜的時候。 1893 1894 「-q」 1895 「--question」 1896 不運行命令,也不輸出。僅僅是檢查所指定的目標是否須要更新。若是是0則說明要更新,若是是2則說明有錯誤發生。 1897 1898 「-r」 1899 「--no-builtin-rules」 1900 禁止make使用任何隱含規則。 1901 1902 「-R」 1903 「--no-builtin-variabes」 1904 禁止make使用任何做用於變量上的隱含規則。 1905 1906 「-s」 1907 「--silent」 1908 「--quiet」 1909 在命令運行時不輸出命令的輸出。 1910 1911 「-S」 1912 「--no-keep-going」 1913 「--stop」 1914 取消「-k」選項的做用。由於有些時候,make的選項是從環境變量「MAKEFLAGS」中繼承下來的。因此你能夠在命令行中使用這個參數來讓環境變量中的「-k」選項失效。 1915 1916 「-t」 1917 「--touch」 1918 至關於UNIX的touch命令,只是把目標的修改日期變成最新的,也就是阻止生成目標的命令運行。 1919 1920 「-v」 1921 「--version」 1922 輸出make程序的版本、版權等關於make的信息。 1923 1924 「-w」 1925 「--print-directory」 1926 輸出運行makefile以前和以後的信息。這個參數對於跟蹤嵌套式調用make時頗有用。 1927 1928 「--no-print-directory」 1929 禁止「-w」選項。 1930 1931 「-W <file>」 1932 「--what-if=<file>」 1933 「--new-file=<file>」 1934 「--assume-file=<file>」 1935 假定目標<file>須要更新,若是和「-n」選項使用,那麼這個參數會輸出該目標更新時的運行動做。若是沒有「-n」那麼就像運行UNIX的「touch」命令同樣,使得<file>的修改時間爲當前時間。 1936 1937 「--warn-undefined-variables」 1938 只要make發現有未定義的變量,那麼就輸出警告信息。 1939 1940 隱含規則 1941 ———— 1942 1943 在咱們使用Makefile時,有一些咱們會常用,並且使用頻率很是高的東西,好比,咱們編譯C/C++的源程序爲中間目標文件(Unix下是[.o]文件,Windows下是[.obj]文件)。本章講述的就是一些在Makefile中的「隱含的」,早先約定了的,不須要咱們再寫出來的規則。 1944 1945 「隱含規則」也就是一種慣例,make會按照這種「慣例」心照不喧地來運行,那怕咱們的Makefile中沒有書寫這樣的規則。例如,把[.c]文件編譯成[.o]文件這一規則,你根本就不用寫出來,make會自動推導出這種規則,並生成咱們須要的[.o]文件。 1946 1947 「隱含規則」會使用一些咱們系統變量,咱們能夠改變這些系統變量的值來定製隱含規則的運行時的參數。如系統變量「CFLAGS」能夠控制編譯時的編譯器參數。 1948 1949 咱們還能夠經過「模式規則」的方式寫下本身的隱含規則。用「後綴規則」來定義隱含規則會有許多的限制。使用「模式規則」會更回得智能和清楚,但「後綴規則」能夠用來保證咱們Makefile的兼容性。 1950 咱們瞭解了「隱含規則」,可讓其爲咱們更好的服務,也會讓咱們知道一些「約定俗成」了的東西,而不至於使得咱們在運行Makefile時出現一些咱們以爲莫名其妙的東西。固然,任何事物都是矛盾的,水能載舟,亦可覆舟,因此,有時候「隱含規則」也會給咱們形成不小的麻煩。只有瞭解了它,咱們才能更好地使用它。 1951 1952 1953 1、使用隱含規則 1954 1955 若是要使用隱含規則生成你須要的目標,你所須要作的就是不要寫出這個目標的規則。那麼,make會試圖去自動推導產生這個目標的規則和命令,若是make能夠自動推導生成這個目標的規則和命令,那麼這個行爲就是隱含規則的自動推導。固然,隱含規則是make事先約定好的一些東西。例如,咱們有下面的一個Makefile: 1956 1957 foo : foo.o bar.o 1958 cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS) 1959 1960 咱們能夠注意到,這個Makefile中並無寫下如何生成foo.o和bar.o這兩目標的規則和命令。由於make的「隱含規則」功能會自動爲咱們自動去推導這兩個目標的依賴目標和生成命令。 1961 1962 make會在本身的「隱含規則」庫中尋找能夠用的規則,若是找到,那麼就會使用。若是找不到,那麼就會報錯。在上面的那個例子中,make調用的隱含規則是,把[.o]的目標的依賴文件置成[.c],並使用C的編譯命令「cc –c $(CFLAGS) [.c]」來生成[.o]的目標。也就是說,咱們徹底沒有必要寫下下面的兩條規則: 1963 1964 foo.o : foo.c 1965 cc –c foo.c $(CFLAGS) 1966 bar.o : bar.c 1967 cc –c bar.c $(CFLAGS) 1968 1969 由於,這已是「約定」好了的事了,make和咱們約定好了用C編譯器「cc」生成[.o]文件的規則,這就是隱含規則。 1970 1971 固然,若是咱們爲[.o]文件書寫了本身的規則,那麼make就不會自動推導並調用隱含規則,它會按照咱們寫好的規則忠實地執行。 1972 1973 還有,在make的「隱含規則庫」中,每一條隱含規則都在庫中有其順序,越靠前的則是越被常用的,因此,這會致使咱們有些時候即便咱們顯示地指定了目標依賴,make也不會管。以下面這條規則(沒有命令): 1974 1975 foo.o : foo.p 1976 1977 依賴文件「foo.p」(Pascal程序的源文件)有可能變得沒有意義。若是目錄下存在了「foo.c」文件,那麼咱們的隱含規則同樣會生效,並會經過「foo.c」調用C的編譯器生成foo.o文件。由於,在隱含規則中,Pascal的規則出如今C的規則以後,因此,make找到能夠生成foo.o的C的規則就再也不尋找下一條規則了。若是你確實不但願任何隱含規則推導,那麼,你就不要只寫出「依賴規則」,而不寫命令。 1978 1979 1980 2、隱含規則一覽 1981 1982 這裏咱們將講述全部預先設置(也就是make內建)的隱含規則,若是咱們不明確地寫下規則,那麼,make就會在這些規則中尋找所須要規則和命令。固然,咱們也能夠使用make的參數「-r」或「--no-builtin-rules」選項來取消全部的預設置的隱含規則。 1983 1984 固然,即便是咱們指定了「-r」參數,某些隱含規則仍是會生效,由於有許多的隱含規則都是使用了「後綴規則」來定義的,因此,只要隱含規則中有「後綴列表」(也就一系統定義在目標.SUFFIXES的依賴目標),那麼隱含規則就會生效。默認的後綴列表是:.out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el。具體的細節,咱們會在後面講述。 1985 1986 仍是先來看一看經常使用的隱含規則吧。 1987 1988 一、編譯C程序的隱含規則。 1989 「<n>.o」的目標的依賴目標會自動推導爲「<n>.c」,而且其生成命令是「$(CC) –c $(CPPFLAGS) $(CFLAGS)」 1990 1991 二、編譯C++程序的隱含規則。 1992 「<n>.o」的目標的依賴目標會自動推導爲「<n>.cc」或是「<n>.C」,而且其生成命令是「$(CXX) –c $(CPPFLAGS) $(CFLAGS)」。(建議使用「.cc」做爲C++源文件的後綴,而不是「.C」) 1993 1994 三、編譯Pascal程序的隱含規則。 1995 「<n>.o」的目標的依賴目標會自動推導爲「<n>.p」,而且其生成命令是「$(PC) –c $(PFLAGS)」。 1996 1997 四、編譯Fortran/Ratfor程序的隱含規則。 1998 「<n>.o」的目標的依賴目標會自動推導爲「<n>.r」或「<n>.F」或「<n>.f」,而且其生成命令是: 1999 「.f」 「$(FC) –c $(FFLAGS)」 2000 「.F」 「$(FC) –c $(FFLAGS) $(CPPFLAGS)」 2001 「.f」 「$(FC) –c $(FFLAGS) $(RFLAGS)」 2002 2003 五、預處理Fortran/Ratfor程序的隱含規則。 2004 「<n>.f」的目標的依賴目標會自動推導爲「<n>.r」或「<n>.F」。這個規則只是轉換Ratfor或有預處理的Fortran程序到一個標準的Fortran程序。其使用的命令是: 2005 「.F」 「$(FC) –F $(CPPFLAGS) $(FFLAGS)」 2006 「.r」 「$(FC) –F $(FFLAGS) $(RFLAGS)」 2007 2008 六、編譯Modula-2程序的隱含規則。 2009 「<n>.sym」的目標的依賴目標會自動推導爲「<n>.def」,而且其生成命令是:「$(M2C) $(M2FLAGS) $(DEFFLAGS)」。「<n.o>」 的目標的依賴目標會自動推導爲「<n>.mod」,而且其生成命令是:「$(M2C) $(M2FLAGS) $(MODFLAGS)」。 2010 2011 七、彙編和彙編預處理的隱含規則。 2012 「<n>.o」 的目標的依賴目標會自動推導爲「<n>.s」,默認使用編譯品「as」,而且其生成命令是:「$(AS) $(ASFLAGS)」。「<n>.s」 的目標的依賴目標會自動推導爲「<n>.S」,默認使用C預編譯器「cpp」,而且其生成命令是:「$(AS) $(ASFLAGS)」。 2013 2014 八、連接Object文件的隱含規則。 2015 「<n>」目標依賴於「<n>.o」,經過運行C的編譯器來運行連接程序生成(通常是「ld」),其生成命令是:「$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)」。這個規則對於只有一個源文件的工程有效,同時也對多個Object文件(由不一樣的源文件生成)的也有效。例如以下規則: 2016 2017 x : y.o z.o 2018 2019 而且「x.c」、「y.c」和「z.c」都存在時,隱含規則將執行以下命令: 2020 2021 cc -c x.c -o x.o 2022 cc -c y.c -o y.o 2023 cc -c z.c -o z.o 2024 cc x.o y.o z.o -o x 2025 rm -f x.o 2026 rm -f y.o 2027 rm -f z.o 2028 2029 若是沒有一個源文件(如上例中的x.c)和你的目標名字(如上例中的x)相關聯,那麼,你最好寫出本身的生成規則,否則,隱含規則會報錯的。 2030 2031 九、Yacc C程序時的隱含規則。 2032 「<n>.c」的依賴文件被自動推導爲「n.y」(Yacc生成的文件),其生成命令是:「$(YACC) $(YFALGS)」。(「Yacc」是一個語法分析器,關於其細節請查看相關資料) 2033 2034 十、Lex C程序時的隱含規則。 2035 「<n>.c」的依賴文件被自動推導爲「n.l」(Lex生成的文件),其生成命令是:「$(LEX) $(LFALGS)」。(關於「Lex」的細節請查看相關資料) 2036 2037 十一、Lex Ratfor程序時的隱含規則。 2038 「<n>.r」的依賴文件被自動推導爲「n.l」(Lex生成的文件),其生成命令是:「$(LEX) $(LFALGS)」。 2039 2040 十二、從C程序、Yacc文件或Lex文件建立Lint庫的隱含規則。 2041 「<n>.ln」 (lint生成的文件)的依賴文件被自動推導爲「n.c」,其生成命令是:「$(LINT) $(LINTFALGS) $(CPPFLAGS) -i」。對於「<n>.y」和「<n>.l」也是一樣的規則。 2042 2043 2044 3、隱含規則使用的變量 2045 2046 在隱含規則中的命令中,基本上都是使用了一些預先設置的變量。你能夠在你的makefile中改變這些變量的值,或是在make的命令行中傳入這些值,或是在你的環境變量中設置這些值,不管怎麼樣,只要設置了這些特定的變量,那麼其就會對隱含規則起做用。固然,你也能夠利用make的「-R」或「--no–builtin-variables」參數來取消你所定義的變量對隱含規則的做用。 2047 2048 例如,第一條隱含規則——編譯C程序的隱含規則的命令是「$(CC) –c $(CFLAGS) $(CPPFLAGS)」。Make默認的編譯命令是「cc」,若是你把變量「$(CC)」重定義成「gcc」,把變量「$(CFLAGS)」重定義成「-g」,那麼,隱含規則中的命令所有會以「gcc –c -g $(CPPFLAGS)」的樣子來執行了。 2049 2050 咱們能夠把隱含規則中使用的變量分紅兩種:一種是命令相關的,如「CC」;一種是參數相的關,如「CFLAGS」。下面是全部隱含規則中會用到的變量: 2051 2052 一、關於命令的變量。 2053 2054 AR 2055 函數庫打包程序。默認命令是「ar」。 2056 AS 2057 彙編語言編譯程序。默認命令是「as」。 2058 CC 2059 C語言編譯程序。默認命令是「cc」。 2060 CXX 2061 C++語言編譯程序。默認命令是「g++」。 2062 CO 2063 從 RCS文件中擴展文件程序。默認命令是「co」。 2064 CPP 2065 C程序的預處理器(輸出是標準輸出設備)。默認命令是「$(CC) –E」。 2066 FC 2067 Fortran 和 Ratfor 的編譯器和預處理程序。默認命令是「f77」。 2068 GET 2069 從SCCS文件中擴展文件的程序。默認命令是「get」。 2070 LEX 2071 Lex方法分析器程序(針對於C或Ratfor)。默認命令是「lex」。 2072 PC 2073 Pascal語言編譯程序。默認命令是「pc」。 2074 YACC 2075 Yacc文法分析器(針對於C程序)。默認命令是「yacc」。 2076 YACCR 2077 Yacc文法分析器(針對於Ratfor程序)。默認命令是「yacc –r」。 2078 MAKEINFO 2079 轉換Texinfo源文件(.texi)到Info文件程序。默認命令是「makeinfo」。 2080 TEX 2081 從TeX源文件建立TeX DVI文件的程序。默認命令是「tex」。 2082 TEXI2DVI 2083 從Texinfo源文件建立軍TeX DVI 文件的程序。默認命令是「texi2dvi」。 2084 WEAVE 2085 轉換Web到TeX的程序。默認命令是「weave」。 2086 CWEAVE 2087 轉換C Web 到 TeX的程序。默認命令是「cweave」。 2088 TANGLE 2089 轉換Web到Pascal語言的程序。默認命令是「tangle」。 2090 CTANGLE 2091 轉換C Web 到 C。默認命令是「ctangle」。 2092 RM 2093 刪除文件命令。默認命令是「rm –f」。 2094 2095 二、關於命令參數的變量 2096 2097 下面的這些變量都是相關上面的命令的參數。若是沒有指明其默認值,那麼其默認值都是空。 2098 2099 ARFLAGS 2100 函數庫打包程序AR命令的參數。默認值是「rv」。 2101 ASFLAGS 2102 彙編語言編譯器參數。(當明顯地調用「.s」或「.S」文件時)。 2103 CFLAGS 2104 C語言編譯器參數。 2105 CXXFLAGS 2106 C++語言編譯器參數。 2107 COFLAGS 2108 RCS命令參數。 2109 CPPFLAGS 2110 C預處理器參數。( C 和 Fortran 編譯器也會用到)。 2111 FFLAGS 2112 Fortran語言編譯器參數。 2113 GFLAGS 2114 SCCS 「get」程序參數。 2115 LDFLAGS 2116 連接器參數。(如:「ld」) 2117 LFLAGS 2118 Lex文法分析器參數。 2119 PFLAGS 2120 Pascal語言編譯器參數。 2121 RFLAGS 2122 Ratfor 程序的Fortran 編譯器參數。 2123 YFLAGS 2124 Yacc文法分析器參數。 2125 2126 2127 4、隱含規則鏈 2128 2129 有些時候,一個目標可能被一系列的隱含規則所做用。例如,一個[.o]的文件生成,可能會是先被Yacc的[.y]文件先成[.c],而後再被C的編譯器生成。咱們把這一系列的隱含規則叫作「隱含規則鏈」。 2130 2131 在上面的例子中,若是文件[.c]存在,那麼就直接調用C的編譯器的隱含規則,若是沒有[.c]文件,但有一個[.y]文件,那麼Yacc的隱含規則會被調用,生成[.c]文件,而後,再調用C編譯的隱含規則最終由[.c]生成[.o]文件,達到目標。 2132 2133 咱們把這種[.c]的文件(或是目標),叫作中間目標。無論怎麼樣,make會努力自動推導生成目標的一切方法,無論中間目標有多少,其都會執着地把全部的隱含規則和你書寫的規則所有合起來分析,努力達到目標,因此,有些時候,可能會讓你以爲奇怪,怎麼個人目標會這樣生成?怎麼個人makefile發瘋了? 2134 2135 在默認狀況下,對於中間目標,它和通常的目標有兩個地方所不一樣:第一個不一樣是除非中間的目標不存在,纔會引起中間規則。第二個不一樣的是,只要目標成功產生,那麼,產生最終目標過程當中,所產生的中間目標文件會被以「rm -f」刪除。 2136 2137 一般,一個被makefile指定成目標或是依賴目標的文件不能被看成中介。然而,你能夠明顯地說明一個文件或是目標是中介目標,你能夠使用僞目標「.INTERMEDIATE」來強制聲明。(如:.INTERMEDIATE : mid ) 2138 2139 你也能夠阻止make自動刪除中間目標,要作到這一點,你能夠使用僞目標「.SECONDARY」來強制聲明(如:.SECONDARY : sec)。你還能夠把你的目標,以模式的方式來指定(如:%.o)成僞目標「.PRECIOUS」的依賴目標,以保存被隱含規則所生成的中間文件。 2140 2141 在「隱含規則鏈」中,禁止同一個目標出現兩次或兩次以上,這樣一來,就可防止在make自動推導時出現無限遞歸的狀況。 2142 2143 Make會優化一些特殊的隱含規則,而不生成中間文件。如,從文件「foo.c」生成目標程序「foo」,按道理,make會編譯生成中間文件「foo.o」,而後連接成「foo」,但在實際狀況下,這一動做能夠被一條「cc」的命令完成(cc –o foo foo.c),因而優化過的規則就不會生成中間文件。 2144 2145 2146 2147 5、定義模式規則 2148 2149 你能夠使用模式規則來定義一個隱含規則。一個模式規則就好像一個通常的規則,只是在規則中,目標的定義須要有"%"字符。"%"的意思是表示一個或多個任意字符。在依賴目標中一樣能夠使用"%",只是依賴目標中的"%"的取值,取決於其目標。 2150 2151 有一點須要注意的是,"%"的展開發生在變量和函數的展開以後,變量和函數的展開發生在make載入Makefile時,而模式規則中的"%"則發生在運行時。 2152 2153 2154 一、模式規則介紹 2155 2156 模式規則中,至少在規則的目標定義中要包含"%",不然,就是通常的規則。目標中的"%"定義表示對文件名的匹配,"%"表示長度任意的非空字符串。例如:"%.c"表示以".c"結尾的文件名(文件名的長度至少爲3),而"s.%.c"則表示以"s."開頭,".c"結尾的文件名(文件名的長度至少爲5)。 2157 2158 若是"%"定義在目標中,那麼,目標中的"%"的值決定了依賴目標中的"%"的值,也就是說,目標中的模式的"%"決定了依賴目標中"%"的樣子。例若有一個模式規則以下: 2159 2160 %.o : %.c ; <command ......> 2161 2162 其含義是,指出了怎麼從全部的[.c]文件生成相應的[.o]文件的規則。若是要生成的目標是"a.o b.o",那麼"%c"就是"a.c b.c"。 2163 2164 一旦依賴目標中的"%"模式被肯定,那麼,make會被要求去匹配當前目錄下全部的文件名,一旦找到,make就會規則下的命令,因此,在模式規則中,目標可能會是多個的,若是有模式匹配出多個目標,make就會產生全部的模式目標,此時,make關心的是依賴的文件名和生成目標的命令這兩件事。 2165 2166 2167 二、模式規則示例 2168 2169 下面這個例子表示了,把全部的[.c]文件都編譯成[.o]文件. 2170 2171 %.o : %.c 2172 $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ 2173 2174 其中,"$@"表示全部的目標的挨個值,"$<"表示了全部依賴目標的挨個值。這些奇怪的變量咱們叫"自動化變量",後面會詳細講述。 2175 2176 下面的這個例子中有兩個目標是模式的: 2177 2178 %.tab.c %.tab.h: %.y 2179 bison -d $< 2180 2181 這條規則告訴make把全部的[.y]文件都以"bison -d <n>.y"執行,而後生成"<n>.tab.c"和"<n>.tab.h"文件。(其中,"<n>"表示一個任意字符串)。若是咱們的執行程序"foo"依賴於文件"parse.tab.o"和"scan.o",而且文件"scan.o"依賴於文件"parse.tab.h",若是"parse.y"文件被更新了,那麼根據上述的規則,"bison -d parse.y"就會被執行一次,因而,"parse.tab.o"和"scan.o"的依賴文件就齊了。(假設,"parse.tab.o"由"parse.tab.c"生成,和"scan.o"由"scan.c"生成,而"foo"由"parse.tab.o"和"scan.o"連接生成,並且foo和其[.o]文件的依賴關係也寫好,那麼,全部的目標都會獲得知足) 2182 2183 2184 三、自動化變量 2185 2186 在上述的模式規則中,目標和依賴文件都是一系例的文件,那麼咱們如何書寫一個命令來完成從不一樣的依賴文件生成相應的目標?由於在每一次的對模式規則的解析時,都會是不一樣的目標和依賴文件。 2187 2188 自動化變量就是完成這個功能的。在前面,咱們已經對自動化變量有所提涉,相信你看到這裏已對它有一個感性認識了。所謂自動化變量,就是這種變量會把模式中所定義的一系列的文件自動地挨個取出,直至全部的符合模式的文件都取完了。這種自動化變量只應出如今規則的命令中。 2189 2190 下面是全部的自動化變量及其說明: 2191 2192 $@ 2193 表示規則中的目標文件集。在模式規則中,若是有多個目標,那麼,"$@"就是匹配於目標中模式定義的集合。 2194 2195 $% 2196 僅當目標是函數庫文件中,表示規則中的目標成員名。例如,若是一個目標是"foo.a(bar.o)",那麼,"$%"就是"bar.o","$@"就是"foo.a"。若是目標不是函數庫文件(Unix下是[.a],Windows下是[.lib]),那麼,其值爲空。 2197 2198 $< 2199 依賴目標中的第一個目標名字。若是依賴目標是以模式(即"%")定義的,那麼"$<"將是符合模式的一系列的文件集。注意,其是一個一個取出來的。 2200 2201 $? 2202 全部比目標新的依賴目標的集合。以空格分隔。 2203 2204 $^ 2205 全部的依賴目標的集合。以空格分隔。若是在依賴目標中有多個重複的,那個這個變量會去除重複的依賴目標,只保留一份。 2206 2207 $+ 2208 這個變量很像"$^",也是全部依賴目標的集合。只是它不去除重複的依賴目標。 2209 2210 $* 2211 這個變量表示目標模式中"%"及其以前的部分。若是目標是"dir/a.foo.b",而且目標的模式是"a.%.b",那麼,"$*"的值就是"dir/a.foo"。這個變量對於構造有關聯的文件名是比較有較。若是目標中沒有模式的定義,那麼"$*"也就不能被推導出,可是,若是目標文件的後綴是make所識別的,那麼"$*"就是除了後綴的那一部分。例如:若是目標是"foo.c",由於".c"是make所能識別的後綴名,因此,"$*"的值就是"foo"。這個特性是GNU make的,頗有可能不兼容於其它版本的make,因此,你應該儘可能避免使用"$*",除非是在隱含規則或是靜態模式中。若是目標中的後綴是make所不能識別的,那麼"$*"就是空值。 2212 2213 當你但願只對更新過的依賴文件進行操做時,"$?"在顯式規則中頗有用,例如,假設有一個函數庫文件叫"lib",其由其它幾個object文件更新。那麼把object文件打包的比較有效率的Makefile規則是: 2214 2215 lib : foo.o bar.o lose.o win.o 2216 ar r lib $? 2217 2218 在上述所列出來的自動量變量中。四個變量($@、$<、$%、$*)在擴展時只會有一個文件,而另三個的值是一個文件列表。這七個自動化變量還能夠取得文件的目錄名或是在當前目錄下的符合模式的文件名,只須要搭配上"D"或"F"字樣。這是GNU make中老版本的特性,在新版本中,咱們使用函數"dir"或"notdir"就能夠作到了。"D"的含義就是Directory,就是目錄,"F"的含義就是File,就是文件。 2219 2220 下面是對於上面的七個變量分別加上"D"或是"F"的含義: 2221 2222 $(@D) 2223 表示"$@"的目錄部分(不以斜槓做爲結尾),若是"$@"值是"dir/foo.o",那麼"$(@D)"就是"dir",而若是"$@"中沒有包含斜槓的話,其值就是"."(當前目錄)。 2224 2225 $(@F) 2226 表示"$@"的文件部分,若是"$@"值是"dir/foo.o",那麼"$(@F)"就是"foo.o","$(@F)"至關於函數"$(notdir $@)"。 2227 2228 "$(*D)" 2229 "$(*F)" 2230 和上面所述的同理,也是取文件的目錄部分和文件部分。對於上面的那個例子,"$(*D)"返回"dir",而"$(*F)"返回"foo" 2231 2232 "$(%D)" 2233 "$(%F)" 2234 分別表示了函數包文件成員的目錄部分和文件部分。這對於形同"archive(member)"形式的目標中的"member"中包含了不一樣的目錄頗有用。 2235 2236 "$(<D)" 2237 "$(<F)" 2238 分別表示依賴文件的目錄部分和文件部分。 2239 2240 "$(^D)" 2241 "$(^F)" 2242 分別表示全部依賴文件的目錄部分和文件部分。(無相同的) 2243 2244 "$(+D)" 2245 "$(+F)" 2246 分別表示全部依賴文件的目錄部分和文件部分。(能夠有相同的) 2247 2248 "$(?D)" 2249 "$(?F)" 2250 分別表示被更新的依賴文件的目錄部分和文件部分。 2251 2252 最後想提醒一下的是,對於"$<",爲了不產生沒必要要的麻煩,咱們最好給$後面的那個特定字符都加上圓括號,好比,"$(< )"就要比"$<"要好一些。 2253 2254 還得要注意的是,這些變量只使用在規則的命令中,並且通常都是"顯式規則"和"靜態模式規則"(參見前面"書寫規則"一章)。其在隱含規則中並無意義。 2255 2256 四、模式的匹配 2257 2258 通常來講,一個目標的模式有一個有前綴或是後綴的"%",或是沒有先後綴,直接就是一個"%"。由於"%"表明一個或多個字符,因此在定義好了的模式中,咱們把"%"所匹配的內容叫作"莖",例如"%.c"所匹配的文件"test.c"中"test"就是"莖"。由於在目標和依賴目標中同時有"%"時,依賴目標的"莖"會傳給目標,當作目標中的"莖"。 2259 2260 當一個模式匹配包含有斜槓(實際也不常常包含)的文件時,那麼在進行模式匹配時,目錄部分會首先被移開,而後進行匹配,成功後,再把目錄加回去。在進行"莖"的傳遞時,咱們須要知道這個步驟。例若有一個模式"e%t",文件"src/eat"匹配於該模式,因而"src/a"就是其"莖",若是這個模式定義在依賴目標中,而被依賴於這個模式的目標中又有個模式"c%r",那麼,目標就是"src/car"。("莖"被傳遞) 2261 2262 2263 五、重載內建隱含規則 2264 2265 你能夠重載內建的隱含規則(或是定義一個全新的),例如你能夠從新構造和內建隱含規則不一樣的命令,如: 2266 2267 %.o : %.c 2268 $(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date) 2269 2270 你能夠取消內建的隱含規則,只要不在後面寫命令就行。如: 2271 2272 %.o : %.s 2273 2274 一樣,你也能夠從新定義一個全新的隱含規則,其在隱含規則中的位置取決於你在哪裏寫下這個規則。朝前的位置就靠前。 2275 2276 2277 6、老式風格的"後綴規則" 2278 2279 後綴規則是一個比較老式的定義隱含規則的方法。後綴規則會被模式規則逐步地取代。由於模式規則更強更清晰。爲了和老版本的Makefile兼容,GNU make一樣兼容於這些東西。後綴規則有兩種方式:"雙後綴"和"單後綴"。 2280 2281 雙後綴規則定義了一對後綴:目標文件的後綴和依賴目標(源文件)的後綴。如".c.o"至關於"%o : %c"。單後綴規則只定義一個後綴,也就是源文件的後綴。如".c"至關於"% : %.c"。 2282 2283 後綴規則中所定義的後綴應該是make所認識的,若是一個後綴是make所認識的,那麼這個規則就是單後綴規則,而若是兩個連在一塊兒的後綴都被make所認識,那就是雙後綴規則。例如:".c"和".o"都是make所知道。於是,若是你定義了一個規則是".c.o"那麼其就是雙後綴規則,意義就是".c"是源文件的後綴,".o"是目標文件的後綴。以下示例: 2284 2285 .c.o: 2286 $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $< 2287 2288 後綴規則不容許任何的依賴文件,若是有依賴文件的話,那就不是後綴規則,那些後綴通通被認爲是文件名,如: 2289 2290 .c.o: foo.h 2291 $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $< 2292 2293 這個例子,就是說,文件".c.o"依賴於文件"foo.h",而不是咱們想要的這樣: 2294 2295 %.o: %.c foo.h 2296 $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $< 2297 2298 後綴規則中,若是沒有命令,那是毫無心義的。由於他也不會移去內建的隱含規則。 2299 2300 而要讓make知道一些特定的後綴,咱們能夠使用僞目標".SUFFIXES"來定義或是刪除,如: 2301 2302 .SUFFIXES: .hack .win 2303 2304 把後綴.hack和.win加入後綴列表中的末尾。 2305 2306 .SUFFIXES: # 刪除默認的後綴 2307 .SUFFIXES: .c .o .h # 定義本身的後綴 2308 2309 先清楚默認後綴,後定義本身的後綴列表。 2310 2311 make的參數"-r"或"-no-builtin-rules"也會使用得默認的後綴列表爲空。而變量"SUFFIXE"被用來定義默認的後綴列表,你能夠用".SUFFIXES"來改變後綴列表,但請不要改變變量"SUFFIXE"的值。 2312 2313 2314 7、隱含規則搜索算法 2315 2316 好比咱們有一個目標叫 T。下面是搜索目標T的規則的算法。請注意,在下面,咱們沒有提到後綴規則,緣由是,全部的後綴規則在Makefile被載入內存時,會被轉換成模式規則。若是目標是"archive(member)"的函數庫文件模式,那麼這個算法會被運行兩次,第一次是找目標T,若是沒有找到的話,那麼進入第二次,第二次會把"member"看成T來搜索。 2317 2318 一、把T的目錄部分分離出來。叫D,而剩餘部分叫N。(如:若是T是"src/foo.o",那麼,D就是"src/",N就是"foo.o") 2319 2320 二、建立全部匹配於T或是N的模式規則列表。 2321 2322 三、若是在模式規則列表中有匹配全部文件的模式,如"%",那麼從列表中移除其它的模式。 2323 2324 四、移除列表中沒有命令的規則。 2325 2326 五、對於第一個在列表中的模式規則: 2327 1)推導其"莖"S,S應該是T或是N匹配於模式中"%"非空的部分。 2328 2)計算依賴文件。把依賴文件中的"%"都替換成"莖"S。若是目標模式中沒有包含斜框字符,而把D加在第一個依賴文件的開頭。 2329 3)測試是否全部的依賴文件都存在或是理當存在。(若是有一個文件被定義成另一個規則的目標文件,或者是一個顯式規則的依賴文件,那麼這個文件就叫"理當存在") 2330 4)若是全部的依賴文件存在或是理當存在,或是就沒有依賴文件。那麼這條規則將被採用,退出該算法。 2331 2332 六、若是通過第5步,沒有模式規則被找到,那麼就作更進一步的搜索。對於存在於列表中的第一個模式規則: 2333 1)若是規則是終止規則,那就忽略它,繼續下一條模式規則。 2334 2)計算依賴文件。(同第5步) 2335 3)測試全部的依賴文件是否存在或是理當存在。 2336 4)對於不存在的依賴文件,遞歸調用這個算法查找他是否能夠被隱含規則找到。 2337 5)若是全部的依賴文件存在或是理當存在,或是就根本沒有依賴文件。那麼這條規則被採用,退出該算法。 2338 2339 七、若是沒有隱含規則能夠使用,查看".DEFAULT"規則,若是有,採用,把".DEFAULT"的命令給T使用。 2340 2341 一旦規則被找到,就會執行其至關的命令,而此時,咱們的自動化變量的值纔會生成。 2342 2343 2344 使用make更新函數庫文件 2345 ——————————— 2346 2347 函數庫文件也就是對Object文件(程序編譯的中間文件)的打包文件。在Unix下,通常是由命令"ar"來完成打包工做。 2348 2349 1、函數庫文件的成員 2350 2351 一個函數庫文件由多個文件組成。你能夠以以下格式指定函數庫文件及其組成: 2352 2353 archive(member) 2354 2355 這個不是一個命令,而一個目標和依賴的定義。通常來講,這種用法基本上就是爲了"ar"命令來服務的。如: 2356 2357 foolib(hack.o) : hack.o 2358 ar cr foolib hack.o 2359 2360 若是要指定多個member,那就以空格分開,如: 2361 2362 foolib(hack.o kludge.o) 2363 2364 其等價於: 2365 2366 foolib(hack.o) foolib(kludge.o) 2367 2368 你還能夠使用Shell的文件通配符來定義,如: 2369 2370 foolib(*.o) 2371 2372 2373 2、函數庫成員的隱含規則 2374 2375 當make搜索一個目標的隱含規則時,一個特殊的特性是,若是這個目標是"a(m)"形式的,其會把目標變成"(m)"。因而,若是咱們的成員是"%.o"的模式定義,而且若是咱們使用"make foo.a(bar.o)"的形式調用Makefile時,隱含規則會去找"bar.o"的規則,若是沒有定義bar.o的規則,那麼內建隱含規則生效,make會去找bar.c文件來生成bar.o,若是找獲得的話,make執行的命令大體以下: 2376 2377 cc -c bar.c -o bar.o 2378 ar r foo.a bar.o 2379 rm -f bar.o 2380 2381 還有一個變量要注意的是"$%",這是專屬函數庫文件的自動化變量,有關其說明請參見"自動化變量"一節。 2382 2383 2384 3、函數庫文件的後綴規則 2385 2386 你能夠使用"後綴規則"和"隱含規則"來生成函數庫打包文件,如: 2387 2388 .c.a: 2389 $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o 2390 $(AR) r $@ $*.o 2391 $(RM) $*.o 2392 2393 其等效於: 2394 2395 (%.o) : %.c 2396 $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o 2397 $(AR) r $@ $*.o 2398 $(RM) $*.o 2399 2400 2401 4、注意事項 2402 2403 在進行函數庫打包文件生成時,請當心使用make的並行機制("-j"參數)。若是多個ar命令在同一時間運行在同一個函數庫打包文件上,就頗有能夠損壞這個函數庫文件。因此,在make將來的版本中,應該提供一種機制來避免並行操做發生在函數打包文件上。 2404 2405 但就目前而言,你仍是應該不要儘可能不要使用"-j"參數。 2406 2407 2408 2409 後序 2410 —— 2411 2412 終於到寫結束語的時候了,以上基本上就是GNU make的Makefile的全部細節了。其它的產商的make基本上也就是這樣的,不管什麼樣的make,都是以文件的依賴性爲基礎的,其基本是都是遵循一個標準的。這篇文檔中80%的技術細節都適用於任何的make,我猜想"函數"那一章的內容可能不是其它make所支持的,而隱含規則方面,我想不一樣的make會有不一樣的實現,我沒有精力來查看GNU的make和VC的nmake、BCB的make,或是別的UNIX下的make有些什麼樣的差異,一是時間精力不夠,二是由於我基本上都是在Unix下使用make,之前在SCO Unix和IBM的AIX,如今在Linux、Solaris、HP-UX、AIX和Alpha下使用,Linux和Solaris下更多一點。不過,我能夠確定的是,在Unix下的make,不管是哪一種平臺,幾乎都使用了Richard Stallman開發的make和cc/gcc的編譯器,並且,基本上都是GNU的make(公司裏全部的UNIX機器上都被裝上了GNU的東西,因此,使用GNU的程序也就多了一些)。GNU的東西仍是很不錯的,特別是使用得深了之後,愈來愈以爲GNU的軟件的強大,也愈來愈以爲GNU的在操做系統中(主要是Unix,甚至Windows)"殺傷力"。 2413 2414 對於上述全部的make的細節,咱們不但能夠利用make這個工具來編譯咱們的程序,還能夠利用make來完成其它的工做,由於規則中的命令能夠是任何Shell之下的命令,因此,在Unix下,你不必定只是使用程序語言的編譯器,你還能夠在Makefile中書寫其它的命令,如:tar、awk、mail、sed、cvs、compress、ls、rm、yacc、rpm、ftp……等等,等等,來完成諸如"程序打包"、"程序備份"、"製做程序安裝包"、"提交代碼"、"使用程序模板"、"合併文件"等等五花八門的功能,文件操做,文件管理,編程開發設計,或是其它一些異想天開的東西。好比,之前在書寫銀行交易程序時,因爲銀行的交易程序基本同樣,就見到有人書寫了一些交易的通用程序模板,在該模板中把一些網絡通信、數據庫操做的、業務操做共性的東西寫在一個文件中,在這些文件中用些諸如"@@@N、###N"奇怪字串標註一些位置,而後書寫交易時,只需按照一種特定的規則書寫特定的處理,最後在make時,使用awk和sed,把模板中的"@@@N、###N"等字串替代成特定的程序,造成C文件,而後再編譯。這個動做很像數據庫的"擴展C"語言(即在C語言中用"EXEC SQL"的樣子執行SQL語句,在用cc/gcc編譯以前,須要使用"擴展C"的翻譯程序,如cpre,把其翻譯成標準C)。若是你在使用make時有一些更爲絕妙的方法,請記得告訴我啊。 2415 2416 回頭看看整篇文檔,不覺記起幾年前剛剛開始在Unix下作開發的時候,有人問我會不會寫Makefile時,我兩眼發直,根本不知道在說什麼。一開始看到別人在vi中寫完程序後輸入"!make"時,還覺得是vi的功能,後來才知道有一個Makefile在做怪,因而上網查啊查,那時又不肯意看英文,發現就根本沒有中文的文檔介紹Makefile,只得看別人寫的Makefile,本身瞎碰瞎搞才積累了一點知識,但在不少地方徹底是知其然不知因此然。後來開始從事UNIX下產品軟件的開發,看到一個400人年,近200萬行代碼的大工程,發現要編譯這樣一個龐然大物,若是沒有Makefile,那會是多麼恐怖的同樣事啊。因而橫下心來,狠命地讀了一堆英文文檔,才以爲對其掌握了。但發現目前網上對Makefile介紹的文章仍是少得那麼的可憐,因此想寫這樣一篇文章,共享給你們,但願能對各位有所幫助。 2417 2418 如今我終於寫完了,看了看文件的建立時間,這篇技術文檔也寫了兩個多月了。發現,本身知道是一回事,要寫下來,跟別人講述又是另一回事,並且,如今愈來愈沒有時間專研技術細節,因此在寫做時,發如今闡述一些細節問題時很難作到嚴謹和精練,並且對先講什麼後講什麼不是很清楚,因此,仍是參考了一些國外站點上的資料和題綱,以及一些技術書籍的語言風格,才得以完成。整篇文檔的提綱是基於GNU的Makefile技術手冊的提綱來書寫的,並結合了本身的工做經驗,以及本身的學習歷程。由於歷來沒有寫過這麼長,這麼細的文檔,因此必定會有不少地方存在表達問題,語言歧義或是錯誤。因些,我迫切地得等待各位給我指證和建議,以及任何的反饋。 2419 2420 最後,仍是利用這個後序,介紹一下本身。我目前從事於全部Unix平臺下的軟件研發,主要是作分佈式計算/網格計算方面的系統產品軟件,而且我對於下一代的計算機革命——網格計算很是地感興趣,對於分佈式計算、P2P、Web Service、J2EE技術方向也很感興趣,同時,對於項目實施、團隊管理、項目管理也小有心得,但願一樣和我戰鬥在「技術和管理並重」的陣線上的年輕一代,可以和我多多地交流。個人MSN是:haoel@hotmail.com(經常使用),QQ是:753640(不經常使用)。(注:請勿給我MSN的郵箱發信,因爲hotmail的垃圾郵件致使我拒收這個郵箱的全部來信) 2421 2422 我歡迎任何形式的交流,不管是討論技術仍是管理,或是其它海闊天空的東西。除了政治和娛樂新聞我不關心,其它只要積極向上的東西我都歡迎! 2423 2424 最最後,我還想介紹一下make程序的設計開發者。 2425 2426 首當其衝的是: Richard Stallman 2427 2428 開源軟件的領袖和先驅,歷來沒有領過一天工資,歷來沒有使用過Windows操做系統。對於他的事蹟和他的軟件以及他的思想,我無需說過多的話,相信你們對這我的並不比我陌生,這是他的主頁:http://www.stallman.org/ 。 2429 2430 2431 2432 第二位是:Roland McGrath 2433 2434 我的主頁是:http://www.frob.com/~roland/ ,下面是他的一些事蹟: 2435 2436 1) 合做編寫了並維護GNU make。 2437 2438 2) 和Thomas Bushnell一同編寫了GNU Hurd。 2439 2440 3) 編寫並維護着GNU C library。 2441 2442 4) 合做編寫並維護着部分的GNU Emacs。 2443 2444 2445 2446 在此,向這兩位開源項目的鬥士致以最真切的敬意。 2447 2448 2449 (全文完)