Makefile中=与:=的区别

在GNU make中,可以使用“=”和“:=”来定义变量,二者的区别主要在于展开时机。

1. 使用“:=”定义变量

  使用“:=”定义的变量称为“简单展开变量(simply expanded variables)”,顾名思义,“:=”右边的表达式如果包含对变量的引用,则这些对变量的引用会直接展开,得到一个确切的值,并赋给“:=”左边的变量。这种定义和赋值的形式类似于C语言中对变量的赋值,举例来说:

 等价于:

 在 y := $(x) bar 处, x 的值为 foo ,故 y 被赋值为 foo bar ,随后 x := later 更新了 x 的值,最终 x 的值为 later , y 的值为 foo bar 。

2.使用“=”定义变量

  使用“=”定义的变量称为“递归展开变量(recursively expanded variables)”,在使用“=”定义变量时,如果“=”右边存在对其他变量或函数的引用,这些引用并不会立即展开。在实际使用到递归展开变量时,递归展开变量才会展开,同时定义递归展开变量时使用的其他变量或函数的引用也会被展开,从而得到当前递归展开变量的值。在引用递归展开变量的地方,执行的是严格的文本替换过程,递归展开变量中的字符串原模原样的出现在引用变量的地方,而递归展开变量中对其他变量的引用,只会在递归展开变量被展开的同时被展开。文字的描述比较复杂,举例来说,下面的例子将输出 Huh? :

foo = $(bar) 处, foo 的定义使用了“=”,是一个递归展开变量,在这里 bar 还没有定义,实际上 bar 并也没有在这里定义 foo 时展开。在 bar = $(ugh) 处同理。而在 echo $(foo) 处,引用了 foo ,这时 foo 被展开,发现 bar 的引用,进而展开 bar ,又发现了 ugh 的引用,再展开 ugh ,得到 Huh? ,于是以此作为 foo 的值进行输出。

  实际上,递归展开变量的值是它在整个Makefile中最后被指定的值,如下面的例子将输出 Ha!

注意 ugh := Huh? 和 ugh := Ha! 使用了“:=”。这里涉及到GUN make的执行过程,GUN make的执行过程分为两个阶段:第一阶段读取所有的makefile文件(包括“MAKIFILES”变量指定的、指示符“include”指定的、以及命令行选项“-f(–file)”指定的makefile文件),内建所有的变量、明确规则和隐含规则,并建立所有目标和依赖之间的依赖关系结构链表;第二阶段根据第一阶段已经建立的依赖关系结构链表决定哪些目标需要更新,并使用对应的规则来重建这些目标。如果变量和函数在make执行的第一阶段中被展开,则称此展开是“立即(Immediate)”的,此时所有的变量和函数被展开在需要构建的结构链表的对应规则中(此规则在建立链表是需要使用)。其他的展开称之为“延后(Deferred)”的。这些变量和函数不会被“立即”展开,而是直到后续某些规则须要使用时或者在make处理的第二阶段它们才会被展开。变量定义的解析过程为:

  回到刚才的例子:

对于 ugh 这个简单展开的变量,是立即(Immediate)展开的,会在make的第一阶段立即展开;而对于 foo 这个递归展开的变量,是延后(Deferred)展开的,在make的第二阶段才会展开,此时 ugh 已经展开完毕,得到了最终的值 Ha! ,所以在 echo $(foo) 处,输出 foo 的值为 Ha! 。

  使用递归展开变量时,需要注意不要出现循环引用,如变量a引用自身(如进行自加操作),或者变量a引用变量b、而b又引用a,这会导致make对变量进行无限展开而发生错误。

  现在问题来了,对于下面的例子:

echo $(foo) 将输出什么值?注意这里 ugh 是递归展开变量。【答案:Ha!

 

  更多说明可以查看GNU的相关文档:

http://www.gnu.org/software/make/manual/make.html

  其中关于变量的部分在:

http://www.gnu.org/software/make/manual/make.html#Reading

http://www.gnu.org/software/make/manual/make.html#Setting

http://www.gnu.org/software/make/manual/make.html#Flavors

等处。