概述
本文將介紹Makefile種注釋、回顯、通配符、變量、循環判斷、函數
注釋
Makefile中只有單行注釋,沒有多行注釋,注釋以 # 開頭。以下Makefile注釋片段節選自Lua的Makefile
# Makefile for installing Lua # See doc/readme.html for installation and customization instructions. # == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT ======================= # Your platform. See PLATS for possible values. PLAT= none
echoing(回顯)
通常,make在執行命令行之前會把要執行的命令行進行輸出。我們稱之為“回顯”,就好像我們輸入命令執行一樣。
@
如果要執行的命令行以字符“@”開始,則make在執行時這個命令就不會被回顯。典型的用法是我們在使用“echo”命令輸出一些信息時。如:
@echo 開始編譯XXX模塊......
當make執行時,將輸出“開始編譯XXX模塊......”這個信息。如果在命令行之前沒有字符“@”,那么,make的輸出就是:
echo編譯XXX模塊......
編譯XXX模塊......
“-n”或“--just-print”
如果使用make的命令行參數“-n”或“--just-print”,那么make執行時只顯示所要執行的命令,但不會真正的去執行這些命令。只有在這種情況下make才會打印出所有make需要執行的命令,其中也包括了使用“@”字符開始的命令。這個選項對於我們調試Makefile非常有用,使用這個選項我們可以按執行順序打印出Makefile中所有需要執行的命令。
“-s”或“--slient”
make參數“-s”或“--slient”則是禁止所有執行命令的顯示,就好像所有的命令行均使用“@”開始一樣。
.SILENT
這個關鍵字的行為很像.PHONY,.PHONY標記的target可以理解成一個無條件執行的動作。被.SILENT標價的target,為完成該target執行的所有command都是沒有回顯的。
.SILENT:clean .PHONY:clean clean: rm -rf *.o
很顯然這里面控制回顯最靈活的方式就是@,推薦使用“@”來控制命令的回顯。
通配符(Wildcard )
概述
Makefile中使用的通配符與Bash下面的filename wildcards一樣,但似乎Makefile主要使用‘*’, ‘?’ 和 ‘[…]’
~字符也有特殊意義,~ 或者 ~/file name 代表home directory
~ 展開為 /home/you 你的家目錄
~/bin 展開為 /home/you/bin 你的加目錄下邊的bin目錄
~后面不接/則表示別人的家目錄
~john 展開為 /home/john john的家目錄
~john/bin 展開為 /home/john/bin john的家目錄下邊的bin目錄
targets 和 prerequisites中的通配符,有make工具負責展開。commands中的通配符則由shell負責展開。除此之外的其他情況,要想展開通配符則需要顯式調用wildcard
函數。
由於*默認情況下具有通配符的特殊含義,如果需要按照普通字符理解他,則需要使用 \* 轉義。
通配符舉例
在commands中使用通配符
此時通配符展開工作由shell負責
clean: rm -f *.o
在prerequisites 中使用通配符
此時通配符展開由make負責,在target中也可以使用通配符,依然是由amke負責展開。
print: *.c lpr -p $? touch print
這個例子除了表明通配符用法外,同時展示了empty target的用法。empty target是phony target的變體,empty target中的target可以存在,也可以不存在。其特色是在commands最后會touch更新target。
在定義變量時使用通配符
objects = *.o
變量objects的值就是*.o,但是當你把objects的值用於target、prerequisite時,make工具會展開;用於commands時,shell會展開。表面看上去,這似乎沒啥問題,看如下代碼
objects = *.o foo : $(objects) cc -o foo $(CFLAGS) $(objects)
objects的值就是*.o,單獨看他就是一個名字古怪的文件。但是用在target、prerequisite、commands時,make或shell會自動將其展開為有意義的具體xxx.o文件名,因此上面代碼看上去沒有問題。但是如果,當前目錄下並沒有.o文件,target、prerequisite、commands展開時也找不到.o文件,他就會找*.o這個文件,當然*.o文件也是沒有的,於是會報"cannot figure out how to make *.o."錯誤。
解決這個陷阱的方法是使用wildcard函數
wildcard函數通常和patsubst函數一起使用,這是因為在函數內部通配符也不會自動展開
objects := $(patsubst %.c,%.o,$(wildcard *.c)) foo : $(objects) cc -o foo $(objects)
變量
自定義變量與賦值符
Makefile 允許使用等號自定義變量。
var = Hello World
test:
@echo $(var)
上面代碼中,變量 var等於 Hello World。調用時,變量需要放在 $( ) 之中。
調用Shell變量,需要在美元符號前,再加一個美元符號,這是因為Make命令會對美元符號轉義。
test:
@echo $$HOME
有時,變量的值可能指向另一個變量。
v1 = $(v2)
上面代碼中,變量 v1 的值是另一個變量 v2。這時會產生一個問題,v1 的值到底在定義時擴展(靜態擴展),還是在運行時擴展(動態擴展)?如果 v2 的值是動態的,這兩種擴展方式的結果可能會差異很大。
為了解決類似問題,Makefile一共提供了四個賦值運算符 (=、:=、?=、+=),它們的區別請看StackOverflow。
VARIABLE = value # 在執行時擴展,允許遞歸擴展。 VARIABLE := value # 在定義時擴展。 VARIABLE ?= value # 只有在該變量為空時才設置值。 VARIABLE += value # 將值追加到變量的尾端。
內置變量(Implicit Variables)
Make命令提供一系列內置變量,(感覺上和gcc/g++的預定義宏差不多)比如,$(CC) 指向當前使用的編譯器,$(MAKE) 指向當前使用的Make工具。這主要是為了跨平台的兼容性,詳細的內置變量清單見手冊。
output:
$(CC) -o output input.c
自動變量(Automatic Variables)
Make命令還提供一些自動變量,它們的值與當前規則有關。主要有以下幾個。
(1)$@
$@指代當前目標,就是Make命令當前構建的那個目標。比如,make foo
的 $@ 就指代foo。
a.txt b.txt: touch $@
等同於下面的寫法。
a.txt: touch a.txt b.txt: touch b.txt
(2)$<
$< 指代第一個前置條件。比如,規則為 t: p1 p2,那么$< 就指代p1。
a.txt: b.txt c.txt cp $< $@
等同於下面的寫法。
a.txt: b.txt c.txt cp b.txt a.txt
(3)$?
$? 指代比目標更新的所有前置條件,之間以空格分隔。比如,規則為 t: p1 p2,其中 p2 的時間戳比 t 新,$?就指代p2。
(4)$^
$^ 指代所有前置條件,之間以空格分隔。比如,規則為 t: p1 p2,那么 $^ 就指代 p1 p2 。
(5)$*
$* 指代匹配符 % 匹配的部分, 比如% 匹配 f1.txt 中的f1 ,$* 就表示 f1。
(6)$(@D) 和 $(@F)
$(@D) 和 $(@F) 分別指向 $@ 的目錄名和文件名。
比如,$@是 src/input.c,那么$(@D) 的值為 src ,$(@F) 的值為 input.c。
(7)$(<D) 和 $(<F)
$(<D) 和 $(<F) 分別指向 $< 的目錄名和文件名。
所有的自動變量清單,請看手冊。下面是自動變量的一個例子。
dest/%.txt: src/%.txt @[ -d dest ] || mkdir dest cp $< $@
上面代碼將 src 目錄下的 txt 文件,拷貝到 dest 目錄下。首先判斷 dest 目錄是否存在,如果不存在就新建,然后,$< 指代前置文件(src/%.txt), $@ 指代目標文件(dest/%.txt)。
判斷和循環
循環和判斷應用在commands處,而commands的解析則有shell負責。因此Makefile的循環和判斷其實也就是shell的循環和判斷,其語法與shell完全一樣。
ifeq ($(CC),gcc) libs=$(libs_for_gcc) else libs=$(normal_libs) endif
上面代碼判斷當前編譯器是否 gcc ,然后指定不同的庫文件。
LIST = one two three all: for i in $(LIST); do \ echo $$i; \ done # 等同於 all: for i in one two three; do \ echo $i; \ done
上面代碼的運行結果。
one
two
three
函數
Makefile 還可以使用函數,格式如下。
$(function arguments) # 或者 ${function arguments}
Makefile提供了許多內置函數,可供調用。下面是幾個常用的內置函數。
(1)shell 函數
shell 函數用來執行 shell 命令
srcfiles := $(shell echo src/{00..99}.txt)
(2)wildcard 函數
wildcard 函數用來在 Makefile 中,替換 Bash 的通配符。
srcfiles := $(wildcard src/*.txt)
(3)subst 函數
subst 函數用來文本替換,格式如下。
$(subst from,to,text)
下面的例子將字符串"feet on the street"替換成"fEEt on the strEEt"。
$(subst ee,EE,feet on the street)
下面是一個稍微復雜的例子。
comma:= , empty:= # space變量用兩個空變量作為標識符,當中是一個空格 space:= $(empty) $(empty) foo:= a b c bar:= $(subst $(space),$(comma),$(foo)) # bar is now `a,b,c'.
(4)patsubst函數
patsubst 函數用於模式匹配的替換,格式如下。
$(patsubst pattern,replacement,text)
下面的例子將文件名"x.c.c bar.c",替換成"x.c.o bar.o"。
$(patsubst %.c,%.o,x.c.c bar.c)
(5)替換后綴名
替換后綴名函數的寫法是:變量名 + 冒號 + 后綴名替換規則。它實際上patsubst函數的一種簡寫形式。
min: $(OUTPUT:.js=.min.js)
上面代碼的意思是,將變量OUTPUT中的后綴名 .js 全部替換成 .min.js 。