一个变量名称几乎可以由任何字符组成,包括大部分的标点符号。即使是空格也可以使用,但请不要这样做。事实上,只有:、\#和=等字符不允许使用在变量名称中。
请注意,变量名称是区分大小写的。
建议使用的命名习惯:
当变量用来表示用户在命令行上或环境中所自定义的常数时,习惯上全部以大写来命名,单词之间用下划线隔开。
至于只在makefile中出现的内部变量,则全部用小写来编写其名称,单词之间以下划线符号隔开。
内含用户自定义函数的变量以及宏都会以小写来编写其名称,单词之间以破折号(-)隔开。
1.自动变量
自动变量 | 含义 |
---|---|
$@ | 工作目标的文件名 |
$% | 档案文件成员(archive member)结构中的文件名元素。 |
$< | 第一个必要条件的文件名 |
$? | 时间戳在工作目标(的时间戳)之后的所有必要条件,并以空格隔开这些必要条件 |
$^ | 所有必要条件的文件名,并以空格隔开这些文件名。这份列表已删掉重复的文件名,因为对大多数的应用而言,比如编译、复制等,并不会用到重复的文件名 |
$+ | 如同$^ ,代表所有必要条件的文件名,并以空格隔开这些文件名。不过,$+包含重复的文件名。 |
$* | 工作目标的主文件名。一个文件名称是由两部分组成:主文件名(stem)和扩展名(suffix) |
2. 变量的类型
一般来说,以变量来代表外部程序是一个不错的注意,这让makefile的用户较容易针对他们特有的环境来改写makefile。
变量可用来保存简单的常数,也可用来存放自定义的命令序列。例如下面的设定可用来汇报尚未使用的磁盘空间:
DF = df
AWK =awk
free-space := $(DF) . | $(AWK) 'NR == 2 {print $$4}'
make的变量有两种类型:经简单扩展的变量(simply expanded variable)以及经递归扩展的变量(recursively expanded variable)。
经简单扩展的变量
使用:=
赋值运算符来定义一个经简单扩展的变量,如MAKE_DEPEND := $(CC) -M
一旦make从makefile中读进该变量的定义语句,赋值运算符的右边部分会立刻被扩展。赋值运算符的右边部分只要出现make变量的引用就会被扩展,而扩展后所产生的的文本则会被存储成该变量的值。上面的变量被扩展之后就变成下面这样
gcc -M
然而,如果上面的CC变量尚未定义,则变量被扩展后将变成
<space>-M
未被定义的变量将被扩展为空值
经递归扩展的变量
直接使用=
来定义,如MAKE_DEPEND = $(CC) -M ... # 稍后 CC = gcc
make在读取变量时只会将=号右边的值赋值给变量,不会做任何的展开的动作,展开的动作会被延迟到该变量被使用的时候才进行。
这样,当MAKE_DEPEND被使用的时候,即使CC并未定义,MAKE_DEPEND在脚本的值也会被扩展成gcc -M
2.1 其他的赋值类型
- 条件赋值
?=
此运算只会在变量的值尚不存在的状况下进行变量要求赋值的动作。 - 附加运算符
+=
此运算符会将文本附加到变量里。当递归变量被使用时,赋值运算符右边部分的值会在"不影响变量中原有值的状况下"被附加到变量里。
3. 宏
变量适合用来存储单行形式的值,可是对于多行形式的值,例如命令脚本,如果我们想在不同的地方执行它,该怎么办?
在GNU make中,我们可以通过define指令以创建“封装命令序列”(canned sequence)的方式来解决此问题,在这里简称为宏。
例如,
define build_target_with_subfeature_country
@echo build_target_with_subfeature_country......
$(call build_target,$(filter $(TARGETS),$(subst _, ,$(1))),$(filter $(SUBFEATURE),$(subst _, ,$(1))),$(filter $(COUNTRY),$(subst _, ,$(1))))
endef
define指令后面跟着变量名称以及一个换行符号。变量的主体包含了所有命令序列(每一行命令都必须前置一个Tab符号)直到ended关键字出现为止,endef关键字必须自成一行。
在echo命令前置了一个@字符。当执行命令脚本时,前置@字符的命令不会被make输出。因此,当我们运行echo命令本身,只会输出该命令的输出。如果在宏内部使用@前缀,这个前缀字符只会影响使用到它的命令行。然而,如果将这个前缀字符用在宏引用上,则整个宏主体都会被隐藏起来。
4. 何时扩展变量
当make运行时。它会以两个阶段来完成它的工作。第一个阶段,make会读进makefile以及被引入的任何其他makefile。这个时候,其中所定义的变量和规则会被加载进make的内部数据库,而且依存图也会被建立起来。
第二个阶段,make会分析依存图并且判断需要更新的工作目标,然后执行脚本以完成所需要的更新动作。
当make在处理递归变量或define指令的时候,会将变量里的每一行或宏的主体存储起来,包括换行符号,但不会予以扩展。宏定义里的最后一个换行符号并不会被存储成宏的一部分;否则,宏被扩展时make会读进一个额外的换行符号。
当宏被扩展时,make会立即扫描被扩展的文本中是否存在宏或变量的引用,如果存在就予以扩展,如此递归进行下去。如果宏是在命令脚本的语境中被扩展的,则宏主体的每一行都会被插入一个前导的跳格符(Tab)。
下面是用来处理“makefile中的元素何时被扩展”的准则:
- 对于变量赋值,make会在第一阶段读进该行时,立即扩展赋值运算符左边的部分。
=
和?=
的右边部分会被延后到它们被使用的时候扩展,并且在第二阶段进行。:=
的右边部分会被立即扩展- 如果
+=
的左边部分原本就被定义成一个简单变量,+=
的右边部分就会被立即扩展,否则,它的求值动作会被延后。 - 对于宏定义(使用define指令),宏的变量名称会被立即扩展,宏的主体会被延后到被使用的时候扩展
- 对于规则,工作目标和必要条件总是会被立即扩展,然而命令总是会被延后扩展
定义 | 何时扩展a | 何时扩展b |
---|---|---|
a = b | 立即 | 延后 |
a ?= b | 立即 | 延后 |
a := b | 立即 | 立即 |
a += b | 立即 | 延后或立即 |
define a b ... endef | 立即 | 延后 |
一个通则是总是先定义变量和宏,然后再使用它们。尤其是,在工作目标或必要条件中使用变量时,就需要在使用变量之前予以定义。
5. 工作目标与模式的专属变量
在makefile运行期间,变量通常只有一个值。对需要经过两个处理阶段的makefile来说是这样没错。第一个阶段,make读进makefile之后,会对变量进行赋值和扩展的动作并建立依存图。第二个阶段,make会分析以及遍历依存图。所以,等到make指令命令脚本的时候,所有的变量都已经处理完毕了。但是如果我们想为特定的规则或模式重新定义变量,该怎么办?
例如,现在我们想要编译一个需要额外命令行选项-DUSE_NEW_MALLOC=1
的文件,但是其他的编译项目并不需要这个额外的命令行选项:
gui.o: gui.h
$(COMPILE.c) -DUSE_NEW_MALLOC=1 $(OUTPUT_OPTION) $<
当规则有改动,或者有许多这样的文件需要处理,就会做很多重复的动作。
为了解决此类问题,make提供了工作目标的专属变量。这些变量的定义会附加在工作目标之上,且只有该工作目标以及相应的任何必要条件被处理的时候,它们才会起作用。通过使用专属变量,可以把前面的例子改写为
gui.o: CPPFLAGS += -DUSE_NEW_MALLOC=1
gui.o: gui.h
$(COMPILE.c) $(OUTPUT_OPTION) $<
工作目标的专属变量的语法如下所示:
target...: variable = value
target...: variable := value
target...: variable += value
target...: variable ?= value
这类变量的赋值动作会延后到开始处理工作目标的时候进行。所以赋值运算符右边部分的值,可由另一个工作目标的专属变量来设定。同样地,此变量只有在必要条件的处理期间,才会发生作用。
6. 变量来自何处
- 文件
在文件中创建,或者通过include指令引入 命令行
直接在make命令行上定义或重新定义变量:$ make CFLAGS=-g CPPFLAGS='-DBSD -DDEBUG'
在命令行上,每个变量赋值运算符的右边部分必须是一个单独的shell参数。如果变量的值(或变量本身)包含空格,则必须为参数加上括号或是规避空格。
命令行上变量的赋值结果将会覆盖掉环境变量以及makefile文件中的赋值结果。还可以使用:=
或=
赋值运算符将命令行参数设定成简单或递归变量。此外,如果使用override指令,你还可以要求make采用makefile的赋值结果,而不要采用命令行的赋值结果。环境
make启动时,所有来自环境的变量都会被自动定义成make的变量。
这些环境变量的优先级很低,所以makefile文件或命令行参数的赋值结果将会覆盖掉环境变量的值。
不过,可以使用--environment-overrides
或-e
命令行选项,让环境变量覆盖掉相应的makefile变量。
当make被递归调用时,有若干来自上层的make变量会通过环境传递给下层的make。默认情况下,只有原先就来自环境的变量会被导出到下层的环境之中。不过,你只要使用export指令就可以让任何变量被导出到环境之中:
要求将所有变量全部导出,可以这么做:export
请注意,即使这些变量的名称包含了无效的shell变量字符,make也会进行导出的动作。
使用unexport可以避免环境变量被导出到子进程
条件赋值运算符与环境变量的交互良好。假如你已经在makefile中定义了输出目录,但是你希望用户能轻易地改写,使用条件赋值运算符将会是最佳解决方案:# 假设输出目录为$(PROJECT_DIR)/out OUTPUT_DIR ?= $(PROJECT_DIR)/out
使用下面的较冗长的方式同样可以实现同样的效果
ifndef OUTPUT_DIR # 假设输出目录为$(PROJECT_DIR)/out OUTPUT_DIR = $(PROJECT_DIR)/out endif
其中的差别在于,如果变量的值已经设定,那么即使是空值,条件赋值运算符也会跳过赋值的动作,而运算符ifdef和ifndef只会测试“非空值”。因此,我们会使用条件运算符而不会使用ifdef来对OUTPUT_DIR赋值。
不建议过多的使用环境变量- 自动创建
最后,make会在执行一个规则的命令脚本之前立刻创建自动变量。
7. 条件指令
条件指令的基本语法如下所示:
if-condition
text if the condition is true
endif
或:
if-condition
text if the condition is true
else
text if the condition is false
endif
其中,if-condition
可以是以下之一:
ifdef variable-name
ifndef variable-name
ifeq test
ifneq test
进行ifdef/ifndef的测试时,不应该以$()
括住variable-name。最后,test可以表示成下面这样
"a" "b"
或
(a,b)
其中,单引号或双引号可以交替使用(但是引号必须成对出现)。
条件处理指令可用在宏定义和命令脚本中,还可以放在makefile的顶层:
libGui.a: $(gui_objects)
$(AR) $(ARFLAGS) $@ $<
ifdef RANLIB
$ (RANLIB) $@
endif
我喜欢缩排我的条件指令,但是草率的缩排动作可能会导致错误。在前面的例子中,条件指令被缩排了四个空格,而且其所括住的命令具有一个前导的tab符号。如果其所括住的命令并非以一个tab符开头,make将不会把它视为命令;如果条件指令具有一个前导的tab符,make会误以为"条件指令"就是"命令"而将之传递给subshell。
ifeq和ifneq条件指令可用来测试其参数是否相等。条件指令里空格的处理有些微妙。举例来说,如果参数采用小括号的形式,那么逗号之后的空格将会被忽略,除此之外所有其他的空格都是有意义的:
ifeq (a, a)
# These are equal
endif
ifeq ( b, b )
# So are these
endif
我比较喜欢使用等效的引号形式:
ifeq "a" "a"
# These are equal
endif
ifeq 'b' 'b'
# So are these
endif
即使如此,还是经常会发生"变量扩展后包含了非预期的空格符号"的状况。这可能引发一些问题,因为进行匹配时会将所有字符纳入考虑。为了创建更稳定的makefile,我们会使用strip函数。
ifeq "$(strip $(OPTIONS))" "d"
COMPILATION_FLAGS += -DDEBUG
endif
8. include指令
用法
include xxx.mk
引入文件与依存关系
当make看到include指令时,会事先对通配符以及变量引用进行扩展的动作,然后试着读进include的文件。如果这个文件存在,则整个处理过程会继续下去;然而,如果这个文件不存在,则make会汇报问题并且继续读取其余的makefile。
当所有的读取动作皆已完成之后,make会从规则数据库中找出任何可用来更新引入文件的规则。如果找到了一个相符的规则,make就会按照正常的步骤来更新工作目标。如果任何一个引入文件被规则更新,make接着会清楚它的内部数据库并且重新读进整个makefile。如果完成读取、更新和重新读取的过程之后,仍有include指令因为文件不存在而执行失败,那么make就会显示错误状态并终止执行。
如果想让make忽略无法加载的引入文件,可以为include指令前置一个破折号
-include xxx.mk
9. 标准的make变量
除了自动变量,make还会为“自己的状态以及内置规则的定义”提供变量,以便对外提供相关信息:
- MAKE_VERSION
GNU make的版本号
- CURDIR
正在执行make进程的当前工作目录。
此变量的值将会是shell变量PWD的值,除非make在运行时用到了--directory(或-C)选项。--directory选项会使得make在搜索任何makefile之前变更到不同的目录。这个选项的完整形式为--directory=directory-name
或-C directory-name
。这样,CURDIR将会包含--include-dir
的目录参数。
在makefile文件中,所有路径都应该被设定成相对于makefile所在的目录。需要使用绝对路径时可以通过CURDIR进行访问。
- MAKEFILE_LIST
make所读进的各个makefile文件的名称所构成的列表,包括默认的makefile以及命令行或include指令所指定的makefile。
在每个makefile被读进make之前,其文件名会被附加到MAKEFILE_LIST变量里。所以,任何一个总是可以查看此列表的最后一项来判断自己的文件名。
- MAKECMDGOALS
对当前运行的make而言,make运行时命令行上指定了哪些工作目标。此变量并不包含命令行选项或变量的赋值。
- .VARIABLES
到目前为止,make从各个makefile文件所读进的变量的名称所构成的列表,不含工作目标的专属变量。此变量仅供读取,对它所进行的任何赋值动作都会被忽略掉。
评论