归档文件是把称为成员的有名子文件汇集存放在一起的文件。归档文件由 ar 这个程序进行管理,主要用作链接时的子程序库。
归档文件中的各个成员可以用作 make 的目标或前置条件 (prerequisite)。要指定归档文件 archive 中名为 member 的成员,写法如下:
archive(member)
这种写法只能用在目标和前置条件中,不能用在命令 (recipe) 里!因为你在命令里会用到的大多数程序都不支持这种语法,无法直接对归档成员进行操作。能够做到这一点的,只有 ar 以及其他专门为操作归档而设计的程序。因此,用来更新归档成员目标的有效命令多半要使用 ar。例如,下面这条规则的含义是:复制文件 hack.o,在归档 foolib 中创建一个成员 hack.o:
foolib(hack.o) : hack.o
ar cr foolib hack.o
实际上,几乎所有的归档成员目标都正是以这种方式更新的,所以已经准备好了一条能自动完成此事的隐含规则。[请注意] 如果归档文件还不存在,就必须给 ar 加上 ‘c’ 标志。
要指定同一个归档中的多个成员,可以把所有成员名一起写在括号里。例如,像下面这样写:
foolib(hack.o kludge.o)
这与下面这样写的含义相同:
foolib(hack.o) foolib(kludge.o)
在引用归档成员时,也可以使用 shell 风格的通配符。请参阅在文件名中使用通配符。例如 ‘foolib(*.o)’ 会展开为 foolib 归档中所有名字以 ‘.o’ 结尾的现有成员,比如 ‘foolib(hack.o) foolib(kludge.o)’。
请回想一下,形如 a(m) 的目标表示归档文件 a 中名为 m 的成员。
当 make 为这样的目标查找隐含规则时,作为一种特殊机制,它不仅会考虑匹配实际目标 a(m) 的规则,也会把匹配 (m) 的隐含规则纳入对象。
正因如此,有一条目标为 (%) 的特殊规则会匹配上。这条规则通过把文件 m 复制到归档中来更新目标 a(m)。例如,它会把文件 bar.o 作为名为 bar.o 的成员复制到归档 foo.a 中,从而更新归档成员目标 foo.a(bar.o)。
当这条规则与其他规则连锁时,效果会非常强大。因此,只要存在一个名为 bar.c 的文件,即使完全没有 makefile,只需键入 ‘make "foo.a(bar.o)"’(引号是必需的,用来防止 ‘(’ 和 ‘)’ 被 shell 解释为特殊含义),就会执行下面的命令:
cc -c bar.c -o bar.o ar r foo.a bar.o rm -f bar.o
这里 make 把文件 bar.o 设想为一个中间文件。请参阅隐含规则的连锁。
像这样的隐含规则是使用自动变量 ‘$%’ 来编写的。请参阅自动变量。
归档中的归档成员名不能包含目录名,但在 makefile 中假装它能包含目录名有时会很方便。如果你写出归档成员目标 foo.a(dir/file.o),make 会用下面的命令进行自动更新:
ar r foo.a dir/file.o
这样做的效果是把文件 dir/file.o 复制为一个名为 file.o 的成员。与这种用法相关联,自动变量 %D 和 %F 有时会很有用。
用作库的归档文件通常包含一个名为 __.SYMDEF 的特殊成员。它保存着由其他所有成员定义的外部符号名的目录。在你更新了其他成员之后,需要同时更新 __.SYMDEF,以便它能正确地概括其他成员。这是通过运行 ranlib 程序来完成的:
ranlib archivefile
通常,你会把这条命令写在归档文件的规则中,并把归档文件的所有成员作为该规则的前置条件。例如,像下面这样写:
libfoo.a: libfoo.a(x.o y.o …)
ranlib libfoo.a
这样做的效果是更新归档成员 x.o、y.o 等,接着通过运行 ranlib 更新符号目录成员 __.SYMDEF。这里没有给出更新这些成员的规则;你多半可以把它们省略掉,交给上一节所介绍的、把文件复制到归档中的隐含规则来处理。
另外,使用 GNU 的 ar 程序时,这是不必要的,因为 GNU 的 ar 会自动更新 __.SYMDEF 成员。
用于更新归档的内置规则与并行构建相处得并不好。这些规则(它们是 POSIX 标准所要求的)会在每个目标文件(object)被编译时就把它添加到归档中。当启用并行构建时,这会导致多个 ar 命令试图同时更新同一个归档,而这是不受支持的。
如果你想对归档使用并行构建,可以通过在 makefile 中添加下面这些行来覆盖 (override) 默认规则:
(%) : % ; %.a : ; $(AR) $(ARFLAGS) $@ $?
第一行把更新归档内各个 object 的规则改为什么都不做。第二行把构建归档的默认规则改为用一条命令更新所有过时的 object($?)。
当然,你仍然需要使用归档语法来声明库的前置条件:
libfoo.a: libfoo.a(x.o y.o …)
如果你更愿意编写显式规则,可以这样写:
libfoo.a: libfoo.a(x.o y.o …)
$(AR) $(ARFLAGS) $@ $?
你也可以编写一种特殊的后缀规则来处理归档文件。关于后缀规则的完整说明,请参阅老式的后缀规则。归档的后缀规则在 GNU make 中已经过时,因为面向归档的模式规则是更为通用的机制(请参阅归档成员目标的隐含规则)。不过,为了与其他 make 兼容,它们被保留了下来。
要编写归档的后缀规则,只需以 ‘.a’(归档文件常用的后缀)作为目标后缀来编写一条后缀规则即可。例如,下面是从 C 源文件更新库归档的老式后缀规则:
.c.a:
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
$(AR) r $@ $*.o
$(RM) $*.o
它的行为与编写下面这条模式规则完全相同:
(%.o): %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
$(AR) r $@ $*.o
$(RM) $*.o
实际上,当 make 看到一条以 ‘.a’ 作为目标后缀的后缀规则时,所做的正是这件事。任何形如 ‘.x.a’ 的双后缀规则都会被转换为目标模式为 ‘(%.o)’、前置条件模式为 ‘%.x’ 的模式规则。
由于你可能想把 ‘.a’ 用作其他某种文件的后缀,make 也会按通常方式把归档的后缀规则转换为模式规则(请参阅老式的后缀规则)。因此,双后缀规则 ‘.x.a’ 会生成两条模式规则:‘(%.o): %.x’ 和 ‘%.a: %.x’。