###### tags: `note` `make`
# GNU Make
[GNU make manual](https://www.gnu.org/software/make/manual/make.html)
## 2. An Introduction to Makefiles
### 2.1 What a Rule Looks Like
- 一個簡單的 makefile 包含的 rules 長得像這樣 :
``` make=
target ... : prerequisites ...
recipe
...
...
```
- target 通常是 program 產生的檔案的檔名,例如可執行黨的名稱或 object files 的名稱。 target 也可以是一個動作的名稱,例如 'clean'
- prerequisite 是一個檔案,用來作為創造 target 的 input 。 target 通常會依賴多個檔案來產生。
- recipe 是 make 會執行的動作/指令。一個 recipe 可能會含有一個以上的指令,可以寫在同一行,或分成多行來寫。要特別注意的是 : 必須要在 recipe **每一行的開頭加上 Tab**。([如果你不想放 Tab 想放其他的字元也可以][special variables])
- 通常 recipe 存在於含有 prerequisites 的 rule 之中,用途為 : 有任何的 prerequisites 被更新、改變了,就產生 target file 。然而,有些 rule 的 recipe 是為了 target 而存在,這種 rule 就不需要 prerequisites ,例如 'clean' 。
### 2.2 A Simple Makefile
- 這裡有一個簡單的 makefile ,描述檔案之間的關係 : 可執行檔 edit 依賴八個 object files ,八個 object files 依賴八個 C source 和 三個 header files 。
``` make=
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
```
- 在範例中可以發現,所有的 C files 都有 include defs.h ;只有部分的檔案 include command.h 和 buffer.h 。
- 我們可以使用反斜線 **\\** 把很長的一行分割成多行,這樣比較容易閱讀。
- 想要產生可執行檔 edit ,要輸入 :
``` terminal
make
```
- 要用這個 makefile 來刪除這個資料夾裡面的執行檔和所有的 object files 要輸入 :
``` terminal
make clean
```
- 在這個範例中, targets 包含 edit 、 main.o 、 kbd.o ... 等; prerequisites 例如 : main.o 的 prerequisites 為 : main.c 和 defs.h ; Recips 例如 : cc -c main.c 、 cc -c kbd.c 。
- 當 target 是檔案時,如果它的 prerequisites 更新、更動了, target 則需要重新編譯(recompile)或重新連結(relink)。
- 自動產生的 prerequisites 必須要先更新。在範例中, edit 依賴其他八個 object files ,如果當前資料夾裡面沒有相對的 object files ,則會執行 object file 的 rule 。
- recipe 通常會跟隨在 target 後面。 recipe 開頭必須要加上 Tab ,用意是將指令和其他 makefile 的內容做區分。
- target 'clean' 並不是一個檔案,它只是一個動作的名稱。通常我們不會想要執行這個 rule ,所以 'clean' 不會成為其他 rule 的 prerequisite ,當你特別要求 make 的時侯才會去執行。這種 target 通常是用來執行特殊的指令,只會執行指定的動作不會和檔案有關,所以不含有 prerequisites ,這種 target 稱為 **Phony Targets** 。
### 2.3 How make Processes a Makefile
- 在預設的情況下, make 會從第一個 target 開始執行,稱為 **default goal** 。( Goals 就是 make 要更新的 targets )
- 在上面的範例中, default goal 就是要更新執行檔 edit ,因此把更新執行檔的 rule 放在 makefile 的最前面。
- 當執行 make 時, make 會讀取當前資料夾中的 makefile ,並且開始執行第一個 rule 。在範例中,第一個 rule 是 relink edit ,但在完全執行這個 rule 之前, make 必須要執行 edit 依賴的 rules ,也就是和 object files 相關的 rules 。每個 object file 都會執行和自己相關的 rule ,這些 rule 藉由編譯 source file 來更新各個 .o 檔。當 prerequisites 中的 source file 或 header files 比 object file 還要新(可能是檔案更新,或在 prerequisites 加入新的檔案),或是 object file 不存在的時候,必須要重新編譯(recompilation)。
- 重新編譯之後, make 會決定是否要重新連結(relink) edit 。在 edit 檔案不存在,或是有任何 object file 比 edit 還要新(例如 : 更新 source file,重新編譯之後產生新的 object file)的時候, relink 一定會執行。
### 2.3 Variables Make Makefiles Simpler
- 在上面的範例中,我們必須在 **edit** rule 列出所有的 object file 兩遍
``` make
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
```
- 當有新的 object file 要新增到 list 時,這種重複的狀況容易導致錯誤,你可能會忘了在某些 list 裡面加上新的檔案。我們可以透過使用變數來減少這種錯誤的發生,並且簡化 makefile 。
- 變數(Variables)允許一個文字的字串被定義一次,並且可以在定義之後被使用(代換)。
- 定義變數的方法 : `varname = file1 file2 ... `
- 使用變數的方法 : `$(varname)`
- 將範例中的 object file list 用變數代換之後的結果如下 :
``` make=
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)
```
### 2.4 Letting **make** Deduce the Recipes
- 當你要編譯 C source files 的時候,可以不必把編譯 C source file 的指令打出來,**make** 有一個內建的規則(implicit rule),這個規則是使用 `cc -c` 的指令來編譯 .c 檔(C source file),得以更新相對應的 .o 檔(object file)。
- 使用這種省略方法時, .c 檔也可以從 prerequisites 中省略。
``` make=
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
rm edit $(objects)
```
- 這就是實務上常用的 makefile 寫法。(**.PHONY** 之後會提到)
### 2.5 Another Style of Makefile
- 當 makefile 的 object files 都使用上述的 implicit rules 來創造時,你可以使用另一種風格來寫 makefile 。
- 這種風格是以 prerequisites 做分類,有相同的 prerequisites 的 object files 放在一起。(前面的風格是把有相同 target 的 prerequisites 放在一起)
``` make=
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
.PHONY : clean
clean :
rm edit $(objects)
```
- 在範例中,**defs.h**是所有 object files 的 prerequisite ; **command.h** 和 **buffer.h** 是某幾個指定的 object files 的 prerequisite 。
### 2.6 Rules for Cleaning the Directory
- 除了編譯程式, makefile 還可以做一些其他的工作,像是清除當前目錄中的 object files 和 執行檔。
- 在範例中,我們加上了一些東西讓 rule 可以處理一些我們不想遇到的狀況。
``` make
.PHONY : clean
clean :
-rm edit $(objects)
```
- 在當前目錄中,如果含有檔名為 **clean** 的檔案,用上面的方法可以避免 **make** 被混淆,避免 object files 和執行檔在不被預期的狀況下被刪除。
- 像 **clean** 這類的 rule 不應該被放在 makefile 的開頭,因為這不是我們預設要做的事情,在之前的範例中,我們想要做的事情是 rule **edit** 被執行,所以把它放在所有 rule 的最上面。
- 因為 **clean** 不是 **edit** 的 prerequisite ,所以當使用者在終端機輸入 `make` 時,並不會被執行。只有在使用者輸入 `make clean` 的時候才會執行 **clean** rule 。
## 3. Writing Makefiles
### 3.1 What Makefiles Contain
Makefiles 包含了五種東西 : *explicit rules*, *implicit rules*, *variable definitions*, *directives*, and *comments* 。
1. **explicit rule** : explicit rule 要傳達何時以及如何標記一個或多個檔案(也就是 targets)。 explicit rule 會列出 targets 依賴的其他檔案,也就是 prerequisites of target 。而且可能會提供一個 recipe ,用來創造或更新 targets 。
2. **implicit rule** : implicit rule 要傳達何時以及如何藉由檔案名稱,重新製造一群檔案。implicit rule 會描述一個 target 是如何依賴和自己名稱像似的檔案(例如 .o 檔和 .c 檔之間的關係)。
3. **variable definition** : variable definition 是一行指令用來表達在定義變數之後,一個文字字串可以被文字(variable name)代換。
4. **directive** : directive 是一個指令,讓 **make** 可以在讀取 makefile 時可以做一些特別的事情,包含 :
- 讀取其他 makefile
- 決定是否 使用/忽略 部分的 makefile
- 定義一個多行的 verbatim string 的變數
5. **#** : makefile 註解的開頭。可以使用反斜線讓註解延續到下一行。如果需要用到井字號,在前面加一個反斜線即可 `\#` 。要特別注意,不能在使用變數(variable references)或呼叫函式(function calls)時使用註解,此時的 # 會被當成 variable reference 或 function call 裡文字的部分。在 recipe 中的註解會被傳送給 shell , shell 會決定如何翻譯、解釋(interpret)它,被傳送的註解是否為指令是由 shell 來判定。在 **define** directive 中,註解不會被忽略,會完整地和變數值一起保留。當變數被展開時,這些註解也會被當成 **make** 的註解或是 recipe ,取決於變數是在哪裡被展開。
#### 3.1.1 Splitting Long Lines
- makefiles 是使用 "line-based" 的語法, newline 字元是特別的,被標註為 statement 的結束。 **GNU make** 沒有限制 statement 的長度。
- statement 太長的話可讀性很差。你可以使用反斜線來迴避作為結尾的 newline 字元,讓 statement 延續到下一行。
- 我們將 statement 分為兩種 :
1. physical lines : 只有一行的 statement
2. logical lines : 用反斜線形成的多行 statement
- 在 recipe 以外的地方,反斜線+newline 會被轉換成一個單一的空白(space)字元,因此會和其他相鄰的 space 一樣被省略成一個單一的 space 。
- 如果 **.POSIX** 這個特別的 target 被定義了,反斜線+newline 會遵守 **POSIX.2** 的規範 :
1. backslash 之前的 whitespace 不會被移除
2. 連續的 backslash/newlines 不會被濃縮
### 3.2 What Name to Give Your Makefile
- 預設的情況下,當 **make** 在尋找 makefile 時,會按照順序嘗試下列這些檔名 : **GNUmakefile**, **makefile**, **Makefile** 。
- 通常你應該把你的 makefile 稱為 **makefile** 或 **Makefile** ,後者稍微好一點,因為會被放在目錄比較前面的地方,而且會放在一些重要檔案(例如 **README** )的附近。除非你有特別要使用 **GNU make** 的 makefile ,盡量不要使用 **GNUmakefile** 作為檔名,如此一來,其他版本的 **make** 才能找到你的 makefile ,其他 **make** 程式會尋找 **makefile** 和 **Makefile** ,不會尋找 **GNUmakefile** 。
- 如果 **make** 沒有找到上述那些名稱的檔案,它就不會使用任何 makefile ,這時你就必須要藉由 command argument 標註你希望 **make** 執行的目標工作。
- 如果你想要使用非正式(nonstandard)的名稱作為 makefile 的檔名,你可以用 `-f filename` 或 `--file=filename` 參數來標註你的 makefile 檔名。如果你使用超過一個 `-f` 或 `--file` ,你可以標註多個 makefiles ,所有的 makefiles 會被按照標註的順序有效的連接起來,這時候預設的 makefile name 就不會被自動檢查,除非你也使用 `-f` 或 `--file` 來標註。
### 3.3 Including Other Makefiles
- **include** directive 會告訴 **make** 先暫停讀取當前的 makefile ,讀取完一個或多個其他的 makefiles 之後再繼續讀取當前的 makefile 。語法為 : `include filenames...` 。如果 filenames 為 empty ,沒有任何東西會被 include ,也不會產生、印出任何錯誤。
- 註解可以從 **include** 這一行的結尾開始。
- 如果 filenames 包含 variable, function references ,這些 references 會被展開。
- 例如 : 如果你有三個 `.mk` 檔案,`a.mk` 、 `b.mk` 和 `c.mk` ,`$(bar)` 為 `bish bash` ,而下面的 expression
`include foo *.mk $(bar)`
會等價於
`include foo a.mk b.mk c.mk bish bash`
- 會使用到 **include** directives 的一種情況是 : 當一些由各自的 makefile 操作的程式需要使用到同樣的 variable definitions 或是 pattern rules 。
- 另外一個情況是 : 當你從別的 source files 自動產生 prerequisites ;這些 prerequisites 可以被放在要被 include 到 main makefile 的檔案中。這個方法會比以前在 makefile 裡面加上 prerequisites 的方式還要簡潔。
- 如果標註要 include 的名稱不是以 slash 開頭,而且不存在於當前目錄,首先,你使用 `-I` 或 `--include-dir` 標示的目錄會被搜尋,接著,下列的這些目錄(如果存在的話)會被搜尋,順序為 : prefix/include (通常是 /usr/local/include ), /usr/gnu/include, /usr/local/include, /usr/include 。
- 如果被 include 的 makefile 在這些目錄中都沒有被找到,會產生 warning message ,但不會有 fatal error ,含有 **include** 的 makefile 會繼續執行。只有在 remake makefile 失敗時,**make** 才會把搜尋失敗判定為 fatal error 。
- 如果你想要 **make** 簡單的忽略不存在或不能被 remake 的 makefile (不會產生 error message),使用 `-include` 取代 `include` 即可。在不同的 **make** 相容性的考量上, `sinclude` 是 `-include` 的另外一個別名。
### 3.4 The Variable `MAKEFILES`
- 如果環境變數 `MAKEFILES` 被定義了, **make** 會把它的值當成一個額外的 makefiles 的 name list ,這串額外的 makefiles 會先於其他 makefile 被讀取。
- default goal 不會使用到這些 makefiles ,當 list 裡的 makefiles 無法被搜尋時也不會有錯誤。
- `MAKEFILES` 主要用在遞迴引用(recursive invocations)的 **make** 之間。通常不會想要把環境變數設置在 **make** 最上層的引用(top-level invocation)之前,以避免讓 makefile 外部變的雜亂。
- 如果你執行 **make** 時沒有表明特別的 makefile ,在 `MAKEFILES` 裡的一個 makefile 可以幫助內建的 implicit rules 更好的運作。
- 有些使用這會嘗試當登入時,在環境下自動設置 `MAKEFILES` ,並且寫 makefiles 讓這件事情可以運作,但這不是個好主意,如果有 makefile 正在被其他人運作,會導致 makefiles 運作失敗,比較好的方式還是使用 **include** directives 。
### 3.5 How Makefiles Are Remade :question:
- 有些時候 makefiles 可以被其他檔案 remake ,像是 RCS 或 SCCS 檔案,如果一個 makefile 可以被其他檔案 remake ,你可能會想要 **make** 去讀取更新版本之後的 makefile 。
- 為了達到這個目的,在讀取所有 makefiles 之後, **make** 會把所有 makefiles 當成一個 goal target ,並且嘗試去更新這個 target 。如果有一個 makefile 有 rule 可以更新這個 target ,或者 target 有使用 implicit rule ,這個 target 在必要時就會被更新。
- 在所有 makefiles 都被檢查完畢之後,如果任何一個被更動, **make** 會列一份無過錯清單(clean slate),並且再次讀取所有的 makefiles 。
- 如果你知道有一個或多個你的 makefiles 不能被 remake ,而你不想要 **make** 的 implicit rule 執行在這些 makefiles 上,你可以寫一個 empty recipe 的 explicit rule 給這些 makefiles ,讓這些 makefiles 不會被 implicit rule 搜尋到。
- 如果 makefiles 標註 double-colon rule 來 remake 一個檔案,並且有 recipe 沒有 prerequisites ,這個檔案一定會被 remake ,在這個狀況下的 makefiles ,會在每次執行 **make** 時被 remake ,並且在 **make** 運作之後以及讀取到這些 makefiles 時再次被 remake 。
- 這樣會造成一個無窮迴圈 : **make** 會不斷的 remake 這個 makefile 而不會做其他事情。為了避免這個情形, **make** 不會 remake 有 double-colon rule 有 recipe 沒有 prerequisites 的 makefiles 。
- 如果你沒有標示用 `-f` 或 `--file` 讀取任何 makefiles , **make** 會嘗試 remake default makefile names 。
- 當你使用 `-t` 或 `--touch` 選項時,你可能不會想要使用過時的 makefile 來決定要 touch 哪一個 target 。所以 `-t` 選項不會影像 makefiles 的更新,像 `-q` 和 `-n` 一樣。因此 `make -f mfile -n foo` 會更新 `mfile` ,把它讀進來,然後印出更新 `foo` 的 recipe 和 prerequisites ,但不會去執行 recipe 。
- 有時候你可能會希望避免更新 makefiles ,你可以在 command lines 標是這個 makefiles 為 goal 同時標示他們為 makefiles 。當 makefile 的名稱被標示為 goal , options 就會在 makefile 上被啟用。
- 因此, `make -f mfile -n mfile foo` 會讀取 makefile `mfile` ,印出 recipe 但不執行,接著印出要更新 `foo` 的 recipe 。
### 3.6 Overriding Part of Another Makefile
- 在 containing makefile (include 其他人的 makefile)中,你可以使用 match-anything pattern rule 來 remake 任何沒有記載資訊在 containing makefile 的 target , **make** 應該要尋找其他 makefile 。
- 舉例來說,如果你有一個 makefile 叫做 `Makeifle` 表明如何 make target `foo` ,你可以寫一個 makefile 叫做 `GNUmakefile` 裡面包含 :
```make=
foo:
frobnicate > foo
%: force
@$(MAKE) -f Makefile $@
force: ;
```
- 如果你執行 `make foo` , **make** 會找到並讀取 `GNUmakeifle` ,並且看看要如何 make `foo` ,這會需要執行 recipe `frobnicate > foo` 。如果你執行 `make bar` , **make** 在 `GNUmakefile` 內會找不到 make `bar` 的方法,所以它會從 pattern rule 使用 recipe : `make -f Makefile bar` ,如果 `Makefile` 提供更新 `bar` 的 rule , **make** 會使用這個 rule ,對於其他 `GNUmakefile` 沒有表明的 target 也是一樣。
- 這個方法運息的方式為 pattern rule 只有 `%` 這個 pattern ,所以它會符合任何 target 。這個 rule 標示了一個 prerequisite `force` ,用來確保即使 target file 已經存在了這個 recipe 還是會執行。我們給了 `force` target 一個 empty recipe ,避免 **make** 找 implicit rule 來建立它,否則他會對 `force` 自己用同樣的 match-anything rule ,並且建立一個 prerequisite loop 。
### 3.7 How **make** Reads a Makefile
- **GNU make** 在讀取 makefile 時是以兩個階段進行運作。
1. read-in phase : **make** 讀取所有的 makefiles 和 included makefiles 等,初始化所有的變數和所有 implicit 、 explicit rules ,建立所有 target 和它們的 prerequisites 的 dependency graph 。
2. target-update phase : **make** 使用這些內部的結構來決定哪些 targets 會需要被重新建立,以及引用必須的 rules 來做這件事情。
- 了解這個方法非常重要,因為這會直接影響 variable 和 function 的展開。在第一階段裡,展開是立即的,在這種情況下 **make** 會展開建構部分的任何的 variables 或 functions ,如同 makefile 被 parse 。我們稱呼非即時的展開為 **deferred** ,延遲建構(defered construct)的展開會在即時展開之後,或是直到第二階段才被展開。
#### Variable Assignment
- 變數的定義會被下列的方式 parse :
```
immediate = deferred
immediate ?= deferred
immediate := immediate
immediate ::= immediate
immediate += deferred or immediate
immediate != immediate
define immediate
deferred
endef
define immediate =
deferred
endef
define immediate ?=
deferred
endef
define immediate :=
deferred
endef
define immediate ::=
deferred
endef
define immediate +=
deferred
endef
define immediate !=
deferred
endef
```
- 對於 append operator `+=` ,如果右手邊的變數先前被設定為 simple variable(`:=` 或 `::=`),就會被判定為 immediate ,相反的則是 deferred 。
- 對於 shell assignment operator `!=` ,右手邊的變數會被立刻運算並交給 shell ,運算的結果會存在左邊的變數裡,成為一個 simple variable 。
#### Conditional Directives
- conditional directives 會被立刻 parse 。舉例來說, automatic variables 不能被使用在 conditional directives ,因為 automatic variables 不會被設置,直到該 rule 的 recipe 被引用。如果你需要在 conditional directives 中使用 automatic variables ,你必須把條件移進 recipe ,並且使用 shell conditional syntax 。
#### Rule Definition
- 不論 rule 的形式如何,都會被相同的方式展開, :
```make
immediate : immediate ; deferred
deferred
```
- 也就是說, target 和 prerequisite 的部分會被即時的展開,而用來建造 target 的 recipe 永遠都是 deferred ,對這個 general rule 對於 explicit rules 、 pattern rules 、 suffix rules 、 static pattern rules 和 simple prerequisite definitions 都成立。
### 3.8 Secondary Expansion
- **GNU make** 能夠讓 makefile 中,部分或是全部的 target 的 prerequisites 可以進行 second expansion 。要讓 second expension 可以進行,必須在第一個使用 second expansion 的 prerequisite list 之前定義特別的 target : `.SECONDEXPANSION` 。
- 如果這個特別的 target 被定義在兩個階段之間,也就是 read-in phase 的結尾之後,所有被定義在 special target 之後的 target 的 prerequisites 都會被展開第二次。在大部分的情況下第二次展開是沒有作用的,因為所有 variables 、 function references 都會在 makefile 的 initial parsing 被展開。
- 要得到 secondary expansion 的好處,必須要 **escape** makefile 中的 variable 、 function reference 。在這個情況, first expansion 幾乎是 un-escapes 這個 reference 但沒有展開它,而展開會被留到 secondary expansion phase 進行。
- 舉個例子,有個 makefile :
```make=
.SECONDEXPANSION:
ONEVAR = onefile
TWOVAR = twofile
myfile: $(ONEVAR) $$(TWOVAR)
```
- 在第一是展開之後, `myfile` target 的 prerequisites list 會變成 `onefile` 和 `$(TWOVAR)` 。第一個(unescaped) 對於 `ONEVAR` 的 variable reference 被展開了,然而,第二個(escaped) variable reference 被簡單地 unescaped (???),沒有被當成 variable reference 。在第二次展開時,第一個字會再被展開一次,但它已經不包含 variable 、 function reference ,只保留 `onefile` 這個值,然而第二個字現在面呈的普通的 reference to variable `TWOVAR` ,會被展開變成 `twofile` ,最後的結果就是兩個 prerequisites 變成 `onefile` 和 `twofile` 。
- 這裡有一個很有趣的狀況,因為一樣的結果可以用更簡單的方式來達成,藉由使用 prerequisites list 中出現的變數和 unescaped 的變數,不同之處在於變數重新定義設置後的外觀。範例如下 :
``` make=
.SECONDEXPANSION:
AVAR = top
onefile: $(AVAR)
twofile: $$(AVAR)
AVAR = bottom
```
- `onefile` 的 prerequisite 會被馬上展開,解出來的值為 `top` ,然而 `twofile` 的 prerequisite 不會被完全展開,直到第二次展開,得到的值為 `bottom` 。這個方法看起來沒有特別令人震驚,但它和 automatic variables 結合起來會發揮強大的力量。
- 在第二次展開時, automatic variables 會得到它們的展開值。範例如下 :
```make=
.SECONDEXPANSION:
main_OBJS := main.o try.o test.o
lib_OBJS := lib.o api.o
main lib: $$($$@_OBJS)
```
- 在此處,經過第一次展開, `main` 和 `lib` targets 會變成 `$($@_OBJS)` ,在第二次展開時, `$@` 變數會被設置成 target 的名字,所以 `main` target 會產生 `$(main_OBJS)` 或 `main.o try.o test.o` , `lib` 會產生 `$(lib_OBJS)` 或 `lib.o api.o` 。你也可以和 functions 一起混用,只要它們可以被適當的 escape :
``` make=
main_SRCS := main.c try.c test.c
lib_SRCS := lib.c api.c
.SECONDEXPANSION:
main lib: $$(patsubst %.c,%.o,$$($$@_SRCS))
```
- 這個版本可以允許使用者標示 source files 而不是 object files ,並可以得到和前面範例一樣的 prerequisites list 。
- 在第二次展開階段 automatic variables 的賦值,特別是 target name variable `$$@` ,會很像在 recipes 裡面進行賦值,然而兩者之間有一些細微的差異和在不同種類的 rule definitions 起作用的 [corner cases][WhatIsCornerCases] 。
#### Secondary Expansion of Explicit Rules
- 在 explicit rules 的第二次展開過程中, `$$@` 和 `$$%` 會被分別賦值為 file name of target (當 target 是一個 archive member)和 target member name 。 `$$<` variable 會賦值這個 target 第一個 rule 的第一個 prerequisite 。`$$^` 和 `$$+` 會賦值一個 list , list 為所有已經在同一個 target 出現過的 rules 的 prerequisites , `$$+` 會重複 `$$^` 不會,下面的範例會有助於描述這些變數運行的行為 :
```make=
.SECONDEXPANSION:
foo: foo.1 bar.1 $$< $$^ $$+ # line #1
foo: foo.2 bar.2 $$< $$^ $$+ # line #2
foo: foo3 bar.3 $$< $$^ $$+ # line #3
```
- 在第一個 prerequisite list 中,三個 variables 都展開為 empty string 。在第二個 list ,它們分別為 `foo.1` 、 `foo.1 bar.1` 、 `foo.1 bar.1` 。在第三個 list ,它們分別為 `foo.1` 、 `foo.1 bar.1 foo.2 bar.2` 、 `foo.1 bar.1 foo.2 bar.2 foo.1 foo.1 bar.1 foo.1 bar.1` 。
- Rules 第二次展開是按照 makefile 中的順序進行,除了有 recipe 的 rule 之外,這種 rule 最後才會被賦值。
- varialbes `$$?` 和 `$$*` 無法被取用,會被展開為 empty string 。
#### Secondary Expansion of Static Pattern Rules
- static pattern rules 的第二次展開和 explicit rules 相同,除了 `$$*` 會被設置到 pattern stem 。
#### Secondary Expansion of Implicit Rules
- 當 **make** 搜尋 implicit rule ,它代入 stem 然後對符合 target pattern 的所有 rule 執行第二次展開, automatic variables 的值和 static pattern rules 一樣,如範例 :
```make=
.SECONDEXPANSION:
foo: bar
foo foz: fo%: bo%
%oo: $$< $$^ $$+ $$*
```
- 當 implicit rule 嘗試在 target `foo` 運作時, `$$<` 展開為 `bar` , `$$^` 展開為 `bar boo` , `$$+` 展開為 `bar boo` , `$$*` 展開為 `f` 。
- 要注意一下 directory prefix (D) 再展開後會被加在所有 prerequisites list 的 patterns ,如範例 :
```make=
.SECONDEXPANSION:
/tmp/foo.o:
%.o: $$(addsuffix /%.c,foo bar) foo.h
@echo $^
```
- 在第二次展開和 directory prefix 重新建構之後,這個 prerequisite list 印出的結果為 `/tmp/foo/foo.c /tmp/bar/foo.c foo.h` 。如果你不喜歡這種 reconstruction ,在 prerequisites list 中你可以用 `$$*` 取代 `%` 。
## 4 Writing Rules
- rule 出現於 makefile 中,並傳達如何 remake 某些檔案,也就是 rule 的 targets (通常一個 rule 只會有一個 target)。 rule 會列出其他的檔案( target 的 prerequisites ),而 recipe 用來創造或更新 target 。
- rules 的排列順序不太重要,除了決定誰是 default goal 。 default goal 是第一個 makefile 中,第一個 rule 的 target ,如果第一個 rule 有多個 targets ,只有第一個會被當成 default goal ,這裡有兩個例外 :
1. 一個以 period (?) 開頭的 target 不是 default goal ,除非它包含一個或多個 slash 。:question:
2. 定義了 pattern rule 但對於 default goal 沒有任何影響的 target 。:question:
### 4.1 Rule Syntax
- 通常 rule 會長的像這樣 :
```make=
targets : prerequisites
recipe
...
```
或是這樣 :
```make=
targets : prerequisites ; recipe
recipe
...
```
- targets 為檔案名稱,用 spaces 隔開。 Wildcard characters 可能被使用,而 `a(m)` 的名稱形式表示檔案 `a` 裡面的 member `m` 。通常一個 rule 只有一個 target ,但偶爾會需要更多個。
- recipe 以 tab character 作為開頭,或是 `.RECIPEPREFIX` variable 裡的第一個字元。第一個 recipe 可能會出現在 prerequisites 的下一行,以 tab character 作為開頭,也可能和 prerequisites 同一行,以 semicolon 一起加在後面,兩個方法功能都一樣,差在語法而已。
- 因為 dollar signs 被用於 **make** variable references 的開頭,如果你想要在 target 或 prerequisite 使用 dollar signs ,你必須要寫兩個,寫成 `$$` ,如果你已經啟用 secondary expansion ,而你希望文字意義上的 dollar sign 出現在 prerequisites list 中,你必須要寫 4 個 dollar signs `$$$$` 。
- rules 告訴 **make** 兩件事情 :
1. targets 何時會逾期 : 逾期的評斷標準被標示在 prerequisites ,一個 target 如果不存在,或是比任何 prerequisites 還要舊(以最後更動時間(last-modification)來比較),它就是逾期的。
2. 必要時該如何更新它們 : 如何更新是由 recipe 標示, recipe 為一行或多行的指令,會被 shell 執行。
### 4.2 Types of Prerequisites
- 有兩種不同的種類的 prerequisites 是 `GNU make` 真的可以理解的 :
1. normal prerequisites : normal prerequisite 會製造兩個狀態 :
1. 它會在要被引用的 recipe 加上排列順序。 target 的所有對於 prerequisites 的 recipes 都會在 target 執行之前,先執行過。
2. 它會加上依賴關係(dependency relationship) : 如果任何的 prerequisite 比 target 還要新,這個 target 就會被判定為逾期,並重新建造。
- 有時候會出現一種情況,你想要在會被引用的 rules 加上特別的順序,但這些 rules 在執行時不會強迫 target 更新。這種情況下,你會想要定義 order-only prerequisites 。
2. order-only prerequisites : order-only prerequisites 可以在 prerequisites list 中,藉由由放置 pipe symbol (|)來標示,任何 pip symbol 左側的 prerequisites 都是 normal ,在右側的都是 order-only :
`targets : normal-prerequisites | order-only-prerequisites`
- normal prerequisites 可能為 empty ,所以你可能仍然對同樣的 target 宣告很多行 prerequisites ,它們會被妥當的附加, normal prerequisites 會被加在 normal prerequisites list , order-only prerequisites 會被加在 order-only prerequisites list 。
- 注意,如果你對 normal 和 order-only prerequisite 宣告同樣的檔案, normal prerequisite 會有較高的優先權(precedence)。
- 看個範例,你的 targets 被放置在分開的目錄,而這個目錄在 **make** 執行隻錢可能不存在。在這種情況下,你想要在任何 target 放進目錄之前建立這個目錄,因為當有檔案被新增、移除、改名時,目錄的 timestamps 會改變,我們不希望每當目錄的 timestamps 改變時 rebuild 所有的 target 。有個方法是使用 order-only prerequisites 來管理這個狀況,讓目錄變成所有 targets 的 order-only prerequisite :
```make=
OBJDIR := objdir
OBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o)
$(OBJDIR)/%.o : %.c
$(COMPILE.c) $(OUTPUT_OPTION) $<
all : $(OBJS)
$(OBJS): | $(OBJDIR)
$(OBJDIR):
mkdir $(OBJDIR)
```
[special variables]: https://www.gnu.org/software/make/manual/make.html#Special-Variables
[WhatIsCornerCases]: https://en.wikipedia.org/wiki/Corner_case