QQ登录

只需一步,快速开始

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 5086|回复: 3

makefile中变量的使用心得

[复制链接]
发表于 2005-4-24 10:47:32 | 显示全部楼层 |阅读模式
前一阵子,看了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的选项。
                                                                                 
                                                                                  by puretears
                                                                                      fin
      ======================================================================================================================

      最后真诚感谢各位能耐着性子看到这里,唉,总觉得自己的表达能力有问题,如果各位有什么疑问或发现了什么错误,请一定要指出来
      我会及时改正,谢谢大家。
发表于 2005-4-24 20:49:53 | 显示全部楼层
这帖不适合放在Magic技术支持版吧?
移到哪个版面比较合适?
回复

使用道具 举报

 楼主| 发表于 2005-4-24 20:54:17 | 显示全部楼层
我就是没想好放到哪,所以贴在这里了,给版主带来麻烦了,不好意思!  
回复

使用道具 举报

发表于 2005-4-25 10:56:15 | 显示全部楼层
可以发《应用编程技术与项目孵化》版
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

GMT+8, 2024-4-24 22:06 , Processed in 0.136622 second(s), 15 queries .

© 2021 Powered by Discuz! X3.5.

快速回复 返回顶部 返回列表