3. 變數

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

彙編器的選項,沒有定義。

CC

C編譯器的名字,預設值是cc

CFLAGS

C編譯器的選項,沒有定義。

CXX

C++編譯器的名字,預設值是g++

CXXFLAGS

C++編譯器的選項,沒有定義。

CPP

C預處理器的名字,預設值是$(CC) -E

CPPFLAGS

C預處理器的選項,沒有定義。

LD

連結器的名字,預設值是ld

LDFLAGS

連結器的選項,沒有定義。

TARGET_ARCH

和目標平台相關的命令行選項,沒有定義。

OUTPUT_OPTION

輸出的命令行選項,預設值是-o $@

LINK.o

.o檔案連結在一起的命令行,預設值是$(CC) $(LDFLAGS) $(TARGET_ARCH)

LINK.c

.c檔案連結在一起的命令行,預設值是$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)

LINK.cc

.cc檔案(C++源檔案)連結在一起的命令行,預設值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)

COMPILE.c

編譯.c檔案的命令行,預設值是$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c

COMPILE.cc

編譯.cc檔案的命令行,預設值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c

RM

刪除命令的名字,預設值是rm -f