Makefile 笔记(2)— 使用变量
一个完整的 Makefile ,包含了五个东西:显式规则、隐含规则、变量定义、指示符和注释。
显式规则
显式规则说明了,如何生成一个或多个目标文件。书写 Makefile 时需要明确地给出目标文件、目标的依赖文件列表以及更新目标文件所需要的命令(有些规则没有命令,这样的规则只是纯粹的描述了文件之间的依赖关系)。
隐式规则
由于我们的make有自动推导的功能,所以隐式的规则可以让我们比较简略地书写Makefile,这是由make所支持的。
变量定义
使用一个字符或字符串代表一段文本串,当定义了一个变量以后,Makefile后续在需要使用此文本串的地方,通过引用这个变量来实现对文本串的使用。
Makefile 指示符
指示符指明在 make 程序读取 Makefile 文件过程中所要执行的一个动作。其中包括:
读取一个文件,读取给定文件名的文件,将其内容作为 Makefile 文件的一部分。
决定(通常是根据一个变量的得值)处理或者忽略 Makefile 中的某一特定部分。
定义一个多行变量。
注释 Makefile 中" #"字符后的内容被作为是注释内容(和 shell 脚本一样)处理。如果此行的第一个非空字符为" #",那么此行为注释行。注释行的结尾如果存在反斜线( \),那么下一行也被作为注释行。
2. 解析Makefile
GNU make 的执行过程分为两个阶段:
第一阶段:
读取所有Makefile文件(包含 "MakefileS" 变量指定的、指示符 "include" 指定的、以及命令行选项 "-f(--file)" 指定的 Makefile 文件),内建所有的变量、明确规则和隐含规则,并建立所有目标和依赖关系结构链表。
第二阶段:
根据第一阶段已经建立的依赖关系结构链表决定哪些目标需要更新,并使用对应的规则来重建这些目标。
3. 使用变量
当定义了一个变量后,就可以在 Makefile 的很多地方使用这个变量。变量的引用方式是: $(VARIABLE_NAME) 或 ${VARIABLE_NAME} 。
举例如下:
objects = program.o foo.o utils.o program : $(objects) cc -o program $(objects) $(objects) : defs.h
注意:美元符号 "$" 在 Makefile 中有特殊的含义,因此,在命令或者文件名中使用 "$" 时,需用两个美元符号 "$$" 来代替。
变量引用的展开过程是严格的文本替换,即变量的字符串被精确的展开在变量被引用的地方,如下:
foo = c prog.o : prog.$(foo) $(foo)$(foo) -$(foo) prog.$(foo)
展开后就是:
prog.c : prog.c cc -c prog.c
3.1 赋值变量
赋值方式,主要有以下四种:
IMMEDIATE = DEFERRED IMMEDIATE := IMMEDIATE IMMEDIATE ?= DEFERRED IMMEDIATE += DEFERRED
延迟变量 "="
采用 "IMMEDIATE = DEFERRED" 方式定义变量。这种风格的变量采用递归展开方式。变量的引用采用严格的文本替换方式,如果此变量定义在其他变量的引用,这些被引用的变量会在它被展开的同时被展开。例如:
foo = $(bar) bar = $(ugh) ugh = Huh? all: @echo $(foo)
输出结果为 'Huh?' ,且输出结果与变量定义的位置无关。
立即变量 ":="
采用 "IMMEDIATE := IMMEDIATE" 方式定义变量 。这种风格的变量采用直接展开方式。变量值中对其他量或者函数的引用在定义变量时被展开,所以变量定义后就是一个实际需要的文本串,其中不再包含变量的引用例如:
x := foo y := $(x) bar x := later 等价于: y := foo bar x := later
和递归展开不同,此风格变量在定义时就完成了对所有变量和函数的展开,因此若将x定义在y之后,则得到 "y := bar"。
条件赋值 "?="
采用 "IMMEDIATE ?= DEFERRED" 方式定义变量 。
符号"?=",被称为条件赋值,因为,只有此变量在之前没有赋值的情况下才会对这个变量进行赋值。如:
FOO ?= bar 等价于: ifeq ($(orign FOO), undefined) FOO = bar endif
追加赋值 "+="
采用 "IMMEDIATE += DEFERRED or IMMEDIATE" 方式定义变量。实现对一个变量值的追加操作。例如:
objects += another.o
这个操作把字符串 "another.o" 添加到变量 "objects" 原有值的末尾,使用空格和原有值分开。
3.2 make 环境变量
MAKEFILES
如果当前环境定义了一个 "MAKEFILES" 环境变量,make执行时首先将变量的值作为需要读入的 Makefile 文件,多个文件之间使用空格分开。类似使用指示符 "include" 包含其它 Makefile 文件一样。
MAKEFILES_LIST
make程序在读取多个 Makefile 文件时,包括由环境变量 "MAKEFILES" 指定、命令行指定、当前工作下默认以及使用指示符"include" 指定包含的,在对这些文件进行解析执行之前 make 读取的文件名将会被自动依次追加到变量 "MAKEFILES_LIST" 的定义域中。
这样可以通过测试此变量的最后一个字获取当前 make 程序正在处理的 Makfile 文件名。
name1 := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) include inc.mk name2 := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) all: @echo name1 = $(name1) @echo name2 = $(name2)
输出结果为:
name1 = Makefile name2 = inc.mk
VPATH
GNU make 可以识别到一个特殊变量 "PATH"。通过变量 "PATH" 可以指定依赖文件的搜索路径,当规则的依赖文件在当前目录不存在时,make 会在此变量所指定的目录下去寻找这些依赖文件。其实,"VPATH" 变量所指定的是 Makefile 中所有文件的搜索路径,包括了规则的依赖文件和目标文件。
定义变量 "VPATH" 时,使用空格或者冒号(:)将多个需要搜索的目录分开。例如:
VPATH = src:../headers
这样,就为所有规则的依赖指定了两个搜索目录,"src" 和 "../headers"。
SHELL
make 对所有规则命令的解析使用环境变量 "SHELL" 所指定的那个程序,在GNU make中,默认的程序是 "/bin/sh"。
MAKE
在使用 make 的递归调用时,在 Makefile 规则的命令行中应该使用变量 "MAKE" 来代替直接使用 "make"。例如:
subsystem: cd subdir && $(MAKE)
变量 "MAKE" 的值是 "make"。如果其值为 "/bin/make" 那么上边规则的命令就是 "cd subdir && /bin/make"。好处是,当我们使用一个其它版本的make程序时,可以保证最上层使用的make程序和其子目录下执行的make程序保持一致。
MAKELEVEL
在多级递归调用的 make 执行过程中,变量 "MAKELEVEL" 代表了调用的深度。在 make 一级级的执行过程中变量 "MAKELEVEL" 的值不断发生变化。
最上一级 "MAKELEVEL" 的值是 "0"、下一级是 "1",再下一级是 "2"。
MAKEFLAGS
在 make 的递归过程中,最上层make的命令行选项如 "-k"、"-s" 等会被自动的通过环境变量 "MAKEFLAGS" 传递给子 make 进程。
传递过程中变量 "MAKEFLAGS" 的值会被主控 make 自动的设置为包含执行 make 时的命令行选项的字符串。
执行多级的 make 调用时,当不希望传递 "MAKEFLAGS" 给子 make 时,需要再调用子程序 make 对这个变量进行赋空,例如:
subsystem: cd subdir && $(MAKE) MAKEFLAGS=
MAKECMDGOALS
make 在执行时,设置一个特殊变量 "MAKECMDGOALS",此变量记录了命令行参数指定的终极目标列表,没有通过参数指定终极目标时,此值为空。
例如,使用变量 "MAKECMDGOALS" 来判断命令行参数是否指定终极目标为 "clean",如果不是才包含。
sources = foo.c bar.c ifneq($(MAKECMDGOALS),clean) include $(sources:.c=.d) endif
CURDIR
在 make 递归调用中,变量 "CURDIR" 代表 make 的工作目录。当使用 "-C" 选项进入一个子目录后,此变量将被重新赋值。
.SUFFIXES
特殊目标 ".SUFFIXES" 的所有依赖指出了一系列在后缀规则中需要检查的后缀名。例如,把后缀 ".hack" 和 ".win" 加入到可识别后缀列表的末尾。
.SUFFIXES:.hack .win
若需要重设置默认的可识别后缀,如下
.SUFFIXES #删除所有已定义的可识别后缀 .SUFFIXES:.c .o .h #重新定义
.LIBPATTERNS
变量 ".LIBPATTERNS" 就是告诉链接器在执行链接过程中,对于出现 "-INAME" 的文件如何展开。
".LIBPATTERNS" 的值一般是多个包含模式字符(%)的字(一般不包含空格的字符串),多个字之间使用空格分开。
也可以将此变量置空,取消链接器对 "-INAME"格式的展开。
3.3 make 自动化变量
$@
表示规则的目标文件名。如果目标是一个文档文件(linux下,一般称.a文件为文档文件,也称为静态库文件),那么它代表这个文档的文件名。在多目标模式规则中,它代表的是哪个触发规则被执行的目标文件名。
$%
当规则的目标文件时一个静态库时,代表静态库的一个成员名。例如,规则的目标是 "foo.a(bar.o)",那么, "$%" 的值就为 "bar.o","$@" 的值就是 "foo.a"。如果目标文件不是静态库文件,其值为空。
$<
目标依赖列表中的第一个依赖文件名。如果是一个目标文件使用隐含规则来重建,则它代表由隐含规则加入的第一个依赖文件。
$?
所有比目标文件更新的依赖文件列表,即最新被修改过的文件。空格分割。如果目标是静态库文件名,代表的是库成员(.o文件)。
$^
规则的所有依赖文件列表,使用空格分隔。如果目标是静态库文件,它所代表的只能是所有库成员(.o文件)名。一个文件可重复的出现在目标的依赖中,变量 "$^" 只记录它的一次引用情况。就是说变量 "$^" 会去掉重复的依赖文件。
$+
类似 "$^",但是它保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。
$*
在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的部分(当文件名中存在目录时,“茎”也包含目录(斜杠之前)部分),例如:文件 "dir/a.foo.b",当目标的模式是 “a.%.b” 时,"$*" 的值为 "dir/a.foo"。“茎” 对于构造相关文件名非常有用。
$(@D)
表示目标文件的目录部分(不包括斜杠)。如果 "$@" 是 "dir/foo.o",那么 "$(@D)"的值为 "dir"。如果 "$@" 不存在斜杠,其值就是 "."(当前目录)。
$(@F)
目标文件的完整文件名中除目录以来的部分(实际文件名)。如果 "$@" 为 "dir/foo.o",那么 "$(@F)" 只就是 "foo.o"
$(*D)
$(*F)
分别代表目标 "茎" 中的目录部分和文件名部分。
$(%D)
$(%F)
当以如 "archive(member)" 形式静态库为目标时,分别表示库文件成员 "member" 名中的目录部分和文件名部分。它仅对这种形式的规则目标有效。
$(<D)
$(<F)
分别表示规则中第一个依赖文件的目录部分和文件名部分。
$(^D)
$(^F)
分别表示所有依赖文件的目录部分和文件部分(不存在同一文件)。
$(+D)
$(+F)
分别表示所有依赖文件的目录部分和文件部分(可存在重复文件)。
$(?D)
$(?F)
分别表示被更新的依赖文件的目录部分和文件部分。