make(2/3):变量、宏与函数

变量与宏

变量可以由任意除了:#=的字符组成,在访问变量的时候,如果变量名超过一个字母,要用小括号括起来(花括号{}也可以,但不建议用)。

习惯上用大写字母表示用户在命令行上或环境中自定义的常数,用小写字母表示makefile中出现的变量,单词之间都用下划线隔开。

变量的值由赋值符号右边删除前导空格的所有字符组成。和shell一样,没有定义的变量会被解析为空,举一个例子:

1
MAKE_DEPEND := $(CC) -M

假如$(CC)没有被定义,MAKE_DEPENDd的值最终为<space>-M,这里的空格并不会被删除。

赋值类型

类型 解释
:= 简单扩展,扩展后的字面值被存入变量
= 经递归扩展,储存的实际是待扩展的字符串,等到用的时候再执行扩展
?= 条件赋值,只在变量值不存在的时候才执行赋值
+= 附加运算,将文本附加在变量里。var = $(var) sss的写法是错误的,会无限递归,然而使用:=就可以

使用define指令可以定义宏,宏其实是一种特殊的变量,使用格式为:

1
2
3
4
define MACRO_NAME
command1
command2
endef

我们可以像使用变量一样使用宏。

工作目标的专属变量

可以在定义规则之前定义专属变量,格式如下:

1
2
3
target...: variable [:+?]= value
target...: prerequisite...
rules...

执行该条规则后,variable的值会自动恢复。

变量来自何处

  1. 文件:直接定义在makefile中,或者使用include引入
  2. 命令行:如:make CFLAGS=-g,命令行中的变量会覆盖环境变量和文件变量,当然也可以使用override指令在文件中强行赋值(不建议,可能会逼得用户砸电脑。。)
  3. 环境:环境变量会被自动定义成make变量(优先级很低),同时可以使用export来导出变量到环境中(unexport作用相反)。过多依赖环境变量会降低makefile的可移植性。

条件指令

格式如下:

1
2
3
4
5
if-condition
text if the condition is true
[else
text if the condition is false]
endif

其中if-condition可以是:

1
2
3
4
ifdef variable-name
ifndef variable-name
ifeq test # ifeq (a, a) === ifeq "a" "a"
ifneq test

strip函数可以去除变量首尾空格,建议在比较时使用,如:

1
2
3
ifeq "$(strip $(OPTIONS))" "-d"
COMPILATION_FLAGS += -DDEBUG
endif

include指令

include指令可以引入其他文件。有意思的是:make可以把makefile本身作为一个可能的工作目标。因此,下面的makefile是可以正常工作的:

1
2
3
4
5
include foo.mk
$(warning Finished include)

foo.mk: bar.mk
m4 --defile=FILENAME=$@ bar.mk > $@

bar.mk的代码为:

1
$(warning Reading FILENAME)

当make读进整个makefile后,会试着寻找可用来重建当前所执行的makefile的规则,如果找到了,会处理此规则,然后再检查makefile是否更新,如果更新了,会清除内部状态,并重新读进此makefile。

include默认会在当前目录、--include-dir-I以及make自己被编译的目录(如/usr/local/include)下寻找文件,如果没找到,会报错并结束运行,如果想让其忽略无法加载的文件,可以在include前面加一个-

标准make变量

MAKE_VERSION

make的版本编号

CURDIR

正在执行make进程的当前工作目录,也就是当前shell的pwd值。在make命令中使用-C可以变更目录。

MAKEFILE_LIST

make读进的makefile文件名列表,当前被执行的是该列表的最后一项。

MAKECMDGOALS

返回对于当前所运行的make而言,make命令行上指定的工作目标。不包括命令行选项或对变量的赋值。

.VARIABLES

当前make从各个makefile文件读进的变量名称构成的列表,不含工作目标的专属变量

函数

自定义函数

使用前面介绍的来定义函数!可以使用$n来访问第n个参数。在调用函数时,使用以下格式:

1
$(call macro-name[, param1 ...])

内置函数

使用函数的语法为:

1
$(function-name arg1[, argn])

其中第一个参数的前导空格会被删除,而从第二个开始前导空格会被保留。

字符串函数

$(filter pattern …,text)

将text视为一系列被空格隔开的单纯,与pattern比较厚,返回相符者。

$(filter-out pattern…,text)

与filter相反,选出不符的每个单词

$(findstring string…,text)

返回在text里面搜索到的string;没找到返回空值(无%)

$(subst search-string,replace-string,text)

不具备通配符能力的替换函数

$(patsubst search-pattern,replace-pattern,text)

具备通配符能力的替换函数

$(words text)

返回text中单词数量

$(words, n,text)

返回text中的第n个单词

$(firstword text)

返回text中的第一个单词

$(wordlist start,end,text)

返回text中范围从start(含)到end(含)的单词

两个重要函数

$(sort list)

排序list参数并移除重复的项目

$(shell command)

将command参数扩展后传递给subshell执行

文件名函数

$(wildcard pattern…)

返回符合pattern的文件名,如

1
source := $(wildcard *.c *.h)

还可以用于测试文件是否存在,如

1
dot-example-exists := $(wildcard ~/.emacs)

如果返回空字符串,则说明用户主目录不包含.emacs这个文件。

$(dir list…)

返回list中每个单词的目录部分

$(notdir name…)

返回文件路径的文件名部分

$(suffix name…)

返回参数中每个单词的后缀

$(basename name…)

与suffix相反,返回不含后缀的部分

$(addsuffix suffix,name…)

将后缀添加到每个name后面,同理addprefix可以添加前缀

$(join prefix-list,suffix-list)

重建dir和notdir分解的表

流程控制

$(if condition,then-part,else-part)

make对condition求值时,会对其进行扩展,如果扩展后包含任意字符(即使是空格),求值结果也是真。

$(error text)

输出fatal error message。之后make会以返回值2结束运行

$(foreach variable,list,body)

反复扩展文本,例如:

1
2
3
letters := $(foreach letter,a b c d,$(letter))
show-words:
# letters has $(words $(letters)) words: '$(letters)'

其它函数

$(strip text)

移除前后空格

$(origin variable)

返回变量来源,可能的返回值有:

  • undefined: 未定义
  • default: 来自make的内置数据库
  • environment: 来自环境(没有覆写)
  • file: 来自makefile
  • command line: 来自命令行
  • override: 来自override指令
  • automatic: 自动变量
$(warning text)

类似error,但是不会导致make结束运行。

高级函数

eval函数

将文本直接放入make解析器。用起来十分复杂,有很多神奇用法,真正用到的时候再学吧。

传递参数

使用函数参数传递最符合模块化的标准,有时为了方便,我们也可以使用变量前缀来共享。


系列导航