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所不能识别的,那么 $* 就是空值。 |
(全文完)
参考资料
-
跟我一起写Makefile_Version1.0 https://seisman.github.io/how-to-write-makefile/overview.html
-
GNU make https://www.gnu.org/software/make/manual/make.html#Rule-Example
-
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)。转载请注明出处!
限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。