當前位置:首頁 > 汽車電子1 > 糖果Autosar
[導(dǎo)讀]1.makefile很重要什么是makefile?或許很多Winodws的程序員都不知道這個東西,因為那些Windows的IDE都為你做了這個工作,但我覺得要作一個好的和professional的程序員,makefile還是要懂。這就好像現(xiàn)在有這么多的HTML的編輯器,但如果你想...


1. makefile很重要

什么是makefile?或許很多Winodws的程序員都不知道這個東西,因為那些Windows的IDE都為你做了這個工作,但我覺得要作一個好的和professional的程序員,makefile還是要懂。這就好像現(xiàn)在有這么多的HTML的編輯器,但如果你想成為一個專業(yè)人士,你還是要了解HTML的標識的含義。特別在Unix下的軟件編譯,你就不能不自己寫makefile了,會不會寫makefile,從一個側(cè)面說明了一個人是否具備完成大型工程的能力。因為,makefile關(guān)系到了整個工程的編譯規(guī)則。一個工程中的源文件不計數(shù),其按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的規(guī)則來指定,哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯,甚至于進行更復(fù)雜的功能操作,因為makefile就像一個Shell腳本一樣,其中也可以執(zhí)行操作系統(tǒng)的命令。makefile帶來的好處就是——“自動化編譯”,一旦寫好,只需要一個make命令,整個工程完全自動編譯,極大的提高了軟件開發(fā)的效率。make是一個命令工具,是一個解釋makefile中指令的命令工具,一般來說,大多數(shù)的IDE都有這個命令,比如:Delphi的make,VisualC 的nmake,Linux下GNU的make??梢?,makefile都成為了一種在工程方面的編譯方法。

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

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

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

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

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

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

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

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

2. Makefile介紹

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

首先,我們用一個示例來說明Makefile的書寫規(guī)則。以便給大家一個感性認識。這個示例來源于GNU的make使用手冊,在這個示例中,我們的工程有8個C文件,和3個頭文件,我們要寫一個Makefile來告訴make命令如何編譯和鏈接這幾個文件。我們的規(guī)則是:

1)如果這個工程沒有編譯過,那么我們的所有C文件都要編譯并被鏈接。

2)如果這個工程的某幾個C文件被修改,那么我們只編譯被修改的C文件,并鏈接目標程序。

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

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

2.1 Makefile的規(guī)則

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

target...:prerequisites...

command

...

...

-------------------------------------------------------------------------------

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

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

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

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

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

2.2 一個示例

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

edit:main.o kbd.o command.odisplay.o\

insert.o search.o files.outils.o

cc-oeditmain.okbd.ocommand.odisplay.o\

insert.osearch.ofiles.outils.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.hcommand.h

cc –c command.c

display.o:display.c defs.hbuffer.h

cc –c display.c

insert.o:insert.c defs.hbuffer.h

cc-c insert.c

search.o:search.c defs.hbuffer.h

cc-c search.c

files.o:files.c defs.hbuffer.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.odisplay.o\

insert.o search.o files.outils.o

反斜杠(\)是換行符的意思。這樣比較便于Makefile的易讀。我們可以把這個內(nèi)容保存在文件為“Makefile”或“makefile”的文件中,然后在該目錄下直接輸入命令“make”就可以生成執(zhí)行文件edit。如果要刪除執(zhí)行文件和所有的中間目標文件,那么,只要簡單地執(zhí)行一下“make clean”就可以了。

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

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

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

2.3 make是如何工作的

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

make會在當前目錄下找名字叫“Makefile”或“makefile”的文件。

如果找到,它會找文件中的第一個目標文件(target),在上面的例子中,他會找到“edit”這個文件,并把這個文件作為最終的目標文件。

如果edit文件不存在,或是edit所依賴的后面的.o文件的文件修改時間要比edit這個文件新,那么,他就會執(zhí)行后面所定義的命令來生成edit這個文件。

如果edit所依賴的.o文件也存在,那么make會在當前文件中找目標為.o文件的依賴性,如果找到則再根據(jù)那一個規(guī)則生成.o文件。(這有點像一個堆棧的過程)

當然,你的C文件和H文件是存在的啦,于是make會生成.o文件,然后再用.o文件聲明make的終極任務(wù),也就是執(zhí)行文件edit了。

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

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

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

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

2.4 makefile中使用變量

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

edit:main.o kbd.o command.odisplay.o\

insert.o search.o files.outils.o

cc –o edit main.o kbd.ocommand.o display.o\

insert.o search.o files.outils.o

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

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

objects=main.o kbd.o command.odisplay.o\

insert.o search.o files.outils.o

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

objects=? main.o kbd.o command.o display.o\

insert.osearch.o files.o utils.o

edit: $(objects)

cc–o edit$(objects)

main.o :?? main.cdefs.h

cc –c main.c

kbd.o :kbd.c defs.h command.h

cc–c kbd.c

command.o :command.c defs.hcommand.h

cc–c command.c

display.o :display.c defs.hbuffer.h

cc-c display.c

insert.o :insert.c defs.hbuffer.h

cc–c insert.c

search.o :search.c defs.hbuffer.h

cc–c search.c

files.o :files.c defs.hbuffer.hcommand.h

cc–c files.c

utils.o :utils.c defs.h

cc– c utils.c

clean:

rmedit$(objects)

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

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

2.5 讓make自動推導(dǎo)

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

只要make看到一個[.o]文件,它就會自動的把[.c]文件加在依賴關(guān)系中,如果make找到一個whatever.o,那么whatever.c,就會是whatever.o的依賴文件。并且cc-cwhatever.c也會被推導(dǎo)出來,于是,我們的makefile再也不用寫得這么復(fù)雜。我們的是新的makefile又出爐了。

objects = main.okbd.o command.o display.o \

insert.osearch.o files.o utils.o

edit : $(objects)

cc-o edit $(objects)

main.o : defs.h

kbd.o : defs.hcommand.h

command.o : defs.hcommand.h

display.o : defs.hbuffer.h

insert.o : defs.hbuffer.h

search.o : defs.hbuffer.h

files.o : defs.hbuffer.h command.h

utils.o : defs.h

.PHONY : clean

clean :

rmedit $(objects)

這種方法,也就是make的“隱晦規(guī)則”。上面文件內(nèi)容中,“.PHONY”表示,clean是個偽目標文件。

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

2.6 另類風格的makefile

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

objects = main.okbd.o command.o display.o \

insert.osearch.o files.o utils.o

edit : $(objects)

cc -o edit $(objects)

$(objects) : defs.h

kbd.o command.ofiles.o : command.h

display.o insert.osearch.o files.o : buffer.h

.PHONY : clean

clean :

rm edit $(objects)

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

2.7 清空目標文件的規(guī)則

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

clean:

rm edit $(objects)

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

.PHONY: clean

clean:

-rm edit $(objects)

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

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

3. Makefile總述

3.1 Makefile里有什么?

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

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

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

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

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

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

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

3.2 Makefile的文件名

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

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

3.3 引用其它的Makefile

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

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

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

include foo.make *.mk$(bar)

等價于:

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

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

1.如果make執(zhí)行時,有“-I”“--include-dir”參數(shù),那么make就會在這個參數(shù)所指定的目錄下去尋找。

2.如果目錄/include(一般是:/usr/local/bin/usr/include)存在的話,make也會去找。

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

-include

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

3.4 環(huán)境變量MAKEFILES

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

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

3.5 make的工作方式

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

1.讀入所有的Makefile。

2.讀入被include的其它Makefile。

3.初始化文件中的變量。

4.推導(dǎo)隱晦規(guī)則,并分析所有規(guī)則。

5.為所有的目標文件創(chuàng)建依賴關(guān)系鏈。

6.根據(jù)依賴關(guān)系,決定哪些目標要重新生成。

7.執(zhí)行生成命令。

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

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

4. Makefile書寫規(guī)則

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

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

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

4.1 規(guī)則舉例

foo.o:????? foo.c defs.h #foo模塊

cc–c –g foo.c

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

1.文件的依賴關(guān)系,foo.o依賴于foo.cdefs.h的文件,如果foo.cdefs.h的文件日期要比foo.o文件日期要新,或是foo.o不存在,那么依賴關(guān)系發(fā)生。

2.如果生成(或更新)foo.o文件。也就是那個cc命令,其說明了,如何生成foo.o這個文件。(當然foo.c文件includedefs.h文件)

4.2 規(guī)則的語法

targets:prerequisites

command

...

或是這樣:

targets:prerequisites;command

command

...

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

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

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

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

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

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

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

"~"

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

"*"

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

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

clean:

rm–f *.o

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

print: *.c

lpr–p $?

touchprint

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

objects=*.o

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

objects:=$(wildcard*.o)

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

4.4 文件搜尋

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

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

VPATH=src:../headers

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

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

1.vpath為符合模式的文件指定搜索目錄。

2.vpath清除符合模式的文件的搜索目錄。

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

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

vpath %.h ../headers

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

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

vpath %.c foo

vpath % blish

vpath %.c bar

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

vpath %.c foo:bar

vpath % blish

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

4.5 偽目標

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

clean:

rm*.o temp

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

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

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

.PHONY:clean

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

.PHONY:clean

clean:

rm*.otemp

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

all:prog1 prog2 prog3

.PHONY:all

prog1:prog1.o utils.o

cc–o prog1 prog1.o utils.o

prog2:prog2.o

cc–o prog2 prog2.o

prog3:prog3.o sort.o utils.o

cc-o prog3 prog3.o sort.o utils.o

我們知道,Makefile中的第一個目標會被作為其默認目標。我們聲明了一個“all”的偽目標,其依賴于其它三個目標。由于偽目標的特性是,總是被執(zhí)行的,所以其依賴的那三個目標就總是不如“all”這個目標新。所以,其它三個目標的規(guī)則總是會被決議。也就達到了我們一口氣生成多個目標的目的?!?PHONY:all”聲明了“all”這個目標為“偽目標”。

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

.PHONY:cleanall cleanobjcleandiff

cleanall:cleanobj cleandiff

rmprogram

cleanobj:

rm*.o

cleandiff:

rm*.diff

“makeclean”將清除所有要被清除的文件?!癱leanobj”和“cleandiff”這兩個偽目標有點像“子程序”的意思。我們可以輸入“make cleanall”和“make cleanobj”和“make cleandiff”命令來達到清除不同種類文件的目的

4.6 多目標

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

bigoutput littleoutput:text.g

generate text.g -@)>$@

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

bigoutput:text.g

generate text.g -big >bigoutput

littleoutput:text.g

generate text.g -little> littleoutput

其中,-@)中的“@”表示目標的集合,就像一個數(shù)組,“$@”依次取出目標,并執(zhí)于命令。

4.7 靜態(tài)模式

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

::

...

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

target-parrtern是指明了targets的模式,也就是的目標集模式。

prereq-parrterns是目標的依賴模式,它對target-parrtern形成的模式再進行一次依賴目標的定義。

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

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

看一個例子:

objects=foo.o bar.o

all:$(objects)

$(objects):%.o:%.c

(CFLAGS) @

上面的例子中,指明了我們的目標從object集合的模式,而依賴模式“%.c”則取模式“%.o”的“%”,也就是“foobar”,并為其加下“.c”的后綴,于是,我們的依賴目標就是“foo.c bar.c”。而命令中的“@”則是自動化變量,“@”表示目標集(也褪恰癴oo.o bar.o”)。于是,上面的規(guī)則展開后等價于下面的規(guī)則:

foo.o:foo.c

(CFLAGS) foo.c –o foo.o

bar.o:bar.c

(CFLAGS) bar.c –o bar.o

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

files=foo.elc bar.o lose.o

(files)):%.o:%.c

(CFLAGS) @

(files)):%.elc:%.el

emacs-f batch-byte-compile $<

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

4.8 自動生成依賴性

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

main.o:main.c defs.h

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

cc -M main.c

其輸出是:

main.o:main.c defs.h

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

gcc -M main.c的輸出是:

main.o:main.cdefs.h/usr/include/stdio.h/usr/include/features.h\

/usr/include/sys/cdefs.h/usr/include/gnu/stubs.h\

/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h\

/usr/include/bits/types.h/usr/include/bits/pthreadtypes.h\

/usr/include/bits/sched.h/usr/include/libio.h\

/usr/include/_G_config.h/usr/include/wchar.h\

/usr/include/bits/wchar.h/usr/include/gconv.h\

/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h\

/usr/include/bits/stdio_lim.h

gcc -MM main.c的輸出則是:

main.o:main.c defs.h

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

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

這里,我們給出了一個模式規(guī)則來產(chǎn)生[.d]文件:

%.d: %.c

@set -e;rm -f $@;\

(CPPFLAGS) " data-formula-type="inline-equation" style="">@.;\

sed's,@:,g'< " data-formula-type="inline-equation" style="">@;\

rm -f $@.

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

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

main.o:main.c defs.h

轉(zhuǎn)成:

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

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

sources = foo.c bar.c

include $(sources:.c=.d)

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

5. Makefile書寫命令

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

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

5.1 顯示命令

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

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

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

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

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

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

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

5.2 命令執(zhí)行

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

示例一:

exec:

cd/home/hchen

pwd

示例二:

exec:

cd/home/hchen;pwd

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

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

5.3 命令出錯

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

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

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

clean:

-rm-f *.o

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

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

5.4 嵌套執(zhí)行make

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

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

subsystem:

cd subdir
本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
關(guān)閉