ILD

GNU Make: variable, call, eval 深入分析
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2020-1-5 站点:Inside Linux Development

在makefile中除了recipes和和循环扩展变量的定义的任何地方,变量和函数都会被扩展。

Variables and functions in all parts of a makefile are expanded when read, except for
in recipes, the right-hand sides of variable definitions using ‘=’, and the bodies of variable definitions using the define directive.


使用圆括号()或大括号{}引用变量。如$(FOO)或者${FOO}。

对于但字符变量,可以使用$接字符,如用户定义变量$x,自动变量$<,$1等。

使用$$来表示字符$。


变量引用可以在任何上下文:

1 用在变量定义中,被别的变量引用:

1
2
a := 123
b := abc$a


2 作为target,如下定义123目标。

1
2
3
a := 123
$a:
    echo $@


3 用作变量名

1
2
a := 123
a$a := 1234

上述定义了变量a123为1234


4 引用变量的时候,作为变量名的一部分

1
2
3
a_obj := test.o
type := obj
b = ${a_${type}}

上述,b的值为a_obj变量的值。


5 通常不要直接吧变量扩展到Makefile上下文去,而是通过eval。

1
2
a := b := 3
$(eval $(a))

上面这个是对的,执行了 b := 3,而下面这个是错的(直接扩展是怎么实现的,手册并没有提到)。

1
2
a := b := 3
$(a)

报错:*** empty variable name.  Stop.


recursively expanded variable

使用=定义的变量,定义的时候不会立即进行扩展,而是在使用的时候才进行扩展。


simply expanded variables

使用:=定义的变量,定义的时候,立即扩展得到变量的值。


1
2
3
4
5
a :=1
b := $a
c = $a
$(warning $(value b))
$(warning $(value c))

value函数可以得到变量的定义,前者b是立即扩展,b的定义就是1。而c是延迟扩展,c的定义是$a。


define, call和eval

define通常用来定义一个Multi-Line Variables。


把define的变量通过:=赋值给simple expanded variables,换行符被保留,下面的例子可以证明:

1
2
3
4
5
6
7
define d
y := $1
a:
        echo a
endef
c := $(call d,test)
$(eval $(c))

运行结果和 $(eval $(call d,test))是一样的。


define中可以嵌套定义define。


call的语法:

$(call variable,param,param,...)

它首先把variable后面的参数分别赋给$1,$2等变量,然后在这个临时赋值的上下文中扩展variable,扩展的结果就是call函数的结果。


call在赋给临时变量时,会先扩展param。


call可以嵌套调用。


和其它函数一样,param之间的空白被保留,作为参数的一部分。


思考:

call的时候,它不关心变量的内容是啥,虽然它通常是一段Make syntax,最终的扩展结果会给eval函数使用。call,只是把variable中的变量和函数扩展,这个阶段是在eval之前的,当然variable中也可以包括eval,这样扩展的时候也会执行。


注意:

测试发现,call的variable必须是recursively expanded variable。


value的语法

$(value var)

得到var的定义,对于simple expanded variable它就是变量的值,对于recursively expanded variable,它是变量的定义。


eval的语法:

$(eval param)

首先扩展param,然后扩展的结果作为makefile syntax被parsed。扩展的结果可以包含变量,目标,rules等。

eval函数的结果是空字符串,因此eval函数可以出现在makefile的任何地方,而不引起语法错误。


看个例子:

1
2
3
4
5
6
7
8
9
define a
b := $1
c := /work/$b
endef
 
$(eval $(call a,sb))
 
test:
        echo $c

make test,输出结果为/work/。

分析:

执行$(call a,sb)的时候,b还是个未定义的变量,因此$(call a,sb)的结果是:

b := sb

c := /work/

这样,在调用eval的时候,就是单纯按上面定义了b和c。理解了call和eval的原理和过程,就能举一反三分析了。


这样变通一下,c就可以拿到变量b的值了:

define a
$(eval b := $1)
c := /work/$b
endef


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.