第六章:Makefile中的变量在Makefile中,变量是一个名字(像是C语言中的宏),代表一个文本字符串(变量的值)。
在Makefile的目标、依赖、命令中引用变量的地方,变量会被它的值所取代(与C语言中宏引用的方式相同,因此其他版本的make也把变量称之为“宏”)。
在Makefile中变量有以下几个特征:1.Makefile中变量和函数的展开(除规则命令行中的变量和函数以外),是在make读取makefile文件时进行的,这里的变量包括了使用“=”定义和使用指示符“define”定义的。
2.变量可以用来代表一个文件名列表、编译选项列表、程序运行的选项参数列表、搜索源文件的目录列表、编译输出的目录列表和所有我们能够想到的事物。
3.变量名是不包括“:”、“#”、“=”、前置空白和尾空白的任何字符串。
需要注意的是,尽管在GNUmake中没有对变量的命名有其它的限制,但定义一个包含除字母、数字和下划线以外的变量的做法也是不可取的,因为除字母、数字和下划线以外的其它字符可能会在make的后续版本中被赋予特殊含义,并且这样命名的变量对于一些shell来说是不能被作为环境变量来使用的。
4.变量名是大小写敏感的。
变量“foo”、“Foo”和“FOO”指的是三个不同的变量。
Makefile传统做法是变量名是全采用大写的方式。
推荐的做法是在对于内部定义定义的一般变量(例如:目标文件列表objects)使用小写方式,而对于一些参数列表(例如:编译选项CFLAGS)采用大写方式,但这并不是要求的。
但需要强调一点:对于一个工程,所有Makefile中的变量命名应保持一种风格,否则会显得你是一个蹩脚的程序员(就像代码的变量命名风格一样)。
5.另外有一些变量名只包含了一个或者很少的几个特殊的字符(符号)。
称它们为自动化变量。
像“$<”、“$@”、“$?”、“$*”等。
6.1变量的引用当我们定义了一个变量之后,就可以在Makefile的很多地方使用这个变量。
变量的引用方式是:“$(VARIABLE_NAME)”或者“${ VARIABLE_NAME }”来引用一个变量的定义。
例如:“$(foo)”或者“${foo}”就是取变量“foo”的值。
美元符号“$”在Makefile中有特殊的含义,所有在命令或者文件名中使用“$”时需要用两个美元符号“$$”来表示。
对一个变量的引用可以在Makefile的任何上下文中,目标、依赖、命令、绝大多数指示符和新变量的赋值中。
这里有一个例子,其中变量保存了所有.o文件的列表:objects = program.o foo.o utils.oprogram : $(objects)cc -o program $(objects)$(objects) : defs.h变量引用的展开过程是严格的文本替换过程,就是说变量值的字符串被精确的展开在变量被引用的地方。
因此规则:foo = cprog.o : prog.$(foo)$(foo) $(foo) -$(foo) prog.$(foo)被展开后就是:prog.c : prog.ccc -c prog.c通过这个例子会发现变量的展开过程和c语言中的宏展开的过程相同,是一个严格的文本替换过程。
上例中变量“foo”被展开的过程中,变量值中的前导空格会忽略。
举这个例子的目的是为了让我们更清楚地了解变量的展开过程,而不是建议大家按照这样的方式来书写Makefile。
在实际书写时,最好不要这么干。
否则将会给你带来很多不必要的麻烦。
注意:Makefile中在对一些简单变量的引用,我们也可以不使用“()”和“{}”来标记变量名,而直接使用“$x”的格式来实现,此种用法仅限于变量名为单字符的情况。
另外自动化变量也使用这种格式。
对于一般多字符变量的引用必须使用括号了标记,否则make将把变量名的首字母作为作为变量而不是整个字符串(“$PATH”在Makefile中实际上是“$(P)ATH”)。
这一点和shell中变量的引用方式不同。
shell中变量的引用可以是“${xx}”或者“$xx”格式。
但在Makefile中多字符变量名的引用只能是“$(xx)”或者“${xx}”格式。
一般在我们书写Makefile时,各部分变量引用的格式我们建议如下:1.make变量(Makefile中定义的或者是make的环境变量)的引用使用“$(VAR)”格式,无论“VAR”是单字符变量名还是多字符变量名。
2.出现在规则命令行中shell变量(一般为执行命令过程中的临时变量,它不属于Makefile变量,而是一个shell变量)引用使用shell的“$tmp”格式。
3.对出现在命令行中的make变量我们同样使用“$(CMDVAR)”格式来引用。
例如:# sample Makefile……SUBDIRS := src foo.PHONY : subdirSubdir :@for dir in $(SUBDIRS); do \$(MAKE) –C $$dir || exit 1; \done……6.2两种变量定义(赋值)在GNU make中,变量的定义有两种方式(或者称为风格)。
我们把使用这两种方式定义的变量可以看作变量的两种不同风格。
变量的这两种不同的风格的区别在于:1. 定义方式;2. 展开时机。
下边我们分别对这两种不同的风格进行详细地讨论。
6.2.1递归展开式变量第一种风格的变量是递归方式扩展的变量。
这一类型变量的定义是通过“=”或者使用指示符“define”定义的。
这种变量的引用,在引用的地方是严格的文本替换过程,此变量值的字符串原模原样的出现在引用它的地方。
如果此变量定义中存在对其他变量的引用,这些被引用的变量会在它被展开的同时被展开。
就是说在变量定义时,变量值中对其他变量的引用不会被替换展开;而是变量在引用它的地方替换展开的同时,它所引用的其它变量才会被一同替换展开。
语言的描述可能比较晦涩,让我们来看一个例子:foo = $(bar)bar = $(ugh)ugh = Huh?all:;echo $(foo)执行“make”将会打印出“Huh?”。
整个变量的替换过程时这样的:首先“$(foo)”被替换为“$(bar)”,接下来“$(bar)”被替换为“$(ugh)”,最后“$(ugh)”被替换为“Hug?”。
整个替换的过程是在执行“echo $(foo)”时完成的。
这种类型的变量是其它版本的make所支持的类型。
我们可以把这种类型的变量称为“递归展开”式变量。
此类型变量存有它的优点同时也存在其缺点。
其优点是:这种类型变量在定义时,可以引用其它的之前没有定义的变量(可能在后续部分定义,或者是通过make的命令行选项传递的变量)。
看一个这样的例子:CFLAGS = $(include_dirs) -Oinclude_dirs = -Ifoo -Ibar“CFLAGS”会在命令中被展开为“-Ifoo -Ibar -O”。
而在“CFLAGS”的定义中使用了其后才定义的变量“include_dirs”。
其缺点是:1.使用此风格的变量定义,可能会由于出现变量的递归定义而导致make陷入到无限的变量展开过程中,最终使make执行失败。
例如,接上边的例子,我们给这个变量追加值:CFLAGS = $(CFLAGS) –O它将会导致make对变量“CFLAGS”的无限展过程中去(这种定义就是变量的递归定义)。
因为一旦后续同样存在对“CLFAGS”定义的追加,展开过程将是套嵌的、不能终止的(在发生这种情况时,make会提示错误信息并结束)。
一般书写Makefile时,这种追加变量值的方法很少使用(也不是我们推荐的方式)。
看另外一个例子:x = $(y)y = $(x) $(z)这种情况下变量在进行展开时,同样会陷入死循环。
所以对于此风格的变量,当在一个变量的定义中需要引用其它的同类型风格的变量时需特别注意,防止变量展开过程的死循环。
2.第二个缺点:这种风格的变量定义中如果使用了函数,那么包含在变量值中的函数总会在变量被引用的地方执行(变量被展开时)。
这是因为在这种风格变量的定义中,对函数引用的替换展开发生在变量展开的过程中,而不是在定义这个变量的时候。
这样所带来的问题是:使make的执行效率降低(每一次在变量被展开时都要展开他所引用的函数);另外在某些时候会出现一些变量和函数的引用出现非预期的结果。
特别是当变量定义中引用了“shell”和“wildcard”函数的情况,可能出现不可控制或者难以预料的错误,因为我们无法确定它在何时会被展开。
6.2.2直接展开式变量为了避免“递归展开式”变量存在的问题和不方便。
GNU make支持另外一种风格的变量,称为“直接展开”式。
这种风格的变量使用“:=”定义。
在使用“:=”定义变量时,变量值中对其他量或者函数的引用在定义变量时被展开(对变量进行替换)。
所以变量被定义后就是一个实际需要的文本串,其中不再包含任何变量的引用。
因此x := fooy := $(x) barx := later就等价于:y := foo barx := later和递归展开式变量不同:此风格变量在定义时就完成了对所引用变量和函数的展开,因此不能实现对其后定义变量的引用。
如:CFLAGS := $(include_dirs) -Oinclude_dirs := -Ifoo -Ibar由于变量“include_dirs”的定义出现在“CFLAGS”定义之后。
因此在“CFLAGS”的定义中,“include_dirs”的值为空。
“CFLAGS”的值为“-O”而不是“-Ifoo -Ibar -O”。
这一点也是直接展开式和递归展开式变量的不同点。
注意这里的两个变量都是“直接展开”式的。
大家不妨试试将其中某一个变量使用递归展开式定义后看一下又会出现什么样的结果。
下边我们来看一个复杂一点的例子。
分析一下直接展开式变量定义(:=)的用法,这里也用到了make的shell函数和变量“MAKELEVEL”(此变量在make的递归调用时代表make的调用深度)。
其中包括了对函数、条件表达式和系统变量“MAKELEVEL”的使用:ifeq (0,${MAKELEVEL})cur-dir := $(shell pwd)whoami := $(shell whoami)host-type := $(shell arch)MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}endif第一行是一个条件判断,如果是顶层Makefile,就定义下列变量。
否则不定义任何变量。
第二、三、四、五行分别定义了一个变量,在进行变量定义时对引用到的其它变量和函数展开。