国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁(yè) > news >正文

科技創(chuàng)新網(wǎng)站建設(shè)策劃書溫州seo網(wǎng)站建設(shè)

科技創(chuàng)新網(wǎng)站建設(shè)策劃書,溫州seo網(wǎng)站建設(shè),聊城做網(wǎng)站做的不錯(cuò)的網(wǎng)絡(luò)公司,廊坊網(wǎng)站建設(shè)技術(shù)托管目錄 一、概述 1.1 Makefile 介紹 1.2規(guī)則 1.3核心 1.4示例 1.5定義命令 1.6 make是如何工作的 1.7、makefile中使用變量 1.8讓make自動(dòng)推導(dǎo) 1.9、另類風(fēng)格的makefile 1.10、清空目標(biāo)文件的規(guī)則 二、Makefile 總述 2.1、Makefile里有什么? 2.2、 mak…

目錄

一、概述

1.1? Makefile 介紹

1.2規(guī)則

1.3核心

1.4示例

1.5定義命令

1.6?make是如何工作的

1.7、makefile中使用變量

1.8讓make自動(dòng)推導(dǎo)

1.9、另類風(fēng)格的makefile

1.10、清空目標(biāo)文件的規(guī)則

二、Makefile 總述

2.1、Makefile里有什么?

2.2、?makefile文件名

2.3、引用其它的Makefile

2.4、環(huán)境變量 MAKEFILES

2.5、make的工作方式

2.6.2規(guī)則舉例

2.6.3規(guī)則的語(yǔ)法

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

2.7.1? 通配符

三、文件搜尋

3.1?VPATH

四.偽目標(biāo)

五、多目標(biāo)

5.2靜態(tài)模式

5.3?自動(dòng)生成依賴性

六.書寫命令

6.1、顯示命令

6.2、命令執(zhí)行

6.3、命令出錯(cuò)

七、嵌套執(zhí)行make

八、定義命令包

九.使用變量

9.1變量的基礎(chǔ)

9.2、變量中的變量

9.3、變量高級(jí)用法

9.4、追加變量值

9.5、override?指示符

9.6、多行變量

9.7、環(huán)境變量

9.8、目標(biāo)變量

9.9、模式變量

十.使用條件判斷

10.1、示例

10.2、語(yǔ)法

十一.使用函數(shù)

11.1、函數(shù)的調(diào)用語(yǔ)法

11.2.字符串處理函數(shù)

11.3、文件名操作函數(shù)

11.4、foreach 函數(shù)

11.5、if 函數(shù)

11.6.call函數(shù)

11.7、origin函數(shù)

11.8、shell函數(shù)

11.9、控制make的函數(shù)

十二.make 的運(yùn)行

12.1、make的退出碼

12.2、指定Makefile

12.3、指定目標(biāo)

12.4、檢查規(guī)則

12.5、make的參數(shù)

十三.隱含規(guī)則

13.1使用隱含規(guī)則

13.2、隱含規(guī)則一覽

13.3、隱含規(guī)則使用的變量

13.4、隱含規(guī)則鏈

13.5、定義模式規(guī)則

13.5.1、模式規(guī)則介紹

13.5.2、模式規(guī)則示例

13.5.4、模式的匹配

13.5.5、重載內(nèi)建隱含規(guī)則

13.6、老式風(fēng)格的"后綴規(guī)則"

13.7、隱含規(guī)則搜索算法

十四.使用make更新函數(shù)庫(kù)文件

14.1、函數(shù)庫(kù)文件的成員

14.2、函數(shù)庫(kù)成員的隱含規(guī)則

14.3、函數(shù)庫(kù)文件的后綴規(guī)則

14.4、注意事項(xiàng)

Make常用函數(shù)


一、概述

makefile關(guān)系到了整個(gè)工程的編譯規(guī)則。一個(gè)工程中的源文件不計(jì)數(shù),其按類型、功能、模塊分別放在若干個(gè)目錄中,makefile定義了一系列的規(guī)則來指定,哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯,甚至于進(jìn)行更復(fù)雜的功能操作,因?yàn)閙akefile就像一個(gè)Shell腳本一樣,其中也可以執(zhí)行操作系統(tǒng)的命令。

makefile帶來的好處就是——“自動(dòng)化編譯”,一旦寫好,只需要一個(gè)make命令,整個(gè)工程完全自動(dòng)編譯,極大的提高了軟件開發(fā)的效率。make是一個(gè)命令工具,是一個(gè)解釋makefile中指令的命令工具,一般來說,大多數(shù)的IDE都有這個(gè)命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make??梢?#xff0c;makefile都成為了一種在工程方面的編譯方法。

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

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

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

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

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

1.1? Makefile 介紹

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

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

1.2規(guī)則

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

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

3)如果這個(gè)工程的頭文件被改變了,那么我們需要編譯引用了這幾個(gè)頭文件的C文件,并鏈接目標(biāo)程序。只要我們的Makefile寫得夠好,所有的這一切,我們只用一個(gè)make命令就可以完成,make命令會(huì)自動(dòng)智能地根據(jù)當(dāng)前的文件修改的情況來確定哪些文件需要重編譯,從而自己編譯所需要的文件和鏈接目標(biāo)程序。在講述這個(gè)Makefile之前,還是讓我們先來粗略地看一看Makefile的規(guī)則。

Target ...:prerequisites ...command          ...              .. .             

target是一個(gè)目標(biāo)文件,也可以是Object File,也可以是執(zhí)行文件。還可以是一個(gè)標(biāo)簽(Label),對(duì)于標(biāo)簽這種特性,在后續(xù)的“偽目標(biāo)”章節(jié)中會(huì)有敘述。

Prerequisites表示要生成target所需要的文件或是目標(biāo)。

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

這是一個(gè)文件的依賴關(guān)系,也就是說,target這一個(gè)或多個(gè)的目標(biāo)文件依賴于prerequisites中的文件,其生成規(guī)則定義在command中。

1.3核心

prerequisites中如果有一個(gè)以上的文件比target文件要新的話,command所定義的命令就會(huì)被執(zhí)行。這就是Makefile的規(guī)則。也就是Makefile中最核心的內(nèi)容。

1.4示例

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

edit : main.o kbd.o command.o display.o \insert.o search.o files.o utils.occ -o edit main.o kbd.o command.o display.o \insert.o search.o files.o utils.o
main.o : main.c defs.hcc -c main.c
kbd.o : kbd.c defs.h command.hcc -c kbd.c
command.o : command.c defs.h command.hcc -c command.c
display.o : display.c defs.h buffer.hcc -c display.c
insert.o : insert.c defs.h buffer.hcc -c insert.c
search.o : search.c defs.h buffer.hcc -c search.c
files.o : files.c defs.h buffer.h command.hcc -c files.c
utils.o : utils.c defs.hcc -c utils.c
clean :rm edit main.o kbd.o command.o display.o \insert.o search.o files.o utils.o

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

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

1.5定義命令

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

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

1.6?make是如何工作的

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

? ? 1、make會(huì)在當(dāng)前目錄下找名字叫“Makefile”或“makefile”的文件。
??? 2、如果找到,它會(huì)找文件中的第一個(gè)目標(biāo)文件(target),在上面的例子中,他會(huì)找到“edit”這個(gè)文件,并把這個(gè)文件作為最終的目標(biāo)文件。
??? 3、如果edit文件不存在,或是edit所依賴的后面的 .o 文件的文件修改時(shí)間要比edit這個(gè)文件新,那么,他就會(huì)執(zhí)行后面所定義的命令來生成edit這個(gè)文件。
??? 4、如果edit所依賴的.o文件也存在,那么make會(huì)在當(dāng)前文件中找目標(biāo)為.o文件的依賴性,如果找到則再根據(jù)那一個(gè)規(guī)則生成.o文件。(這有點(diǎn)像一個(gè)堆棧的過程)
??? 5、當(dāng)然,你的C文件和H文件是存在的啦,于是make會(huì)生成 .o 文件,然后再用 .o 文件生命make的終極任務(wù),也就是執(zhí)行文件edit了。

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

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

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

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

1.7、makefile中使用變量

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

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

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

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

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

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

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.hcc -c main.c
kbd.o : kbd.c defs.h command.hcc -c kbd.c
command.o : command.c defs.h command.hcc -c command.c
display.o : display.c defs.h buffer.hcc -c display.c
insert.o : insert.c defs.h buffer.hcc -c insert.c
search.o : search.c defs.h buffer.hcc -c search.c
files.o : files.c defs.h buffer.h command.hcc -c files.c
utils.o : utils.c defs.hcc -c utils.c
clean :rm edit $(objects)

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

1.8讓make自動(dòng)推導(dǎo)

GNU的make很強(qiáng)大,它可以自動(dòng)推導(dǎo)文件以及文件依賴關(guān)系后面的命令,于是我們就沒必要去在每一個(gè)[.o]文件后都寫上類似的命令,因?yàn)?#xff0c;我們的make會(huì)自動(dòng)識(shí)別,并自己推導(dǎo)命令。

只要make看到一個(gè)[.o]文件,它就會(huì)自動(dòng)的把[.c]文件加在依賴關(guān)系中,如果make找到一個(gè)whatever.o,那么whatever.c,就會(huì)是whatever.o的依賴文件。并且 cc -c whatever.c 也會(huì)被推導(dǎo)出來,于是,我們的makefile再也不用寫得這么復(fù)雜。我們的是新的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的“隱晦規(guī)則”。上面文件內(nèi)容中,“.PHONY”表示,clean是個(gè)偽目標(biāo)文件。

1.9、另類風(fēng)格的makefile

即然我們的make可以自動(dòng)推導(dǎo)命令,那么我看到那堆[.o]和[.h]的依賴就有點(diǎn)不爽,那么多的重復(fù)的[.h],能不能把其收攏起來,好吧,沒有問題,這個(gè)對(duì)于make來說很容易,誰叫它提供了自動(dòng)推導(dǎo)命令和文件的功能呢?來看看最新風(fēng)格的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.hkbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
.PHONY:clean
clean:rm edit $(objects)

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

1.10、清空目標(biāo)文件的規(guī)則

每個(gè)Makefile中都應(yīng)該寫一個(gè)清空目標(biāo)文件(.o和執(zhí)行文件)的規(guī)則,這不僅便于重編譯,也很利于保持文件的清潔。這是一個(gè)“修養(yǎng)一般的風(fēng)格都是:

clean:rm edit $(objects)

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

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

rm命令前面加了一個(gè)小減號(hào)

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

二、Makefile 總述

2.1、Makefile里有什么?

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

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

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

3、變量的定義。在Makefile中我們要定義一系列的變量,變量一般都是字符串,這個(gè)有點(diǎn)你C語(yǔ)言中的宏,當(dāng)Makefile被執(zhí)行時(shí),其中的變量都會(huì)被擴(kuò)展到相應(yīng)的引用位置上。

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

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

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

2.2、?makefile文件名

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

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

2.3、引用其它的Makefile

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

include “filename”

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

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

include foo.make *.mk $(bar)

等價(jià)于:

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

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

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

?2、如果目錄<prefix>/include(一般是:/usr/local/bin或/usr/include)存在的話,make也會(huì)去找。

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

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

2.4、環(huán)境變量 MAKEFILES

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

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

2.5、make的工作方式

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

?1、讀入所有的Makefile。

?2、讀入被include的其它Makefile。

?3、初始化文件中的變量。

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

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

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

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

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

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

2.6.書寫規(guī)則

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

在Makefile中,規(guī)則的順序是很重要的,因?yàn)?#xff0c;Makefile中只應(yīng)該有一個(gè)最終目標(biāo),其它的目標(biāo)都是被這個(gè)目標(biāo)所連帶出來的,所以一定要讓make知道你的最終目標(biāo)是什么。

2.6.1?Makefile中的目標(biāo)

一般來說,定義在Makefile中的目標(biāo)可能會(huì)有很多,但是第一條規(guī)則中的目標(biāo)將被確立為最終的目標(biāo)。如果第一條規(guī)則中的目標(biāo)有很多個(gè),那么,第一個(gè)目標(biāo)會(huì)成為最終的目標(biāo)。make所完成的也就是這個(gè)目標(biāo)。

2.6.2規(guī)則舉例

foo.o : foo.c defs.h       # foo模塊cc -c -g foo.c

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

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

2、如果生成(或更新)foo.o文件。也就是那個(gè)cc命令,其說明了,如何生成foo.o這個(gè)文件。(當(dāng)然foo.c文件include了defs.h文件)

2.6.3規(guī)則的語(yǔ)法

targets:prerequisitescommand...

?或是這樣:?

targets:prerequisites;commandcommand...

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

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

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

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

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

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

2.7.1? 通配符

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

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

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

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

clean:rm -f *.o

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

print: *.clpr -p $?touch print

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

objects = *.o

這種用法由關(guān)鍵字“wildcard”指出,關(guān)于Makefile的關(guān)鍵字

objects := $(wildcard *.o)  

疑問:(無法通過)

object:=$(wildcard *.o)
main:$(object)cc -o main $(object)
clean:-rm -f main $(object)

三、文件搜尋

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

3.1?VPATH

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

VPATH = src:../headers

上面的的定義指定兩個(gè)目錄,“src”和“../headers”,make會(huì)按照這個(gè)順序進(jìn)行搜索。目錄由“冒號(hào)”分隔。(當(dāng)然,當(dāng)前目錄永遠(yuǎn)是最高優(yōu)先搜索的地方)

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

??? 1、vpath <pattern> <directories>

??? 為符合模式<pattern>的文件指定搜索目錄<directories>。

??? 2、vpath <pattern>

??? 清除符合模式<pattern>的文件的搜索目錄。

??? 3、vpath

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

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

vpath %.h ../headers

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

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

vpath %.c foo
vpath %   blish
vpath %.c bar

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

vpath %.c foo:bar
vpath %   blish

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

四.偽目標(biāo)

最早先的一個(gè)例子中,我們提到過一個(gè)“clean”的目標(biāo),這是一個(gè)“偽目標(biāo)”,

clean:rm *.o temp

正像我們前面例子中的“clean”一樣,即然我們生成了許多文件編譯文件,我們也應(yīng)該提供一個(gè)清除它們的“目標(biāo)”以備完整地重編譯而用。 (以“make clean”來使用該目標(biāo))因?yàn)?#xff0c;我們并不生成“clean”這個(gè)文件?!皞文繕?biāo)”并不是一個(gè)文件,只是一個(gè)標(biāo)簽,由于“偽目標(biāo)”不是文件,所以make無法生成它的依賴關(guān)系和決定它是否要執(zhí)行。我們只有通過顯示地指明這個(gè)“目標(biāo)”才能讓其生效。當(dāng)然,“偽目標(biāo)”的取名不能和文件名重名,不然其就失去了“偽目標(biāo)”的意義了。

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

.PHONY : clean

只要有這個(gè)聲明,不管是否有“clean”文件,要運(yùn)行“clean”這個(gè)目標(biāo),只有“make ?clean”這樣。于是整個(gè)過程可以這樣寫:

.PHONY: clean
clean:rm *.o temp

偽目標(biāo)一般沒有依賴的文件。但是,我們也可以為偽目標(biāo)指定所依賴的文件。偽目標(biāo)同樣可以作為“默認(rèn)目標(biāo)”,只要將其放在第一個(gè)。一個(gè)示例就是,如果你的Makefile需要一口氣生成若干個(gè)可執(zhí)行文件,但你只想簡(jiǎn)單地敲一個(gè)make完事,并且,所有的目標(biāo)文件都寫在一個(gè)Makefile中,那么你可以使用“偽目標(biāo)”這個(gè)特性:(待實(shí)際運(yùn)用,分析與不用.PHONY:all的區(qū)別)

all : prog1 prog2 prog3.PHONY : all
prog1 : prog1.o utils.occ -o prog1 prog1.o utils.o
prog2 : prog2.occ -o prog2 prog2.o
prog3 : prog3.o sort.o utils.occ -o prog3 prog3.o sort.o utils.o

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

隨便提一句,從上面的例子我們可以看出,目標(biāo)也可以成為依賴。所以,偽目標(biāo)同樣也可成為依賴??聪旅娴睦?#xff1a;

.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiffrm program
cleanobj :rm *.o
cleandiff :rm *.diff

“make clean”將清除所有要被清除的文件。“cleanobj”和“cleandiff”這兩個(gè)偽目標(biāo)有點(diǎn)像“子程序”的意思。我們可以輸入“make cleanall”和“make cleanobj”和“make cleandiff”命令來達(dá)到清除不同種類文件的目的。

五、多目標(biāo)

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

bigoutput littleoutput : text.ggenerate text.g -$(subst output,,$@) > $@

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

bigoutput : text.ggenerate text.g -big > bigoutput
littleoutput : text.ggenerate text.g -little > littleoutput

其中,-$(subst output,,$@)中的“$”表示執(zhí)行一個(gè)Makefile的函數(shù),函數(shù)名為subst,后面的為參數(shù)。關(guān)于函數(shù),將在后面講述。這里的這個(gè)函數(shù)是截取字符串的意思,“$@”表示目標(biāo)的集合,就像一個(gè)數(shù)組,“$@”依次取出目標(biāo),并執(zhí)于命令。

5.2靜態(tài)模式

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

<targets ...>: <target-pattern>: <prereq-patterns ...><commands>...
  • ????targets定義了一系列的目標(biāo)文件,可以有通配符。是目標(biāo)的一個(gè)集合。
  • ??? target-parrtern是指明了targets的模式,也就是的目標(biāo)集模式。
  • ??? prereq-parrterns是目標(biāo)的依賴模式,它對(duì)target-parrtern形成的模式再進(jìn)行一次依賴目標(biāo)的定義。

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

所以,我們的“目標(biāo)模式”或是“依賴模式”中都應(yīng)該有“%”這個(gè)字符,如果你的文件名中有“%”那么你可以使用反斜杠“\”進(jìn)行轉(zhuǎn)義,來標(biāo)明真實(shí)的“%”字符??匆粋€(gè)例子:

objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c$(CC) -c $(CFLAGS) $< -o $@

?????????????????????

上面的例子中,指明了我們的目標(biāo)從$object中獲取,“%.o”表明要所有以“.o”結(jié)尾的目標(biāo),也就是“foo.o bar.o”,也就是變量$object集合的模式,而依賴模式“%.c”則取模式“%.o”的“%”,也就是“foo bar”,并為其加下“.c”的后綴,于是,我們的依賴目標(biāo)就是“foo.c bar.c”。而命令中的“$<”和“$@”則是自動(dòng)化變量,“$<”表示所有的依賴目標(biāo)集(也就是“foo.c bar.c”),“$@”表示目標(biāo)集(也就是“foo.o bar.o”)。于是,上面的規(guī)則展開后等價(jià)于下面的規(guī)則:

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”有幾百個(gè),那種我們只要用這種很簡(jiǎn)單的“靜態(tài)模式規(guī)則”就可以寫完一堆規(guī)則,實(shí)在是太有效率了?!办o態(tài)模式規(guī)則”的用法很靈活,如果用得好,那會(huì)一個(gè)很強(qiáng)大的功能。再看一個(gè)例子:

files = foo.elc bar.o lose.o$(filter+ %.o,$(files)): %.o: %.c$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.elemacs -f batch-byte-compile $<

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

?????????????????

5.3?自動(dòng)生成依賴性

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

main.o : main.c defs.h

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

cc -M main.c

其輸出是:

main.o : main.c defs.h

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

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

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

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

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

%.d: %.c@set -e; rm -f $@;\$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \rm -f $@.$$$$

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

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

main.o : main.c defs.h

轉(zhuǎn)成:

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

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

sources=foo.c bar.c
include $(sources:.c=.d)

上述語(yǔ)句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一個(gè)替換,把變量$(sources)所有[.c]的字串都替換成[.d],關(guān)于這個(gè)“替換”的內(nèi)容,在后面我會(huì)有更為詳細(xì)的講述。當(dāng)然,你得注意次序,因?yàn)閕nclude是按次來載入文件,最先載入的[.d]文件中的目標(biāo)會(huì)成為默認(rèn)目標(biāo)。

六.書寫命令

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

我們?cè)赨NIX下可能會(huì)使用不同的Shell,但是make的命令默認(rèn)是被“/bin/sh”——UNIX的標(biāo)準(zhǔn)Shell解釋執(zhí)行的。除非你特別指定一個(gè)其它的Shell。Makefile中,“#”是注釋符,很像C/C++中的“//”,其后的本行字符都被注釋。

6.1、顯示命令

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

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

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

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

6.2、命令執(zhí)行

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

示例一:

exec:cd /home/hchenpwd

示例二:

exec:cd /home/hchen; pwd

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

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

6.3、命令出錯(cuò)

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

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

為了做到這一點(diǎn),忽略命令的出錯(cuò),我們可以在Makefile的命令行前加一個(gè)減號(hào)“-”(在Tab鍵之后),標(biāo)記為不管命令出不出錯(cuò)都認(rèn)為是成功的。如:

clean:-rm -f *.o

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

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

七、嵌套執(zhí)行make

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

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

subsystem:cd subdir && $(MAKE)

其等價(jià)于:

subsystem:$(MAKE) -C subdir

定義$(MAKE)宏變量的意思是,也許我們的make需要一些參數(shù),所以定義成一個(gè)變量比較利于維護(hù)。這兩個(gè)例子的意思都是先進(jìn)入“subdir”目錄,然后執(zhí)行make命令。

我們把這個(gè)Makefile叫做“總控Makefile”,總控Makefile的變量可以傳遞到下級(jí)的Makefile中(如果你顯示的聲明),但是不會(huì)覆蓋下層的Makefile中所定義的變量,除非指定了“-e”參數(shù)。

如果你要傳遞變量到下級(jí)Makefile中,那么你可以使用這樣的聲明:

export <variable ...>

如果你不想讓某些變量傳遞到下級(jí)Makefile中,那么你可以這樣聲明:

unexport <variable ...>

如果你要傳遞所有的變量,那么,只要一個(gè)export就行了。后面什么也不用跟,表示傳遞所有的變量。

需要注意的是,有兩個(gè)變量,一個(gè)是SHELL,一個(gè)是MAKEFLAGS,這兩個(gè)變量不管你是否export,其總是要傳遞到下層Makefile中,特別是MAKEFILES變量,其中包含了make的參數(shù)信息,如果我們執(zhí)行“總控Makefile”時(shí)有make參數(shù)或是在上層Makefile中定義了這個(gè)變量,那么MAKEFILES變量將會(huì)是這些參數(shù),并會(huì)傳遞到下層Makefile中,這是一個(gè)系統(tǒng)級(jí)的環(huán)境變量。

但是make命令中的有幾個(gè)參數(shù)并不往下傳遞,它們是“-C”,“-f”,“-h”“-o”和“-W”(有關(guān)Makefile參數(shù)的細(xì)節(jié)將在后面說明),如果你不想往下層傳遞參數(shù),那么,你可以這樣來:

subsystem:cd subdir && $(MAKE) MAKEFLAGS=

如果你定義了環(huán)境變量MAKEFLAGS,那么你得確信其中的選項(xiàng)是大家都會(huì)用到的,如果其中有“-t”,“-n”,和“-q”參數(shù),那么將會(huì)有讓你意想不到的結(jié)果,或許會(huì)讓你異常地恐慌。

還有一個(gè)在“嵌套執(zhí)行”中比較有用的參數(shù),“-w”或是“--print-directory”會(huì)在make的過程中輸出一些信息,讓你看到目前的工作目錄。比如,如果我們的下級(jí)make目錄是“/home/hchen/gnu/make”,如果我們使用“make -w”來執(zhí)行,那么當(dāng)進(jìn)入該目錄時(shí),我們會(huì)看到:

make: Entering directory `/home/hchen/gnu/make'.

而在完成下層make后離開目錄時(shí),我們會(huì)看到:

make: Leaving directory `/home/hchen/gnu/make'

當(dāng)你使用“-C”參數(shù)來指定make下層Makefile時(shí),“-w”會(huì)被自動(dòng)打開的。如果參數(shù)中有“-s”(“--slient”)或是“--no-print-directory”,那么,“-w”總是失效的。

八、定義命令包

如果Makefile中出現(xiàn)一些相同命令序列,那么我們可以為這些相同的命令序列定義一個(gè)變量。定義這種命令序列的語(yǔ)法以“define”開始,以“endef”結(jié)束,如:

define run-yacc
yacc $(firstword $^)mv y.tab.c $@
endef

這里,“run-yacc”是這個(gè)命令包的名字,其不要和Makefile中的變量重名。在“define”和“endef”中的兩行就是命令序列。這個(gè)命令包中的第一個(gè)命令是運(yùn)行Yacc程序,因?yàn)閅acc程序總是生成“y.tab.c”的文件,所以第二行的命令就是把這個(gè)文件改改名字。還是把這個(gè)命令包放到一個(gè)示例中來看看吧。

foo.c : foo.y$(run-yacc)

我們可以看見,要使用這個(gè)命令包,我們就好像使用變量一樣。在這個(gè)命令包的使用中,命令包“run-yacc”中的“$^”就是“foo.y”,“$@”就是“foo.c”(有關(guān)這種以“$”開頭的特殊變量,我們會(huì)在后面介紹),make在執(zhí)行命令包時(shí),命令包中的每個(gè)命令會(huì)被依次獨(dú)立執(zhí)行。

九.使用變量

在Makefile中的定義的變量,就像是C/C++語(yǔ)言中的宏一樣,他代表了一個(gè)文本字串,在Makefile中執(zhí)行的時(shí)候其會(huì)自動(dòng)原模原樣地展開在所使用的地方。其與C/C++所不同的是,你可以在Makefile中改變其值。在Makefile中,變量可以使用在“目標(biāo)”,“依賴目標(biāo)”,“命令”或是Makefile的其它部分中。

變量的命名字可以包含字符、數(shù)字,下劃線(可以是數(shù)字開頭),但不應(yīng)該含有“:”、“#”、“=”或是空字符(空格、回車等)。變量是大小寫敏感的,“foo”、“Foo”和“FOO”是三個(gè)不同的變量名。傳統(tǒng)的Makefile的變量名是全大寫的命名方式,但我推薦使用大小寫搭配的變量名,如:MakeFlags。這樣可以避免和系統(tǒng)的變量沖突,而發(fā)生意外的事情。

有一些變量是很奇怪字串,如“$<”、“$@”等,這些是自動(dòng)化變量,在后面介紹。

9.1變量的基礎(chǔ)

變量在聲明時(shí)需要給予初值,而在使用時(shí),需要給在變量名前加上“$”符號(hào),但最好用小括號(hào)“()”或是大括號(hào)“{}”把變量給包括起來。如果你要使用真實(shí)的“$”字符,那么你需要用“$$”來表示。

變量可以使用在許多地方,如規(guī)則中的“目標(biāo)”、“依賴”、“命令”以及新的變量中。先看一個(gè)例子:

objects = program.o foo.o utils.o
program : $(objects)cc -o program $(objects)
$(objects) : defs.h

變量會(huì)在使用它的地方精確地展開,就像C/C++中的宏一樣,例如:

foo = c
prog.o : prog.$(foo)$(foo)$(foo) -$(foo) prog.$(foo)

展開后得到:

prog.o : prog.ccc -c prog.c

當(dāng)然,千萬不要在你的Makefile中這樣干,這里只是舉個(gè)例子來表明Makefile中的變量在使用處展開的真實(shí)樣子??梢娖渚褪且粋€(gè)“替代”的原理。

另外,給變量加上括號(hào)完全是為了更加安全地使用這個(gè)變量,在上面的例子中,如果你不想給變量加上括號(hào),那也可以,但我還是強(qiáng)烈建議你給變量加上括號(hào)。

9.2、變量中的變量

在定義變量的值時(shí),我們可以使用其它變量來構(gòu)造變量的值,在Makefile中有兩種方式來在用變量定義變量的值。

先看第一種方式,也就是簡(jiǎn)單的使用“=”號(hào),在“=”左側(cè)是變量,右側(cè)是變量的值,右側(cè)變量的值可以定義在文件的任何一處,也就是說,右側(cè)中的變量不一定非要是已定義好的值,其也可以使用后面定義的值。如:

foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:echo $(foo)

我們執(zhí)行“make all”將會(huì)打出變量$(foo)的值是“Huh?”( $(foo)的值是$(bar),$(bar)的值是$(ugh),$(ugh)的值是“Huh?”)可見,變量是可以使用后面的變量來定義的。

這個(gè)功能有好的地方,也有不好的地方,好的地方是,我們可以把變量的真實(shí)值推到后面來定義,如:

CFLAGS = $(include_dirs) -Oinclude_dirs = -Ifoo -Ibar

當(dāng)“CFLAGS”在命令中被展開時(shí),會(huì)是“-Ifoo -Ibar -O”。但這種形式也有不好的地方,那就是遞歸定義,如:

CFLAGS = $(CFLAGS) -O

或:

A = $(B)
B = $(A)

這會(huì)讓make陷入無限的變量展開過程中去,當(dāng)然,我們的make是有能力檢測(cè)這樣的定義,并會(huì)報(bào)錯(cuò)。還有就是如果在變量中使用函數(shù),那么,這種方式會(huì)讓我們的make運(yùn)行時(shí)非常慢,更糟糕的是,他會(huì)使用得兩個(gè)make的函數(shù)“wildcard”和“shell”發(fā)生不可預(yù)知的錯(cuò)誤。因?yàn)槟悴粫?huì)知道這兩個(gè)函數(shù)會(huì)被調(diào)用多少次。

為了避免上面的這種方法,我們可以使用make中的另一種用變量來定義變量的方法。這種方法使用的是“:=”操作符,如:

x := foo
y := $(x) bar
x := later

其等價(jià)于:

y := foo bar
x := later

值得一提的是,這種方法,前面的變量不能使用后面的變量,只能使用前面已定義好了的變量。如果是這樣:

y := $(x) bar
x := foo

那么,y的值是“bar”,而不是“foo bar”。

上面都是一些比較簡(jiǎn)單的變量使用了,讓我們來看一個(gè)復(fù)雜的例子,其中包括了make的函數(shù)、條件表達(dá)式和一個(gè)系統(tǒng)變量“MAKELEVEL”的使用:

ifeq (0,${MAKELEVEL})
cur-dir:= $(shell pwd)
whoami:= $(shell whoami)
host-type:= $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif

關(guān)于條件表達(dá)式和函數(shù),我們?cè)诤竺嬖僬f,對(duì)于系統(tǒng)變量“MAKELEVEL”,其意思是,如果我們的make有一個(gè)嵌套執(zhí)行的動(dòng)作(參見前面的“嵌套使用make”),那么,這個(gè)變量會(huì)記錄了我們的當(dāng)前Makefile的調(diào)用層數(shù)。

下面再介紹兩個(gè)定義變量時(shí)我們需要知道的,請(qǐng)先看一個(gè)例子,如果我們要定義一個(gè)變量,其值是一個(gè)空格,那么我們可以這樣來:

nullstring :=
space := $(nullstring) # end of the line

nullstring是一個(gè)Empty變量,其中什么也沒有,而我們的space的值是一個(gè)空格。因?yàn)樵诓僮鞣挠疫吺呛茈y描述一個(gè)空格的,這里采用的技術(shù)很管用,先用一個(gè)Empty變量來標(biāo)明變量的值開始了,而后面采用“#”注釋符來表示變量定義的終止,這樣,我們可以定義出其值是一個(gè)空格的變量。請(qǐng)注意這里關(guān)于“#”的使用,注釋符“#”的這種特性值得我們注意,如果我們這樣定義一個(gè)變量:

dir := /foo/bar    # directory to put the frobs in

dir這個(gè)變量的值是“/foo/bar”,后面還跟了4個(gè)空格,如果我們這樣使用這樣變量來指定別的目錄——“$(dir)/file”那么就完蛋了。

還有一個(gè)比較有用的操作符是“?=”,先看示例:

FOO ?= bar

其含義是,如果FOO沒有被定義過,那么變量FOO的值就是“bar”,如果FOO先前被定義過,那么這條語(yǔ)將什么也不做,其等價(jià)于:

ifeq ($(origin FOO), undefined)
FOO = bar
endif

9.3、變量高級(jí)用法

這里介紹兩種變量的高級(jí)使用方法,第一種是變量值的替換。

我們可以替換變量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把變量“var”中所有以“a”字串“結(jié)尾”的“a”替換成“b”字串。這里的“結(jié)尾”意思是“空格”或是“結(jié)束符”。

還是看一個(gè)示例吧:

foo := a.o b.o c.o
bar := $(foo:.o=.c)

這個(gè)示例中,我們先定義了一個(gè)“$(foo)”變量,而第二行的意思是把“$(foo)”中所有以“.o”字串“結(jié)尾”全部替換成“.c”,所以我們的“$(bar)”的值就是“a.c b.c c.c”。

另外一種變量替換的技術(shù)是以“靜態(tài)模式”(參見前面章節(jié))定義的,如:

foo := a.o b.o c.o
bar := $(foo:%.o=%.c)

在這個(gè)例子中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。(注意,是“x=y”,而不是“x=$(y)”)

我們還可以使用更多的層次:

x = y
y = z
z = u
a := $($($(x)))

這里的$(a)的值是“u”,相關(guān)的推導(dǎo)留給讀者自己去做吧。

讓我們?cè)購(gòu)?fù)雜一點(diǎn),使用上“在變量定義中使用變量”的第一個(gè)方式,來看一個(gè)例子:

x = $(y)
y = z
z = Hello
a := $($(x))

這里的$($(x))被替換成了$($(y)),因?yàn)?(y)值是“z”,所以,最終結(jié)果是:a:=$(z),也就是“Hello”。

再?gòu)?fù)雜一點(diǎn),我們?cè)偌由虾瘮?shù):

x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))

這個(gè)例子中,“$($($(z)))”擴(kuò)展為“$($(y))”,而其再次被擴(kuò)展為“$($(subst 1,2,$(x)))”。$(x)的值是“variable1”,subst函數(shù)把“variable1”中的所有“1”字串替換成“2”字串,于是,“variable1”變成“variable2”,再取其值,所以,最終,$(a)的值就是$(variable2)的值——“Hello”。

在這種方式中,或要可以使用多個(gè)變量來組成一個(gè)變量的名字,然后再取其值:

first_second = Hello
a = first
b = second
all = $($a_$b)

這里的“$a_$b”組成了“first_second”,于是,$(all)的值就是“Hello”。

再來看看結(jié)合第一種技術(shù)的例子:

a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
sources := $($(a1)_objects:.o=.c)

這個(gè)例子中,如果$(a1)的值是“a”的話,那么,$(sources)的值就是“a.c b.c c.c”;如果$(a1)的值是“1”,那么$(sources)的值是“1.c 2.c 3.c”。

再來看一個(gè)這種技術(shù)和“函數(shù)”與“條件語(yǔ)句”一同使用的例子:

ifdef do_sort
func := sort
elsefunc := strip
endifbar := a d b g q cfoo := $($(func) $(bar))

這個(gè)示例中,如果定義了“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),調(diào)用的就是strip函數(shù)。

當(dāng)然,“把變量的值再當(dāng)成變量”這種技術(shù),同樣可以用在操作符的左邊:

dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_printlpr $($(dir)_sources)
endef

這個(gè)例子中定義了三個(gè)變量:“dir”,“foo_sources”和“foo_print”。

9.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被追加進(jìn)去了)

使用“+=”操作符,可以模擬為下面的這種例子:

objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o

所不同的是,用“+=”更為簡(jiǎn)潔。

如果變量之前沒有定義過,那么,“+=”會(huì)自動(dòng)變成“=”,如果前面有變量定義,那么“+=”會(huì)繼承于前次操作的賦值符。如果前一次的是“:=”,那么“+=”會(huì)以“:=”作為其賦值符,如:

variable := value
variable += more

等價(jià)于:

variable := value
variable := $(variable) more

但如果是這種情況:

variable = value
variable += more

由于前次的賦值符是“=”,所以“+=”也會(huì)以“=”來做為賦值,那么豈不會(huì)發(fā)生變量的遞補(bǔ)歸定義,這是很不好的,所以make會(huì)自動(dòng)為我們解決這個(gè)問題,我們不必?fù)?dān)心這個(gè)問題。

9.5、override?指示符

如果有變量是通常make的命令行參數(shù)設(shè)置的,那么Makefile中對(duì)這個(gè)變量的賦值會(huì)被忽略。如果你想在Makefile中設(shè)置這類參數(shù)的值,那么,你可以使用“override”指示符。其語(yǔ)法是:

override <variable> = <value>
override <variable> := <value>

當(dāng)然,你還可以追加:

override <variable> += <more text>

對(duì)于多行的變量定義,我們用define指示符,在define指示符前,也同樣可以使用ovveride指示符,如:

override define foobar
endef

9.6、多行變量

還有一種設(shè)置變量值的方法是使用define關(guān)鍵字。使用define關(guān)鍵字設(shè)置變量的值可以有換行,這有利于定義一系列的命令(前面我們講過“命令包”的技術(shù)就是利用這個(gè)關(guān)鍵字)。

define指示符后面跟的是變量的名字,而重起一行定義變量的值,定義是以endef關(guān)鍵字結(jié)束。其工作方式和“=”操作符一樣。變量的值可以包含函數(shù)、命令、文字,或是其它變量。因?yàn)槊钚枰訹Tab]鍵開頭,所以如果你用define定義的命令變量中沒有以[Tab]鍵開頭,那么make就不會(huì)把其認(rèn)為是命令。

下面的這個(gè)示例展示了define的用法:

define two-linesecho fooecho $(bar)
endef

9.7、環(huán)境變量

make運(yùn)行時(shí)的系統(tǒng)環(huán)境變量可以在make開始運(yùn)行時(shí)被載入到Makefile文件中,但是如果Makefile中已定義了這個(gè)變量,或是這個(gè)變量由make命令行帶入,那么系統(tǒng)的環(huán)境變量的值將被覆蓋。(如果make指定了“-e”參數(shù),那么,系統(tǒng)環(huán)境變量將覆蓋Makefile中定義的變量)

因此,如果我們?cè)诃h(huán)境變量中設(shè)置了“CFLAGS”環(huán)境變量,那么我們就可以在所有的Makefile中使用這個(gè)變量了。這對(duì)于我們使用統(tǒng)一的編譯參數(shù)有比較大的好處。如果Makefile中定義了CFLAGS,那么則會(huì)使用Makefile中的這個(gè)變量,如果沒有定義則使用系統(tǒng)環(huán)境變量的值,一個(gè)共性和個(gè)性的統(tǒng)一,很像“全局變量”和“局部變量”的特性。

當(dāng)make嵌套調(diào)用時(shí)(參見前面的“嵌套調(diào)用”章節(jié)),上層Makefile中定義的變量會(huì)以系統(tǒng)環(huán)境變量的方式傳遞到下層的Makefile中。當(dāng)然,默認(rèn)情況下,只有通過命令行設(shè)置的變量會(huì)被傳遞。而定義在文件中的變量,如果要向下層Makefile傳遞,則需要使用export關(guān)鍵字來聲明。(參見前面章節(jié))

當(dāng)然,我并不推薦把許多的變量都定義在系統(tǒng)環(huán)境中,這樣,在我們執(zhí)行不用的Makefile時(shí),擁有的是同一套系統(tǒng)變量,這可能會(huì)帶來更多的麻煩。

9.8、目標(biāo)變量

前面我們所講的在Makefile中定義的變量都是“全局變量”,在整個(gè)文件,我們都可以訪問這些變量。當(dāng)然,“自動(dòng)化變量”除外,如“$<”等這種類量的自動(dòng)化變量就屬于“規(guī)則型變量”,這種變量的值依賴于規(guī)則的目標(biāo)和依賴目標(biāo)的定義。

當(dāng)然,我樣同樣可以為某個(gè)目標(biāo)設(shè)置局部變量,這種變量被稱為“Target-specific Variable”,它可以和“全局變量”同名,因?yàn)樗淖饔梅秶辉谶@條規(guī)則以及連帶規(guī)則中,所以其值也只在作用范圍內(nèi)有效。而不會(huì)影響規(guī)則鏈以外的全局變量的值。

其語(yǔ)法是:

<target ...> : <variable-assignment>
<target ...> : overide <variable-assignment>

<variable-assignment>可以是前面講過的各種賦值表達(dá)式,如“=”、“:=”、“+=”或是“?=”。第二個(gè)語(yǔ)法是針對(duì)于make命令行帶入的變量,或是系統(tǒng)環(huán)境變量。

這個(gè)特性非常的有用,當(dāng)我們?cè)O(shè)置了這樣一個(gè)變量,這個(gè)變量會(huì)作用到由這個(gè)目標(biāo)所引發(fā)的所有的規(guī)則中去。如:

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

在這個(gè)示例中,不管全局的$(CFLAGS)的值是什么,在prog目標(biāo),以及其所引發(fā)的所有規(guī)則中(prog.o foo.o bar.o的規(guī)則),$(CFLAGS)的值都是“-g”

9.9、模式變量

在GNU的make中,還支持模式變量(Pattern-specific Variable),通過上面的目標(biāo)變量中,我們知道,變量可以定義在某個(gè)目標(biāo)上。模式變量的好處就是,我們可以給定一種“模式”,可以把變量定義在符合這種模式的所有目標(biāo)上。

我們知道,make的“模式”一般是至少含有一個(gè)“%”的,所以,我們可以以如下方式給所有以[.o]結(jié)尾的目標(biāo)定義目標(biāo)變量:

%.o : CFLAGS = -O

同樣,模式變量的語(yǔ)法和“目標(biāo)變量”一樣:

<pattern ...> : <variable-assignment><pattern ...> : override <variable-assignment>

override同樣是針對(duì)于系統(tǒng)環(huán)境傳入的變量,或是make命令行指定的變量。

十.使用條件判斷

使用條件判斷,可以讓make根據(jù)運(yùn)行時(shí)的不同情況選擇不同的執(zhí)行分支。條件表達(dá)式可以是比較變量的值,或是比較變量和常量的值。

10.1、示例

下面的例子,判斷$(CC)變量是否“gcc”,如果是的話,則使用GNU函數(shù)編譯目標(biāo)。

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

可見,在上面示例的這個(gè)規(guī)則中,目標(biāo)“foo”可以根據(jù)變量“$(CC)”值來選取不同的函數(shù)庫(kù)來編譯程序。我們可以從上面的示例中看到三個(gè)關(guān)鍵字:ifeq、else和endif。ifeq的意思表示條件語(yǔ)句的開始,并指定一個(gè)條件表達(dá)式,表達(dá)式包含兩個(gè)參數(shù),以逗號(hào)分隔,表達(dá)式以圓括號(hào)括起。else表示條件表達(dá)式為假的情況。endif表示一個(gè)條件語(yǔ)句的結(jié)束,任何一個(gè)條件表達(dá)式都應(yīng)該以endif結(jié)束。

當(dāng)我們的變量$(CC)值是“gcc”時(shí),目標(biāo)foo的規(guī)則是:

foo: $(objects)$(CC) -o foo $(objects) $(libs_for_gcc)

而當(dāng)我們的變量$(CC)值不是“gcc”時(shí)(比如“cc”),目標(biāo)foo的規(guī)則是:

foo: $(objects)$(CC) -o foo $(objects) $(normal_libs)

當(dāng)然,我們還可以把上面的那個(gè)例子寫得更簡(jiǎn)潔一些:

libs_for_gcc = -lgnu
normal_libs =
ifeq ($(CC),gcc)libs=$(libs_for_gcc)
elselibs=$(normal_libs)
endif
foo: $(objects)$(CC) -o foo $(objects) $(libs)

10.2、語(yǔ)法

條件表達(dá)式的語(yǔ)法為:

<conditional-directive>
<text-if-true>
endif

以及:

<conditional-directive>
<text-if-true>
else
<text-if-false>
endif

其中<conditional-directive>表示條件關(guān)鍵字,如“ifeq”。這個(gè)關(guān)鍵字有四個(gè)。

第一個(gè)是我們前面所見過的“ifeq”

ifeq (<arg1>, <arg2>) 
ifeq '<arg1>' '<arg2>' 
ifeq "<arg1>" "<arg2>" 
ifeq "<arg1>" '<arg2>' 
ifeq '<arg1>' "<arg2>" 

比較參數(shù)“arg1”和“arg2”的值是否相同。當(dāng)然,參數(shù)中我們還可以使用make的函數(shù)。如:

ifeq ($(strip $(foo)),)
<text-if-empty>
endif

這個(gè)示例中使用了“strip”函數(shù),如果這個(gè)函數(shù)的返回值是空(Empty),那么<text-if-empty>就生效。

第二個(gè)條件關(guān)鍵字是“ifneq”。語(yǔ)法是:

ifneq (<arg1>, <arg2>) 
ifneq '<arg1>' '<arg2>' 
ifneq "<arg1>" "<arg2>" 
ifneq "<arg1>" '<arg2>' 
ifneq '<arg1>' "<arg2>" 

其比較參數(shù)“arg1”和“arg2”的值是否相同,如果不同,則為真。和“ifeq”類似。

第三個(gè)條件關(guān)鍵字是“ifdef”。語(yǔ)法是:

如果變量<variable-name>的值非空,那到表達(dá)式為真。否則,表達(dá)式為假。當(dāng)然,<variable-name>同樣可以是一個(gè)函數(shù)的返回值。注意,ifdef只是測(cè)試一個(gè)變量是否有值,其并不會(huì)把變量擴(kuò)展到當(dāng)前位置。還是來看兩個(gè)例子:

示例一:

bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif

示例二:

foo =
ifdef foo
frobozz = yes
else
frobozz = no
endif

第一個(gè)例子中,“$(frobozz)”值是“yes”,第二個(gè)則是“no”。

第四個(gè)條件關(guān)鍵字是“ifndef”。其語(yǔ)法是:

ifndef <variable-name>

這個(gè)我就不多說了,和“ifdef”是相反的意思。

在<conditional-directive>這一行上,多余的空格是被允許的,但是不能以[Tab]鍵做為開始(不然就被認(rèn)為是命令)。而注釋符“#”同樣也是安全的。“else”和“endif”也一樣,只要不是以[Tab]鍵開始就行了。

特別注意的是,make是在讀取Makefile時(shí)就計(jì)算條件表達(dá)式的值,并根據(jù)條件表達(dá)式的值來選擇語(yǔ)句,所以,你最好不要把自動(dòng)化變量(如“$@”等)放入條件表達(dá)式中,因?yàn)樽詣?dòng)化變量是在運(yùn)行時(shí)才有的。

而且,為了避免混亂,make不允許把整個(gè)條件語(yǔ)句分成兩部分放在不同的文件中。

十一.使用函數(shù)

在Makefile中可以使用函數(shù)來處理變量,從而讓我們的命令或是規(guī)則更為的靈活和具有智能。make所支持的函數(shù)也不算很多,不過已經(jīng)足夠我們的操作了。函數(shù)調(diào)用后,函數(shù)的返回值可以當(dāng)做變量來使用。


11.1、函數(shù)的調(diào)用語(yǔ)法

函數(shù)調(diào)用,很像變量的使用,也是以“$”來標(biāo)識(shí)的,其語(yǔ)法如下:

$(<function> <arguments>)

或是

${<function> <arguments>}

這里,<function>就是函數(shù)名,make支持的函數(shù)不多。<arguments>是函數(shù)的參數(shù),參數(shù)間以逗號(hào)“,”分隔,而函數(shù)名和參數(shù)之間以“空格”分隔。函數(shù)調(diào)用以“$”開頭,以圓括號(hào)或花括號(hào)把函數(shù)名和參數(shù)括起。感覺很像一個(gè)變量,是不是?函數(shù)中的參數(shù)可以使用變量,為了風(fēng)格的統(tǒng)一,函數(shù)和變量的括號(hào)最好一樣,如使用“$(subst a,b,$(x))”這樣的形式,而不是“$(subst a,b,${x})”的形式。因?yàn)榻y(tǒng)一會(huì)更清楚,也會(huì)減少一些不必要的麻煩。

還是來看一個(gè)示例:

comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))

在這個(gè)示例中,$(comma)的值是一個(gè)逗號(hào)。$(space)使用了$(empty)定義了一個(gè)空格,$(foo)的值是“a b c”,$(bar)的定義用,調(diào)用了函數(shù)“subst”,這是一個(gè)替換函數(shù),這個(gè)函數(shù)有三個(gè)參數(shù),第一個(gè)參數(shù)是被替換字串,第二個(gè)參數(shù)是替換字串,第三個(gè)參數(shù)是替換操作作用的字串。這個(gè)函數(shù)也就是把$(foo)中的空格替換成逗號(hào),所以$(bar)的值是“a,b,c”。

11.2.字符串處理函數(shù)

$(subst <from>,<to>,<text>)?

名稱:字符串替換函數(shù)——subst。
功能:把字串<text>中的<from>字符串替換成<to>? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 返回:函數(shù)返回被替換過后的字符串。

示例:

$(subst ee,EE,feet on the street)

?把“feet on the street”中的“ee”替換成“EE”,返回結(jié)果是“fEEt on the strEEt”。

$(patsubst <pattern>,<replacement>,<text>)?

名稱:模式字符串替換函數(shù)——patsubst。
功能:查找<text>中的單詞(單詞以“空格”、“Tab”或“回車”“換行”分隔)是否符合模式<pattern>,如果匹配的話,則以? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <replacement>替換。這里,<pattern>可以包括通配符“%”,表示任意長(zhǎng)度的字串。如果<replacement>中也包含“%”,那么,? ? ? ? ? ? ? ? ? ? ? ?<replacement>中的這個(gè)“%”將是<pattern>中的那個(gè)“%”所代表的字串。(可以用“\”來轉(zhuǎn)義,以“\%”來表示真實(shí)含義的“%”字符)
?返回:函數(shù)返回被替換過后的字符串。

示例:

$(patsubst %.c,%.o,x.c.c bar.c)

把字串“x.c.c bar.c”符合模式[%.c]的單詞替換成[%.o],返回結(jié)果是“x.c.o bar.o”

備注:這和我們前面“變量章節(jié)”說過的相關(guān)知識(shí)有點(diǎn)相似。如:

$(var:<pattern>=<replacement>)”

相當(dāng)于

$(patsubst <pattern>,<replacement>,$(var))

$(patsubst <pattern>,<replacement>,$(var))

則相當(dāng)于

$(patsubst %<suffix>,%<replacement>,$(var))

例如有:

objects = foo.o bar.o baz.o

那么

$(objects:.o=.c)
//是一樣的
$(patsubst %.o,%.c,$(objects))

$(strip <string>)

名稱:去空格函數(shù)——strip。
功能:去掉<string>字串中開頭和結(jié)尾的空字符。
返回:返回被去掉空格的字符串值。
示例:

$(strip a b c )

把字串“a b c ”去到開頭和結(jié)尾的空格,結(jié)果是“a b c”。

$(findstring <find>,<in>)

名稱:查找字符串函數(shù)——findstring。
功能:在字串<in>中查找<find>字串。
返回:如果找到,那么返回<find>,否則返回空字符串。

示例:

$(findstring a,a b c)
$(findstring a,b c)

第一個(gè)函數(shù)返回“a”字符串,第二個(gè)返回“”字符串(空字符串)

$(filter <pattern...>,<text>)

名稱:過濾函數(shù)——filter。
功能:以<pattern>模式過濾<text>字符串中的單詞,保留符合模式<pattern>的單詞??梢杂卸鄠€(gè)模式。
返回:返回符合模式<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>)

名稱:反過濾函數(shù)——filter-out。
功能:以<pattern>模式過濾<text>字符串中的單詞,去除符合模式<pattern>的單詞??梢杂卸鄠€(gè)模式。
返回:返回不符合模式<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>)

名稱:排序函數(shù)——sort。
功能:給字符串<list>中的單詞排序(升序)。
返回:返回排序后的字符串。
示例:

$(sort foo bar lose)

返回“bar foo lose” 。備注:sort函數(shù)會(huì)去掉<list>中相同的單詞。

$(word <n>,<text>)

名稱:取單詞函數(shù)——word。
功能:取字符串<text>中第<n>個(gè)單詞。(從一開始)
返回:返回字符串<text>中第<n>個(gè)單詞。如果<n>比<text>中的單詞數(shù)要大,那么返回空字符串。

示例:

$(word 2, foo bar baz)

返回值是“bar”。

$(wordlist <s>,<e>,<text>)

名稱:取單詞串函數(shù)——wordlist。
功能:從字符串<text>中取從<s>開始到<e>的單詞串。<s>和<e>是一個(gè)數(shù)字。
返回:返回字符串<text>中從<s>到<e>的單詞字串。如果<s>比<text>中的單詞數(shù)要大,那么返回空字符串。如果<e>大于<text>的單詞數(shù),那么返回從<s>開始,到<text>結(jié)束的單詞串。

示例:

$(wordlist 2, 3, foo bar baz)

返回值是“bar baz”。

$(words <text>)

名稱:單詞個(gè)數(shù)統(tǒng)計(jì)函數(shù)——words。
功能:統(tǒng)計(jì)<text>中字符串中的單詞個(gè)數(shù)。
返回:返回<text>中的單詞數(shù)。

示例:

$(words, foo bar baz)

返回值是“3”。 備注:如果我們要取<text>中最后的一個(gè)單詞,我們可以這樣:$(word $(words <text>),<text>)。

$(firstword <text>)

名稱:首單詞函數(shù)——firstword。
功能:取字符串<text>中的第一個(gè)單詞。
返回:返回字符串<text>的第一個(gè)單詞。

示例:

$(firstword foo bar)

返回值是“foo”。備注:這個(gè)函數(shù)可以用word函數(shù)來實(shí)現(xiàn):$(word 1,<text>)。

以上,是所有的字符串操作函數(shù),如果搭配混合使用,可以完成比較復(fù)雜的功能。這里,舉一個(gè)現(xiàn)實(shí)中應(yīng)用的例子。我們知道,make使用“VPATH”變量來指定“依賴文件”的搜索路徑。于是,我們可以利用這個(gè)搜索路徑來指定編譯器對(duì)頭文件的搜索路徑參數(shù)CFLAGS,如:

override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))

如果我們的“$(VPATH)”值是“src:../headers”,那么“$(patsubst %,-I%,$(subst :, ,$(VPATH)))”將返回“-Isrc -I../headers”,這正是cc或gcc搜索頭文件路徑的參數(shù)。

11.3、文件名操作函數(shù)

下面我們要介紹的函數(shù)主要是處理文件名的。每個(gè)函數(shù)的參數(shù)字符串都會(huì)被當(dāng)做一個(gè)或是一系列的文件名來對(duì)待。

$(dir <names...>)

名稱:取目錄函數(shù)——dir。
功能:從文件名序列<names>中取出目錄部分。目錄部分是指最后一個(gè)反斜杠(“/”)之前的部分。如果沒有反斜杠,那么返回“./”。
返回:返回文件名序列<names>的目錄部分。

示例:

$(dir src/foo.c hacks)

返回值是“src/ ./”。

$(notdir <names...>)

名稱:取文件函數(shù)——notdir。
功能:從文件名序列<names>中取出非目錄部分。非目錄部分是指最后一個(gè)反斜杠(“/”)之后的部分。
返回:返回文件名序列<names>的非目錄部分。

$(notdir src/foo.c hacks)

返回值是“foo.c hacks”。

$(suffix <names...>)

名稱:取后綴函數(shù)——suffix。
功能:從文件名序列<names>中取出各個(gè)文件名的后綴。
返回:返回文件名序列<names>的后綴序列,如果文件沒有后綴,則返回空字串。

示例:

$(basename src/foo.c src-1.0/bar.c hacks)

返回值是“src/foo src-1.0/bar hacks”。

$(addsuffix <suffix>,<names...>)

名稱:加后綴函數(shù)——addsuffix。
功能:把后綴<suffix>加到<names>中的每個(gè)單詞后面。
返回:返回加過后綴的文件名序列。

示例:

$(addsuffix .c,foo bar)

返回值是“foo.c bar.c”。

$(addprefix <prefix>,<names...>)

名稱:加前綴函數(shù)——addprefix。
功能:把前綴<prefix>加到<names>中的每個(gè)單詞后面。
返回:返回加過前綴的文件名序列。

示例:

$(addprefix <prefix>,<names...>) 

返回值是“src/foo src/bar”。

$(join <list1>,<list2>)

功能:把<list2>中的單詞對(duì)應(yīng)地加到<list1>的單詞后面。如果<list1>的單詞個(gè)數(shù)要比<list2>的多,那么,<list1>中的多出來的單詞將保持原樣。如果<list2>的單詞個(gè)數(shù)要比<list1>多,那么,<list2>多出來的單詞將被復(fù)制到<list2>中。
返回:返回連接過后的字符串。

示例:

$(join aaa bbb , 111 222 333)

返回值是“aaa111 bbb222 333”。

11.4、foreach 函數(shù)

foreach函數(shù)和別的函數(shù)非常的不一樣。因?yàn)檫@個(gè)函數(shù)是用來做循環(huán)用的,Makefile中的foreach函數(shù)幾乎是仿照于Unix標(biāo)準(zhǔn)Shell(/bin/sh)中的for語(yǔ)句,或是C-Shell(/bin/csh)中的foreach語(yǔ)句而構(gòu)建的。它的語(yǔ)法是:

$(foreach <var>,<list>,<text>)

這個(gè)函數(shù)的意思是,把參數(shù)<list>中的單詞逐一取出放到參數(shù)<var>所指定的變量中,然后再執(zhí)行<text>所包含的表達(dá)式。每一次<text>會(huì)返回一個(gè)字符串,循環(huán)過程中,<text>的所返回的每個(gè)字符串會(huì)以空格分隔,最后當(dāng)整個(gè)循環(huán)結(jié)束時(shí),<text>所返回的每個(gè)字符串所組成的整個(gè)字符串(以空格分隔)將會(huì)是foreach函數(shù)的返回值。

所以,<var>最好是一個(gè)變量名,<list>可以是一個(gè)表達(dá)式,而<text>中一般會(huì)使用<var>這個(gè)參數(shù)來依次枚舉<list>中的單詞。舉個(gè)例子:

names := a b c d
files := $(foreach n,$(names),$(n).o)

上面的例子中,$(name)中的單詞會(huì)被挨個(gè)取出,并存到變量“n”中,“$(n).o”每次根據(jù)“$(n)”計(jì)算出一個(gè)值,這些值以空格分隔,最后作為foreach函數(shù)的返回,所以,$(files)的值是“a.o b.o c.o d.o”。

注意,foreach中的<var>參數(shù)是一個(gè)臨時(shí)的局部變量,foreach函數(shù)執(zhí)行完后,參數(shù)<var>的變量將不在作用,其作用域只在foreach函數(shù)當(dāng)中。

11.5、if 函數(shù)

if函數(shù)很像GNU的make所支持的條件語(yǔ)句——ifeq(參見前面所述的章節(jié)),if函數(shù)的語(yǔ)法是:

    $(if <condition>,<then-part>) 

或是

    $(if <condition>,<then-part>,<else-part>)

可見,if函數(shù)可以包含“else”部分,或是不含。即if函數(shù)的參數(shù)可以是兩個(gè),也可以是三個(gè)。<condition>參數(shù)是if的表達(dá)式,如果其返回的為非空字符串,那么這個(gè)表達(dá)式就相當(dāng)于返回真,于是,<then-part>會(huì)被計(jì)算,否則<else-part>會(huì)被計(jì)算。

而if函數(shù)的返回值是,如果<condition>為真(非空字符串),那個(gè)<then-part>會(huì)是整個(gè)函數(shù)的返回值,如果<condition>為假(空字符串),那么<else-part>會(huì)是整個(gè)函數(shù)的返回值,此時(shí)如果<else-part>沒有被定義,那么,整個(gè)函數(shù)返回空字串。

所以,<then-part>和<else-part>只會(huì)有一個(gè)被計(jì)算。

11.6.call函數(shù)

call函數(shù)是唯一一個(gè)可以用來創(chuàng)建新的參數(shù)化的函數(shù)。你可以寫一個(gè)非常復(fù)雜的表達(dá)式,這個(gè)表達(dá)式中,你可以定義許多參數(shù),然后你可以用call函數(shù)來向這個(gè)表達(dá)式傳遞參數(shù)。其語(yǔ)法是:

$(call <expression>,<parm1>,<parm2>,<parm3>...)

當(dāng)make執(zhí)行這個(gè)函數(shù)時(shí),<expression>參數(shù)中的變量,如$(1),$(2),$(3)等,會(huì)被參數(shù)<parm1>,<parm2>,<parm3>依次取代。而<expression>的返回值就是call函數(shù)的返回值。例如:

reverse =  $(1) $(2)
foo = $(call reverse,a,b)

那么,foo的值就是“a b”。當(dāng)然,參數(shù)的次序是可以自定義的,不一定是順序的,如:

reverse =  $(2) $(1)
foo = $(call reverse,a,b)

此時(shí)的foo的值就是“b a”。

11.7、origin函數(shù)

origin函數(shù)不像其它的函數(shù),他并不操作變量的值,他只是告訴你你的這個(gè)變量是哪里來的?其語(yǔ)法是:

$(origin <variable>)

注意,<variable>是變量的名字,不應(yīng)該是引用。所以你最好不要在<variable>中使用“$”字符。Origin函數(shù)會(huì)以其返回值來告訴你這個(gè)變量的“出生情況”,下面,是origin函數(shù)的返回值:

“undefined”:如果<variable>從來沒有定義過,origin函數(shù)返回這個(gè)值“undefined”。

“default”:?如果<variable>是一個(gè)默認(rèn)的定義,比如“CC”這個(gè)變量,這種變量我們將在后面講述。

“environment”:如果<variable>是一個(gè)環(huán)境變量,并且當(dāng)Makefile被執(zhí)行時(shí),“-e”參數(shù)沒有被打開。

“file”:如果<variable>這個(gè)變量被定義在Makefile中。

“command line”:如果<variable>這個(gè)變量是被命令行定義的。

“override”:?如果<variable>是被override指示符重新定義的。

“automatic”:如果<variable>是一個(gè)命令運(yùn)行中的自動(dòng)化變量。關(guān)于自動(dòng)化變量將在后面講述。

這些信息對(duì)于我們編寫Makefile是非常有用的,例如,假設(shè)我們有一個(gè)Makefile其包了一個(gè)定義文件Make.def,在Make.def中定義了一個(gè)變量“bletch”,而我們的環(huán)境中也有一個(gè)環(huán)境變量“bletch”,此時(shí),我們想判斷一下,如果變量來源于環(huán)境,那么我們就把之重定義了,如果來源于Make.def或是命令行等非環(huán)境的,那么我們就不重新定義它。于是,在我們的Makefile中,我們可以這樣寫:

ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf, gag, etc.
endif
endif

當(dāng)然,你也許會(huì)說,使用override關(guān)鍵字不就可以重新定義環(huán)境中的變量了嗎?為什么需要使用這樣的步驟?是的,我們用override是可以達(dá)到這樣的效果,可是override過于粗暴,它同時(shí)會(huì)把從命令行定義的變量也覆蓋了,而我們只想重新定義環(huán)境傳來的,而不想重新定義命令行傳來的。

11.8、shell函數(shù)

shell函數(shù)也不像其它的函數(shù)。顧名思義,它的參數(shù)應(yīng)該就是操作系統(tǒng)Shell的命令。它和反引號(hào)“`”是相同的功能。這就是說,shell函數(shù)把執(zhí)行操作系統(tǒng)命令后的輸出作為函數(shù)返回。于是,我們可以用操作系統(tǒng)命令以及字符串處理命令awk,sed等等命令來生成一個(gè)變量,如:

contents := $(shell cat foo)
files := $(shell echo *.c)

注意,這個(gè)函數(shù)會(huì)新生成一個(gè)Shell程序來執(zhí)行命令,所以你要注意其運(yùn)行性能,如果你的Makefile中有一些比較復(fù)雜的規(guī)則,并大量使用了這個(gè)函數(shù),那么對(duì)于你的系統(tǒng)性能是有害的。特別是Makefile的隱晦的規(guī)則可能會(huì)讓你的shell函數(shù)執(zhí)行的次數(shù)比你想像的多得多。

11.9、控制make的函數(shù)

make提供了一些函數(shù)來控制make的運(yùn)行。通常,你需要檢測(cè)一些運(yùn)行Makefile時(shí)的運(yùn)行時(shí)信息,并且根據(jù)這些信息來決定,你是讓make繼續(xù)執(zhí)行,還是停止。

$(error <text ...>)

產(chǎn)生一個(gè)致命的錯(cuò)誤,<text ...>是錯(cuò)誤信息。注意,error函數(shù)不會(huì)在一被使用就會(huì)產(chǎn)生錯(cuò)誤信息,所以如果你把其定義在某個(gè)變量中,并在后續(xù)的腳本中使用這個(gè)變量,那么也是可以的。例如:

示例一:

ifdef ERROR_001
$(error error is $(ERROR_001))
endif

示例二:

ERR = $(error found an error!).PHONY: err
err: ; $(ERR)

示例一會(huì)在變量ERROR_001定義了后執(zhí)行時(shí)產(chǎn)生error調(diào)用,而示例二則在目錄err被執(zhí)行時(shí)才發(fā)生error調(diào)用。

十二.make 的運(yùn)行

一般來說,最簡(jiǎn)單的就是直接在命令行下輸入make命令,make命令會(huì)找當(dāng)前目錄的makefile來執(zhí)行,一切都是自動(dòng)的。但也有時(shí)你也許只想讓make重編譯某些文件,而不是整個(gè)工程,而又有的時(shí)候你有幾套編譯規(guī)則,你想在不同的時(shí)候使用不同的編譯規(guī)則,等等。本章節(jié)就是講述如何使用make命令的。

12.1、make的退出碼

make命令執(zhí)行后有三個(gè)退出碼:

? ? ? ? ? ? ? ? ? ? ? ? ?0 —— 表示成功執(zhí)行。
? ? ? ? ? ? ? ? ? ? ? ? ?1 —— 如果make運(yùn)行時(shí)出現(xiàn)任何錯(cuò)誤,其返回1。
? ? ? ? ? ? ? ? ? ? ? ? ?2 —— 如果你使用了make的“-q”選項(xiàng),并且make使得一些目標(biāo)不需要更新,那么返回2。

Make的相關(guān)參數(shù)我們會(huì)在后續(xù)章節(jié)中講述。

12.2、指定Makefile

前面我們說過,GNU make找尋默認(rèn)的Makefile的規(guī)則是在當(dāng)前目錄下依次找三個(gè)文件——“GNUmakefile”、“makefile”和“Makefile”。其按順序找這三個(gè)文件,一旦找到,就開始讀取這個(gè)文件并執(zhí)行。

當(dāng)前,我們也可以給make命令指定一個(gè)特殊名字的Makefile。要達(dá)到這個(gè)功能,我們要使用make的“-f”或是“--file”參數(shù)(“--makefile”參數(shù)也行)。例如,我們有個(gè)makefile的名字是“hchen.mk”,那么,我們可以這樣來讓make來執(zhí)行這個(gè)文件:

make –f hchen.mk

如果在make的命令行是,你不只一次地使用了“-f”參數(shù),那么,所有指定的makefile將會(huì)被連在一起傳遞給make執(zhí)行。

12.3、指定目標(biāo)

一般來說,make的最終目標(biāo)是makefile中的第一個(gè)目標(biāo),而其它目標(biāo)一般是由這個(gè)目標(biāo)連帶出來的。這是make的默認(rèn)行為。當(dāng)然,一般來說,你的makefile中的第一個(gè)目標(biāo)是由許多個(gè)目標(biāo)組成,你可以指示make,讓其完成你所指定的目標(biāo)。要達(dá)到這一目的很簡(jiǎn)單,需在make命令后直接跟目標(biāo)的名字就可以完成(如前面提到的“make clean”形式)

任何在makefile中的目標(biāo)都可以被指定成終極目標(biāo),但是除了以“-”打頭,或是包含了“=”的目標(biāo),因?yàn)橛羞@些字符的目標(biāo),會(huì)被解析成命令行參數(shù)或是變量。甚至沒有被我們明確寫出來的目標(biāo)也可以成為make的終極目標(biāo),也就是說,只要make可以找到其隱含規(guī)則推導(dǎo)規(guī)則,那么這個(gè)隱含目標(biāo)同樣可以被指定成終極目標(biāo)。

有一個(gè)make的環(huán)境變量叫“MAKECMDGOALS”,這個(gè)變量中會(huì)存放你所指定的終極目標(biāo)的列表,如果在命令行上,你沒有指定目標(biāo),那么,這個(gè)變量是空值。這個(gè)變量可以讓你使用在一些比較特殊的情形下。比如下面的例子:

sources = foo.c bar.c
ifneq ( $(MAKECMDGOALS),clean)
include $(sources:.c=.d)
endif

基于上面的這個(gè)例子,只要我們輸入的命令不是“make clean”,那么makefile會(huì)自動(dòng)包含“foo.d”和“bar.d”這兩個(gè)makefile。

使用指定終極目標(biāo)的方法可以很方便地讓我們編譯我們的程序,例如下面這個(gè)例子:

    .PHONY: all
all: prog1 prog2 prog3 prog4

從這個(gè)例子中,我們可以看到,這個(gè)makefile中有四個(gè)需要編譯的程序——“prog1”, “prog2”, “prog3”和 “prog4”,我們可以使用“make all”命令來編譯所有的目標(biāo)(如果把a(bǔ)ll置成第一個(gè)目標(biāo),那么只需執(zhí)行“make”),我們也可以使用“make prog2”來單獨(dú)編譯目標(biāo)“prog2”。

即然make可以指定所有makefile中的目標(biāo),那么也包括“偽目標(biāo)”,于是我們可以根據(jù)這種性質(zhì)來讓我們的makefile根據(jù)指定的不同的目標(biāo)來完成不同的事。在Unix世界中,軟件發(fā)布時(shí),特別是GNU這種開源軟件的發(fā)布時(shí),其makefile都包含了編譯、安裝、打包等功能。我們可以參照這種規(guī)則來書寫我們的makefile中的目標(biāo)。

“all”:這個(gè)偽目標(biāo)是所有目標(biāo)的目標(biāo),其功能一般是編譯所有的目標(biāo)。
“clean”:?這個(gè)偽目標(biāo)功能是刪除所有被make創(chuàng)建的文件。
“install”:?這個(gè)偽目標(biāo)功能是安裝已編譯好的程序,其實(shí)就是把目標(biāo)執(zhí)行文件拷貝到指定的目標(biāo)中去。
“print”:這個(gè)偽目標(biāo)的功能是例出改變過的源文件。
“tar”:這個(gè)偽目標(biāo)功能是把源程序打包備份。也就是一個(gè)tar文件。
“dist”:這個(gè)偽目標(biāo)功能是創(chuàng)建一個(gè)壓縮文件,一般是把tar文件壓成Z文件?;蚴莋z文件。
“TAGS”:這個(gè)偽目標(biāo)功能是更新所有的目標(biāo),以備完整地重編譯使用。
“check”和“test”:這兩個(gè)偽目標(biāo)一般用來測(cè)試makefile的流程。

當(dāng)然一個(gè)項(xiàng)目的makefile中也不一定要書寫這樣的目標(biāo),這些東西都是GNU的東西,但是我想,GNU搞出這些東西一定有其可取之處(等你的UNIX下的程序文件一多時(shí)你就會(huì)發(fā)現(xiàn)這些功能很有用了),這里只不過是說明了,如果你要書寫這種功能,最好使用這種名字命名你的目標(biāo),這樣規(guī)范一些,規(guī)范的好處就是——不用解釋,大家都明白。而且如果你的makefile中有這些功能,一是很實(shí)用,二是可以顯得你的makefile很專業(yè)(不是那種初學(xué)者的作品)。

12.4、檢查規(guī)則

有時(shí)候,我們不想讓我們的makefile中的規(guī)則執(zhí)行起來,我們只想檢查一下我們的命令,或是執(zhí)行的序列。于是我們可以使用make命令的下述參數(shù):

不執(zhí)行參數(shù),這些參數(shù)只是打印命令,不管目標(biāo)是否更新,把規(guī)則和連帶規(guī)則下的命令打印出來,但不執(zhí)行,這些參數(shù)對(duì)于我們調(diào)試makefile很有用處:?“-n”、?“--just-print”、?“--dry-run”、“--recon”

這個(gè)參數(shù)的意思就是把目標(biāo)文件的時(shí)間更新,但不更改目標(biāo)文件。也就是說,make假裝編譯目標(biāo),但不是真正的編譯目標(biāo),只是把目標(biāo)變成已編譯過的狀態(tài):“-t”、?“--touch”

這個(gè)參數(shù)的行為是找目標(biāo)的意思,也就是說,如果目標(biāo)存在,那么其什么也不會(huì)輸出,當(dāng)然也不會(huì)執(zhí)行編譯,如果目標(biāo)不存在,其會(huì)打印出一條出錯(cuò)信息:?“-q”、?“--question”

這個(gè)參數(shù)需要指定一個(gè)文件。一般是是源文件(或依賴文件),Make會(huì)根據(jù)規(guī)則推導(dǎo)來運(yùn)行依賴于這個(gè)文件的命令,一般來說,可以和“-n”參數(shù)一同使用,來查看這個(gè)依賴文件所發(fā)生的規(guī)則命令:?“-W <file>”、?“--what-if=<file>”、“--assume-new=<file>”、“--new-file=<file>”

12.5、make的參數(shù)

下面列舉了所有GNU make 3.80版的參數(shù)定義。其它版本和產(chǎn)商的make大同小異,不過其它產(chǎn)商的make的具體參數(shù)還是請(qǐng)參考各自的產(chǎn)品文檔。

這兩個(gè)參數(shù)的作用是忽略和其它版本make的兼容性:“-b”、“-m”

認(rèn)為所有的目標(biāo)都需要更新(重編譯):“-B”、“--always-make”

指定讀取makefile的目錄。如果有多個(gè)“-C”參數(shù),make的解釋是后面的路徑以前面的作為相對(duì)路徑,并以最后的目錄作為被指定目錄:“-C <dir>”、“--directory=<dir>”。如:“make –C ~hchen/test –C prog”等價(jià)于“make –C ~hchen/test/prog”。
輸出make的調(diào)試信息。它有幾種不同的級(jí)別可供選擇,如果沒有參數(shù),那就是輸出最簡(jiǎn)單的調(diào)試信息:“—debug[=<options>]”,下面是<options>的取值:
? ? ? ? ? ? ? ? ? ? ? ? ? ?a —— 也就是all,輸出所有的調(diào)試信息。(會(huì)非常的多)
? ? ? ? ? ? ? ? ? ? ? ? ? ?b —— 也就是basic,只輸出簡(jiǎn)單的調(diào)試信息。即輸出不需要重編譯的目標(biāo)。
? ? ? ? ? ? ? ? ? ? ? ? ? ?v —— 也就是verbose,在b選項(xiàng)的級(jí)別之上。輸出的信息包括哪個(gè)makefile被解析,不需要被重編譯的依賴文件(或是? ? ? ? ? ? ? ? ? ? ? ? ? 依賴目標(biāo))等。
? ? ? ? ? ? ? ? ? ? ? ? ? i —— 也就是implicit,輸出所以的隱含規(guī)則。
? ? ? ? ? ? ? ? ? ? ? ? ? j —— 也就是jobs,輸出執(zhí)行規(guī)則中命令的詳細(xì)信息,如命令的PID、返回碼等。
? ? ? ? ? ? ? ? ? ? ? ? ?m —— 也就是makefile,輸出make讀取makefile,更新makefile,執(zhí)行makefile的信息。

“-d”:相當(dāng)于“--debug=a”。

指明環(huán)境變量的值覆蓋makefile中定義的變量的值:“-e”、“--environment-overrides”

指定需要執(zhí)行的makefile:“-f=<file>”、“--file=<file>”、“--makefile=<file>”

顯示幫助信息:“-h”、“--help”

在執(zhí)行時(shí)忽略所有的錯(cuò)誤:“-i”、“--ignore-errors”

指定一個(gè)被包含makefile的搜索目標(biāo)??梢允褂枚鄠€(gè)“-I”參數(shù)來指定多個(gè)目錄:“-I <dir>”、“--include-dir=<dir>”

指同時(shí)運(yùn)行命令的個(gè)數(shù)。如果沒有這個(gè)參數(shù),make運(yùn)行命令時(shí)能運(yùn)行多少就運(yùn)行多少。如果有一個(gè)以上的“-j”參數(shù),那么僅最后一個(gè)“-j”才是有效的。(注意這個(gè)參數(shù)在MS-DOS中是無用的):“--jobs[=<jobsnum>]”、“-j [<jobsnum>]”

出錯(cuò)也不停止運(yùn)行。如果生成一個(gè)目標(biāo)失敗了,那么依賴于其上的目標(biāo)就不會(huì)被執(zhí)行了:“-k”、“--keep-going”

指定make運(yùn)行命令的負(fù)載:“-l <load>”、“--load-average[=<load]”、“—max-load[=<load>]”

僅輸出執(zhí)行過程中的命令序列,但并不執(zhí)行:“-n”、“--just-print”、“--dry-run”、“--recon”

不重新生成的指定的<file>,即使這個(gè)目標(biāo)的依賴文件新于它:“-o <file>”、“--old-file=<file>”、“--assume-old=<file>”

輸出makefile中的所有數(shù)據(jù),包括所有的規(guī)則和變量。這個(gè)參數(shù)會(huì)讓一個(gè)簡(jiǎn)單的makefile都會(huì)輸出一堆信息。如果你只是想輸出信息而不想執(zhí)行makefile,你可以使用“make -qp”命令。如果你想查看執(zhí)行makefile前的預(yù)設(shè)變量和規(guī)則,你可以使用“make –p –f /dev/null”。這個(gè)參數(shù)輸出的信息會(huì)包含著你的makefile文件的文件名和行號(hào),所以,用這個(gè)參數(shù)來調(diào)試你的makefile會(huì)是很有用的,特別是當(dāng)你的環(huán)境變量很復(fù)雜的時(shí)候:“-p”、“--print-data-base”

不運(yùn)行命令,也不輸出。僅僅是檢查所指定的目標(biāo)是否需要更新。如果是0則說明要更新,如果是2則說明有錯(cuò)誤發(fā)生:“-q”、“--question”

禁止make使用任何隱含規(guī)則:“-r”、“--no-builtin-rules”

禁止make使用任何作用于變量上的隱含規(guī)則:“-R”、“--no-builtin-variabes”

在命令運(yùn)行時(shí)不輸出命令的輸出:“-s”、“--silent”、“--quiet”

取消“-k”選項(xiàng)的作用。因?yàn)橛行r(shí)候,make的選項(xiàng)是從環(huán)境變量“MAKEFLAGS”中繼承下來的。所以你可以在命令行中使用這個(gè)參數(shù)來讓環(huán)境變量中的“-k”選項(xiàng)失效:“-S”、“--no-keep-going”、“--stop”

相當(dāng)于UNIX的touch命令,只是把目標(biāo)的修改日期變成最新的,也就是阻止生成目標(biāo)的命令運(yùn)行:“-t”、“--touch”

輸出make程序的版本、版權(quán)等關(guān)于make的信息:“-v”、“--version”

輸出運(yùn)行makefile之前和之后的信息。這個(gè)參數(shù)對(duì)于跟蹤嵌套式調(diào)用make時(shí)很有用:“-w”、“--print-directory”

”禁止“-w”選項(xiàng):“--no-print-directory

假定目標(biāo)<file>需要更新,如果和“-n”選項(xiàng)使用,那么這個(gè)參數(shù)會(huì)輸出該目標(biāo)更新時(shí)的運(yùn)行動(dòng)作。如果沒有“-n”那么就像運(yùn)行UNIX的“touch”命令一樣,使得<file>的修改時(shí)間為當(dāng)前時(shí)間。:“-W <file>”、“--what-if=<file>”、“--new-file=<file>”、“--assume-file=<file>”

只要make發(fā)現(xiàn)有未定義的變量,那么就輸出警告信息:“--warn-undefined-variables”

十三.隱含規(guī)則

在我們使用Makefile時(shí),有一些我們會(huì)經(jīng)常使用,而且使用頻率非常高的東西,比如,我們編譯C/C++的源程序?yàn)橹虚g目標(biāo)文件(Unix下是[.o]文件,Windows下是[.obj]文件)。本章講述的就是一些在Makefile中的“隱含的”,早先約定了的,不需要我們?cè)賹懗鰜淼囊?guī)則。

“隱含規(guī)則”也就是一種慣例,make會(huì)按照這種“慣例”心照不喧地來運(yùn)行,那怕我們的Makefile中沒有書寫這樣的規(guī)則。例如,把[.c]文件編譯成[.o]文件這一規(guī)則,你根本就不用寫出來,make會(huì)自動(dòng)推導(dǎo)出這種規(guī)則,并生成我們需要的[.o]文件。

“隱含規(guī)則”會(huì)使用一些我們系統(tǒng)變量,我們可以改變這些系統(tǒng)變量的值來定制隱含規(guī)則的運(yùn)行時(shí)的參數(shù)。如系統(tǒng)變量“CFLAGS”可以控制編譯時(shí)的編譯器參數(shù)。

我們還可以通過“模式規(guī)則”的方式寫下自己的隱含規(guī)則。用“后綴規(guī)則”來定義隱含規(guī)則會(huì)有許多的限制。使用“模式規(guī)則”會(huì)更回得智能和清楚,但“后綴規(guī)則”可以用來保證我們Makefile的兼容性。
我們了解了“隱含規(guī)則”,可以讓其為我們更好的服務(wù),也會(huì)讓我們知道一些“約定俗成”了的東西,而不至于使得我們?cè)谶\(yùn)行Makefile時(shí)出現(xiàn)一些我們覺得莫名其妙的東西。當(dāng)然,任何事物都是矛盾的,水能載舟,亦可覆舟,所以,有時(shí)候“隱含規(guī)則”也會(huì)給我們?cè)斐刹恍〉穆闊?。只有了解了?#xff0c;我們才能更好地使用它。

13.1使用隱含規(guī)則

如果要使用隱含規(guī)則生成你需要的目標(biāo),你所需要做的就是不要寫出這個(gè)目標(biāo)的規(guī)則。那么,make會(huì)試圖去自動(dòng)推導(dǎo)產(chǎn)生這個(gè)目標(biāo)的規(guī)則和命令,如果make可以自動(dòng)推導(dǎo)生成這個(gè)目標(biāo)的規(guī)則和命令,那么這個(gè)行為就是隱含規(guī)則的自動(dòng)推導(dǎo)。當(dāng)然,隱含規(guī)則是make事先約定好的一些東西。例如,我們有下面的一個(gè)Makefile:

foo : foo.o bar.occ –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)

我們可以注意到,這個(gè)Makefile中并沒有寫下如何生成foo.o和bar.o這兩目標(biāo)的規(guī)則和命令。因?yàn)閙ake的“隱含規(guī)則”功能會(huì)自動(dòng)為我們自動(dòng)去推導(dǎo)這兩個(gè)目標(biāo)的依賴目標(biāo)和生成命令。

make會(huì)在自己的“隱含規(guī)則”庫(kù)中尋找可以用的規(guī)則,如果找到,那么就會(huì)使用。如果找不到,那么就會(huì)報(bào)錯(cuò)。在上面的那個(gè)例子中,make調(diào)用的隱含規(guī)則是,把[.o]的目標(biāo)的依賴文件置成[.c],并使用C的編譯命令“cc –c $(CFLAGS) [.c]”來生成[.o]的目標(biāo)。也就是說,我們完全沒有必要寫下下面的兩條規(guī)則:

foo.o : foo.ccc –c foo.c $(CFLAGS)
bar.o : bar.ccc –c bar.c $(CFLAGS)

因?yàn)?#xff0c;這已經(jīng)是“約定”好了的事了,make和我們約定好了用C編譯器“cc”生成[.o]文件的規(guī)則,這就是隱含規(guī)則。

當(dāng)然,如果我們?yōu)閇.o]文件書寫了自己的規(guī)則,那么make就不會(huì)自動(dòng)推導(dǎo)并調(diào)用隱含規(guī)則,它會(huì)按照我們寫好的規(guī)則忠實(shí)地執(zhí)行。

還有,在make的“隱含規(guī)則庫(kù)”中,每一條隱含規(guī)則都在庫(kù)中有其順序,越靠前的則是越被經(jīng)常使用的,所以,這會(huì)導(dǎo)致我們有些時(shí)候即使我們顯示地指定了目標(biāo)依賴,make也不會(huì)管。如下面這條規(guī)則(沒有命令):

foo.o : foo.p

依賴文件“foo.p”(Pascal程序的源文件)有可能變得沒有意義。如果目錄下存在了“foo.c”文件,那么我們的隱含規(guī)則一樣會(huì)生效,并會(huì)通過“foo.c”調(diào)用C的編譯器生成foo.o文件。因?yàn)?#xff0c;在隱含規(guī)則中,Pascal的規(guī)則出現(xiàn)在C的規(guī)則之后,所以,make找到可以生成foo.o的C的規(guī)則就不再尋找下一條規(guī)則了。如果你確實(shí)不希望任何隱含規(guī)則推導(dǎo),那么,你就不要只寫出“依賴規(guī)則”,而不寫命令。

13.2、隱含規(guī)則一覽

這里我們將講述所有預(yù)先設(shè)置(也就是make內(nèi)建)的隱含規(guī)則,如果我們不明確地寫下規(guī)則,那么,make就會(huì)在這些規(guī)則中尋找所需要規(guī)則和命令。當(dāng)然,我們也可以使用make的參數(shù)“-r”或“--no-builtin-rules”選項(xiàng)來取消所有的預(yù)設(shè)置的隱含規(guī)則。

當(dāng)然,即使是我們指定了“-r”參數(shù),某些隱含規(guī)則還是會(huì)生效,因?yàn)橛性S多的隱含規(guī)則都是使用了“后綴規(guī)則”來定義的,所以,只要隱含規(guī)則中有“后綴列表”(也就一系統(tǒng)定義在目標(biāo).SUFFIXES的依賴目標(biāo)),那么隱含規(guī)則就會(huì)生效。默認(rèn)的后綴列表是:.out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el。具體的細(xì)節(jié),我們會(huì)在后面講述。

還是先來看一看常用的隱含規(guī)則吧。

1、編譯C程序的隱含規(guī)則:“<n>.o”的目標(biāo)的依賴目標(biāo)會(huì)自動(dòng)推導(dǎo)為“<n>.c”,并且其生成命令是

$(CC) –c $(CPPFLAGS) $(CFLAGS)

2、編譯C++程序的隱含規(guī)則:“<n>.o”的目標(biāo)的依賴目標(biāo)會(huì)自動(dòng)推導(dǎo)為“<n>.cc”或是“<n>.C”,并且其生成命令是

$(CXX) –c $(CPPFLAGS) $(CFLAGS)

。(建議使用“.cc”作為C++源文件的后綴,而不是“.C”)

3、編譯Pascal程序的隱含規(guī)則:“<n>.o”的目標(biāo)的依賴目標(biāo)會(huì)自動(dòng)推導(dǎo)為“<n>.p”,并且其生成命令是:

$(PC) –c? $(PFLAGS)

4、編譯Fortran/Ratfor程序的隱含規(guī)則:“<n>.o”的目標(biāo)的依賴目標(biāo)會(huì)自動(dòng)推導(dǎo)為“<n>.r”或“<n>.F”或“<n>.f”,并且其生成命令是:

“.f”? “$(FC) –c? $(FFLAGS)”
“.F”? “$(FC) –c? $(FFLAGS) $(CPPFLAGS)”
“.f”? “$(FC) –c? $(FFLAGS) $(RFLAGS)”

5、預(yù)處理Fortran/Ratfor程序的隱含規(guī)則:“<n>.f”的目標(biāo)的依賴目標(biāo)會(huì)自動(dòng)推導(dǎo)為“<n>.r”或“<n>.F”。這個(gè)規(guī)則只是轉(zhuǎn)換Ratfor或有預(yù)處理的Fortran程序到一個(gè)標(biāo)準(zhǔn)的Fortran程序。其使用的命令是:

“.F”? “$(FC) –F $(CPPFLAGS) $(FFLAGS)”
“.r”? “$(FC) –F $(FFLAGS) $(RFLAGS)”

6、編譯Modula-2程序的隱含規(guī)則:“<n>.sym”的目標(biāo)的依賴目標(biāo)會(huì)自動(dòng)推導(dǎo)為“<n>.def”,并且其生成命令是:“$(M2C) $(M2FLAGS) $(DEFFLAGS)”?!?lt;n.o>” 的目標(biāo)的依賴目標(biāo)會(huì)自動(dòng)推導(dǎo)為“<n>.mod”,并且其生成命令是:

$(M2C) $(M2FLAGS) $(MODFLAGS)

7、匯編和匯編預(yù)處理的隱含規(guī)則:“<n>.o” 的目標(biāo)的依賴目標(biāo)會(huì)自動(dòng)推導(dǎo)為“<n>.s”,默認(rèn)使用編譯品“as”,并且其生成命令是:“$(AS) $(ASFLAGS)”?!?lt;n>.s” 的目標(biāo)的依賴目標(biāo)會(huì)自動(dòng)推導(dǎo)為“<n>.S”,默認(rèn)使用C預(yù)編譯器“cpp”,并且其生成命令是:

$(AS) $(ASFLAGS)

8、鏈接Object文件的隱含規(guī)則:“<n>”目標(biāo)依賴于“<n>.o”,通過運(yùn)行C的編譯器來運(yùn)行鏈接程序生成(一般是“l(fā)d”),其生成命令是:“$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)”。這個(gè)規(guī)則對(duì)于只有一個(gè)源文件的工程有效,同時(shí)也對(duì)多個(gè)Object文件(由不同的源文件生成)的也有效。例如如下規(guī)則:x : y.o z.o

并且“x.c”、“y.c”和“z.c”都存在時(shí),隱含規(guī)則將執(zhí)行如下命令:

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

如果沒有一個(gè)源文件(如上例中的x.c)和你的目標(biāo)名字(如上例中的x)相關(guān)聯(lián),那么,你最好寫出自己的生成規(guī)則,不然,隱含規(guī)則會(huì)報(bào)錯(cuò)的。

9、Yacc C程序時(shí)的隱含規(guī)則。
“<n>.c”的依賴文件被自動(dòng)推導(dǎo)為“n.y”(Yacc

$(YACC) $(YFALGS)//(“Yacc”是一個(gè)語(yǔ)法分析器,關(guān)于其細(xì)節(jié)請(qǐng)查看相關(guān)資料)

生成的文件),其生成命令是

10、Lex C程序時(shí)的隱含規(guī)則。
“<n>.c”的依賴文件被自動(dòng)推導(dǎo)為“n.l”(Lex生成的文件),其生成命令是:

$(LEX) $(LFALGS)//(關(guān)于“Lex”的細(xì)節(jié)請(qǐng)查看相關(guān)資料)

11、Lex Ratfor程序時(shí)的隱含規(guī)則。
“<n>.r”的依賴文件被自動(dòng)推導(dǎo)為“n.l”(Lex生成的文件),其生成命令是:

$(LEX) $(LFALGS)


12、從C程序、Yacc文件或Lex文件創(chuàng)建Lint庫(kù)的隱含規(guī)則。
“<n>.ln” (lint生成的文件)的依賴文件被自動(dòng)推導(dǎo)為“n.c”,其生成命令是:

$(LINT) $(LINTFALGS) $(CPPFLAGS) -i

對(duì)于“<n>.y”和“<n>.l”也是同樣的規(guī)則。

13.3、隱含規(guī)則使用的變量

在隱含規(guī)則中的命令中,基本上都是使用了一些預(yù)先設(shè)置的變量。你可以在你的makefile中改變這些變量的值,或是在make的命令行中傳入這些值,或是在你的環(huán)境變量中設(shè)置這些值,無論怎么樣,只要設(shè)置了這些特定的變量,那么其就會(huì)對(duì)隱含規(guī)則起作用。當(dāng)然,你也可以利用make的“-R”或“--no–builtin-variables”參數(shù)來取消你所定義的變量對(duì)隱含規(guī)則的作用。

例如,第一條隱含規(guī)則——編譯C程序的隱含規(guī)則的命令是“$(CC) –c $(CFLAGS) $(CPPFLAGS)”。Make默認(rèn)的編譯命令是“cc”,如果你把變量“$(CC)”重定義成“gcc”,把變量“$(CFLAGS)”重定義成“-g”,那么,隱含規(guī)則中的命令全部會(huì)以“gcc –c -g $(CPPFLAGS)”的樣子來執(zhí)行了。

我們可以把隱含規(guī)則中使用的變量分成兩種:一種是命令相關(guān)的,如“CC”;一種是參數(shù)相的關(guān),如“CFLAGS”。下面是所有隱含規(guī)則中會(huì)用到的變量:

1、關(guān)于命令的變量。

AR:?函數(shù)庫(kù)打包程序。默認(rèn)命令是“ar”。

AS:?匯編語(yǔ)言編譯程序。默認(rèn)命令是“as”。

CC:C語(yǔ)言編譯程序。默認(rèn)命令是“cc”。

CXX:C++語(yǔ)言編譯程序。默認(rèn)命令是“g++”。

CO:從 RCS文件中擴(kuò)展文件程序。默認(rèn)命令是“co”。

CPP:C程序的預(yù)處理器(輸出是標(biāo)準(zhǔn)輸出設(shè)備)。默認(rèn)命令是“$(CC) –E”。

FC:Fortran 和 Ratfor 的編譯器和預(yù)處理程序。默認(rèn)命令是“f77”。

GET:從SCCS文件中擴(kuò)展文件的程序。默認(rèn)命令是“get”。

LEX:Lex方法分析器程序(針對(duì)于C或Ratfor)。默認(rèn)命令是“l(fā)ex”。

PC:Pascal語(yǔ)言編譯程序。默認(rèn)命令是“pc”。

YACC:Yacc文法分析器(針對(duì)于C程序)。默認(rèn)命令是“yacc”。

YACCR:Yacc文法分析器(針對(duì)于Ratfor程序)。默認(rèn)命令是“yacc –r”。

MAKEINFO:轉(zhuǎn)換Texinfo源文件(.texi)到Info文件程序。默認(rèn)命令是“makeinfo”。

TEX:從TeX源文件創(chuàng)建TeX DVI文件的程序。默認(rèn)命令是“tex”。

TEXI2DVI:從Texinfo源文件創(chuàng)建軍TeX DVI 文件的程序。默認(rèn)命令是“texi2dvi”。

WEAVE:轉(zhuǎn)換Web到TeX的程序。默認(rèn)命令是“weave”。

CWEAVE:轉(zhuǎn)換C Web 到 TeX的程序。默認(rèn)命令是“cweave”。

TANGLE:轉(zhuǎn)換Web到Pascal語(yǔ)言的程序。默認(rèn)命令是“tangle”。

CTANGLE:轉(zhuǎn)換C Web 到 C。默認(rèn)命令是“ctangle”。

RM:刪除文件命令。默認(rèn)命令是“rm –f”。

2、關(guān)于命令參數(shù)的變量

下面的這些變量都是相關(guān)上面的命令的參數(shù)。如果沒有指明其默認(rèn)值,那么其默認(rèn)值都是空。

ARFLAGS:函數(shù)庫(kù)打包程序AR命令的參數(shù)。默認(rèn)值是“rv”。

ASFLAGS:?匯編語(yǔ)言編譯器參數(shù)。(當(dāng)明顯地調(diào)用“.s”或“.S”文件時(shí))。

CFLAGS:C語(yǔ)言編譯器參數(shù)。

CXXFLAGS:C++語(yǔ)言編譯器參數(shù)。

COFLAGS:RCS命令參數(shù)。

CPPFLAGS:C預(yù)處理器參數(shù)。( C 和 Fortran 編譯器也會(huì)用到)。

FFLAGS:?Fortran語(yǔ)言編譯器參數(shù)。

GFLAGS:SCCS “get”程序參數(shù)。

LDFLAGS:鏈接器參數(shù)。(如:“l(fā)d”)

LFLAGS:Lex文法分析器參數(shù)。

PFLAGS:Pascal語(yǔ)言編譯器參數(shù)。

RFLAGS:Ratfor 程序的Fortran 編譯器參數(shù)。

YFLAGS:Yacc文法分析器參數(shù)。

13.4、隱含規(guī)則鏈

有些時(shí)候,一個(gè)目標(biāo)可能被一系列的隱含規(guī)則所作用。例如,一個(gè)[.o]的文件生成,可能會(huì)是先被Yacc的[.y]文件先成[.c],然后再被C的編譯器生成。我們把這一系列的隱含規(guī)則叫做“隱含規(guī)則鏈”。

在上面的例子中,如果文件[.c]存在,那么就直接調(diào)用C的編譯器的隱含規(guī)則,如果沒有[.c]文件,但有一個(gè)[.y]文件,那么Yacc的隱含規(guī)則會(huì)被調(diào)用,生成[.c]文件,然后,再調(diào)用C編譯的隱含規(guī)則最終由[.c]生成[.o]文件,達(dá)到目標(biāo)。

我們把這種[.c]的文件(或是目標(biāo)),叫做中間目標(biāo)。不管怎么樣,make會(huì)努力自動(dòng)推導(dǎo)生成目標(biāo)的一切方法,不管中間目標(biāo)有多少,其都會(huì)執(zhí)著地把所有的隱含規(guī)則和你書寫的規(guī)則全部合起來分析,努力達(dá)到目標(biāo),所以,有些時(shí)候,可能會(huì)讓你覺得奇怪,怎么我的目標(biāo)會(huì)這樣生成?怎么我的makefile發(fā)瘋了?

在默認(rèn)情況下,對(duì)于中間目標(biāo),它和一般的目標(biāo)有兩個(gè)地方所不同:第一個(gè)不同是除非中間的目標(biāo)不存在,才會(huì)引發(fā)中間規(guī)則。第二個(gè)不同的是,只要目標(biāo)成功產(chǎn)生,那么,產(chǎn)生最終目標(biāo)過程中,所產(chǎn)生的中間目標(biāo)文件會(huì)被以“rm -f”刪除。

通常,一個(gè)被makefile指定成目標(biāo)或是依賴目標(biāo)的文件不能被當(dāng)作中介。然而,你可以明顯地說明一個(gè)文件或是目標(biāo)是中介目標(biāo),你可以使用偽目標(biāo)“.INTERMEDIATE”來強(qiáng)制聲明。(如:.INTERMEDIATE : mid )

你也可以阻止make自動(dòng)刪除中間目標(biāo),要做到這一點(diǎn),你可以使用偽目標(biāo)“.SECONDARY”來強(qiáng)制聲明(如:.SECONDARY : sec)。你還可以把你的目標(biāo),以模式的方式來指定(如:%.o)成偽目標(biāo)“.PRECIOUS”的依賴目標(biāo),以保存被隱含規(guī)則所生成的中間文件。

在“隱含規(guī)則鏈”中,禁止同一個(gè)目標(biāo)出現(xiàn)兩次或兩次以上,這樣一來,就可防止在make自動(dòng)推導(dǎo)時(shí)出現(xiàn)無限遞歸的情況。

Make會(huì)優(yōu)化一些特殊的隱含規(guī)則,而不生成中間文件。如,從文件“foo.c”生成目標(biāo)程序“foo”,按道理,make會(huì)編譯生成中間文件“foo.o”,然后鏈接成“foo”,但在實(shí)際情況下,這一動(dòng)作可以被一條“cc”的命令完成(cc –o foo foo.c),于是優(yōu)化過的規(guī)則就不會(huì)生成中間文件。

13.5、定義模式規(guī)則

你可以使用模式規(guī)則來定義一個(gè)隱含規(guī)則。一個(gè)模式規(guī)則就好像一個(gè)一般的規(guī)則,只是在規(guī)則中,目標(biāo)的定義需要有"%"字符。"%"的意思是表示一個(gè)或多個(gè)任意字符。在依賴目標(biāo)中同樣可以使用"%",只是依賴目標(biāo)中的"%"的取值,取決于其目標(biāo)。

有一點(diǎn)需要注意的是,"%"的展開發(fā)生在變量和函數(shù)的展開之后,變量和函數(shù)的展開發(fā)生在make載入Makefile時(shí),而模式規(guī)則中的"%"則發(fā)生在運(yùn)行時(shí)。

13.5.1、模式規(guī)則介紹

模式規(guī)則中,至少在規(guī)則的目標(biāo)定義中要包含"%",否則,就是一般的規(guī)則。目標(biāo)中的"%"定義表示對(duì)文件名的匹配,"%"表示長(zhǎng)度任意的非空字符串。例如:"%.c"表示以".c"結(jié)尾的文件名(文件名的長(zhǎng)度至少為3),而"s.%.c"則表示以"s."開頭,".c"結(jié)尾的文件名(文件名的長(zhǎng)度至少為5)。

如果"%"定義在目標(biāo)中,那么,目標(biāo)中的"%"的值決定了依賴目標(biāo)中的"%"的值,也就是說,目標(biāo)中的模式的"%"決定了依賴目標(biāo)中"%"的樣子。例如有一個(gè)模式規(guī)則如下:

%.o:%.c;<command ......>

其含義是,指出了怎么從所有的[.c]文件生成相應(yīng)的[.o]文件的規(guī)則。如果要生成的目標(biāo)是"a.o b.o",那么"%c"就是"a.c b.c"。

一旦依賴目標(biāo)中的"%"模式被確定,那么,make會(huì)被要求去匹配當(dāng)前目錄下所有的文件名,一旦找到,make就會(huì)規(guī)則下的命令,所以,在模式規(guī)則中,目標(biāo)可能會(huì)是多個(gè)的,如果有模式匹配出多個(gè)目標(biāo),make就會(huì)產(chǎn)生所有的模式目標(biāo),此時(shí),make關(guān)心的是依賴的文件名和生成目標(biāo)的命令這兩件事。

13.5.2、模式規(guī)則示例

下面這個(gè)例子表示了,把所有的[.c]文件都編譯成[.o]文件.

%.o:%.c$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

其中,"$@"表示所有的目標(biāo)的挨個(gè)值,"$<"表示了所有依賴目標(biāo)的挨個(gè)值。這些奇怪的變量我們叫"自動(dòng)化變量",后面會(huì)詳細(xì)講述。

下面的這個(gè)例子中有兩個(gè)目標(biāo)是模式的:

%.tab.c %.tab.h: %.ybison -d $<

這條規(guī)則告訴make把所有的[.y]文件都以"bison -d <n>.y"執(zhí)行,然后生成"<n>.tab.c"和"<n>.tab.h"文件。(其中,"<n>"表示一個(gè)任意字符串)。如果我們的執(zhí)行程序"foo"依賴于文件"parse.tab.o"和"scan.o",并且文件"scan.o"依賴于文件"parse.tab.h",如果"parse.y"文件被更新了,那么根據(jù)上述的規(guī)則,"bison -d parse.y"就會(huì)被執(zhí)行一次,于是,"parse.tab.o"和"scan.o"的依賴文件就齊了。(假設(shè),"parse.tab.o"由"parse.tab.c"生成,和"scan.o"由"scan.c"生成,而"foo"由"parse.tab.o"和"scan.o"鏈接生成,而且foo和其[.o]文件的依賴關(guān)系也寫好,那么,所有的目標(biāo)都會(huì)得到滿足)。

在上述的模式規(guī)則中,目標(biāo)和依賴文件都是一系例的文件,那么我們?nèi)绾螘鴮懸粋€(gè)命令來完成從不同的依賴文件生成相應(yīng)的目標(biāo)?因?yàn)樵诿恳淮蔚膶?duì)模式規(guī)則的解析時(shí),都會(huì)是不同的目標(biāo)和依賴文件。

自動(dòng)化變量就是完成這個(gè)功能的。在前面,我們已經(jīng)對(duì)自動(dòng)化變量有所提涉,相信你看到這里已對(duì)它有一個(gè)感性認(rèn)識(shí)了。所謂自動(dòng)化變量,就是這種變量會(huì)把模式中所定義的一系列的文件自動(dòng)地挨個(gè)取出,直至所有的符合模式的文件都取完了。這種自動(dòng)化變量只應(yīng)出現(xiàn)在規(guī)則的命令中。下面是所有的自動(dòng)化變量及其說明:

$@:?表示規(guī)則中的目標(biāo)文件集。在模式規(guī)則中,如果有多個(gè)目標(biāo),那么,"$@"就是匹配于目標(biāo)中模式定義的集合。

$%:僅當(dāng)目標(biāo)是函數(shù)庫(kù)文件中,表示規(guī)則中的目標(biāo)成員名。例如,如果一個(gè)目標(biāo)是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目標(biāo)不是函數(shù)庫(kù)文件(Unix下是[.a],Windows下是[.lib]),那么,其值為空。

$<:依賴目標(biāo)中的第一個(gè)目標(biāo)名字。如果依賴目標(biāo)是以模式(即"%")定義的,那么"$<"將是符合模式的一系列的文件集。注意,其是一個(gè)一個(gè)取出來的。

$?:所有比目標(biāo)新的依賴目標(biāo)的集合。以空格分隔。

$^:?所有的依賴目標(biāo)的集合。以空格分隔。如果在依賴目標(biāo)中有多個(gè)重復(fù)的,那個(gè)這個(gè)變量會(huì)去除重復(fù)的依賴目標(biāo),只保留一份。

$+:這個(gè)變量很像"$^",也是所有依賴目標(biāo)的集合。只是它不去除重復(fù)的依賴目標(biāo)。

$*:這個(gè)變量表示目標(biāo)模式中"%"及其之前的部分。如果目標(biāo)是"dir/a.foo.b",并且目標(biāo)的模式是"a.%.b",那么,"$*"的值就是"dir/a.foo"。這個(gè)變量對(duì)于構(gòu)造有關(guān)聯(lián)的文件名是比較有較。如果目標(biāo)中沒有模式的定義,那么"$*"也就不能被推導(dǎo)出,但是,如果目標(biāo)文件的后綴是make所識(shí)別的,那么"$*"就是除了后綴的那一部分。例如:如果目標(biāo)是"foo.c",因?yàn)?#34;.c"是make所能識(shí)別的后綴名,所以,"$*"的值就是"foo"。這個(gè)特性是GNU make的,很有可能不兼容于其它版本的make,所以,你應(yīng)該盡量避免使用"$*",除非是在隱含規(guī)則或是靜態(tài)模式中。如果目標(biāo)中的后綴是make所不能識(shí)別的,那么"$*"就是空值。

當(dāng)你希望只對(duì)更新過的依賴文件進(jìn)行操作時(shí),"$?"在顯式規(guī)則中很有用,例如,假設(shè)有一個(gè)函數(shù)庫(kù)文件叫"lib",其由其它幾個(gè)object文件更新。那么把object文件打包的比較有效率的Makefile規(guī)則是:

lib : foo.o bar.o lose.o win.oar r lib $?

在上述所列出來的自動(dòng)量變量中。四個(gè)變量($@、$<、$%、$*)在擴(kuò)展時(shí)只會(huì)有一個(gè)文件,而另三個(gè)的值是一個(gè)文件列表。這七個(gè)自動(dòng)化變量還可以取得文件的目錄名或是在當(dāng)前目錄下的符合模式的文件名,只需要搭配上"D"或"F"字樣。這是GNU make中老版本的特性,在新版本中,我們使用函數(shù)"dir"或"notdir"就可以做到了。"D"的含義就是Directory,就是目錄,"F"的含義就是File,就是文件。

下面是對(duì)于上面的七個(gè)變量分別加上"D"或是"F"的含義:

$(@D):?表示"$@"的目錄部分(不以斜杠作為結(jié)尾),如果"$@"值是"dir/foo.o",那么"$(@D)"就是"dir",而如果"$@"中沒有包含斜杠的話,其值就是"."(當(dāng)前目錄)。

$(@F):表示"$@"的文件部分,如果"$@"值是"dir/foo.o",那么"$(@F)"就是"foo.o","$(@F)"相當(dāng)于函數(shù)"$(notdir $@)"。

"$(*D)"、"$(*F)"?和上面所述的同理,也是取文件的目錄部分和文件部分。對(duì)于上面的那個(gè)例子,"$(*D)"返回"dir",而"$(*F)"返回"foo"

"$(%D)"、"$(%F)":?分別表示了函數(shù)包文件成員的目錄部分和文件部分。這對(duì)于形同"archive(member)"形式的目標(biāo)中的"member"中包含了不同的目錄很有用。

"$(<D)"、"$(<F)":分別表示依賴文件的目錄部分和文件部分。

"$(^D)"、"$(^F)":分別表示所有依賴文件的目錄部分和文件部分。(無相同的)

"$(+D)"、"$(+F)":分別表示所有依賴文件的目錄部分和文件部分。(可以有相同的)

"$(?D)"、"$(?F)":分別表示被更新的依賴文件的目錄部分和文件部分。

最后想提醒一下的是,對(duì)于"$<",為了避免產(chǎn)生不必要的麻煩,我們最好給$后面的那個(gè)特定字符都加上圓括號(hào),比如,"$(<)"就要比"$<"要好一些。

還得要注意的是,這些變量只使用在規(guī)則的命令中,而且一般都是"顯式規(guī)則"和"靜態(tài)模式規(guī)則"(參見前面"書寫規(guī)則"一章)。其在隱含規(guī)則中并沒有意義。

13.5.4、模式的匹配

一般來說,一個(gè)目標(biāo)的模式有一個(gè)有前綴或是后綴的"%",或是沒有前后綴,直接就是一個(gè)"%"。因?yàn)?#34;%"代表一個(gè)或多個(gè)字符,所以在定義好了的模式中,我們把"%"所匹配的內(nèi)容叫做"莖",例如"%.c"所匹配的文件"test.c"中"test"就是"莖"。因?yàn)樵谀繕?biāo)和依賴目標(biāo)中同時(shí)有"%"時(shí),依賴目標(biāo)的"莖"會(huì)傳給目標(biāo),當(dāng)做目標(biāo)中的"莖"。

當(dāng)一個(gè)模式匹配包含有斜杠(實(shí)際也不經(jīng)常包含)的文件時(shí),那么在進(jìn)行模式匹配時(shí),目錄部分會(huì)首先被移開,然后進(jìn)行匹配,成功后,再把目錄加回去。在進(jìn)行"莖"的傳遞時(shí),我們需要知道這個(gè)步驟。例如有一個(gè)模式"e%t",文件"src/eat"匹配于該模式,于是"src/a"就是其"莖",如果這個(gè)模式定義在依賴目標(biāo)中,而被依賴于這個(gè)模式的目標(biāo)中又有個(gè)模式"c%r",那么,目標(biāo)就是"src/car"。("莖"被傳遞)

13.5.5、重載內(nèi)建隱含規(guī)則

你可以重載內(nèi)建的隱含規(guī)則(或是定義一個(gè)全新的),例如你可以重新構(gòu)造和內(nèi)建隱含規(guī)則不同的命令,如:

%.o : %.c$(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)

你可以取消內(nèi)建的隱含規(guī)則,只要不在后面寫命令就行。如:

%.o : %.s

同樣,你也可以重新定義一個(gè)全新的隱含規(guī)則,其在隱含規(guī)則中的位置取決于你在哪里寫下這個(gè)規(guī)則。朝前的位置就靠前。

13.6、老式風(fēng)格的"后綴規(guī)則"

后綴規(guī)則是一個(gè)比較老式的定義隱含規(guī)則的方法。后綴規(guī)則會(huì)被模式規(guī)則逐步地取代。因?yàn)槟J揭?guī)則更強(qiáng)更清晰。為了和老版本的Makefile兼容,GNU make同樣兼容于這些東西。后綴規(guī)則有兩種方式:"雙后綴"和"單后綴"。

雙后綴規(guī)則定義了一對(duì)后綴:目標(biāo)文件的后綴和依賴目標(biāo)(源文件)的后綴。如".c.o"相當(dāng)于"%o : %c"。單后綴規(guī)則只定義一個(gè)后綴,也就是源文件的后綴。如".c"相當(dāng)于"% : %.c"。

后綴規(guī)則中所定義的后綴應(yīng)該是make所認(rèn)識(shí)的,如果一個(gè)后綴是make所認(rèn)識(shí)的,那么這個(gè)規(guī)則就是單后綴規(guī)則,而如果兩個(gè)連在一起的后綴都被make所認(rèn)識(shí),那就是雙后綴規(guī)則。例如:".c"和".o"都是make所知道。因而,如果你定義了一個(gè)規(guī)則是".c.o"那么其就是雙后綴規(guī)則,意義就是".c"是源文件的后綴,".o"是目標(biāo)文件的后綴。如下示例:

.c.o:$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

后綴規(guī)則不允許任何的依賴文件,如果有依賴文件的話,那就不是后綴規(guī)則,那些后綴統(tǒng)統(tǒng)被認(rèn)為是文件名,如:

.c.o: foo.h$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

這個(gè)例子,就是說,文件".c.o"依賴于文件"foo.h",而不是我們想要的這樣:

%.o: %.c foo.h$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

后綴規(guī)則中,如果沒有命令,那是毫無意義的。因?yàn)樗膊粫?huì)移去內(nèi)建的隱含規(guī)則。

而要讓make知道一些特定的后綴,我們可以使用偽目標(biāo)".SUFFIXES"來定義或是刪除,如:

.SUFFIXES: .hack .win

把后綴.hack和.win加入后綴列表中的末尾。

.SUFFIXES:              # 刪除默認(rèn)的后綴
.SUFFIXES: .c .o .h   # 定義自己的后綴

先清楚默認(rèn)后綴,后定義自己的后綴列表。

make的參數(shù)"-r"或"-no-builtin-rules"也會(huì)使用得默認(rèn)的后綴列表為空。而變量"SUFFIXE"被用來定義默認(rèn)的后綴列表,你可以用".SUFFIXES"來改變后綴列表,但請(qǐng)不要改變變量"SUFFIXE"的值。

13.7、隱含規(guī)則搜索算法

比如我們有一個(gè)目標(biāo)叫 T。下面是搜索目標(biāo)T的規(guī)則的算法。請(qǐng)注意,在下面,我們沒有提到后綴規(guī)則,原因是,所有的后綴規(guī)則在Makefile被載入內(nèi)存時(shí),會(huì)被轉(zhuǎn)換成模式規(guī)則。如果目標(biāo)是"archive(member)"的函數(shù)庫(kù)文件模式,那么這個(gè)算法會(huì)被運(yùn)行兩次,第一次是找目標(biāo)T,如果沒有找到的話,那么進(jìn)入第二次,第二次會(huì)把"member"當(dāng)作T來搜索。

1、把T的目錄部分分離出來。叫D,而剩余部分叫N。(如:如果T是"src/foo.o",那么,D就是"src/",N就是"foo.o")

2、創(chuàng)建所有匹配于T或是N的模式規(guī)則列表。

3、如果在模式規(guī)則列表中有匹配所有文件的模式,如"%",那么從列表中移除其它的模式。

4、移除列表中沒有命令的規(guī)則。

5、對(duì)于第一個(gè)在列表中的模式規(guī)則:
??? 1)推導(dǎo)其"莖"S,S應(yīng)該是T或是N匹配于模式中"%"非空的部分。
??? 2)計(jì)算依賴文件。把依賴文件中的"%"都替換成"莖"S。如果目標(biāo)模式中沒有包含斜框字符,而把D加在第一個(gè)依賴文件的開頭。
? ? 3)測(cè)試是否所有的依賴文件都存在或是理當(dāng)存在。(如果有一個(gè)文件被定義成另外一個(gè)規(guī)則的目標(biāo)文件,或者是一個(gè)顯式規(guī)則的依賴文件,那么這個(gè)文件就叫"理當(dāng)存在")
??? 4)如果所有的依賴文件存在或是理當(dāng)存在,或是就沒有依賴文件。那么這條規(guī)則將被采用,退出該算法。

6、如果經(jīng)過第5步,沒有模式規(guī)則被找到,那么就做更進(jìn)一步的搜索。對(duì)于存在于列表中的第一個(gè)模式規(guī)則:
??? 1)如果規(guī)則是終止規(guī)則,那就忽略它,繼續(xù)下一條模式規(guī)則。
? ? 2)計(jì)算依賴文件。(同第5步)
? ? 3)測(cè)試所有的依賴文件是否存在或是理當(dāng)存在。
? ? 4)對(duì)于不存在的依賴文件,遞歸調(diào)用這個(gè)算法查找他是否可以被隱含規(guī)則找到。
? ? 5)如果所有的依賴文件存在或是理當(dāng)存在,或是就根本沒有依賴文件。那么這條規(guī)則被采用,退出該算法。

7、如果沒有隱含規(guī)則可以使用,查看".DEFAULT"規(guī)則,如果有,采用,把".DEFAULT"的命令給T使用。

一旦規(guī)則被找到,就會(huì)執(zhí)行其相當(dāng)?shù)拿?#xff0c;而此時(shí),我們的自動(dòng)化變量的值才會(huì)生成。

十四.使用make更新函數(shù)庫(kù)文件

函數(shù)庫(kù)文件也就是對(duì)Object文件(程序編譯的中間文件)的打包文件。在Unix下,一般是由命令"ar"來完成打包工作。

14.1、函數(shù)庫(kù)文件的成員

一個(gè)函數(shù)庫(kù)文件由多個(gè)文件組成。你可以以如下格式指定函數(shù)庫(kù)文件及其組成: archive(member)

這個(gè)不是一個(gè)命令,而一個(gè)目標(biāo)和依賴的定義。一般來說,這種用法基本上就是為了"ar"命令來服務(wù)的。如:

foolib(hack.o) : hack.oar cr foolib hack.o

如果要指定多個(gè)member,那就以空格分開,如:

foolib(hack.o kludge.o)

其等價(jià)于:

foolib(hack.o) foolib(kludge.o)

你還可以使用Shell的文件通配符來定義,如:

foolib(*.o)

14.2、函數(shù)庫(kù)成員的隱含規(guī)則

當(dāng)make搜索一個(gè)目標(biāo)的隱含規(guī)則時(shí),一個(gè)特殊的特性是,如果這個(gè)目標(biāo)是"a(m)"形式的,其會(huì)把目標(biāo)變成"(m)"。于是,如果我們的成員是"%.o"的模式定義,并且如果我們使用"make foo.a(bar.o)"的形式調(diào)用Makefile時(shí),隱含規(guī)則會(huì)去找"bar.o"的規(guī)則,如果沒有定義bar.o的規(guī)則,那么內(nèi)建隱含規(guī)則生效,make會(huì)去找bar.c文件來生成bar.o,如果找得到的話,make執(zhí)行的命令大致如下:

cc -c bar.c -o bar.oar r foo.a bar.orm -f bar.o

還有一個(gè)變量要注意的是"$%",這是專屬函數(shù)庫(kù)文件的自動(dòng)化變量,有關(guān)其說明請(qǐng)參見"自動(dòng)化變量"一節(jié)。

14.3、函數(shù)庫(kù)文件的后綴規(guī)則

你可以使用"后綴規(guī)則"和"隱含規(guī)則"來生成函數(shù)庫(kù)打包文件,如:

.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

14.4、注意事項(xiàng)

在進(jìn)行函數(shù)庫(kù)打包文件生成時(shí),請(qǐng)小心使用make的并行機(jī)制("-j"參數(shù))。如果多個(gè)ar命令在同一時(shí)間運(yùn)行在同一個(gè)函數(shù)庫(kù)打包文件上,就很有可以損壞這個(gè)函數(shù)庫(kù)文件。所以,在make未來的版本中,應(yīng)該提供一種機(jī)制來避免并行操作發(fā)生在函數(shù)打包文件上。

但就目前而言,你還是應(yīng)該不要盡量不要使用"-j"參數(shù)。

Make常用函數(shù)

1、make file中添加打印
$(info,"here add the debug")

測(cè)試

2、指定編譯的filename
make -f filename

http://m.aloenet.com.cn/news/33004.html

相關(guān)文章:

  • 做網(wǎng)站用服務(wù)器軟文發(fā)布平臺(tái)媒體
  • 做網(wǎng)站到底需要什么it培訓(xùn)班出來現(xiàn)狀
  • 綠色環(huán)保企業(yè)網(wǎng)站模板鄭州seo公司
  • wordpress私人建站主題百度極速版客服人工在線咨詢
  • 網(wǎng)站策劃模版百度廣告銷售
  • 提高網(wǎng)站速度如何建立自己的網(wǎng)頁(yè)
  • 網(wǎng)站空間不支持php5.4關(guān)鍵詞查詢網(wǎng)站的工具
  • 網(wǎng)站建設(shè)seo運(yùn)營(yíng)規(guī)劃優(yōu)就業(yè)seo課程學(xué)多久
  • 可以做網(wǎng)站的行業(yè)手機(jī)網(wǎng)站排名優(yōu)化
  • 做網(wǎng)站的企劃書谷歌關(guān)鍵詞推廣怎么做
  • 建立自己的平臺(tái)網(wǎng)站嗎哪家公司做推廣優(yōu)化好
  • 國(guó)內(nèi)做任務(wù)得數(shù)字貨幣的網(wǎng)站關(guān)鍵詞歌詞表達(dá)的意思
  • 網(wǎng)頁(yè)設(shè)計(jì)與網(wǎng)站建設(shè)實(shí)戰(zhàn)大全競(jìng)價(jià)賬戶托管哪家好
  • 網(wǎng)站客服模板免費(fèi)二級(jí)域名注冊(cè)申請(qǐng)
  • 學(xué)校網(wǎng)站建設(shè)介紹騰訊朋友圈廣告怎么投放
  • 網(wǎng)站建設(shè) 開發(fā)的團(tuán)隊(duì)需要幾個(gè)人網(wǎng)絡(luò)營(yíng)銷的方式有幾種
  • 南橋做網(wǎng)站百度問答首頁(yè)
  • 深圳龍崗做網(wǎng)站的廈門網(wǎng)
  • 淄博建網(wǎng)站哪家好百度搜索排名查詢
  • 做電影解析網(wǎng)站網(wǎng)站推廣100種方法
  • 程序員用來做筆記的網(wǎng)站搜索引擎是指什么
  • 前端網(wǎng)站搜索導(dǎo)航怎么做網(wǎng)站搜索引擎優(yōu)化診斷
  • 淮南市建設(shè)工程質(zhì)量監(jiān)督中心網(wǎng)站百度健康
  • 手機(jī)網(wǎng)站端域名怎樣做解析網(wǎng)絡(luò)營(yíng)銷就是
  • 黔西南州建設(shè)局網(wǎng)站系統(tǒng)優(yōu)化的方法
  • html個(gè)人主頁(yè)制作seo運(yùn)營(yíng)學(xué)校
  • 做網(wǎng)站對(duì)商家的好處b2b平臺(tái)有哪些平臺(tái)
  • 怎么做網(wǎng)站設(shè)計(jì)推廣引流渠道
  • 哪家公司制作網(wǎng)站互聯(lián)網(wǎng)廣告投放代理公司
  • 網(wǎng)站可以做哪些廣告怎樣搭建自己的網(wǎng)站