這一節我們詳細看看Makefile中關於變數的語法規則。先看一個簡單的例子:
foo = $(bar) bar = Huh? all: @echo $(foo)
我們執行make
將會打出Huh?
。當make
讀到foo = $(bar)
時,確定foo
的值是$(bar)
,但並不立即展開$(bar)
,然後讀到bar = Huh?
,確定bar
的值是Huh?
,然後在執行規則all:
的命令列表時才需要展開$(foo)
,得到$(bar)
,再展開$(bar)
,得到Huh?
。因此,雖然bar
的定義寫在foo
之後,$(foo)
展開還是能夠取到$(bar)
的值。
這種特性有好處也有壞處。好處是我們可以把變數的值推遲到後面定義,例如:
main.o: main.c $(CC) $(CFLAGS) $(CPPFLAGS) -c $< CC = gcc CFLAGS = -O -g CPPFLAGS = -Iinclude
編譯命令可以展開成gcc -O -g -Iinclude -c main.c
。通常把CFLAGS
定義成一些編譯選項,例如-O
、-g
等,而把CPPFLAGS
定義成一些預處理選項,例如-D
、-I
等。用=
號定義變數的延遲展開特性也有壞處,就是有可能寫出無窮遞歸的定義,例如CFLAGS = $(CFLAGS) -O
,或者:
A = $(B) B = $(A)
當然,make
有能力檢測出這樣的錯誤而不會陷入死循環。有時候我們希望make
在遇到變數定義時立即展開,可以用:=
運算符,例如:
x := foo y := $(x) bar all: @echo "-$(y)-"
當make
讀到y := $(x) bar
定義時,立即把$(x)
展開,使變數y
的取值是foo bar
,如果把這兩行顛倒過來:
y := $(x) bar x := foo
那麼當make
讀到y := $(x) bar
時,x
還沒有定義,展開為空值,所以y
的取值是␣bar
,注意bar
前面有個空格。一個變數的定義從=
後面的第一個非空白字元開始(從$(x)
的$
開始),包括後面的所有字元,直到註釋或換行之前結束。如果要定義一個變數的值是一個空格,可以這樣:
nullstring := space := $(nullstring) # end of the line
nullstring
的值為空,space
的值是一個空格,後面寫個註釋是為了增加可讀性,如果不寫註釋就換行,則很難看出$(nullstring)
後面有個空格。
還有一個比較有用的賦值運算符是?=
,例如foo ?= $(bar)
的意思是:如果foo
沒有定義過,那麼?=
相當於=
,定義foo
的值是$(bar)
,但不立即展開;如果先前已經定義了foo
,則什麼也不做,不會給foo
重新賦值。
+=
運算符可以給變數追加值,例如:
objects = main.o objects += $(foo) foo = foo.o bar.o
object
是用=
定義的,+=
仍然保持=
的特性,objects
的值是main.o $(foo)
(注意$(foo)
前面自動添一個空格),但不立即展開,等到後面需要展開$(objects)
時會展開成main.o foo.o bar.o
。
再比如:
objects := main.o objects += $(foo) foo = foo.o bar.o
object
是用:=
定義的,+=
保持:=
的特性,objects
的值是main.o $(foo)
,立即展開得到main.o
(這時foo
還沒定義),注意main.o
後面的空格仍保留。
如果變數還沒有定義過就直接用+=
賦值,那麼+=
相當於=
。
上一節我們用到了特殊變數$@
和$<
,這兩個變數的特點是不需要給它們賦值,在不同的上下文中它們自動取不同的值。常用的特殊變數有:
$@
,表示規則中的目標。
$<
,表示規則中的第一個條件。
$?
,表示規則中所有比目標新的條件,組成一個列表,以空格分隔。
$^
,表示規則中的所有條件,組成一個列表,以空格分隔。
例如前面寫過的這條規則:
main: main.o stack.o maze.o gcc main.o stack.o maze.o -o main
可以改寫成:
main: main.o stack.o maze.o gcc $^ -o $@
這樣即使以後又往條件裡添加了新的目標檔案,編譯命令也不需要修改,減少了出錯的可能。
$?
變數也很有用,有時候希望只對更新過的條件進行操作,例如有一個庫檔案libsome.a
依賴于幾個目標檔案:
libsome.a: foo.o bar.o lose.o win.o ar r libsome.a $? ranlib libsome.a
這樣,只有更新過的目標檔案才需要重新打包到libsome.a
中,沒更新過的目標檔案原本已經在libsome.a
中了,不必重新打包。
在上一節我們看到make
的隱含規則資料庫中用到了很多變數,有些變數沒有定義(例如CFLAGS
),有些變數定義了預設值(例如CC
),我們寫Makefile時可以重新定義這些變數的值,也可以在預設值的基礎上追加。以下列舉一些常用的變數,請讀者體會其中的規律。
AR
靜態庫打包命令的名字,預設值是ar
。
ARFLAGS
靜態庫打包命令的選項,預設值是rv
。
AS
彙編器的名字,預設值是as
。
ASFLAGS
彙編器的選項,沒有定義。
C編譯器的名字,預設值是cc
。
C編譯器的選項,沒有定義。
C++編譯器的名字,預設值是g++
。
C++編譯器的選項,沒有定義。
C預處理器的名字,預設值是$(CC) -E
。
C預處理器的選項,沒有定義。
連結器的名字,預設值是ld
。
連結器的選項,沒有定義。
和目標平台相關的命令行選項,沒有定義。
輸出的命令行選項,預設值是-o $@
。
把.o
檔案連結在一起的命令行,預設值是$(CC) $(LDFLAGS) $(TARGET_ARCH)
。
把.c
檔案連結在一起的命令行,預設值是$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
。
把.cc
檔案(C++源檔案)連結在一起的命令行,預設值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
。
編譯.c
檔案的命令行,預設值是$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
。
編譯.cc
檔案的命令行,預設值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
。
刪除命令的名字,預設值是rm -f
。