0.1 關于程式的編譯和連結
在此,我想多說關于程式編譯的一些規範和方法,一般來說,無論是C、C++、還是pas,首先要把源檔案編譯成中間代碼檔案,在Windows下也就是 .obj 檔案,UNIX下是 .o 檔案,即ObjectFile,這個動作叫做編譯(compile)。然後再把大量的Object File合成執行檔案,這個動作叫作連結(link)。
編譯時,編譯器需要的是文法的正确,函數與變量的聲明的正确。對于後者,通常是你需要告訴編譯器頭檔案的所在位置(頭檔案中應該隻是聲明,而定義應該放在C/C++檔案中),隻要所有的文法正确,編譯器就可以編譯出中間目标檔案。一般來說,每個源檔案都應該對應于一個中間目标檔案(O檔案或是OBJ檔案)。
連結時,主要是連結函數和全局變量。是以,我們可以使用這些中間目标檔案(.O檔案或是.OBJ檔案)來連結我們的應用程式。連結器并不管函數所在的源檔案,隻管函數的中間目标檔案。多數時候,由于源檔案太多,編譯生成的中間目标檔案太多,而在連結時需要明确指出中間目标檔案名,這對于編譯很不友善,是以,我們要給中間目标檔案打個包,在Windows下這種包叫“庫檔案”(Library File),也就是 .lib 檔案,在UNIX下,是Archive File,也就是.a 檔案。
總結一下,源檔案首先會生成中間目标檔案,再由中間目标檔案生成可執行檔案。在編譯時,編譯器隻檢測程式文法,和函數、變量是否被聲明。如果函數未被聲明,編譯器會給出一個警告,但可以生成ObjectFile。而在連結程式時,連結器會在所有的ObjectFile中找尋函數的實作,如果找不到,那到就會報連結錯誤碼(LinkerError),在VC下,這種錯誤一般是:Link 2001錯誤,意思說是說,連結器未能找到函數的實作。你需要指定函數的ObjectFile.
言歸正傳。
Makefile 介紹
-----------------------------------------------------------------
make指令執行時,需要一個 Makefile 檔案,以告訴make怎樣去編譯和連結程式。
首先,我們用一個示例來說明Makefile的書寫規則。給大家一個感性認識。這個示例來源于GNU的make使用手冊,在這個示例中,我們的工程有8個C檔案,和3個頭檔案,我們要寫一個Makefile告訴make如何編譯和連結這些檔案。我們的規則是:
(1)如果這個工程沒有編譯過,那麼我們的所有C檔案都要編譯并被連結。
(2)如果這個工程的某幾個C檔案被修改,那麼我們隻編譯被修改的C檔案,并連結目标程式。
(3)如果這個工程的頭檔案被改變了,那麼我們需要重新編譯所有引用了這幾個頭檔案的C檔案,并連結目标程式。
隻要Makefile寫得夠好,所有這一切,我們隻用一個make指令就可以完成,make指令會根據目前的檔案修改的情況來确定哪些檔案需要重編譯,進而自動編譯必要的檔案并連結目标程式。
1.1 Makefile的規則
在講述這個Makefile前,還是先來看一看Makefile的規則。
target... : prerequisites ...
command
...
target就是一個目标檔案,可以是Object File,也可以是可執行檔案,還可以是一個标簽(Label),對于标簽這種特性,在後續的“僞目标”章節中會有叙述。
prerequisites就是生成上面那個target所需要的檔案或是目标。
command是make需要執行的指令。(任意的Shell指令)
這是一個檔案的依賴關系,也就是說,target這一個或多個目标檔案依賴于prerequisites中的檔案,其生成規則定義在command中。說白一點就是,prerequisites中如果有一個以上的檔案比target檔案要新的話,command所定義的指令就會被執行。這就是 Makefile的規則。也是Makefile最核心的内容。
1.2 一個示例
如果一個工程有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.odisplay.o /
insert.o search.o files.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.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.odisplay.o /
insert.o search.o files.o utils.o
斜杠(/)是換行符的意思。這樣便于Makefile的閱讀。我們可以把這個内容儲存在名為“Makefile”或“makefile”的檔案中,然後在該目錄下直接輸入指令“make”就可以生成執行檔案edit。如果要删除執行檔案和所有的中間目标檔案,那麼,隻要簡單地執行一下“makeclean”就可以了。
在這個makefile中,target包含:可執行檔案edit和中間目标檔案(*.o),依賴檔案(prerequisites)就是冒号後面的那些 .c 檔案和.h檔案。每一個 .o 檔案都有一組依賴檔案,而這些 .o 檔案又是edit的依賴檔案。依賴關系實質上說明了目标檔案是由哪些檔案生成的,換言之,目标檔案是根據哪些檔案來進行更新的。
在定義好依賴關系之後,後續的那一行定義了生成目标檔案所需執行的作業系統指令,一定要以一個Tab鍵作為開頭。記住,make并不管指令是怎麼工作的,他隻管執行所定義的指令。make會比較targets檔案和prerequisites檔案的修改日期,如果prerequisites檔案的日期要比targets檔案的日期要新,或者target不存在的話,那麼,make就會執行後續定義的指令。
這裡要說明的是,clean不是一個檔案,而是一個動作名字,有點像C語言中的lable一樣,其冒号後什麼也沒有,是以,make就不會自動去找檔案的依賴性,也就不會自動執行其後所定義的指令。要執行其後的指令,就要在make指令後顯式地指出這個lable的名字。這樣的方法非常有用,我們可以在一個makefile中定義不用編譯或是和編譯無關的指令,比如程式的打包,程式的備份,等等。
1.3 make是如何工作的
預設方式下,我們隻要輸入make指令,make會在目前目錄下找名字叫“Makefile”或“makefile”的檔案。
如果找到,它會找檔案中的第一個target,在上面的例子中,它會找到“edit”,并把這個檔案作為最終的目标檔案。
如果edit檔案不存在,或是edit所依賴的.o 檔案的修改時間比edit新,那麼,它就會執行後面所定義的指令來生成edit這個檔案。
如果edit所依賴的.o檔案也不存在,那麼make會在目前檔案中找目标為.o檔案的依賴性,如果找到則再根據那一個規則生成.o檔案。(這有點像一個堆棧的過程)
當然,你的C檔案和H檔案是存在的啦,于是make會生成.o 檔案,然後再用 .o 檔案生成edit。
這就是整個make的依賴性,make會一層又一層地去找檔案的依賴關系,直到最終編譯出第一個目标檔案。在找尋的過程中,如果出現錯誤,比如被依賴的檔案找不到,make就會直接退出,并報錯,而對于所定義的指令的錯誤,或是編譯不成功,make根本不理。make隻管檔案的依賴性,即,如果在我找了依賴關系之後,冒号後面的檔案還是不在,那麼對不起,我就不工作啦。
通過上述分析,我們知道,像clean這種,沒有被第一個目标檔案直接或間接關聯,那麼它後面所定義的指令将不會被自動執行,不過,我們可以顯式要make執行。即指令——“make clean”,以此來清除所有的目标檔案,以便重編譯。
于是在我們程式設計中,如果這個工程已被編譯過了,當我們修改了其中一個源檔案,比如file.c,那麼根據依賴性,我們的目标file.o會被重編譯,于是file.o檔案随即更新,于是file.o的修改時間比edit要新,是以edit也會被重新連結了(詳見edit目标檔案後定義的指令)。
而如果我們改變了“command.h”,那麼,kdb.o、command.o和files.o都會被重編譯,并且,edit會被重連結。
1.4 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.odisplay.o /
insert.o search.o files.outils.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 變量就可以了。
1.5 讓make自動推導
GNU的make很強大,它可以自動推導檔案以及檔案依賴關系後面的指令,于是我們就沒必要在每一個[.o]檔案後都寫上類似的指令。
隻要make看到一個[.o]檔案,它就會自動的把[.c]檔案加在依賴關系中,如果make找到一個whatever.o,那麼whatever.c,就會是whatever.o的依賴檔案。并且 cc -c whatever.c 也會被推導出來,于是,我們的makefile再也不用寫得這麼複雜。我們的是新的makefile又出爐了。
objects = main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
rm edit $(objects)
這種方法,也就是make的“隐晦規則”。上面檔案内容中,“.PHONY”表示,clean是個僞目标檔案。
1.6 另類風格的makefile
既然make可以自動推導指令,那麼我們就想到——多個重複的[.h]能不能收攏起來?沒有問題,來看看最新風格的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檔案,那就理不清楚了。
1.7 清空目标檔案的規則
每個Makefile中都應該寫一個清空目标檔案(.o和執行檔案)的規則,這不僅便于重編譯,也很利于保持檔案的清潔。這是一個“修養”。一般的風格都是:
clean:
rm edit $(objects)
更為穩健的做法是:
.PHONY : clean
clean :
-rm edit $(objects)
前面說過,.PHONY意思表示clean是一個“僞目标”。而在rm指令前面加了一個小減号的意思是:也許某些檔案出現問題,但不要管,繼續做後面的事。當然,clean的規則不要放在檔案的開頭,不然,這就會變成make的預設目标,相信誰也不願意這樣。不成文的規矩是——clean從來都是放在檔案的最後。
上面是一個makefile的概貌,也是基礎,下面還有很多makefile的相關細節。
2 Makefile 總述
2.1 Makefile裡有什麼?
Makefile主要包含五類東西:顯式規則、隐晦規則、變量定義、檔案訓示和注釋。
顯式規則:說明如何生成一個或多個目标檔案。這是由Makefile的書寫者明确指出要生成的檔案、依賴檔案和生成指令。
隐晦規則。由于make有自動推導的功能,是以隐晦規則讓我們可以比較粗糙地簡略地書寫Makefile。
變量的定義。在Makefile中我們要定義一系列的變量,變量一般都是字元串,這個有點像C語言中的宏,當Makefile被執行時,其中的變量都會被擴充到相應的引用位置上。
檔案訓示。包括三個部分,(1)在一個Makefile中引用另一個Makefile,就像C語言中的#include一樣;(2)根據某些情況指定 Makefile中的有效部分,就像C語言中的條件編譯#if一樣;(3)定義一個多行的指令。有關這一部分的内容,我會在後續部分講述。
注釋。Makefile中隻有行注釋,和UNIX的Shell腳本一樣,其注釋是用“#”字元,這個就像C/C++中的“//”一樣。如果你要在Makefile中使用“#”字元,可以用反斜框進行轉義,如:“/#”。
最後,還值得一提的是,在Makefile中的指令,必須要以[Tab]鍵開始。
2.2 Makefile的檔案名
預設情況下,make指令會在目前目錄下按順序找尋檔案名為“GNUmakefile”、“makefile”、“Makefile”的檔案,找到後便解釋這個檔案。在這三個檔案名中,最好使用“Makefile”這個檔案名,因為,這個檔案名第一個字元為大寫,這樣有一種醒目的感覺。最好不要用“GNUmakefile”,這個檔案是GNU的make識别的。
有另外一些make隻對全小寫的“makefile”檔案名敏感,但是基本上來說,大多數的make都支援“makefile”和“Makefile”這兩種預設檔案名。
當然,你可以使用别的檔案名來書寫Makefile,比如:“Make.Linux”,“Make.Solaris”,“Make.AIX”等,如果要指定特定的Makefile,你可以使用make的“-f”和“--file”參數,如:make-f Make.Linux或make --file Make.AIX。
2.3 引用其它的Makefile
在Makefile中使用include關鍵字可以把别的Makefile包含進來,這很像C語言的#include,被包含的檔案會原模原樣的放在目前檔案的包含位置。include的文法是:
include<filename>
filename可以是目前作業系統Shell的檔案模式(可以包含路徑和通配符)。在include前面可以有一些空字元,但是絕不能是[Tab]鍵開始。Include多個Makefile時,可以用一個或多個空格隔開。舉個例子,你有這樣幾個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,并把其内容安置在目前的位置。如果檔案沒有指定絕對路徑或是相對路徑的話,make會首先在目前目錄下尋找,如果沒有找到,那麼還會在下面的幾個目錄下找:
如果make執行時,有“-I”或“--include-dir”參數,那麼make就會在這個參數所指定的目錄下去尋找。
如果目錄/include(一般是:/usr/local/bin或/usr/include)存在的話,make也會去找。
如果有檔案沒有找到的話,make會生成一條警告資訊,但不會馬上出現緻命錯誤。它會繼續載入其它的檔案,一旦完成makefile的讀取,make會再重試這些沒有找到,或是不能讀取的檔案,如果還是不行,make才會出現一條緻命資訊。如果你想讓make不理那些無法讀取的檔案,而繼續執行,你可以在include前加一個減号“-”。如:
-include<filename>
這表示:無論include過程中出現什麼錯誤,都不要報錯,繼續執行。和其它版本make相容的相關指令是sinclude,其作用和這一個是一樣的。
2.4 環境變量MAKEFILES
如果你的目前環境中定義了環境變量MAKEFILES,那麼,make會把這個變量中的值做一個類似于include的動作。這個變量中的值是其它的Makefile,用空格分隔。
隻是,它和include不同的是,從這個環境變中引入的Makefile的“目标”不會起作用,如果環境變量中定義的檔案發現錯誤,make也會不理。
但是在這裡我還是建議不要使用這個環境變量,因為隻要這個變量一被定義,那麼當你使用make時,所有的 Makefile都會受到它的影響,這絕不是你想看到的。在這裡提這個事,隻是為了告訴大家,也許有時候你的Makefile出現了怪事,那麼你可以看看目前環境中有沒有定義這個變量。
2.5 make的工作方式
GNU的make工作時的執行步驟入下:(想來其它的make也是類似)
讀入所有的Makefile。
讀入被include的其它Makefile。
初始化檔案中的變量。
推導隐晦規則,并分析所有規則。
為所有的目标檔案建立依賴關系鍊。
根據依賴關系,決定哪些目标要重新生成。
執行生成指令。
1-5步為第一個階段,6-7為第二個階段。第一個階段中,如果定義的變量被使用了,那麼,make會把其展開在使用的位置。但make并不會完全馬上展開,make使用的是拖延戰術,如果變量出現在依賴關系的規則中,那麼僅當這條依賴被決定要使用了,變量才會在其内部展開。
當然,這個工作方式你不一定要清楚,但是知道這個方式你也會對make更為熟悉。有了這個基礎,後續部分也就容易看懂了。
3 Makefile書寫規則
-----------------------------------------------------------------
規則包含兩個部分,一個是依賴關系,一個是生成目标的方法。
在 Makefile中,規則的順序是很重要的,因為,Makefile隻應有一個最終目标,其它的目标都是被這個目标所帶出來的,是以一定要讓 make知道你的最終目标是什麼。一般來說,定義在Makefile中的目标可能會有很多,但是第一條規則中的目标将被确立為最終的目标。如果第一條規則中的目标有很多個,那麼,第一個目标會成為最終的目标。make所完成的也就是這個目标。
好了,還是讓我們來看一看如何書寫規則。
3.1 規則舉例
foo.o: foo.c defs.h # foo子產品
cc -c -g foo.c
看到這個例子,各位應該不陌生了,前面已說過,foo.o是我們的目标,foo.c和defs.h是目标所依賴的源檔案,而隻有一個指令“cc -c -g foo.c”(以Tab鍵開頭)。這個規則告訴我們兩件事:
檔案的依賴關系,foo.o依賴于foo.c和defs.h的檔案,如果foo.c和defs.h的檔案日期要比foo.o檔案日期要新,或是foo.o不存在,那麼依賴關系發生。
如何生成(或更新)foo.o檔案。也就是那個cc指令。(當然foo.c檔案include了defs.h檔案)
3.2 規則的文法
targets : prerequisites
command
...
或是這樣:
targets : prerequisites ; command
command
...
targets是檔案名,以空格分開,可以使用通配符。一般來說,我們的目标基本上是一個檔案,但也有可能是多個檔案。
command是指令行,如果它不與“target:prerequisites”在一行,那麼,必須以[Tab鍵]開頭,如果和prerequisites在一行,那麼可以用分号做為分隔。(見上)
prerequisites也就是目标所依賴的檔案。如果其中的某個檔案要比目标檔案要新,那麼,目标就被認為是“過時的”,被認為是需要重生成的。
如果指令太長,你可以使用反斜框(‘/’)作為換行符。make對一行上有多少個字元沒有限制。規則告訴make兩件事,檔案的依賴關系和如何成成目标檔案。
一般來說,make會以UNIX的标準Shell,也就是/bin/sh來執行指令。
3.3 在規則中使用通配符
如果我們想定義一系列比較類似的檔案,我們很自然地就想起使用通配符。make支援三各通配符:“*”,“?”和“[...]”。這和Unix的B-Shell是相同的。
波浪号(“~”)在檔案名中也有比較特殊的用途。如果是“~/test”,表示目前使用者的$HOME目錄下的test目錄。而“~hchen /test”則表示使用者hchen的宿主目錄下的test目錄。(這些都是Unix下的小知識了,make也支援)而在Windows或是MS-DOS下,使用者沒有宿主目錄,那麼波浪号所指的目錄則根據環境變量“HOME”而定。
通配符代替了你一系列的檔案,如“*.c”表示是以字尾為c的檔案。需要注意的是,如果我們的檔案名中有通配符,如:“*”,那麼可以用轉義字元“/”,如“)/.o[ :]*,/1.o [email protected] : ,g'< [email protected]$$$$ > [email protected]; /
rm -f [email protected]$$$$
這個規則的意思是,所有的[.d]檔案依賴于[.c]檔案,“rm -f [email protected]”的意思是删除所有的目标,也就是[.d]檔案,第二行的意思是,為每個依賴檔案“$ <”,也就是[.c]檔案生成依賴檔案,“[email protected]”表示模式“%.d”檔案,如果有一個C檔案是name.c,那麼“%”就是 “name”,“$$$$”意為一個随機編号,第二行生成的檔案有可能是“name.d.12345”,第三行使用sed指令做了一個替換,關于sed指令的用法請參看相關的使用文檔。第四行就是删除臨時檔案。
總而言之,這個模式要做的事就是在編譯器生成的依賴關系中加入[.d]檔案的依賴,即把依賴關系:
main.o : main.c defs.h
轉成:
main.omain.d : main.c defs.h
于是,我們的[.d]檔案也會自動更新了,并會自動生成了,當然,你可以在這個[.d]檔案中加入的不隻是依賴關系,包括生成指令也可一并加入,讓每個 [.d]檔案都包含一個完整的規則。一旦我們完成這個工作,我們就要把這些自動生成的規則放進我們的主Makefile中。我們可以使用 Makefile的“include”指令,來引入别的Makefile檔案(前面講過),例如:
sources = foo.c bar.c
include $(sources:.c=.d)
上述語句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一個替換,把變量$(sources)所有[.c]的字串都替換成[.d],關于這個“替換”的内容,後面我會有詳細的講述。當然,你得注意次序,因為include是按序載入檔案,最先載入的[.d]檔案中的目标會成為預設目标
4 Makefile 書寫指令
-----------------------------------------------------------------
每條規則中的指令和作業系統Shell的指令行一緻。make按順序一條一條的執行指令,每條指令必須以[Tab]鍵開頭,除非指令是緊跟在依賴規則後面的分号後的。在指令行之間的空格或是空行會被忽略,但是如果該空格或空行是以Tab鍵開頭的,那麼make會認為其是一個空指令。
我們在UNIX下可能會使用不同的Shell,但是make的指令預設是被“/bin/sh”——UNIX的标準Shell解釋執行的。除非你特别指定一個其它的Shell。Makefile中,“#”是注釋符,很像C/C++中的“//”,其後的本行字元都被注釋。
4.1 顯示指令
通常,make會把要執行的指令行在指令執行前輸出到螢幕上。當我們把“@”字元放在指令行前,那麼該指令将不被顯示出來,如:
@echo 正在編譯XXX子產品......
當make執行時,會輸出“正在編譯XXX子產品......”字串,但不會輸出指令,如果沒有“@”,那麼,make将輸出:
echo 正在編譯XXX子產品......
正在編譯XXX子產品......
如果make執行時,帶入參數“-n”或“--just-print”,那麼隻顯示指令,不會執行指令,這個功能很有利于我們調試我們的Makefile,看看我們書寫的指令是執行起來是什麼樣子的或是什麼順序。
而參數“-s”或“--slient”(silent?)則是全面禁止指令的顯示。
4.2 指令執行
當依賴目标新于目标時,亦即最終目标需要被更新時,make會一條一條的執行指令。需要注意的是,當你要讓上一條指令的結果應用在下一條指令時,你應該使用分号分隔這兩條指令。比如你的第一條指令是cd指令,你希望第二條指令得在cd的基礎上運作,那麼你就不能把這兩條指令寫在兩行上,而應寫在一行上,用分号分隔。如:
示例一:
exec:
cd /home/hchen
pwd
示例二:
exec:
cd /home/hchen; pwd
當我們執行“make exec”時,第一個例子中的cd沒有作用,pwd會列印出目前的Makefile目錄,而第二個例子中,cd就起作用了,pwd會列印出“/home/hchen”。
make一般使用環境變量SHELL中定義的系統Shell來執行指令,預設情況下使用UNIX的标準Shell——/bin/sh來執行指令。但在MS-DOS下有點特殊,因為MS-DOS沒有SHELL環境變量,當然你也可以指定。如果你指定了UNIX風格的目錄形式,首先,make會在SHELL所指定的路徑中找尋指令解釋器,如果找不到,其會在目前盤符中的目前目錄中尋找,如果再找不到,其會在PATH環境變量中所定義的所有路徑中尋找。MS- DOS中,如果你定義的指令解釋器沒有找到,其會給你的指令解釋器加上諸如“.exe”、“.com”、“.bat”、“.sh”等字尾。
4.3 指令出錯
指令運作完後,make會檢測每個指令的傳回碼,如果傳回成功,那麼make會執行下一條指令,當規則中所有的指令成功傳回後,這個規則就算是成功完成了。如果一個規則中的某個指令出錯了(指令退出碼非零),那麼make就會終止執行目前規則,這将有可能終止所有規則的執行。
有時候,指令的出錯并不一定就表示錯誤。例如mkdir指令,我們一定需要建立一個目錄,如果目錄不存在,那麼mkdir就成功執行,萬事大吉,如果目錄存在,那麼就出錯了。我們之是以使用mkdir的意思就是一定要有這樣的一個目錄,于是我們就不希望mkdir出錯而終止規則的運作。
要做到忽略指令的報錯,我們可以在Makefile的指令行前加一個減号“-”(在Tab鍵之後),意為不管指令出錯與否都認為是成功的。如:
clean:
-rm -f *.o
另外有一個全局的辦法是,給make加上“-i”或是“--ignore-errors”參數,那麼,Makefile中所有指令都會忽略錯誤。而如果一個規則是以“.IGNORE”作為目标的,那麼這個規則中的所有指令将會忽略錯誤。這些是不同級别的防止指令出錯的方法,你可以根據你的喜好來設定。
還有一個要提一下的make的參數的是“-k”或是“--keep-going”,這個參數的意思是,如果某規則中的指令出錯了,那麼就終止該規則的執行,但繼續執行其它規則。
4.4 嵌套執行make
在一些大的工程中,我們會把不同子產品或是不同功能的源檔案放在不同目錄中,我們可以在每個目錄中都書寫一個該目錄的Makefile,這有利于讓我們的Makefile變得更加簡潔,而不必把所有的東西全部寫在一個Makefile中,這樣會很難維護我們的Makefile,這個技術對于我們子產品編譯和分段編譯有着非常大的好處。
例如,我們有一個子目錄叫subdir,這個目錄下有個Makefile檔案,來指明了這個目錄下檔案的編譯規則。那麼我們總控的Makefile可以這樣書寫:
subsystem:
cd subdir && $(MAKE)
它等價于:
subsystem:
$(MAKE) -C subdir
定義$(MAKE)宏變量的意思是,也許make需要一些參數,是以定義成一個變量比較利于維護。這兩個例子的意思都是先進入“subdir”目錄,然後執行make指令。
我們把這個Makefile叫做“總控Makefile”,總控Makefile的變量可以傳遞到下級的Makefile中(如果你顯示的聲明),但是不會覆寫下層的Makefile中所定義的變量,除非指定了“-e”參數。
如果要傳遞變量到下級Makefile中,可以這樣聲明: export <variable ...>
如不想某些變量傳遞到下級Makefile,則這樣聲明:unexport<variable ...>
示例一:
export variable = value
它等價于:
variable = value
export variable
也等價于:
export variable := value
還等價于:
variable := value
export variable
示例二:
export variable += value
等價于:
variable += value
export variable
如果要傳遞所有的變量,那麼隻要一個export就行了。後面什麼也不用跟,表示傳遞所有的變量。
需要注意的是,有兩個變量,一個是SHELL,一個是MAKEFLAGS,這兩個變量不管你是否export,總是要傳遞到下層Makefile中,特别是MAKEFILES變量,其中包含了make的參數資訊,如果我們執行“總控Makefile”時有make參數或是在上層Makefile中定義了這個變量,那麼MAKEFILES變量将會是這些參數,并會傳遞到下層Makefile中,這是一個系統級的環境變量。
但是make指令中有幾個參數并不往下傳遞,它們是“-C”、“-f”、“-h”、“-o”和“-W”(有關Makefile參數的細節将在後面說明),如果你不想往下層傳遞參數,你可以這樣:
subsystem:
cd subdir && $(MAKE)MAKEFLAGS=
如果你定義了環境變量MAKEFLAGS,那麼你得确信其中的選項是大家都會用到的,如果其中有“-t”,“-n”,和“-q”參數,那麼将會有讓你意想不到的結果,或許會讓你異常地恐慌。
還有一個在“嵌套執行”中比較有用的參數,“-w”或是“--print-directory”會在make的過程中輸出一些資訊,讓你看到目前的工作目錄。比如,如果我們的下級make目錄是“/home/hchen/gnu/make”,如果我們使用“make -w”來執行,那麼當進入該目錄時,我們會看到:
make:Entering directory `/home/hchen/gnu/make'.
而在完成下層make後離開目錄時,我們會看到:
make: Leaving directory `/home/hchen/gnu/make'
當你使用“-C”參數來指定make下層Makefile時,“-w”會被自動打開的。如果參數中有“-s”(“--slient”)或是“--no-print-directory”,那麼,“-w”總是失效的。
4.5 定義指令包
如果Makefile中出現一些相同指令序列,那麼我們可以為這些相同的指令序列定義一個變量。定義這種指令序列的文法以“define”開始,以“endef”結束,如:
define run-yacc
yacc $(firstword $^)
mv y.tab.c [email protected]
endef
這裡,“run-yacc”是這個指令包的名字,注意不要和Makefile中的變量重名。位于“define”和“endef”中間的兩行文本就是指令序列。這個指令包中的第一個指令是運作Yacc程式,因為Yacc程式總是生成“y.tab.c”的檔案,是以第二行的指令就是把這個檔案改改名字。還是把這個指令包放到一個示例中來看看吧。
foo.c : foo.y
$(run-yacc)
可見,使用指令包就像使用變量一樣。在“run-yacc”這個指令包中,“$^”就是“foo.y”,“[email protected]”就是“foo.c”(有關這種以“$”開頭的特殊變量,我們會在後面介紹),make在執行指令包時,指令包中的每個指令會被依次獨立執行。
文章出處(http://qiuye.javaeye.com/blog/451972)