原创 Makedile之六 使用变量

2009-6-26 11:19 2931 8 8 分类: MCU/ 嵌入式

#使用变量

    在Makefile中定义的变量, 与C中的宏相同, 他代表了一个文本串,在Makefile中执行的时候
其会自动地展开在使用的地方. 与C所不同的是, 你可以在Makefile中改变其值. 在Makefile中,
变量可以使用在 "目标", "依赖目标", "命令" 或是Makefile的其它部分中.
    变量的命名可以包含字符, 数字, 下划线(可以是数字开头), 但不应该含有 ':', '#', '='
或是空字符(空格, 回车等). 变量是大小写敏感的, "foo", "Foo" 和 "FOO" 是三个不同的变量
传统Makefile的变量名是全大写的命名方式. 但推荐使用大小写搭配的变量名, 如: MakeFlags.
这样可以避免和系统的变量冲突, 而发生意外的事情.
    有一些变量是很奇怪字串, 如 '$<', '$@' 等, 这些是自动变量, 后面将会介绍.

一. 变量的基础

    变量在声明时需要给予初值, 而在使用时,需要给在变量名前加上 '$' 符号. 但最好用小括
号 "()" 或是大括号 "{}" 把变量给包括起来. 如果使用真实的 '$' 字符,那么需要用 "$$" 来
表示.
    变量可以使用在许多地方, 如规则中的 "目标", "依赖", "命令" 以及新的变量中. 例如:
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#Exampke make start
objects = program.o foo.o utils.o
program: $(objects)
    cc -o program $(objects)
$(objects): defs.h
#Example make end
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

变量会在使用它的地方精确地展开, 就像C中的宏, 例如:
foo = c
prog.o: prog.$(foo)
    $(foo)$(foo) -$(foo) prog.$(foo)
展开后得到:
prog.o: prog.c
    cc -c prog.c

    给变量加上括号完全是为了更加安全地使用这个变量.

二. 变量中的变量

    在定义变量的值时, 我们可以使用其它变量来构造变量的值, 在Makefile中有两种方式来在
用变量定义变量的值.

    1. 就是简单的使用 '=' 号

    在 '=' 左侧是变量, 右侧是变量的值, 右侧变量
的值可以定义在文件的任何一处, 也就是说, 右侧中的变量不一定非要是已定义好的值, 其也可
以使用后面定义的值. 如:
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#Exampke make start
foo = $(bar)
bar = $(ugh)
ugh = Huh?

all:
    @echo $(foo)
#Example make end
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
执行make, 可以看到输出结果是 "Huh?", 可见, 变量是可以使用后面的变量来定义的, 这是有别
于C的地方.

关于这种用法的利弊分析如下:
利用该方法, 我们可以把变量值放到后面来定义, 如:
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#Exampke make start
CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar
# 当 "CFLAGS" 被展开时, 会是 "-Ifoo -Ibar -O".
#Example make end
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

但这种形式也有不好的地方, 就是产生递归定义.
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#Exampke make start
CFLAGS = $(CFLAGS) -O
#或:
A = $(B)
B = $(A)
#Example make end
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
这让make陷入无限的变量展开过程中, 当然, make可以检测这样的定义, 并会报错. 如果在变量
中使用函数, 这种方式会让我们的make运行时非常慢, 更糟糕的是, 他会使用得两个make的函数
"wildcard" 和 "shell" 发生不可预知的错误. 因为无法预知这两个函数会被调用多少次.
    为了避免上面这种情况的发生, 我们可以使用make中的另一种用变量来定义变量的方法.

    2. 使用 ":=" 操作符

#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#Exampke make start
x := foo
y := $(x) bar
x := later
#等价于:
y := foo bar
x := later
#Example make end
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
使用这种方法, 前面的变量不能使用后面的变量, 只能使用前面已定义好了的变量, 如果是这样
y := $(x) bar
x := foo
那么, y的值是 "bar", 而不是 "foo bar".

#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#Exampke make start
#该例包括了make的函数, 条件表达式和一个系统变量 "MAKELEVEL”的使用:
ifeq (0, ${MAKELEVEL})
    cur-dir:= $(shell pwd)
    whoami := $(shell whoami)
    host-type := $(shell arch)
    MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif
#Example make end
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
系统变量 "MAKELEVEL" 记录了我们的当前Makefile的调用层数.

    如果我们要定义一个变量, 其值是一个空格, 那么我们可以这样来:
nullstring :=
space := $(nullstring)  ##

nullstring是一个Empty变量, 我们的space的值是一个空格. 因为在操作符的右边是很难描述一
个空格的, 以先用一个Empty变量来标明变量的值开始了, 而后面采用 '#' 注释符来表示变量
定义的终止, 这样, 我们可以定义出其值是一个空格的变量. 请注意这里关于 '#' 的使用,注释
符 '#' 的这种特性值得我们注意, 如果我们这样定义一个变量:
dir := /foo/bar    ##

dir变量值是 "/foo/bar", 后面还跟了4个空格, 使用该变量来指定别的目录 "$(dir)/file" 那
么结果便是错误的.

    3. 比较有用的操作符是 "?="

    FOO ?= bar,含义是, 如果FOO没有被定义过, 那么变量FOO的值就是 "bar", 如果FOO先前被
定义过, 那么这条语句无效. 其等价于:

ifeq ($(origin FOO), undefined)
    FOO = bar
endif

三. 变量高级用法

    1. 变量值的替换

    我们可以替换变量中的共有的部分,其格式是 "$(var:a=b)" 或是 "${var:a=b}", 其意思是
把变量 "var" 中所有以 "a" 字串 "结尾" 的 "a" 替换成 "b" 字串. 这里的 "结尾" 意思是 "
空格" 或是 "结束符".

foo := a.o b.o c.o
bar := $(foo:.o=.c)
上例中, 我们先定义了一个 "$(foo)" 变量, 接着把 "$(foo)" 中所有以 ".o" 字串结尾的全部
替换成 ".c", 所以我们的 "$(bar)" 的值就是 "a.c b.c c.c".

    另外一种变量替换的方法是以 "静态模式" 定义的, 如:

foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
##注意函数操作中间部内包括空格.

    2. 把变量的值再当成变量

例3-1:
    x = y
    y = z
    a := $($(x))
    在这个例子中, $(x)的值是 'y', 所以$($(x))就是$(y), 于是$(a)的值就是 'z'.

    我们还可以使用更多的层次:
    x = y
    y = z
    z = u
    a := $($($(x)))
    这里的$(a)的值是 'u'.

例3-2: 使用 "在变量定义中使用变量" 的第一个方式
    x = $(y)
    y = z
    z = Hello
    a := $($(x))
    这里的$($(x))被替换成了$($(y)), 因为$(y)值是 'z', 所以, 最终结果是: a:=$(z),也就
是 "Hello".

例3-3: 使用函数
    x = variable1
    variable2 := Hello
    y = $(subst 1,2,$(x))
    z = y
    a := $($($(z)))
    例子中, "$($($(z)))" 扩展为 "$($(y))", 再次被扩展为 "$($(subst 1,2,$(x)))", $(x)
值是 "variable1", subst函数把 "variable1" 中的所有 '1' 替换成 '2'.于是,"variable1"变
成 "variable2", 再取其值, 最终, $(a)的值就是$(variable2)的值 "Hello".

    在这种方式中, 可以使用多个变量来组成一个变量的名字, 然后再取其值:
    first_second = Hello
    a = first
    b = second
    all = $($a_$b)
    这里的 "$a_$b" 组成了 "first_second", 于是, $(all)的值就是 "Hello".

例3-4: 结合第一种技术
    a_objects := a.o b.o c.o
    1_objects := 1.o 2.o 3.o

    sources := $($(a1)_objects:.o=.c)
    这个例子中, 如果$(a1)的值是 'a' 的话, 那么, $(sources)的值就是 "a.c b.c c.c"; 如
果$(a1)的值是 '1', 那么$(sources)的值是 "1.c 2.c 3.c".

例3-5: "函数" 与 "条件语句" 一同使用

ifdef do_sort
    func := sort
else
    func := strip
endif

    bar := a d b g q c
    foo := $($(func) $(bar))
    这个示例中, 如果定义了 "do_sort", 那么, foo := $(sort a d b g q c), 于是$(foo)的
值就是 "a b c d g q", 而如果没有定义 "do_sort", 那么, foo := $(strip a d b g q c),调
用的就是strip函数.

例3-6:     "把变量的值再当成变量" 同样可以用在操作符的左边

    dir = foo
    $(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
    lpr $($(dir)_sources)
endef

这个例子中定义了三个变量:“dir”,“foo_sources”和“foo_print”。

四. 追加变量值

    可以使用 "+=" 操作符给变量追加值, 如:
    objects = main.o foo.o bar.o utils.o
    objects += another.o
    于是, 我们的$(objects)值变成: "main.o foo.o bar.o utils.o another.o"

    使用 "+=" 操作符, 可以模拟为下面的这种例子:
    objects = main.o foo.o bar.o utils.o
    objects := $(objects) another.o
    不同的是, 用 "+=" 更为简洁.

    如果变量之前没有定义过, 那么, "+=" 会自动变成 "=", 如果前面有变量定义, 那么 "+="
会继承于前次操作的赋值符, 如果前一次的是 ":=", 那么 "+=" 会以 ":=" 作为其赋值符, 如:
    variable := value
    variable += more
等价于:
    variable := value
    variable := $(variable) more

但如果是这种情况:
    variable = value
    variable += more
由于前次的赋值符是 "=", 所以 "+=" 也会以 "=" 来做为赋值,那么会发生变量的递归定义吗?
不必担心, make会自动为我们解决这个问题的.


五. override指示符
    
    如果有变量是通常make的命令行参数设置的, 那么Makefile中对这个变量的赋值会被忽略.
如果你想在Makefile中设置这类参数的值, 你可以使用 "override" 指示符. 语法是:

override <variable> = <value>
override <variable> := <value>

还可以追加:
override <variable> += <more text>

对于多行的变量定义, 我们用define指示符, 在define指示符前, 也同样可以使用ovveride指示
符, 如:
    override define foo
        bar
    endef

六. 多行变量

    另外一种设置变量值的方法是使用define关键字. 使用define关键字设置变量的值可以有换
行, 这有利于定义一系列的命令, 前面我们讲过 "命令包" 的技术就是利用这个关键字.
    define指示符后面跟的是变量的名字, 而重起一行定义变量的值, 定义以endef关键字结束.
其工作方式和 '=' 操作符一样. 变量的值可以包含函数, 命令, 文字或是其它变量.因为命令需
要以Tab键开头, 所以如果你用define定义的命令变量中没有以Tab键开头, 那么make就不会把其
认为是命令.

下面的这个示例展示了define的用法:
define two-lines
    echo foo
    echo $(bar)
endef


七. 环境变量

    make运行的系统环境变量可以在make开始运行时被载入到Makefile文件中, 但如果Makefile
中已定义了这个变量, 或是这个变量由make命令行带入, 那么系统的环境变量的值将被覆盖. 如
果make指定了 "-e" 参数, 那么, 系统环境变量将覆盖Makefile中定义的变量.
    因此, 如果在环境变量中设置了 "CFLAGS" 环境变量, 那么我们就可以在所有的Makefile中
使用这个变量了. 这对于我们使用统一的编译参数比较方便. 如果Makefile中定义了CFLAGS, 那
么则会使用Makefile中的这个变量, 如果没有定义则使用系统环境变量的值, 一个共性和个性的
统一, 很像 "全局变量" 和 "局部变量" 的特性.
    当make嵌套调用时, 上层Makefile中的变量会传递到下层的Makefile中. 当然, 默认情况下
只有通过命令行设置的变量会被传递. 而定义在文件中的变量,如果要向下层Makefile传递, 则
需要使用exprot关键字来声明.
    不推荐把许多的变量都定义在系统环境中, 这样, 在我们执行不用的Makefile时, 拥有的是
同一套系统变量, 这可能会带来更多的麻烦.

八. 目标变量
    
    前面我们所讲的在Makefile中定义的变量都是 "全局变量" 在整个文件, 我们都可以访问这
些变量. 当然, "自动变量" 除外, 如 "$<" 等这种类量的自动化变量就属于 "规则型变量", 这
种变量的值依赖于规则的目标和依赖目标的定义.
    当然, 同样可以为某个目标设置局部变量, 这种变量被称为 "Target-specific Variable"
它可以和 "全局变量" 同名, 因为它的作用范围只在这条规则以及连带规则中, 所以其值也只在
作用范围内有效. 而不会影响规则链以外的全局变量的值. 其语法是:

    <target ...> : <variable-assignment>
    <target ...> : overide <variable-assignment>
    <variable-assignment>可以是前面讲过的各种赋值表达式,如 '=', ':=', '+=' 或是 '?='
第二个语法是针对于make命令行带入的变量, 或是系统环境变量.
    这个特性非常的有用, 当我们设置了这样一个变量: 这个变量会作用到由这个目标所引发的
所有的规则中去. 如:
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#Exampke make start
prog: CFLAGS = -g
prog: prog.o foo.o bar.o
    $(CC) $(CFLAGS) prog.o foo.o bar.o

prog.o: prog.c
    $(CC) $(CFLAGS) prog.c
foo.o : foo.c
    $(CC) $(CFLAGS) foo.c
bar.o : bar.c
    $(CC) $(CFLAGS) bar.c
#Example make end
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
在这个示例中, 不管全局的$(CFLAGS)值是什么, 在prog目标以及其所引发的所有规则中(prog.o
foo.o bar.o的规则), $(CFLAGS)的值都是 "-g".

九. 模式变量

    在GNU的make中, 还支持模式变量(Pattern-specific Variable), 通过上面的目标变量中,
我们知道, 变量可以定义在某个目标上. 模式变量的好处就是, 我们可以给定一种 "模式",可以
把变量定义在符合这种模式的所有目标上.
    我们知道, make的 "模式" 一般是至少含有一个 '%' 的, 所以,我们可以以如下方式给所有
以.o结尾的目标定义目标变量:
    %.o: CFLAGS = -O
模式变量的语法和 "目标变量" 一样:
    <pattern ...> : <variable-assignment>
    <pattern ...> : override <variable-assignment>
override同样是针对于系统环境传入的变量, 或是make命令行指定的变量.


【2006-12-25】

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
我要评论
0
8
关闭 站长推荐上一条 /3 下一条