编写测试
如果现有的特征测试不能完成你所需要的工作,你就必须编写一个新的。这些宏是创建模块。它们为其它宏提供了检查各种特征是否存在并且报告结果的方式。
本章包括一些建议和一些关于现有的测试的为什么要那样编写的原因。通过阅读现有的测试,你还可以学到许多关于编写 Autoconf测试的方法。如果在一个或多个Autoconf测试中出现了错误,这些信息可以帮助你理解它们意味着什么,这有助于你找到最佳的解决问题的办法。
这些宏检查C编译器系统的输出。它们并不为未来的使用而缓存测试的结果(参见缓存结果),这是因为它们没有足够的信息以生成缓存变量名。基于同样的原因,它们还不会输出任何消息。对特殊的C的特征进行的测试调用这些宏并且缓存它们的结果、打印关于它们所进行的测试的消息。
当你编写了一个可以适用于多于一个软件包的特征测试时,最好的方式就是用一个新宏封装它。关于如何封装,参见 编写宏。
检验声明
宏AC_TRY_CPP用于检测某个特定的头文件是否存在。你可以一次检查一个头文件,或者如果你为了某些目的而希望多个头文件都存在,也可以一次检查多个头文件。
宏: AC_TRY_CPP (includes, [action-if-true [, action-if-false]])
includes是C或C++的#include语句和声明,对于它,将进行shell变量、反引用(backquote)、以及反斜线(backslash)替换。(实际上,它可以是任何C程序,但其它的语句可能没有用。)如果预处理器在处理它的时候没有报告错误,就运行shell命令 action-if-true。否则运行shell命令action-if-false。
本宏使用CPPFLAGS,而不使用CFLAGS,这是因为`-g'、`-O'等选项对于许多C预处理器来说都是不合法的选项。
下面是如何确认在某个头文件中是否包含一个特定的声明,比如说typedef、结构、结构成员或者一个函数。使用 AC_EGREP_HEADER而不是对头文件直接运行grep;在某些系统中,符号可能是在另一个你所检查的 `#include'文件。
宏: AC_EGREP_HEADER (pattern, header-file, action-if-found [, action-if-not-found])
如果对系统头文件header-file运行预处理器所产生的输出与egrep常规表达式pattern相匹配,就执行shell命令action-if-found,否则执行action-if-not-found。
为了检查由头文件或者C预处理器预定义的C预处理器符号,使用AC_EGREP_CPP。下面是后者的一个例子:
AC_EGREP_CPP(yes,
[#ifdef _AIX
yes
#endif
], is_aix=yes, is_aix=no)
宏: AC_EGREP_CPP (pattern, program, [action-if-found [, action-if-not-found]])
program是C或者C++的程序文本,对于它,将进行shell变量、反引号(backquote)以及反斜线(backslash)替换。如果对 program运行预处理器产生的输出与egrep常规表达式(regular expression)pattern 相匹配,就执行shell命令action-if-found,否则执行action-if-not-found。
如果宏还没有调用AC_PROG_CPP或者AC_PROG_CXXCPP(根据当前语言来确定使用那个宏,参见对语言的选择),本宏将调用它。
检验语法
为了检查C、C++或者Fortran 77编译器的语法特征,比如说它是否能够识别某个关键字,就使用AC_TRY_COMPILE 来尝试编译一个小的使用该特征的程序。你还可以用它检查不是所有系统都支持的结构和结构成员。
宏: AC_TRY_COMPILE (includes, function-body, [action-if-found [, action-if-not-found]])
创建一个C、C++或者Fortran 77测试程序(依赖于当前语言,参见对语言的选择),来察看由function-body组成的函数是否可以被编译。
对于C和C++,includes是所有function-body中的代码需要的#include语句(如果当前选择的语言是Fortran 77,includes将被忽略)。如果当前选择的语言是C或者C++,本宏还将在编译的时侯使用CFLAGS或者CXXFLAGS,以及 CPPFLAGS。如果当前选择的语言是Fortran 77,那么就在编译的时候使用FFLAGS。
如果文件被成功地编译了,就运行shell命令action-if-found,否则运行action-if-not-found。
本宏并不试图进行连接;如果你希望进行连接,使用AC_TRY_LINK (参见检验库)。
检验库
为了检查一个库、函数或者全局变量,Autoconf configure脚本试图编译并连接一个使用它的小程序。不像Metaconfig,它在缺省情况下对C库使用nm或者ar以试图确认可以使用那个函数。由于与函数相连接避免了处理nm和ar的各个变种的选项及输出格式,而且不必处理标准库的位置,所以与函数连接通常是更加可靠的办法。如果需要,它还允许进行交叉配置或者检查函数的运行是特征。另一方面,它比一次性扫描库要慢一些。
少数系统的连接器在出现找不到的函数错误(unresolved functions)时不返回失败的退出状态。这个错误使得由Autoconf 生成的配置脚本不能在这样的系统中使用。然而,有些这样的连接器允许给出选项以便正确地返回错误状态。 Autoconf目前还不能自动地处理这个问题。如果用户遇到了这样的问题,他们可能可以通过在环境中设置LDFLAGS 以把连接器所需要的选项(例如,`-Wl,-dn' on MIPS RISC/OS)传递给连接器,从而解决这个问题。
AC_TRY_LINK用于编译测试程序,以测试函数和全局变量。AC_CHECK_LIB还用本宏把被测试的库暂时地加入LIBS并试图连接一个小程序,从而对库进行检查(参见库文件)。
宏: AC_TRY_LINK (includes, function-body, [action-if-found [, action-if-not-found]])
根据当前语言(参见对语言的选择),创建一个测试程序以察看一个函数体为function-body的函数是否可以被编译和连接。
对C和C++来说,includes给出了所有function-body中的代码需要的#include语句(如果当前选定的语言是Fortran 77,includes将被忽略)。如果当前语言是C或者C++,本宏在编译时还将使用 CFLAGS或者CXXFLAGS,以及CPPFLAGS。如果当前选定的语言是Fortran 77,那么在编译时将使用FFLAGS。然而,在任何情况下,连接都将使用LDFLAGS和LIBS。
如果文件被成功地编译和连接了,就运行shell命令action-if-found,否则就运行action-if-not-found。
宏: AC_TRY_LINK_FUNC (function, [action-if-found [, action-if-not-found]])
根据当前语言(参见对语言的选择),创建一个测试程序以察看一个含有function 原型和对它的调用的程序是否可以被编译和连接。
如果文件被成功地编译和连接了,就运行shell命令action-if-found,否则就运行action-if-not-found。
宏: AC_TRY_LINK_FUNC (function, [action-if-found [, action-if-not-found]])
试图编译并且连接一个与function相连接的小程序。如果文件被成功地编译和连接了,就运行shell命令 action-if-found,否则就运行action-if-not-found。
宏: AC_COMPILE_CHECK (echo-text, includes, function-body, action-if-found [, action-if-not-found])
本宏是AC_TRY_LINK的一个过时的版本。此外,如果echo-text不为空,它首先还要把 `checking for echo-text'打印到标准输出。用AC_MSG_CHECKING 和AC_MSG_RESULT来代替本宏的打印消息的功能(参见打印消息)。
检验运行时的特征
有时候,你需要知道系统在运行时作了些什么,比如说某个给定的函数是否具备某种能力或者是否含有错误。如果你能,你可以在你的程序初始化时自行检查这类事件(比如说machine's endianness)。
如果你实在需要在配置时刻检查运行时的特征,你可以编写一个测试程序以确定结果,并且通过AC_TRY_RUN 来编译和运行它。如果可能就避免运行测试程序,这是因为使用它们使得人们不能对你的包进行交叉编译。
运行测试程序
如果你希望在配置的时候测试系统运行时的特征,就使用如下的宏。
宏: AC_TRY_RUN (program, [action-if-true [, action-if-false [, action-if-cross-compiling]]])
program是C程序的文本,将对该文本进行shell变量和反引用(backquote)替换。如果它被成功地编译和连接了并且在执行的时候返回的退出状态为0,就运行shell命令action-if-true。否则就运行shell命令action-if-false;程序的退出状态可以通过 shell变量`$?'得到。本宏在编译时使用CFLAGS或者CXXFLAGS以及 CPPFLAGS、LDFLAGS和LIBS。
如果使用的C编译器生成的不是在configure运行的系统上运行的可执行文件,那么测试程序就不运行。如果给出了可选的shell命令action- if-cross-compiling,它们就代替生成的可执行文件执行。否则, configure打印一条错误消息并且退出。
当交叉编译使运行时测试变得不可能的时候,就尝试提供一个应急(pessimistic)的缺省值以供使用。你通过把可选的最后一个参数传递给 AC_TRY_RUN来完成这个工作。在每次生成configure的过程中,每次遇到没有提供 action-if-cross-compiling参数的AC_TRY_RUN调用时,autoconf都打印一条警告消息。虽然用户将不能为交叉编译你的包而进行配置,你仍可以忽略该警告。与Autoconf一同发行的少数宏产生该警告消息。
为了为交叉编译进行配置,你还可以根据规范系统名(canonical system name)为这些参数选择值(参见手工配置)。另一种方式是把测试缓存文件设置成目标系统的正确值(参见缓存结果)。
为了给嵌入到其它宏(包括少数与Autoconf一同发行的宏)中的,对AC_TRY_RUN的调用提供缺省值,你可以在它们运行之前调用 AC_PROG_CC。那么,如果shell变量cross_compiling被设置成 `yes',就使用另一种方法来获取结果,而不是调用宏。
宏: AC_C_CROSS
本宏已经过时;它不作任何事情。
测试程序指南
测试程序不应该向标准输出输出任何信息。如果测试成功,它们应该返回0,否则返回非0,以便于把成功的执行从core dump或者其它失败中区分出来;段冲突(segmentation violations)和其它失败产生一个非0的退出状态。测试程序应该从main中exit,而不是return,这是因为在某些系统中(至少在老式的 Sun上),main的return的参数将被忽略。
测试程序可以使用#if或者#ifdef来检查由已经执行了的测试定义的预处理器宏的值。例如,如果你调用AC_HEADER_STDC,那么在`configure.in'的随后部分,你可以使用一个有条件地引入标准C头文件的测试程序:
#if STDC_HEADERS
# include
#endif
如果测试程序需要使用或者创建数据文件,其文件名应该以`conftest'开头,例如`conftestdata'。在运行测试程序之后或者脚本被中断时,configure将通过运行`rm -rf conftest*'来清除数据文件。
测试函数
在测试程序中的函数声明应该条件地含有为C++提供的原型。虽然实际上测试程序很少需要带参数的函数。
#ifdef __cplusplus
foo(int i)
#else
foo(i) int i;
#endif
测试程序声明的函数也应该有条件地含有为C++提供的,需要`extern "C"'的原型。要确保不要引入任何包含冲突原型的头文件。
#ifdef __cplusplus
extern "C" void *malloc(size_t);
#else
char *malloc();
#endif
如果测试程序以非法的参数调用函数(仅仅看它是否存在),就组织程序以确保它从不调用这个函数。你可以在另一个从不调用的函数中调用它。你不能把它放在对 exit的调用之后,这是因为GCC第2版知道 exit永远不会返回,并且把同一块中该调用之后的所有代码都优化掉。
如果你引入了任何头文件,确保使用正确数量的参数调用与它们相关的函数,即使它们不带参数也是如此,以避免原型造成的编译错误。GCC第2版为有些它自动嵌入(inline)的函数设置了内置原型;例如, memcpy。为了在检查它们时避免错误,既可以给它们正确数量的参数,也可以以不同的返回类型(例如char)重新声明它们。
可移植的Shell编程
在编写你自己的测试时,为了使你的代码可以移植,你应该避免使用某些shell脚本编程技术。 Bourne shell和诸如Bash和Korn shell之类的向上兼容的shell已经发展了多年,但为了避免麻烦,不要利用在UNIX版本7,circa 1977之后添加的新特征。你不应该使用shell函数、别名、负字符集(negated character classes)或者其它不是在所有与Bourne兼容的shell中都能找到的特征;把你自己限制到最低的风险中去。(the lowest common denominator)。即使是unset都不能够被所有的shell所支持!还有,像下面那样在指定解释器的惊叹号之后给出空格:
#! /usr/bin/perl
如果你忽略了路径之前的空格,那么基于4.2BSD的系统(比如说Sequent DYNIX)将忽略这一行,这是因为它们把 `#! /'看作一个四字节的魔数(magic number)。
你在configure脚本中运行的外部程序,应该是一个相当小的集合。关于可用的外部程序列表,参见 GNU编码标准中的‘Makefile中的工具’一节。这个限制允许用户在只拥有相当少的程序时进行配置和编译,这避免了软件包之间过多的依赖性。
此外,这些外部工具中的某些工具只有一部分特征是可移植的。例如,不要依赖ln支持`-f'选项,也不要依赖cat含有任何选项。sed脚本不应该含有注释,也不应该使用长于8个字符的分支标记。不要使用`grep -s'来禁止(suppress)输出。而要把grep的标准输出和标准错误输出(在文件不存在的情况下会输出信息到标准错误输出)重新定向到 `/dev/null'中。检查grep的退出状态以确定它是否找到了一个匹配。
测试值和文件
configure脚本需要测试许多文件和字符串的属性。下面是在进行这些测试的时候需要提防的一些移植性问题。
程序test是进行许多文件和字符串测试的方式。人们使用替代(alternate)名`['来调用它,但因为`['是一个m4的引用字符,在Autoconf代码中使用`['将带来麻烦。
如果你需要通过test创建多个检查,就用shell操作符`&&'和`||' 把它们组合起来,而不是使用test操作符`-a'和`-o'。在System V中, `-a'和`-o'相对于unary操作符的优先级是错误的;为此,POSIX并未给出它们,所以使用它们是不可移植的。如果你在同一个语句中组合使用了 `&&'和`||',要记住它们的优先级是相同的。
为了使得configure脚本可以支持交叉编译,它们不能作任何测试主系统而不是测试目标系统的事。但你偶尔可以发现有必要检查某些特定(arbitrary)文件的存在。为此,使用`test -f'或者`test -r'。不要使用`test -x',因为4.3BSD不支持它。
另一个不可移植的shell编程结构是
var=${var:-value}
它的目的是仅仅在没有设定var的值的情况下,把var设置成value,但如果var已经含有值,即使是空字符串,也不修改var。老式BSD shell,包括 Ultrix sh,不接受这个冒号,并且给出错误并停止。一个可以移植的等价方式是
: ${var=value}
多种情况
有些操作是以几种可能的方式完成的,它依赖于UNIX的变种。检查它们通常需要一个"case 语句"。Autoconf不能直接提供该语句;然而,通过用一个shell变量来记录是否采用了操作的某种已知的方式,可以容易地模拟该语句。
下面是用shell变量fstype记录是否还有需要检查的情况的例子。
AC_MSG_CHECKING(how to get filesystem type)
fstype=no
# The order of these tests is important.
AC_TRY_CPP([#include
#include ], AC_DEFINE(FSTYPE_STATVFS) fstype=SVR4)
if test $fstype = no; then
AC_TRY_CPP([#include
#include ], AC_DEFINE(FSTYPE_USG_STATFS) fstype=SVR3)
fi
if test $fstype = no; then
AC_TRY_CPP([#include
#include ], AC_DEFINE(FSTYPE_AIX_STATFS) fstype=AIX)
fi
# (more cases omitted here)
AC_MSG_RESULT($fstype)
对语言的选择
既使用C又使用C++的包需要同时测试两个编译器。Autoconf生成的configure脚本在缺省情况下检查C的特征。以下的宏决定在`configure.in'的随后部分使用那个语言的编译器。
宏: AC_LANG_C
使用CC和CPP进行编译测试并且把`.c'作为测试程序的扩展名。如果已经运行过AC_PROG_CC,就把把shell变量cross_compiling的值设置成该宏计算的结果,否则就设置为空。
宏: AC_LANG_CPLUSPLUS
使用CXX和CXXPP进行编译测试并且把`.C'作为测试程序的扩展名。如果已经运行过AC_PROG_CXX,就把把shell变量cross_compiling的值设置成该宏计算的结果,否则就设置为空。
宏: AC_LANG_FORTRAN77
使用F77进行编译测试并且把`.f'作为测试程序的扩展名。如果已经运行过AC_PROG_F77,就把把shell变量cross_compiling的值设置成该宏计算的结果,否则就设置为空。
宏: AC_LANG_SAVE
在堆栈中记录当前的语言(由AC_LANG_C、AC_LANG_CPLUSPLUS或者AC_LANG_FORTRAN77 所设定)。不改变当前使用的语言。在需要暂时地切换到其它特殊语言的宏之中使用本宏和AC_LANG_RESTORE。
宏: AC_LANG_RESTORE
选择储存在栈顶的,由AC_LANG_SAVE设置的语言,并且把它从栈顶删除。本宏等价于运行在最后被调用的 AC_LANG_SAVE之前最近的AC_LANG_C、AC_LANG_CPLUSPLUS或者 AC_LANG_FORTRAN77。
调用本宏的次数不要多于调用AC_LANG_SAVE的次数。
宏: AC_REQUIRE_CPP
确认已经找到了当前用于测试的预处理器。本宏根据当前选择的语言,以AC_PROG_CPP或者AC_PROG_CXXCPP 为参数调用AC_REQUIRE(参见首要的宏)。
测试的结果
一旦configure确定了某个特征是否存在,它将如何记录这一信息?这里有四种记录方式:定义一个C预处理器符号、在输出文件中设置一个变量、为将来运行configure而把结果储存到一个缓存文件中,以及打印一条消息以便让用户知道测试的结果。
定义C预处理器符号
对一个特征的检测的常见回应是定义一个表示测试结果的C预处理器符号。这是通过调用AC_DEFINE 或者AC_DEFINE_UNQUOTED来完成的。
在缺省状态下,AC_OUTPUT把由这些宏定义的符号放置到输出变量DEFS中,该变量为每个定义了的符号添加一个选项`-Dsymbol= value'。与Autoconf第1版不同,在运行时不定义DEFS变量。为了检查Autoconf宏是否已经定义了某个C预处理器符号,就检查适当的缓存变量的值,例子如下:
AC_CHECK_FUNC(vprintf, AC_DEFINE(HAVE_VPRINTF))
if test "$ac_cv_func_vprintf" != yes; then
AC_CHECK_FUNC(_doprnt, AC_DEFINE(HAVE_DOPRNT))
fi
如果已经调用了AC_CONFIG_HEADER,那么就不是创建DEFS,而是由AC_OUTPUT 创建一个头文件,这是通过在一个暂时文件中把正确的值替换到#define语句中来实现的。关于这种输出的详情,请参见配置头文件。
宏: AC_DEFINE (variable [, value [, description]])
定义C预处理器变量variable。如果给出了value,就把variable设置成那个值(不加任何改变),否则的话就设置为1。value不应该含有新行,同时如果你没有使用AC_CONFIG_HEADER,它就不应该含有任何`#'字符,这是因为make将删除它们。为了使用shell变量(你需要使用该变量定义一个包含了 m4引用字符`['或者`]'的值),就使用AC_DEFINE_UNQUOTED。只有在你使用AC_CONFIG_HEADER的时候, description才有用。在这种情况下,description被作为注释放置到生成的`config.h.in'的宏定义之前;不必在 `acconfig.h'中提及该宏。下面的例子把 C预处理器变量EQUATION的值定义成常量字符串`"$a > $b"':