Makefile學(xué)習(xí)筆記——3.Makefile規(guī)則

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中有兩種依賴

  1. 常規(guī)依賴,這是書寫Makefile規(guī)則時最常用的一種
  2. 不經(jīng)常使用的,它比較特殊、稱之為“order-only”依賴

3.1 “order-only”依賴

一個規(guī)則的依賴表面了兩件事

  1. 決定了重建此規(guī)則目標(biāo)所要執(zhí)行規(guī)則(確切的說是執(zhí)行命令)的順序
  2. 更新目標(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中,通常只有兩種情況才可以使用通配符

  1. 可以用在規(guī)則的目標(biāo)、依賴中,make在讀取Makefile時會自動對其進(jìn)行匹配處理(通配符展開)
  2. 可出現(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)鍵字,它有三種用法

  1. vpath PATTERN DIRECTORIES
    為所有符合模式“PATTERN”的文件指定搜索目錄“DIRECTORIES”。多個目錄使用空格或者冒號(:)分開。類似“VPATH”變量
  2. vpath PATTERN
    清除之前為符合模式“PATTERN”的文件設(shè)置的搜索路徑
  3. 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ù)的算法如下

  1. 如果規(guī)則的目標(biāo)文件在Makefile文件所在的目錄(工作目錄)下不存在,那么就執(zhí)行目錄搜尋
  2. 如果目錄搜尋成功,在指定的目錄下存在此規(guī)則的目標(biāo)。那么搜索到的完整的路徑名就被作為臨時的目標(biāo)文件被保存
  3. 對于規(guī)則中的所有依賴文件使用相同的方法處理
  4. 完成第三步的依賴處理后,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ì)搜索過程如下

  1. make在執(zhí)行規(guī)則時會在當(dāng)前目錄下搜索一個名字為“l(fā)ibNAME.so”的文件
  2. 如果當(dāng)前工作目錄下不存在這樣一個文件,則 make會繼續(xù)搜索使用“VPATH”或者“vpath”指定的搜索目錄
  3. 還是不存在,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)的原因(目的):

  1. 避免在我們的Makefile中定義的只執(zhí)行命令的目標(biāo)(此目標(biāo)的目的為了執(zhí)行執(zhí)行一些列命令,而不需要創(chuàng)建這個目標(biāo))和工作目錄下的實際文件出現(xiàn)名字沖突。
  2. 提高執(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)的一個常見用法是并行和遞歸中,例如

  1. 沒有使用偽目標(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對目錄的并行處理功能
  1. 使用偽目標(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 $@
  1. 規(guī)則描述了所有的.o文件依賴.c文件
  2. 對于目標(biāo)“foo.o”,取其莖“foo”替代對應(yīng)的依賴模式“%.c”中的模式字符“%”之后可得到目標(biāo)的依賴文件“foo.c”,這就是依賴關(guān)系"foo.o: foo.c"
  3. 描述了如何完成由“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)點

  1. 不能根據(jù)文件名通過詞法分析進(jìn)行分類的文件,可以明確列出并使用靜態(tài)模式規(guī)則來重建其隱含規(guī)則
  2. 對于無法確定工作目錄內(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ī)則的不同

  1. 雙冒號規(guī)則中,對于一個沒有依賴而只有命令行的雙冒號規(guī)則,當(dāng)引用此目標(biāo)時,規(guī)則的命令將會被無條件執(zhí)行。而普通規(guī)則,當(dāng)規(guī)則的目標(biāo)文件存在時,此規(guī)則的命令永遠(yuǎn)不會被執(zhí)行(目 標(biāo)文件永遠(yuǎn)是最新的)
  2. 當(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并不會被刪除

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容