1.前言
在Makefile中,規(guī)則
描述了用什么命令生成一個文件,該文件被稱為規(guī)則的目標(biāo)
,生成"目標(biāo)"的方式就是規(guī)則,目標(biāo)通常只有一個,除了目標(biāo)之外的其他文件稱為“目標(biāo)”的依賴
“終極目標(biāo)”就是當(dāng)沒有指定具體目標(biāo)時,make的默認(rèn)目標(biāo),通常是Makefile中的第一個目標(biāo)。
2.規(guī)則語法
規(guī)則語法通常如下:
TARGETS : PREREQUISITES
[Tab]COMMAND
或
TARGETS : PREREQUISITES;COMMAND
- ”TARGETS“可以是空格分開的多個文件名,也可以是一個標(biāo)簽(例如“clean”),它可以使用通配符
- 書寫有兩種方式:1.命令可以和目標(biāo):依賴描述放同一行,命令在依賴文件列表后使用分號“;”和依賴文件列表分開。2.命令在目標(biāo)的下一行,當(dāng)獨(dú)立命令行,次數(shù)必須以[Tab]字符開始
- Makefile中符號“$”有特殊的含義(表示變量或者函數(shù)的引用),在規(guī)則中需
要使用符號“$”的地方,需要書寫兩個連續(xù)的“$$” - 對于較長行,我們可以使用反斜線“\”將其分離
規(guī)則的思想:目標(biāo)文件的內(nèi)容是由依賴文件文件決定,依賴文件的任何一處改動,將導(dǎo)致目前已經(jīng)存在的目標(biāo)文件的內(nèi)容過期
。
3.依賴
在GNU make中有兩種依賴
- 常規(guī)依賴,這是書寫Makefile規(guī)則時最常用的一種
- 不經(jīng)常使用的,它比較特殊、稱之為“order-only”依賴
3.1 “order-only”依賴
一個規(guī)則的依賴表面了兩件事
- 決定了重建此規(guī)則目標(biāo)所要執(zhí)行規(guī)則(確切的說是執(zhí)行命令)的順序
- 更新目標(biāo)(執(zhí)行此規(guī)則的命令行)之前要按照什么順序、執(zhí)行那些規(guī)則(命令)來重建依賴文件
通常,如果規(guī)則中依賴文件中的任何一個被更新,則規(guī)則的目標(biāo)相應(yīng)地也應(yīng)該被更新
對于更新這些依賴的,不需要更新規(guī)則的目標(biāo)。我們稱為:“order-only”依賴
書寫規(guī)則時,“order-only”依賴使用管道符號“|”開始,作為目標(biāo)的一個依賴文件。規(guī)則依賴列表中管道符號“|”左邊的是常規(guī)依賴,管道符號右邊的就是“order-only”依賴
如下
TARGETS : NORMAL-PREREQUISITES | ORDER-ONLY-PREREQUISITES
3.3 通配符
Maekfile中表示文件名時可使用通配符。用法和含義和 Linux(unix)的 Bourne shell完全相同,包含
“*”、“?”和“[...]”
在Makefile中,通常只有兩種情況才可以使用通配符
- 可以用在規(guī)則的目標(biāo)、依賴中,make在讀取Makefile時會自動對其進(jìn)行匹配處理(通配符展開)
- 可出現(xiàn)在規(guī)則的命令中,通配符的通配處理是在shell在執(zhí)行此命令時完成的
例如
clean:
[Tab]rm -f *.o
3.3.1 通配符的缺陷
在使用通配符時,可能有如下缺陷,例如
objects = *.o
foo : $(objects)
[Tab]cc -o foo $(CFLAGS) $(objects)
在構(gòu)建foo目標(biāo)的時候,對變量object進(jìn)行展開,如果工作目錄下存在必要的.o文件,那么這些.o文件就會成為依賴文件,如果目錄下沒有.o文件,那么會出現(xiàn)“沒有創(chuàng)建.o文件”的錯誤
3.3.2 wildcard
上文提到通配符定義變量時,會出現(xiàn)錯誤的情況,這時可以使用函數(shù)“wildcard”,用法如下
$(wildcard pattern...)
這樣的話,如果變量展開為已存在,使用空格分開的文件列表,如果不存在對應(yīng)文件,也不會出現(xiàn)不存在文件的錯誤,它會忽略模式字符并返回空,使用如下
$(wildcard *.c)
這樣就可以獲得工作目錄下所有的.c文件列表
4 搜索
4.1 一般搜索(變量VPATH)
變量“VPATH”可以指定依賴文件的搜索路徑,
當(dāng)規(guī)則的依賴文件在當(dāng)前目錄不存在時,make 會在此變量所指定的目錄下去尋找這些依賴文件。
其實“VPATH”變量所指定的是Makefile中所有文件的搜索路徑
,包括了規(guī)則的依賴文件和目標(biāo)文件
用法如下:使用空格或冒號":"將多個需要搜索的目錄分開
VPATH = src:../headers
這樣搜索目錄就是"src"和"../headers"
4.2 選擇性搜索(關(guān)鍵字vpath)
還有一種搜索是使用make的關(guān)鍵字"vpath",這是一個關(guān)鍵字,它有三種用法
- vpath PATTERN DIRECTORIES
為所有符合模式“PATTERN”的文件指定搜索目錄“DIRECTORIES”。多個目錄使用空格或者冒號(:)分開。類似“VPATH”變量 - vpath PATTERN
清除之前為符合模式“PATTERN”的文件設(shè)置的搜索路徑 - vpath
清除所有已被設(shè)置的文件搜索路徑
4.2.1 %匹配字符
vapth使用的“PATTERN”需要包含模式字符“%”,表示匹配一個或者多個字符,例如"%.h"表示所有以".h"結(jié)尾的字符,如果沒有%,那么就是一個明確文件
例如
vpath %.h ../headers
在"../headers"目錄下搜索".h"結(jié)尾的文件
多個具有相同“PATTERN”的vpath語句之間相互獨(dú)立,例如
vpath %.c foo
vpath %.c bar
和
vpath %.c foo:bar
表示對所有的.c文件,依次查找目錄是foo和bar
4.3 目錄搜索機(jī)制
make在解析Makefile文件執(zhí)行規(guī)則時對文件路徑保存或廢棄所依據(jù)的算法如下
- 如果規(guī)則的目標(biāo)文件在Makefile文件所在的目錄(工作目錄)下不存在,那么就執(zhí)行目錄搜尋
- 如果目錄搜尋成功,在指定的目錄下存在此規(guī)則的目標(biāo)。那么搜索到的完整的路徑名就被作為臨時的目標(biāo)文件被保存
- 對于規(guī)則中的所有依賴文件使用相同的方法處理
- 完成第三步的依賴處理后,make程序就可以決定規(guī)則的目標(biāo)是否需要重建
4.1. 規(guī)則的目標(biāo)不需要重建:那么通過目錄搜索得到的所有完整的依賴文件路徑名有效,同樣,規(guī)則的目標(biāo)文件的完整的路徑名同樣有效
4.2. 規(guī)則的目標(biāo)需要重建:那么通過目錄搜索所得到的目標(biāo)文件的完整的路徑名無效,規(guī)則中的目標(biāo)文件將會被在工作目錄下重建
4.4 隱含規(guī)則和搜索目錄
通過變量“VPATH”、或者關(guān)鍵字“vpath”指定的搜索目錄,對于隱含規(guī)則同樣有效
例如一個目標(biāo)文件“foo.o”在Makefile中沒有重建它的明確規(guī)則,那么會根據(jù)隱含規(guī)則由已經(jīng)存在的“foo.c”來重建它。當(dāng)“foo.c”在當(dāng)前目錄下不存在時,make將會進(jìn)行目錄搜索。
4.5 庫文件和搜索目錄
Makefile中程序鏈接的靜態(tài)庫、共享庫同樣也可以通過搜索目錄得到
例如對于依賴文件“-lNAME”,詳細(xì)搜索過程如下
- make在執(zhí)行規(guī)則時會在當(dāng)前目錄下搜索一個名字為“l(fā)ibNAME.so”的文件
- 如果當(dāng)前工作目錄下不存在這樣一個文件,則 make會繼續(xù)搜索使用“VPATH”或者“vpath”指定的搜索目錄
- 還是不存在,make 將搜索系統(tǒng)庫文件存在的默認(rèn)目錄,順序是:“/lib”、“/usr/lib”和“PREFIX/lib”(不同系統(tǒng)可能有差異)
如果“l(fā)ibNAME.so”通過以上的途徑最后還是沒有找到的話,那么make將會按照以上的搜索順序查找名字為“l(fā)ibNAME.a”的文件
5 Makefile偽目標(biāo)
偽目標(biāo)是這樣一個目標(biāo): 它不代表一個真正的文件名,在執(zhí)行make時可以指定這個目標(biāo)來執(zhí)行其所在規(guī)則定義的命令,有時也可以將一個偽目標(biāo)稱為標(biāo)簽
使用偽目標(biāo)的原因(目的):
- 避免在我們的Makefile中定義的只執(zhí)行命令的目標(biāo)(此目標(biāo)的目的為了執(zhí)行執(zhí)行一些列命令,而不需要創(chuàng)建這個目標(biāo))和工作目錄下的實際文件出現(xiàn)名字沖突。
- 提高執(zhí)行make時的效率
常見的偽目標(biāo)
clean:
rm *.o temp
這里的clean就是一個偽目標(biāo)
但是,如果當(dāng)前目錄下存在clean文件,那這就不是一個偽目標(biāo)了
這時,為了讓它依舊是一個偽目標(biāo),可以這樣做
.PHONY : clean
這樣無論當(dāng)前目錄是否存在clean,clean都是一個偽目標(biāo)
完整格式如下
.PHONY: clean clean:
rm *.o temp
5.1 偽目標(biāo)的用法
除了上文中偽目標(biāo)的用法,偽目標(biāo)的一個常見用法是并行和遞歸中,例如
- 沒有使用偽目標(biāo)
SUBDIRS = foo bar baz
subdirs:
for dir in $(SUBDIRS); do \
$(MAKE) -C $$dir; \ done
這樣做的缺點:
- 當(dāng)子目錄執(zhí)行make出現(xiàn)錯誤時,make不會退出
- 使用這種shell的循環(huán)方式時, 沒有用到make對目錄的并行處理功能
- 使用偽目標(biāo)
SUBDIRS = foo bar baz
.PHONY: subdirs $(SUBDIRS)
subdirs: $(SUBDIRS) $(SUBDIRS):
[Tab]$(MAKE) -C $@
foo: baz
上面最后一行是限制foo,baz的順序,baz在foo之后執(zhí)行,這樣寫就解決了方法1中的兩個問題
一般情況下,一個偽目標(biāo)不作為另外一個目標(biāo)的依賴。這是因為當(dāng)一個目標(biāo)文件的依賴包含偽目標(biāo)時,每一次在執(zhí)行這個規(guī)則時偽目標(biāo)所定義的命令都會被執(zhí)行
6 強(qiáng)制目標(biāo)
一個規(guī)則沒有命令或者依賴,并且它的目標(biāo)不是一個存在的文件名.在執(zhí)行此規(guī)則時,目標(biāo)總會被認(rèn)為是最新的
就是說:這個規(guī)則一旦被執(zhí)行,make就認(rèn)為它的目標(biāo)已經(jīng)被更新過。這樣的目標(biāo)在作為一個規(guī)則的依賴時,因為依賴總被認(rèn)為被更新過,因此作為依賴所在的規(guī)則中定義的命令總會被執(zhí)行
例如
clean: FORCE
rm $(objects)
FORCE:
目標(biāo)“FORCE”符合上邊的條件。它作為目標(biāo)“clean”的依賴,在執(zhí)行make時,總被認(rèn)為被更新過。因此“clean”所在規(guī)則在被執(zhí)行時其所定義的命令總會被執(zhí)行。這樣的一個目標(biāo)通常我們將其命名為“FORCE”
7 空目標(biāo)文件
空目標(biāo)文件是偽目標(biāo)的一個變種;此目標(biāo)所在規(guī)則執(zhí)行的目的和偽目標(biāo)相同——通過make命令行指定將其作為終極目標(biāo)來執(zhí)行此規(guī)則所定義的命令
空目標(biāo)文件只是用來記錄上一次執(zhí)行此規(guī)則命令的時間,當(dāng)前目錄下如果不存在 這個文件,“touch”會在第一次執(zhí)行時創(chuàng)建一個空的文件(命名為空目標(biāo)文件名),例如
print: foo.c bar.c
[Tab]lpr -p $?
[Tab]touch print
執(zhí)行“make print”,當(dāng)目標(biāo)“print”的依賴文件任何一個被修改之后,命令“l(fā)pr –p $?”都會被執(zhí)行,打印這個被修改的文件
8 Makefile的特殊目標(biāo)
在 Makefile 中,有一些名字,當(dāng)它們作為規(guī)則的目標(biāo)時,具有特殊含義,即特殊目標(biāo)。具體如下
8.1 .PHONY:
目標(biāo)“.PHONY”的所有的依賴被作為偽目標(biāo)
8.1 .SUFFIXES:
特殊目標(biāo)“SUFFIXES”的所有依賴指出了一系列在后綴規(guī)則中需要檢查的后綴名 (就是當(dāng)前make需要處理的后綴,后續(xù)說明)
8.3 .DEFAULT
就是說一個文件作為某個規(guī)則的依賴,但卻不是另外一個規(guī)則的目標(biāo)時。Make程序無法找到重建此文件的規(guī)則,此種情況時就執(zhí)行“.DEFAULT”所指定的命令
8.4 .PRECIOUS和.SECONDARY
特殊處理:當(dāng)命令在執(zhí)行過程中被中斷時,make不會刪除它們,而且如果目標(biāo)的依賴文件是中間過程文件,同樣這些文件不會被刪除
8.5 .INTERMEDIATE
目標(biāo)“.INTERMEDIATE”的依賴文件在make時被作為中間過程文件對待。沒有 任何依賴文件的目標(biāo)“.INTERMEDIATE”沒有意義
8.6 .DELETE_ON_ERROR
make在執(zhí)行過程中,如果規(guī)則的命令執(zhí)行錯誤,將刪除已經(jīng)被修改的目標(biāo)文件
8.7 .IGNORE
給目標(biāo)“.IGNORE”指定依賴文件,則忽略創(chuàng)建這個文件所執(zhí)行命令的錯誤,給此目標(biāo)指定命令是沒有意義的
8.8 .LOW_RESOLUTION_TIME
目標(biāo)“.LOW_RESOLUTION_TIME”的依賴文件被make認(rèn)為是低分辨率時間戳 文件。給目標(biāo)“.LOW_RESOLUTION_TIME”指定命令是沒有意義的
8.9 .SILENT
出現(xiàn)在目標(biāo)“.SILENT”的依賴列表中的文件,make在創(chuàng)建這些文件時,不打印出重建此文件所執(zhí)行的命令。同樣,給目標(biāo)“.SILENT”指定命令行是沒有意義的
8.10 .EXPORT_ALL_VARIABLES
此目標(biāo)應(yīng)該作為一個簡單的沒有依賴的目標(biāo),它的功能含義是將之后所有的變量傳 遞給子make進(jìn)程
8.10 .NOTPARALLEL
Makefile 中,如果出現(xiàn)目標(biāo)“.NOPARALLEL”,則所有命令按照串行方式執(zhí)行,但在遞歸調(diào)用的字make進(jìn)程中,命令可以并行執(zhí)行
9 多目標(biāo)
一個規(guī)則中可以有多個目標(biāo),規(guī)則所定義的命令對所有的目標(biāo)有效
例如
kbd.o command.o files.o: command.h
這個規(guī)則實現(xiàn)了同時給三個目標(biāo)文件指定一個依賴文件
對于多個具有類似重建命令的目標(biāo)。重建這些目標(biāo)的命令并不需要是完全相同,
因為可以在命令行中使用自動環(huán)變量“$@”來引用具體的目標(biāo),完成對它的重建
例如
bigoutput littleoutput : text.g
[Tab]generate text.g -$(subst output,,$@) > $@
//等價于
bigoutput : text.g
[Tab]generate text.g -big > bigoutput
littleoutput : text.g
[Tab]generate text.g -little > littleoutput
10 多規(guī)則
上面說了一個規(guī)則可以有多個目標(biāo),另外,一個目標(biāo)也可以有多個規(guī)則。這時,以這個文件的所有依賴文件將會被合并成此目標(biāo)一個依賴文件列表。
11 靜態(tài)模式
靜態(tài)模式:規(guī)則存在多個目標(biāo),并且不同的目標(biāo)可以根據(jù)目標(biāo)文件的名字來自動構(gòu)造出依賴文件。
靜態(tài)模式比多目標(biāo)更通用,它不需要多目標(biāo)具有相同的依賴。但是依賴文件必須是相類似的而不是完全相同的。
用法如下
TARGETS ...: TARGET-PATTERN: PREREQ-PATTERNS ...
[Tab]COMMANDS
- TARGETS:目標(biāo)文件
- “TAGET-PATTERN”和“PREREQ-PATTERNS”:如何為每一個目標(biāo)文件生成依賴文件
從目標(biāo)模式(TAGET-PATTERN)的目標(biāo)名字中抽取一部分字符串(稱 為“莖”)。使用“莖”替代依賴模式(PREREQ-PATTERNS)中的相應(yīng)部分來產(chǎn)生對應(yīng)目標(biāo)的依賴文件
舉一個例子
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
[Tab]$(CC) -c $(CFLAGS) $< -o $@
- 規(guī)則描述了所有的.o文件依賴.c文件
- 對于目標(biāo)“foo.o”,取其莖“foo”替代對應(yīng)的依賴模式“%.c”中的模式字符“%”之后可得到目標(biāo)的依賴文件“foo.c”,這就是依賴關(guān)系"foo.o: foo.c"
- 描述了如何完成由“foo.c”編譯生成目標(biāo)“foo.o”,命令行中“
@”是自動化變量
上述規(guī)則等價于
foo.o : foo.c
[Tab]$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
[Tab]$(CC) -c $(CFLAGS) bar.c -o bar.o
11.1 靜態(tài)模式和隱含規(guī)則
隱含規(guī)則
可被用在任何和它相匹配的目標(biāo)上
當(dāng)存在多個隱含規(guī)則和目標(biāo)模式相匹配時,只執(zhí)行其中的一個規(guī)則。具體執(zhí)行哪一個規(guī)則取決 于定義規(guī)則的順序
靜態(tài)模式
只能用在規(guī)則中明確指出的那些文件的重建過程中,不能用在除此之外的任何文件的重建過程中
它對指定的每一個目標(biāo)來說是唯一的。如果一個目標(biāo)存在于兩個規(guī)則,并且這兩個規(guī)則都定以了命令,make執(zhí)行時就會提示錯誤
靜態(tài)模式的優(yōu)點
- 不能根據(jù)文件名通過詞法分析進(jìn)行分類的文件,可以明確列出并使用靜態(tài)模式規(guī)則來重建其隱含規(guī)則
- 對于無法確定工作目錄內(nèi)容,并且不能確定是否此目錄下的無關(guān)文件會使用錯誤的隱含規(guī)則而導(dǎo)致make失敗的情況,使用靜態(tài)模式規(guī)則就可以避免這些不確定因素
12 雙冒號規(guī)則
雙冒號規(guī)則就是使用“::”代替普通規(guī)則的“:”得到的規(guī)則
當(dāng)同一個文件作為多個規(guī)則的目標(biāo)時,雙冒號規(guī)則的處理和普通規(guī)則的處理過程完全不同
Makefile中,一個目標(biāo)可以出現(xiàn)在多個規(guī)則中。但是規(guī)則必須是同一類型的規(guī)則,要么都是普通規(guī)則,要么都是雙冒號規(guī)則。而不允許一個目標(biāo)同時出現(xiàn)在兩種不同類型的規(guī)則中
12.1 雙冒號規(guī)則和普通規(guī)則的不同
- 雙冒號規(guī)則中,對于一個沒有依賴而只有命令行的雙冒號規(guī)則,當(dāng)引用此目標(biāo)時,規(guī)則的命令將會被無條件執(zhí)行。而普通規(guī)則,當(dāng)規(guī)則的目標(biāo)文件存在時,此規(guī)則的命令永遠(yuǎn)不會被執(zhí)行(目 標(biāo)文件永遠(yuǎn)是最新的)
- 當(dāng)同一個文件作為多個雙冒號規(guī)則的目標(biāo)時。這些不同的規(guī)則會被獨(dú)立的處理,而不是像普通規(guī)則那樣合并所有的依賴到一個目標(biāo)文件
舉個例子
Newprog :: foo.c
[Tab]$(CC) $(CFLAGS) $< -o $@
Newprog :: bar.c
[Tab]$(CC) $(CFLAGS) $< -o $@
如果“foo.c”文件被修改,執(zhí)行make以后將根據(jù)“foo.c”文件重建目標(biāo)“Newprog”。 而如果“bar.c”被修改那么“Newprog”將根據(jù)“bar.c”被重建
當(dāng)同一個目標(biāo)出現(xiàn)在多個雙冒號規(guī)則中時,規(guī)則的執(zhí)行順序和普通規(guī)則的執(zhí)行順序 一樣,按照其在 Makefile 中的書寫順序執(zhí)行
一般這種需要的情況很少,所以雙冒號規(guī)則的使用比較罕
13 自動產(chǎn)生依賴
Makefile中,有時需要書寫一些規(guī)則來描述一個.o文件和頭文件的依賴關(guān)系,例如在 main.c中使用“#include defs.h”,那么需要這樣寫
main.o: defs.h
這樣修改起來就比較麻煩,所有可以使用自動尋找源文件中包含的頭文件功能,如下
gcc -M main.c
它實際的作用是
main.o : main.c defs.h
這樣就避免了手動寫的麻煩和出錯,當(dāng)然,如果main.c中使用了標(biāo)準(zhǔn)庫的頭文件,那么使用gcc 的“-M”選項時,就會包含對標(biāo)準(zhǔn)庫的頭文件的依賴關(guān)系,如果不需要,則可以使用”-MM“
使用自動產(chǎn)生依賴功能時,所產(chǎn)生的規(guī)則明確指明了目標(biāo),在通過.c產(chǎn)生執(zhí)行文件后,作為中間文件的main.o并不會被刪除