zoukankan      html  css  js  c++  java
  • Makefile学习笔记之变量定义与赋值

    1. makefile的基本规则

    target ... : prerequisites ...
        command
        ...
        ...
    
    • target
      可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。
    • prerequisites
      生成该target所依赖的文件和/或target
    • command
      该target要执行的命令(任意的shell命令),注意命令必须要以 Tab 键开始。

    这就是Makefile中文件的依赖关系,target这一个或多个目标文件依赖于prerequisites中的文件,其生成规则定义在command中。
    prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
    一般来说,make会以UNIX的标准Shell,也就是 /bin/sh 来执行命令。

    2. 定义变量

    在Makefile中定义的变量,就像是C/C++语言中的宏一样,执行时会原模原样地展开在所使用的地方。
    变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有 : 、 # 、 = 或是空字符(空格、回车等)。

    在使用时,需要给在变量名前加上 $ 符号,但最好用小括号 () 或是大括号 {} 把变量给包括起来。小括号的用法比较常见。

    2.1 一种叫做递归展开 (Recursively Expanded),使用 = 或者 define 语句来定义变量的值。

    在使用 = 号定义变量的值时,左侧是变量,右侧是变量的值,可以是一个单词,也可以是空格隔开的多个单词,一直到该行末尾。

    直到该变量被使用时等号右边的内容才会被展开。而且每次使用该变量时,等号右边的内容都会被重新展开。

    foo = $(bar)
    bar = $(ugh)
    ugh = Huh?
    
    all:
    	echo $(foo)
    

    执行 make all 将会打印出变量 $(foo) 的值为 Huh?。$(foo) 被展开成 $(bar),$(bar) 被展开成 $(ugh),$(ugh) 被展开成 Huh?,于是最后输出为 Huh?

    • 优点

      使用这种方法的一个好处是,我们可以把变量的真实值推到后面来定义。

      CFLAGS = $(include_dirs) -O
      include_dirs = -Ifoo -Ibar
      

      CFLAGS 在命令中被展开时,会是-Ifoo -Ibar -O

    • 缺点

      • 当然最主要的缺点就是递归定义可能导致出现无限循环展开,尽管 make 能检测出这样的无限循环展开并报错。
        如下示例

        CFLAGS = $(CFLAGS) -O
        
      • 另一个问题就是如果在变量中使用函数,每次展开变量时都要重新执行函数,这种方式会使make运行得非常慢。
        更糟糕的是,这种用法会使得“wildcard”和“shell”发生不可预知的错误,因为你不知道这两个函数会被调用多少次。

    2.2 另一种叫做简单展开 (Simply Expanded),使用 := 来定义变量的值。

    使用这种方法,读到变量定义这一行时 等号右边立即被展开,引用的所有变量也会被立即展开。

    前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。

    x := foo
    y := $(x) bar
    x := later
    

    等价于:

    y := foo bar
    x := later
    

    使用这种方法可以在变量中引入开头空格。见下面的示例。

    nullstring :=
    space := $(nullstring) # end of the line
    

    nullstring 是一个 Empty 变量,其中什么也没有,而 space 的值是一个空格。
    因为在操作符的右边是很难描述一个空格的,这里采用的技术很管用。
    先用一个 Empty 变量来标明变量的值开始了,而后面采用“#”注释符来表示变量定义的终止,这样,我们可以定义出其值是一个空格的变量。

    2.3 条件变量赋值,使用 ?= 操作符

    FOO ?= bar
    

    等价于:

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

    如果 FOO 没有被定义过,那么变量 FOO 的值被定义为 bar,如果FOO先前被定义过,那么这条语将什么也不做。
    注意将变量定义为空字符也是定义的一种,这里的 ?= 并没有进行这种操作。

    2.4 追加变量值,使用 += 操作符

    在已定义的变量后面追加更多的值也是一种很常见的用法。

    • 2.4.1 对未定义变量使用追加

      如果变量之前没有定义过,那么,+= 会自动变成 =,追加变量直接变成递归展开。

    • 2.4.2 对使用 := 方式定义的变量使用追加

      如果前面是以简单展开方式 (:=) 定义的变量,那么 += 在将新的值追加到已有变量的值的后面之前,会以简单展开 (:=) 的方式将新的内容先展开。
      换句话说,如果第一次定义变量时使用的是 := ,那么 += 会以 := 作为其赋值符。

      variable := value
      variable += more
      

      等价于:

      variable := value
      variable := $(variable) more
      
    • 2.4.3 对使用 = 方式定义的变量使用追加

      我们前面解释过,当使用 = 定义递归展开的变量时,make 并不会立即为变量或函数展开设置的值,而是先以字面内容将变量或函数原样保存,待之后引用变量时再逐步展开。

      与 2.4.2 相比,如果对使用递归展开方式定义的变量使用追加,情况稍稍有些不同。

      第一种不包含任何其他变量的情况,理解起来比较简单。

      objects = main.o foo.o bar.o utils.o
      objects += another.o
      

      将会把 another.o 追加进 objects 变量中,使其变成 main.o foo.o bar.o utils.o another.o

      make 解释上面的语句时其实相当于引入了一个中间变量 temp,

      temp = main.o foo.o bar.o utils.o
      objects = $(temp) another.o
      

      其结果类似于使用简单展开操作。

      objects = main.o foo.o bar.o utils.o
      objects := $(objects) another.o
      

      显然用 += 更为简洁。

      第二种包含有其他变量或函数的情况

      CFLAGS = $(includes) -O
      …
      CFLAGS += -pg # enable profiling
      

      第一行定义 CFLAGS 时引用了另一个变量 includes,这里 CFLAGS 是一个递归展开变量,意味着 make 处理该行定义时 $(includes) -O 并不会被展开。

      因此,includes 是否已被定义并不影响在该行被引用,它只需要在之后任何对 CFLAGS 的引用之前完成定义即可。

      如果不使用 "+=" 将值追加到 CFLAGS 后面,我们可能会像下面这样做

      CFLAGS := $(CFLAGS) -pg # enable profiling
      

      这非常接近,但并不是我们确切想要的。

      使用:=CFLAGS 定义为简单展开变量,这意味着 make 需要在完成变量设置之前展开 $(CFLAGS) -pg。如果 includes 尚未定义,我们将得到 -O -pg,之后 includes 的定义也将不起任何作用。
      而使用 += 时我们将 CFLAGS 定义为未展开的 $(includes) -O -pg,这样我们就可以保留对 includes 的引用,当之后的某个节点完成对 includes 的定义时,对 $(CFLAGS) 的引用将仍可以使用 includes 的值。

    3. 注释

    Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用 # 字符。

    4. 常用自动化变量

    符号 含义
    $@ 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么, $@ 就是匹配于目标中模式定义的集合。
    $% 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是 foo.a(bar.o) ,那么, $% 就是 bar.o , $@ 就是 foo.a 。如果目标不是函数库文件(Unix下是 .a ,Windows下是 .lib ),那么,其值为空。
    $< 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 % )定义的,那么 $< 将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
    $? 所有比目标新的依赖目标的集合。以空格分隔。
    $^ 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那么这个变量会去除重复的依赖目标,只保留一份。
    $+ 这个变量很像 $^ ,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
    $* 这个变量表示目标模式中 % 及其之前的部分。如果目标是 dir/a.foo.b ,并且目标的模式是 a.%.b ,那么, $* 的值就是 dir/a.foo 。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么 $* 也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么 $* 就是除了后缀的那一部分。例如:如果目标是 foo.c ,因为 .c 是make所能识别的后缀名,所以, $* 的值就是 foo 。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用 $* ,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么 $* 就是空值。

    (全文完)


    参考资料

    1. 跟我一起写Makefile_Version1.0 https://seisman.github.io/how-to-write-makefile/overview.html

    2. GNU make https://www.gnu.org/software/make/manual/make.html#Rule-Example

    3. Makefile 中几种等号的用法 https://blog.csdn.net/lgxqf/article/details/1859685

    本文作者 :phillee
    发表日期 :2021年7月19日
    本文链接https://www.cnblogs.com/phillee/p/15031245.html
    版权声明 :自由转载-非商用-非衍生-保持署名(创意共享3.0许可协议/CC BY-NC-SA 3.0)。转载请注明出处!
    限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

  • 相关阅读:
    vb combobox 用法问题总结
    VB6.0 String 用法总结
    VB6.0 GetTcpTable 使用详解
    Python 自学笔记(二)第一个程序 Hello World
    Python 自学笔记(一)环境搭建
    VB MSFlexGrid 用法
    wifi 攻破
    python核心编程-第五章-习题
    python核心编程-第五章-个人笔记
    python核心编程-第四章-习题
  • 原文地址:https://www.cnblogs.com/phillee/p/15031245.html
Copyright © 2011-2022 走看看