Makefile目標匯總和變量的高級用法
規則中的目標形式是多種多樣的,它可以是一個或多個的文件、可以是一個偽目標,這是我們之前講到過的,也是經常使用的。其實規則目標還可以是其他的類型,下面是對這些類型的詳細的說明。
強制目標
如果一個目標中沒有命令或者是依賴,並且它的目標不是一個存在的文件名,在執行此規則時,目標總會被認為是最新的。就是說:這個規則一旦被執行,make 就認為它的目標已經被更新過。這樣的目標在作為一個規則的依賴時,因為依賴總被認為更新過,因此作為依賴在的規則中定義的命令總會被執行。看一個例子:
clean:FORCE
rm $(OBJECTS)
FORCE:
這個例子中,目標 "FORCE" 符合上邊的條件。它作為目標 "clean" 的依賴,在執行 make 的時候,總被認為更新過。因此 "clean" 所在的規則而在被執行其所定義的那個命令總會被執行。這樣的一個目標通常我們將其命名為 "FORCE"。
例子中使用 "FORCE" 目標的效果和將 "clean" 聲明為偽目標的效果相同。
空目標文件
空目標文件是偽目標的一個變種,此目標所在的規則執行的目的和偽目標相同——通過 make 命令行指定將其作為終極目標來執行此規則所定義的命令。和偽目標不同的是:這個目標可以是一個存在的文件,但文件的具體內容我們並不關心,通常此文件是一個空文件。
空目標文件只是用來記錄上一次執行的此規則的命令的時間。在這樣的規則中,命令部分都會使用 "touch" 在完成所有的命令之后來更新目標文件的時間戳,記錄此規則命令的最后執行時間。make 時通過命令行將此目標作為終極目標,當前目標下如果不存在這個文件,"touch" 會在第一次執行時創建一個的文件。
通常,一個空目標文件應該存在一個或者多個依賴文件。將這個目標作為終極目標,在它所依賴的文件比它更新時,此目標所在的規則的命令行將被執行。就是說如果空目標文件的依賴文件被改變之后,空目標文件所在的規則中定義的命令會被執行。看一個例子:
print:foot.c bar.c lpr -p $? touch print
執行 "make print" ,當目標文件 "print" 的依賴文件被修改之后,命令 "lpr -p $?" 都會被執行,打印這個被修改的文件。
特殊的目標
名稱 | 功能 |
---|---|
.PHONY: | 這個目標的所有依賴被作為偽目標。偽目標是這樣一個目標:當使用 make 命令行指定此目標時,這個目標所在的規則定義的命令、無論目標文件是否存在都會被無條件執行。 |
.SUFFIXES: | 這個目標的所有依賴指出了一系列在后綴規則中需要檢查的后綴名 |
.DEFAULT: | Makefile 中,這個特殊目標所在規則定義的命令,被用在重建那些沒有具體規則的目標,就是說一個文件作為某個規則的依賴,卻不是另外一個規則的目標時,make 程序無法找到重建此文件的規則,這種情況就執行 ".DEFAULT" 所指定的命令。 |
.PRECIOUS: | 這個特殊目標所在的依賴文件在 make 的過程中會被特殊處理:當命令執行的過程中斷時,make 不會刪除它們。而且如果目標的依賴文件是中間過程文件,同樣這些文件不會被刪除。 |
.INTERMEDIATE: | 這個特殊目標的依賴文件在 make 執行時被作為中間文件對待。沒有任何依賴文件的這個目標沒有意義。 |
.SECONDARY: | 這個特殊目標的依賴文件被作為中過程的文件對待。但是這些文件不會被刪除。這個目標沒有任何依賴文件的含義是:將所有的文件視為中間文件。 |
.IGNORE | 這個目標的依賴文件忽略創建這個文件所執行命令的錯誤,給此目標指定命令是沒有意義的。當此目標沒有依賴文件時,將忽略所有命令執行的錯誤。 |
.DELETE_ON_ERROR: | 如果在 Makefile 中存在特殊的目標 ".DELETE_ON_ERROR" ,make 在執行過程中,榮國規則的命令執行錯誤,將刪除已經被修改的目標文件。 |
.LOW_RESOLUTION_TIME: | 這個目標的依賴文件被 make 認為是低分辨率時間戳文件,給這個目標指定命令是沒有意義的。通常的目標都是高分辨率時間戳。 |
.SILENT: | 出現在此目標 ".SILENT" 的依賴文件列表中的文件,make 在創建這些文件時,不打印出此文件所執行的命令。同樣,給目標 "SILENT" 指定命令行是沒有意義的。 |
.EXPORT_ALL_VARIABLES: | 此目標應該作為一個簡單的沒有依賴的目標,它的功能是將之后的所有變量傳遞給子 make 進程。 |
.NOTPARALLEL: | Makefile 中如果出現這個特殊目標,則所有的命令按照串行的方式執行,即使是存在 make 的命令行參數 "-j" 。但在遞歸調用的子make進程中,命令行可以並行執行。此目標不應該有依賴文件,所有出現的依賴文件將會被忽略。 |
多規則目標
Makefile 中,一個文件可以作為多個規則的目標。這種情況時,以這個文件為目標的規則的所有依賴文件將會被合並成此目標一個依賴文件列表,當其中的任何一個依賴文件比目標更新時,make 將會執行特定的命令來重建這個目標。
對於一個多規則的目標,重建這個目標的命令只能出現在一個規則中。如果多個規則同時給出重建此目標的命令,make 將使用最后一個規則中所定義的命令,同時提示錯誤信息。某些情況,需要對相同的目標使用不同的規則中所定義的命令,我們需要使用另一種方式——雙冒號規則來實現。
一個僅僅描述依賴關系的描述規則可以用來給出一個或者時多個目標文件的依賴文件。例如,Makefile 中通常存在一個變量,就像我們以前提到的 "objects" ,它定義為所有的需要編譯的生成 .o 文件的列表。這些 .o 文件在其源文件中包含的頭文件 "config.h" 發生變化之后能夠自動的被重建,我們可以使用多目標的方式來書寫 Makefile:
objects=foo.o bar.o
foo.o:defs.h
bar.o:defs.h test.h
$(objects):config.h
這樣做的好處是:我們可以在源文件增加或者刪除了包含的頭文件以后不用修改已存在的 Makefile 的規則,只需要增加或者刪除某一個 .o 文件依賴的頭文件。這種方式很簡單也很方便。
我們也可以通過一個變量來增加目標的依賴文件,使用 make 的命令行來指定某一個目標的依賴頭文件,例如:
extradeps=
$(objects):$(exteradeps)
它的意思是:如果我們執 "make exteradeps=foo.h" 那么 "foo.h" 將作為所有的 .o 文件的依賴文件。當然如果只執行 "make" 的話,就沒有指定任何文件作為 .o 文件的依賴文件。
變量的高級用法
之前已經學習過變量的定義和基本的賦值運算,我們可以更深入的了解一下變量的一些高級的用法。高級使用方法有兩種:一種是變量的替換引用,一種是變量的嵌套引用。這是我們在使用的時候比較常見的兩種使用方法。
變量的替換引用
我們定義變量的目的是為了簡化我們的書寫格式,代替我們在代碼中頻繁出現且冗雜的部分。它可以出現在我們規則的目標中,也可以是我們規則的依賴中。我們使用的時候會經常的對它的值(表示的字符串)進行操作。遇到這樣的問題我們可能會想到我們的字符串操作函數,比如 "patsubst" 就是我們經常使用的。但是我們使用變量同樣可以解決這樣的問題,我們通過下面的例子來具體的分析一下。
實例:
foo:=a.c b.c d.c obj:=$(foo:.c=.o) All: @echo $(obj)
這段代碼實現的功能是字符串的后綴名的替換,把變量 foo 中所有的以 .c 結尾的字符串全部替換成 .o 結尾的字符串。我們在 Makefile 中這樣寫,然后再 shell 命令行執行 make 命令,就可以看到打印出來的是 "a.o b.o d.o" ,實現了文件名后綴的替換。
注意:括號中的變量使用的是變量名而不是變量名的引用,變量名的后面要使用冒號和參數選項分開,表達式中間不能使用空格。第二個變量 obj 是對整體的引用。
上面的例子我們可以換一種更加通用的方式來寫,代碼展示如下:
foo:=a.c b.c d.c obj:=$(foo:%.c=%.o) All: @echo $(obj)
我們在 shell 中執行 make 命令,發現結果是相同的。
對比上面的實例我們可以看到,表達式中使用了 "%" 這個字符,這個字符的含義就是自動匹配一個或多個字符。在開發的過程中,我們通常會使用這種方式來進行變量替換引用的操作。
為什么這種方式比第一種方式更加實用呢?我們在實際使用的過程中,我們對變量值的操作不只是修改其中的一個部分,甚至是改變其中的多個,那么第一種方式就不能實現了。我們來看一下這種情況:
foo:=a123c a1234c a12345c obj:=$(foo:a%c=x%y) All: @echo $(obj)
我們可以看到這個例子中我們操作的是兩個不連續的部分,我們執行 make 后打印的值是 "x123y x1234y x12345y",這種情況下我們使用第一種情況就不能實現,所以第二種的使用更全面。
變量的嵌套使用
變量的嵌套引用的具體含義是這樣的,我們可以在一個變量的賦值中引用其他的變量,並且引用變量的數量和和次數是不限制的。下面我們通過幾個實例來說明一下。
實例 1:
foo:=test var:=$(foo) All: @echo $(var)
這種用法是最常見的使用方法,打印出 var 的值就是 test。我們可以認為是一層的嵌套引用。
實例 2:
foo=bar var=test var:=$($(foo)) All: @echo $(var)
我們再去執行 make 命令的時候得到的結果也是 test,我們可以來分析一下這段代碼執行的過程:$(foo) 代表的字符串是 bar,我們也定義了變量 bar,所以我們可以對 bar 進行引用,變量 bar 表示的值是 test,所以對 bar 的引用就是 test,所以最終 var 的值就是 test。這是變量的二層嵌套執行,當然我們還可以使用三層的嵌套執行,寫法跟上面的方式是一樣的。嵌套的層數也可以更多,但是不提倡使用。
我們再去使用變量的時候,我們並不是只能引用一個變量,可以有多個變量的引用,還可以包含很多的變量還可以是一些文本字符。我們可以通過一些例子來說明一下。
實例 4:
first_pass=hello bar=first var:=$(bar)_pass all: @echo $(var)
在命令行執行 make 我們可以得到 var 的值是 hello。這是變量嵌套引用的時候可以包含其它字符的使用情況。
實例 5:
first_pass=hello bar=first foo=pass var:=$(bar)_$(foo) all: @echo $(var)
這個實例跟上面實例的運行結果是一樣的。我們可以看到這個實例中使用了兩個變量的引用還有其它的字符。
變量的嵌套引用和我們的變量的遞歸賦值的區別:嵌套引用的使用方法就是用一個變量表示另外一個變量,然后進行多層的引用。而遞歸展開的變量表示當一個變量存在對其它變量的引用時,對這變量替換的方式。遞歸展開在另外一個角度描述了這個變量在定義是賦予它的一個屬性或者風格。並且我們可以在定義個一個遞歸展開式的變量時使用套嵌引用的方式,但是建議你的實際編寫 Makefile 時要盡量避免這種復雜的用法。
在實際使用的過程中變量的第一種用法經常使用的,第二種用法我們很少使用,應該說是盡量避免使用變量的嵌套引用。在必須要使用的時候我們應該做到嵌套的層數是越少越好的。因為使用這種方法表達會比較的復雜,如果條理不清楚的話我們就會出錯。並且在給其他人看的時候也會不容易理解。