# 初學 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`,則文件會以更改時間進行排序,由晚到早排列,如下圖: ![image](https://hackmd.io/_uploads/HyVDoBxJ0.png) - 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了