JavaShuo
欄目
標籤
【轉載】跟我一塊兒寫 Makefile 2013-07-22 16:31 201人閱讀 評論(0) 收藏
時間 2019-11-12
標籤
轉載
一塊兒
makefile
閱讀
評論
收藏
欄目
C&C++
简体版
原文
原文鏈接
跟我一塊兒寫 Makefile
陳皓 (CSDN)
概述
——
什麼是makefile?或許不少Winodws 的程序員都不知道這個東西,由於那
些Windows 的IDE 都爲你作了這個工做,但我以爲要做一個好的和
professional 的程序員,makefile 仍是要懂。這就好像如今有這麼多的HTML
的編輯器,但若是你想成爲一個專業人士,你仍是要了解HTML 的標識的含
義。特別在Unix 下的軟件編譯,你就不能不本身寫makefile 了,會不會寫
makefile,從一個側面說明了一我的是否具有完成大型工程的能力。
由於,makefile 關係到了整個工程的編譯規則。一個工程中的源文件不計
數,其按類型、功能、模塊分別放在若干個目錄中,makefile 定義了一系
列的規則來指定,哪些文件須要先編譯,哪些文件須要後編譯,哪些文件需
要從新編譯,甚至於進行更復雜的功能操做,由於makefile 就像一個Shell
腳本同樣,其中也能夠執行操做系統的命令。
makefile 帶來的好處就是——「自動化編譯」,一旦寫好,只須要一個make
命令,整個工程徹底自動編譯,極大的提升了軟件開發的效率。make 是一
個命令工具,是一個解釋makefile 中指令的命令工具,通常來講,大多數
的IDE 都有這個命令,好比:Delphi 的make,Visual C++的nmake,Linux
下GNU 的make。可見,makefile 都成爲了一種在工程方面的編譯方法。
如今講述如何寫makefile 的文章比較少,這是我想寫這篇文章的緣由。當
然,不一樣產商的make 各不相同,也有不一樣的語法,但其本質都是在「文件
依賴性」上作文章,這裏,我僅對GNU 的make 進行講述,個人環境是RedHat
Linux 8.0,make 的版本是3.80。必竟,這個make 是應用最爲普遍的,也
是用得最多的。並且其仍是最遵循於IEEE 1003.2-1992 標準的(POSIX.2)。
在這篇文檔中,將以C/C++的源碼做爲咱們基礎,因此必然涉及一些關於
C/C++的編譯的知識,相關於這方面的內容,還請各位查看相關的編譯器的
文檔。這裏所默認的編譯器是UNIX 下的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 命令會自動智能地根據當前的文件修改的狀況來肯定哪些
文件須要重編譯,從而本身編譯所須要的文件和連接目標程序。
1、Makefile 的規則
在講述這個Makefile 以前,仍是讓咱們先來粗略地看一看Makefile 的規則。
target ... : prerequisites ...
command
...
...
target 也就是一個目標文件,能夠是Object File,也能夠是執行文件。還
能夠是一個標籤(Label),對於標籤這種特性,在後續的「僞目標」章節中
會有敘述。
prerequisites 就是,要生成那個target 所須要的文件或是目標。
command 也就是make 須要執行的命令。(任意的Shell 命令)
這是一個文件的依賴關係,也就是說,target 這一個或多個的目標文件依
賴於prerequisites 中的文件,其生成規則定義在command 中。說白一點就
是說,prerequisites 中若是有一個以上的文件比target 文件要新的話,
command 所定義的命令就會被執行。這就是Makefile 的規則。也就是
Makefile 中最核心的內容。
說到底,Makefile 的東西就是這樣一點,好像個人這篇文檔也該結束了。
呵呵。還不盡然,這是Makefile 的主線和核心,但要寫好一個Makefile 還
不夠,我會之後面一點一點地結合個人工做經驗給你慢慢到來。內容還多着
呢。:)
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.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 並無論命令是怎麼工做的,
他只管執行所定義的命令。make 會比較targets 文件和prerequisites 文
件的修改日期,若是prerequisites 文件的日期要比targets 文件的日期要
新,或者target 不存在的話,那麼,make 就會執行後續定義的命令。
這裏要說明一點的是,clean 不是一個文件,它只不過是一個動做名字,有
點像C 語言中的lable 同樣,其冒號後什麼也沒有,那麼,make 就不會自
動去找文件的依賴性,也就不會自動執行其後所定義的命令。要執行其後的
命令,就要在make 命令後明顯得指出這個lable 的名字。這樣的方法很是
有用,咱們能夠在一個makefile 中定義不用的編譯或是和編譯無關的命令,
好比程序的打包,程序的備份,等等。
3、make 是如何工做的
在默認的方式下,也就是咱們只輸入make 命令。那麼,
一、make 會在當前目錄下找名字叫「Makefile」或「makefile」的文件。
二、若是找到,它會找文件中的第一個目標文件(target),在上面的例子中,
他會找到「edit」這個文件,並把這個文件做爲最終的目標文件。
三、若是edit 文件不存在,或是edit 所依賴的後面的 .o 文件的文件修改
時間要比edit 這個文件新,那麼,他就會執行後面所定義的命令來生成edit
這個文件。
四、若是edit 所依賴的.o 文件也存在,那麼make 會在當前文件中找目標爲.o
文件的依賴性,若是找到則再根據那一個規則生成.o 文件。(這有點像一個
堆棧的過程)
五、固然,你的C 文件和H 文件是存在的啦,因而make 會生成 .o 文件,然
後再用 .o 文件生命make 的終極任務,也就是執行文件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 會被重連接。
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.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 變量就
能夠了。
關於變量更多的話題,我會在後續給你一一道來。
5、讓make 自動推導
GNU 的make 很強大,它能夠自動推導文件以及文件依賴關係後面的命令,
因而咱們就不必去在每個[.o]文件後都寫上相似的命令,由於,咱們的
make 會自動識別,並本身推導命令。
只要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 是個僞目標文件。
關於更爲詳細的「隱晦規則」和「僞目標文件」,我會在後續給你一一道來。
6、另類風格的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 文
件,那就理不清楚了。
7、清空目標文件的規則
每一個Makefile 中都應該寫一個清空目標文件(.o 和執行文件)的規則,這
不只便於重編譯,也很利於保持文件的清潔。這是一個「修養」(呵呵,還
記得個人《編程修養》嗎)。通常的風格都是:
clean:
rm edit $(objects)
更爲穩健的作法是:
.PHONY : clean
clean :
-rm edit $(objects)
前面說過,.PHONY 意思表示clean 是一個「僞目標」,。而在rm 命令前面加
了一個小減號的意思就是,也許某些文件出現問題,但不要管,繼續作後面
的事。固然,clean 的規則不要放在文件的開頭,否則,這就會變成make
的默認目標,相信誰也不肯意這樣。不成文的規矩是——「clean 歷來都是
放在文件的最後」。
上面就是一個makefile 的概貌,也是makefile 的基礎,下面還有不少
makefile 的相關細節,準備好了嗎?準備好了就來。
Makefile 總述
———————
1、Makefile 裏有什麼?
Makefile 裏主要包含了五個東西:顯式規則、隱晦規則、變量定義、文件
指示和註釋。
一、顯式規則。顯式規則說明了,如何生成一個或多的的目標文件。這是由
Makefile 的書寫者明顯指出,要生成的文件,文件的依賴文件,生成的命
令。
二、隱晦規則。因爲咱們的make 有自動推導的功能,因此隱晦的規則可讓
咱們比較粗糙地簡略地書寫Makefile,這是由make 所支持的。
三、變量的定義。在Makefile 中咱們要定義一系列的變量,變量通常都是字
符串,這個有點你C 語言中的宏,當Makefile 被執行時,其中的變量都會
被擴展到相應的引用位置上。
四、文件指示。其包括了三個部分,一個是在一個Makefile 中引用另外一個
Makefile,就像C 語言中的include 同樣;另外一個是指根據某些狀況指定
Makefile 中的有效部分,就像C 語言中的預編譯#if 同樣;還有就是定義一
個多行的命令。有關這一部分的內容,我會在後續的部分中講述。
五、註釋。Makefile 中只有行註釋,和UNIX 的Shell 腳本同樣,其註釋是
用「#」字符,這個就像C/C++中的「//」同樣。若是你要在你的Makefile
中使用「#」字符,能夠用反斜框進行轉義,如:「\#」。
最後,還值得一提的是,在Makefile 中的命令,必需要以[Tab]鍵開始。
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。
3、引用其它的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 還會在下面的幾個目錄下找:
一、若是make 執行時,有「-I」或「--include-dir」參數,那麼make 就會
在這個參數所指定的目錄下去尋找。
二、若是目錄<prefix>/include(通常是:/usr/local/bin 或/usr/include)
存在的話,make 也會去找。
若是有文件沒有找到的話,make 會生成一條警告信息,但不會立刻出現致
命錯誤。它會繼續載入其它的文件,一旦完成makefile 的讀取,make 會再
重試這些沒有找到,或是不能讀取的文件,若是仍是不行,make 纔會出現
一條致命信息。若是你想讓make 不理那些沒法讀取的文件,而繼續執行,
你能夠在include 前加一個減號「-」。如:
-include <filename>
其表示,不管include 過程當中出現什麼錯誤,都不要報錯繼續執行。和其它
版本make 兼容的相關命令是sinclude,其做用和這一個是同樣的。
4、環境變量 MAKEFILES
若是你的當前環境中定義了環境變量MAKEFILES,那麼,make 會把這個變量
中的值作一個相似於include 的動做。這個變量中的值是其它的Makefile,
用空格分隔。只是,它和include 不一樣的是,從這個環境變中引入的Makefile
的「目標」不會起做用,若是環境變量中定義的文件發現錯誤,make 也會
不理。
可是在這裏我仍是建議不要使用這個環境變量,由於只要這個變量一被定
義,那麼當你使用make 時,全部的Makefile 都會受到它的影響,這毫不是
你想看到的。在這裏提這個事,只是爲了告訴你們,也許有時候你的Makefile
出現了怪事,那麼你能夠看看當前環境中有沒有定義這個變量。
5、make 的工做方式
GNU 的make 工做時的執行步驟入下:(想來其它的make 也是相似)
一、讀入全部的Makefile。
二、讀入被include 的其它Makefile。
三、初始化文件中的變量。
四、推導隱晦規則,並分析全部規則。
五、爲全部的目標文件建立依賴關係鏈。
六、根據依賴關係,決定哪些目標要從新生成。
七、執行生成命令。
1-5 步爲第一個階段,6-7 爲第二個階段。第一個階段中,若是定義的變量
被使用了,那麼,make 會把其展開在使用的位置。但make 並不會徹底立刻
展開,make 使用的是拖延戰術,若是變量出如今依賴關係的規則中,那麼
僅當這條依賴被決定要使用了,變量纔會在其內部展開。
固然,這個工做方式你不必定要清楚,可是知道這個方式你也會對make 更
爲熟悉。有了這個基礎,後續部分也就容易看懂了。
書寫規則
————
規則包含兩個部分,一個是依賴關係,一個是生成目標的方法。
在Makefile 中,規則的順序是很重要的,由於,Makefile 中只應該有一個
最終目標,其它的目標都是被這個目標所連帶出來的,因此必定要讓make
知道你的最終目標是什麼。通常來講,定義在Makefile 中的目標可能會有
不少,可是第一條規則中的目標將被確立爲最終的目標。若是第一條規則中
的目標有不少個,那麼,第一個目標會成爲最終的目標。make 所完成的也
就是這個目標。
好了,仍是讓咱們來看一看如何書寫規則。
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.o 這個文件。(固然foo.c 文件include 了defs.h 文件)
2、規則的語法
targets : prerequisites
command
...
或是這樣:
targets : prerequisites ; command
command
...
targets 是文件名,以空格分開,能夠使用通配符。通常來講,咱們的目標
基本上是一個文件,但也有多是多個文件。
command 是命令行,若是其不與「targetrerequisites」在一行,那麼,必
須以[Tab 鍵]開頭,若是和prerequisites 在一行,那麼能夠用分號作爲分
隔。(見上)
prerequisites 也就是目標所依賴的文件(或依賴目標)。若是其中的某個
文件要比目標文件要新,那麼,目標就被認爲是「過期的」,被認爲是須要
重生成的。這個在前面已經講過了。
若是命令太長,你能夠使用反斜框(‘\’)做爲換行符。make 對一行上有多
少個字符沒有限制。規則告訴make 兩件事,文件的依賴關係和如何成成目
標文件。
通常來講,make 會以UNIX 的標準Shell,也就是/bin/sh 來執行命令。
3、在規則中使用通配符
若是咱們想定義一系列比較相似的文件,咱們很天然地就想起使用通配符。
make 支持三各通配符:「*」,「?」和「[...]」。這是和Unix 的B-Shell 是
相同的。
波浪號(「~」)字符在文件名中也有比較特殊的用途。若是是「~/test」,這
就表示當前用戶的$HOME 目錄下的test 目錄。而「~hchen/test」則表示用
戶hchen 的宿主目錄下的test 目錄。(這些都是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 的關鍵字,咱們將在
後面討論。
4、文件搜尋
在一些大的工程中,有大量的源文件,咱們一般的作法是把這許多的源文件
分類,並存放在不一樣的目錄中。因此,當make 須要去找尋文件的依賴關係
時,你能夠在文件前加上路徑,但最好的方法是把一個路徑告訴make,讓
make 在自動去找。
Makefile 文件中的特殊變量「VPATH」就是完成這個功能的,若是沒有指明
這個變量,make 只會在當前的目錄中去找尋依賴文件和目標文件。若是定
義了這個變量,那麼,make 就會在噹噹前目錄找不到的狀況下,到所指定
的目錄中去找尋文件了。
VPATH = src:../headers
上面的的定義指定兩個目錄,「src」和「../headers」,make 會按照這個順
序進行搜索。目錄由「冒號」分隔。(固然,當前目錄永遠是最高優先搜索
的地方)
另外一個設置文件搜索路徑的方法是使用make 的「vpath」關鍵字(注意,它
是全小寫的),這不是變量,這是一個make 的關鍵字,這和上面提到的那個
VPATH 變量很相似,可是它更爲靈活。它能夠指定不一樣的文件在不一樣的搜索
目錄中。這是一個很靈活的功能。它的使用方法有三種:
一、vpath <pattern> <directories>
爲符合模式<pattern>的文件指定搜索目錄<directories>。
二、vpath <pattern>
清除符合模式<pattern>的文件的搜索目錄。
三、vpath
清除全部已被設置好了的文件搜索目錄。
vapth 使用方法中的<pattern>須要包含「%」字符。「%」的意思是匹配零或
若干字符,例如,「%.h」表示全部以「.h」結尾的文件。<pattern>指定了
要搜索的文件集,而<directories>則指定了<pattern>的文件集的搜索的目
錄。例如:
vpath %.h ../headers
該語句表示,要求make 在「../headers」目錄下搜索全部以「.h」結尾的
文件。(若是某文件在當前目錄沒有找到的話)
咱們能夠連續地使用vpath 語句,以指定不一樣搜索策略。若是連續的vpath
語句中出現了相同的<pattern>,或是被重複了的<pattern>,那麼,make
會按照vpath 語句的前後順序來執行搜索。如:
vpath %.c foo
vpath % blish
vpath %.c bar
其表示「.c」結尾的文件,先在「foo」目錄,而後是「blish」,最後是「bar」
目錄。
vpath %.c foo:bar
vpath % blish
而上面的語句則表示「.c」結尾的文件,先在「foo」目錄,而後是「bar」
目錄,最後纔是「blish」目錄。
5、僞目標
最先先的一個例子中,咱們提到過一個「clean」的目標,這是一個「僞目
標」,
clean:
rm *.o temp
正像咱們前面例子中的「clean」同樣,即然咱們生成了許多文件編譯文件,
咱們也應該提供一個清除它們的「目標」以備完整地重編譯而用。 (以「make
clean」來使用該目標)
由於,咱們並不生成「clean」這個文件。「僞目標」並非一個文件,只是
一個標籤,因爲「僞目標」不是文件,因此make 沒法生成它的依賴關係和
決定它是否要執行。咱們只有經過顯示地指明這個「目標」才能讓其生效。
固然,「僞目標」的取名不能和文件名重名,否則其就失去了「僞目標」的
意義了。
固然,爲了不和文件重名的這種狀況,咱們能夠使用一個特殊的標記
「.PHONY」來顯示地指明一個目標是「僞目標」,向make 說明,無論是否有
這個文件,這個目標就是「僞目標」。
.PHONY : clean
只要有這個聲明,無論是否有「clean」文件,要運行「clean」這個目標,
只有「make clean」這樣。因而整個過程能夠這樣寫:
.PHONY: clean
clean:
rm *.o temp
僞目標通常沒有依賴的文件。可是,咱們也能夠爲僞目標指定所依賴的文件。
僞目標一樣能夠做爲「默認目標」,只要將其放在第一個。一個示例就是,
若是你的Makefile 須要一口氣生成若干個可執行文件,但你只想簡單地敲
一個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」的僞目標,其依賴於其它三個目標。因爲僞目標的特性是,老是
被執行的,因此其依賴的那三個目標就老是不如「all」這個目標新。因此,
其它三個目標的規則老是會被決議。也就達到了咱們一口氣生成多個目標的
目的。「.PHONY : all」聲明瞭「all」這個目標爲「僞目標」。
隨便提一句,從上面的例子咱們能夠看出,目標也能夠成爲依賴。因此,僞
目標一樣也可成爲依賴。看下面的例子:
.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
「make clean」將清除全部要被清除的文件。「cleanobj」和「cleandiff」
這兩個僞目標有點像「子程序」的意思。咱們能夠輸入「make cleanall」
和「make cleanobj」和「make cleandiff」命令來達到清除不一樣種類文件
的目的。
6、多目標
Makefile 的規則中的目標能夠不止一個,其支持多目標,有可能咱們的多
個目標同時依賴於一個文件,而且其生成的命令大致相似。因而咱們就能把
其合併起來。固然,多個目標的生成規則的執行命令是同一個,這可能會可
咱們帶來麻煩,不過好在咱們的能夠使用一個自動化變量「$@」(關於自動
化變量,將在後面講述),這個變量表示着目前規則中全部的目標的集合,
這樣說可能很抽象,仍是看一個例子吧。
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
上述規則等價於:
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
其中,-$(subst output,,$@)中的「$」表示執行一個Makefile 的函數,函
數名爲subst,後面的爲參數。關於函數,將在後面講述。這裏的這個函數
是截取字符串的意思,「$@」表示目標的集合,就像一個數組,「$@」依次取
出目標,並執於命令。
7、靜態模式
靜態模式能夠更加容易地定義多目標的規則,可讓咱們的規則變得更加的
有彈性和靈活。咱們仍是先來看一下語法:
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
...
targets 定義了一系列的目標文件,能夠有通配符。是目標的一個集合。
target-parrtern 是指明瞭targets 的模式,也就是的目標集模式。
prereq-parrterns 是目標的依賴模式,它對target-parrtern 造成的模式
再進行一次依賴目標的定義。
這樣描述這三個東西,可能仍是沒有說清楚,仍是舉個例子來講明一下吧。
若是咱們的<target-parrtern>定義成「%.o」,意思是咱們的<target>集合
中都是以「.o」結尾的,而若是咱們的<prereq-parrterns>定義成「%.c」,
意思是對<target-parrtern>所造成的目標集進行二次定義,其計算方法是,
取<target-parrtern>模式中的「%」(也就是去掉了[.o]這個結尾),併爲其
加上[.c]這個結尾,造成的新集合。
因此,咱們的「目標模式」或是「依賴模式」中都應該有「%」這個字符,
若是你的文件名中有「%」那麼你能夠使用反斜槓「\」進行轉義,來標明真
實的「%」字符。
看一個例子:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
上面的例子中,指明瞭咱們的目標從$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」)。因而,
上面的規則展開後等價於下面的規則:
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
試想,若是咱們的「%.o」有幾百個,那種咱們只要用這種很簡單的「靜態
模式規則」就能夠寫完一堆規則,實在是太有效率了。「靜態模式規則」的
用法很靈活,若是用得好,那會一個很強大的功能。再看一個例子:
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
$(filter %.o,$(files))表示調用Makefile 的filter 函數,過濾「$filter」
集,只要其中模式爲「%.o」的內容。其的它內容,我就不用多說了吧。這
個例字展現了Makefile 中更大的彈性。
8、自動生成依賴性
在Makefile 中,咱們的依賴關係可能會須要包含一系列的頭文件,好比,
若是咱們的main.c 中有一句「#include "defs.h"」,那麼咱們的依賴關係
應該是:
main.o : main.c defs.h
可是,若是是一個比較大型的工程,你必需清楚哪些C 文件包含了哪些頭文
件,而且,你在加入或刪除頭文件時,也須要當心地修改Makefile,這是
一個很沒有維護性的工做。爲了不這種繁重而又容易出錯的事情,咱們可
以使用C/C++編譯的一個功能。大多數的C/C++編譯器都支持一個「-M」的
選項,即自動找尋源文件中包含的頭文件,並生成一個依賴關係。例如,如
果咱們執行下面的命令:
cc -M main.c
其輸出是:
main.o : main.c defs.h
因而由編譯器自動生成的依賴關係,這樣一來,你就沒必要再手動書寫若干文
件的依賴關係,而由編譯器自動生成了。須要提醒一句的是,若是你使用
GNU 的C/C++編譯器,你得用「-MM」參數,否則,「-M」參數會把一些標準
庫的頭文件也包含進來。
gcc -M main.c 的輸出是:
main.o: main.c defs.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 聯繫在一塊兒呢。由於這樣
一來,咱們的Makefile 也要根據這些源文件從新生成,讓Makefile 自已依
賴於源文件?這個功能並不現實,不過咱們能夠有其它手段來迂迴地實現這
一功能。GNU 組織建議把編譯器爲每個源文件的自動生成的依賴關係放到
一個文件中,爲每個「name.c」的文件都生成一個「name.d」的Makefile
文件,[.d]文件中就存放對應[.c]文件的依賴關係。
因而,咱們能夠寫出[.c]文件和[.d]文件的依賴關係,並讓make 自動更新
或自成[.d]文件,並把其包含在咱們的主Makefile 中,這樣,咱們就能夠
自動化地生成每一個文件的依賴關係了。
這裏,咱們給出了一個模式規則來產生[.d]文件:
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< >
$@.$$$$
; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' <
$@.$$$$
> $@; \
rm -f
$@.$$$$
這個規則的意思是,全部的[.d]文件依賴於[.c]文件,「rm -f $@」的意思
是刪除全部的目標,也就是[.d]文件,第二行的意思是,爲每一個依賴文件
「$<」,也就是[.c]文件生成依賴文件,「$@」表示模式「%.d」文件,若是
有一個C 文件是name.c,那麼「%」就是「name」,「$$$$」意爲一個隨機編
號,第二行生成的文件有多是「name.d.12345」,第三行使用sed 命令作
了一個替換,關於sed 命令的用法請參看相關的使用文檔。第四行就是刪除
臨時文件。
總而言之,這個模式要作的事就是在編譯器生成的依賴關係中加入[.d]文件
的依賴,即把依賴關係:
main.o : main.c defs.h
轉成:
main.o main.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]文件中的目標會成爲默認目標。
書寫命令
————
每條規則中的命令和操做系統Shell 的命令行是一致的。make 會一按順序
一條一條的執行命令,每條命令的開頭必須以[Tab]鍵開頭,除非,命令是
緊跟在依賴規則後面的分號後的。在命令行之間中的空格或是空行會被忽
略,可是若是該空格或空行是以Tab 鍵開頭的,那麼make 會認爲其是一個
空命令。
咱們在UNIX 下可能會使用不一樣的Shell,可是make 的命令默認是被
「/bin/sh」——UNIX 的標準Shell 解釋執行的。除非你特別指定一個其它
的Shell。Makefile 中,「#」是註釋符,很像C/C++中的「//」,其後的本
行字符都被註釋。
1、顯示命令
一般,make 會把其要執行的命令行在命令執行前輸出到屏幕上。當咱們用
「@」字符在命令行前,那麼,這個命令將不被make 顯示出來,最具表明性
的例子是,咱們用這個功能來像屏幕顯示一些信息。如:
@echo 正在編譯XXX 模塊......
當make 執行時,會輸出「正在編譯XXX 模塊......」字串,但不會輸出命
令,若是沒有「@」,那麼,make 將輸出:
echo 正在編譯XXX 模塊......
正在編譯XXX 模塊......
若是make 執行時,帶入make 參數「-n」或「--just-print」,那麼其只是
顯示命令,但不會執行命令,這個功能頗有利於咱們調試咱們的Makefile,
看看咱們書寫的命令是執行起來是什麼樣子的或是什麼順序的。
而make 參數「-s」或「--slient」則是全面禁止命令的顯示。
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」等後綴。
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、嵌套執行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」老是失效的。
5、定義命令包
若是Makefile 中出現一些相同命令序列,那麼咱們能夠爲這些相同的命令
序列定義一個變量。定義這種命令序列的語法以「define」開始,以「endef」
結束,如:
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
這裏,「run-yacc」是這個命令包的名字,其不要和Makefile 中的變量重名。
在「define」和「endef」中的兩行就是命令序列。這個命令包中的第一個
命令是運行Yacc 程序,由於Yacc 程序老是生成「y.tab.c」的文件,因此
第二行的命令就是把這個文件改更名字。仍是把這個命令包放到一個示例中
來看看吧。
foo.c : foo.y
$(run-yacc)
咱們能夠看見,要使用這個命令包,咱們就好像使用變量同樣。在這個命令
包的使用中,命令包「run-yacc」中的「$^」就是「foo.y」,「$@」就是「foo.c」
(有關這種以「$」開頭的特殊變量,咱們會在後面介紹),make 在執行命
令包時,命令包中的每一個命令會被依次獨立執行。
使用變量
————
在Makefile 中的定義的變量,就像是C/C++語言中的宏同樣,他表明了一
個文本字串,在Makefile 中執行的時候其會自動原模原樣地展開在所使用
的地方。其與C/C++所不一樣的是,你能夠在Makefile 中改變其值。在Makefile
中,變量能夠使用在「目標」,「依賴目標」,「命令」或是Makefile 的其它
部分中。
變量的命名字能夠包含字符、數字,下劃線(能夠是數字開頭),但不該該
含有「:」、「#」、「=」或是空字符(空格、回車等)。變量是大小寫敏感的,
「foo」、「Foo」和「FOO」是三個不一樣的變量名。傳統的Makefile 的變量名
是全大寫的命名方式,但我推薦使用大小寫搭配的變量名,如:MakeFlags。
這樣能夠避免和系統的變量衝突,而發生意外的事情。
有一些變量是很奇怪字串,如「$<」、「$@」等,這些是自動化變量,我會在
後面介紹。
1、變量的基礎
變量在聲明時須要給予初值,而在使用時,須要給在變量名前加上「$」符
號,但最好用小括號「()」或是大括號「{}」把變量給包括起來。若是你要
使用真實的「$」字符,那麼你須要用「$$」來表示。
變量能夠使用在許多地方,如規則中的「目標」、「依賴」、「命令」以及新的
變量中。先看一個例子:
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
變量會在使用它的地方精確地展開,就像C/C++中的宏同樣,例如:
foo = c
prog.o : prog.$(foo)
$(foo)$(foo) -$(foo) prog.$(foo)
展開後獲得:
prog.o : prog.c
cc -c prog.c
固然,千萬不要在你的Makefile 中這樣幹,這裏只是舉個例子來代表
Makefile 中的變量在使用處展開的真實樣子。可見其就是一個「替代」的
原理。
另外,給變量加上括號徹底是爲了更加安全地使用這個變量,在上面的例子
中,若是你不想給變量加上括號,那也能夠,但我仍是強烈建議你給變量加
上括號。
2、變量中的變量
在定義變量的值時,咱們能夠使用其它變量來構造變量的值,在Makefile
中有兩種方式來在用變量定義變量的值。
先看第一種方式,也就是簡單的使用「=」號,在「=」左側是變量,右側是
變量的值,右側變量的值能夠定義在文件的任何一處,也就是說,右側中的
變量不必定非要是已定義好的值,其也能夠使用後面定義的值。如:
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
echo $(foo)
咱們執行「make all」將會打出變量$(foo)的值是「Huh?」( $(foo)的值是
$(bar),$(bar)的值是$(ugh),$(ugh)的值是「Huh?」)可見,變量是能夠
使用後面的變量來定義的。
這個功能有好的地方,也有很差的地方,好的地方是,咱們能夠把變量的真
實值推到後面來定義,如:
CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar
當「CFLAGS」在命令中被展開時,會是「-Ifoo -Ibar -O」。但這種形式也
有很差的地方,那就是遞歸定義,如:
CFLAGS = $(CFLAGS) -O
或:
A = $(B)
B = $(A)
這會讓make 陷入無限的變量展開過程當中去,固然,咱們的make 是有能力檢
測這樣的定義,並會報錯。還有就是若是在變量中使用函數,那麼,這種方
式會讓咱們的make 運行時很是慢,更糟糕的是,他會使用得兩個make 的函
數「wildcard」和「shell」發生不可預知的錯誤。由於你不會知道這兩個
函數會被調用多少次。
爲了不上面的這種方法,咱們能夠使用make 中的另外一種用變量來定義變
量的方法。這種方法使用的是「:=」操做符,如:
x := foo
y := $(x) bar
x := later
其等價於:
y := foo bar
x := later
值得一提的是,這種方法,前面的變量不能使用後面的變量,只能使用前面
已定義好了的變量。若是是這樣:
y := $(x) bar
x := foo
那麼,y 的值是「bar」,而不是「foo bar」。
上面都是一些比較簡單的變量使用了,讓咱們來看一個複雜的例子,其中包
括了make 的函數、條件表達式和一個系統變量「MAKELEVEL」的使用:
ifeq (0,${MAKELEVEL})
cur-dir := $(shell pwd)
whoami := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif
關於條件表達式和函數,咱們在後面再說,對於系統變量「MAKELEVEL」,其
意思是,若是咱們的make 有一個嵌套執行的動做(參見前面的「嵌套使用
make」),那麼,這個變量會記錄了咱們的當前Makefile 的調用層數。
下面再介紹兩個定義變量時咱們須要知道的,請先看一個例子,若是咱們要
定義一個變量,其值是一個空格,那麼咱們能夠這樣來:
nullstring :=
space := $(nullstring) # end of the line
nullstring 是一個Empty 變量,其中什麼也沒有,而咱們的space 的值是
一個空格。由於在操做符的右邊是很難描述一個空格的,這裏採用的技術很
管用,先用一個Empty 變量來標明變量的值開始了,然後面採用「#」註釋
符來表示變量定義的終止,這樣,咱們能夠定義出其值是一個空格的變量。
請注意這裏關於「#」的使用,註釋符「#」的這種特性值得咱們注意,若是
咱們這樣定義一個變量:
dir := /foo/bar # directory to put the frobs in
dir 這個變量的值是「/foo/bar」,後面還跟了4 個空格,若是咱們這樣使
用這樣變量來指定別的目錄——「$(dir)/file」那麼就完蛋了。
還有一個比較有用的操做符是「?=」,先看示例:
FOO ?= bar
其含義是,若是FOO 沒有被定義過,那麼變量FOO 的值就是「bar」,若是
FOO 先前被定義過,那麼這條語將什麼也不作,其等價於:
ifeq ($(origin FOO), undefined)
FOO = bar
endif
3、變量高級用法
這裏介紹兩種變量的高級使用方法,第一種是變量值的替換。
咱們能夠替換變量中的共有的部分, 其格式是「 $(var:a=b) 」 或是
「${var:a=b}」,其意思是,把變量「var」中全部以「a」字串「結尾」的
「a」替換成「b」字串。這裏的「結尾」意思是「空格」或是「結束符」。
仍是看一個示例吧:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
這個示例中,咱們先定義了一個「$(foo)」變量,而第二行的意思是把
「$(foo)」中全部以「.o」字串「結尾」所有替換成「.c」,因此咱們的「$(bar)」
的值就是「a.c b.c c.c」。
另一種變量替換的技術是以「靜態模式」(參見前面章節)定義的,如:
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
這依賴於被替換字串中的有相同的模式,模式中必須包含一個「%」字符,
這個例子一樣讓$(bar)變量的值爲「a.c b.c c.c」。
第二種高級用法是——「把變量的值再當成變量」。先看一個例子:
x = y
y = z
a := $($(x))
在這個例子中,$(x)的值是「y」,因此$($(x))就是$(y),因而$(a)的值就
是「z」。(注意,是「x=y」,而不是「x=$(y)」)
咱們還能夠使用更多的層次:
x = y
y = z
z = u
a := $($($(x)))
這裏的$(a)的值是「u」,相關的推導留給讀者本身去作吧。
讓咱們再複雜一點,使用上「在變量定義中使用變量」的第一個方式,來看
一個例子:
x = $(y)
y = z
z = Hello
a := $($(x))
這裏的$($(x))被替換成了$($(y)),由於$(y)值是「z」,因此,最終結果是:
a:=$(z),也就是「Hello」。
再複雜一點,咱們再加上函數:
x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))
這個例子中,「$($($(z)))」擴展爲「$($(y))」,而其再次被擴展爲「$($(subst
1,2,$(x)))」。$(x)的值是「variable1」,subst 函數把「variable1」中的
全部「1」字串替換成「2」字串,因而,「variable1」變成「variable2」,
再取其值,因此,最終,$(a)的值就是$(variable2)的值——「Hello」。(喔,
好不容易)
在這種方式中,或要能夠使用多個變量來組成一個變量的名字,而後再取其
值:
first_second = Hello
a = first
b = second
all = $($a_$b)
這裏的「$a_$b」組成了「first_second」,因而,$(all)的值就是「Hello」。
再來看看結合第一種技術的例子:
a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
sources := $($(a1)_objects:.o=.c)
這個例子中,若是$(a1)的值是「a」的話,那麼,$(sources)的值就是「a.c
b.c c.c」;若是$(a1)的值是「1」,那麼$(sources)的值是「1.c 2.c 3.c」。
再來看一個這種技術和「函數」與「條件語句」一同使用的例子:
ifdef do_sort
func := sort
else
func := strip
endif
bar := a d b g q c
foo := $($(func) $(bar))
這個示例中,若是定義了「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 函數。
固然,「把變量的值再當成變量」這種技術,一樣能夠用在操做符的左邊:
dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
lpr $($(dir)_sources)
endef
這個例子中定義了三個變量:「dir」,「foo_sources」和「foo_print」。
4、追加變量值
咱們能夠使用「+=」操做符給變量追加值,如:
objects = main.o foo.o bar.o utils.o
objects += another.o
因而,咱們的$(objects)值變成:「main.o foo.o bar.o utils.o another.o」
(another.o 被追加進去了)
使用「+=」操做符,能夠模擬爲下面的這種例子:
objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o
所不一樣的是,用「+=」更爲簡潔。
若是變量以前沒有定義過,那麼,「+=」會自動變成「=」,若是前面有變量
定義,那麼「+=」會繼承於前次操做的賦值符。若是前一次的是「:=」,那
麼「+=」會以「:=」做爲其賦值符,如:
variable := value
variable += more
等價於:
variable := value
variable := $(variable) more
但若是是這種狀況:
variable = value
variable += more
因爲前次的賦值符是「=」,因此「+=」也會以「=」來作爲賦值,那麼豈不
會發生變量的遞補歸定義,這是很很差的,因此make 會自動爲咱們解決這
個問題,咱們沒必要擔憂這個問題。
5、override 指示符
若是有變量是一般make 的命令行參數設置的,那麼Makefile 中對這個變量
的賦值會被忽略。若是你想在Makefile 中設置這類參數的值,那麼,你可
以使用「override」指示符。其語法是:
override <variable> = <value>
override <variable> := <value>
固然,你還能夠追加:
override <variable> += <more text>
對於多行的變量定義,咱們用define 指示符,在define 指示符前,也一樣
能夠使用ovveride 指示符,如:
override define foo
bar
endef
6、多行變量
還有一種設置變量值的方法是使用define 關鍵字。使用define 關鍵字設置
變量的值能夠有換行,這有利於定義一系列的命令(前面咱們講過「命令包」
的技術就是利用這個關鍵字)。
define 指示符後面跟的是變量的名字,而重起一行定義變量的值,定義是
以endef 關鍵字結束。其工做方式和「=」操做符同樣。變量的值能夠包含
函數、命令、文字,或是其它變量。由於命令須要以[Tab]鍵開頭,因此如
果你用define 定義的命令變量中沒有以[Tab]鍵開頭,那麼make 就不會把
其認爲是命令。
下面的這個示例展現了define 的用法:
define two-lines
echo foo
echo $(bar)
endef
7、環境變量
make 運行時的系統環境變量能夠在make 開始運行時被載入到Makefile 文
件中,可是若是Makefile 中已定義了這個變量,或是這個變量由make 命令
行帶入,那麼系統的環境變量的值將被覆蓋。(若是make 指定了「-e」參數,
那麼,系統環境變量將覆蓋Makefile 中定義的變量)
所以,若是咱們在環境變量中設置了「CFLAGS」環境變量,那麼咱們就能夠
在全部的Makefile 中使用這個變量了。這對於咱們使用統一的編譯參數有
比較大的好處。若是Makefile 中定義了CFLAGS,那麼則會使用Makefile
中的這個變量,若是沒有定義則使用系統環境變量的值,一個共性和個性的
統一,很像「全局變量」和「局部變量」的特性。
當make 嵌套調用時(參見前面的「嵌套調用」章節),上層Makefile 中定
義的變量會以系統環境變量的方式傳遞到下層的Makefile 中。固然,默認
狀況下,只有經過命令行設置的變量會被傳遞。而定義在文件中的變量,如
果要向下層Makefile 傳遞,則須要使用exprot 關鍵字來聲明。(參見前面
章節)
固然,我並不推薦把許多的變量都定義在系統環境中,這樣,在咱們執行不
用的Makefile 時,擁有的是同一套系統變量,這可能會帶來更多的麻煩。
8、目標變量
前面咱們所講的在Makefile 中定義的變量都是「全局變量」,在整個文件,
咱們均可以訪問這些變量。固然,「自動化變量」除外,如「$<」等這種類
量的自動化變量就屬於「規則型變量」,這種變量的值依賴於規則的目標和
依賴目標的定義。
固然, 我樣一樣能夠爲某個目標設置局部變量, 這種變量被稱爲
「Target-specific Variable」,它能夠和「全局變量」同名,由於它的做
用範圍只在這條規則以及連帶規則中,因此其值也只在做用範圍內有效。而
不會影響規則鏈之外的全局變量的值。
其語法是:
<target ...> : <variable-assignment>
<target ...> : overide <variable-assignment>
<variable-assignment>能夠是前面講過的各類賦值表達式,如「=」、「:=」、
「+=」或是「?=」。第二個語法是針對於make 命令行帶入的變量,或是系
統環境變量。
這個特性很是的有用,當咱們設置了這樣一個變量,這個變量會做用到由這
個目標所引起的全部的規則中去。如:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o
prog.o : prog.c
$(CC) $(CFLAGS) prog.c
foo.o : foo.c
$(CC) $(CFLAGS) foo.c
bar.o : bar.c
$(CC) $(CFLAGS) bar.c
在這個示例中,無論全局的$(CFLAGS)的值是什麼,在prog 目標,以及其所
引起的全部規則中(prog.o foo.o bar.o 的規則),$(CFLAGS)的值都是「-g」
9、模式變量
在GNU 的make 中,還支持模式變量(Pattern-specific Variable),經過
上面的目標變量中,咱們知道,變量能夠定義在某個目標上。模式變量的好
處就是,咱們能夠給定一種「模式」,能夠把變量定義在符合這種模式的所
有目標上。
咱們知道,make 的「模式」通常是至少含有一個「%」的,因此,咱們能夠
以以下方式給全部以[.o]結尾的目標定義目標變量:
%.o : CFLAGS = -O
一樣,模式變量的語法和「目標變量」同樣:
<pattern ...> : <variable-assignment>
<pattern ...> : override <variable-assignment>
override 一樣是針對於系統環境傳入的變量,或是make 命令行指定的變量。
使用條件判斷
——————
使用條件判斷,可讓make 根據運行時的不一樣狀況選擇不一樣的執行分支。
條件表達式能夠是比較變量的值,或是比較變量和常量的值。
1、示例
下面的例子,判斷$(CC)變量是否「gcc」,若是是的話,則使用GNU 函數編
譯目標。
libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
可見,在上面示例的這個規則中,目標「foo」能夠根據變量「$(CC)」值來
選取不一樣的函數庫來編譯程序。
咱們能夠從上面的示例中看到三個關鍵字:ifeq、else 和endif。ifeq 的
意思表示條件語句的開始,並指定一個條件表達式,表達式包含兩個參數,
以逗號分隔,表達式以圓括號括起。else 表示條件表達式爲假的狀況。endif
表示一個條件語句的結束,任何一個條件表達式都應該以endif 結束。
當咱們的變量$(CC)值是「gcc」時,目標foo 的規則是:
foo: $(objects)
$(CC) -o foo $(objects) $(libs_for_gcc)
而當咱們的變量$(CC)值不是「gcc」時(好比「cc」),目標foo 的規則是:
foo: $(objects)
$(CC) -o foo $(objects) $(normal_libs)
固然,咱們還能夠把上面的那個例子寫得更簡潔一些:
libs_for_gcc = -lgnu
normal_libs =
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
foo: $(objects)
$(CC) -o foo $(objects) $(libs)
2、語法
條件表達式的語法爲:
<conditional-directive>
<text-if-true>
endif
以及:
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif
其中<conditional-directive>表示條件關鍵字,如「ifeq」。這個關鍵字有
四個。
第一個是咱們前面所見過的「ifeq」
ifeq (<arg1>, <arg2> )
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"
比較參數「arg1」和「arg2」的值是否相同。固然,參數中咱們還能夠使用
make 的函數。如:
ifeq ($(strip $(foo)),)
<text-if-empty>
endif
這個示例中使用了「strip」函數,若是這個函數的返回值是空(Empty),
那麼<text-if-empty>就生效。
第二個條件關鍵字是「ifneq」。語法是:
ifneq (<arg1>, <arg2> )
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"
其比較參數「arg1」和「arg2」的值是否相同,若是不一樣,則爲真。和「ifeq」
相似。
第三個條件關鍵字是「ifdef」。語法是:
ifdef <variable-name>
若是變量<variable-name>的值非空,那到表達式爲真。不然,表達式爲假。
固然,<variable-name>一樣能夠是一個函數的返回值。注意,ifdef 只是
測試一個變量是否有值,其並不會把變量擴展到當前位置。仍是來看兩個例
子:
示例一:
bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif
示例二:
foo =
ifdef foo
frobozz = yes
else
frobozz = no
endif
第一個例子中,「$(frobozz)」值是「yes」,第二個則是「no」。
第四個條件關鍵字是「ifndef」。其語法是:
ifndef <variable-name>
這個我就很少說了,和「ifdef」是相反的意思。
在<conditional-directive>這一行上,多餘的空格是被容許的,可是不能
以[Tab]鍵作爲開始(否則就被認爲是命令)。而註釋符「#」一樣也是安全
的。「else」和「endif」也同樣,只要不是以[Tab]鍵開始就好了。
特別注意的是,make 是在讀取Makefile 時就計算條件表達式的值,並根據
條件表達式的值來選擇語句,因此,你最好不要把自動化變量(如「$@」等)
放入條件表達式中,由於自動化變量是在運行時纔有的。
並且,爲了不混亂,make 不容許把整個條件語句分紅兩部分放在不一樣的
文件中。
使用函數
————
在Makefile 中能夠使用函數來處理變量,從而讓咱們的命令或是規則更爲
的靈活和具備智能。make 所支持的函數也不算不少,不過已經足夠咱們的
操做了。函數調用後,函數的返回值能夠當作變量來使用。
1、函數的調用語法
函數調用,很像變量的使用,也是以「$」來標識的,其語法以下:
$(<function> <arguments> )
或是
${<function> <arguments>}
這裏,<function>就是函數名,make 支持的函數很少。<arguments>是函數
的參數,參數間以逗號「,」分隔,而函數名和參數之間以「空格」分隔。
函數調用以「$」開頭,以圓括號或花括號把函數名和參數括起。感受很像
一個變量,是否是?函數中的參數能夠使用變量,爲了風格的統一,函數和
變量的括號最好同樣,如使用「$(subst a,b,$(x))」這樣的形式,而不是
「$(subst a,b,${x})」的形式。由於統一會更清楚,也會減小一些沒必要要
的麻煩。
仍是來看一個示例:
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
在這個示例中,$(comma)的值是一個逗號。$(space)使用了$(empty)定義了
一個空格,$(foo)的值是「a b c」,$(bar)的定義用,調用了函數「subst」,
這是一個替換函數,這個函數有三個參數,第一個參數是被替換字串,第二
個參數是替換字串,第三個參數是替換操做做用的字串。這個函數也就是把
$(foo)中的空格替換成逗號,因此$(bar)的值是「a,b,c」。
2、字符串處理函數
$(subst <from>,<to>,<text> )
名稱:字符串替換函數——subst。
功能:把字串<text>中的<from>字符串替換成<to>。
返回:函數返回被替換事後的字符串。
示例:
$(subst ee,EE,feet on the street),
把「feet on the street」中的「ee」替換成「EE」,返回結果是「fEEt on
the strEEt」。
$(patsubst <pattern>,<replacement>,<text> )
名稱:模式字符串替換函數——patsubst。
功能:查找<text>中的單詞(單詞以「空格」、「Tab」或「回車」「換行」分
隔)是否符合模式<pattern>,若是匹配的話,則以<replacement>替換。這
裏, <pattern> 能夠包括通配符「 % 」, 表示任意長度的字串。若是
<replacement>中也包含「%」,那麼,<replacement>中的這個「%」將是
<pattern>中的那個「%」所表明的字串。(能夠用「\」來轉義,以「\%」來
表示真實含義的「%」字符)
返回:函數返回被替換事後的字符串。
示例:
$(patsubst %.c,%.o,x.c.c bar.c)
把字串「x.c.c bar.c」符合模式[%.c]的單詞替換成[%.o],返回結果是
「x.c.o bar.o」
備註:
這和咱們前面「變量章節」說過的相關知識有點類似。如:
「$(var:<pattern>=<replacement> )」
至關於
「$(patsubst <pattern>,<replacement>,$(var))」,
而「$(var: <suffix>=<replacement> )」
則至關於
「$(patsubst %<suffix>,%<replacement>,$(var))」。
例若有:objects = foo.o bar.o baz.o,
那麼,「$(objects:.o=.c)」和「$(patsubst %.o,%.c,$(objects))」是一
樣的。
$(strip <string> )
名稱:去空格函數——strip。
功能:去掉<string>字串中開頭和結尾的空字符。
返回:返回被去掉空格的字符串值。
示例:
$(strip a b c )
把字串「a b c 」去到開頭和結尾的空格,結果是「a b c」。
$(findstring <find>,<in> )
名稱:查找字符串函數——findstring。
功能:在字串<in>中查找<find>字串。
返回:若是找到,那麼返回<find>,不然返回空字符串。
示例:
$(findstring a,a b c)
$(findstring a,b c)
第一個函數返回「a」字符串,第二個返回「」字符串(空字符串)
$(filter <pattern...>,<text> )
名稱:過濾函數——filter。
功能: 以<pattern> 模式過濾<text>字符串中的單詞,保留符合模式
<pattern>的單詞。能夠有多個模式。
返回:返回符合模式<pattern>的字串。
示例:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
$(filter %.c %.s,$(sources))返回的值是「foo.c bar.c baz.s」。
$(filter-out <pattern...>,<text> )
名稱:反過濾函數——filter-out。
功能: 以<pattern> 模式過濾<text>字符串中的單詞,去除符合模式
<pattern>的單詞。能夠有多個模式。
返回:返回不符合模式<pattern>的字串。
示例:
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects)) 返回值是「foo.o bar.o」。
$(sort <list> )
名稱:排序函數——sort。
功能:給字符串<list>中的單詞排序(升序)。
返回:返回排序後的字符串。
示例:$(sort foo bar lose)返回「bar foo lose」 。
備註:sort 函數會去掉<list>中相同的單詞。
$(word <n>,<text> )
名稱:取單詞函數——word。
功能:取字符串<text>中第<n>個單詞。(從一開始)
返回:返回字符串<text>中第<n>個單詞。若是<n>比<text>中的單詞數要大,
那麼返回空字符串。
示例:$(word 2, foo bar baz)返回值是「bar」。
$(wordlist <s>,<e>,<text> )
名稱:取單詞串函數——wordlist。
功能:從字符串<text>中取從<s>開始到<e>的單詞串。<s>和<e>是一個數字。
返回:返回字符串<text>中從<s>到<e>的單詞字串。若是<s>比<text>中的
單詞數要大,那麼返回空字符串。若是<e>大於<text>的單詞數,那麼返回
從<s>開始,到<text>結束的單詞串。
示例: $(wordlist 2, 3, foo bar baz)返回值是「bar baz」。
$(words <text> )
名稱:單詞個數統計函數——words。
功能:統計<text>中字符串中的單詞個數。
返回:返回<text>中的單詞數。
示例:$(words, foo bar baz)返回值是「3」。
備註:若是咱們要取<text>中最後的一個單詞,咱們能夠這樣:$(word
$(words <text> ),<text> )。
$(firstword <text> )
名稱:首單詞函數——firstword。
功能:取字符串<text>中的第一個單詞。
返回:返回字符串<text>的第一個單詞。
示例:$(firstword foo bar)返回值是「foo」。
備註:這個函數能夠用word 函數來實現:$(word 1,<text> )。
以上,是全部的字符串操做函數,若是搭配混合使用,能夠完成比較複雜的
功能。這裏,舉一個現實中應用的例子。咱們知道,make 使用「VPATH」變
量來指定「依賴文件」的搜索路徑。因而,咱們能夠利用這個搜索路徑來指
定編譯器對頭文件的搜索路徑參數CFLAGS,如:
override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))
若是咱們的「 $(VPATH) 」 值是「 src:../headers 」, 那麼
「 $(patsubst %,-I%,$(subst :, ,$(VPATH))) 」 將返回「 -Isrc
-I../headers」,這正是cc 或gcc 搜索頭文件路徑的參數。
3、文件名操做函數
下面咱們要介紹的函數主要是處理文件名的。每一個函數的參數字符串都會被
當作一個或是一系列的文件名來對待。
$(dir <names...> )
名稱:取目錄函數——dir。
功能:從文件名序列<names>中取出目錄部分。目錄部分是指最後一個反斜
槓(「/」)以前的部分。若是沒有反斜槓,那麼返回「./」。
返回:返回文件名序列<names>的目錄部分。
示例: $(dir src/foo.c hacks)返回值是「src/ ./」。
$(notdir <names...> )
名稱:取文件函數——notdir。
功能:從文件名序列<names>中取出非目錄部分。非目錄部分是指最後一個
反斜槓(「/」)以後的部分。
返回:返回文件名序列<names>的非目錄部分。
示例: $(notdir src/foo.c hacks)返回值是「foo.c hacks」。
$(suffix <names...> )
名稱:取後綴函數——suffix。
功能:從文件名序列<names>中取出各個文件名的後綴。
返回:返回文件名序列<names>的後綴序列,若是文件沒有後綴,則返回空
字串。
示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是「.c .c」。
$(basename <names...> )
名稱:取前綴函數——basename。
功能:從文件名序列<names>中取出各個文件名的前綴部分。
返回:返回文件名序列<names>的前綴序列,若是文件沒有前綴,則返回空
字串。
示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是「src/foo
src-1.0/bar hacks」。
$(addsuffix <suffix>,<names...> )
名稱:加後綴函數——addsuffix。
功能:把後綴<suffix>加到<names>中的每一個單詞後面。
返回:返回加事後綴的文件名序列。
示例:$(addsuffix .c,foo bar)返回值是「foo.c bar.c」。
$(addprefix <prefix>,<names...> )
名稱:加前綴函數——addprefix。
功能:把前綴<prefix>加到<names>中的每一個單詞後面。
返回:返回加過前綴的文件名序列。
示例:$(addprefix src/,foo bar)返回值是「src/foo src/bar」。
$(join <list1>,<list2> )
名稱:鏈接函數——join。
功能:把<list2>中的單詞對應地加到<list1>的單詞後面。若是<list1>的
單詞個數要比<list2>的多,那麼,<list1>中的多出來的單詞將保持原樣。
若是<list2>的單詞個數要比<list1>多,那麼,<list2>多出來的單詞將被
複製到<list2>中。
返回:返回鏈接事後的字符串。
示例:$(join aaa bbb , 111 222 333)返回值是「aaa111 bbb222 333」。
4、foreach 函數
foreach 函數和別的函數很是的不同。由於這個函數是用來作循環用的,
Makefile 中的foreach 函數幾乎是仿照於Unix 標準Shell(/bin/sh)中的
for 語句,或是C-Shell(/bin/csh)中的foreach 語句而構建的。它的語
法是:
$(foreach <var>,<list>,<text> )
這個函數的意思是,把參數<list>中的單詞逐一取出放到參數<var>所指定
的變量中,而後再執行<text>所包含的表達式。每一次<text>會返回一個字
符串,循環過程當中,<text>的所返回的每一個字符串會以空格分隔,最後當整
個循環結束時,<text>所返回的每一個字符串所組成的整個字符串(以空格分
隔)將會是foreach 函數的返回值。
因此,<var>最好是一個變量名,<list>能夠是一個表達式,而<text>中一
般會使用<var>這個參數來依次枚舉<list>中的單詞。舉個例子:
names := a b c d
files := $(foreach n,$(names),$(n).o)
上面的例子中,$(name)中的單詞會被挨個取出,並存到變量「n」中,「$(n).o」
每次根據「$(n)」計算出一個值,這些值以空格分隔,最後做爲foreach 函
數的返回,因此,$(files)的值是「a.o b.o c.o d.o」。
注意,foreach 中的<var>參數是一個臨時的局部變量,foreach 函數執行完
後,參數<var>的變量將不在做用,其做用域只在foreach 函數當中。
5、if 函數
if 函數很像GNU 的make 所支持的條件語句——ifeq(參見前面所述的章節),
if 函數的語法是:
$(if <condition>,<then-part> )
或是
$(if <condition>,<then-part>,<else-part> )
可見,if 函數能夠包含「else」部分,或是不含。即if 函數的參數能夠是
兩個,也能夠是三個。<condition>參數是if 的表達式,若是其返回的爲非
空字符串,那麼這個表達式就至關於返回真,因而,<then-part>會被計算,
不然<else-part>會被計算。
而if 函數的返回值是,若是<condition>爲真(非空字符串),那個
<then-part>會是整個函數的返回值,若是<condition>爲假(空字符串),
那麼<else-part>會是整個函數的返回值,此時若是<else-part>沒有被定
義,那麼,整個函數返回空字串。
因此,<then-part>和<else-part>只會有一個被計算。
6、call 函數
call 函數是惟一一個能夠用來建立新的參數化的函數。你能夠寫一個很是
複雜的表達式,這個表達式中,你能夠定義許多參數,而後你能夠用call
函數來向這個表達式傳遞參數。其語法是:
$(call <expr
ession>,<parm1>,<parm2>,<parm3>...)
當make 執行這個函數時,<expr
ession>參數中的變量,如$(1),$(2),$(3)
等,會被參數<parm1>,<parm2>,<parm3>依次取代。而<expr
ession>的返
回值就是call 函數的返回值。例如:
reverse = $(1) $(2)
foo = $(call reverse,a,b)
那麼,foo 的值就是「a b」。固然,參數的次序是能夠自定義的,不必定是
順序的,如:
reverse = $(2) $(1)
foo = $(call reverse,a,b)
此時的foo 的值就是「b a」。
7、origin 函數
origin 函數不像其它的函數,他並不操做變量的值,他只是告訴你你的這
個變量是哪裏來的?其語法是:
$(origin <variable> )
注意,<variable>是變量的名字,不該該是引用。因此你最好不要在
<variable>中使用「$」字符。Origin 函數會以其返回值來告訴你這個變量
的「出生狀況」,下面,是origin 函數的返回值:
「undefined」
若是<variable>歷來沒有定義過,origin 函數返回這個值「undefined」。
「default」
若是<variable>是一個默認的定義,好比「CC」這個變量,這種變量咱們將
在後面講述。
「environment」
若是<variable>是一個環境變量,而且當Makefile 被執行時,「-e」參數沒
有被打開。
「file」
若是<variable>這個變量被定義在Makefile 中。
「command line」
若是<variable>這個變量是被命令行定義的。
「override」
若是<variable>是被override 指示符從新定義的。
「automatic」
若是<variable>是一個命令運行中的自動化變量。關於自動化變量將在後面
講述。
這些信息對於咱們編寫Makefile 是很是有用的,例如,假設咱們有一個
Makefile 其包了一個定義文件Make.def,在Make.def 中定義了一個變量
「bletch」,而咱們的環境中也有一個環境變量「bletch」,此時,咱們想判
斷一下,若是變量來源於環境,那麼咱們就把之重定義了,若是來源於
Make.def 或是命令行等非環境的,那麼咱們就不從新定義它。因而,在我
們的Makefile 中,咱們能夠這樣寫:
ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf, gag, etc.
endif
endif
固然,你也許會說,使用override 關鍵字不就能夠從新定義環境中的變量
了嗎?爲何須要使用這樣的步驟?是的,咱們用override 是能夠達到這
樣的效果,但是override 過於粗暴,它同時會把從命令行定義的變量也覆
蓋了,而咱們只想從新定義環境傳來的,而不想從新定義命令行傳來的。
8、shell 函數
shell 函數也不像其它的函數。顧名思義,它的參數應該就是操做系統Shell
的命令。它和反引號「`」是相同的功能。這就是說,shell 函數把執行操
做系統命令後的輸出做爲函數返回。因而,咱們能夠用操做系統命令以及字
符串處理命令awk,sed 等等命令來生成一個變量,如:
contents := $(shell cat foo)
files := $(shell echo *.c)
注意,這個函數會新生成一個Shell 程序來執行命令,因此你要注意其運行
性能,若是你的Makefile 中有一些比較複雜的規則,並大量使用了這個函
數,那麼對於你的系統性能是有害的。特別是Makefile 的隱晦的規則可能
會讓你的shell 函數執行的次數比你想像的多得多。
9、控制make 的函數
make 提供了一些函數來控制make 的運行。一般,你須要檢測一些運行
Makefile 時的運行時信息,而且根據這些信息來決定,你是讓make 繼續執
行,仍是中止。
$(error <text ...> )
產生一個致命的錯誤,<text ...>是錯誤信息。注意,error 函數不會在一
被使用就會產生錯誤信息,因此若是你把其定義在某個變量中,並在後續的
腳本中使用這個變量,那麼也是能夠的。例如:
示例一:
ifdef ERROR_001
$(error error is $(ERROR_001))
endif
示例二:
ERR = $(error found an error!)
.PHONY: err
err: ; $(ERR)
示例一會在變量ERROR_001 定義了後執行時產生error 調用,而示例二則在
目錄err 被執行時才發生error 調用。
$(warning <text ...> )
這個函數很像error 函數,只是它並不會讓make 退出,只是輸出一段警告
信息,而make 繼續執行。
make 的運行
——————
通常來講,最簡單的就是直接在命令行下輸入make 命令,make 命令會找當
前目錄的makefile 來執行,一切都是自動的。但也有時你也許只想讓make
重編譯某些文件,而不是整個工程,而又有的時候你有幾套編譯規則,你想
在不一樣的時候使用不一樣的編譯規則,等等。本章節就是講述如何使用make
命令的。
1、make 的退出碼
make 命令執行後有三個退出碼:
0 —— 表示成功執行。
1 —— 若是make 運行時出現任何錯誤,其返回1。
2 —— 若是你使用了make 的「-q」選項,而且make 使得一些目標不須要
更新,那麼返回2。
Make 的相關參數咱們會在後續章節中講述。
2、指定Makefile
前面咱們說過,GNU make 找尋默認的Makefile 的規則是在當前目錄下依次
找三個文件——「GNUmakefile」、「makefile」和「Makefile」。其按順序找
這三個文件,一旦找到,就開始讀取這個文件並執行。
當前,咱們也能夠給make 命令指定一個特殊名字的Makefile。要達到這個
功能,咱們要使用make 的「-f」或是「--file」參數(「--makefile」參數
也行)。例如,咱們有個makefile 的名字是「hchen.mk」,那麼,咱們能夠
這樣來讓make 來執行這個文件:
make –f hchen.mk
若是在make 的命令行是,你不僅一次地使用了「-f」參數,那麼,全部指
定的makefile 將會被連在一塊兒傳遞給make 執行。
3、指定目標
通常來講,make 的最終目標是makefile 中的第一個目標,而其它目標通常
是由這個目標連帶出來的。這是make 的默認行爲。固然,通常來講,你的
makefile 中的第一個目標是由許多個目標組成,你能夠指示make,讓其完
成你所指定的目標。要達到這一目的很簡單,需在make 命令後直接跟目標
的名字就能夠完成(如前面提到的「make clean」形式)
任何在makefile 中的目標均可以被指定成終極目標,可是除了以「-」打頭,
或是包含了「=」的目標,由於有這些字符的目標,會被解析成命令行參數
或是變量。甚至沒有被咱們明確寫出來的目標也能夠成爲make 的終極目標,
也就是說,只要make 能夠找到其隱含規則推導規則,那麼這個隱含目標同
樣能夠被指定成終極目標。
有一個make 的環境變量叫「MAKECMDGOALS」,這個變量中會存放你所指定的
終極目標的列表,若是在命令行上,你沒有指定目標,那麼,這個變量是空
值。這個變量可讓你使用在一些比較特殊的情形下。好比下面的例子:
sources = foo.c bar.c
ifneq ( $(MAKECMDGOALS),clean)
include $(sources:.c=.d)
endif
基於上面的這個例子,只要咱們輸入的命令不是「make clean」,那麼
makefile 會自動包含「foo.d」和「bar.d」這兩個makefile。
使用指定終極目標的方法能夠很方便地讓咱們編譯咱們的程序,例以下面這
個例子:
.PHONY: all
all: prog1 prog2 prog3 prog4
從這個例子中,咱們能夠看到,這個makefile 中有四個須要編譯的程序—
—「prog1」, 「prog2」, 「prog3」和 「prog4」,咱們能夠使用「make all」
命令來編譯全部的目標(若是把all 置成第一個目標,那麼只需執行
「make」),咱們也能夠使用「make prog2」來單獨編譯目標「prog2」。
即然make 能夠指定全部makefile 中的目標,那麼也包括「僞目標」,因而
咱們能夠根據這種性質來讓咱們的makefile 根據指定的不一樣的目標來完成
不一樣的事。在Unix 世界中,軟件發佈時,特別是GNU 這種開源軟件的發佈
時,其makefile 都包含了編譯、安裝、打包等功能。咱們能夠參照這種規
則來書寫咱們的makefile 中的目標。
「all」
這個僞目標是全部目標的目標,其功能通常是編譯全部的目標。
「clean」
這個僞目標功能是刪除全部被make 建立的文件。
「install」
這個僞目標功能是安裝已編譯好的程序,其實就是把目標執行文件拷貝到指
定的目標中去。
「print」
這個僞目標的功能是例出改變過的源文件。
「tar」
這個僞目標功能是把源程序打包備份。也就是一個tar 文件。
「dist」
這個僞目標功能是建立一個壓縮文件,通常是把tar 文件壓成Z 文件。或是
gz 文件。
「TAGS」
這個僞目標功能是更新全部的目標,以備完整地重編譯使用。
「check」和「test」
這兩個僞目標通常用來測試makefile 的流程。
固然一個項目的makefile 中也不必定要書寫這樣的目標,這些東西都是GNU
的東西,可是我想,GNU 搞出這些東西必定有其可取之處(等你的UNIX 下
的程序文件一多時你就會發現這些功能頗有用了),這裏只不過是說明了,
若是你要書寫這種功能,最好使用這種名字命名你的目標,這樣規範一些,
規範的好處就是——不用解釋,你們都明白。並且若是你的makefile 中有
這些功能,一是很實用,二是能夠顯得你的makefile 很專業(不是那種初
學者的做品)。
4、檢查規則
有時候,咱們不想讓咱們的makefile 中的規則執行起來,咱們只想檢查一
下咱們的命令,或是執行的序列。因而咱們能夠使用make 命令的下述參數:
「-n」
「--just-print」
「--dry-run」
「--recon」
不執行參數,這些參數只是打印命令,無論目標是否更新,把規則和連帶規
則下的命令打印出來,但不執行,這些參數對於咱們調試makefile 頗有用
處。
「-t」
「--touch」
這個參數的意思就是把目標文件的時間更新,但不更改目標文件。也就是說,
make 僞裝編譯目標,但不是真正的編譯目標,只是把目標變成已編譯過的
狀態。
「-q」
「--question」
這個參數的行爲是找目標的意思,也就是說,若是目標存在,那麼其什麼也
不會輸出,固然也不會執行編譯,若是目標不存在,其會打印出一條出錯信
息。
「-W <file>」
「--what-if=<file>」
「--assume-new=<file>」
「--new-file=<file>」
這個參數須要指定一個文件。通常是是源文件(或依賴文件),Make 會根據
規則推導來運行依賴於這個文件的命令,通常來講,能夠和「-n」參數一同
使用,來查看這個依賴文件所發生的規則命令。
另一個頗有意思的用法是結合「-p」和「-v」來輸出makefile 被執行時
的信息(這個將在後面講述)。
5、make 的參數
下面列舉了全部GNU make 3.80 版的參數定義。其它版本和產商的make 大
同小異,不過其它產商的make 的具體參數仍是請參考各自的產品文檔。
「-b」
「-m」
這兩個參數的做用是忽略和其它版本make 的兼容性。
「-B」
「--always-make」
認爲全部的目標都須要更新(重編譯)。
「-C <dir>」
「--directory=<dir>」
指定讀取makefile 的目錄。若是有多個「-C」參數,make 的解釋是後面的
路徑之前面的做爲相對路徑,並以最後的目錄做爲被指定目錄。如:「make –
C ~hchen/test –C prog」等價於「make –C ~hchen/test/prog」。
「—debug[=<options>]」
輸出make 的調試信息。它有幾種不一樣的級別可供選擇,若是沒有參數,那
就是輸出最簡單的調試信息。下面是<options>的取值:
a —— 也就是all,輸出全部的調試信息。(會很是的多)
b —— 也就是basic,只輸出簡單的調試信息。即輸出不須要重編譯的目
標。
v —— 也就是verbose,在b 選項的級別之上。輸出的信息包括哪一個
makefile 被解析,不須要被重編譯的依賴文件(或是依賴目標)等。
i —— 也就是implicit,輸出因此的隱含規則。
j —— 也就是jobs,輸出執行規則中命令的詳細信息,如命令的PID、返
回碼等。
m —— 也就是makefile,輸出make 讀取makefile,更新makefile,執行
makefile 的信息。
「-d」
至關於「--debug=a」。
「-e」
「--environment-overrides」
指明環境變量的值覆蓋makefile 中定義的變量的值。
「-f=<file>」
「--file=<file>」
「--makefile=<file>」
指定須要執行的makefile。
「-h」
「--help」
顯示幫助信息。
「-i」
「--ignore-errors」
在執行時忽略全部的錯誤。
「-I <dir>」
「--include-dir=<dir>」
指定一個被包含makefile 的搜索目標。能夠使用多個「-I」參數來指定多
個目錄。
「-j [<jobsnum>]」
「--jobs[=<jobsnum>]」
指同時運行命令的個數。若是沒有這個參數,make 運行命令時能運行多少
就運行多少。若是有一個以上的「-j」參數,那麼僅最後一個「-j」纔是有
效的。(注意這個參數在MS-DOS 中是無用的)
「-k」
「--keep-going」
出錯也不中止運行。若是生成一個目標失敗了,那麼依賴於其上的目標就不
會被執行了。
「-l <load>」
「--load-average[=<load]」
「—max-load[=<load>]」
指定make 運行命令的負載。
「-n」
「--just-print」
「--dry-run」
「--recon」
僅輸出執行過程當中的命令序列,但並不執行。
「-o <file>」
「--old-file=<file>」
「--assume-old=<file>」
不從新生成的指定的<file>,即便這個目標的依賴文件新於它。
「-p」
「--print-da
ta-base」
輸出makefile 中的全部數據,包括全部的規則和變量。這個參數會讓一個
簡單的makefile 都會輸出一堆信息。若是你只是想輸出信息而不想執行
makefile,你能夠使用「make -qp」命令。若是你想查看執行makefile 前
的預設變量和規則,你能夠使用「make –p –f /dev/null」。這個參數輸
出的信息會包含着你的makefile 文件的文件名和行號,因此,用這個參數
來調試你的makefile 會是頗有用的,特別是當你的環境變量很複雜的時候。
「-q」
「--question」
不運行命令,也不輸出。僅僅是檢查所指定的目標是否須要更新。若是是0
則說明要更新,若是是2 則說明有錯誤發生。
「-r」
「--no-builtin-rules」
禁止make 使用任何隱含規則。
「-R」
「--no-builtin-variabes」
禁止make 使用任何做用於變量上的隱含規則。
「-s」
「--silent」
「--quiet」
在命令運行時不輸出命令的輸出。
「-S」
「--no-keep-going」
「--stop」
取消「-k」選項的做用。由於有些時候,make 的選項是從環境變量「MAKEFLAGS」
中繼承下來的。因此你能夠在命令行中使用這個參數來讓環境變量中的「-k」
選項失效。
「-t」
「--touch」
至關於UNIX 的touch 命令,只是把目標的修改日期變成最新的,也就是阻
止生成目標的命令運行。
「-v」
「--version」
輸出make 程序的版本、版權等關於make 的信息。
「-w」
「--print-directory」
輸出運行makefile 以前和以後的信息。這個參數對於跟蹤嵌套式調用make
時頗有用。
「--no-print-directory」
禁止「-w」選項。
「-W <file>」
「--what-if=<file>」
「--new-file=<file>」
「--assume-file=<file>」
假定目標<file>須要更新,若是和「-n」選項使用,那麼這個參數會輸出該
目標更新時的運行動做。若是沒有「-n」那麼就像運行UNIX 的「touch」命
令同樣,使得<file>的修改時間爲當前時間。
「--warn-undefined-variables」
只要make 發現有未定義的變量,那麼就輸出警告信息。
隱含規則
————
在咱們使用Makefile 時,有一些咱們會常用,並且使用頻率很是高的
東西,好比,咱們編譯C/C++的源程序爲中間目標文件(Unix 下是[.o]文件,
Windows 下是[.obj]文件)。本章講述的就是一些在Makefile 中的「隱含的」,
早先約定了的,不須要咱們再寫出來的規則。
「隱含規則」也就是一種慣例,make 會按照這種「慣例」心照不喧地來運
行,那怕咱們的Makefile 中沒有書寫這樣的規則。例如,把[.c]文件編譯
成[.o]文件這一規則,你根本就不用寫出來,make 會自動推導出這種規則,
並生成咱們須要的[.o]文件。
「隱含規則」會使用一些咱們系統變量,咱們能夠改變這些系統變量的值來
定製隱含規則的運行時的參數。如系統變量「CFLAGS」能夠控制編譯時的編
譯器參數。
咱們還能夠經過「模式規則」的方式寫下本身的隱含規則。用「後綴規則」
來定義隱含規則會有許多的限制。使用「模式規則」會更回得智能和清楚,
但「後綴規則」能夠用來保證咱們Makefile 的兼容性。
咱們瞭解了「隱含規則」,可讓其爲咱們更好的服務,也會讓咱們知道一
些「約定俗成」了的東西,而不至於使得咱們在運行Makefile 時出現一些
咱們以爲莫名其妙的東西。固然,任何事物都是矛盾的,水能載舟,亦可覆
舟,因此,有時候「隱含規則」也會給咱們形成不小的麻煩。只有瞭解了它,
咱們才能更好地使用它。
1、使用隱含規則
若是要使用隱含規則生成你須要的目標,你所須要作的就是不要寫出這個目
標的規則。那麼,make 會試圖去自動推導產生這個目標的規則和命令,如
果make 能夠自動推導生成這個目標的規則和命令,那麼這個行爲就是隱含
規則的自動推導。固然,隱含規則是make 事先約定好的一些東西。例如,
咱們有下面的一個Makefile:
foo : foo.o bar.o
cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
咱們能夠注意到,這個Makefile 中並無寫下如何生成foo.o 和bar.o 這
兩目標的規則和命令。由於make 的「隱含規則」功能會自動爲咱們自動去
推導這兩個目標的依賴目標和生成命令。
make 會在本身的「隱含規則」庫中尋找能夠用的規則,若是找到,那麼就
會使用。若是找不到,那麼就會報錯。在上面的那個例子中,make 調用的
隱含規則是,把[.o]的目標的依賴文件置成[.c],並使用C 的編譯命令「cc
–c $(CFLAGS) [.c]」來生成[.o]的目標。也就是說,咱們徹底沒有必要寫
下下面的兩條規則:
foo.o : foo.c
cc –c foo.c $(CFLAGS)
bar.o : bar.c
cc –c bar.c $(CFLAGS)
由於,這已是「約定」好了的事了,make 和咱們約定好了用C 編譯器「cc」
生成[.o]文件的規則,這就是隱含規則。
固然,若是咱們爲[.o]文件書寫了本身的規則,那麼make 就不會自動推導
並調用隱含規則,它會按照咱們寫好的規則忠實地執行。
還有,在make 的「隱含規則庫」中,每一條隱含規則都在庫中有其順序,
越靠前的則是越被常用的,因此,這會致使咱們有些時候即便咱們顯示
地指定了目標依賴,make 也不會管。以下面這條規則(沒有命令):
foo.o : foo.p
依賴文件「foo.p」(Pascal 程序的源文件)有可能變得沒有意義。若是目
錄下存在了「foo.c」文件,那麼咱們的隱含規則同樣會生效,並會經過
「foo.c」調用C 的編譯器生成foo.o 文件。由於,在隱含規則中,Pascal
的規則出如今C 的規則以後,因此,make 找到能夠生成foo.o 的C 的規則
就再也不尋找下一條規則了。若是你確實不但願任何隱含規則推導,那麼,你
就不要只寫出「依賴規則」,而不寫命令。
2、隱含規則一覽
這裏咱們將講述全部預先設置(也就是make 內建)的隱含規則,若是咱們
不明確地寫下規則,那麼,make 就會在這些規則中尋找所須要規則和命令。
固然,咱們也能夠使用make 的參數「-r」或「--no-builtin-rules」選項
來取消全部的預設置的隱含規則。
固然,即便是咱們指定了「-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, .txi
nfo, .w, .ch .web, .sh, .elc, .el。具體的細節,咱們會在後面講述。
仍是先來看一看經常使用的隱含規則吧。
一、編譯C 程序的隱含規則。
「<n>.o」的目標的依賴目標會自動推導爲「<n>.c」,而且其生成命令是「$(CC)
–c $(CPPFLAGS) $(CFLAGS)」
二、編譯C++程序的隱含規則。
「<n>.o」的目標的依賴目標會自動推導爲「<n>.cc」或是「<n>.C」,而且
其生成命令是「$(CXX) –c $(CPPFLAGS) $(CFLAGS)」。(建議使用「.cc」
做爲C++源文件的後綴,而不是「.C」)
三、編譯Pascal 程序的隱含規則。
「<n>.o」的目標的依賴目標會自動推導爲「<n>.p」,而且其生成命令是「$(PC)
–c $(PFLAGS)」。
四、編譯Fortran/Ratfor 程序的隱含規則。
「<n>.o」的目標的依賴目標會自動推導爲「<n>.r」或「<n>.F」或「<n>.f」,
而且其生成命令是:
「.f」 「$(FC) –c $(FFLAGS)」
「.F」 「$(FC) –c $(FFLAGS) $(CPPFLAGS)」
「.f」 「$(FC) –c $(FFLAGS) $(RFLAGS)」
五、預處理Fortran/Ratfor 程序的隱含規則。
「<n>.f」的目標的依賴目標會自動推導爲「<n>.r」或「<n>.F」。這個規則
只是轉換Ratfor 或有預處理的Fortran 程序到一個標準的Fortran 程序。
其使用的命令是:
「.F」 「$(FC) –F $(CPPFLAGS) $(FFLAGS)」
「.r」 「$(FC) –F $(FFLAGS) $(RFLAGS)」
六、編譯Modula-2 程序的隱含規則。
「<n>.sym」的目標的依賴目標會自動推導爲「<n>.def」,而且其生成命令
是:「$(M2C) $(M2FLAGS) $(DEFFLAGS)」。「<n.o>」 的目標的依賴目標會自
動推導爲「 <n>.mod 」, 而且其生成命令是:「 $(M2C) $(M2FLAGS)
$(MODFLAGS)」。
七、彙編和彙編預處理的隱含規則。
「<n>.o」 的目標的依賴目標會自動推導爲「<n>.s」,默認使用編譯品「as」,
而且其生成命令是:「$(AS) $(ASFLAGS)」。「<n>.s」 的目標的依賴目標會
自動推導爲「<n>.S」,默認使用C 預編譯器「cpp」,而且其生成命令是:「$(AS)
$(ASFLAGS)」。
八、連接Object 文件的隱含規則。
「<n>」目標依賴於「<n>.o」,經過運行C 的編譯器來運行連接程序生成(一
般是「ld」),其生成命令是:「$(CC) $(LDFLAGS) <n>.o $(LOADLIBES)
$(LDLIBS)」。這個規則對於只有一個源文件的工程有效,同時也對多個
Object 文件(由不一樣的源文件生成)的也有效。例如以下規則:
x : y.o z.o
而且「x.c」、「y.c」和「z.c」都存在時,隱含規則將執行以下命令:
cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
rm -f x.o
rm -f y.o
rm -f z.o
若是沒有一個源文件(如上例中的x.c)和你的目標名字(如上例中的x)
相關聯,那麼,你最好寫出本身的生成規則,否則,隱含規則會報錯的。
九、Yacc C 程序時的隱含規則。
「<n>.c」的依賴文件被自動推導爲「n.y」(Yacc 生成的文件),其生成命
令是:「$(YACC) $(YFALGS)」。(「Yacc」是一個語法分析器,關於其細節請
查看相關資料)
十、Lex C 程序時的隱含規則。
「<n>.c」的依賴文件被自動推導爲「n.l」(Lex 生成的文件),其生成命令
是:「$(LEX) $(LFALGS)」。(關於「Lex」的細節請查看相關資料)
十一、Lex Ratfor 程序時的隱含規則。
「<n>.r」的依賴文件被自動推導爲「n.l」(Lex 生成的文件),其生成命令
是:「$(LEX) $(LFALGS)」。
十二、從C 程序、Yacc 文件或Lex 文件建立Lint 庫的隱含規則。
「<n>.ln」 (lint 生成的文件)的依賴文件被自動推導爲「n.c」,其生成
命令是:「$(LINT) $(LINTFALGS) $(CPPFLAGS) -i」。對於「<n>.y」和「<n>.l」
也是一樣的規則。
3、隱含規則使用的變量
在隱含規則中的命令中,基本上都是使用了一些預先設置的變量。你能夠在
你的makefile 中改變這些變量的值,或是在make 的命令行中傳入這些值,
或是在你的環境變量中設置這些值,不管怎麼樣,只要設置了這些特定的變
量,那麼其就會對隱含規則起做用。固然,你也能夠利用make 的「-R」或
「--no–builtin-variables」參數來取消你所定義的變量對隱含規則的做
用。
例如,第一條隱含規則——編譯C 程序的隱含規則的命令是「$(CC) –c
$(CFLAGS) $(CPPFLAGS)」。Make 默認的編譯命令是「cc」,若是你把變量
「$(CC)」重定義成「gcc」,把變量「$(CFLAGS)」重定義成「-g」,那麼,
隱含規則中的命令所有會以「gcc –c -g $(CPPFLAGS)」的樣子來執行了。
咱們能夠把隱含規則中使用的變量分紅兩種:一種是命令相關的,如「CC」;
一種是參數相的關,如「CFLAGS」。下面是全部隱含規則中會用到的變量:
一、關於命令的變量。
AR
函數庫打包程序。默認命令是「ar」。
AS
彙編語言編譯程序。默認命令是「as」。
CC
C 語言編譯程序。默認命令是「cc」。
CXX
C++語言編譯程序。默認命令是「g++」。
CO
從 RCS 文件中擴展文件程序。默認命令是「co」。
CPP
C 程序的預處理器(輸出是標準輸出設備)。默認命令是「$(CC) –E」。
FC
Fortran 和 Ratfor 的編譯器和預處理程序。默認命令是「f77」。
GET
從SCCS 文件中擴展文件的程序。默認命令是「get」。
LEX
Lex 方法分析器程序(針對於C 或Ratfor)。默認命令是「lex」。
PC
Pascal 語言編譯程序。默認命令是「pc」。
YACC
Yacc 文法分析器(針對於C 程序)。默認命令是「yacc」。
YACCR
Yacc 文法分析器(針對於Ratfor 程序)。默認命令是「yacc –r」。
MAKEINFO
轉換Texinfo 源文件(.texi)到Info 文件程序。默認命令是「makeinfo」。
TEX
從TeX 源文件建立TeX DVI 文件的程序。默認命令是「tex」。
TEXI2DVI
從Texinfo 源文件建立軍TeX DVI 文件的程序。默認命令是「texi2dvi」。
WEAVE
轉換Web 到TeX 的程序。默認命令是「weave」。
CWEAVE
轉換C Web 到 TeX 的程序。默認命令是「cweave」。
TANGLE
轉換Web 到Pascal 語言的程序。默認命令是「tangle」。
CTANGLE
轉換C Web 到 C。默認命令是「ctangle」。
RM
刪除文件命令。默認命令是「rm –f」。
二、關於命令參數的變量
下面的這些變量都是相關上面的命令的參數。若是沒有指明其默認值,那麼
其默認值都是空。
ARFLAGS
函數庫打包程序AR 命令的參數。默認值是「rv」。
ASFLAGS
彙編語言編譯器參數。(當明顯地調用「.s」或「.S」文件時)。
CFLAGS
C 語言編譯器參數。
CXXFLAGS
C++語言編譯器參數。
COFLAGS
RCS 命令參數。
CPPFLAGS
C 預處理器參數。( C 和 Fortran 編譯器也會用到)。
FFLAGS
Fortran 語言編譯器參數。
GFLAGS
SCCS 「get」程序參數。
LDFLAGS
連接器參數。(如:「ld」)
LFLAGS
Lex 文法分析器參數。
PFLAGS
Pascal 語言編譯器參數。
RFLAGS
Ratfor 程序的Fortran 編譯器參數。
YFLAGS
Yacc 文法分析器參數。
4、隱含規則鏈
有些時候,一個目標可能被一系列的隱含規則所做用。例如,一個[.o]的文
件生成,可能會是先被Yacc 的[.y]文件先成[.c],而後再被C 的編譯器生
成。咱們把這一系列的隱含規則叫作「隱含規則鏈」。
在上面的例子中,若是文件[.c]存在,那麼就直接調用C 的編譯器的隱含規
則,若是沒有[.c]文件,但有一個[.y]文件,那麼Yacc 的隱含規則會被調
用,生成[.c]文件,而後,再調用C 編譯的隱含規則最終由[.c]生成[.o]
文件,達到目標。
咱們把這種[.c]的文件(或是目標),叫作中間目標。無論怎麼樣,make 會
努力自動推導生成目標的一切方法,無論中間目標有多少,其都會執着地把
全部的隱含規則和你書寫的規則所有合起來分析,努力達到目標,因此,有
些時候,可能會讓你以爲奇怪,怎麼個人目標會這樣生成?怎麼個人
makefile 發瘋了?
在默認狀況下,對於中間目標,它和通常的目標有兩個地方所不一樣:第一個
不一樣是除非中間的目標不存在,纔會引起中間規則。第二個不一樣的是,只要
目標成功產生,那麼,產生最終目標過程當中,所產生的中間目標文件會被以
「rm -f」刪除。
一般,一個被makefile 指定成目標或是依賴目標的文件不能被看成中介。
然而,你能夠明顯地說明一個文件或是目標是中介目標,你能夠使用僞目標
「.INTERMEDIATE」來強制聲明。(如:.INTERMEDIATE : mid )
你也能夠阻止make 自動刪除中間目標,要作到這一點,你能夠使用僞目標
「.SECONDARY」來強制聲明(如:.SECONDARY : sec)。你還能夠把你的目
標,以模式的方式來指定(如:%.o)成僞目標「.PRECIOUS」的依賴目標,
以保存被隱含規則所生成的中間文件。
在「隱含規則鏈」中,禁止同一個目標出現兩次或兩次以上,這樣一來,就
可防止在make 自動推導時出現無限遞歸的狀況。
Make 會優化一些特殊的隱含規則,而不生成中間文件。如,從文件「foo.c」
生成目標程序「foo」,按道理,make 會編譯生成中間文件「foo.o」,而後
連接成「foo」,但在實際狀況下,這一動做能夠被一條「cc」的命令完成(cc
–o foo foo.c),因而優化過的規則就不會生成中間文件。
5、定義模式規則
你能夠使用模式規則來定義一個隱含規則。一個模式規則就好像一個通常的
規則,只是在規則中,目標的定義須要有"%"字符。"%"的意思是表示一個或
多個任意字符。在依賴目標中一樣能夠使用"%",只是依賴目標中的"%"的取
值,取決於其目標。
有一點須要注意的是,"%"的展開發生在變量和函數的展開以後,變量和函
數的展開發生在make 載入Makefile 時,而模式規則中的"%"則發生在運行
時。
一、模式規則介紹
模式規則中,至少在規則的目標定義中要包含"%",不然,就是通常的規則。
目標中的"%"定義表示對文件名的匹配,"%"表示長度任意的非空字符串。例
如:"%.c"表示以".c"結尾的文件名(文件名的長度至少爲3),而"s.%.c"
則表示以"s."開頭,".c"結尾的文件名(文件名的長度至少爲5)。
若是"%"定義在目標中,那麼,目標中的"%"的值決定了依賴目標中的"%"的
值,也就是說,目標中的模式的"%"決定了依賴目標中"%"的樣子。例若有一
個模式規則以下:
%.o : %.c ; <command ......>
其含義是,指出了怎麼從全部的[.c]文件生成相應的[.o]文件的規則。若是
要生成的目標是"a.o b.o",那麼"%c"就是"a.c b.c"。
一旦依賴目標中的"%"模式被肯定,那麼,make 會被要求去匹配當前目錄下
全部的文件名,一旦找到,make 就會規則下的命令,因此,在模式規則中,
目標可能會是多個的,若是有模式匹配出多個目標,make 就會產生全部的
模式目標,此時,make 關心的是依賴的文件名和生成目標的命令這兩件事。
二、模式規則示例
下面這個例子表示了,把全部的[.c]文件都編譯成[.o]文件.
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
其中,"$@"表示全部的目標的挨個值,"$<"表示了全部依賴目標的挨個值。
這些奇怪的變量咱們叫"自動化變量",後面會詳細講述。
下面的這個例子中有兩個目標是模式的:
%.tab.c %.tab.h: %.y
bison -d $<
這條規則告訴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]文件的依賴關係也寫好,那麼,全部的目標都會得
到知足)
三、自動化變量
在上述的模式規則中,目標和依賴文件都是一系例的文件,那麼咱們如何書
寫一個命令來完成從不一樣的依賴文件生成相應的目標?由於在每一次的對
模式規則的解析時,都會是不一樣的目標和依賴文件。
自動化變量就是完成這個功能的。在前面,咱們已經對自動化變量有所提涉,
相信你看到這裏已對它有一個感性認識了。所謂自動化變量,就是這種變量
會把模式中所定義的一系列的文件自動地挨個取出,直至全部的符合模式的
文件都取完了。這種自動化變量只應出如今規則的命令中。
下面是全部的自動化變量及其說明:
$@
表示規則中的目標文件集。在模式規則中,若是有多個目標,那麼,"$@"
就是匹配於目標中模式定義的集合。
$%
僅當目標是函數庫文件中,表示規則中的目標成員名。例如,若是一個目標
是"foo.a(bar.o)",那麼,"$%"就是"bar.o","$@"就是"foo.a"。若是目標
不是函數庫文件(Unix 下是[.a],Windows 下是[.lib]),那麼,其值爲空。
$<
依賴目標中的第一個目標名字。若是依賴目標是以模式(即"%")定義的,
那麼"$<"將是符合模式的一系列的文件集。注意,其是一個一個取出來的。
$?
全部比目標新的依賴目標的集合。以空格分隔。
$^
全部的依賴目標的集合。以空格分隔。若是在依賴目標中有多個重複的,那
個這個變量會去除重複的依賴目標,只保留一份。
$+
這個變量很像"$^",也是全部依賴目標的集合。只是它不去除重複的依賴目
標。
$*
這個變量表示目標模式中"%"及其以前的部分。若是目標是"dir/a.foo.b",
而且目標的模式是"a.%.b",那麼,"$*"的值就是"dir/a.foo"。這個變量對
於構造有關聯的文件名是比較有較。若是目標中沒有模式的定義,那麼"$*"
也就不能被推導出,可是,若是目標文件的後綴是make 所識別的,那麼"$*"
就是除了後綴的那一部分。例如:若是目標是"foo.c",由於".c"是make 所
能識別的後綴名,因此,"$*"的值就是"foo"。這個特性是GNU make 的,很
有可能不兼容於其它版本的make,因此,你應該儘可能避免使用"$*",除非
是在隱含規則或是靜態模式中。若是目標中的後綴是make 所不能識別的,
那麼"$*"就是空值。
當你但願只對更新過的依賴文件進行操做時,"$?"在顯式規則中頗有用,例
如,假設有一個函數庫文件叫"lib",其由其它幾個object 文件更新。那麼
把object 文件打包的比較有效率的Makefile 規則是:
lib : foo.o bar.o lose.o win.o
ar r lib $?
在上述所列出來的自動量變量中。四個變量($@、$<、$%、$*)在擴展時只
會有一個文件,而另三個的值是一個文件列表。這七個自動化變量還能夠取
得文件的目錄名或是在當前目錄下的符合模式的文件名,只須要搭配上"D"
或"F"字樣。這是GNU make 中老版本的特性,在新版本中,咱們使用函數"dir"
或"notdir"就能夠作到了。"D"的含義就是Directory,就是目錄,"F"的含
義就是File,就是文件。
下面是對於上面的七個變量分別加上"D"或是"F"的含義:
$(@D)
表示"$@"的目錄部分(不以斜槓做爲結尾),若是"$@"值是"dir/foo.o",那
麼"$(@D)"就是"dir",而若是"$@"中沒有包含斜槓的話,其值就是"."(當
前目錄)。
$(@F)
表示"$@"的文件部分,若是"$@" 值是"dir/foo.o" , 那麼"$(@F)" 就是
"foo.o","$(@F)"至關於函數"$(notdir $@)"。
"$(*D)"
"$(*F)"
和上面所述的同理,也是取文件的目錄部分和文件部分。對於上面的那個例
子,"$(*D)"返回"dir",而"$(*F)"返回"foo"
"$(%D)"
"$(%F)"
分別表示了函數包文件成員的目錄部分和文件部分。這對於形同
"archive(member)"形式的目標中的"member"中包含了不一樣的目錄頗有用。
"$(<D)"
"$(<F)"
分別表示依賴文件的目錄部分和文件部分。
"$(^D)"
"$(^F)"
分別表示全部依賴文件的目錄部分和文件部分。(無相同的)
"$(+D)"
"$(+F)"
分別表示全部依賴文件的目錄部分和文件部分。(能夠有相同的)
"$(?D)"
"$(?F)"
分別表示被更新的依賴文件的目錄部分和文件部分。
最後想提醒一下的是,對於"$<",爲了不產生沒必要要的麻煩,咱們最好給
$後面的那個特定字符都加上圓括號,好比,"$(< )"就要比"$<"要好一些。
還得要注意的是,這些變量只使用在規則的命令中,並且通常都是"顯式規
則"和"靜態模式規則"(參見前面"書寫規則"一章)。其在隱含規則中並無
意義。
四、模式的匹配
通常來講,一個目標的模式有一個有前綴或是後綴的"%",或是沒有先後綴,
直接就是一個"%"。由於"%"表明一個或多個字符,因此在定義好了的模式中,
咱們把"%"所匹配的內容叫作"莖",例如"%.c"所匹配的文件"test.c"中
"test"就是"莖"。由於在目標和依賴目標中同時有"%"時,依賴目標的"莖"
會傳給目標,當作目標中的"莖"。
當一個模式匹配包含有斜槓(實際也不常常包含)的文件時,那麼在進行模
式匹配時,目錄部分會首先被移開,而後進行匹配,成功後,再把目錄加回
去。在進行"莖"的傳遞時,咱們須要知道這個步驟。例若有一個模式"e%t",
文件"src/eat"匹配於該模式,因而"src/a"就是其"莖",若是這個模式定義
在依賴目標中,而被依賴於這個模式的目標中又有個模式"c%r",那麼,目
標就是"src/car"。("莖"被傳遞)
五、重載內建隱含規則
你能夠重載內建的隱含規則(或是定義一個全新的),例如你能夠從新構造
和內建隱含規則不一樣的命令,如:
%.o : %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)
你能夠取消內建的隱含規則,只要不在後面寫命令就行。如:
%.o : %.s
一樣,你也能夠從新定義一個全新的隱含規則,其在隱含規則中的位置取決
於你在哪裏寫下這個規則。朝前的位置就靠前。
6、老式風格的"後綴規則"
後綴規則是一個比較老式的定義隱含規則的方法。後綴規則會被模式規則逐
步地取代。由於模式規則更強更清晰。爲了和老版本的Makefile 兼容,GNU
make 一樣兼容於這些東西。後綴規則有兩種方式:"雙後綴"和"單後綴"。
雙後綴規則定義了一對後綴:目標文件的後綴和依賴目標(源文件)的後綴。
如".c.o"至關於"%o : %c"。單後綴規則只定義一個後綴,也就是源文件的
後綴。如".c"至關於"% : %.c"。
後綴規則中所定義的後綴應該是make 所認識的,若是一個後綴是make 所認
識的,那麼這個規則就是單後綴規則,而若是兩個連在一塊兒的後綴都被make
所認識,那就是雙後綴規則。例如:".c"和".o"都是make 所知道。於是,
若是你定義了一個規則是".c.o"那麼其就是雙後綴規則,意義就是".c"是源
文件的後綴,".o"是目標文件的後綴。以下示例:
.c.o:
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
後綴規則不容許任何的依賴文件,若是有依賴文件的話,那就不是後綴規則,
那些後綴通通被認爲是文件名,如:
.c.o: foo.h
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
這個例子,就是說,文件".c.o"依賴於文件"foo.h",而不是咱們想要的這
樣:
%.o: %.c foo.h
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
後綴規則中,若是沒有命令,那是毫無心義的。由於他也不會移去內建的隱
含規則。
而要讓make 知道一些特定的後綴,咱們能夠使用僞目標".SUFFIXES"來定義
或是刪除,如:
.SUFFIXES: .hack .win
把後綴.hack 和.win 加入後綴列表中的末尾。
.SUFFIXES: # 刪除默認的後綴
.SUFFIXES: .c .o .h # 定義本身的後綴
先清楚默認後綴,後定義本身的後綴列表。
make 的參數"-r"或"-no-builtin-rules"也會使用得默認的後綴列表爲空。
而變量"SUFFIXE"被用來定義默認的後綴列表,你能夠用".SUFFIXES"來改變
後綴列表,但請不要改變變量"SUFFIXE"的值。
7、隱含規則搜索算法
好比咱們有一個目標叫 T。下面是搜索目標T 的規則的算法。請注意,在下
面,咱們沒有提到後綴規則,緣由是,全部的後綴規則在Makefile 被載入
內存時,會被轉換成模式規則。若是目標是"archive(member)"的函數庫文
件模式,那麼這個算法會被運行兩次,第一次是找目標T,若是沒有找到的
話,那麼進入第二次,第二次會把"member"看成T 來搜索。
一、把T 的目錄部分分離出來。叫D,而剩餘部分叫N。(如:若是T 是
"src/foo.o",那麼,D 就是"src/",N 就是"foo.o")
二、建立全部匹配於T 或是N 的模式規則列表。
三、若是在模式規則列表中有匹配全部文件的模式,如"%",那麼從列表中移
除其它的模式。
四、移除列表中沒有命令的規則。
五、對於第一個在列表中的模式規則:
1)推導其"莖"S,S 應該是T 或是N 匹配於模式中"%"非空的部分。
2)計算依賴文件。把依賴文件中的"%"都替換成"莖"S。若是目標模式中沒
有包含斜框字符,而把D 加在第一個依賴文件的開頭。
3)測試是否全部的依賴文件都存在或是理當存在。(若是有一個文件被定義
成另一個規則的目標文件,或者是一個顯式規則的依賴文件,那麼這個文
件就叫"理當存在")
4)若是全部的依賴文件存在或是理當存在,或是就沒有依賴文件。那麼這
條規則將被採用,退出該算法。
六、若是通過第5 步,沒有模式規則被找到,那麼就作更進一步的搜索。對
於存在於列表中的第一個模式規則:
1)若是規則是終止規則,那就忽略它,繼續下一條模式規則。
2)計算依賴文件。(同第5 步)
3)測試全部的依賴文件是否存在或是理當存在。
4)對於不存在的依賴文件,遞歸調用這個算法查找他是否能夠被隱含規則
找到。
5)若是全部的依賴文件存在或是理當存在,或是就根本沒有依賴文件。那
麼這條規則被採用,退出該算法。
七、若是沒有隱含規則能夠使用,查看".DEFAULT"規則,若是有,採用,把
".DEFAULT"的命令給T 使用。
一旦規則被找到,就會執行其至關的命令,而此時,咱們的自動化變量的值
纔會生成。
使用make 更新函數庫文件
———————————
函數庫文件也就是對Object 文件(程序編譯的中間文件)的打包文件。在
Unix 下,通常是由命令"ar"來完成打包工做。
1、函數庫文件的成員
一個函數庫文件由多個文件組成。你能夠以以下格式指定函數庫文件及其組
成:
archive(member)
這個不是一個命令,而一個目標和依賴的定義。通常來講,這種用法基本上
就是爲了"ar"命令來服務的。如:
foolib(hack.o) : hack.o
ar cr foolib hack.o
若是要指定多個member,那就以空格分開,如:
foolib(hack.o kludge.o)
其等價於:
foolib(hack.o) foolib(kludge.o)
你還能夠使用Shell 的文件通配符來定義,如:
foolib(*.o)
2、函數庫成員的隱含規則
當make 搜索一個目標的隱含規則時,一個特殊的特性是,若是這個目標是
"a(m)"形式的,其會把目標變成"(m)"。因而,若是咱們的成員是"%.o"的模
式定義,而且若是咱們使用"make foo.a(bar.o)"的形式調用Makefile 時,
隱含規則會去找"bar.o"的規則,若是沒有定義bar.o 的規則,那麼內建隱
含規則生效,make 會去找bar.c 文件來生成bar.o,若是找獲得的話,make
執行的命令大體以下:
cc -c bar.c -o bar.o
ar r foo.a bar.o
rm -f bar.o
還有一個變量要注意的是"$%",這是專屬函數庫文件的自動化變量,有關其
說明請參見"自動化變量"一節。
3、函數庫文件的後綴規則
你能夠使用"後綴規則"和"隱含規則"來生成函數庫打包文件,如:
.c.a:
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
$(AR) r $@ $*.o
$(RM) $*.o
其等效於:
(%.o) : %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
$(AR) r $@ $*.o
$(RM) $*.o
4、注意事項
在進行函數庫打包文件生成時,請當心使用make 的並行機制("-j"參數)。
若是多個ar 命令在同一時間運行在同一個函數庫打包文件上,就頗有能夠
損壞這個函數庫文件。因此,在make 將來的版本中,應該提供一種機制來
避免並行操做發生在函數打包文件上。
但就目前而言,你仍是應該不要儘可能不要使用"-j"參數。
後序
——
終於到寫結束語的時候了,以上基本上就是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)"殺傷力"。
對於上述全部的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 時有一些更爲絕
妙的方法,請記得告訴我啊。
回頭看看整篇文檔,不覺記起幾年前剛剛開始在Unix 下作開發的時候,有
人問我會不會寫Makefile 時,我兩眼發直,根本不知道在說什麼。一開始
看到別人在vi 中寫完程序後輸入"!make"時,還覺得是vi 的功能,後來才
知道有一個Makefile 在做怪,因而上網查啊查,那時又不肯意看英文,發
現就根本沒有中文的文檔介紹Makefile,只得看別人寫的Makefile,本身
瞎碰瞎搞才積累了一點知識,但在不少地方徹底是知其然不知因此然。後來
開始從事UNIX 下產品軟件的開發,看到一個400 人年,近200 萬行代碼的
大工程,發現要編譯這樣一個龐然大物,若是沒有Makefile,那會是多麼
恐怖的同樣事啊。因而橫下心來,狠命地讀了一堆英文文檔,才以爲對其掌
握了。但發現目前網上對Makefile 介紹的文章仍是少得那麼的可憐,因此
想寫這樣一篇文章,共享給你們,但願能對各位有所幫助。
如今我終於寫完了,看了看文件的建立時間,這篇技術文檔也寫了兩個多月
了。發現,本身知道是一回事,要寫下來,跟別人講述又是另一回事,而
且,如今愈來愈沒有時間專研技術細節,因此在寫做時,發如今闡述一些細
節問題時很難作到嚴謹和精練,並且對先講什麼後講什麼不是很清楚,因此,
仍是參考了一些國外站點上的資料和題綱,以及一些技術書籍的語言風格,
才得以完成。整篇文檔的提綱是基於GNU 的Makefile 技術手冊的提綱來書
寫的,並結合了本身的工做經驗,以及本身的學習歷程。由於歷來沒有寫過
這麼長,這麼細的文檔,因此必定會有不少地方存在表達問題,語言歧義或
是錯誤。因些,我迫切地得等待各位給我指證和建議,以及任何的反饋。
最後,仍是利用這個後序,介紹一下本身。我目前從事於全部Unix 平臺下
的軟件研發,主要是作分佈式計算/網格計算方面的系統產品軟件,而且我
對於下一代的計算機革命——網格計算很是地感興趣,對於分佈式計算、
P2P、Web Service、J2EE 技術方向也很感興趣,同時,對於項目實施、團
隊管理、項目管理也小有心得,但願一樣和我戰鬥在「技術和管理並重」的
陣線上的年輕一代,可以和我多多地交流。個人MSN 是:
haoel@hotmail.com
(經常使用),QQ 是:753640(不經常使用)。(注:請勿給我MSN 的郵箱發信,因爲
hotmail 的垃圾郵件致使我拒收這個郵箱的全部來信)
我歡迎任何形式的交流,不管是討論技術仍是管理,或是其它海闊天空的東
西。除了政治和娛樂新聞我不關心,其它只要積極向上的東西我都歡迎!
最最後,我還想介紹一下make 程序的設計開發者。
首當其衝的是: Richard Stallman
開源軟件的領袖和先驅,歷來沒有領過一天工資,歷來沒有使用過Windows
操做系統。對於他的事蹟和他的軟件以及他的思想,我無需說過多的話,相
信你們對這我的並不比我陌生, 這是他的主頁:
http://www.stallman.org/
。
第二位是:Roland McGrath
我的主頁是:
http://www.frob.com/~roland/
,下面是他的一些事蹟:
1) 合做編寫了並維護GNU make。
2) 和Thomas Bushnell 一同編寫了GNU Hurd。
3) 編寫並維護着GNU C library。
4) 合做編寫並維護着部分的GNU Emacs。
在此,向這兩位開源項目的鬥士致以最真切的敬意。
(全文完)
版權聲明:本文爲博主原創文章,未經博主容許不得轉載。
linux
相關文章
1.
跟我一塊兒寫Makefile
2.
跟我一塊兒寫 Makefile
3.
【轉】跟我一塊兒寫 Makefile
4.
讀書之--跟我一塊兒寫Makefile
5.
(轉) 跟我一塊兒寫 Makefile --- 陳皓
6.
(轉)跟我一塊兒寫MAKEFILE
7.
跟我一塊兒寫 Makefile(一)
8.
跟我一塊兒寫 Makefile (重製版)
9.
跟我一塊兒寫 Makefile(十二)
10.
跟我一塊兒學Makefile
更多相關文章...
•
RSS 閱讀器
-
RSS 教程
•
PHP 實例 - AJAX RSS 閱讀器
-
PHP教程
•
JDK13 GA發佈:5大特性解讀
•
再有人問你分佈式事務,把這篇扔給他
相關標籤/搜索
轉載收藏
論文閱讀
轉收藏
收集-轉載
makefile
閱讀
一塊兒
收藏
C&C++
Thymeleaf 教程
Redis教程
PHP教程
0
分享到微博
分享到微信
分享到QQ
每日一句
每一个你不满意的现在,都有一个你没有努力的曾经。
最新文章
1.
windows下配置opencv
2.
HED神經網
3.
win 10+ annaconda+opencv
4.
ORB-SLAM3系列-多地圖管理
5.
opencv報錯——(mtype == CV_8U || mtype == CV_8S)
6.
OpenCV計算機視覺學習(9)——圖像直方圖 & 直方圖均衡化
7.
【超詳細】深度學習原理與算法第1篇---前饋神經網絡,感知機,BP神經網絡
8.
Python數據預處理
9.
ArcGIS網絡概述
10.
數據清洗(三)------檢查數據邏輯錯誤
本站公眾號
歡迎關注本站公眾號,獲取更多信息
相關文章
1.
跟我一塊兒寫Makefile
2.
跟我一塊兒寫 Makefile
3.
【轉】跟我一塊兒寫 Makefile
4.
讀書之--跟我一塊兒寫Makefile
5.
(轉) 跟我一塊兒寫 Makefile --- 陳皓
6.
(轉)跟我一塊兒寫MAKEFILE
7.
跟我一塊兒寫 Makefile(一)
8.
跟我一塊兒寫 Makefile (重製版)
9.
跟我一塊兒寫 Makefile(十二)
10.
跟我一塊兒學Makefile
>>更多相關文章<<