puretears 发表于 2005-4-24 10:47:32

makefile中变量的使用心得

前一阵子,看了linux驱动程序中makefile变量的写法,有些东西没搞清楚,所
以索性就想把关于这块的内容搞明白,在这里感谢Dragonfly,他给我推荐了
一篇好文章,看了之后,豁然开朗,写点心得,希望大家喜欢。

原文见这里:
http://www.gnu.org/software/make/manual/html_chapter/make_6.html#SEC65
如果觉得英文烦,就听我先给各位侃侃吧,没按字翻译,写了点觉得有用的东西。

一. 为什么使用变量
变量在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)
bar = $(ugh)
ugh = Huh?
$(foo)的值究竟是什么呢? $(foo)被展开成$(bar),$(bar)被展开成$(ugh),最终$(ugh)
被展开成“Huh?”,那么$(foo)的值就是“Huh?”这种类型的变量是所有其他make工具支持
的变量类型。它有着自己的有点和缺点:
优点:
   它可以向后引用变量
缺点:
1).你不能对该变量进行任何扩展,例如
   CFLAGS=$(CFLAGS) -O
   会造成无限循环展开,所有对于递归展开变量,这样做是不允许的。
2).如果makefile中的某个变量调用了某些函数,那么在变量被展开的每一次,函数都会被调用,
   说的好些,无非是make慢点,更坏的情况是一些 shell函数和通配符的重复调用的结果往往
   是难以预知的。
最后,用一个例子说明上面的问题。用这个makefile试一下就明白了(分别开启和注销掉第三行,
你就能看到结果)
bar = test
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all: ;echo foo

为了解决这些问题,GNU定义了另一中其它风格的变量,叫做简单扩展变量(Simply Expanded Variables)
简单扩展变量用符号:=来定义,用这种方式定义的变量,会在变量的定义点,按照被引用的变量的当前值
进行展开。同样写个例子,测试一下,这东西用文字表达太困难了
m := mm
x := $(m)
y := $(x) bar
x := later
all:;echo $(x) $(y)
这种定义变量的方式更适合在大的编程项目中使用,因为它更像我们一般的编程语言。

其实,GNU还对makefile中定义变量的方式进行了第三种扩展,用?=定义变量,它的含义是如果变量还没定义
就定义它,例如 FOO ?= Am I defined?,那么,它相当于下面的代码片断
ifeq ($(origin FOO), undefined)
        FOO = Am I defined?
endif
这种写法可以用于为用户提供默认的选项,即如果用户不定义,就提供给他默认的。另外,要说明的是把变量
定义成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 := $(foo:.o=.c)
all:;echo $(bar)
这时,bar的值等于a.c b.c c.c
另外,我们还可以用bar := $(foo:%.o=%.c)的形式

第二种
这是一种高级的makefile编程技术,一般我们很少使用它,但是它并不难,先看个例子
x = y
y = z
a := $($(x))
此时,a的值是什么呢?答案是z。因为内层的$(x)=y,而外层的$(y)=z,也就是说a的值是通过$(x)计算出来的,所以
叫做计算变量的值。当然嵌套可是有很多层,自己试试吧,不多说了。在嵌套变量引用的时候,还可以包含函数调用,
例如:
x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))
一番推导,a的值等于Hello
整个变量的推导过程中可以涉及到多个变量,这样,一个变量就可以有多个字面值,例如:
a_dirs := dira dirb
1_dirs := dir1 dir2

a_files := filea fileb
1_files := file1 file2

ifeq "$(use_a)" "yes"
a1 := a
else
a1 := 1
endif

ifeq "$(use_dirs)" "yes"
df := dirs
else
df := files
endif

dirs := $($(a1)_$(df))
根据usa_a和use_dirs这两个两个变量的值,dirs可以有不同的值

另外,推导变量值还可以用在替换变量引用中,例如:
a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o

sources := $($(a1)_objects:.o=.c)
根据上面a1值的不同,source可以等于a.c b.c c.c或1.c 2.c 3.c

这种内嵌变量的限制是你不能把函数调用作为定义变量的一种方式,举个例子
ifdef do_sort
func := sort
else
func := strip
endif

bar := a d b g q c

foo := $($(func) $(bar))
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
$(dir)_sources := $(wildcard $(dir)/*.c)

最后要说明的是,要注意把递归扩展变量和嵌套推广变量区分开。

六. 变量得到值的办法
1) 在你运行make的时候覆盖变量的值,例如make CFLAGS='-g -O'
2) 在makefile中用上面说的方式为变量赋值
3) 设定的环境变量可以在makefile中成为变量
4) GNU定义的一些自动变量,详见http://www.gnu.org/software/make/manual/html_chapter/make_10.html#SEC111
5) 一些变量有固定的初始值,详见http://www.gnu.org/software/make/manual/html_chapter/make_10.html#SEC106

七. 为变量添加值
你可以通过+=为已定义的变量添加新的值,例如
objects = main.o foo.o bar.o utils.o
objects += another.o
看上去,+=有些和下面的代码类似
objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o
但是,它们还是存在这一些不同

当变量从前没有被定义过, +=和=是一样的,它定义一个递归展开的变量,但是,当变量已经有定义的时候,+=只是简单
的进行字符的添加工作。

如果起初你用:=定义变量,那么+=只是利用变量的当前值进行添加,这和我们的直觉是一样的,例如:
variable := magic_
variable += linux
此时variable的值就是magic_linux

如果起初用=定义变量,+=的行为就变得有些古怪,它并不会在使用+=的地方马上进行变量展开,而是会把展开工作推后,
直到它找到最后变量的定义,这和=定义变量的行为是类似的,但是总觉得不合直觉,例如:
var = I love
variable = linux

var += $(variable)

variable = magic

all:;echo $(var)
当你使用var := I love和上面的情况作对比的时候,这种差别就明显了。这种定义方式在当变量中引用了其他的变量时是
很有用的。例如:
CFLAGS = $(includes) -O
...
CFLAGS += -pg # enable profiling
由于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
echo foo
echo $(bar)
endef
它相当于
two-lines = echo foo; echo $(bar)

十. 为特定目标的指定变量值
我们可以在编译特定的目标文件的时候,为变量设定特殊的值,例如:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
在这个依赖关系中,编译prog时,就会为编译参数加上-g选项。当然你也可以使用override关键字
prog : override CFLAGS = -g
prog : prog.o foo.o bar.o

十一. 为特定样式的文件指定变量值
我们还可以为生成特定各式的文件指定特殊的编译参数,例如
%.o : CFLAGS = -O
这样,所有生成.o的编译参数都会被加上-O的选项。
                                                                           
                                                                            by puretears
                                                                                fin
======================================================================================================================

最后真诚感谢各位能耐着性子看到这里,唉,总觉得自己的表达能力有问题,如果各位有什么疑问或发现了什么错误,请一定要指出来
我会及时改正,谢谢大家。

樱家冢 发表于 2005-4-24 20:49:53

这帖不适合放在Magic技术支持版吧?
移到哪个版面比较合适?

puretears 发表于 2005-4-24 20:54:17

我就是没想好放到哪,所以贴在这里了,给版主带来麻烦了,不好意思!:oops:

casper 发表于 2005-4-25 10:56:15

可以发《应用编程技术与项目孵化》版
页: [1]
查看完整版本: makefile中变量的使用心得