Skip to content

LV010-变量简介

一、变量简介

1. Makefile 中的变量

变量,我们并不陌生, Makefile 又不是编程语言,为什么也需要变量呢?我们什么需要创建变量呢?创建变量的目的其实就是用来代替一个文本字符串:系列文件的名字、传递给编译器的参数、需要运行的程序、需要查找源代码的目录、我们需要输出信息的目录等。

在 Makefile 中的定义的变量,就像是 C/C++ 语言中的宏一样,他代表了一个文本字串,在 Makefile 中执行的时候其会自动原模原样地展开在所使用的地方。其与 C/C++ 所不同的是,我们可以在 Makefile 中改变其值。在 Makefile 中,变量可以使用在“目标”,“依赖目标”,“命令”或是 Makefile 的其它部分中。

2. 变量的命名规则

变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有 :#= 或是空字符(空格、回车等)。

变量是大小写敏感的,“foo”、“Foo”和“FOO”是三个不同的变量名。传统的 Makefile 的变量名是全大写的命名方式,但一般推荐使用大小写搭配的变量名,如:MakeFlags。这样可以避免和系统的变量冲突,而发生意外的事情。

有一些变量是很奇怪字串,如 <@ 等,这些是自动化变量,在后面会学习。

3. 可以用在哪里?

变量可以使用在许多地方,如规则中的“目标”、“依赖”、“命令”以及新的变量中。

4. 变量的定义

4.1 定义格式

变量定义的基本格式如下:

shell
variable=value

【说明】

(1)变量的不需要使用数据类型。变量的名称可以由大小写字母、阿拉伯数字和下划线构成。

(2)等号左右的空白符没有明确的要求,因为在执行 make 的时候多余的空白符会被自动的删除。

(3) value 表示值列表,既可以是零项,又可以是一项或者是多项。例如,

shell
VALUE_LIST = one two three

(4)变量定义时是可以换行书写的,只是需要在每行结尾处添加一个 \

4.2 使用实例

Makefile 文件内容如下:

makefile
a = main.o \
	 test.o \
	 test.h

test:
	@echo "a=$(a)"

然后在终端中执行 make test ,可以看到终端会有以下内容输出:

shell
a=main.o test.o test.h

5. 变量的使用

5.1 使用格式

上边定义的变量如何引用呢?引用格式如下:

makefile
$(VALUE_LIST)
# 或者
${VALUE_LIST}

在使用时,需要给在变量名前加上 $ 符号,但最好用小括号 () 或 是大括号 {} 把变量给包括起来。如果要使用真实的 $ 字符,那么需要用 $$ 来表示。

5.2 使用实例

变量会在使用它的地方精确地展开,就像 C/C++ 中的宏一样,例如:

makefile
foo = c
prog.o : prog.$(foo)
	$(foo)$(foo) -$(foo) prog.$(foo)

展开后如下所示:

makefile
prog.o : prog.c
	cc -c prog.c

5. 变量的类型

5.1 有哪些类型变量?

Makefile 中的变量可以分为多种类型,下面通过表格来总结说明:

变量类型 定义/来源 说明 示例
自定义变量 Makefile 中手动定义 用户自己在 Makefile 中定义的变量,使用 =、:=、+=、?= 等赋值符号。这是最基础的变量类型 CC = gcc
命令行变量 make 命令传入 通过 make 命令行传入的变量,如 make VAR = value。它的优先级最高,会覆盖 Makefile 中的同名变量。 make CC = g++
系统环境变量 操作系统环境(shell) 执行 make 命令时,从父进程 Shell 继承的环境变量,make 运行时会自动加载到 Makefile 中。默认情况下,其优先级低于 Makefile 中的定义。 $PATH, $ HOME
预定义变量
(隐含变量)
make 内置 Make 工具为了支持其隐含规则而预先定义的变量,通常为编译器、链接器等工具链相关。这是“自定义变量”的一个特例(由 Make 预先定义)。 CC, CFLAGS, AR
自动化变量 make 自动生成 由 Makefile 自动产生,在规则的命令部分,其值依赖于当前规则的目标和依赖文件。它们只能被引用,不能被赋值。 $@, $ <, $^等
目标变量 Makefile 中定义 为特定目标设置的局部变量,其作用域仅限于该目标及其依赖链中的规则。 target: VAR = value
模式变量 Makefile 中定义 通过模式规则定义的变量,作用于匹配特定模式的文件 %.o: CFLAGS =-Wall
Shell 赋值变量 make 命令中执行 shell 命令 通过$(shell cmd)或`cmd`执行 shell 命令,并将其标准输出作为变量的值。注意:这与在 Shell 环境中设置的变量(系统环境变量)是两回事。 files := $(shell ls *.c)

5.2 变量优先级总结

当不同类型的变量同名时,优先级从高到低排列如下:

优先级 变量类型 说明
1 命令行变量 make 命令传入的参数,优先级最高
2 Makefile 中定义的变量 包括自定义变量、目标变量、模式变量等
3 系统环境变量 操作系统环境变量,make -e 时优先级提升
4 预定义变量 make 内置的默认变量

5.3 变量作用范围

变量类型 作用范围
自定义变量(全局) 整个 Makefile 文件
目标变量 仅在指定目标及其连带规则中有效
模式变量 仅在匹配模式的目标规则中有效
自动化变量 仅在规则命令中有效

二、变量的赋值

上边我们定义变量的时候使用的符号是 = ,但是其实在 Makefile 中,赋值方式有四种:

符号 说明
:= 简单赋值,编程语言中常规理解的赋值方式,只对当前语句的变量有效。
= 递归赋值,赋值语句可能影响多个变量,所有目标变量相关的其他变量都受影响。
?= 条件赋值,如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效。
+= 追加赋值,原变量用空格隔开的方式追加一个新值。

1. 简单赋值 :=

我们在 Makefile 文件中添加以下内容:

makefile
a:=sumu1
b:=${a} 123456@qq.com
a:=sumu2
test:
	@echo "a=${a}"
	@echo "b=${b}"

会看到输出的结果如下:

shell
sumu@virtual-machine:~/hk/alpha$ make
a=sumu2
b=sumu1 123456@qq.com

用这种方式定义的变量,会在变量的定义点,按照被引用的变量的当前值进行展开 。这种方式定义的变量不能使用后面的变量,只能使用前面已定义好了的变量。

2. 递归赋值 =

我们在 Makefile 文件中添加以下内容:

makefile
a=sumu1
b=${a} 123456@qq.com
a=sumu2
test:
	@echo "a=${a}"
	@echo "b=${b}"

会看到如下打印:

shell
sumu@virtual-machine:~/hk/alpha$ make
a=sumu2
b=sumu2 123456@qq.com

这意味着,即便 a 是在 b 后边进行了修改,但是变量 b 依然引用了 a 修改之后的值。这种赋值方式优点是可以向后引用变量,缺点是不能对该变量进行任何扩展,例如:

shell
a=sumu1
b=${a} 123456@qq.com
a=$(a)
test:
	@echo "a=${a}"
	@echo "b=${b}"

这样会造成一种死循环,应该是会直接报错的:

shell
Makefile:3: *** Recursive variable 'a' references itself (eventually)。 停止。

3. 条件赋值 ?=

我们在 Makefile 文件中添加以下内容:

shell
a:=sumu1
b:=${a} 123456@qq.com
a?=sumu2
test:
	@echo "a=${a}"
	@echo "b=${b}"

得到的打印信息如下:

shell
sumu@virtual-machine:~/hk/alpha$ make
a=sumu1
b=sumu1 123456@qq.com

若是将第一行的 a:= sumu1 删除,则输出结果是这样的:

shell
sumu@virtual-machine:~/hk/alpha$ make
a=sumu2
b= 123456@qq.com

这种赋值方式其实就等价于:

shell
ifeq ($(origin a), undefined)
      a = sumu2
endif

【说明】什么是 ifeq ?其实是 Makefile 中的条件判断,后边会详细解释,这里简单了解下就可以了。

4. 追加赋值

我们在 Makefile 文件中添加以下内容:

makefile
a:=sumu1
b:=${a} 123456@qq.com
a+=${b}
test:
	@echo "a=${a}"
	@echo "b=${b}"

会有如下打印:

shell
sumu@virtual-machine:~/hk/alpha$ make
a=sumu1 sumu1 123456@qq.com
b=sumu1 123456@qq.com

三、override 指示符

如果有变量是通常 make 的命令行参数设置的,那么 Makefile 中对这个变量的赋值会被忽略。如果想在 Makefile 中设置这类参数的值,那么,我们可以使用“override”指示符。其语法是:

makefile
override <variable>; = <value>;
override <variable>; := <value>;

当然,还可以追加

makefile
override <variable>; += <more text>;

对于多行的变量定义,我们用 define 指示符,在 define 指示符前,也同样可以使用 override 指示符, 如:

makefile
override define foo
bar
endef

四、 多行变量

还有一种设置变量值的方法是使用 define 关键字。使用 define 关键字设置变量的值可以有换行,这 有利于定义一系列的命令(前面我们讲过“命令包”的技术就是利用这个关键字)。

define 指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以 endef 关键字结束。其工 作方式和“=”操作符一样。变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以 [Tab] 键开头,所以如果你用 define 定义的命令变量中没有以 Tab 键开头,那么 make 就不会把其认为是命令。

下面的这个示例展示了 define 的用法:

makefile
define two-lines
echo foo
echo $(bar)
endef

五、不同类型的变量

这一部分实际说明的是 makefile 文件之外的那一些变量:

1. 预定义变量

内嵌隐含规则(后面会学习)的命令中, 所使用的变量都是预定义的变量。我们将这些变量称为“隐含变量”。这些变量允许对它进行修改:在 Makefile 中、通过命令行参数或者设置系统环境变量的方式来对它进行重定义。

1.1 常见预定义变量

在 Makefile 中也存在着一些预定义变量:

AR 库文件维护程序的名称,默认值为 ar。AS 汇编程序的名称,默认值为 as。
CC C 编译器的名称,默认值为 cc。CPP C 预编译器的名称,默认值为$(CC) –E。
CXX C++编译器的名称,默认值为 g++。
FC FORTRAN 编译器的名称,默认值为 f77
RM 文件删除程序的名称,默认值为 rm -f
ARFLAGS 库文件维护程序的选项,无默认值。
ASFLAGS 汇编程序的选项,无默认值。
CFLAGS C 编译器的选项,无默认值。
CPPFLAGS C 预编译的选项,无默认值。
CXXFLAGS C++编译器的选项,无默认值。
FFLAGS FORTRAN 编译器的选项,无默认值。

1.2 使用实例

我们在 Makefile 文件中添加以下内容:

makefile
test:
	@echo "CC = $(CC)"
	@echo "AR = $(AR)"
	@echo "RM = $(RM)"
	@echo "CXX= $(CXX)"

将会有以下打印信息:

shell
sumu@virtual-machine:~/hk/alpha$ make
CC = cc
AR = ar
RM = rm -f
CXX= g++

2. 目标变量

前面我们所讲的在 Makefile 中定义的变量都是“全局变量”,在整个文件,我们都可以访问这些变 量。当然,“自动化变量”除外,如 $< 等这种类量的自动化变量就属于“规则型变量”,这种变量的值依 赖于规则的目标和依赖目标的定义。

当然,我也同样可以 为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以 和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。

2.1 语法格式

makefile
<target ...> : <variable-assignment>

<variable-assignment> 可以是各种赋值表达式,如 = 、 := 、 += 或是 ?= 。目标变量 可以和全局变量同名,因为它的 作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效,而不会影响规则链以外的全局变量的值。

2.2 使用实例

Makefile 文件内容如下:

makefile
a := a.o b.o c.o
test : a := main.o test.o
test :
	@echo "a=$(a)"
a:
	@echo "a=$(a)"

然后我们在终端中执行 make test ,会发现终端输出信息如下:

shell
a=main.o test.o

接着我们在终端中再执行 make a ,会发现终端中输出的信息如下:

shell
a=a.o b.o c.o

这也就说明了,目标变量仅仅在它自己的规则中有效,并且 make 会优先使用规则内定义的局部变量。

3. 系统环境变量

make 运行时的系统环境变量可以在 make 开始运行时被载入到 Makefile 文件中,但是如果 Makefile 中已定义了这个变量,或是这个变量由 make 命令行带入,那么系统的环境变量的值将被覆盖。(如果 make 指定了“-e”参数,那么,系统环境变量将覆盖 Makefile 中定义的变量)

因此,如果我们在环境变量中设置了 CFLAGS 环境变量,那么我们就可以在所有的 Makefile 中使用 这个变量了。这对于我们使用统一的编译参数有比较大的好处。如果 Makefile 中定义了 CFLAGS,那么 则会使用 Makefile 中的这个变量,如果没有定义则使用系统环境变量的值,一个共性和个性的统一,很 像“全局变量”和“局部变量”的特性。

当 make 嵌套调用时(后面会学习),上层 Makefile 中定义的变量会以系统环境变 量的方式传递到下层的 Makefile 中。当然,默认情况下,只有通过命令行设置的变量会被传递。而定义 在文件中的变量,如果要向下层 Makefile 传递,则需要使用 exprot 关键字来声明。

当然,并不推荐把许多的变量都定义在系统环境中,这样,在我们执行不用的 Makefile 时,拥有的是同一套系统变量,这可能会带来更多的麻烦。

4. 模式变量

在 GNU 的 make 中,还支持模式变量(Pattern-specific Variable),通过上面的目标变量中,我们 知道,变量可以定义在某个目标上。模式变量的好处就是,我们可以给定一种“模式”,可以把变量定义 在符合这种模式的所有目标上。

我们知道,make 的“模式”一般是至少含有一个 % 的,所以,我们可以以如下方式给所有以 .o 结 尾的目标定义目标变量:

makefile
%.o : CFLAGS = -O

同样,模式变量的语法和“目标变量”一样:

makefile
<pattern ...>; : <variable-assignment>;
<pattern ...>; : override <variable-assignment>;

override 同样是针对于系统环境传入的变量,或是 make 命令行指定的变量。

5. 自动化变量

关于自动化变量可以理解为由 Makefile 自动产生的变量。在模式规则中,规则的目标和依赖的文件名代表了一类的文件。规则的命令是对所有这一类文件的描述。我们在 Makefile 中描述规则时,依赖文件和目标文件是变动的,显然在命令中不能出现具体的文件名称,否则模式规则将失去意义。

那么模式规则命令中该如何表示文件呢?就需要使用“自动化变量”,自动化变量的取值根据执行的规则来决定,取决于执行规则的目标文件和依赖文件。

5.1 常用自动化变量

自动化变量可以理解为由 Makefile 自动产生的变量。

$* 不包含扩展名的目标文件名称(当文件名中存在目录时,也会包含目录部分)。例如,
(1)main.o: main.c test.h 中 $* 就代表 main 。
(2)这个变量还表示目标模式中 % 及其之前的部分。如果目标是 dir/a.foo.b ,并且目标的模式是 a.%.b ,那么,$* 的值就是 dir/a.foo 。

【说明】在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的部分(当文件名中存在目录时,“茎”也包含目录部分)。
$@ 表示规则的目标文件完整名称名。如果目标是一个文档文件(Linux 中,一般成 .a 文件为文档文件,也成为静态的库文件),那么它代表这个文档的文件名。在多目标模式规则中,它代表的是触发规则被执行的文件名。
$% 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是 "foo.a(bar.o)",那么,"$%"就是" bar.o ","$@" 就是 " foo.a "。如果目标不是函数库文件(Unix 下是 [.a],Windows 下是 [.lib]),那么,其值为空。
$< 规则的第一个依赖的文件名。如果是一个目标文件使用隐含的规则来重建,则它代表由隐含规则加入的第一个依赖文件。如果依赖目标是以模式(即 % )定义的,那么 $< 将是符合模式的一系列的文件集。注意,其是一个一个取出来执行的
$? 所有比目标文件更新的依赖文件列表,空格分隔。如果目标文件是静态库文件,代表的是库文件(.o 文件)。
$^ 代表的是所有依赖文件列表,使用空格分隔。如果目标是静态库文件,它所代表的只能是所有的库成员(.o 文件)名。一个可重复的文件出现在目标的依赖中,变量“$^”只记录它的第一次引用的情况。就是说变量“$^”会去掉重复的依赖文件(自动去重)。
$+ 所有的依赖文件,以空格分开。类似“$^”,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。

(1)在上述所列出来的自动量变量中。四个变量($@$<$%$* )在扩展时只会有一个文件,而另三个的值是一个文件列表。

(2)茎的概念在后边静态模式的笔记中会有说明。

(3)对于 $< ,为了避免产生不必要的麻烦,我们最好给 $ 后面的那个特定字符都加上圆括号,比如,$(<) 就要比 $< 要好一些。。

5.2 自动变量中的D与F

GNU make 中在这些变量中加入字符 "D" 或者 "F" 就形成了一系列变种的自动化变量,这些自动化变量可以对文件的名称进行操作。这是 GNU make 中老版本的特性,在新版本中,我们使用函数 dir 或 notdir 就可以做到了。D 的含义就是 Directory,就是目录,F 的含义就是 File,就是文件。

变量名功能
$(@D)表示 $@ 的目录部分(不以斜杠作为结尾),如果 $@ 值是 dir/foo.o ,那么 $(@D) 就是 dir ,而如果 $@ 中没有包含斜杠的话,其值就是 . (当前目录)。
$(@F)表示 $@ 的文件部分,如果 $@ 值是 dir/foo.o ,那么 $(@F) 就是 foo.o ,$(@F) 相当于函数$(notdir $@) 。
$(*D), $(*F)和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子,$(*D) 返回 dir ,而 $(*F) 返回 foo
$(%D), $(%F)分别表示了函数包文件成员的目录部分和文件部分。这对于形同 archive(member) 形式的目标中的 member 中包含了不同的目录很有用。
$(<D), $(<F)分别表示依赖文件的目录部分和文件部分。
$(^D), $(^F)分别表示所有依赖文件的目录部分和文件部分。(无相同的)
$(+D), $(+F)分别表示所有依赖文件的目录部分和文件部分。(可以有相同的)
$(?D), $(?F)分别表示被更新的依赖文件的目录部分和文件部分。

5.3 使用实例

我们创建一个Makefile文件,需要注意一点,在 Makefile 中,@echo " $$(@D) = $(@D) ← 目标目录"这句会报错,原因分析如下:

(1)$$ 会被 Make 转换为单个 $ 传递给 Shell。

(2)当我们使用双引号 " 时,Shell 会尝试展开 $(...)

(3)在 Shell 中,$(...) 意味着“运行括号里的命令”。当Shell 收到了 $(@D),它试图运行名为 @D 的命令,但系统中没有这个命令,所以报错 /bin/sh: @D : 未找到命令

makefile
.PHONY: all prepare clean demo check_new

# 默认目标
all: demo

# ============================================
# 1. 准备演示文件 (make prepare)
# ============================================
prepare:
	@echo "=== 创建演示文件 ==="
	@mkdir -p demo_dir
	@echo "源文件 1" > demo_dir/source1.c
	@sleep 1
	@echo "源文件 2" > demo_dir/source2.c
	@echo "头文件" > demo_dir/header.h
	@echo "=== 文件创建完成 ==="

# ============================================
# 2. 主演示目标 (make demo)
# ============================================
# 注意:| demo_dir 是顺序依赖,用于演示 $|
demo: demo_dir/source1.c demo_dir/source2.c demo_dir/source2.c demo_dir/header.h | demo_dir
	@echo ""
	@echo "============================================"
	@echo "         7 个自动变量及其 D/F 参数演示        "
	@echo "============================================"
	@echo ""
	@echo "【1. 目标文件】"
	@echo '$$@      = ' $@
	@echo '$$(@D)   = ' $(@D)
	@echo '$$(@F)   = ' $(@F)
	@echo ""
	@echo "【2. 第一个依赖】"
	@echo '$$<      = ' $<
	@echo '$$(<D)   = ' $(<D)
	@echo '$$(<F)   = ' $(<F)
	@echo ""
	@echo "【3. 所有依赖 (去重)】"
	@echo '$$^      = ' $^
	@echo '$$(^D)   = ' $(^D)
	@echo '$$(^F)   = ' $(^F)
	@echo ""
	@echo "【4. 所有依赖 (不去重)】"
	@echo '$$+      = ' $+
	@echo '$$ (+D)   = ' $(+D)
	@echo '$$ (+F)   = ' $(+F)
	@echo ""
	@echo "【5. 顺序依赖 (Order-only)】"
	@echo '$$|      = ' $|
	@echo ""
	@echo "【6. 比目标新的依赖】"
	@echo '$$?      = ' $?
	@echo ""
	@echo "【7. 匹配 stem (模式规则)】"
	@echo '$$*      = ' $*
	@echo "  (注:普通规则中为空,请看下方模式规则演示)"
	@echo ""
	@echo "============================================"
	@touch demo

# ============================================
# 3. 模式规则演示 (演示 $* 的效果)
#    运行:make demo_dir/source1.o
# ============================================
demo_dir/%.o: demo_dir/%.c
	@echo ""
	@echo "【模式规则中的 $$* 演示】"
	@echo '  目标:$@'
	@echo '  依赖:$<'
	@echo '$$*      = ' $*
	@echo '$$ (*D)   = ' $( *D)
	@echo '$$ (*F)   = ' $( *F)
	@touch $@

# ============================================
# 4. 单独演示 $?
#    运行:make check_new
# ============================================
check_new: demo_dir/source1.c
	@echo ""
	@echo "【$$? 专项演示】"
	@echo '$$?      = ' $?
	@touch check_new

# ============================================
# 5. 清理
# ============================================
clean:
	@echo "=== 清理演示文件 ==="
	@rm -rf demo_dir
	@rm -f demo check_new
	@echo "=== 清理完成 ==="

我们按如下顺序执行命令并观察输出信息

shell
sumu@virtual-machine:~/hk/alpha$ make prepare 
=== 创建演示文件 ===
=== 文件创建完成 ===
sumu@virtual-machine:~/hk/alpha$ make

============================================
         7 个自动变量及其 D/F 参数演示        
============================================

【1. 目标文件】
$@      =  demo
$(@D)   =  .
$(@F)   =  demo

【2. 第一个依赖】
$<      =  demo_dir/source1.c
$(<D)   =  demo_dir
$(<F)   =  source1.c

【3. 所有依赖 (去重)】
$^      =  demo_dir/source1.c demo_dir/source2.c demo_dir/header.h
$(^D)   =  demo_dir demo_dir demo_dir
$(^F)   =  source1.c source2.c header.h

【4. 所有依赖 (不去重)】
$+      =  demo_dir/source1.c demo_dir/source2.c demo_dir/source2.c demo_dir/header.h
$ (+D)   =  demo_dir demo_dir demo_dir demo_dir
$ (+F)   =  source1.c source2.c source2.c header.h

【5. 顺序依赖 (Order-only)】
$|      =  demo_dir

【6. 比目标新的依赖】
$?      =  demo_dir/source1.c demo_dir/source2.c demo_dir/header.h

【7. 匹配 stem (模式规则)】
$*      = 
  (注:普通规则中为空,请看下方模式规则演示)

============================================
sumu@virtual-machine:~/hk/alpha$ make demo_dir/source1.o

【模式规则中的  演示】
  目标:demo_dir/source1.o
  依赖:demo_dir/source1.c
$*      =  source1
$ (*D)   = 
$ (*F)   = 
sumu@virtual-machine:~/hk/alpha$ make check_new

【0 专项演示】
$?      =  demo_dir/source1.c

六、变量高级用法

1. 变量值的替换

1.1 基本格式

我们可以在引用的时候直接替换变量的值,可以直接替换变量中的共有的部分,一般格式如下:

makefile
# 先定义一个变量,并简单赋值
var1 := value_list

# 开始替换变量值
var2 := $(var1:<string1>=<string2>)
# 或者
var2 := ${var1:<string1>=<string2>}

上边的含义就是,将变量 var1 中的所有以 string1 字符串结尾的变量值替换成以 string2 字符串结尾。

注意

(1)这里是 结尾字符串的替换,其他位置的好像是不可以进行替换的。

(2)也可以使用模式规则进行替换,也就是通过 % 来匹配除需要替换的部分以外的字符串。

(3) var1:< string1 >=< string2 > 这一部分的 : 和 = 两端最好不要有空格,否则可能会出现问题。

1.2 使用实例

Makefile 文件内容如下:

makefile
var1 := main.o test.o
var2 := $(var1:.o=.c)
var3 := $(var1:%.o=%.c)
test:
	@echo "var1=$(var1)"
	@echo "var2=$(var2)"
	@echo "var3=$(var3)"

然后在终端运行 make test ,输出结果如下:

shell
var1=main.o test.o
var2=main.c test.c
var3=main.c test.c

可以发现所有的 .o 都被替换为 .c 。

2. 变量嵌套

2.1 语法格式

变量的嵌套引用的具体含义是,我们可以在一个变量的赋值中引用其他的变量,并且引用变量的数量和和次数是不限制的。也就是说可以把变量的值再当成变量。例如,

makefile
x = y
y = z
a := $($(x))
test:
	@echo "x=$(x)"
	@echo "y=$(y)"
	@echo "a=$(a)"

然后在终端执行 make test ,会看到如下输出:

shell
x=y
y=z
a=z

其实, $(x) 的值就是 y ,而外边还有一个 $ ,这样就会变成 $(y) 而 $(y)= z ,所以最后就是 a = $($(x))=$(y)= z 了。

说明】遇到这种变量嵌套的情况吗,我们就从最里层的 $ 开始向外一层一层进行分析即可。

2.2 使用实例

2.2.1 更多的层次
makefile
# 这里a的值最终是 u
x = y
y = z
z = u
a := $($($(x)))
2.2.2 在变量定义中使用变量
makefile
# 这里的 $($(x)) 被替换成了 $($(y)) ,因为 $(y) 值是“z”,所以,最终结果是:a:=$(z) ,也就是“Hello”。
x = $(y)
y = z
z = Hello
a := $($(x))
2.2.3 再加上函数
makefile
# $($($(z))) 扩展为 $($(y)) ,而其再次被扩展为 $($(subst 1,2,$(x))) 。
# $(x) 的值是“variable1”,subst 函数把“variable1”中的所有“1”字串替换成“2”字串,于是,“variable1”变成“variable2”,再取其值,所以,最终,$(a) 的值就是 $(variable2) 的值——“Hello”。

x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))
2.2.4 多个变量来组成一个变量
makefile
# $a_$b 组成了“first_second”,于是,$(all) 的值就是“Hello”。
first_second = Hello
a = first
b = second
all = $($a_$b)

参考资料:

Makefile 变量详解 - 通辽节度使 - 博客园

跟我一起写Makefile — 跟我一起写Makefile 1.0 文档

五、Makefile自动化变量 - 星星1988 - 博客园