系列文章目錄
Make學習一:make初探
Make學習二:makefile組成要素
文章目錄
- 系列文章目錄
- 前言
- 默認目標
- 規則語法
- order-only prerequisites
- 文件名中的通配符
- 偽目標 Phony Targets
- 沒有 Prerequisites 和 recipe
- 內建特殊目標名
- 一個目標多條規則或多個目標共享 prerequisites
- 自動生成依賴
- 總結
前言
前文 Make學習二:makefile組成要素 中提到,Makefile 組成中的重要成員就是顯示規則。本文重點在編寫規則上。
顯示規則的格式如下:
target ... : prerequisites ...recipe...
引用前面 Make學習一:make初探 中的話,簡單來說,一條規則就像一道菜。如果說 target
是我們的西紅柿炒雞蛋,那 prerequisites
就是西紅柿炒雞蛋的依賴,或者說輸入,簡單點就是西紅柿和雞蛋。而 recipe
就是西紅柿炒雞蛋的菜譜,也就是要做成這道菜,需要什么步驟,每一步要怎么做。 實際上,recipe
確實是菜譜的意思,prerequisites
意為先決條件。
默認目標
每一條規則在 Makefile 中的 書寫順序一般不影響功能,但會決定默認目標(default goal),即如果你運行 make 沒有指定目標,默認會構建的那個目標。
默認目標的規則是:取第一個 Makefile 里第一條規則的第一個 target。
因此,通常會把整個程序編譯的規則寫在 Makefile
最前面,這個規則的目標經常叫做 all
。類似,make 的約定。
如下示例,直接運行 make 輸出 Hello, world!:
all: hellohello:@echo "Hello, world!"
規則語法
一條規則的基本寫法是:
target ... : prerequisites ...recipe...
targets 一般是文件名,可以用空格分隔多個目標。也可以用 通配符(wildcards)。
配方(recipe)行必須以 Tab 開頭(這一點非常重要!)。 也可以通過設置 .RECIPEPREFIX 變量來改成別的符號
一條規則可以有:
- 多個依賴(prerequisites);
- 多個目標(targets);
- 也可以沒有依賴(比如 .PHONY 規則);
- 或者沒有 recipe(表示目標是偽目標或不需要命令)。
多個依賴示例:
edit: main.o kbd.o command.o display.ogcc -o edit main.o kbd.o command.o display.o
多個目標示例:
clean temp:rm -f *.o temp
這里 clean 和 temp 是 兩個目標,共享同一個配方。
長行可以用 \ 續行,但 recipe 不需要續行,必須每行一個 Tab。
規則的作用:如果目標不存在或目標的修改時間早于任何一個依賴,則按照 recipe 去更新它們
order-only prerequisites
有時你會想要某個依賴在目標之前被構建,但不希望因為這個依賴的更新導致目標被重建。這就需要用 order-only prerequisite(僅順序依賴)。
簡單一句話概括:該依賴必須存在,但同時它的更新不能影響到 target 的更新。那么你就需要 order-only prerequisite
寫法:target : normal-prerequisites | order-only-prerequisites
使用管道符把 order-only-prerequisites 放在右邊即可。
例如:我們想把當前目錄下的 foo.c 和 bar.c 文件構建出來的 foo.o 和 bar.o 放在 obj 目錄中。但這個目錄可能一開始不存在。我們需要:先保證 obj/ 目錄存在;但不要因為 obj/ 目錄變化而導致目標重建。則 Makefile 的內容如下所示:
OBJDIR := obj
OBJS := $(OBJDIR)/foo.o $(OBJDIR)/bar.oall: $(OBJS)# 創建 obj/ 目錄(order-only)
$(OBJDIR)/%.o : %.c | $(OBJDIR)@echo "Compiling $< to $@"@cp $< $@# 僅順序依賴:保證 obj/ 目錄存在,但不影響重建判斷
$(OBJDIR):@echo "Creating directory $@"@mkdir -p $@
第一次執行 make
,輸出如下:
Creating directory obj
Compiling foo.c to obj/foo.o
Compiling bar.c to obj/bar.o
即便我們在 obj
目錄下通過命令touch obj/temp
創建文件,執行 make
依舊不會有任何更新:make: 對“all”無需做任何事。
文件名中的通配符
在 Makefile 里,一個文件名可以使用通配符(wildcard characters)來匹配多個文件。支持的通配符和 Linux shell 里的一樣:*
匹配 任意長度的任意字符,?
匹配任意單個字符,[abc]
匹配括號中任意一個字符。~
(波浪號)在文件名開頭(單獨出現或后跟 /)代表當前用戶根目錄。
一條規則分 target,prerequisite 和 recipe。target 和 prerequisite 中的 通配符由 make 來展開。而 recipe 中的通配符會把命令傳遞給 shell 由 shell 來處理。
例如:以下 Makefile 執行 make 之后會打印出所有更新之后的 .c 文件內容。$? = 依賴中比目標 print 新的文件。
print: *.c@echo $?
但變量中的通配符不會自動展開,需要使用函數 $(wildcard)。 例如:objects = *.o
中變量賦值并不會展開,objects 只是 “*.o” 字符串。
此時,當我們通過以下 Makefile 來執行 make 命令:
objects = *.ofoo : $(objects)cc -o foo $(CFLAGS) $(objects)
若當前目錄下沒有任何 .o 文件,則 *.o 就不會展開,則會出錯:make: *** 沒有規則可制作目標“*.c”,由“*.o” 需求。 停止。
為了解決這個問題,則需要在變量定義時則進行通配符展開為文件名列表。使用 wildcard 函數,格式:$(wildcard pattern...)
。
objects := $(wildcard *.o)
foo : $(objects) cc -o foo $(CFLAGS) $(objects)
若當前目錄下沒有任何 .o 文件,則 *.o 仍舊會展開,只是變量 objects 為空字符串。報錯:cc -o foo
和 cc: fatal error: no input files
偽目標 Phony Targets
所謂 偽目標(phony target),指的是根本就不是某個實際文件名的目標,而是一個用于明確執行某個配方(recipe)的名字。
使用偽目標的兩個主要原因是:避免與同名文件沖突 和 提高性能。例如,如下 Makefile 內容:
clean:rm *.o temp
上面例子中,clean 不是實際會被創建的文件。因此,每次運行 make clean 時,都會執行 rm 命令。
如果當前目錄里真有一個名字叫 clean 的文件,make clean 就會誤以為 clean 已是最新,不執行刪除操作
使用 .PHONY: clean
后,clean
的配方 總是會執行,不會被任何文件阻止。
沒有 Prerequisites 和 recipe
如果一個規則 既沒有依賴(prerequisite)也沒有配方(recipe),并且它的目標文件不存在,那么每次 make 運行時都會認為這個目標已經“更新”。這意味著:所有依賴于這個目標的規則,每次都會執行它們自己的配方。
這個機制常用于強制重新構建(不論文件是否真正變化)。例如以下示例:
clean: FORCErm $(objects)FORCE:
內建特殊目標名
有一些目標名稱如果出現在 Makefile 中,會具有特殊的含義。這些名字通常以 .
開頭。
.PHONY
(偽目標聲明),.PHONY
后面列出的所有目標都被認為是偽目標(phony targets)。
.SUFFIXES
目標后面列出的是用于后綴規則 suffix rules的后綴列表。可以用 .SUFFIXES:
清除所有后綴規則(這樣就不會自動匹配如 .c → .o 這些規則了)。
.DEFAULT
(沒有匹配規則時的最后手段),如果一個目標沒有任何匹配的規則(無顯式也無隱式規則),就會使用 .DEFAULT
提供的配方。
.DEFAULT:@echo "No rule for $@"
等等等等
一個目標多條規則或多個目標共享 prerequisites
例如:kbd.o command.o files.o: command.h
這條 Makefile 內容中,多個目標同時依賴同一個頭文件。此時可以用 $@
來區分當前到底是哪個目標。
一個文件可以同時是多條規則的目標(target)。來自不同規則的所有依賴(prerequisites)會合并成一個總的依賴列表。只要目標比任何一個依賴舊,就會執行配方(recipe)。
例如以下 Makefile 的內容:
objects = foo.o bar.ofoo.o : defs.hbar.o : defs.h test.h$(objects) : config.h
foo.o
的依賴會合并成一個列表:config.h + defs.h
自動生成依賴
在程序的 Makefile 中,你通常需要寫很多這種規則:main.o: defs.h
。main.c
里用了 #include "defs.h"
,那么你就得寫 main.o: defs.h
。
你寫這個規則是為了讓 make 知道:如果 defs.h
變了,就得重新編譯 main.o
。如果你的程序很大,#include
很多,每次增加或刪掉頭文件都得手動改 Makefile,非常麻煩,容易出錯。
為了解決這個問題,現代 C 編譯器可以自動幫你生成這些依賴規則。它們會掃描你的源碼里的 #include 語句,通常使用 -M 選項來實現。例如:cc -M main.c
它會輸出:main.o : main.c defs.h
所以你不再需要自己寫這些規則,編譯器會幫你搞定。
總結
完結撒花!!