分类: C/C++
2006-01-25 09:28:33
前一阵子,看了linux驱动程序中makefile变量的写法,有些东西没搞清楚,所
以索性就想把关于这块的内容搞明白,在这里感谢Dragonfly,他给我推荐了
一篇好文章,看了之后,豁然开朗,写点心得,希望大家喜欢。
原文见这里:
如果觉得英文烦,就听我先给各位侃侃吧,没按字翻译,写了点觉得有用的东西。
一. 为什么使用变量
变量在makefile中用来代表一个字符串,用来表示
1. 一系列文件的名字
2. 传递给编译器的参数
3. 需要运行的程序
4. 需要查找源代码的目录
5. 你需要输出信息的目录
6. 你想做的其它事情。
说白了,这有些类似于编程语言中的宏。
二. 定义变量的方式和建议
变量的名字是大小新敏感的,从大的方面来说,makefile中的变量被分为两种,一个是用=来定义的,老外叫right-hand sides of variable,另外一种是用define关键字定义的,叫做bodies of variable。先简单说这些,后面详述。
传统上使用大写字母为变量命名,但是GNU推荐使用小写字母作为makefile内部使用的变量的名字,并用大写字母定义隐式规则中的参数或在命令行中允许用户重新定义的参数。
三. 基本的变量引用
用$(name)或${name}来引用一个变量,所以当你要表示一个$符号时,你要使用$$。当你只使用一个字母作为变量的名字时,你可以用$name来引用这个变量,不过GNU 并不推荐这样做,因为这种方式通常用来引用自动变量(Automatic Variables)。
四. 两种风格的变量定义
GNU有两种定义变量的方式,它们的不同体现在定义它们的风格和他们被展开的方式。
第一类叫做递归展开变量(Recursively Expanded Variable)。用=或define关键字都
可以定义这种变量,如果变量的定义引用了其它的变量,那么引用会一直展开下去,
直到找到被引用的变量的最新的定义,并以此作为改变量的值返回。例如:
代码: |
foo = $(bar) |
$(foo)的值究竟是什么呢? $(foo)被展开成$(bar),$(bar)被展开成$(ugh),最终$(ugh)
被展开成“Huh?”,那么$(foo)的值就是“Huh?”这种类型的变量是所有其他make工具支持
的变量类型。它有着自己的有点和缺点:
优点:
它可以向后引用变量
缺点:
1).你不能对该变量进行任何扩展,例如
CFLAGS=$(CFLAGS) -O
会造成无限循环展开,所有对于递归展开变量,这样做是不允许的。
2).如果makefile中的某个变量调用了某些函数,那么在变量被展开的每一次,函数都会被调用,
说的好些,无非是make慢点,更坏的情况是一些 shell函数和通配符的重复调用的结果往往
是难以预知的。
最后,用一个例子说明上面的问题。用这个makefile试一下就明白了(分别开启和注销掉第三行,
你就能看到结果)
代码: |
bar = test |
为了解决这些问题,GNU定义了另一中其它风格的变量,叫做简单扩展变量(Simply Expanded Variables)
简单扩展变量用符号:=来定义,用这种方式定义的变量,会在变量的定义点,按照被引用的变量的当前值
进行展开。同样写个例子,测试一下,这东西用文字表达太困难了
代码: |
m := mm |
这种定义变量的方式更适合在大的编程项目中使用,因为它更像我们一般的编程语言。
其实,GNU还对makefile中定义变量的方式进行了第三种扩展,用?=定义变量,它的含义是如果变量还没定义
就定义它,例如 FOO ?= Am I defined?,那么,它相当于下面的代码片断
代码: |
ifeq ($(origin FOO), undefined) |
这种写法可以用于为用户提供默认的选项,即如果用户不定义,就提供给他默认的。另外,要说明的是把变量
定义成NULl,也是定义了变量,此时?=将不再起作用。
注意:
最后要说的是,无论我们使用那种风格定义变量,都不要在定义变量的行后面加上随机数目的空格,之后写注释,
这个我们想象的是不一样的,例如
ml = magic_linux
和
ml = magic_linux # My favourite linux distribution
这是两个完全不同的变量,其中第一个ml的值是magic_linux,而后一个的值是‘magic_linux ’,所以除非
你有意要定义末尾带有空格的变量,否则不要在定义变量行末尾随便添加空格和注释。
五. 高级的变量引用方法
有两种方法: 1. 替换变量引用 2. 计算变量的值(更确切的说是推导)
第一种
定义方法: $(var: a=b) ${var: a=b}
意义:把其变量a的值中,每一个词的最后一个字幕换成b
例如:
代码: |
foo := a.o b.o c.o |
这时,bar的值等于a.c b.c c.c
另外,我们还可以用bar := $(foo:%.o=%.c)的形式
第二种
这是一种高级的makefile编程技术,一般我们很少使用它,但是它并不难,先看个例子
代码: |
x = y |
此时,a的值是什么呢?答案是z。因为内层的$(x)=y,而外层的$(y)=z,也就是说a的值是通过$(x)计算出来的,所以
叫做计算变量的值。当然嵌套可是有很多层,自己试试吧,不多说了。在嵌套变量引用的时候,还可以包含函数调用,
例如:
代码: |
x = variable1 |
一番推导,a的值等于Hello
整个变量的推导过程中可以涉及到多个变量,这样,一个变量就可以有多个字面值,例如:
代码: |
a_dirs := dira dirb |
根据usa_a和use_dirs这两个两个变量的值,dirs可以有不同的值
另外,推导变量值还可以用在替换变量引用中,例如:
代码: |
a_objects := a.o b.o c.o |
根据上面a1值的不同,source可以等于a.c b.c c.c或1.c 2.c 3.c
这种内嵌变量的限制是你不能把函数调用作为定义变量的一种方式,举个例子
代码: |
ifdef do_sort |
foo的是什么呢?它是sort a d b g q c或strip a d b g q c,而并不是我们想象的把a d b g q c作为参数
传递给sort或strip
你还可以把推导出来的变量名作为变量的左值
代码: |
dir = foo |
最后要说明的是,要注意把递归扩展变量和嵌套推广变量区分开。
六. 变量得到值的办法
1) 在你运行make的时候覆盖变量的值,例如make CFLAGS='-g -O'
2) 在makefile中用上面说的方式为变量赋值
3) 设定的环境变量可以在makefile中成为变量
4) GNU定义的一些自动变量,详见
5) 一些变量有固定的初始值,详见
七. 为变量添加值
你可以通过+=为已定义的变量添加新的值,例如
代码: |
objects = main.o foo.o bar.o utils.o |
看上去,+=有些和下面的代码类似
代码: |
objects = main.o foo.o bar.o utils.o |
但是,它们还是存在这一些不同
当变量从前没有被定义过, +=和=是一样的,它定义一个递归展开的变量,但是,当变量已经有定义的时候,+=只是简单
的进行字符的添加工作。
如果起初你用:=定义变量,那么+=只是利用变量的当前值进行添加,这和我们的直觉是一样的,例如:
variable := magic_
variable += linux
此时variable的值就是magic_linux
如果起初用=定义变量,+=的行为就变得有些古怪,它并不会在使用+=的地方马上进行变量展开,而是会把展开工作推后,
直到它找到最后变量的定义,这和=定义变量的行为是类似的,但是总觉得不合直觉,例如:
代码: |
var = I love |
当你使用var := I love和上面的情况作对比的时候,这种差别就明显了。这种定义方式在当变量中引用了其他的变量时是
很有用的。例如:
代码: |
CFLAGS = $(includes) -O |
由于CFLAGS是递归扩展的,所以make处理CFLAGS时,并不会对其进行扩展,所以只要在使用CFLAGS前,把include定义就好了。
看似我们可以用CFLAGS := $(CFLAGS) -O来完成上面的任务,但是他们之间仍有差别,这会使CFLAGS变成一个简单扩展型的变
量,如果此时include尚未定义,整个CFLAGS就会变成-O -pg,而我们用+=是想把CFLAGS设定成$(include) -O-pg,这显然和
我们想象的有差距。
八. 使用override关键字
一般情况下,如果你是用命令行参数定义一个变量的话,在makefile中的定义就会被忽略,如果你想让你的定义仍然有效,就要
使用override关键字,向下面这样:
override variable = magic 或 override variable := magic
此时,用户在命令行中指定的值就会被忽略,如果你想在用户指定的命令行后面添加上自己的文字,用下面的方法
override variable += magic
这个东西的使用动机就是,你可以为用户指定一些你默认想让他们使用的选项,例如:
override CFLAGS += -g
九. 使用define定义变量
define可以定义一个代表多行指令的变量,例如
代码: |
define two-lines |
它相当于
代码: |
two-lines = echo foo; echo $(bar) |
十. 为特定目标的指定变量值
我们可以在编译特定的目标文件的时候,为变量设定特殊的值,例如:
代码: |
prog : CFLAGS = -g |
在这个依赖关系中,编译prog时,就会为编译参数加上-g选项。当然你也可以使用override关键字
代码: |
prog : override CFLAGS = -g |