"configure.in" 的书写

本贴最后更新于 1669 天前,其中的信息可能已经斗转星移

What is Portability?

在进行分析什么值得去检测, 如何检测之前, 抛出一个问题: 到底什么是可移植性? 可移植性是一种代码的质量, 可以使得编译和运行在各种各样的平台上. 在 Autoconf 的背景下, 可移植性通常指的是可以运行在类 Unix 的系统上--有时候包含 Windows.
当我第一次使用 Autoconf, 很难决定到底去检测什么在"configure.in".当时在做的一些项目是在 SunOS 4 上. 但是很是有兴趣移植到其他的 Unix 平台上(比如 Solaris).
我采用的方法虽然可行但是相对耗时且痛苦: 我编写了一个最小的'configure.in', 然后继续简单地尝试在 Solaris 上构建我的程序. 每次遇到构建问题时, 我都会更新'configure.in'和我的源代码并重新启动. 正确构建后, 我开始测试是否存在与可移植性相关的运行时问题.
因为我没有从一个相对可移植的基础开始, 也因为我不知道可用于帮助添加 Autoconf 支持的工具. 如果可能的话, 最好先编写可移植代码.
世界上有许多类 Unix 系统, 这些系统虽然仍在运行, 但只能被认为是过时的. 虽然将某些程序移植到所有这样的系统存在可能性,但通常尝试也没什么实际的用处. 移植就是一个困难的过程, 特别是考虑到通常无法在所有平台上进行测试, 并且这些平台每年都会发布具有自己的 bug 和特性的新操作系统.
我们提倡一种实用的可移植性方法: 我们编写的程序针对的是一个相当大的, 但也相当现代的类 Unix 系统. 由于在我们的可移植性框架中发现了缺陷, 我们更新'configure.in'和我们的源代码, 然后继续. 在实践中, 这才是一种有效的方法.

Brief introduction to portable sh

如果你读过好多的"configure.in"文件, 你会发现这个文件的书写使用的是一种非比寻常的风格. 比如说, 你很少见一个程序是"["样书写的; 这片我们不会深入太多细节关于这个脚本怎么去写, 这个会在后面详细的说道的.
与可移植性的其他方面一样, 在'configure.in'和'Makefile.am'中编写 shell 脚本的方法应该取决于设定的的目标. 一些平台已经臭名昭着地打破了 sh 实现. 例如, Ultrix sh 没有实现 unset. 当然, GNU Autotools 是以最可移植的方式编写的, 避免对可能性的定制扩展进行了限制.
另外, sh 脚本本身做得很少, 大多数实际工作是由单独的程序完成的, 每个程序都有自己潜在的可移植性问题. 例如, 某些选项在系统之间不可移植, 并且并不是每个系统上都会存在可见的公共程序 - 因此不仅要知道哪些 sh 结构不可移植, 而且还必须知道哪些程序可以(或不能)使用, 以及这些程序的哪些选项是可移植的.
这个看起来很难以实现, 但其实写一个可移植脚本程序不是那么难 - 只要对规则了如指掌了. 但是要想达到这个境界, 这个过程还是需要耗费点时间的.
了解你可能会关心哪些架构是值得的 - 如果你正在编写一个可移植度高的程序, 比如 emacs 或 gcc, 那么你会做出不同的选择, 而不是你正在编写的东西只能运行在各种 Linux 版本上. 此外, 在"configure.in"中使用不可移植代码的成本相对较低 - 通常, 在找到不可移植的结构时, 按需重写片段代码相当容易.

Ordering Tests

除了编写可移植的 sh 代码的问题之外, 第一次编写"configure.in"面临的另一个问题是确定运行各种测试的顺序. Autoconf 通过 Autoscan(以后会详细讲述到)间接的建议了标准顺序:

  • 样板文件. 这一部分应该包含标准的样板代码, 比如说 AC_INIT(这个必须是第一个), AM_INIT_AUTOMAKE, AC_CONFIG_HEADER, 也许还有 AC_REVISON.
  • 选项. 这一部分应该包含一定的宏, 用来向"configure"中增加命令行选项, 比如说"AC_ARG_ENABLE". 如果足够短的话, 在这一部分也可以增加一些支持性的代码, 比如来自"libgcj"的例子:
AC_ARG_ENABLE(getenv-properties,
         [  --disable-getenv-properties 
         don’t set system properties from GCJ_PROPERTIES]) 

 dnl Whether GCJ_PROPERTIES is used depends on the target.
         if test -n "$enable_getenv_properties";then 
 		enable_getenv_properties=${enable_getenv_properties_default-yes}
         fi 

	 if test "$enable_getenv_properties" = no; then
		AC_DEFINE(DISABLE_GETENV_PROPERTIES) 
	 fi
  • 程序. 接下来是传统意义上的检查程序, 这个检查程序同样需要被用在配置过程, 编译过过程, 或者被其中一个程序编译. 这个通常会有两个宏参与"AC_CHECK_PROG"和"AC_PATH_TOOL".
  • 库. 在检查 C(或者 C++, etc)的其他对象之前先进行库的检查. 这个是必要的, 因为有一些检查时需要检查链接和程序运行的, 但在这之前先进行库的检查确保链接的时候的可靠性.
  • 头. 接下来检查已存的头文件.
  • 类型定义(typedef)和结构体. 我们在检查完头文件后再检查 typedef 是因为 typedef 出现在头文件中, 并且我们在深入头之前需要知道什么样的头是可以用的.
  • 函数. 我们最后检查函数, 是因为函数对前面的这些都多多少少会有依赖性. 在搜索函数时, 需要库是为了能正确的链接, 需要头文件是因为要函数原型(这个对于 c++ 来说是更为重要的, 因为有着比 C 更加严格的原型规则). 对于那些使用和返回内置类型的函数需要 typedef.
  • 输出. " AC_OUTPUT" 来完成这个.
    这个顺序应该只是一个粗略的准则. 有时需要交错测试, 以使"configure.in"更容易维护, 或者因为测试本身确实需要使用不同的顺序. 例如, 如果项目同时使用 C 和 C ++, 可能会选择在完成所有 C 检查后执行所有 C ++ 检查, 以使"configure.in"更容易阅读.

What to check for

决定要检查的内容实际上是编写"configure.in"的重中之重. 一旦参考了 Autoconf 手册, 那么编写特定测试的"方法"应该不是什么复杂的事儿. 但是"when"可能仍然存在一定的不确定性.
各种类 Unix 系统之间存在着一个显着的分歧, 即相同的程序并不会出现在所有的系统上, 即使它们存在, 它们并不总是以相同的方式工作. 对于这些问题, 建议在可能的情况下遵循 GNU 编码标准的建议: 使用相对有限的一组程序中最常见的选项. 如果做不到这一点, 尝试使用 POSIX 指定的程序和选项, 或者通过检查关心的平台上的已知问题来扩充这种方法.
对工具的和差异性的检查通常只是一小部分. 更多的是对函数和库的检查.
除了像"libc"这样的核心库, 像"libm"和像"libX11"这样的库(不被认为是系统库), 对于不同 Unix 系统之间的库名或内容几乎没有保持一致的. 尽管如此, 库也是很容易处理的. 因为关于库的决定几乎总是只影响各种"Makefile". 这意味着检查另一个库通常不需要对源代码进行重大更改. 此外, 因为添加新的库进行测试对开发周期的影响很小 - 实际上只是重新运行"configure"然后重新链接. 可以采用不严格的库方法来提高效率. 比如说, 针对于想要部署的平台上进行库的测试就行了, 然后基于这些 base 进行定制化更改.
假设遇到链接问题. 怎么处理它? 首先要做的是使用 nm 查看系统库来查看是否存在缺失的函数. 如果是, 并且它在你知道的库中, 只需添加另一个"AC_ CHECK_LIB". 注意, 仅仅在库中查找函数是不够的, 因为在某些系统上, 某些"标准"库是不合需要的. 比如说"libucb"是你应该避免使用的最常见的库.
如果在系统库中找不到该函数, 那么就遇到了一个比较棘手的问题: 非可移植的函数. 针对于缺失函数基本上有三种方法. 下面我们讨论函数, 但实际上这些方法或多或少适用于 typedef, 结构体和全局变量.

  • 方法一是编写替换函数并条件编译它, 或者将其放入命名合适的文件中并使用"AC_REPLACE_FUNCS". 例如, Tcl 使用"AC_REPLACE_FUNCS(strstr)"来处理没有 strstr 函数的系统.
  • 当存在具有不同名称的类似函数时, 使用第二种方法. 这里的想法是检查所有替代方案, 然后修改源以使用可能存在的任何一个. 这里的习惯用法是在"AC_CHECK_FUNCS"的第二个参数中使用 break; 这用于跳过不必要的测试并向读者表明这些检查是相关的. 例如, 以下是 libgcj 如何检查 inet_atoninet_addr; 它只使用找到的第一个:
AC_CHECK_FUNCS(inet_aton inet_addr, break)

使用这些检查结果的代码看起来像:

#if HAVE_INET_ATON
 ... use inet_aton here 
 #else
#if HAVE_INET_ADDR 
 ... use inet_addr here
#else 
#error Function missing!
#endif
#endif

注意, 如果函数不存在, 我们如何使它成为编译时错误. 通常, 最好在构建过程中尽早发生错误.

  • 非可移植函数的第三种方法是编写代码, 使得这些函数可选地使用. 例如, 如果正在编写编辑器, 那么可能决定使用 mmap 将文件映射到编辑器的内存中. 但是, 由于 mmap 不可移植, 所以可以编写一个函数来使用更具有一致性的的读取.
    然后, 处理这些众所周知的不可移植的函数只是问题的一小部分. 当然用上面实际的方法解决问题肯定不错, 但是如果你是在类似于 GNU/linux 环境下编写的代码, 那么这样看起来就会不是那么的高效, 是因为在 GNU/linux 很少有函数的缺失. 以至于根本发现不了可移植性的问题.
    遗憾的是, 并没有很高明的方式来避免这个问题. 只有通过渊博的知识面和丰富的经验对 Unix 和类 Unix 等系统环境了解了. 比如熟悉 POSIX, XPG 会有很大的帮助. 如果某些函数不是在 POSIX 下的, 那么就应该考考虑进行检测了. 但是标准并不是万能的, 有些系统甚至于并不兼容 POSIX 规则, 还有的实现是存在 bug 的, 你需要根据自己的需求 workaround.
    最后一个容易遇到的问题就是检查的有时候太多了. 这样累赘没有什么意义的检查会对程序的维护增加负担. 比如有的程序中会检查 <sys/typs.h>, 这样的检查时毫无意义的. 因为 <sys/typs.h> 是完全具有可以执行的. 但这些都得根据经验和知识储备进行实际的判断了.

Using Configuration Names

虽然功能测试绝对是最好的方法, 但是"configure"脚本有时可能不得不根据 configuration name 做出决定. 如果某些代码必须基于无法使用标准"Autoconf"功能测试进行测试的内容进行不同的编译, 那么 configuration name 则是必要的. 例如, "expect"包需要找到有关系统的"tty"实现的信息; 如果不检查特定的 configuration name 就无法可靠地完成交叉编译.
通常最好测试特定功能, 而不是测试特定系统类型. 这是因为随着 Unix 和其他操作系统的发展, 不同的系统会相互复制功能.
如果没有其他方法可以在"configure"脚本中测试 configuration name, 则最好定义一个描述功能的宏, 而不是定义一个描述特定系统的宏. 这允许在具有相同功能的其他系统上使用相同的宏.
通常使用"autoconf"的"configure.in"文件中的 case 语句对特定系统进行测试. case 语句可能类似于以下内容

case "${host}" in
      i[[3456]]86-*-linux-gnu*) do something ;;
      sparc*-sun-solaris2.[[56789]]*) do something ;;
      sparc*-sun-solaris*) do something ;;
      mips*-*-elf*) do something ;;
      esac

假设"host"是一个包含规范配置系统的 shell 变量-如果"configure.in"使用"AC_CANONICAL_HOST"或"AC_CANONICAL_SYSTEM"宏, 则情况将也是如此.
请注意这段代码中的双方括号. 这些用于解决"autoconf"丑陋的实现细节-它在后台使用 M4. 没有这些额外的括号, case 语句中的方括号将被 M4 吞噬, 并且不会出现在结果的"configure"中. 这部分恶心细节会在之后长篇概述.
在操作系统字段后使用""尤为重要, 以匹配将由"config.guess"生成的版本号. 在大多数情况下, 必须小心匹配一系列处理器类型. 对于大多数处理器系列, 如上面的"mips "所示, 尾随的""就足够了. 对于 i386 家族而言, 目前满足"i[34567] 86"的要求就足够了. 对于 m68k 系列, 将需要"m68 "之类的东西. 当然如果不需要匹配处理器, 简单的使用'', 在'--irix'中即可.

OK, 到这里初步的针对于"configure.in"的介绍就结束了, 其中的好多好多的细节, 只后会介绍到.

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...