self-confidence,the source of all the power

导航

Makefile系列之三 : 变量

一、变量的基础
  变量在声明时需要给予初值,而在使用时,需要给在变量名前加上“$”符号,但最好用小括号“()”或是大括号“{}”把变量给包括起来。如果你要使用真实的“$”字符,那么你需要用“$$”来表示。
二、变量中的变量
  在定义变量的值时,我们可以使用其它变量来构造变量的值,在Makefile中有两种方式来在用变量定义变量的值。
  1)使用“=”号,在“=”左侧是变量,右侧是变量的值,右侧变量的值可以定义在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好的值,如:
        foo = $(bar)
        bar = $(ugh)
        ugh = Huh?
        all:
          echo $(foo)
  执行“make all”的输出结果 : 打出变量$(foo)的值是“Huh?”

  可见,变量是可以使用后面的变量来定义,这个功能好处就是可以把变量的真实值推到后面来定义,缺点就是在碰到递归调用时会让make运行效率慢或报错。更糟糕的是,他会使用得两个make的函数“wildcard” 和“shell”发生不可预知的
错误。因为你不会知道这两个函数会被调用多少次。为了避免上面的这种方法,我们可以使用第二种用变量来定义变量的方法。

  2)是“:=”操作符,如:
        x := foo
        y := $(x) bar
        x := later
    其等价于:
        y := foo bar
        x := later
  很显然,这种方法,前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。如果是这样:
        y := $(x) bar
        x := foo
  那么,y的值是“bar”,而不是“foo bar”。
  小技巧:

  值为空格的变量,如
        nullstring :=
        space := $(nullstring) # end of the line
  nullstring是一个Empty变量,其中什么也没有,而操作符的右边是很难描述一个空格的,所以采用“#”注释符来表示变量定义的终止,这样就可以定义出其值是一个空格的变量。

三、变量高级用法
  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)
  
这依赖于被替换字串中的有相同的模式,模式中必须包含一个“%”字符,$(bar)变量的值为“a.c b.c c.c”。
  2)把变量的值再当成变量。先看一个例子:
        x = y
        y = z
        a := $($(x))
  
在这个例子中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。

  另一个例子:
        x = variable1
        variable2 := Hello
        y = $(subst 1,2,$(x))
        z = y
        a := $($($(z)))
  
这个例子中,“$($($(z)))”扩展为“$($(y))”,而其再次被扩展为 “$($(subst1,2,$(x)))”。$(x)的值是“variable1”,subst函数把“variable1”中的所有“1”字串替换成“2”字串,于是,“variable1”变成“variable2”,再取其值,所以,最终,
$(a)的值就是$(variable2)的值——“Hello”。
  
3)多个变量来组成一个变量的名字,然后再取其值:
        first_second = Hello
        a = first
        b = second
        all = $($a_$b)
  
这里的“$a_$b”组成了“first_second”,于是,$(all)的值就是“Hello”。
  
4)这种技术和“函数”与“条件语句”一同使用的例子:
        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函数。

四、追加变量值
   可以使用“+=”操作符给变量追加值,如:
        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
  如果变量之前没有定义过,那么,“+=”会自动变成“=”,如果前面有变量定义,那么“+=”会继承于前次操作的赋值符。如果前一次的是“:=”,那么“+=”会以“:=”作为其赋值符。

五、目标变量
  相当于局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效,而不会影响规则链以外的全局变量的值。其语法是:
        <target ...> : <variable-assignment>
        <target ...> : overide <variable-assignment>
  <variable-assignment>可以是前面讲过的各种赋值表达式,如“=”、“:=”、“+=”或是“?=”。第二个语法是针对于make命令行带入的变量,或是系统环境变量。
这个特性非常的有用,当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中去。如:
        prog : CFLAGS = -g
        prog : prog.o foo.o 
          $(CC) $(CFLAGS) prog.o foo.o 
        prog.o : prog.c
          $(CC) $(CFLAGS) prog.c
        foo.o : foo.c
          $(CC) $(CFLAGS) foo.c
  在这个示例中,不管全局的$(CFLAGS)的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o 的规则),$(CFLAGS)的值都是“-g”。

模式变量

  目标变量的应用扩展,模式变量的好处就是,我们可以给定一种“模式”,可以把变量定义在符合这种模式的所有目标上。
我们知道,make的“模式”一般是至少含有一个“%”的,所以,我们可以以如下方式给所有以[.o]结尾的目标定义一个统一的目标变量(也就是模式变量):
        %.o : CFLAGS = -O
  
同样,模式变量的语法和“目标变量”一样:
        <pattern ...> : <variable-assignment>
        <pattern ...> : override <variable-assignment>
  
override同样是针对于系统环境传入的变量,或是make命令行指定的变量。

六、自动化变量

  所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。下面是所有的自动化变量及其说明:
  1)$@
    ——表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。
  2)$%
    ——仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就 是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。
  3)$<
    ——依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。
  4)$?
    ——所有比目标新的依赖目标的集合,以空格分隔。
  5)$^
    ——所有的依赖目标的集合。以空格分隔。如在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标。
  6)$+
    ——这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
  7)$*
    ——这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的模式是"a.%.b",那么,"$*"的值就是"dir/a.foo"。如果目标中没有模式的定义,那么"$*"也就不能被推导出,但是,如果目标文件的后缀是 make所识别的,那么"$*"就是除了后缀的那一部分。例如:如果目标是"foo.c",因为".c"是make所能识别的后缀名,所以,"$*"的值 就是"foo"。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用"$*",除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么"$*"就是空值。
  在上述所列出来的自动量变量中。四个变量($@、$<、$%、$*)在扩展时只会有一个文件,
而另三个的值是一个文件列表。这七个自动化变量还可以取得文件的目录名或是在当前目录下的符合模式的文件名,只需要搭配上"D"或"F"字样。这是 GNU make中老版本的特性,在新版本中,我们使用函数"dir"或"notdir"就可以做到了。"D"的含义就是Directory,就是目录,"F"的含义就是File,就是文件。
  下面是对于上面的七个变量分别加上"D"或是"F"的含义:
    $(@D)
    表示"$@"的目录部分(不以斜杠作为结尾),如果"$@"值是"dir/foo.o",那么"$(@D)"就是"dir",而如果"$@"中没有包含斜杠的话,其值就是"."(当前目录)。
    $(@F)
    表示"$@"的文件部分,如果"$@"值是"dir/foo.o",那么"$(@F)"就是"foo.o","$(@F)"相当于函数"$(notdir $@)"。
    "$(*D)","$(*F)"
    同上,也是取文件的目录部分和文件部分。对于上面的那个例子,"$(*D)"返回"dir",而"$(*F)"返回"foo"
    "$(%D)" "$(%F)"
    分别表示了函数包文件成员的目录部分和文件部分。这对于形同"archive(member)"形式的目标中的"member"中包含了不同的目录很有用。
    "$(<D)" "$(<F)"
    分别表示依赖文件的目录部分和文件部分。
    "$(^D)" "$(^F)"
    分别表示所有依赖文件的目录部分和文件部分。(无相同的)
    "$(+D)" "$(+F)"
    分别表示所有依赖文件的目录部分和文件部分。(可以有相同的)
    "$(?D)" "$(?F)"
    分别表示被更新的依赖文件的目录部分和文件部分。

posted on 2013-12-22 01:58  漩涡鸣人  阅读(332)  评论(0编辑  收藏  举报