makefile 变量与宏
变量和宏其实说的是同一东西。一个变量的内容是一个字符串,从一个变量名获得变量内容的过程叫做变量的扩展,用$()或者${}扩住变量名即可。而不想编程语言那样,使用变量名就能引用变量的值。
变量的类型
make的变量有两种:简易变量和递归扩展的变量。变量的定义是一个赋值动作,把等号右边的内容赋给左边。这里等号可以有多种::=、=、?=,他们决定了怎样赋值。等号两边的东西可以只是字面值,或者含有变量。赋值时,变量名需要是明确的,等号左边的内容立即扩展(没有变量就保持不变),右边的内容根据赋值符号决定何时扩展。
简易变量
用:=
或者::=
赋值运算符定义的是一个简易扩展的变量。一旦make读入该变量的定义语句,赋值运算符右边部分会立刻扩展,而扩展后的文本会被存储成该变量的值。变量名和变量内容加入到数据库。MAKE_DEPEND := $(CC) -M
此变量一般被扩展为gcc -M
然而,如果CC变量尚未定义,则扩展为:<space>-M
变量没有定义不算错误。
递归变量
用=
定义的变量。或者define
定义的变量。make
只会读进赋值运算符后边的部分,并将之存储成该变量的值,但不会进行任何扩展的动作,扩展的动作会被延迟到该变量被使用的时候进行。
其他赋值类型
make还提供了另外两种赋值运算符:?=
和+=
。
?=
运算符称为附带条件的变量赋值运算符。此运算符只会在变量的值尚不存在是进行复制动作。+=
运算符称为附加运算符。此运算符会将文本附加到变量里。对递归变量仍有用。
对于简单变量,等效于simple := $(simple) new stuff
然而,对于递归变量recursive = $(recursive) new stuff
这种表达式是非法的,会被无限扩展。要想将某段文本附加到递归变量上,就需要附加运算符了。
工作目标与模式的专属变量
make提供了工作目标的专属变量。这些变量的定义会附加在工作目标上,且只有在该工作目标以及相应的必要条件被处理的时候,他们才会发生作用。
工作目标的专属变量语法如下:
target...: variable = value
target...: variable := value
target...: variable ?= value
target...: variable += value
如果在编译某个源文件的时候需要单独指定一个宏定义,然而该文件又在某个模式规则中:
gui.o: CPPFLAGS += -DUSE_NEW_MALLOC=1
gui.o: gui.h
当make处理gui.o
这个工作目标时,CPPFLAGS
这个变量会附加-DUSE_NEW_MALLOC=1
,当处理完gui.o
这个目标之后,CPPFLAGS
会恢复它原来的值。
变量的来源
文件
变量可以被定义在makefile中,或是被makefile引入(include指令)
命令行
可以直接在make命令行上定义或者重新定义变量: $ make CFLAG=-g CPPFLAGS='DBSD -DDEBUG'
每个命令行参数中所包含的等号,都是一个变量赋值运算符。在命令行上,每个变量赋值运算符的右边部分必须是一个单独的shell参数。如果变量的值含有空格,则必须为参数加上括号或是引号。
命令行上变量的赋值将会覆盖掉环境变量以及makefile中的赋值结果。如果要使makefile中的变量覆盖命令行中的变量,可以在makefile中的变量前加override指令。
环境
当make启动时,所有来自环境的变量都会被定义为make的变量。这些变量具有非常低的优先级,makefile文件或命令行参数的赋值结果都会覆盖环境变量的值。但是,可以使用–environment-overrides(或-e)命令行选项,让环境变量覆盖相应的makefile变量。
当make被递归调用时,有若干来自上层make的变量会通过环境传递给下层的make。默认只有原来就来自环境的变量会被导出到下层的环境中。可以使用export指令导出任何变量。
自动创建
make会在执行一个规则的命令脚本之前立即创建自动变量。注意自动变量创建的时机。
在makefile中定义变量
variable := value
variable = value
variable ?= value
define variable =
...
...
endef
define variable :=
...
...
endef
define
的特长是可以定义多行内容的变量。
一个变量的值由赋值符号右边除去前导空格的所有字符组成。跟在所有字之后的空格不会被删除。这有时会导致问题。
宏
宏是以前对变量的另一种称呼。
可以通过define
指令创建“封装命令序列”,称为宏。在make中,宏只是用来定义变量的另一种形式,此变量还可以包含换行符。一般,将由define
定义的变量称为宏,由赋值运算符定义的变量称为变量。
define
后跟着变量名和换行,变量的主体是由跳格符开头的命令行,最后以endef
结尾。
define create-jar
@echo Creating $@...
$(RM) $(TMP_JAR_DIR)
$(MKDIR) $(TMP_JAR_DIR)
$(CP) -r $^ $(TMP_JAR_DIR)
cd $(TMP_JAR_DIR) && $(JAR) $(JARFLAGS) $@ .
$(JAR) -ufm $@ $(MANIFEST)
$(RM) $(TMP_JAR_DIR)
endef
@
的作用:make将每条命令交给shell执行之前都会打印出此条命令,在该命令之前加@
可以使make不这样做。如果在命令中应用了一个宏,使用@将使整个宏扩展后的命令之前都加上@。
当make运行时,它会以两个阶段来完成他的工作。第一阶段,make读进makefile以及被引入的任何其他makefile。这时,其中所定义的变量会被加载到make的内部数据库,并建立依存图。第二阶段,make分析依存图并判断需要更新的目标,然后执行脚本。
当make在处理递归变量或者define指令的时候,会将变量里的每一行或宏的主体存储起来,包括换行符号,但不会予以扩展。宏定义的最后一个换行符不会作为宏的一部分,否则,宏被扩展时会多一个换行。
当宏被扩展时,make会立即扫描被扩展的文本中是否存在宏或变量的引用,如果存在就予以扩展,如此递归下去。如果宏是在命令脚本里被扩展的,则宏的主体的每一行都会被插入一个跳格符。
下面是makefile中的元素何时被扩展的原则:
- 对于变量赋值,make会在第一阶段读取该行时,立即扩展赋值运算符左边的部分。
-
=
和?=
的右边会被延后到他们被使用时扩展,并且在第二阶段运行。 -
:=
右边的部分会被立即扩展 - 如果
+=
的左边部分原本被定义成一个简单变量,+=
的右边就会被立即扩展,否则,求值动作就会延后。 - 对于宏定义,宏的变量名会被立即扩展,宏的主体延后扩展。
- 对于规则,工作目标和必要条件总是被立即扩展,而命令总是延后扩展。
延后扩展发生在它所在的表达式需要被扩展时,比如在规则的目标和必要条件中、在一个将要执行的命令中或者出现在简易变量赋值的右边等等。
OUTPUT_DIR := /tmp
$(OUTPUT_DIR)/very_big_file:
$(free-space)
define free-space
$(PRINTF) "Free disk space"
$(DF) . | $(AWK) 'NR == 2 { print $$4 }'
endef
BIN := /usr/bin
PRINTF := $(BIN)/printf
DF := $(BIN)/df
AWK := $(BIN)/awk
说明:
第一阶段:make逐行读取makefile并将变量加入内部数据库,建立依存图。OUTPUT_DIR
是简单变量,它的值就是普通的字面值(如果这里有$
引用的变量,将会进行扩展动作),放到数据库中。
接下来是一条规则,规则的目标和条件都是立即扩展的,而命令是延后扩展的,保持不变。所以这条规则变为:
/tmp/very_big_file:
$(free-space)
之后是一个宏定义,宏名是立即扩展的,这里只是字面值,不用扩展。宏体是延后扩展的,在使用该宏的时候才扩展。
最后4个简易变量都是直接扩展的,将变量值加入到数据库中。
第二阶段:
按照后序遍历规则树进行规则的执行动作。这时要使用规则中的命令部分,对命令中的变量和宏进行扩展,并执行命令。
自动变量
自动变量是一种make在处理规则时自动赋值的变量。
变量名 | 描述 |
---|---|
$@ | 工作目标的文件名 |
$% | 档案文件成员结构中的文件名元素 |
$< | 第一个必要条件的文件名 |
$? | 时间戳在工作目标之后的所有必要条件,并以空格隔开。 |
$^ | 所有必要条件的文件名,并以空格隔开。 |
$+ | 如同$^ ,代表所有必要条件的文件名,并以空格隔开。不过$+ 包含重复的文件名。 |
$* | 工作目标的主文件名。文件名由主文件名和扩展名构成。 |
说明: 档案文件中个别的成员可作为工作目标或必要条件。可以通过archive(member)
这样的语法在档案文件archive
中指定名为member
的成员。若工作目标是foo.a(bar.o)
,则$%
是bar.o
而$@
是foo.a
。当工作目标不是一个档案文件时,$%
是空的。