makefile中变量的使用心得(转)

时间:2022-04-05 13:37:58



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

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

一. 为什么使用变量
变量在makefile中用来代表一个字符串,用来表示
      

      1. 一系列文件的名字
              2. 传递给编译器的参数
              3. 需要运行的程序
              4. 需要查找源代码的目录
              5. 你需要输出信息的目录
              6. 你想做的其它事情。[/list:u]
      说白了,这有些类似于编程语言中的宏。

      二. 定义变量的方式和建议
      变量的名字是大小新敏感的,从大的方面来说,makefile中的变量被分为两种,
      一个是用=来定义的,老外叫right-hand sides of variable,另外一种是用
      define关键字定义的,叫做bodies of variable。先简单说这些,后面详述。

      传统上使用大写字母为变量命名,但是GNU推荐使用小写字母作为makefile内部
      使用的变量的名字,并用大写字母定义隐式规则中的参数或在命令行中允许用户
      重新定义的参数。

      三. 基本的变量引用
      用$(name)或${name}来引用一个变量,所以当你要表示一个$符号时,你要使用$$。
      当你只使用一个字母作为变量的名字时,你可以用$name来引用这个变量,不过GNU
      并不推荐这样做,因为这种方式通常用来引用自动变量(Automatic Variables)。

      四. 两种风格的变量定义

      GNU有两种定义变量的方式,它们的不同体现在定义它们的风格和他们被展开的方式。

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

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

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

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

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

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

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

      bar := a d b g q c

      foo := $($(func) $(bar)) [/code:1]
      foo的是什么呢?它是sort a d b g q c或strip a d b g q c,而并不是我们想象的把a d b g q c作为参数
      传递给sort或strip

      你还可以把推导出来的变量名作为变量的左值
      [code:1]dir = foo
      $(dir)_sources := $(wildcard $(dir)/*.c) [/code:1]

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

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

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

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

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

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

            var += $(variable)

            variable = magic

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

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

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