7. GNU Automake 介绍

Automake 主要目标是生成一个遵从 GNU Makefile 标准的 ‘Makefile.in’ 文件。同时,它尝试减少无聊的重复工作和模板文件。此外,它可以帮助编写者实现大多数维护者没有耐心手工实现的 ‘Makefile’ 的功能(如自动解决依赖性问题)。 它可以通过一些好的方法解决供应商的产生的难于解决的问题。

Automake 另一个目标是使自由软件很好地工作,特别是 GNU 工具。例如,Automake 支持基于 Dejagnu 的测试组件。

你可能不会在意 GNU 代码标准。这样也没问题。你仍可能感受到 Automake 的便利,你会发现遵从 GNU 标准的功能在大多数时候对你的帮助要比阻碍多。

Automake 提供了五大主要功能和无数小的功能。最基础的功能包括:

1. 构建

2. 检测

3. 清理

4. 安装和卸载

5. 发布

这一章包括了前三个功能,其它的将在后续章节中介绍。在我们学习之前,要花点时间讲一下 Automake 的能用原则。

7.1 能用 Automake 原则

Automake 通过 ‘configure’ 简单地把 ‘Makefile.am’ 转成GNU标准的 ‘Makefile.in’。每个 ‘Makefile.am’ 是遵照 make 语法编写的,Automake 基于这些特定的宏和目标名称生成代码。

有一些 Automake 规则和 make 语法有些细微差异:

  • 在输出过程中 make 注释不会被忽略掉,Automake 注释用 ‘##’,会被忽略。
  • Automake 支持 include 指令。 在生成 ‘Makefile.in’ 时这些指令不会被传递,它们在 automake 中处理 — 这样包含的文件它们是在 ‘Makefile.am’ 包含的一样。这个特性可以在对每个 ‘Makefile.am’ 文件增加一个模板时使用。 ‘$(top_srcdir)’ 指示要在最顶层目录寻找文件;如果是相对路径或是以 ‘$(srcdir)’ 开头则会在当前目录寻找文件。例如,下面是怎么使用模板文件 ‘config/Make-rules’ 的代码(假设 ‘config’ 是顶层目录):include $(top_srcdir)/config/Make-rules
  • Automake 不直接传递到 ‘Makefile.in’ 的条件语句。这个功能将会在第19章讨论。
  • Automake 支持用 ‘+=’ 对宏赋值。Automake 会把它成 ‘Makefile.in’ 中转化成普通的 ‘=’ 赋值 。

所有的宏和对象,包括那些 Automake 不知如何处理的,都会传递到生成的 ‘Makefile.in’ 文件中。这是一种有力的扩展机制。Automake 有时会在内部定义宏和对象。如果它们在 ‘Makefile.am’ 先被定义过, ‘Makefile.am’ 的优先级更高。这个功能用简单的方法裁剪输出文件的特定部分。

覆盖没有文档化(被 Automake 直接输出)的生成代码是错误的,它可能使 Automake 不能正常工作。

Automake 也会扫描 ‘configure.in’。有时它用发现的信息生成额外的代码,有时会进行额外的错误检测。Automake 会把 AC_SUBST 转化成为 ‘Makefile’ 变量。这样带来许多便利:这不只意味着你可以不用另外的工作就可以在 ‘Makefile.am’ 中引用这些宏,由于 Automake 读取 ‘configure.in’ 先于 ‘Makefile.am’,也意味着特殊的变量可以在’configure.in’ 中一次性定义。

7.2 原型介绍

Automake 理解的每种对象都有一个特殊的相关联的根变量。这个根变量叫做原型(primary)。许多 ‘Makefile.am’ 中的实际构造成带有一个原型作为后缀的形式。

例如,可执行的脚本与原型 SCRIPTS 关联。是一个例子,它来自 ‘bindir’ 文件:

bin_SCRIPTS = magic-script

(注意 ‘bin_’ 前缀,我们稍后讨论它)

一个有原型的变量的内容被认为是 ‘Makefile’ 的一个目标。例如,在上面的例子中,可能简单地把它当做一个目标用 sed 生成 ‘magic-script’:

bin_SCRIPTS = magic-script

magic-script: magic-script.in

sed -e ’s/whatever//’ < $(srcdir)/magic-script.in > magic-script
chmod +x magic-script

7.3 简单的原型

本节讨论简单的通用原型,下一节会讨论复杂一些的。

DATA

这是最容易理解的原型。这种类型的宏列出了已安装的文件。包括源文件和生成的文件。

HEADERS

这种类型的宏列出头文件。它不是 DATA 的一部分是因为它允许额外的错误检测。

SCRIPTS

它用于可执行的脚本(交互程序)。它和 DATA 不同是因为它们安装需要不同的权限,另外它们有不同形式的文件名(如 configure 加 ‘–program-transform-name’ 参数)。脚本也和程序不同,因为程序可以进行精简,脚本不可以。

MANS

它列出所有 man 页。安装 man 要比你想像中复杂得多。一个开发人员可能在源文件树中把 man 页命名为 ‘foo.man’ ,在安装中改名为它真正的名字 (’foo.1′)。另外一个开发人员可能用数字后缀命名,在安装时使用同样的名字。有时在数字后缀后面还有一个字母 (如 ‘quux.3n’);在确定最终安装路径之前这些字母必须去掉(这个文件必须安装到 ‘$(man3dir)’)。Automake 支持所有这一类的操作:

  • 如果已经有数字后缀可以用 man_MANS :

    man_MANS = foo.1 bar.2 quux.3n

  • man1_MANS, man2_MANS 等可以在安装时强制重命名。如果后缀是正解的数字则不会重命名。例如:man1_MANS = foo.man
    man3_MANS = quux.3n

    这里 ‘foo.man’ 安装时重命名为 ‘foo.1′ ,’quux.3n’ 在安装时名字不会变。

TEXINFOS

传统 GNU 使用 Texinfo 文档格式,不是 man 。 Automake 完全支持 Texinfo,包括如版本和安装信息等附加功能。在它里我们不深入学习它,除非有用到它。查阅 Automake 参考手册获得更多信息。

Automake 支持很多很少使用的原型,如 JAVA 、LISP 和 PYTHON。查阅 Automake 参考手册获得更多信息。

7.4 程序和库

前面提到的原型相对比较简单。现在我们讨论一些比较复杂的,就是说用来构建程序和库的。构建程序和库要比构建脚本复杂,所以这些原型也相对复杂(经常根本不需要)。

PROGRAMS 用于程序, LIBRARIES 用于库,LTLIBRARIES 用于 Libtool 库(see section 10)。下面是一个很小的例子:

bin_PROGRAMS = doit

这条指令构建程序 doit 并把它安装到 bindir 目录。第一次 make 编译 ‘doit.c’ 产生 ‘doit.o’,然后连接 ‘doit.o’ 创建 ‘doit’。

当然,如果源文件不只一个,你会想要把它们都列出来。用 SOURCES 来完成这个工作。每个程序和库都有一个关联的后缀规范化的变量。规范的名字是把非字母和数字的字符换成下划线的名字。例如,’quux’ 的规范名字是 ‘quux’,但 ‘install-info’ 的规范名字是 ‘install_info’。之所以这样命名是因为这样才符合 make 语法,并且像所有的宏一样,Automake 把这些定义传递到 ‘Makefile.in’。

如果 ‘doit’ 由 ‘main.c’ 和 ‘doit.c’ 生成,我们应该写:

bin_PROGRAMS = doit

doit_SOURCES = doit.c main.c

库文件用同样的方法。在 zlib 包里我们会生成一个 ‘libzlib.a’ 库文件,代码如下:

lib_LIBRARIES = libzlib.a

libzlib_a_SOURCES = adler32.c compress.c crc32.c deflate.c deflate.h /
gzio.c infblock.c infblock.h infcodes.c infcodes.h inffast.c inffast.h /
inffixed.h inflate.c inftrees.c inftrees.h infutil.c infutil.h trees.c /
trees.h uncompr.c zconf.h zlib.h zutil.c zutil.h

同样可以用于 libtool 库。例如,假设我们要构建一个 ‘libzlib.la’:

lib_LTLIBRARIES = libzlib.la

libzlib_la_SOURCES = adler32.c compress.c crc32.c deflate.c deflate.h /
gzio.c infblock.c infblock.h infcodes.c infcodes.h inffast.c inffast.h /
inffixed.h inflate.c inftrees.c inftrees.h infutil.c infutil.h trees.c /
trees.h uncompr.c zconf.h zlib.h zutil.c zutil.h

可以看到,用Automake 生成共享库 Libtool 和构建静态库一样简单。

在上面的例子中,在 SOURCES 变量里列出了头文件。 这些将会被忽略 (除了 dist (1)),但对构建 ‘Makefile.am’ 有清理器的作用(如果不安装库文件会更短)。

注意 ‘configure’ 不能用 SOURCES 代替变量。 Automake 需要知道可以编译到程序里的静态文件列表。有许多方法条件编译文件,如用 Automake 条件或者 LDADD 变量。

在一些版本的 Automake 会静态文件列表自动跟踪依赖性。一般的法则是每一个会被编译的源文件应该在一些 SOURCES 变量里列出。如果源文件是条件编译的, 可以在 EXTRA 变量里列出。 For instance, 例如,假设在这个例子里 ‘@FOO_OBJ@’ 是 ‘configure’ 用于生成 ‘foo.o’ 的变量:

bin_PROGRAMS = foo

foo_SOURCES = main.c
foo_LDADD = @FOO_OBJ@
foo_DEPENDENCIES = @FOO_OBJ@
EXTRA_foo_SOURCES = foo.c

在这里,’EXTRA_foo_SOURCES’ 用于列出条件编译的源文件;它告诉 Automake 这些文件存在,尽管它们的存在性不能自动推测出来。

在上面的例子里,注意 ‘foo_LDADD’ 的用法。这个宏列出生成 foo 程序需要的其它 object 文件和库文件。每个程序或都有一些这样可以自定义连接步骤的宏;这里我们列出最常用的一些:

‘_DEPENDENCIES’

要加在程序依赖列表里的额外的文件。如果没有定义,它会由宏 ‘_LDADD’ 自动计算出来。

‘_LDADD’

传递到连接器的另外的 object 文件。它只用于程序和共享库。

‘_LDFLAGS’

传递到连接器的状态标志。它独立于 ‘_LDADD’,使之可以自动计算 ‘_DEPENDENCIES’。

‘_LIBADD’

像 ‘_LDADD’ 一样,但应用于静态库和非程序。

你不需要定义这里的任何一个宏。

7.5 常见问题

经验显示人们在自己的项目上初用 automake 会遇到许多共性的问题。

用户经常想构建一个源文件在子目录里的库(或程序,但由于一些原因库更频繁):

lib_LIBRARIES = libsub.a

libsub_a_SOURCES = subdir1/something.c …

如果你在 Automake 1.4 里运行,会得到以下错误:

$ automake

automake: Makefile.am: not supported: source file subdir1/something.c is in subdirectory

For libraries, 对于库,解决这个问题可以用 libtool。对于程序,没有简单的解决办法。因些很多人选择重构它们的包。

Automake 的下一个主要版本解决了这个问题。

另一个主要问题来自设置编译状态标志。大多数的规则有状态标志,如,编译 C 代码会自动使用 ‘CFLAGS’。然而,这些变量被认为是用户变量,在 ‘Makefile.am’ 设置它们是不安全的,因为用户希望覆盖它们。

为了解决这个问题,对于每个标志变量 Automake 引进了一个可以在 ‘Makefile.am’ 里设置的 ‘AM_’ 版本。如我们可以像这样设置 C 和 C++ 的编译:

AM_CFLAGS = -DFOR_C

AM_CXXFLAGS = -DFOR_CXX

最后,人员经常询问怎样用不要方法编译同一个源代码文件。例如,可以用 ‘-D’ 选项分别使 Emacs 的 ‘etags.c’ 生成 etags 或 ctags。

Automake 1.4 只能通过编写自己的编译规则完成,像这样:

bin_PROGRAMS = etags ctags

etags_SOURCES = etags.c
ctags_SOURCES =
ctags_LDADD = ctags.o

etags.o: etags.c

$(CC) $(CFLAGS) -DETAGS …

ctags.o: etags.c

$(CC) $(CFLAGS) -DCTAGS …

这对于大的程序难于维护并且乏味。 Automake 1.5 会支持一个更自然的方法:

bin_PROGRAMS = etags ctags

etags_SOURCES = etags.c
etags_CFLAGS = -DETAGS
ctags_SOURCES = etags.c
ctags_CFLAGS = -DCTAGS

7.6 多目录

到现在为止,我们仍然看到的是单目录的项目。Automake 也可以处理多目录项目。’SUBDIRS’ 变量用于列出需要构建的子目录。这里是一个来自 Automake 自己的例子:

SUBDIRS = . m4 tests

Automake 不需要静态地知道子目录的列表,所以没有 ‘EXTRA_SUBDIRS’ 变量。你可能会以为 Automake 会用 ‘SUBDIRS’ 变量查看 ‘Makefile.am 要扫描什么,但事实上它查看 ‘configure.in’ 了解这些信息。 This means that, 这意味着,如果你有一个可选构建的子目录,你依然可以无条件地在 AC_OUTPUT 里列出它,让它作为替补(或者不是)。

子目录总是按照它们列出的顺序构建。但清理规则(如维护者清理)总是逆序运行。这种奇怪的顺序是因为删除一个文件先于删除依赖它的文件是错误的。

可以把 ‘.’ 放到 ‘SUBDIRS’ 里以控制当前目录。 在上面的例子里,’.’ 里的目标会先于子目录里的目标构建。如果 ‘SUBDIRS’ 里没有 ‘.’,就会在所以子目录构建完成后构建。

7.7 检测

Automake 也支持对程序进行简单的检测。

最简单的形式是用 ‘TESTS’ 变量。这个变量保存了在进行 make 检测时运行的检测。每个检测都会构建(如果需要)并运行。每个检测输出一行检测成功与否的信息。失味着退出并返回一个非零值。返回 ‘77′ (2)说明检测应该忽略。检测会输出成功和失败的检测的总数。

Automake 也支持 xfail 的概念,它是希望失败的检测。在跟踪一个已知错误但没准备好立刻修复它时这很有用。希望失败的检测在 ‘TESTS’ 和 ‘XFAIL_TESTS’ 里都要列出。

特殊的前缀 ‘check’ 用在原型里表示它只在进行 make 检测时构建。例如,这里是怎样构建一个只能在检测时使用的的程序。

check_PROGRAMS = test-program

test_program_SOURCES = …

Automake 支持使用 DejaGNU,GNU 检测框架。可以通过 ‘dejagnu’ 选项打开 DejaGNU 支持:

AUTOMAKE_OPTIONS = dejagnu

生成的 ‘Makefile.in’ 会适当包括运行检测程序。