告诉 make 如何重新编译系统的信息,来自读取一个名为 makefile 的数据库。
MAKEFILESmake 如何读取 makefilemakefile 中包含五种内容:显式规则、隐含规则、变量定义、指令(directive),以及注释。规则、变量和指令将在后面的章节中详细说明。
objects 被定义为所有目标文件(object)的列表(请参阅用变量让 makefile 更简洁一节)。
make 执行某些特殊操作的指示。包括以下几种:
#,请用反斜杠对其转义(例如 \#)。注释可以出现在 makefile 的任何一行,不过在某些情况下会受到特殊对待。
变量引用或函数调用的内部不能使用注释:出现在变量引用或函数调用中的 # 会被当作字面的 #,而不是注释的开始。
命令(recipe)中的注释和其他命令文本一样,会被原样传递给 shell。是否将其解释为注释,由 shell 决定。
在 define 指令内部,定义变量期间注释不会被忽略,而是原样保留为变量值的一部分。当该变量展开时,注释要么被当作 make 的注释,要么被当作命令(recipe)文本,具体取决于该变量被求值的上下文。
makefile 采用「基于行」的语法,换行符具有特殊含义,表示语句(statement)的结束。在 GNU make 中,构成语句的一行长度没有限制,只要计算机的内存允许,可以任意长。
不过,长到不折行或不滚动就无法显示的行很难阅读。因此,可以在语句中间插入换行来把 makefile 排得更易读。具体做法是用反斜杠(\)对语句中间的换行进行转义。在需要区分时,我们把(无论是否被转义的)以换行结尾的一行称为「物理行」,把直到第一个未转义换行为止、包含其间所有被转义换行的一条完整语句称为「逻辑行」。
反斜杠加换行的组合如何处理,取决于该语句是命令行(recipe)还是非命令行。命令行中反斜杠+换行的处理稍后说明(请参阅拆分命令行一节)。
在命令行以外,反斜杠+换行会被转换为一个空白字符。完成之后,该反斜杠+换行周围的所有空白都会被合并为一个空白。这包括反斜杠之前的所有空白、反斜杠+换行之后行首的所有空白,以及连续的反斜杠+换行组合。
如果定义了特殊目标 .POSIX,为符合 POSIX.2,反斜杠+换行的处理会略有改变。第一,反斜杠之前的空白不会被去除;第二,连续的反斜杠+换行不会被合并。
如果需要拆分行但不想添加任何空白,可以使用一个小技巧:把反斜杠+换行这一组替换为美元符号、反斜杠、换行这三个字符:
var := one$\
word
当 make 去除反斜杠+换行并把下一行合并为一个空白后,这等价于:
var := one$ word
接着 make 进行变量展开。变量引用「$ 」引用的是一个名字为单字符「 」(空白)的变量,但这样的变量并不存在,因此展开为空字符串。结果,最终的赋值等价于:
var := oneword
默认情况下,make 在查找 makefile 时,会按以下顺序依次尝试这些名字:GNUmakefile、makefile、Makefile。
通常应当把 makefile 命名为 makefile 或 Makefile。(我们推荐 Makefile,因为它在显示目录列表时会出现在靠前的醒目位置,紧邻 README 等其他重要文件。)最先尝试的名字 GNUmakefile 对大多数 makefile 并不推荐。只有当你拥有一个专用于 GNU make、其他版本的 make 无法解释的 makefile 时,才应使用这个名字。其他 make 程序会查找 makefile 和 Makefile,但不会查找 GNUmakefile。
如果这些名字一个都找不到,make 就完全不使用任何 makefile。这时你必须用命令参数指定一个目标(goal),make 将尝试仅使用其内置的隐含规则找出重新生成它的方法。请参阅使用隐含规则一节。
如果想给 makefile 使用非标准的名字,可以用「-f」或「--file」选项指定 makefile 的名字。参数「-f name」或「--file=name」指示 make 把文件 name 作为 makefile 读取。如果多次使用「-f」或「--file」选项,就可以指定多个 makefile。所有指定的 makefile 实际上会按指定顺序连接起来处理。指定了「-f」或「--file」时,不会自动查找默认的 makefile 名 GNUmakefile、makefile、Makefile。
include 指令指示 make 暂停读取当前 makefile,在继续读取之前先读取一个或多个其他 makefile。该指令是 makefile 中如下形式的一行:
include filenames…
filenames 中可以包含 shell 的文件名模式。如果 filenames 为空,则不引入任何内容,也不打印错误。
行首可以有多余的空白,会被忽略。但第一个字符不能是制表符(Tab)(或 .RECIPEPREFIX 的值)——因为以制表符开头的行会被当作命令行(recipe)。include 与文件名之间、以及各文件名之间需要有空白;那里多余的空白以及指令末尾的空白都会被忽略。行尾可以放置以「#」开头的注释。如果文件名中包含变量引用或函数引用,它们会被展开。请参阅如何使用变量一节。
例如,假设有 a.mk、b.mk、c.mk 三个 .mk 文件,并且 $(bar) 展开为 bish bash,那么下面的式子
include foo *.mk $(bar)
等价于
include foo a.mk b.mk c.mk bish bash
make 处理 include 指令时,会中断包含该指令的 makefile 的读取,依次读取列出的每个文件。完成之后,make 恢复读取该指令所在的 makefile。
使用 include 指令的一种场合是:多个程序分别由各自不同目录中的单独 makefile 处理,而它们需要使用一组共同的变量定义(请参阅设置变量一节)或模式规则(请参阅定义与重定义模式规则一节)。
另一种场合是:你想从源文件自动生成前置条件。可以把生成的前置条件放进一个文件,再由主 makefile 引入它。相比其他版本的 make 传统上采用的、设法把前置条件追加到主 makefile 末尾的做法,这种方式通常更加整洁。请参阅自动生成前置条件一节。
如果指定的名字不以斜杠开头(当 GNU Make 编译时带有 MS-DOS / MS-Windows 路径支持时,则不以驱动器盘符和冒号开头),并且该文件在当前目录中找不到,就会搜索其他几个目录。首先,搜索你用「-I」或「--include-dir」选项指定的目录(请参阅选项一览一节)。接着,按此顺序搜索下列目录(如果存在):prefix/include(通常是 /usr/local/include 1)、/usr/gnu/include、/usr/local/include、/usr/include。
.INCLUDE_DIRS 变量包含 make 搜索被引入文件的当前目录列表。请参阅其他特殊变量一节。
如果想避免在这些默认目录中搜索,可以在命令行选项 -I 后加上特殊值 -(例如 -I-)。这样 make 就会忘记所有已经设置的引入目录(包括默认目录)。
即使要引入的 makefile 在这些目录中都找不到,此时也不会立即成为致命错误;包含 include 的 makefile 的处理会照常继续。当所有 makefile 都读取完毕后,make 会尝试重新生成那些过时或不存在的文件。请参阅makefile 自身如何被重新生成一节。只有在找不到用于重新生成该 makefile 的规则,或者找到了规则但其命令(recipe)失败时,make 才会把找不到该 makefile 报告为致命错误。
如果想让 make 直接忽略一个不存在或无法重新生成的 makefile 而不输出错误信息,可以用 -include 指令代替 include,像这样:
-include filenames…
它在各方面都和 include 一样工作,唯一区别在于:即使 filenames 中的任何一个(或它们中任何一个的前置条件)不存在或无法重新生成,也不会出现错误(甚至警告)。
为与其他 make 实现兼容,sinclude 是 -include 的别名。
MAKEFILES
如果定义了环境变量 MAKEFILES,make 会把它的值视为一份(以空白分隔的)应当先于其他 makefile 读取的额外 makefile 名列表。这与 include 指令的行为很相似:会在各个目录中搜索这些文件(请参阅引入其他 makefile一节)。此外,不会从这些 makefile(或它们所引入的 makefile)中取得默认目标(goal),而且即使 MAKEFILES 中列出的文件找不到也不会报错。
MAKEFILES 的主要用途是在 make 的递归调用之间传递信息(请参阅递归使用 make一节)。在顶层调用 make 之前设置这个环境变量通常并不可取,因为一般来说最好不要从外部干预 makefile。不过,如果你在不指定特定 makefile 的情况下运行 make,那么 MAKEFILES 中的 makefile 可以做一些有用的事,比如定义搜索路径(请参阅为前置条件搜索目录一节),从而帮助内置的隐含规则更好地工作。
有些用户会想在登录时自动把 MAKEFILES 设置到环境中,并按照这一前提来编写 makefile。这是非常糟糕的主意,因为这样的 makefile 一旦由别人运行就会失效。在 makefile 中写明确的 include 指令是好得多的做法。请参阅引入其他 makefile一节。
有时 makefile 可以从其他文件(例如 RCS 或 SCCS 文件)重新生成。如果 makefile 能从其他文件重新生成,你大概希望 make 在读取之前先取得该 makefile 的最新版本。
为此,make 在读取完所有 makefile 后,会按处理的顺序把每个 makefile 都视为目标(goal),并尝试更新它。如果启用了并行构建(请参阅并行执行一节),makefile 也会被并行地重新构建。
如果某个 makefile 有一条说明如何更新它自身的规则(这条规则既可以在该 makefile 自身中,也可以在另一个 makefile 中),或者有一条隐含规则适用于它(请参阅使用隐含规则一节),那么必要时该 makefile 就会被更新。检查完所有 makefile 后,只要其中有任何一个实际发生了改变,make 就回到一张白纸的状态,从头重新读取所有 makefile。(这时也会再次尝试更新各个 makefile,但通常它们已经是最新的,因此不会再被改变。)每次重新启动都会更新特殊变量 MAKE_RESTARTS(请参阅其他特殊变量一节)。
如果你知道一个或多个 makefile 无法被重新生成,并且可能出于效率原因不想让 make 对它们进行隐含规则搜索,那么任何防止隐含规则查找的常规方法都可以使用。例如,可以写一条以该 makefile 为目标、命令(recipe)为空的显式规则(请参阅使用空命令一节)。
如果 makefile 指定用一条有命令(recipe)但没有前置条件的双冒号规则来重新生成某个文件,那么该文件总是会被重新生成(请参阅双冒号规则一节)。就 makefile 而言,一个带有「有命令但无前置条件的双冒号规则」的 makefile 会在每次运行 make 时被重新生成,并且在 make 重新开始、再次读取 makefile 之后又被重新生成一次。这会导致无限循环:make 会不停地重新生成 makefile 并重新启动,再也不做其他任何事情。因此,为避免这种情况,make 不会尝试重新生成那些被指定为「有命令但无前置条件的双冒号规则」目标的 makefile。
伪目标(phony)(请参阅伪目标一节)也会产生同样的效果。由于伪目标永远不会被视为最新,因此如果有一个被标记为伪目标的被引入文件,make 就会无止境地重新启动。为避免这一点,make 不会尝试重新生成被标记为伪目标的 makefile。
可以利用这一性质来优化启动时间:如果你知道不需要重新生成 Makefile,就可以通过加入下面任意一项,让 make 不去尝试重新生成它:
.PHONY: Makefile
或者:
Makefile:: ;
如果你没有用「-f」或「--file」选项指定任何要读取的 makefile,make 会尝试默认的 makefile 名。请参阅该给 makefile 取什么名字一节。与用「-f」或「--file」选项明确要求的 makefile 不同,make 并不确定这些 makefile 是否应当存在。不过,如果某个默认 makefile 不存在但可以通过运行 make 的规则来创建,你大概会希望运行这些规则,以便能使用该 makefile。
因此,如果所有默认 makefile 都不存在,make 会按顺序尝试创建它们,直到成功创建出其中一个,或者尝试的名字用尽为止。注意,即使 make 找不到也无法创建任何 makefile,那也不是错误;makefile 并非总是必需的。
当你使用「-t」或「--touch」选项(请参阅代替执行命令一节)时,你不会希望用一个过时的 makefile 来判断要 touch 哪些目标。因此「-t」选项对 makefile 的更新没有影响;即使指定了「-t」,makefile 仍会被实际更新。同样,「-q」(或「--question」)和「-n」(或「--just-print」)也不会阻止 makefile 的更新,因为过时的 makefile 会导致其他目标的输出出错。因此「make -f mfile -n foo」会更新并读取 mfile,然后显示(但不实际运行)用于更新 foo 及其前置条件的命令(recipe)。为 foo 显示的命令将是更新后的 mfile 内容所指定的那些。
不过有时你可能确实希望连 makefile 的更新也阻止。这可以通过:在把 makefile 指定为 makefile 之外,还在命令行上把它指定为目标(goal)来实现。当 makefile 名被明确指定为目标时,「-t」等选项也会应用于它。
因此「make -f mfile -n mfile foo」会读取 makefile mfile,显示(但不实际运行)更新它所需的命令(recipe),接着显示(同样不运行)更新 foo 所需的命令。关于 foo 的命令将是现有 mfile 内容所指定的那些。
有时你会想要一个内容与另一个 makefile 几乎相同的 makefile。多数情况下,用「include」指令把一个引入到另一个中,再添加更多目标或变量定义即可。然而,两个 makefile 为同一目标给出不同的命令(recipe)是不允许的。不过还有另一种办法。
在(引入方,即想要引入对方的那一方)makefile 中,可以使用一条匹配任何内容的模式规则来指定:「要重新生成无法从本 makefile 的信息中生成的目标,请查看另一个 makefile」。关于模式规则的更多信息,请参阅定义与重定义模式规则一节。
例如,假设有一个名为 Makefile 的 makefile,说明了如何生成目标「foo」(以及其他目标)。这时,你可以编写一个名为 GNUmakefile 的 makefile,内容如下:
foo:
frobnicate > foo
%: force
@$(MAKE) -f Makefile $@
force: ;
当你输入「make foo」时,make 会找到并读取 GNUmakefile,得知生成 foo 只需运行命令(recipe)「frobnicate > foo」。当你输入「make bar」时,make 在 GNUmakefile 中找不到生成 bar 的方法,于是使用模式规则的命令「make -f Makefile bar」。如果 Makefile 提供了更新 bar 的规则,make 就会应用该规则。对于 GNUmakefile 未说明如何生成的其他任何目标也是如此。
这套机制之所以奏效,是因为模式规则的模式只是一个「%」,能匹配任何目标。该规则指定了前置条件 force,从而保证即使目标文件已经存在,命令(recipe)也一定会被执行。我们给 force 目标一条空命令,以防 make 为构建它而去搜索隐含规则——否则 make 会把同一条匹配任何内容的规则也应用到 force 自身上,造成前置条件的循环!
make 如何读取 makefile
GNU make 的工作分为两个截然不同的阶段(phase)。第一阶段,它读取所有 makefile、被引入的 makefile 等等,把所有变量及其值、以及隐含规则和显式规则都内部化,并构建一张由所有目标及其前置条件构成的依赖图。第二阶段,make 使用这些内部化的数据来判断哪些目标需要更新,并运行更新它们所需的命令(recipe)。
理解这种两阶段方式很重要,因为它直接影响变量和函数的展开何时发生,而这往往是编写 makefile 时困惑的根源。下面汇总了 makefile 中可能出现的各种结构,以及每种结构的各个部分在哪个阶段发生展开。
如果展开发生在第一阶段,我们称之为即时(immediate)。make 在解析 makefile 的同时展开该结构部分。如果展开不是即时的,我们称之为延迟(deferred)。延迟结构部分的展开会被推迟,直到该展开被使用时——即在即时上下文中被引用时,或在第二阶段需要它时。
这些结构中,有些你可能还不熟悉。在后面的章节中熟悉各种结构后,可以随时回头参阅本节。
变量定义按如下方式解析:
immediate = deferred immediate ?= deferred immediate := immediate immediate ::= immediate immediate :::= immediate-with-escape immediate += deferred or immediate immediate != immediate define immediate deferred endef define immediate = deferred endef define immediate ?= deferred endef define immediate := immediate endef define immediate ::= immediate endef define immediate :::= immediate-with-escape endef define immediate += deferred or immediate endef define immediate != immediate endef
对于追加运算符「+=」,如果该变量先前被设置为简单变量(「:=」或「::=」),则右边被视为即时,否则被视为延迟。
对于带转义的即时运算符「:::=」,右边的值会被立即展开,但随后被转义(也就是说,展开结果中所有的 $ 都被替换为 $$)。
对于 shell 赋值运算符「!=」,右边会被立即求值并交给 shell。其结果存入左边所写的变量,而该变量被视为递归展开变量(因此每次被引用时都会重新求值)。
条件指令会被即时解析。这意味着,例如,不能在条件指令中使用自动变量,因为自动变量要等到该规则的命令(recipe)被调用时才会被设置。如果需要在条件指令中使用自动变量,必须把该条件移入命令(recipe)中,改用 shell 的条件语法。
规则无论其形式如何,总是以相同的方式展开:
immediate : immediate ; deferred
deferred
也就是说,目标部分和前置条件部分被即时展开,而用于构建目标的命令(recipe)总是被延迟。这对显式规则、模式规则、后缀规则、静态模式规则以及简单的前置条件定义都成立。
GNU make 逐行解析 makefile。解析按以下步骤进行:
make 如何读取 makefile一节)。
由此得出的一个重要结论是:只要能放进一行,宏就可以展开为一整条规则。下面的例子可以工作:
myrule = target : ; echo built $(myrule)
但是,下面的例子无法工作,因为 make 不会在展开之后重新拆分行:
define myrule
target:
echo built
endef
$(myrule)
上面的 makefile 不会成为一条带命令(recipe)的规则,而会变成一个带有前置条件「echo」和「built」的目标「target」的定义,就好像 makefile 中写的是 target: echo built 一样。展开完成后行中仍然残留的换行会被当作普通空白忽略。
要正确展开跨多行的宏,必须使用 eval 函数:这会让 make 的解析器对展开后的宏结果运行(请参阅eval 函数一节)。
前面我们已经了解到,GNU make 分两个截然不同的阶段工作:读取阶段和目标更新阶段(请参阅make 如何读取 makefile一节)。GNU Make 还有一项功能,能对 makefile 中定义的部分或全部目标,仅就其前置条件进行第二次展开。要让这第二次展开发生,必须在使用该功能的第一个前置条件列表之前定义特殊目标 .SECONDEXPANSION。
如果定义了 .SECONDEXPANSION,那么当 GNU make 需要检查某个目标的前置条件时,这些前置条件会被第二次展开。在多数情况下,这种二次展开不会有任何效果,因为所有变量引用和函数引用在 makefile 最初的解析时就已经被展开了。因此,要利用解析器的二次展开阶段,就需要在 makefile 中对变量引用或函数引用进行转义。这时第一次展开只是解除引用的转义而并不展开它,展开被留给二次展开阶段。例如,考虑下面这个 makefile:
.SECONDEXPANSION: ONEVAR = onefile TWOVAR = twofile myfile: $(ONEVAR) $$(TWOVAR)
第一次展开阶段之后,myfile 目标的前置条件列表会是 onefile 和 $(TWOVAR)。第一个(未转义的)对 ONEVAR 的变量引用被展开,而第二个(已转义的)变量引用没有被识别为变量引用,只是被解除转义。然后在二次展开期间,第一个词再次被展开,但由于不含任何变量引用或函数引用,它仍保持值 onefile;而第二个词此时成为对变量 TWOVAR 的普通引用,被展开为值 twofile。最终结果是有两个前置条件:onefile 和 twofile。
当然,这并不是一个很有意思的例子,因为同样的结果只要把两个变量都不转义地写进前置条件列表就能更简单地得到。两者的区别在变量被重新设置时才显现出来。考虑下面这个例子:
.SECONDEXPANSION: AVAR = top onefile: $(AVAR) twofile: $$(AVAR) AVAR = bottom
这里,onefile 的前置条件被即时展开并解析为值 top,而 twofile 的前置条件直到二次展开才被完全展开,得到值 bottom。
这稍微有意思一些了,但这项功能的真正威力,要等你发现二次展开总是在该目标的自动变量作用域内进行时才会显现。也就是说,可以在二次展开期间使用 $@、$* 等变量,它们会和在命令(recipe)中完全一样地具有预期的值。你要做的只是通过转义 $ 来推迟展开。此外,二次展开对显式规则和隐含(模式)规则都会发生。知道这一点后,这项功能的用途会急剧扩展。例如:
.SECONDEXPANSION: main_OBJS := main.o try.o test.o lib_OBJS := lib.o api.o main lib: $$($$@_OBJS)
这里,第一次展开之后,main 目标和 lib 目标的前置条件都会是 $($@_OBJS)。在二次展开期间,$@ 变量被设置为目标的名字,因此 main 目标的展开结果是 $(main_OBJS),即 main.o try.o test.o;而 lib 目标的二次展开结果是 $(lib_OBJS),即 lib.o api.o。
函数同样可以混用在这里,只要它们被正确地转义:
main_SRCS := main.c try.c test.c lib_SRCS := lib.c api.c .SECONDEXPANSION: main lib: $$(patsubst %.c,%.o,$$($$@_SRCS))
这个版本允许用户指定源文件而非目标文件,但前置条件列表的结果与前一个例子相同。
二次展开阶段中自动变量的求值,尤其是目标名变量 $$@ 的求值,行为与在命令(recipe)中的求值类似。不过,对于 make 所理解的不同种类的规则定义,会涉及一些细微的差别和「特殊情况」。下面说明使用各种自动变量时的细节。
在显式规则的二次展开中,$$@ 和 $$% 分别求值为目标的文件名,以及当目标是归档文件(archive)的成员时的目标成员名。$$< 变量求值为该目标的第一条规则的第一个前置条件。$$^ 和 $$+ 求值为同一目标已经出现过的各条规则的所有前置条件的列表($$+ 含重复,$$^ 不含)。下面的例子有助于理解这些行为:
.SECONDEXPANSION: foo: foo.1 bar.1 $$< $$^ $$+ # 行 #1 foo: foo.2 bar.2 $$< $$^ $$+ # 行 #2 foo: foo.3 bar.3 $$< $$^ $$+ # 行 #3
在第一个前置条件列表中,三个变量($$<、$$^、$$+)都展开为空字符串。在第二个中,它们的值分别为 foo.1、foo.1 bar.1、foo.1 bar.1。在第三个中,它们的值分别为 foo.1、foo.1 bar.1 foo.2 bar.2、foo.1 bar.1 foo.2 bar.2 foo.1 foo.1 bar.1 foo.1 bar.1。
规则按 makefile 中书写的顺序进行二次展开,但唯独带命令(recipe)的规则总是最后被求值。
变量 $$? 和 $$* 不可用,会展开为空字符串。
静态模式规则二次展开的规则,除一个例外外,与上面显式规则的情形相同。这个例外是:在静态模式规则中,$$* 变量被设置为模式的词干(stem)。与显式规则一样,$$? 不可用,会展开为空字符串。
make 搜索隐含规则时,会先代入词干,然后对所有与目标模式匹配的规则进行二次展开。自动变量的值以与静态模式规则相同的方式导出。例如:
.SECONDEXPANSION: foo: bar foo foz: fo%: bo% %oo: $$< $$^ $$+ $$*
当对目标 foo 尝试这条隐含规则时,$$< 展开为 bar,$$^ 展开为 bar boo,$$+ 也展开为 bar boo,$$* 展开为 f。
请注意,隐含规则的搜索算法中所述的目录前缀(D),会(在展开之后)被附加到前置条件列表中的所有模式上。例如:
.SECONDEXPANSION:
/tmp/foo.o:
%.o: $$(addsuffix /%.c,foo bar) foo.h
@echo $^
经过二次展开和目录前缀重建之后所显示的前置条件列表会是 /tmp/foo/foo.c /tmp/bar/foo.c foo.h。如果你不需要这种重建,可以在前置条件列表中用 $$* 代替 %。