# 初學 GNU Make
## GNU make installation
使用下方指令安裝:
``` bash
% sudo apt install make
```
## Make Command
- Recursive Use of make
可以就由多個makefile組成一個大的執行系統
``` bash
% $(MAKE) -C subdir targetName # -C option為current directory意思, 後方可以再附註sudir中makefile的target name
% cd subdir && $(MAKE) targetName # 與上方效果相同
```
## Makefile
在terminal輸入make,GNU make會依照順序`GNUmakefile`, `makefile`, `Makefile` **尋找**在當前目錄底下的檔案來執行。
- Rule looks like
```
target : prerequisites
<tab>recipe
```
存在多個target時,單純的`make` command執行第一個。
* target:
通常是executable或object file**被產出**的名稱,也可以是make要執行的動作名稱。
* prerequisites:
- timestamp of file
使用linux command:`ls -lt`,則文件會以更改時間進行排序,由晚到早排列,如下圖:

- target通常會有一些depend file, 則prerequisites為target的input file。如一個大專案可能會有多個已經被事先compile的object file,為了不浪費時間重複compile,則可將那些file加入prerequisites的清單中。prerequisites有兩種寫法,以pipe symbol作為分隔,如下方例子:
```bash
targets : normal-prerequisites | order-only-prerequisites
# normal-prequisites為常用的方式,當發現prerequisites的timestamp比target還新,則強制更新target。
# order-only-prerequisites則相反,若發現target比prequisites還舊,也不會更新target。
```
- example:
若target被放不同的資料夾,如下方例子,下指令`make all`,則先建立objdir資料夾,.o檔則在objdir資料夾中產生,由於objdir資料夾內部新增的.o檔被更新,則資料夾的timestamp也變新。**這可能導致小小的修改.c file後,以資料夾作為prequisites的target都需要二次更新**。為了避免,則將資料夾作為order-only-prerequisites,後需相關target則不需要更新。
```bash
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)
```
* recipe:
為指令make後,需要執行的command。每一行最前方都要有一個`<tab>`
* A Simple Makefile example
下方範例為使用8個depend object file製作一個名為`edit`的exexutable file。
``` Makefile=
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
```
::: info
- 當prerequisties的任何file被更改了,則會對該target做recompile的動作
- clean屬於特殊target,為一個make action不是executabel file name,這個target不能隨便被使用,不建議有任何prerequisties或把clean放在任何prerequisties中。
- Makefile的process為:
1. 執行第一個rule`edit`
2. update each depend file in prerequisties
3. 用其他以object file為target的rule檢查是否depend file需要被更新
4. 若edit不存在或 prerequisties file有被更動,則生成新的edit executabel file
:::
- Simplify Makefile
1. 使用variable列舉所有的object file,則可確保不會有遺漏的object。
2. 單純的compile成object file,預設是以同樣檔名的c file來編譯,不須額外指令 `cc -c main.c -o main.o`。
3. .PHONY表示即使有名稱為clean的executable也不會影響`make clean` remove file的target。
```
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)
```
- Variable
指令`make`分成兩個phase,**first phase**為讀取整份makefile,初始化variable,並確認所有資料的dependency問題(target and prerequisite section),**second phase**為判斷哪些target需要被執行和執行recipe section指令。而assign variable的operator可分成在first phase就會被指定的 *immediate和在後續才會被決定的*deferred*。
>所謂**展開**表示variable可能會透過其他variable做assign的動作,則deferred表示要依行數向下查找出真正的值才能作為variable value。
由於可能與某些特殊巨集衝突,官方建議**variable以小寫為主**,在取用variable時,將等號左邊變數名稱放在`$()`括號中,與c語言中的reference意思相近。
|operator|type|說明|
|:-:|:-:|:--|
|=|deferred|有**recursive**的性質,若等號右邊為variable則依照行數向下找到真正的variable,可能拖慢整個makefile,`test = $(test)` 為一個死迴圈|
|?=|deferred|若該變數先前沒有被define則依照等式右方assign|
|:=|immediate|若等號右邊為variable則,則以該variable最近一次出現為該值|
|::=|immediate||
|:::=|immediate with escape||
|+=|deferred/immediate|若原先變數已經有值,則在後方繼續增加等號右邊的值,間隔一的whitespace|
|!=|immediate||
- 範例程式
## 延伸補充
此處紀錄其他在寫Makefile可能會用到的額外語法
* Comments (`#`)
在Makefile中以#作為開始comments的特殊字元,若直接放在backslash後方`\#`則形成用於輸出字元的`#`並非comments功能
```
HELLO = 'hello \#\
world' # comment
all:
@echo $(HELLO)
-----------
terminal output after make command:
hello # world
```
* Splitting Long Lines (backslash `\`)
為了增加Makefile的可閱讀性,往往會使用backslash來作為單行的跳脫字元,可以有多行,直到最後一行沒有 backslash為都視為同一行。**backslash之後必定為<\enter>換行**。其中可細分為non-recipe, recipe兩者
- non-recipe
backslash表示`space` character,與$LaTex$有異曲同工之處,多個backslash則表示多個space。
```
var := one\
word
var := one \
word
var := one word
#三者等效,backslash再多的空白都只有一個而已
```
- recipe
backslash表示單行拆成多行,若沒有額外的空白鍵,則不會有任何空白。
```
all :
<tab>@echo no\
<tab>space
<tab>@echo one \
<tab>space
-----------
terminal output after make command:
nospace
one space
```
* Subtitutes reference (\$(var:a=b)/\${var:a=b})
以whitespace區份word,將在variable中所有word最後方的a取代成b
```
sub = a.o b.o c.o d.o o.o e.e
all :
@echo $(sub:o=c)
terminal output after make command:
a.c b.c c.c d.c o.c e.e
```
* Pattern rules (`%`)
用%表示`某個單字`,則使用語法 `%.o : %.c`表示target與prerequisite名稱相同
* Automatic Variables (\$@, \$%, \$<, \$? ...)
**只能在recipe中使用**,不能在target中使用。
|variable|說明|
|:-:|:--|
|\$@|file name of target, 若同一個rule有許多target,則為啟動該recipe的target|
|\$%|related to *Archive number*|
|\$<|prerequisite list 中的第一個|
|\$?|比當前target還要新的數個prerequisite,亦即被重新編輯過的prerequisite|
|\$^|all prerequisite|
* Function for file name
* $(addprefix prefix, names1 name2 name3..):
空格區隔的name list中**前方個別**加上prefix的字串,如下方例子:
``` bash
$(addprefix src/, foo bar)
# 結果等效為 src/foo src/bar,兩個字串同樣以whitspace間隔
```
* $(shell cat foo)
以whitespace取代foo檔案中的newline,如下方例子:
``` bash
# inside foo file:
# file1
# file2
# file3
contents := $(shell cat foo)
# 等效於 contents = file1 file2 file3
```
# 參考資料
[GNU make](https://www.gnu.org/software/make/manual/make.html#Using-Variables)
目前遇到新語法再來找spec了