1. 程式人生 > 實用技巧 >Makefile目標彙總和變數的高階用法

Makefile目標彙總和變數的高階用法

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 時要儘量避免這種複雜的用法。
在實際使用的過程中變數的第一種用法經常使用的,第二種用法我們很少使用,應該說是儘量避免使用變數的巢狀引用。在必須要使用的時候我們應該做到巢狀的層數是越少越好的。因為使用這種方法表達會比較的複雜,如果條理不清楚的話我們就會出錯。並且在給其他人看的時候也會不容易理解。