变量
1. Makefile中变量和函数的展开(除规则命令行中的变量和函数以外),是在make读取makefile文件时进行的,这里的变量包括了使用“=”定义和使用指示符“define”定义的。
2. 变量可以用来代表一个文件名列表、编译选项列表、程序运行的选项参数列表、搜索源文件的目录列表、编译输出的目录列表和所有我们能够想到的事物。
3. 变量名是不包括“:”、“#”、“=”、前置空白和尾空白的任何字符串。
4. 变量名是大小写敏感的。变量“foo”、“Foo”和“FOO”指的是三个不同的变量。Makefile传统做法是变量名是全采用大写的方式。推荐的做法是在对于内部定义定义的一般变量使用小写方式,而对于一些参数列表(例如:编译选项CFLAGS)采用大写方式。
5. 另外有一些变量名只包含了一个或者很少的几个特殊的字符(符号)。称它们为自动化变量。像“$<”、“[email protected]”、“$?”、“$*”等。
6. 变量的引用方式是:“$(VARIABLE_NAME)”或者“${ VARIABLE_NAME }”来引用一个变量的定义。
一般在我们书写Makefile时,各部分变量引用的格式我们建议如下:
1. make变量(Makefile中定义的或者是make的环境变量)的引用使用“$(VAR)”格式,无论“VAR”是单字符变量名还是多字符变量名。
2. 出现在规则命令行中shell变量(一般为执行命令过程中的临时变量,它不属于Makefile变量,而是一个shell变量)引用使用shell的“$tmp”格式。
3. 对出现在命令行中的make变量我们同样使用“$(CMDVAR)” 格式来引用。
递归展开式变量:
这一类型变量的定义是通过“=”或者使用指示符“define”定义的。这种变量的引用,在引用的地方是严格的文本替换过程,此变量值的字符串原模原样的出现在引用它的地方。如果此变量定义中存在对其他变量的引用,这些被引用的变量会在它被展开的同时被展开。就是说在变量定义时,变量值中对其他变量的引用不会被替换展开;而是变量在引用它的地方替换展开的同时,它所引用的其它变量才会被一同替换展开。语言的描述可能比较晦涩,让我们来看一个例子:
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:;echo $(foo)
执行“make”将会打印出“Huh?”。
其优点是:
这种类型变量在定义时,可以引用其它的之前没有定义的变量
直接展开式变量:
为了避免“递归展开式”变量存在的问题和不方便。GNU make支持另外一种风格的变量,称为“直接展开”式。
x := foo
y := $(x) bar
x := later
就等价于:
y := foo bar
x := later
"?="操作符:
被称为条件赋值是因为:只有此变量在之前没有赋值的情况下才会对这个变量进行赋值。例如:
FOO ?= bar
其等价于:
ifeq ($(origin FOO), undefined)
FOO = bar
endif
含义是:如果变量“FOO”在之前没有定义,就给它赋值“bar”。否则不改变它的值。
变量的替换引用:
格式为“$(VAR:A=B)”(或者“${VAR:A=B}”),意思是,替换变量“VAR”中所有“A”字符结尾的字为“B”结尾的字。例如:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
在这个定义中,变量“bar”的值就为“a.c b.c c.c”。使用变量的替换引用将变量“foo”以空格分开的值中的所有的字的尾字符“o”替换为“c”,其他部分不变。
变量的替换引用其实是函数“patsubst”的一个简化实现。在GNU make中同时提供了这两种方式来实现同样的目的,以兼容其它版本make。
另外一种引用替换的技术使用功能更强大的“patsubst”函数。它的格式和上面“$(VAR:A=B)”的格式相类似,不过需要在“A”和“B”中需要包含模式字符“%”。这时它和“$(patsubst A,B $(VAR))”所实现功能相同。例如:
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
这个例子同样使变量“bar”的值为“a.c b.c c.c”。这种格式的替换引用方式比第一种方式更通用。
变量的追加:
objects = main.o foo.o bar.o utils.o
objects += another.o
上边的两个操作之后变量“objects”的值就为:“main.o foo.o bar.o utils.o another.o”。
overried 指示符:
如果通过命令行定义了一个变量,那么它将替代在Makefile中出现的同名变量的定义。就是说,对于一个在Makefile中使用常规方式(使用“=”、“:=”或者“define”)定义的变量,我们可以在执行make时通过命令行方式重新指定这个变量的值。
如果不希望命令行指定的变量值替代在Makefile中的变量定义,那么我们需要在Makefile中使用指示符“override”来对这个变量进行声明,像下边那样:
override VARIABLE = VALUE
或者:
override VARIABLE := VALUE
也可以对变量使用追加方式:
override VARIABLE += MORE TEXT
对于追加方式需要说明的是:变量在定义时使用了“override”,则后续对它值进行追加时,也需要使用带有“override”指示符的追加方式。否则对此变量值的追加不会生效。
其存在的目的是为了使用户可以改变或者追加那些使用make的命令行指定的变量的定义。
例如:无论命令行指定那些编译参数,编译时必须打开“-g”选项,那么在Makefile中编译选项“CFLAGS”应该这样定义:
override CFLAGS += -g
这样,在执行make时无论在命令行中指定了那些编译选项(“指定CFLAGS”的值),编译时“-g”参数始终存在。
多行定义
定义变量的另外一种方式是使用“define”指示符。它定义一个包含多行字符串的变量,我们就是利用它的这个特点实现了一个完整命令包的定义。使用“define”定义的命令包可以作为“eval”函数的参数来使用。
本文的前些章节已经不止一次的提到并使用了“define”。相信大家已经有所了解。本节就“define”定义变量从以下几个方面来讨论:
1. “define”定义变量的语法格式:以指示符“define”开始,“endif”结束,之间的所有内容就是所定义变量的值。所要定义的变量名字和指示符“define”在同一行,使用空格分开;指示符所在行的下一行开始一直到“endif”所在行的上一行之间的若干行,是变量值。
define two-lines
echo foo
echo $(bar)
endef
如果将变量“two-lines”作为命令包执行时,其相当于:
two-lines = echo foo; echo $(bar)
它把变量“two-lines”的值作为一个完整的shell命令行来处理(是使用分号“;”分开的在同一行中的两个命令而不是作为两个命令行来处理),保证了变量完整。
2. 变量的风格:使用“define”定义的变量和使用“=”定义的变量一样,属于“递归展开”式的变量,两者只是在语法上不同。因此“define”所定义的变量值中,对其它变量或者函数引用不会在定义变量时进行替换展开,其展开是在“define”定义的变量被展开的同时完成的。
3. 可以套嵌引用。因为是递归展开式变量,所以在嵌套引用时“$(x)”将是变量的值的一部分。
4. 变量值中可以包含:换行符、空格等特殊符号(注意如果定义中某一行是以[Tab]字符开始时,当引用此变量时这一行会被作为命令行来处理)。
5. 可以使用“override”在定义时声明变量:这样可以防止变量的值被命令行指定的值替代。例如:
override define two-lines
foo
$(bar)
endef
系统环境变量
make在运行时,系统中的所有环境变量对它都是可见的。在Makefile中,可以引用任何已定义的系统环境变量。(这里我们区分系统环境变量和make的环境变量,系统环境变量是这个系统所有用户所拥有的,而make的环境变量只是对于make的一次执行过程有效,以下正文中出现没有限制的“环境变量”时默认指的是“系统环境变量”,在特殊的场合我们会区分两者)正因为如此,我们就可以设置一个命名为“CFLAGS”的环境变量,用它来指定一个默认的编译选项。就可以在所有的Makefile中直接使用这个变量来对c源代码就行编译。通常这种方式是比较安全的,但是它的前提是大家都明白这个变量所代表的含义,没有人在Makefile中把它作其他的用途。当然了,你也可以在你的Makefile中根据你的需要对它进行重新定义。
使用环境变量需要注意以下几点:
1. 在Makefile中对一个变量的定义或者以make命令行形式对一个变量的定义,都将覆盖同名的环境变量(注意:它并不改变系统环境变量定义,被修改的环境变量只在make执行过程有效)。而make使用“-e”参数时,Makefile和命令行定义的变量不会覆盖同名的环境变量,make将使用系统环境变量中这些变量的定义值。
2. make的递归调用中,所有的系统环境变量会被传递给下一级make。默认情况下,只有环境变量和通过命令行方式定义的变量才会被传递给子make进程。
我们不推荐使用环境变量的方式来完成普通变量的工作,特别是在make的递归调用中。任何一个环境变量的错误定义都对系统上的所有make产生影响,甚至是毁坏性的。因为环境变量具有全局的特征。所以尽量不要污染环境变量,造成环境变量名字污染。我想大多数系统管理员都明白环境变量对系统是多么的重要。
我们来看一个例子,结束本节。假如我们的机器名为“server-cc”;我们的Makefile内容如下:
# test makefile
HOSTNAME = server-http
…………
…………
.PHONY : debug
debug :
@echo “hostname is : $( HOSTNAME)”
@echo “shell is $(SHELL)”
1. 执行“make debug”将显示:
hostname is : server-http
shell is /bin/sh
2. 执行“make –e debug”;将显示:
hostname is : server-cc
shell is /bin/sh
3. 执行“make –e HOSTNAEM=server-ftp”;将显示:
hostname is : server-ftp
shell is /bin/sh
记住:除非必须,否则在你的Makefile中不要重置环境变量“SHELL”的值。因为一个不正确的命令行解释程序可能会导致规则定义的命令执行失败,甚至是无法执行!当需要重置它时,必须有充分的理由和配套的规则命令来适应这个新指定的命令行解释程序。
目标指定变量
在Makefile中定义一个变量,那么这个变量对此Makefile的所有规则都是有效的。它就像是一个“全局的”变量。
另外一个特殊的变量定义就是所谓的“目标指定变量(Target-specific Variable)”。此特性允许对于相同变量根据目标指定不同的值,有点类似于自动化变量。目标指定的变量值只在指定它的目标的上下文中有效,对于其他的目标没有影响。就是说目标指定的变量具有只对此目标上下文有效的“局部性”。
设置一个目标指定变量的语法为:
TARGET ... : VARIABLE-ASSIGNMENT
或者:
TARGET ... : override VARIABLE-ASSIGNMENT
一个多目标指定的变量的作用域是所有这些目标的上下文,它包括了和这个目标相关的所有执行过程。
目标指定变量的一些特点:
1. “VARIABLE-ASSIGNMENT”可以使用任何一个有效的赋值方式,“=”(递归)、“:=”(静态)、“+=”(追加)或者“?=”(条件)。
2. 使用目标指定变量值时,目标指定的变量值不会影响同名的那个全局变量的值。就是说目标指定一个变量值时,如果在Makefile中之前已经存在此变量的定义(非目标指定的),那么对于其它目标全局变量的值没有变化。变量值的改变只对指定的这些目标可见。
3. 目标指定变量和普通变量具有相同的优先级。就是说,当我们使用make命令行的方式定义变量时,命令行中的定义将替代目标指定的同名变量定义(和普通的变量一样会被覆盖)。另外当使用make的“-e”选项时,同名的环境变量也将覆盖目标指定的变量定义。因此为了防止目标指定的变量定义被覆盖,可以使用第二种格式,使用指示符“override”对目标指定的变量进行声明。
4. 目标指定的变量和同名的全局变量属于两个不同的变量,它们在定义的风格(递归展开式和直接展开式)上可以不同。
5. 目标指定的变量变量会作用到由这个目标所引发的所有的规则中去。例如:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
这个例子中,无论Makefile中的全局变量“CFLAGS”的定义是什么。对于目标“prog”以及其所引发的所有(包含目标为“prog.o”、“foo.o”和“bar.o”的所有规则)规则,变量“CFLAGS”值都是“-g”。
使用目标指定变量可以实现对于不同的目标文件使用不同的编译参数。看一个例子:
# sample Makefile
CUR_DIR = $(shell pwd)
INCS := $(CUR_DIR)/include
CFLAGS := -Wall –I$(INCS)
EXEF := foo bar
.PHONY : all clean
all : $(EXEF)
foo : foo.c
foo : CFLAGS+=-O2
bar : bar.c
bar : CFLAGS+=-g
………..
………..
$(EXEF) : debug.h
$(CC) $(CFLAGS) $(addsuffix .c,[email protected]) –o [email protected]
clean :
$(RM) *.o *.d $(EXES)
这个Makefile文件实现了在编译程序“foo”使用优化选项“-O2”但不使用调试选项“-g”,而在编译“bar”时采用了“-g”但没有“-O2”。这就是目标指定变量的灵活之处。目标指定变量的其它特性大家可以修改这个简单的Makefile来进行验证!
模式指定变量
模式指定变量(Pattern-specific Variable)。使用目标定变量定义时,此变量被定义在某个具体目标和由它所引发的规则的目标上。而模式指定变量定义是将一个变量值指定到所有符合此模式的目标上。对于同一个变量如果使用追加方式,通常对于一个目标,它的局部变量值是:(为所有规则定义的全局值)+(引发它所在规则被执行的目标所指定的值)+(它所符合的模式指定值)+(此目标所指定的值)。这个大家也不需要深入了解。
设置一个模式指定变量的语法和设置目标变量的语法相似:
PATTERN ... : VARIABLE-ASSIGNMENT
或者:
PATTERN ... : override VARIABLE-ASSIGNMENT
和目标指定变量语法的唯一区别就是:这里的目标是一个或者多个“模式”目标(包含模式字符“%”)。例如我们可以为所有的.o文件指定变量“CFLAGS”的值:
%.o : CFLAGS += -O
它指定了所有.o文件的编译选项包含“-O”选项,不改变对其它类型文件的编译选项。
需要说明的是:在使用模式指定的变量定义时。目标文件一般除了模式字符(%)以外需要包含某种文件名的特征字符(例如:“a%”、“%.o”、“%.a”等)。当单独使用“%”作为目标时,指定的变量会对所有类型的目标文件有效。