這一節我們詳細看看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。