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
2
3
4
5
6foo = $(bar)
bar = $(ugh)
ugh = Haha
all:
echo $(foo)
执行 make all
时输出的值是 Haha
, 而是用 :=
赋值时就不允许这么赋值,在 :=
右边的值必须只能是字符串或者前面定义的变量,也就是在 :=
右边的值必须要是目前为止已经确定的值。1
2
3
4
5
6
7
8# 示例 1
x := foo
y := $(x) bar
x := later
# 示例 2
y := $(x) bar
x := foo
上面示例 1 中的 y 的值为 foo bar,示例 2 中的值为 bar。
变量值的替换
我们可以替换变量中的共有的部分,其格式是 $(var:a=b)
或是 $(var: %a=%b)
,其意思是,把变量 var
中所有以 a
字串结尾的那些值从 a
替换成 b
; 如下是一个简单的示例1
2
3
4foo := a.o b.o c.o
bar := $(foo:.o=.c) 或 bar := $(foo:%.o=%.c)
# bar 的值是 a.c b.c c.c
把变量值当做变量名
Makefile 中如果变量 a 的值是变量 b 的名称,那么可以把变量 a 的值直接当做变量 b 使用,如下是一些简单的例子1
2
3
4
5
6
7
8
9
10
11# 示例一
x = y
y = z
z = u
a := $($($(x))) # u
# 示例二
x = $(y)
y = z
z = Hello
a := $($(x)) # Hello
局部变量
前面我们所讲的在 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
2foo: $(objects)
$(CC) -o foo $(objects) $(libs_for_gcc)
因此,上面的写法可以写成如下更简洁且容易理解的形式1
2
3
4
5
6
7
8
9
10
11libs_for_gcc = -lgnu
normal_libs =
ifeq ($(CC), gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
foo: $(objects)
$(CC) -o foo $(objects) $(libs)
需要注意的一点是在关键字所在的这一行上,多余的空格是被允许的,但是不能以 Tab 键做为开始,否则就被认为是命令
使用 ifeq
可有若干种形式,如下所示的五种形式都是等价的, ifneq
的使用方法相同1
2
3
4
5ifeq (<arg1>, <arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"
ifdef
和 ifndef
的使用方法也类似,只是其后面只跟着一个变量。
函数
GNU make 内置了一些函数,在 Makefile 中使用函数来处理变量,可以让我们的命令或是规则更为的灵活
函数调用很像变量的使用,也是以 $
来标识的,其语法如下,其中 <function>
就是函数名,<arguments>
为函数的参数,参数间以逗号 ,
分隔,而函数名和参数之间以空格分隔1
2
3$(<function> <arguments>)
或
${<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
2
3names := a b c d
files := $(foreach n,$(names),$(n).o)
上面的例子中,$(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
2reverse = $(2) $(1)
foo = $(call reverse,a,b)
经过上面的表达式得到的 foo 的值是 b a
。当然,可以为 reverse 定义更复杂的操作。
shell 函数
,shell 函数把执行操作系统命令后的输出作为函数返回。因此可以用操作系统命令以及字符串处理命令 awk,sed
等等命令来生成一个变量,如:1
2contents := $(shell cat foo)
files := $(shell echo *.c)