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)。转载请注明出处!
限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

posted @ 2021-07-19 17:31  coffee_tea_or_me  阅读(1796)  评论(0编辑  收藏  举报