Makefile之隐式规则
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页
温故知新
- 练习1 理解编译的基本过程
- 练习2 创建基本的Makefile
- 练习3 Makefile之简单介绍
- 练习4 Makefile之显式规则
- 练习5 Makefile之配方规则
- 练习6 Makefile之使用变量
- 练习7 Makefile之条件部分
- 练习8 Makefile之使用函数
- 练习9 Makefile之运行参数
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页
使用隐式规则
在 make
中,有一些标准的重建目标文件的方式被频繁使用。例如,一种通常的制作目标文件的方式是使用 C 编译器 cc 编译 C 源文件。
隐式规则告诉 make
如何使用通常的技术,因此当您想使用它们时,您不必详细指定它们。例如,有一个用于 C 编译的隐式规则。文件名决定了运行哪些隐式规则。例如,C 编译通常会接受一个 .c 文件并生成一个 .o 文件。因此,当 make
发现这个文件名后缀组合时,它会应用 C 编译的隐式规则。
隐式规则的链可以依次应用;例如,make
将通过 .c 文件从 .y 文件重新制作一个 .o 文件。
内建的隐式规则在它们的配方中使用了几个变量,通过更改这些变量的值,您可以更改隐式规则的工作方式。例如,变量 CFLAGS 控制由 C 编译的隐式规则传递给 C 编译器的标志。
您还可以通过编写模式规则来定义自己的隐式规则。
后缀规则是定义隐式规则的一种更有限的方式。模式规则更通用和更清晰,但为了兼容性,保留了后缀规则。
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页
内置规则目录
要让 make 找到更新目标文件的通常方法,您只需避免自己指定配方。要么编写一个没有配方的规则,要么不写规则。然后,make 将根据存在或可以生成的源文件类型来确定使用哪个隐式规则。
例如,假设 Makefile 如下:
foo : foo.o bar.o
cc -o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
因为提到了 foo.o
但没有为它提供规则,make 将自动查找一个告诉如何更新它的隐式规则。这会发生无论文件 foo.o
当前是否存在。
如果找到了隐式规则,它可以提供一个配方和一个或多个先决条件(源文件)。如果您需要指定额外的先决条件,例如隐式规则无法提供的头文件,您将希望编写一个没有配方的 foo.o
规则。
每个隐式规则都有一个目标模式和先决条件模式。可能有许多具有相同目标模式的隐式规则。例如,有许多规则可以制作 ‘.o’ 文件:一个是使用 C 编译器从 ‘.c’ 文件制作的;另一个是使用 Pascal 编译器从 ‘.p’ 文件制作的;等等。实际应用的规则是那些先决条件存在或可以生成的规则。因此,如果您有一个文件 foo.c
,make 将运行 C 编译器;否则,如果您有一个文件 foo.p
,make 将运行 Pascal 编译器;依此类推。
当您编写 Makefile 时,您知道您希望 make 使用哪个隐式规则,您知道它会选择那个规则,因为您知道哪些可能的先决条件文件应该存在。有关所有预定义隐式规则的目录。
上面,我们说一个隐式规则应用于所需的先决条件“存在或可以生成”。如果一个文件“可以生成”,那么它在 Makefile 中被显式地提及为目标或先决条件,或者可以递归地找到一个隐式规则来生成它。当隐式先决条件是另一隐式规则的结果时,我们称之为正在发生链接。
通常,make 会为每个没有配方的目标和每个没有配方的双冒号规则搜索隐式规则。仅作为先决条件提及的文件被认为是一个规则未指定任何内容的目标,因此为它进行隐式规则搜索。
请注意,显式的先决条件不会影响隐式规则搜索。例如,考虑以下显式规则:
foo.o: foo.p
对 foo.p
的先决条件并不一定意味着 make 将根据生成目标文件的隐式规则重新制作 foo.o
,该规则将从 Pascal 源文件(.p 文件)生成对象文件(.o 文件)。例如,如果 foo.c
也存在,那么将使用从 C 源文件生成对象文件的隐式规则,因为它在预定义隐式规则列表中出现在 Pascal 规则之前。
如果您不希望一个没有配方的目标使用隐式规则,您可以通过写一个分号来给该目标一个空配方。
这里是一份预定义的隐式规则目录,它们始终可用,除非 Makefile 显式地覆盖或取消它们。选项‘-r’或‘–no-builtin-rules’会取消所有预定义的规则。
这个手册仅记录了基于 POSIX 的操作系统上可用的默认规则。其他操作系统,如 VMS、Windows、OS/2 等,可能有不同的默认规则集。要查看您的 GNU make 版本中所有默认规则和变量的完整列表,请在没有 Makefile 的目录中运行 ‘make -p’。
这里列出的并非所有规则都会一直定义,即使没有给出‘-r’选项。许多预定义的隐式规则在 make 中是作为后缀规则实现的,因此哪些规则将被定义取决于后缀列表(特殊目标 .SUFFIXES 的先决条件列表)。默认的后缀列表是:.out、.a、.ln、.o、.c、.cc、.C、.cpp、.p、.f、.F、.m、.r、.y、.l、.ym、.lm、.s、.S、.mod、.sym、.def、.h、.info、.dvi、.tex、.texinfo、.texi、.txinfo、.w、.ch、.web、.sh、.elc、.el。这些具有这些后缀之一的先决条件的所有下面描述的隐式规则实际上都是后缀规则。如果修改了后缀列表,则只会生效列表中指定的一个或两个后缀的预定义后缀规则;其后缀未在列表上的规则被禁用。
编译 C 程序
n.o
会自动从 n.c
制作,使用的配方形式是 ‘$(CC) $(CPPFLAGS) $(CFLAGS) -c’。
编译 C++ 程序
n.o
会自动从 n.cc
、n.cpp
或 n.C
制作,使用的配方形式是 ‘$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c’。我们鼓励您对 C++ 源文件使用后缀 ‘.cc’ 或 ‘.cpp’,而不是 ‘.C’,以更好地支持大小写不敏感的文件系统。
编译 Pascal 程序
n.o
会自动从 n.p
制作,使用的配方是 ‘$(PC) $(PFLAGS) -c’。
编译 Fortran 和 Ratfor 程序
n.o
会自动从 n.r
、n.F
或 n.f
制作,通过运行 Fortran 编译器。使用的具体配方如下:
- ‘.f’:‘$(FC) $(FFLAGS) -c’。
- ‘.F’:‘$(FC) $(FFLAGS) $(CPPFLAGS) -c’。
- ‘.r’:‘$(FC) $(FFLAGS) $(RFLAGS) -c’。
预处理 Fortran 和 Ratfor 程序
n.f
会自动从 n.r
或 n.F
制作。此规则仅运行预处理器,将 Ratfor 或可预处理的 Fortran 程序转换为严格的 Fortran 程序。使用的具体配方如下:
- ‘.F’:‘$(FC) $(CPPFLAGS) $(FFLAGS) -F’。
- ‘.r’:‘$(FC) $(FFLAGS) $(RFLAGS) -F’。
编译 Modula-2 程序
n.sym
会从 n.def
制作,使用的配方形式是 ‘$(M2C) $(M2FLAGS)
(
D
E
F
F
L
A
G
S
)
’。
‘
n
.
o
‘
会从
‘
n
.
m
o
d
‘
制作,形式是:‘
(DEFFLAGS)’。`n.o` 会从 `n.mod` 制作,形式是:‘
(DEFFLAGS)’。‘n.o‘会从‘n.mod‘制作,形式是:‘(M2C) $(M2FLAGS) $(MODFLAGS)’。
**汇编和预处理汇编程序
n.o
会自动从 n.s
制作,通过运行汇编器 as
。具体的配方是 ‘$(AS) $(ASFLAGS)’。
n.s
会自动从 n.S
制作,通过运行 C 预处理器 cpp
。具体的配方是 ‘$(CPP) $(CPPFLAGS)’。
链接单个目标文件
n
会自动从 n.o
制作,通过运行 C 编译器链接程序。使用的具体配方是 ‘$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)’。
这条规则适用于只有一个源文件的简单程序。如果有多个对象文件(通常来自各种其他源文件),其中一个的名称与可执行文件的名称匹配,则它也会表现正确。因此,
x: y.o z.o
当 x.c
、y.c
和 z.c
都存在时,将执行:
cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
rm -f x.o
rm -f y.o
rm -f z.o
在更复杂的情况下,例如当没有对象文件的名称派生自可执行文件的名称时,您必须为链接编写一个显式的配方。
每种文件自动制成 ‘.o’ 对象文件都会使用编译器(‘ ( C C ) ’,‘ (CC)’,‘ (CC)’,‘(FC)’ 或 ‘ ( P C ) ’;使用 C 编译器‘ (PC)’;使用 C 编译器 ‘ (PC)’;使用C编译器‘(CC)’ 用于汇编 ‘.s’ 文件)而不使用 ‘-c’ 选项进行链接。这可以通过使用 ‘.o’ 对象文件作为中间文件来完成,但是通过一步完成编译和链接更快,因此就是这样做的方式。
C 程序的 Yacc
n.c
会通过运行 Yacc 制作,具体的配方是 ‘$(YACC) $(YFLAGS)’。
C 程序的 Lex
n.c
会通过运行 Lex 制作,实际的配方是 ‘$(LEX) $(LFLAGS)’。
Ratfor 程序的 Lex
n.r
会通过运行 Lex 制作,实际的配方是 ‘$(LEX) $(LFLAGS)’。
对于 C、Yacc 或 Lex 程序的 Lint 库的制作
n.ln
会从 n.c
通过运行 lint 制作,具体的配方是 ‘$(LINT) $(LINTFLAGS) $(CPPFLAGS) -i’。从 n.y
或 n.l
制作的 C 代码使用相同的配方。
TeX 和 Web
n.dvi
从 n.tex
通过配方 ‘
(
T
E
X
)
’制作。从
‘
n
.
w
e
b
‘
通过‘
(TEX)’ 制作。从 `n.web` 通过 ‘
(TEX)’制作。从‘n.web‘通过‘(WEAVE)’ 制作 n.tex
,或从 n.w
(如果存在或可以制作)通过 ‘
(
C
W
E
A
V
E
)
’制作。从
‘
n
.
w
e
b
‘
制作
‘
n
.
p
‘
通过‘
(CWEAVE)’ 制作。从 `n.web` 制作 `n.p` 通过 ‘
(CWEAVE)’制作。从‘n.web‘制作‘n.p‘通过‘(TANGLE)’,从 n.w
(如果存在或可以制作)通过 ‘$(CTANGLE)’ 制作 n.c
。
Texinfo 和 Info
n.dvi
从 n.texinfo
、n.texi
或 n.txinfo
通过 ‘$(TEXI2DVI)
(
T
E
X
I
2
D
V
I
F
L
A
G
S
)
’制作。
‘
n
.
i
n
f
o
‘
从
‘
n
.
t
e
x
i
n
f
o
‘
、
‘
n
.
t
e
x
i
‘
或
‘
n
.
t
x
i
n
f
o
‘
通过‘
(TEXI2DVI_FLAGS)’ 制作。`n.info` 从 `n.texinfo`、`n.texi` 或 `n.txinfo` 通过 ‘
(TEXI2DVIFLAGS)’制作。‘n.info‘从‘n.texinfo‘、‘n.texi‘或‘n.txinfo‘通过‘(MAKEINFO) $(MAKEINFO_FLAGS)’ 制作。
RCS
从名为 n,v
或 RCS/n,v
的 RCS 文件中,如果需要,会提取任何文件 n
。使用的具体配方是 ‘$(CO) $(COFLAGS)’。如果 n
已经存在,即使 RCS 文件更新,也不会从 RCS 提取。RCS 的规则是终端的,因此不能从另一个源生成 RCS 文件;它们必须实际存在。
SCCS
从名为 s.n
或 SCCS/s.n
的 SCCS 文件中,如果需要,会提取任何文件 n
。使用的具体配方是 ‘$(GET) $(GFLAGS)’。SCCS 的规则是终端的,因此不能从另一个源生成 SCCS 文件;它们必须实际存在。
出于 SCCS 的考虑,文件 n
从 n.sh
复制并使其可执行(对所有人都可执行)。这是为了将 shell 脚本检入 SCCS。由于 RCS 保留文件的执行权限,因此在使用 RCS 时,您不需要使用此功能。
我们建议避免使用 SCCS。RCS 被普遍认为优越,而且还是免费的。通过选择自由软件替代相当(或更差)的专有软件,您支持自由软件运动。
通常,您只需要更改上表中列出的变量,这些变量在以下部分中有文档。
但是,内置隐式规则中的配方实际上使用诸如 COMPILE.c、LINK.p 和 PREPROCESS.S 等变量,它们的值包含上面列出的配方。
make 遵循以下约定,即用于编译 .x 源文件的规则使用变量 COMPILE.x。类似地,从 .x 文件生成可执行文件的规则使用 LINK.x;从 .x 文件预处理的规则使用 PREPROCESS.x。
每个生成目标文件的规则都使用变量 OUTPUT_OPTION。make 会定义此变量,要么包含 ‘-o $@’,要么为空,这取决于编译时选项。需要 ‘-o’ 选项以确保当源文件位于不同目录中时输出进入正确的文件,就像使用 VPATH 时一样。但是,在某些系统上,编译器不接受用于对象文件的 ‘-o’ 开关。如果使用此类系统,并使用 VPATH,则某些编译将其输出放在错误的位置。此问题的可能解决方法是将 OUTPUT_OPTION 的值设为 ‘; mv $*.o $@’。
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页
隐式规则使用的变量
这是一些内建隐式规则中使用的常见程序名称变量以及它们的默认值:
-
AR: 归档维护程序,默认为 ‘ar’。
-
AS: 用于编译汇编文件的程序,默认为 ‘as’。
-
CC: 用于编译C程序的程序,默认为 ‘cc’。
-
CXX: 用于编译C++程序的程序,默认为 ‘g++’。
-
CPP: 运行C预处理器并将结果输出到标准输出的程序,默认为 ‘$(CC) -E’。
-
FC: 用于编译或预处理Fortran和Ratfor程序的程序,默认为 ‘f77’。
-
M2C: 用于编译Modula-2源代码的程序,默认为 ‘m2c’。
-
PC: 用于编译Pascal程序的程序,默认为 ‘pc’。
-
CO: 用于从RCS中提取文件的程序,默认为 ‘co’。
-
GET: 用于从SCCS中提取文件的程序,默认为 ‘get’。
-
LEX: 用于将Lex语法转换为源代码的程序,默认为 ‘lex’。
-
YACC: 用于将Yacc语法转换为源代码的程序,默认为 ‘yacc’。
-
LINT: 用于在源代码上运行lint的程序,默认为 ‘lint’。
-
MAKEINFO: 用于将Texinfo源文件转换为Info文件的程序,默认为 ‘makeinfo’。
-
TEX: 用于从TeX源文件生成TeX DVI文件的程序,默认为 ‘tex’。
-
TEXI2DVI: 用于从Texinfo源文件生成TeX DVI文件的程序,默认为 ‘texi2dvi’。
-
WEAVE: 用于将Web转换为TeX的程序,默认为 ‘weave’。
-
CWEAVE: 用于将C Web转换为TeX的程序,默认为 ‘cweave’。
-
TANGLE: 用于将Web转换为Pascal的程序,默认为 ‘tangle’。
-
CTANGLE: 用于将C Web转换为C的程序,默认为 ‘ctangle’。
-
RM: 用于删除文件的命令,默认为 ‘rm -f’。
以下是一些用于程序附加参数的变量:
-
ARFLAGS: 传递给归档维护程序的标志,默认为 ‘rv’。
-
ASFLAGS: 传递给汇编程序的额外标志(在显式调用 ‘.s’ 或 ‘.S’ 文件时)。
-
CFLAGS: 传递给C编译器的额外标志。
-
CXXFLAGS: 传递给C++编译器的额外标志。
-
COFLAGS: 传递给RCS co程序的额外标志。
-
CPPFLAGS: 传递给C预处理器和使用它的程序(如C和Fortran编译器)的额外标志。
-
FFLAGS: 传递给Fortran编译器的额外标志。
-
GFLAGS: 传递给SCCS get程序的额外标志。
-
LDFLAGS: 当编译器应该调用链接器(‘ld’)时传递给编译器的额外标志。库(‘-lfoo’)应添加到LDLIBS变量中。
-
LDLIBS: 传递给编译器在应该调用链接器(‘ld’)时的库标志或名称。LOADLIBES是LDLIBS的已弃用(但仍受支持)的替代品。非库链接器标志,如’-L’,应放在LDFLAGS变量中。
-
LFLAGS: 传递给Lex的额外标志。
-
YFLAGS: 传递给Yacc的额外标志。
-
PFLAGS: 传递给Pascal编译器的额外标志。
-
RFLAGS: 传递给Ratfor程序的Fortran编译器的额外标志。
-
LINTFLAGS: 传递给lint的额外标志。
通过更改这些变量的值,您可以自定义隐式规则的行为,而无需重新定义规则本身。这使得对默认规则进行微调变得非常方便。
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页
隐式规则链
这段文本解释了关于中间文件(Intermediate files)的概念和它们在GNU Make中的处理方式。中间文件是由隐式规则生成的文件,它们被认为是临时的,可以被Make系统删除,以避免不必要的文件积累。以下是一些关键点:
-
链(Chain): 有时文件可以通过一系列的隐式规则生成,例如通过运行Yacc和cc可以从
n.y
生成n.o
。这样的一系列规则称为链。 -
中间文件: 如果
n.c
不存在但Make知道如何生成它,那么n.c
被称为中间文件。中间文件是隐式规则生成的,会在Make数据库中以及在文件系统中存在。如果n.c
被当作中间文件,Make将只有在必要的情况下才会生成它,而且在不再需要时会删除它。 -
标记中间文件: 您可以通过将文件列为
.INTERMEDIATE
特殊目标的先决条件来显式标记文件为中间文件。 -
.NOTINTERMEDIATE: 如果您希望防止文件被视为中间文件,可以将其列为
.NOTINTERMEDIATE
特殊目标的先决条件。 -
禁用中间文件: 您可以通过在Makefile中提供
.NOTINTERMEDIATE
作为一个没有先决条件的特殊目标来完全禁用中间文件。 -
次要文件: 如果您不希望Make因为文件不存在而自动创建文件,但又不希望自动删除该文件,可以将其列为
.SECONDARY
特殊目标的先决条件。 -
优化规则: 为了性能原因,Make系统提供了一些优化规则,可以优化某些情况,而不需要使用完整的规则链。
-
链中的规则不可重复: 在一个链中,单个隐式规则不会出现两次。这样可以防止Make尝试通过两次运行链接器来生成目标文件等荒谬的情况。
-
性能优化: Make系统为了性能原因,在搜索构建隐式规则的先决条件时,不会考虑非终端的匹配任意规则。
这些概念有助于理解Make系统如何处理生成文件以及如何优化构建过程。
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页
定义和重新定义模式规则
在GNU Make中,通过编写模式规则(Pattern Rule)来定义隐式规则。模式规则与普通规则类似,但其目标包含字符‘%’(只能有一个)。目标被视为匹配文件名的模式;‘%’可以匹配任何非空子字符串,而其他字符只匹配它们自己。先决条件同样使用‘%’来说明它们的名称与目标名称的关系。
因此,一个模式规则 ‘%.o : %.c’ 表示如何从另一个文件 ‘stem.c’ 生成任何文件 ‘stem.o’。
请注意,模式规则中使用‘%’进行扩展是在任何变量或函数扩展之后进行的,而这些扩展在读取Makefile时发生。
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页
模式规则简介
模式规则包含目标中的字符‘%’(确切地是一个),否则,它看起来就像普通规则。目标是用于匹配文件名的模式;‘%’匹配任何非空子字符串,而其他字符只匹配它们自己。
例如,‘%.c’作为一个模式匹配任何以‘.c’结尾的文件名。‘s.%.c’作为一个模式匹配任何以‘s.’开头,以‘.c’结尾且至少有五个字符长的文件名(‘%’必须至少匹配一个字符)。‘%’匹配的子字符串被称为 stem。
在模式规则的先决条件中,‘%’表示与目标中的‘%’匹配的相同的 stem。为了应用模式规则,其目标模式必须匹配正在考虑的文件名,所有的先决条件(经过模式替换后)必须命名存在或可以生成的文件。这些文件将成为目标的先决条件。
因此,形式为
%.o : %.c ; recipe…
规定如何制作一个文件 ‘n.o’,其先决条件是另一个文件 ‘n.c’,前提是 ‘n.c’ 存在或可以制作。
可能还有不使用‘%’的先决条件;这样的先决条件适用于由此模式规则制作的每个文件。这些不变的先决条件偶尔会有用。
一个模式规则不需要有包含‘%’的先决条件,实际上也不需要任何先决条件。这样的规则实际上是一个通用通配符。它提供了一种制作与目标模式匹配的任何文件的方式。
可能有多个模式规则与一个目标匹配。在这种情况下,make 会选择“最佳匹配”的规则。
模式规则可以有多个目标;然而,每个目标必须包含一个 % 字符。模式规则中的多个目标模式始终被视为分组目标,无论它们是使用 : 还是 & 分隔符。
有一个例外:如果模式目标过时或不存在,并且 Makefile 不需要构建它,那么它将不会导致其他目标被视为过时。请注意,这个历史例外将在将来的 GNU make 版本中被移除,不应该依赖它。如果检测到这种情况,make 将生成警告“模式规则没有更新对等目标”;但是,make 无法检测所有这样的情况。请确保在运行时,您的规则更新了所有目标模式。
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页
模式规则示例
以下是一些实际在 make 中预定义的模式规则的示例。首先,将 ‘.c’ 文件编译为 ‘.o’ 文件的规则:
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
定义了一条规则,可以从 ‘x.c’ 制作任何文件 ‘x.o’。该配方使用自动变量 ‘ @ ′ 和 ′ @' 和 ' @′和′<’ 在规则适用的每个情况下分别替换目标文件和源文件的名称。
这里是第二个内置规则:
% :: RCS/%,v
$(CO) $(COFLAGS) $<
定义了一条规则,可以从子目录 RCS 中相应的文件 ‘x,v’ 制作任何文件 ‘x’。由于目标是 '%,这个规则将适用于任何文件,只要适当的先决条件文件存在。双冒号使规则成为终端规则,这意味着其先决条件不能是中间文件。
这个模式规则有两个目标:
%.tab.c %.tab.h: %.y
bison -d $<
这告诉 make 配方 ‘bison -d x.y’ 将制作 ‘x.tab.c’ 和 ‘x.tab.h’。如果文件 ‘foo’ 依赖于文件 ‘parse.tab.o’ 和 ‘scan.o’,而文件 ‘scan.o’ 又依赖于文件 ‘parse.tab.h’,那么当 ‘parse.y’ 更改时,配方 ‘bison -d parse.y’ 将只执行一次,并且将满足 ‘parse.tab.o’ 和 ‘scan.o’ 的两个目标的先决条件。 (假设文件 ‘parse.tab.o’ 将从 ‘parse.tab.c’ 重新编译,文件 ‘scan.o’ 将从 ‘scan.c’ 重新编译,而 ‘foo’ 将从 ‘parse.tab.o’、‘scan.o’ 和其它先决条件链接,并且它将一直运行愉快地)。
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页
自动变量
上述表格列出了一些自动变量及其描述。这些变量在规则的配方中使用,用于指代目标、前置条件等信息。以下是一些自动变量的示例:
$@
:规则目标的文件名。$<
:第一个前置条件的文件名。$?
:所有比目标更新的前置条件的文件名,用空格分隔。$^
:所有前置条件的文件名,用空格分隔。$*
:与隐式规则匹配的 stem(词根)部分。在静态模式规则中,stem 是目标模式中 ‘%’ 匹配的部分。
此外,还有一些变体变量,如 $(@D)
表示规则目标的目录部分,$(@F)
表示规则目标的目录中的文件名。这些变体提供了对文件路径的更细粒度的访问。
这些自动变量的作用域仅限于规则的配方中,不能在规则的目标列表中使用。请确保在适当的上下文中使用这些变量。
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页
模式如何匹配
目标模式是由前缀和后缀之间的 ‘%’ 构成的,前缀和后缀可以是空的。该模式仅在文件名以前缀开头并以后缀结尾(没有重叠)的情况下匹配文件名。前缀和后缀之间的文本称为 stem。因此,当模式 ‘%.o’ 匹配文件名 test.o 时,stem 是 ‘test’。模式规则的前提条件通过将字符 ‘%’ 替换为 stem 来转换为实际文件名。因此,在同样的例子中,如果其中一个前提条件被写为 ‘%.c’,它会扩展为 ‘test.c’。
当目标模式不包含斜杠(通常是这样),在将文件名与目标前缀和后缀进行比较之前,文件名中的目录名将从文件名中删除。在将目录名与以斜杠结尾的文件名添加到从模式规则的前提条件模式和文件名生成的先决条件文件名之后,与目标模式进行比较。这些目录仅在查找要使用的隐式规则时被忽略,而在应用该规则时则不会被忽略。因此,‘e%t’ 匹配文件名 src/eat,其中 ‘src/a’ 是 stem。将前提条件转换为文件名时,从 stem 中添加的目录位于前面,而 stem 的其余部分替换为 ‘%’。具有前提条件模式 ‘c%r’ 的 stem ‘src/a’ 给出文件名 src/car。
只有在存在匹配文件名的目标模式并且该规则中的所有前提条件都存在或可以构建时,才能使用模式规则构建给定文件。您编写的规则优先于内建规则。但请注意,始终优先于必须通过链接其他隐式规则制作的前提条件的规则的规则(例如,没有前提条件或其前提条件已存在或已被提及的规则)。
可能存在多个模式规则满足这些条件。在这种情况下,make 将选择具有最短 stem 的规则(即最具体匹配的模式)。如果有多个模式规则具有最短的 stem,则 make 将选择在 Makefile 中找到的第一个规则。
这个算法的结果是更具体的规则优先于更通用的规则;例如:
%.o: %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
%.o : %.f
$(COMPILE.F) $(OUTPUT_OPTION) $<
lib/%.o: lib/%.c
$(CC) -fPIC -c $(CFLAGS) $(CPPFLAGS) $< -o $@
给定这些规则,并要求构建 bar.o,其中 bar.c 和 bar.f 都存在,make 将选择第一条规则并将 bar.c 编译为 bar.o。在 bar.c 不存在的情况下,make 将选择第二条规则并将 bar.f 编译为 bar.o。
如果 make 被要求构建 lib/bar.o,且 lib/bar.c 和 lib/bar.f 都存在,则将选择第三条规则,因为此规则的 stem(‘bar’)较短,优先于第一条规则的 stem(‘lib/bar’)。如果 lib/bar.c 不存在,则第三条规则不符合条件,将使用第二条规则,即使 stem 更长。
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页
匹配任意模式规则
在模式规则的目标仅为 ‘%’ 时,它匹配任何文件名。我们将这些规则称为匹配任何规则。它们非常有用,但 make 需要考虑它们可能适用于每个文件名,无论是作为目标还是作为先决条件,这可能会花费很多时间。
假设 Makefile 提到了 foo.c。对于这个目标,make 可以考虑通过链接一个对象文件 foo.c.o 进行构建,或者通过一步完成来自 foo.c.c 的 C 编译和链接,或者通过来自 foo.c.p 的 Pascal 编译和链接等等许多可能性。
我们知道这些可能性是荒谬的,因为foo.c是一个C源文件,而不是可执行文件。如果 make 确实考虑了这些可能性,它最终会拒绝它们,因为诸如 foo.c.o 和 foo.c.p 这样的文件是不存在的。但是这些可能性非常多,如果 make 必须考虑它们,它将运行得非常慢。
为了提高速度,我们对 make 考虑匹配任何规则的方式施加了各种约束。有两种不同的约束可以应用,每次定义匹配任何规则时,您必须为该规则选择其中一种。
一种选择是通过用双冒号定义将匹配任何规则标记为终端。当规则是终端时,它仅在其先决条件实际存在时适用。其他可以通过其他隐式规则制作的先决条件是不够的。换句话说,终端规则不允许进一步的链接。
例如,从 RCS 和 SCCS 文件中提取源文件的内置隐式规则是终端的;因此,如果文件 foo.c,v 不存在,make 将不会尝试从 foo.c,v.o 或从 RCS/SCCS/s.foo.c,v 制作它。RCS 和 SCCS 文件通常是最终源文件,不应该从任何其他文件重新制作;因此,make 可以通过不寻找重新制作它们的方法来节省时间。
如果您未将匹配任何规则标记为终端,则它是非终端的。非终端的匹配任何规则不能应用于隐式规则的先决条件,也不能应用于指示特定类型数据的文件名。如果某个文件名匹配某个非匹配规则的目标,那么文件名表示某种特定类型的数据。
例如,文件名 foo.c 匹配模式规则 ‘%.c : %.y’ 的目标(运行 Yacc 的规则)。无论这条规则是否实际适用(仅当存在文件 foo.y 时才会发生),其目标匹配的事实足以阻止考虑用于文件 foo.c 的任何非终端的匹配规则。因此,make 不会尝试从 foo.c.o、foo.c.c、foo.c.p 等制作 foo.c 作为可执行文件等等。
这个约束的动机在于,非终端的匹配任何规则用于制作包含特定类型数据的文件(例如可执行文件),而带有已识别后缀的文件名表示某种其他特定类型的数据(例如 C 源文件)。
特殊的内置虚拟模式规则仅用于识别某些文件名,以便不考虑非终端的匹配规则。这些虚拟规则没有先决条件和配方,并且在所有其他方面都被忽略。例如,内置的隐式规则
%.p :
存在是为了确保 Pascal 源文件(如 foo.p)匹配特定的目标模式,从而防止浪费时间寻找 foo.p.o 或 foo.p.c。
类似 ‘foo.p’ 这样的虚拟模式规则是为使用后缀规则列出的每个后缀而制作的。
在使用模式规则时,了解它们是终端规则还是非终端规则很重要,因为这会影响 make 的行为。终端规则是不会进一步链接其他隐式规则的规则,而非终端规则可以用于先决条件和文件名的特定类型。
在编写复杂的 Makefile 时,这些规则的理解可以帮助您确保 make 的行为符合预期,避免意外的行为。
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页
取消隐式规则
你可以通过定义一个新的模式规则,该规则具有相同的目标和先决条件,但具有不同的配方,来覆盖一个内置的隐式规则(或您自己定义的规则)。当定义了新规则时,内置规则将被替换。新规则在隐式规则序列中的位置由您编写新规则的位置决定。
你可以通过定义一个目标和先决条件相同但没有配方的模式规则来取消内置的隐式规则。例如,以下内容将取消运行汇编器的规则:
%.o : %.s
这样的规则没有特定的配方,因此 make 将不再使用默认的汇编规则。这在某些情况下可能很有用,例如,如果你希望使用自定义的方式进行汇编而不是默认的方式。
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页
定义最后手段默认规则
你可以通过编写一个没有先决条件的终极匹配任何模式规则来定义最后的默认隐式规则。这与任何其他模式规则一样;唯一特殊的地方在于它将匹配任何目标。因此,这样规则的配方将用于所有没有自己的配方以及没有其他隐式规则适用的目标和先决条件。
例如,在测试 Makefile 时,您可能不关心源文件是否包含实际数据,只需它们存在。然后,您可以执行以下操作:
%::
touch $@
以自动创建所需的所有源文件(作为先决条件)。
您还可以定义一个用于没有任何规则的目标的配方,即使这些目标没有指定配方。通过为目标 .DEFAULT
编写规则来实现这一点。这样规则的配方将用于所有未出现在任何显式规则中的先决条件,且没有适用任何隐式规则。自然地,如果没有编写 .DEFAULT
规则,则不存在该规则。
如果使用没有配方或先决条件的 .DEFAULT
:
.DEFAULT:
则清除先前存储在 .DEFAULT
中的配方。然后,make 就好像您根本没有定义过 .DEFAULT
一样。
如果您不希望一个目标从匹配任何模式规则或 .DEFAULT
中获取配方,但又不希望为该目标运行任何配方,您可以为其提供一个空配方。
您可以使用最后的默认规则来覆盖另一个 Makefile 的一部分。
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页
老式后缀规则
后缀规则是定义 make 隐式规则的老式方法。由于模式规则更通用且更清晰,后缀规则已过时。它们在 GNU make 中得到支持,以兼容旧的 Makefile。后缀规则分为双后缀和单后缀两种。
双后缀规则由一对后缀定义:目标后缀和源后缀。它匹配任何以目标后缀结尾的文件。相应的隐式先决条件是通过在文件名中用源后缀替换目标后缀来生成的。一个双后缀规则 ‘.c.o’(其目标和源后缀分别是 ‘.o’ 和 ‘.c’)等效于模式规则 ‘%.o:%.c’。
单后缀规则由单个后缀定义,即源后缀。它匹配任何文件名,相应的隐式先决条件名称是通过附加源后缀生成的。源后缀是 ‘.c’ 的单后缀规则等效于模式规则 ‘%:%.c’。
通过比较每个规则的目标与已定义的已知后缀列表,make 识别后缀规则的定义。当 make 看到目标是已知后缀时,将此规则视为单后缀规则。当 make 看到目标是两个已知后缀连接在一起时,将此规则视为双后缀规则。
例如,‘.c’ 和 ‘.o’ 都在已知后缀的默认列表上。因此,如果您定义一个目标为 ‘.c.o’ 的规则,make 将其视为双后缀规则,源后缀为 ‘.c’,目标后缀为 ‘.o’。以下是定义编译 C 源文件规则的老式方法:
.c.o:
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
后缀规则不能有自己的先决条件。如果它们有任何先决条件,它们将被视为具有奇怪名称的普通文件,而不是后缀规则。因此,规则:
.c.o: foo.h
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
告诉如何从先决条件文件 foo.h 制作文件 ‘.c.o’,与模式规则:
%.o: %.c foo.h
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
不同,后者告诉如何从 ‘.c’ 文件制作 ‘.o’ 文件,并使所有使用此模式规则的 ‘.o’ 文件都依赖于 foo.h。
没有配方的后缀规则也是没有意义的。它们不像没有配方的模式规则那样删除先前的规则。它们只是将后缀或连接的一对后缀作为目标输入到数据库中。
已知的后缀只是特殊目标 .SUFFIXES 的先决条件列表的名称。您可以通过为 .SUFFIXES 编写规则并添加更多的先决条件来添加自己的后缀,如下所示:
.SUFFIXES: .hack .win
这样将 ‘.hack’ 和 ‘.win’ 添加到后缀列表的末尾。
如果要消除默认已知的后缀而不仅仅是添加到它们,可以为 .SUFFIXES 编写一个没有先决条件的规则。根据特殊规定,这将消除 .SUFFIXES 的所有现有先决条件。然后,您可以编写另一条规则以添加所需的后缀。例如,
.SUFFIXES: # 删除默认后缀
.SUFFIXES: .c .o .h # 定义我们的后缀列表
使用 ‘-r’ 或 ‘–no-builtin-rules’ 标志将导致默认后缀列表为空。
变量 SUFFIXES 在 make 读取任何 Makefile 之前被定义为默认的后缀列表。您可以通过为特殊目标 .SUFFIXES 编写规则更改后缀列表,但这不会更改此变量。
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页
隐式规则搜索算法
这是 make 用于搜索目标 t 的隐式规则的过程。对于每个没有配方的双冒号规则,每个普通规则的目标(这些规则都没有配方),以及不是任何规则目标的每个先决条件,都会按照此过程进行。对于来自隐式规则的先决条件,也会递归地按照此过程进行,以寻找规则链。
此算法中没有提到后缀规则,因为一旦读取 Makefile,后缀规则就会被转换为等效的模式规则。
对于形式为 ‘archive(member)’ 的归档成员目标,以下算法将运行两次,首先使用整个目标名 t,然后如果第一次运行未找到规则,则使用‘(member)’ 作为目标 t。
- 将 t 拆分为目录部分 d 和其余部分 n。例如,如果 t 是 ‘src/foo.o’,那么 d 是 ‘src/’,n 是 ‘foo.o’。
- 制作一个列表,其中包含其目标匹配 t 或 n 的所有模式规则。如果目标模式包含斜杠,则与 t 匹配;否则与 n 匹配。
- 如果列表中的任何规则不是匹配任意规则,或者 t 是隐式规则的先决条件,则从列表中删除所有非终端匹配任意规则。
- 从列表中删除所有没有配方的规则。
- 对于列表中的每个模式规则:
- 找到 stem s,即目标模式中 ‘%’ 所匹配的 t 或 n 的非空部分。
- 通过将 ‘%’ 替换为 s 计算先决条件名称;如果目标模式不包含斜杠,则在每个先决条件名称的前面添加 d。
- 测试所有先决条件是否存在或应该存在(如果文件名在 Makefile 中作为目标或目标 T 的显式先决条件被提及,则我们说它应该存在)。
- 如果所有先决条件存在或应该存在,或者没有先决条件,则应用此规则。
- 如果到目前为止尚未找到模式规则,请更加努力。对于列表中的每个模式规则:
- 如果规则是终端的,请忽略它并继续下一个规则。
- 如上所述计算先决条件名称。
- 测试所有先决条件是否存在或应该存在。
- 对于每个不存在的先决条件,递归地按照此算法进行,以查看是否可以通过隐式规则制作先决条件。
- 如果所有先决条件存在、应该存在或可以通过隐式规则制作,则应用此规则。
- 如果尚未找到模式规则,则使用修改过的“应该存在”的定义再次尝试步骤 5 和步骤 6:如果文件名在任何目标的 Makefile 中作为目标或显式先决条件提及,则它应该存在。此检查仅出于与旧版 GNU Make 的向后兼容性的目的而存在:我们不建议依赖于此。
- 如果没有隐式规则适用,则适用 .DEFAULT 的规则(如果有的话)。在这种情况下,将给 t 赋予与 .DEFAULT 相同的配方。否则,t 没有配方。
- 一旦找到适用的规则,对于规则的每个目标模式,除了与 t 或 n 匹配的模式之外,将模式中的 ‘%’ 替换为 s,将结果文件名存储,直到执行重新制作目标文件 t 的配方。执行配方后,将这些存储的文件名输入到数据库中,并标记为已更新并具有与文件 t 相同的更新状态。
- 当执行模式规则的配方时,自动变量将根据目标和先决条件进行设置。
下一篇:练习11 Makefile之更新存档,上一篇:练习9 Makefile之运行参数,目录|首页