Makefile 语法详解(2)-变量、条件判断与函数
本文内容是之前的文章 Makefile 简介 的补充,详细介绍了 Makefile 中的变量(包括变量的定义、批量替换、局部变量等)、条件判断和函数(内置函数和自定义函数)。
变量
在 Makefile 中的定义的变量,就像是 C/C++ 语言中的宏一样,代表了一个文本字串,在 Makefile 中执行的时候其会自动地展开在所使用的地方。其与C/C++所不同的是,可以在 Makefile 中改变其值。
变量在声明时需要给予初值,而在使用时,需要给在变量名前加上
$
符号,但最好用小括号 ()
或是大括号
{}
把变量给包括起来。如果你要使用真实的 $
字符,那么你需要用 $$
来表示。
变量的赋值
定义 Makefile
中为变量赋值可用四种操作符:=
、:=
、?=
、+=
,
参考 StackOverflow 上的问题 What
is the difference between the GNU Makefile variable assignments =, ?=,
:= and +=?, 这四个符号的主要区别是
=
赋值是 lazy 的,也就是在使用的时候才会递归的获取变量的值(递归指的是可以通过一个变量为另一个变量赋值):=
则是在声明的时候变量的值就确定了?=
表示在变量没有值的时候才给其赋值+=
则是在原来的值上 append 一个其他的值(自动添加空格)
其中 =
赋值是的递归获取值比较难理解,简单来说就是右侧中的变量不一定非要是已定义好的值,也可以使用后面定义的值。如下是一个简单地例子
1 | foo = $(bar) |
执行 make all
时输出的值是 Haha
, 而是用
:=
赋值时就不允许这么赋值,在 :=
右边的值必须只能是字符串或者前面定义的变量,也就是在
:=
右边的值必须要是目前为止已经确定的值。
1 | # 示例 1 |
上面示例1 中的 y 的值为 foo bar,示例2 中的值为 bar。
变量值的替换
我们可以替换变量中的共有的部分,其格式是 $(var:a=b)
或是
$(var: %a=%b)
,其意思是,把变量 var
中所有以
a
字串结尾的那些值从 a
替换成 b
;
如下是一个简单的示例
1 | foo := a.o b.o c.o |
把变量值当做变量名
Makefile 中如果变量 a 的值是变量 b 的名称,那么可以把变量 a 的值直接当做变量 b 使用,如下是一些简单的例子
1 | # 示例一 |
局部变量
前面我们所讲的在 Makefile 中定义的变量都是全局变量, 但是也可以为某个目标设置局部变量,这种变量被称为 Target-specific Variable,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值,因为局部变量的名称可与全局变量的名称相同。
其一般语法如下,首先在 target 中定义局部变量,则在 target
及其依赖的目标中使用该变量即可 1
2target : local_variable assignment
target : use variable
如下是个简单的例子 1
2
3
4
5
6
7
8
9
10
11
12prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o
prog.o : prog.c
$(CC) $(CFLAGS) prog.c
foo.o : foo.c
$(CC) $(CFLAGS) foo.c
bar.o : bar.c
$(CC) $(CFLAGS) bar.c
除了 Target-specific Variable,还有 Pattern-specific
Variable,即不是在某个特定的 target 中定义局部变量,而是在某个特定的
pattern 中定义局部变量,其语法与上面的类似,如下所示是定义了所有以
.o
结尾的目标中的一个局部变量
1 | %.o : CFLAGS = -O |
条件判断
使用条件判断,可以让 make
根据运行时的不同情况选择不同的执行分支,主要有以下几个关键字:
ifeq
、ifneq
、ifdef
、ifndef
、else
和 endif
;
根据名称其实也能基本能猜出各个关键字的作用了, ifeq
和
ifneq
是一对关键字,表示其后面跟随的两个参数是够相等,ifdef
和
ifndef
是一对关键字,表示其后跟随的变量是否已经被定义过。
如下是一个简单的例子,表示目标 foo 可以根据变量 $(CC)
值来选取不同的函数库来编译 1
2
3
4
5
6
7
8
9libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
当 $(CC)
是 gcc 时,目标 foo 的规则是
1 | foo: $(objects) |
因此,上面的写法可以写成如下更简洁且容易理解的形式
1 | libs_for_gcc = -lgnu |
需要注意的一点是在关键字所在的这一行上,多余的空格是被允许的,但是不能以 Tab 键做为开始,否则就被认为是命令
使用 ifeq
可有若干种形式,如下所示的五种形式都是等价的,
ifneq
的使用方法相同
1 | ifeq (<arg1>, <arg2>) |
ifdef
和 ifndef
的使用方法也类似,只是其后面只跟着一个变量。
函数
GNU make 内置了一些函数,在 Makefile 中使用函数来处理变量,可以让我们的命令或是规则更为的灵活
函数调用很像变量的使用,也是以 $
来标识的,其语法如下,其中 <function>
就是函数名,<arguments>
为函数的参数,参数间以逗号
,
分隔,而函数名和参数之间以空格分隔
1 | $(<function> <arguments>) |
以下是 GNU make 内置的一些函数
字符串处理函数
subst 用法:
$(subst <from>,<to>,<text>)
功能:把字串<text>
中的<from>
字符串替换成<to>
。 返回:函数返回被替换过后的字符串。patsubst 用法:
$(patsubst <pattern>,<replacement>,<text>)
功能:查找<text>
中的单词是否符合模式<pattern>
,如果匹配的话,则以<replacement>
替换。这里,<pattern>
可以包括通配符%
,表示任意长度的字串。如果<replacement>
中也包含%
,那么,<replacement>
中的这个%
将是<pattern>
中的那个%
所代表的字串。(可以用\
来转义) 返回:函数返回被替换过后的字符串 示例:$(patsubst %.c,%.o,x.c.c bar.c)
返回的结果是x.c.o bar.o
findstring 用法:
$(findstring <find>,<string>)
功能:在字串<string>
中查找<find>
字串。 返回:如果找到,那么返回<find>
,否则返回空字符串。sort 用法:
$(sort <list>)
功能:给字符串<list>
中的单词排序(空格分隔)。 返回:返回排序后的字符串。 示例:$(sort foo bar lose)
返回bar foo lose
。word 用法:
$(word <n>,<text>)
功能:取字符串<text>
中第<n>
个单词。(从1开始) 返回:返回字符串<text>
中第<n>
个单词 示例:$(word 2, foo bar baz)
返回值是bar
foreach 函数
foreach 函数是用作循环的,其用法如下
$(foreach <var>,<list>,<text>)
该函数的意思是,把参数 <list>
中的单词逐一取出放到参数所指定的变量 <var>
中,然后再执行 < text>
所包含的表达式。每一次
<text>
会返回一个字符串,循环过程中,<text>
的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>
所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。
如下是一个简单的例子
1 | names := a b c d |
上面的例子中,$(name)
中的单词会被挨个取出,并存到变量 n
中,$(n).o
每次根据 $(n)
计算出一个值,这些值以空格分隔,最后作为 foreach
函数的返回值,所以,$(files)
的值是
a.o b.o c.o d.o
需要注意的是,foreach 中的参数是一个临时的局部变量,foreach函数执行完后,参数的变量将不在作用,其作用域只在foreach函数当中。
call函数
call 函数可以用来创建自定义函数。其语法是:
$(call <expression>,<parm1>,<parm2>,<parm3>,...)
当 make 执行这个函数时,<expression>
参数中的变量:
$(1),$(2),$(3)
等,会被参数
<parm1>,<parm2>,<parm3>
依次取代。而
<expression>
的返回值就是 call
函数的返回值。例如:
1 | reverse = $(2) $(1) |
经过上面的表达式得到的 foo 的值是 b a
。当然,可以为
reverse 定义更复杂的操作。
shell函数
,shell函数把执行操作系统命令后的输出作为函数返回。因此可以用操作系统命令以及字符串处理命令
awk,sed
等等命令来生成一个变量,如:
1 | contents := $(shell cat foo) |