Linux下makefile教程

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

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

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

現在講述如何寫 makefile的文章比較少,這是我想寫這篇文章的原因。當然,不同廠商的 
make各不相同,也有不同的語法,但其本質都是在“文件依賴性”上做文章,這裏,我僅 
對GNU的make進行講述,我的環境是RedHat Linux 8.0,make的版本是3.80。必竟,這個m 
ake是應用最爲廣泛的,也是用得最多的。而且其還是最遵循於IEEE 1003.2-1992 標準的 
(POSIX.2)。 

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



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

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

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

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

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

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



Makefile 介紹 
——————— 

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

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

3)如果這個工程的頭文件被改變了,那麼我們需要編譯引用了這個頭文件的所有C文件,並
鏈接目標程序。

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


一、 Makefile的規則 

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

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

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

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

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

這是一個文件的依賴關係,也就是說, target這一個或多個的目標文件依賴於prerequisi
tes中的文件,其生成規則定義在command中。說白一點就是說,prerequisites中如果有一
個以上的文件比target文件要新的話,command所定義的命令就會被執行。這就是 Makefi
le的規則。也就是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並不管命令是怎麼工作的,他只管執行所定義的命令。m 
ake會比較targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期 
要比targets文件的日期要新,或者target不存在的話,那麼,make就會執行後續定義的命 
令。 

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



三、make是如何工作的 

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

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

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

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

於是在我們編程中,如果這個工程已被編譯過了,當我們修改了其中一個源文件,比如fi 
le.c,那麼根據我們的依賴性,我們的目標file.o會被重編譯(也就是在這個依性關係後 
面所定義的命令),於是file.o的文件也是最新的啦,於是file.o的文件修改時間要比ed 
it要新,所以edit也會被重新鏈接了(詳見edit目標文件後定義的命令)。 

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


四、 makefile中使用變量 

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

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

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

比如,我們聲明一個變量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正不管 
什麼啦,只要能夠表示obj文件就行了。我們在 makefile一開始就這樣定義: 

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

於是,我們就可以很方便地在我們的 makefile中以“$(objects)”的方式來使用這個變量 
了,於是我們的改良版 makefile就變成下面這個樣子: 

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

edit : $(objects) 
cc -o edit $(objects) 
main.o : main.c defs.h 
cc -c main.c 
kbd.o : kbd.c defs.h command.h 
cc -c kbd.c 
command.o : command.c defs.h command.h 
cc -c command.c 
display.o : display.c defs.h buffer.h 
cc -c display.c 
insert.o : insert.c defs.h buffer.h 
cc -c insert.c 
search.o : search.c defs.h buffer.h 
cc -c search.c 
files.o : files.c defs.h buffer.h command.h 
cc -c files.c 
utils.o : utils.c defs.h 
cc -c utils.c 
clean : 
rm edit $(objects) 


於是如果有新的 .o 文件加入,我們只需簡單地修改一下 objects 變量就可以了。 

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


五、讓make自動推導 

GNU的make很強大,它可以自動推導文件以及文件依賴關係後面的命令,於是我們就沒必要 
去在每一個[.o]文件後都寫上類似的命令,因爲,我們的make會自動識別,並自己推導命 
令。 

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


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是個僞 
目標文件。 

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


六、另類風格的 makefile 

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

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

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

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

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

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

七、清空目標文件的規則 

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

clean: 
rm edit $(objects) 

更爲穩健的做法是: 

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

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


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

Makefile 總述 
——————— 

一、 Makefile裏有什麼? 

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


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

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

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

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

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

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


二、 Makefile的文件名 

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

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


三、引用其它的 Makefile 

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

include <filename> 

filename可以是當前操作系統Shell的文件模式(可以保含路徑和通配符) 

在 include前面可以有一些空字符,但是絕不能是[Tab]鍵開始。include和<filename>可 
以用一個或多個空格隔開。舉個例子,你有這樣幾個 Makefile:a.mk、b.mk、c.mk,還有 
一個文件叫foo.make,以及一個變量$(bar),其包含了e.mk和 f.mk,那麼,下面的語句: 


include foo.make *.mk $(bar) 

等價於: 

include foo.make a.mk b.mk c.mk e.mk f.mk 

make 命令開始時,會把找尋include所指出的其它 Makefile,並把其內容安置在當前的位 
置。就好像C/C++的#include指令一樣。如果文件都沒有指定絕對路徑或是相對路徑的話, 
make會在當前目錄下首先尋找,如果當前目錄下沒有找到,那麼,make還會在下面的幾個 
目錄下找: 

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

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

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


四、環境變量 MAKEFILES 

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

但是在這裏我還是建議不要使用這個環境變量,因爲只要這個變量一被定義,那麼當你使 
用make時,所有的 Makefile都會受到它的影響,這絕不是你想看到的。在這裏提這個事, 
只是爲了告訴大家,也許有時候你的 Makefile出現了怪事,那麼你可以看看當前環境中有 
沒有定義這個變量。 


五、make的工作方式 

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和de 
fs.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吐舌rerequisites”在一行,那麼,必須以[Tab鍵 
]開頭,如果和prerequisites在一行,那麼可以用分號做爲分隔。(見上) 

prerequisites也就是目標所依賴的文件(或依賴目標)。如果其中的某個文件要比目標文 
件要新,那麼,目標就被認爲是“過時的”,被認爲是需要重生成的。這個在前面已經講 
過了。 

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

一般來說,make會以UNIX的標準Shell,也就是/bin/sh來執行命令。 


三、在規則中使用通配符 

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

波浪號(“~”)字符在文件名中也有比較特殊的用途。如果是“~/test”,這就表示當前 
用戶的$HOME目錄下的test目錄。而“~hchen /test”則表示用戶hchen的宿主目錄下的te 
st目錄。(這些都是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的關鍵字,我們將在後面討論。

四、 makefile中使用變量 

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

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

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

比如,我們聲明一個變量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正不管 
什麼啦,只要能夠表示obj文件就行了。我們在 makefile一開始就這樣定義: 

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

於是,我們就可以很方便地在我們的 makefile中以“$(objects)”的方式來使用這個變量 
了,於是我們的改良版 makefile就變成下面這個樣子: 

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

edit : $(objects) 
cc -o edit $(objects) 
main.o : main.c defs.h 
cc -c main.c 
kbd.o : kbd.c defs.h command.h 
cc -c kbd.c 
command.o : command.c defs.h command.h 
cc -c command.c 
display.o : display.c defs.h buffer.h 
cc -c display.c 
insert.o : insert.c defs.h buffer.h 
cc -c insert.c 
search.o : search.c defs.h buffer.h 
cc -c search.c 
files.o : files.c defs.h buffer.h command.h 
cc -c files.c 
utils.o : utils.c defs.h 
cc -c utils.c 
clean : 
rm edit $(objects) 


於是如果有新的 .o 文件加入,我們只需簡單地修改一下 objects 變量就可以了。 

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


五、讓make自動推導 

GNU的make很強大,它可以自動推導文件以及文件依賴關係後面的命令,於是我們就沒必要 
去在每一個[.o]文件後都寫上類似的命令,因爲,我們的make會自動識別,並自己推導命 
令。 

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


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是個僞 
目標文件。 

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


六、另類風格的 makefile 

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

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

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

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

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

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

七、清空目標文件的規則 

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

clean: 
rm edit $(objects) 

更爲穩健的做法是: 

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

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


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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章