一位物理学家,一位工程师,及一位计算机科学家在讨论上帝的本质。“肯定是一位物理学家”,物理学家说,“因为在创世的早期,上帝创造了光;而你知道的Maxwell方程,电磁波的二相性本质,相对论的结论……”“一位工程师”,工程师说到,“因为在创造光之前,上帝把混沌分为大地和水;这需要一个出色的工程师来处理这么多的泥浆,并有序地从液体中分离出固体……”计算机科学家嚷道:“而那些混乱呢,你认为它来自哪里呢,嗯?”
——无名氏
Autoconf是一个工具,用于产生可以自动配置软件源代码包来适应各种Posix系统的shell脚本。由Autoconf产生的配置脚本,在运行时不依赖于Autoconf,因此它们的用户不需要拥有Autoconf。
由Autoconf产生的配置脚本,在运行时不要求用户干预;通常它们甚至不需要一个指出系统类型的参数。相反,它们逐个地测试每个该软件包可能需要的特性是否存在。(在每个检查之前,它们打印出一行消息,声明它们要检查什么,这样用户在等待脚本结束时不至于太无聊)。作为结果,它们能很好地处理更常见的Posix变种的定制,或混合系统。不需要维护文件列出每个Posix变体所支持的特性。
对于每个与Autoconf一起使用的软件包,Autoconf从一个列出这个软件包需要或使用的特性的模板文件,构造出一个配置脚本。在写出识别及响应一个系统特性的脚本代码后, Autoconf允许使用(或需要)这个特性的软件包共享它。如果随后发现该脚本代码出于某些原因需要改动,只需要在一个地方修改它;所有的配置脚本可以被自动地重新生成,使用更新后的代码。
那些不理解Autoconf的人被谴责拙劣地重新发明它。Autoconf的主要目的是使得用户的生活更轻松些;使得维护者的生活更轻松仅是次要的目的。换而言之,主要目标不是为软件包的维护者自动化‘configure’的生成(虽然在这方面的补丁是受欢迎的,因为包的维护者构成了Autoconf的用户基础);相反,其目标是使得对于每个由autoconf处理的软件包的终端用户,配置没有痛苦,可移植,并可预测。从这个意义来说,Autoconf在这个目标上是非常成功的——对Autoconf的绝大多数抱怨都是关于编写Autoconf输入的困难性,而不是作为结果的‘configure’的行为。就算不使用Autoconf的软件包通常也提供一个configure脚本,至于这些“本土”的脚本,最常见的抱怨是它们不能满足某个或多个GNU编码规范(GNU Coding Standards,参考《The GNUCoding Standards》中“Configuration”一节) ,而用户可以期望Autoconf产生的configure脚本满足这一点。
Metaconfig包在目的上与Autoconf类似,但它产生的脚本要求用户手动干预,当配置大的源代码树时相当不便。与Metaconfig脚本不同,如果在编写它们时遵循某些注意事项,Autoconf脚本可以支持交叉编译。
Autoconf不能解决涉及构建可移植软件包的所有问题——作为一个更完善的解决方案,它应该与其它GNU构建工具,如Automake及Libtool一同工作。这些工具执行诸如,构建一个可移植的、递归的、具有所有标准目标的makefile,链接共享库等等。更多信息,参考第二章【GNU构建系统】,3页。
Autoconf对在C程序中与#if一起使用的宏的名字有某些限制(参考B.3节【预处理符号索引】,页)。
Autoconf要求GNU M4为1.4.6或更新的版本来产生这些脚本。它使用了M4某些版本,包括GNU M41.3,所没有的特性。Autoconf使用GNU M4版本1.4.14或更新版本,能工作得更好,虽然这不做要求。
关于从版本1升级的信息,参考18.5节【Autoconf 1】,281页。关于Autoconf的开发史,参考第21章【历史】,309页。关于Autoconf的常问的问题,参考第20章【FAQ】,300页。
参考Autoconf的网页关于最新的信息,及邮件列表的细节,已知bug的链接等。
向Autoconf的邮件列表发送建议。过去的建议都被存档了。
向Autoconf Bugs邮件列表发送bug报告。过去的bug报告都被存档了。
如果可能,首先检查你的bug不是已经在当前开发版本里被解决了,并且它还没被报告过。确保包含了所有需要的信息及一个显示这个问题的简短的‘configure.ac’。
Autoconf的开发树通过git来访问;细节参考Autoconf Summary,或查看真正的代码库(repository)。匿名CVS访问也是可以的,更多细节参考‘README’。可以向Autoconf补丁邮件列表发送当前git版本相关的补丁进行审查,关于之前补丁的讨论都被存档;所有的委派(commit)被发布在只读的Autoconf提交邮件列表,这也是存档的。
因为其目标,Autoconf包本身仅包括了一组得到验证的、经常使用的宏。不过,如果你希望共享你的宏,或查找存在的宏,参考Autoconf Macro Archive,它由Peter Simons友情维护。
Autoconf解决了一个重要的问题——可靠地发现系统特定的构建(system-specificbuild)及运行时信息——但这只是开发可移植软件这块比萨饼中的一片。为此,GNU项目开发了一套整合的工具来完成由Autoconf开始的工作:GNU构建系统,其中最重要的部分是Autoconf,Automake,及Libtool。在本章里,我们介绍这些工具,向你指出更多信息的来源,并尝试说服你为你的软件使用整个GNU构建系统。
Make的无所不在意味着,makefile几乎就是发布软件自动构建规则仅有的可行方法,但这很快会遇到众多局限。它缺少对依赖性自动追踪的支持,在子目录中的递归构建(recursive build),可靠的时间戳(比如,对于网络文件系统),等等,这意味着开发者必须痛苦地(并且通常是不正确地)为每个项目推倒重来。可移植性不是小事,这归咎于许多系统上make的怪癖。凌驾在这所有之上的是,要求实现用户所期望的多种标准目标(make install,make distclean,make uninstall等)所需的人力。因为你使用Autoconf,你还需要在你的‘Makefile.in’中插入重复的代码来识别@CC@,@CFLAGS@,及其它由‘configure’提供的替代(substitution)。在这团混乱中,走来了Automake。
Automake允许你在一个‘Makefile.am’文件中,使用比一个普通的makefile中大为简化,且更为强大的语法指定你的构建需求,然后产生一个与Autoconf一起使用可移植的‘Makefile.in’。例如,构建及安装一个简单的“Helloworld”程序的‘Makefile.am’看起来可能像这样:
bin_PROGRAMS= hello
hello_SOURCES= hello.c
生成的‘Makefile.in’(~400行)自动地支持所有的标准目标,由Autoconf提供的替代(substitution),自动化依赖追踪,VPATH构建等等。Make构建这个hello程序,并且makeinstall在‘/usr/local/bin’(或任何给到configure的前缀,如果不是‘/usr/local’的话)中安装这个程序。
Automake的好处随着软件包的变大而增加(特别是带有子目录),不过就算对于小程序,带来的方便性及可移植性也可以是可观的。而这还不是全部……
GNU软件拥有当之无愧的好名声,它运行在许多不同类型的系统上。尽管我们的主要目的是为GNU系统编写软件,通过许多用户及开发者正在使用的系统,我们认识了他们。
Gnulib是通用GNU代码的一个集中地点,意为自由软件包共享。其组件(component)通常在源代码级别共享,而不是作为一个库,而得到构建、安装及链接。其思想是从Gnulib拷贝文件到你自己的源代码树。不存在tarball的发布;开发者应该从代码库获取代码模块(source module)。源代码可以在网上获取,受各种许可证的保护,主要是GNU GPL或GNU LGPL。
Gnulib模块通常包含C源代码以及配置这个源代码的Autoconf宏。例如,Gnulib的stdbool模块实现了一个‘stdbool.h’头文件,它基本上符合C99,即便在缺少‘stdbool.h’的旧式平台上。这个模块包含了一个用作替换头文件的源文件,连同一个在旧式系统上安排使用这个替换头文件的Autoconf宏。
通常,人们不仅希望仅构建程序,还有库,这样其他人也可以分享你的劳动成果。理想地,最好能产生共享库(动态链接),它可以被多个程序所使用,而不会在硬盘或内存中产生副本,并且可以独立于被链接程序来升级。不过生成可移植的共享库是一场噩梦——每个系统有自己不兼容的工具,编译标记,及神奇咒语。幸运的是,GNU提供一个解决方案:Libtool。
Libtool为你处理构建共享库的所有要求,并且在现在这个时间点上,似乎是仅有的处理可移植性的方法。它还处理其他许多头痛的问题,比如:Make与不同共享库后缀之间的相互作用,在共享库被超级用户安装之前可靠地链接之,提供一个一致的版本系统(因而一个库的不同版本可以在不破坏二进制相容性的前提下安装或升级)。虽然Libtool,类似Autoconf,可以不与Automake一起使用,配合Automake是最为简单的使用——即,一旦需要共享库就自动使用Libtool,并且你不需要知道它的语法。
习惯于在单个系统上用于小程序的make的简洁性的开发者,可能会被学习使用Automake及Autoconf的预期所吓倒。随着你的软件发布给越来越多的用户,很快你会发现你自己投入了很多精力,重新构建GNU构建工具提供的服务,并犯了他们以前也曾犯的错误(另外,因为你已经在学习Autoconf,Automake只是小菜一碟)。
在许多地方你可以找到关于GNU构建工具的更多信息。
——网络
Autoconf,Automake,Gnulib,及Libtool项目的首页
——Automake手册
参考《GNU Automake》中“Automake”节,关于Automake的更多信息。
——书
《GNU Autoconf, Automake and Libtool》描述了整个GNU构建环境。你也可以在线找到这本书。
Autoconf所产生的配置脚本,按惯例称为configure。当运行时,configure创建出多个文件,以合适的值替代其中的配置参数。Configure创建的文件有:
——一个或多个‘Makefile’文件,通常是每个子目录一个(参考4.8节【Makefile替代】,20页);
——可选地,一个C头文件,其名字是可配置的,包括#define指示(参考4.9节【配置头文件】,30页);
——一个名为‘config.status’的 shell脚本,在运行时,重新创建以上的文件(参考第17章【config.status调用】,265页);
——一个名为‘config.cache’的可选的shell脚本(当使用‘configure--config-cache’时构建),它保存了运行许多测试的结果(参考7.4.2节【缓存文件】,107页);
——一个名为‘config.log’的文件,它包含由编译器所产生的消息,如果configure发生错误,可以帮助调试。
使用Autoconf创建configure脚本,你需要编写一个Autoconf输入文件‘configure.ac’(或‘configure.in’),并为之运行autoconf。如果你编写了自己的特性检测来补充Autoconf自带的检测,你可能还需要编写名为‘aclocal.m4’及‘acsite.m4’的文件。如果你使用一个C头文件来包含#define指示,你可能还要运行autoheader,并且你可以与软件包一起发布生成的文件‘config.h.in’。
这里有一个图显示了在配置中使用的文件如何被产生。执行的程序以‘*’为后缀。可选的文件被包括在方括号内([])。autoconf及autoheader也会读入已安装的Autoconf宏文件(通过读‘autoconf.m4’)。
用于准备一个软件包发布的文件,在仅使用Autoconf时:
你的源文件 à[autoscan*] à [configure.scan] àconfigure.ac
configure.ac --.
| .------> autoconf*-----> configure
[aclocal.m4] --+---+
| ‘-----> [autoheader*]--> [config.h.in]
[acsite.m4] ---’
Makefile.in
另外,如果你使用了Automake,还有下面额外的制作:
[acinclude.m4] --.
|
[local macros] --+-->aclocal* --> aclocal.m4
|
configure.ac -----‘
configure.ac --.
+--> automake* -->Makefile.in
Makefile.am ---‘
在配置一个软件包所使用的文件:
.-------------> [config.cache]
configure* -------------+-------------> config.log
|
[config.h.in] --. v .--> [config.h] ---.
+--> config.status* --+ +--> make*
Makefile.in ---‘ ‘-->Makefile ---‘
为了给一个软件包产生一个configure脚本,构建一个名为‘configure.ac’的文件,它包含了对Autoconf宏的调用,这些宏测试你的软件包需要或可以使用的系统特性。Autoconf宏已经对许多特性进行检测;其描述,参考第5章【现存的测试】,36页。对于其它的大多数特性,你可以使用Autoconf模板宏(templatemacro)来生成定制的检测;有关信息,参考第6章【编写测试】,90页。对于特别棘手或特殊的特性,‘configure.ac’可能需要包含某些手工制作的shell命令;参考第11章【可移植Shell编程】,169页。程序autoscan可以作为编写‘configure.ac’的一个好的开端,(更多信息参考3.2节【autoscan调用】,9页)。
之前的Autoconf版本创立了名字‘configure.in’,它在一定程度上是模棱两可的(其扩展名没有描述需要处理这种文件的工具),并与‘config.h.in’引发了轻微的混淆,等等(那些‘.in’表示“将由configure处理”)。现在优先使用‘configure.ac’。
就像其它的计算机语言,为了正确地在Autoconf中对‘configure.ac’编程,你必须理解这个语言尝试处理什么问题,及它如何处理。
Autoconf所要处理的问题是,这个世界是一个烂摊子。毕竟,你使用Autoconf是为了使你的软件包可以在各种不同的系统上容易编译,其中某些系统特别地不友好。Autoconf 本身包含了这些差异的代价:configure必须运行在所有这些系统上,因此configure必须限制自身在这些系统特性的最小公分母中。
自然,你可能想到shell脚本;谁需要autoconf?一组正确编写的shell函数足以使得手动编写configure脚本变得容易。唉!很不幸,即便在2008年,虽然很少有shell没有函数支持,在使用它们时,却需要避免种种陷阱。同样,找到一个接受shell函数的Bourne shell不是一件小事情,即使在要移植的目标上几乎总有一个shell。
因此,真正需要的是某种编译器——autoconf,它接受一个Autoconf程序——‘configure.ac’,并把它转化为一个可移植的shell脚本——configure。
Autoconf如何执行这个任务呢?
有两种可能性:创建一个全新的语言,或扩展已有的语言。前一个选项是有吸引力的:所有的优化可以容易地实现在编译器中,并且可以在这个Autoconf程序上执行严格的检查(即拒绝任何不可移植的构造)。另一方面,你可以扩展一个现存的语言,比如sh(Bourne shell)语言。
Autoconf选择后者:它是sh上的一层。因而把autoconf实现为一个宏展开器极为方便:一个程序在文本输入上重复执行宏展开,把宏调用替换为宏定义体,并最终产生一个纯粹的sh脚本。很自然使用一个现存在通用宏语言,例如M4,并作为一组M4宏实现这个扩展,而不是实现一个专用的Autoconf宏展开器。
Autoconf语言与其它许多计算机语言的不同,在于它对真正代码的处理与纯文本相同。相比较而言,在C中,例如,数据及指令具有不同的语法地位,在Autoconf里它们的地位是严格相同的。因此,我们需要一个方式来把字符串与要扩展的文本区分开来:引用句(quotation)。
当调用带参数的宏时,在宏名字及左括号之间不能有任意空格。
AC_INIT ([oops], [1.0]) # incorrect
AC_INIT([hello], [1.0]) # good
参数必须被包括在引用(quote)字符‘[’及‘]’里,并由逗号分隔。在参数中任意前导的空格或新行都被忽略,除非它们被引用。你应该总是引用一个可能包括一个宏名字、逗号、前导空格或新行的参数。这个规则被递归地应用到每个宏调用,包括从其它宏调用的宏。至于引用规则的更多细节,参考第8章【M4中的编程】,109页。
例如:
AC_CHECK_HEADER([stdio.h],
[AC_DEFINE([HAVE_STDIO_H], [1],
[Define to1 if you have <stdio.h>.])],
[AC_MSG_ERROR([sorry, can’t do anythingfor you])])
被正确地引用。你可以安全地简化这个引用为:
AC_CHECK_HEADER([stdio.h],
[AC_DEFINE([HAVE_STDIO_H], 1,
[Define to 1 if you have <stdio.h>.])],
[AC_MSG_ERROR([sorry, can’t do anythingfor you])])
因为‘1’不能包含一个宏调用。在这里,AC_MSG_ERROR的参数必须被引用;否则,其逗号将被解析为一个参数分隔符。同样,‘AC_CHECK_HEADER’的第二、第三个参数必须被引用,因为它们包含了宏调用。参数‘HAVE_STDIO_H’,‘stdio.h’,及‘Define to 1if you have <stdio.h>.’不需要引用,但如果你很不明智地定义了一个名字类似于‘Define’或‘stdio’的宏,那么它们就需要引用。谨慎的Autoconf用户将保留这些引用,不过许多Autoconf用户觉得这样过分小心很烦人,他们会这样改写这个例子:
AC_CHECK_HEADER(stdio.h,
[AC_DEFINE(HAVE_STDIO_H, 1,
[Define to 1 if you have <stdio.h>.])],
[AC_MSG_ERROR([sorry, can’t do anythingfor you])])
这是安全的,只要你采纳了好的命名规范,并不定义名字类似‘HAVE_STDIO_H’,‘stdio’,或‘h’的宏。虽然在这里忽略‘Defineto 1 if you have <stdio.h>.’的引用也是安全的,但不推荐这样做,因为消息字符串更有可能不经意地包含逗号。
以下例子是错误而且危险的,因为它没有被充分引用:
AC_CHECK_HEADER(stdio.h,
AC_DEFINE(HAVE_STDIO_H, 1,
Define to 1 if you have <stdio.h>.),
AC_MSG_ERROR([sorry, can’t do anythingfor you]))
在其它情况下,你可能不得不使用类似于一个宏调用的文本。你必须引用这个文本,就算它不作为一个宏参数传递。例如,在‘configure.ac’中的两个方法(仅对可能的问题进行引用,或引用整行)将保护你的脚本,就算autoconf增加了一个宏AC_DC:
echo "Hard rock was here! --[AC_DC]"
[echo "Hard rock was here! --AC_DC"]
这将在‘configure’中产生以下文本:
echo "Hard rock was here! --AC_DC"
echo "Hard rock was here! --AC_DC"
当你在一个宏参数中使用相同的文本,那么你必须具有一个额外的引用层次(因为其中一个被宏替代剥除)。通常,为所有字符串参数使用双重引用是个好主意,引用或者仅包括有问题的部分,或者包括整个参数:
AC_MSG_WARN([[AC_DC] stinks --Iron Maiden])
AC_MSG_WARN([[AC_DC stinks --Iron Maiden]])
不过,上面的例子触发了一个警告,它关于当运行autoconf时,一个可能的未展开的宏,因为这个宏与为Autoconf语言保留的宏名字空间冲突。为了安全起见,你可以使用额外的转义字符(或者一个四字母(quadrigraph),或者创造性的shell构造(construct))来关闭特定的警告:
echo "Hard rock was here! --AC""_DC"
AC_MSG_WARN([[AC@&t@_DC stinks --Iron Maiden]])
你现在可以理解Autoconf一直被误解的构造之一……经验法则是每当你期望宏扩展,期望引用扩展(quoteexpansion)时; 即期望一层引用被丢弃。例如:
AC_COMPILE_IFELSE([char b[10];], [], [AC_MSG_ERROR([you lose])])
是不正确的:在这里,AC_COMPILE_IFELSE的第一个参数是‘charb[10];’并且扩展一次,结果为‘char b10;’。(过去在Autoconf中对付这个问题的一个惯用语法是通过M4的引用改变原语(changequote primitive),不过不要使用它了)!让我们看得更清楚些:作者意谓第一个参数应该理解为一个字符串,那么它必须被两次引用:
AC_COMPILE_IFELSE([[char b[10];]], [], [AC_MSG_ERROR([you lose])])
好了,这次你真的生成了‘char b[10];’!
另一方面,描述(即AC_DEFINE或AS_HELP_STRING的最后一个参数)不是字符串——它们受分行的影响——不应该被双重引用。即便这些描述是短小的,并不会真正分行,双重引用它们会产生奇怪的结果。
某些宏接受可选参数,在本文档中表示为[arg](不要与引用符号混淆)。你可以把它们保留为空,或使用‘[]’来明确地使得该参数为空,或你可以仅忽略拖尾的逗号。下面三行是等效的:
AC_CHECK_HEADERS([stdio.h], [], [], [])
AC_CHECK_HEADERS([stdio.h],,,)
AC_CHECK_HEADERS([stdio.h])
最好在‘configure.ac’中,把每个宏调用放在单独的行上。绝大多数宏不增加额外新行;它们依赖宏调用后的新行来结束命令。这个做法使得产生的configure脚本不会插入过多的空行,而略微提高可读性。在同一行上像一个宏调用那样设置shell变量,通常是安全的,因为shell允许没有介入换行符的赋值。
你可以在‘configure.ac’文件里添加以‘#’开头的注释。例如,在‘configure.ac’文件里以这样的一行开头是有帮助的:
# Process this file with autoconf to produce a configure script.
‘configure.ac’调用Autoconf宏的次序是无关紧要的,除了少数例外。每个 ‘configure.ac’必须在检查之前包含对AC_INIT的一次调用,并在最后包含对AC_OUTPUT的一次调用(参考4.5节【输出】,17页)。另外,某些宏依赖于其它宏首先被调用,因为它们通过检查某些变量先前被设置的值来确定如何做。这些宏在单独的描述中被标识出来(参考第5章【现存测试】,36页),如果不按顺序调用它们构建configure时,它们会给出警告。
为了鼓励一致性,这里是一个调用Autoconf宏的建议次序。通常而言,靠近这个列表底部的是那些依赖于在列表中出现在它们之前的项。例如,库函数可以被类型及库影响。
Autoconfrequirements
AC_INIT(package, version,bug-report-address)
information on the package
checks forprograms
checks forlibraries
checks forheader files
checks fortypes
checks forstructures
checks forcompiler characteristics
checks forlibrary functions
checks for system services
AC_CONFIG_FILES([file...])
AC_OUTPUT