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
。