在Makefile中,变量的值都是一个字符串,所以在Makefile中,变量更像是C语言中的宏定义,当使用到这个变量的时候简单地将这个变量的值(字符串)替代变量所在的位置。变量可以存在于target,prerequisite和recipe中的任何地方,在Makefile中,定义变量的方式和C语言类似,在使用变量的时候,都需要在这个变量的前面加一个‘$'符号(如果你想表示一个'$"符号,则需要用两个'$'表示,即$$),不知道为什么选这个符号,可能是当时Stuart Feldman这位大神当时在开发make时比较缺money吧,或者是想跟shell中的变量引用保持一致吧。有时候,为了显示更加清晰会用圆括号或者花括号将'$'符号后面的变量名用围起来。
一、普通变量
Makefile中的变量可以分为以下两种类型,它们的区别在于变量的展开方式。
1. 循环展开变量,这种类型的变量在需要对其进行展开的时候,如果该变量的值会使用到其他变量,那么引用到的其他变量的值也将会一同被展开,直到没有可继续展开的变量。这种类型的变量用简单的等号’=‘或者define指令来进行定义。这种变量的好处就是你可以在定义变量的时候继续引用其他的变量也不需要担心make无法解析出该变量的值,但这也是它的不足,有时候你只是希望这个变量只是以你定义它时的那个字符串展开,即保留着引用其他变量的形式,这时候你就只能使用简单展开变量了;
2. 简单展开变量,这种类型的变量当使用的时候,它就会进行简单的展开,其展开形式和你定义它时使用的字符串完全一样,即使里面包含了其他的变量,make并不会将这些变量当作真正的变量看待,而只是简单的字符串。也就是说当make看到简单展开变量的时候,只会将其进行第一层的展开,如果你想要将其完全展开的话,则需要对简单展开变量进行多次的展开。这种类型的变量可以用冒号加等号的形式定义,即':='或'::='。
一些常用的变量赋值符号:
= | 循环展开变量赋值符号 |
:=或::= | 简单展开变量赋值符号 |
?= | 只有当这个变量还没有被定义的时候这个赋值才会生效,并且被赋值的变量属于循环展开变量 |
!= | shell赋值运算符 |
+= | 用于添加文本到某一个变量中 |
当然还可以用define指令去定义变量,这种方法可以使变量的值中包含换行字符,这种方法好像不是很常用,可以自己去参考GNU make manual.
当make程序启动的时候,所有make看到的系统环境变量make都会按照原来系统环境变量的名字创建一个名字和变量值完全一样的make变量,并且如果你在Makefile中有对这些变量重新赋值或者更改,那么对这些变量的更改将覆盖其原来的值。也就是说你可以在Makefile中对这些看似环境变量的值进行更改而不要担心会对系统环境进行更改,因为更改的其实是make变量。
Makefile中变量一般都是全局变量的,这些变量不仅仅作用于当前的Makefile中,还可以通过prerequisite继承延续到其他的Makefile中,如果你不想要当前Makefile中的变量继承到其他的Makefile的话,则可以在定义变量的时候给变量加一个private的标记。在Makefile中,变量的值一般都是全局变量,除非这个变量是目标指定变量(target-specific variable)或者自动变量。目标指定变量就是说你可以根据不同的目标对某一个变量进行赋值,并且该变量只有在制定的目标的规则里面才有效(包括规则的prerequisite,及其prerequisite的prerequisite),其实就是某一条规则的局部变量,只是变量名会和其他规则的局部变量相同,但实际上它们在不同的规则里面是项目独立的变量,互不干扰。这样你就可以根据不同的目标给变量赋不同的值,定义目标指定变量的形式如下:
target ...: target-specific variable assignment
如果你想目标指定变量不影响到该规则的prerequisite,那么你可以将这种变量标记为private,这样这个变量只有local的规则可以看见,它的prerequisite是看不见这个变量的。
类似的你可以定义模式指定变量,其形式如下:
pattern ...: pattern-specific variable-assignment
这种变量和目标指定变量类似,只是其作用的范围是匹配于某种模式的规则。
变量的一个实用小功能:
替换,即你可以通过$(var:a=b)这样的形式将变量var中所有以a字符结尾的文件名都替换成b,如假设变量var是当前目录下所有c文件,并且每个.c文件都需要编译生成相应的.o文件,则可以通过$(var:c=o)就可以简单得得到所有想要的.o文件名,而不需要逐个编辑。
另外GNU make里面还提供了很多字符串操作的函数,其实很多就是对变量的值(字符串)的一些操作,make中函数的引用跟变量的引用很相似,其形式为
$(function arguments) or ${function arguments}
如果你在执行mke程序的时候在shell命令行里面顺便去赋值某个变量,那么这个赋值动作将会覆盖Makefile中对这个变量的赋值,即make将会忽略Makefile中对这个变量的更改,除非你在Makefile中定义变量的时候在其前面加上override标记。这种功能应该挺少用到,所以这就提到为止。
另外make还会自己定义一些具有特殊意义的变量,如果想了解更多这些变量可以查询GNU make manual的6.14章节。
二、自动变量
自动化变量使得Makefile编辑起来更加的智能和简洁,只是刚开始的时候看这些自动变量确实不方便,跟看天书似的。自动变量是make自己定义很更新的,每当make执行一条新的规则的时候,make都会根据这条规则的target和prerequisite对这些自动变量进行计算和更新,所以自动变量类似于上面说到的标记为private的目标指定变量,但是其作用范围更加严格,其作用范围是每条规则本身的recipe,在target和prerequisite中都无效。以下是make中的自动变量:
$@ | target的文件名。如果这个target是一个archive成员,那么'$@'就是那个archive文件名。如果在模式规则中,则'$@'就会被赋值成所有可以导致这条规则被执行的target文件名序列。 |
$% | 当target是archive的时候,$%就是相应的archive member |
$< | 第一个prerequisite的名字。如果target通过隐含规则得到它的recipe,那么$<就是隐含规则添加的第一个prerequisite的名字。 |
$? | 所有比target更新的prerequisite名字序列,名字之间用空格隔开。 |
$^ | 所有prerequisite的名字序列,名字之间用空格隔开,并且make会自动将其中重复的名字去掉,即每个名字最多只有一份。 |
$+ | 该变量和$^完全相同,除了它不会对这些名字序列去重,所以相同的名字可能出现多次。 |
$| | 所有顺序(order-only)prerequisite的名字序列,中间以空格隔开。 |
$* | 隐含规格匹配的stem |
注:
1. archive文件,一般是指将多个程序和数据打包到一起的文件,如软件包,archive文件所包含的子文件就叫做archive成员,archive成员和archive文件一般由ar程序来处理,其目的是创建一些library之类的文件。某一个archive成员可以作为make规则中的target或者prerequisite,其出现的形式为: archive(member);这东西在Makefile里面好像并不是很常见。
模式规则:匹配于某种模式的规则,将在隐含规则中做解释。
stem:即模式匹配中匹配的那一部分字符串。
上面所述的这些自动变量其所包含的文件其实不仅仅包含了文件的文件名,还包含了该文件的目录名,所以可以通过通过$(@D)来获取$@这个变量的目录,即在变量的名字后面加一个D,或通过$(@F)来获取$@这个变量的文件名部分,即在变量名的后面添加一个F。
写这个是本着大家一起学习探讨的目的,阅过的请留下您的宝贵意见,我会及时回复。
--The Magic That Brings Hardware To Life.