GNU make 總結 (二)

規則描述了在何種狀況下使用什麼命令來建立或者更新一個目標。若是在makefile中第一個規則有多個目標的話,那麼多個目標中的第一個將會做爲make的「終極目標」。shell

3.1 規則語法函數

TARGETS : PREREQUISITES
    COMMAND
    ...
    ...
或者
TARGETS : PREREQUISITES; COMMAND
    COMMAND
    ...
    ...

 TARGETS: 能夠是空格隔開的多個目標名,也能夠是標籤。也可使用通配符。spa

COMMAND: 能夠和依賴放在同一行,以分號;隔開。能夠單獨放在一行,可是必須以Tab鍵開頭。命令行

注:設計

makefile中,在第一個規則以後出現的全部以Tab開始的行都會被看成COMMAND來處理。code

makefile中,$表示變量或者函數的引用,所以若是確實須要使用$自己,以下書寫:」$$」。blog

makefile中,使用\將比較長的行斷開,而且\後面不能有空格。進程

 

3.2 依賴的類型字符串

一般的依賴規則中,若是有任何一個依賴文件被更新,則必須重建目標。那麼如何只根據部分依賴的狀況來決定目標是否被重建,而不是在依賴文件中的任何一個被修改後都重建目標?編譯器

此時須要對依賴文件進行分類:一類依賴文件改變後重建目標;另外一類依賴文件改變後不重建目標。咱們稱第二類依賴爲order-only依賴。「order-only」使用管道|開始,」|」左邊的是常規依賴,「|」右邊的是」order-only」依賴,以下所示:

TARGETS : NORMAL-PREREQUISITES | ORDER-ONLY-PREREQUISITES

若是一個文件同時出如今常規依賴和」order-only」依賴列表中,那麼此文件將被視爲常規依賴。

 

3.3 通配符

通配符,好比 *?、[...]等。makefile中通配符能夠出如今如下兩種場合:

1>  目標名、依賴名

2>  命令行語句

Linux中的~表示用戶的主目錄,輸入」echo ~」,顯示/home/benxintuzi(benxintuzi爲用戶名)。Makefile中,~後接一個用戶名錶示該用戶的主目錄,好比:~benxintuzi/Desktop表示目錄/home/benxintuzi/Desktop

 

在如上兩種狀況下,通配符會被自動展開。但在變量定義和函數引用中,通配符將退化爲普通字符,此時須要使用函數wildcard,以下:$(wildcard PATTERN...)表示以空格分隔的、匹配此模式的全部文件列表。若是不存在任何符合此模式的文件,則該函數會忽略模式字符並返回空。

好比:使用$(wildcard *.c)獲取當前目錄下的全部.c文件列表。

 

3.4 目錄搜索

  • 通常搜索

經過變量VPATH能夠指定依賴文件的搜索路徑。當依賴文件不在當前目錄中時,make會在VPATH指定的目錄下搜索這些依賴文件。

事實上,VPATH變量指的是makefile中全部文件的搜索路徑,既包括依賴文件,也包括目標文件。

定義VPATH時,使用空格或者冒號分割多個不一樣的目錄,make按照VPATH中目錄定義的順序進行搜索。

以下:

VPATH = src : ../header

 

  • 選擇性搜索

與VPATH變量相比,vpath是一個make關鍵字,其能夠爲不一樣類型的文件指定不一樣的搜索目錄,有三種使用方法:

1>  vpath PATTERN DIRECTORIES

爲全部匹配PATTERN的文件指定搜索目錄DIRECTORIES。多個目錄之間使用空格或者冒號隔開,相似於VPATH。

2>  vpath PATTERN

清除以前爲匹配PATTERN的文件設置的搜索路徑。

3>  vpath

清除以前全部已經被設置的搜索路徑。

vpath中使用%代替*,表示一個或多個。例如:%.h表示全部以.h結尾的文件。

 

  • 命令行和搜索目錄

make在執行時,經過目錄搜索獲得的目標依賴文件可能存在於其餘目錄下,但此時經過變量獲得的依賴文件名爲文件的完整路徑名,若是要保證在命令行正確執行規則命令,必須使用自動化變量。自動化變量的取值是根據所執行的具體規則來決定的,取決於所執行規則的目標和依賴文件名。

$@ : 表示規則的目標文件名(加上完整路徑後的文件名)

$% 當規則的目標文件是一個靜態庫文件時,表示靜態庫中的一個成員;若是目標文件不是靜態庫,則其值爲空。

$< : 表示規則的第一個依賴文件名。

$* : 文件名相關,後面介紹。

$? : 全部比目標文件更新的依賴文件列表,以空格分隔。

$^ : 規則的全部依賴文件列表,以空格分隔,重複的依賴文件只記錄一次。

$+ : 相似於S^,可是其對於重複的依賴文件都會進行記錄,主要用於程序連接時庫的交叉引用場合。

總結:前4個表明文件名,後4個表明文件名列表。

 

在這些變量中加入D或者F就能夠造成一系列的自動化變量了,以下:

$(@D) : 表示目標文件的目錄部分,可是不包括斜槓。例如,"$@"的值爲」dir/foo.o」,那麼"$(@D)"的值爲」dir」;若是"$@"中不存在斜槓,則"$(@D)"的值爲「.」。

$(@F) : 表示目標文件的實際文件名。例如,"$@"的值爲」dir/foo.o」,那麼"$(@F)"的值爲」foo.o」。"$(@F)"等價於"$(notdir$@)"。

$(*D)、$(*F) : 分別表明「莖」中的目錄部分和文件名部分。

$(%D)、$(%F) : 表示靜態庫成員中的目錄部分和文件名部分。

$(<D)、$(<F) : 表示規則中第一個依賴文件的目錄部分和文件名部分。

$(^D)、$(^F) : 表示全部依賴文件的目錄部分和文件名部分,沒有重複文件。

$(+D)、$(+F) : 表示全部依賴文件的目錄部分和文件名部分,能夠有重複文件。

$(?D)、$(?F) : 表示比目標文件更新的依賴文件的目錄部分和文件名部分。

 

注:GNU make同時支持"Sysv"特性,容許在規則的依賴列表中使用特殊的變量引用(通常的自動化變量只能在規則的命令行中被引用)"$$@"、"$$(@D)"、"$$(@F)",分別表示「目標的完整文件名」、「目標文件中的目錄部分」、「目標文件中的文件名部分」。可是,這三個特殊的變量只能用在明確指定目標文件的規則以及靜態模式規則中,不能用於隱含規則中。

 

  • 庫文件和搜索目錄

makefile中程序連接的靜態庫和共享庫一樣能夠經過搜索目錄獲得,須要在依賴文件列表中以下加入庫名:-INAME,其中NAME爲庫名的一部分。make在執行規則時首先會在當前目錄中尋找」libNAME.so」;若是沒找到,接着在」VPATH」和「vpath」中尋找;仍然沒找到,那麼make搜索系統庫/lib、/usr/lib、/usr/local/lib。若是最終仍是沒找到,那麼make將會按照以上的搜索順序查找libNAME.a

在規則的依賴列表中出現「-INAME」格式的依賴時,表示須要搜索的依賴文件名爲「libNAME.so」或者「libNAME.a」,這是由變量「.LIBPATTERNS」指定的。 「.LIBPATTERNS」的值通常是由多個包含模式字符%的字(一個不包含空格的字符串),多個字之間使用空格分開。在規則中出現「-INAME」時,首先使用「NAME」代替變量「.LIBPATTERNS」中的第一個字的%獲得第一個庫文件名,根據這個庫文件名在搜索目錄下查找,若是找到了,就用這個庫文件;不然,使用「NAME」代替第二個字的%,進行一樣的查找流程。默認狀況下,「.LIBPATTERNS」的內容爲lib%.so lib%.a

 

3.5 僞目標

僞目標並不表明一個真正的文件名,在執行make時能夠執行這個目標所定義的命令,有時也可將其稱爲一個標籤。

使用僞目標有兩個緣由:

1>  避免makefile中定義的目標名稱與當前目錄下的文件名發生衝突。

假設makefile文件中有以下目標:

clean :

    rm main main.o benxin.o tuzi.o

可是恰好在makefile文件目錄下有一個名爲clean的文件,此時咱們若是執行:make clean,那麼因爲clean目標沒有依賴文件,所以被認爲是最新的,繼而rm命令不會獲得執行。爲了解決這個問題,咱們須要將目標clean聲明爲僞目標,就是將clean做爲.PHONY的依賴(.PHONY : clean)。使用僞目標後,make程序在執行此規則時不會使用隱含查找,這樣同時也提升了效率。所以,目標「clean」的完整格式以下所示:

.PHONY : clean

clean :

  rm main main.o benxin.o tuzi.o

注:一般在清除文件的僞目標命令中,rm使用選項-f(--force)來忽略不存在的文件。一樣可使用make的內嵌變量RM,其被定義爲」RM=rm -f」,所以編寫clean時,可使用"$(RM)"來代替「rm」。

 

2>  提升make程序的效率。

僞目標的另外一個用途就是用於make的循環設計中。以下程序對多個目錄進行make操做:

SUBDIRS = foo bar baz
subdirs :
    for dir in $(SUBDIRS); do \
        $(MAKE) –C $$dir; \
    done

 假設在某個目錄中執行make時出現錯誤,那麼make是不會退出的,而是會繼續對其餘目錄執行make操做。所以,最後的結果中很難發現究竟是哪個目錄中執行make出錯了。同時因爲規則中的命令是一條完整的shell語句,所以也不能利用並行處理功能。

使用僞目標從新設計以下:

SUBDIRS = foo bar baz
.PHONY : subdirs $(SUBDIRS)
subdirs : $(SUBDIRS)
$(SUBDIRS) :
    $(MAKE) –C $@
foo : baz

上邊的實現中,foo : baz沒有命令行,是用來控制子目錄的make順序,即在處理foo以前,必須等待baz處理完畢。

通常狀況下,一個僞目標不能做爲目標的依賴。這是由於當一個目標的依賴文件列表中包含僞目標時,每一次在執行這個規則時,僞目標定義的命令都會被執行。一個僞目標能夠有本身的依賴,能夠是文件,也能夠是僞目標。若是須要建立多個可執行程序,那麼可使用一個all僞目標做爲「終極目標」,其依賴文件就是那些須要建立的可執行程序,以下所示:

all : prog1 prog2 prog3
.PHONY : all

prog1 : prog1.o utils.o
    cc –o prog1 prog1.o utils.o

prog2 : prog2.o
    cc –o prog2 prog2.o

prog3 : prog3.o sort.o utils.o
    cc –o prog3 prog3.o sort.o utils.o

 

3.6 強制目標

若是一個沒有命令或者依賴而且其目標也不是一個已有的文件名,那麼在執行該規則時,目標老是被認爲是最新的。以下所示:

clean : FORCE

    rm $(OBJECTS)

FORCE :

這個效果與將clean聲明爲僞目標.PHONY等效,主要用於非GNU版的make中。在GNU make中,儘可能使用僞目標方式,這樣更加直觀高效。

 

3.7 空目標

形式上與僞目標相同,但不一樣的是目標能夠是一個已有的文件,而這個文件一般是一個空文件。空目標文件能夠用來記錄上一次執行此規則命令的時間。在這樣的規則中,命令部分會使用touch在最後來更新目標文件的時間戳,用以記錄此命令的最後執行時間。若是當前目錄沒有這個文件,那麼touch會在第一次執行時建立這個空文件:

print : foo.c bar.c

    ...

    touch print

如上所示,一個空目標文件能夠有一個或者多個依賴文件,當依賴文件被修改後,空目標文件所在規則中定義的命令就會被執行。

 

3.8 特殊目標

.PHONY : .PHONY的全部依賴被看做僞目標,當使用make指定僞目標時,不論目標文件是否存在,該規則下所定義的命令都會被無條件地執行。

.SUFFIXES : .SUFFIXES的全部依賴指出了一系列在後綴規則中須要檢查的後綴名。

.DEFAULT : 一個文件做爲某個規則的依賴,但卻不做爲任何一個規則的目標,make在重建這種文件時,因爲沒法找到重建規則,所以須要藉助於.DEFAULT所指定的命令。

.PRECIOUS : 其全部的依賴文件在make中斷時不會被自動刪除,其依賴文件能夠經過模式指定,如「%.o」。

.SECONDARY :.PRECIOUS相似,當沒有任何依賴時,全部文件都不會被自動刪除。

.INTERMEDIATE : 其全部依賴文件被做爲中間過程文件對待,沒有任何依賴的.INTERMEDIATE是沒有意義的。

.DELETE_ON_ERROR : 若是執行make規則的命令中發生錯誤,將刪除已被修改的目標文件。

.IGNORE : 若是.IGNORE存在依賴文件,則忽略重建這個目標文件時發生的錯誤;若是沒有任何依賴文件,則忽略全部命令執行的錯誤。

.LOW_RESOLUTION_TIME : 其依賴文件被認爲是低分辨率時間戳文件,相對於高分辨率時間戳文件而言,有時更易於判別兩個文件建立時間的前後關係。

.SILENT : 重建.SILENT依賴列表中的文件時不會打印出重建命令。

.EXPORT_ALL_VARIABLES : 將其後全部的變量傳遞給子make進程。

.NOTPARALLEL : 沒有依賴文件,全部的命令按串行方式執行。

 

3.9 多目標

多個目標依賴於一樣的文件列表:

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

等價於:

bigoutput : text.g
    generate text.g –big > bigoutput
littleoutput : text.g
    generate text.g –little > littleoutput

 

3.10 多規則目標

makefile中,一個文件能夠做爲多個規則的目標文件,此時,以這個文件爲目標的規則的全部依賴文件將會被合併成一個依賴文件列表,當其中任何一個依賴文件比目標更新時,make將會執行特定的命令來重建這個目標。對於一個多規則的目標,重建此目標的命令只能出如今一個規則中。若是多個規則同時給出重建此目標的命令,則make將會使用最後一個規則中定義的命令,同時給出錯誤提示信息。固然,若是任何一個規則都沒有給出重建此目標的命令,那麼make將會尋找一個合適的隱含規則來重建此目標。

好比,全部的目標文件在config.h發生變化時都須要更新:

objects = foo.o bar.o
foo.o : defs.h
bar.o : defs.h test.h
$(objects) : config.h

 

3.11 靜態模式規則

規則存在多個目標,而且不一樣的目標能夠根據目標文件的名字來自動構造出依賴文件。靜態模式比多目標模式更加靈活,其不須要多個相同的依賴文件。

TARGETS ... : TARGET-PATTERN : PREREQ-PATTERNS ...
    COMMANDS
    ...

TARGETS : 一系列目標

TARGET-PATTERN與PREREQ-PATTERNS中均可以包含模式字符%,%匹配的部分稱爲「莖」,以下所示:

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

對於foo.o,取出「莖」foo,代替模式字符%,則目標foo.o的依賴關係表示爲foo.o : foo.c

 

在使用靜態模式規則時,TARGETS和TARGET-PATTERN必須匹配,不然執行make時將發生錯誤。若是存在一個文件列表,其中一部分符合一種模式,而另外一部分符合另外一種模式,此時咱們可使用filter函數對這個列表進行分類,例如:

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

 

3.12 雙冒號規則

雙冒號用於在多個規則中爲同一個目標指定不一樣的重建命令。須要注意的是,一個目標能夠出如今多個規則中,可是這規則必須屬於同一類型,要麼都是普通規則,要麼都是雙冒號規則

與普通規則的區別以下:

1>  對於一個沒有依賴而只有命令行的雙冒號規則,當make此目標時,規則的命令總會被無條件的執行;而對於普通規則,因爲沒有依賴,所以目標總被認爲是最新的,此規則的命令永遠不會被執行。

2>  當同一個文件做爲多個雙冒號規則的目標時,這些不一樣的規則會被獨立的處理,而不是像普通規則那樣合併全部的依賴到一個目標文件。這就意味着對這些規則的處理就像多個不一樣的普通規則同樣。就是說多個雙冒號規則中的每個的依賴文件被改變以後,make 只執行此規則定義的命令,而其它的以這個文件做爲目標的雙冒號規則將不會被執行。

Newprog :: foo.c
  $(CC) $(CFLAGS) $< -o $@
Newprog :: bar.c
  $(CC) $(CFLAGS) $< -o $@

若是「 foo.c」文件被修改,make將根據「 foo.c」文件重建目標「 Newprog」;而若是「 bar.c」被修改,那麼「 Newprog」將根據「 bar.c」被重建。若是是普通規則,將會發生錯誤。

 

3.13 自動產生依賴

假設main.c中包含頭文件defs.h,那麼使用gcc時能夠經過選項「-M」來自動尋找main.c中包含的頭文件defs.h,並生成文件的依賴關係,若是執行:gcc –M main.c,將輸出:main.o :main.c defs.h。使用「-M」選項時,其輸出結果中也包含了對標準庫頭文件的依賴關係描述,若是不須要標準庫描述,則使用「-MM」便可。

舊版本的 make中,使用編譯器此項功能一般的作法是:在 makefile 中設置一個僞目標「depend」的規則來定義自動產生依賴關係文件的命令。輸入「make depend」將生成一個稱爲「depend」的文件,其中包含了全部源文件的依賴規則描述。makefile 中使用「include」指示符包含這個文件便可。

新版本的 make 中,推薦的方式是爲每個源文件產生一個描述其依賴關係的makefile 文件。對於一個源文件「NAME.c」,對應的這個 makefile 文件爲「NAME.d」。「NAME.d」中描述了文件「NAME.o」所要依賴的全部頭文件。採用這種方式,只有源文件在修改以後纔會從新使用命令生成新的依賴關係描述文件「NAME.o」。採用以下的模式規則來自動生成每個.c文件對應的.d文件:

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

上述規則表示全部的.d文件依賴於同名的.c文件。

首先,使用編譯器自動生成依賴文件"$<"的頭文件的依賴關係,並輸出成爲一個臨時文件,"$$$$"表示當前進程號。產生的依賴頭文件包括了全部使用的系統頭文件用戶定義頭文件。若是須要生成的依賴描述文件中不包含系統頭文件,可以使用「-MM」代替「-M」。

而後,使用 sed 處理產生的臨時文件並生成此規則的目標文件。具體以下轉換:例如對一個.c源文件。將編譯器產生的依賴關係:

main.o : main.c defs.h

轉換爲:

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

這樣就將.d 加入到了規則的目標中,其和對應的.o文件文件同樣依賴於對應的.c源文件和源文件所包含的頭文件。當.c源文件或者頭文件被改變以後規則將會被執行,相應的.d 文件一樣會被更新。

最後,刪除臨時文件。

 

使用上例的規則就能夠創建一個描述目標文件依賴關係的.d文件。咱們能夠在makefile中使用include指示符將描述將這個文件包含進來。在執行make時,makefile所包含的全部.d文件就會被自動建立或者更新。makefile中對當前目錄下.d文件處理能夠參考以下:

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

其中,變量「sources」表示源文件列表,「(sources : .c=.d)」根據「source」指定的.c文件自動生成對應的.d文件。

相關文章
相關標籤/搜索