Autoconf 教程 Part-2 [翻译]

  |   0 评论   |   942 浏览

这是AutoConf教程系列的第二篇, 第一篇翻译请看:Autoconf 教程 Part-1 [翻译]
由衷的感谢原作者的创作, 让我快速的理解了AutoConf的大部分原理与一些细节.
原文链接: http://www.idryman.org/blog/2016/03/14/autoconf-tutorial-2/

这是 autoconf 教程系列的第二篇文章。在这篇文章中,我将介绍 autoconf 和 automatake 中的一些基本单元,以及一个使用本文中概念的跨平台 x11程序示例。在阅读了这篇文章之后,你应该可以为小范围项目编写自己的构建脚本了。

Autoconf

Autoconf是 GNU Autotools 构建系统的一部分。Autotools 是三个主要软件包的集合: autoconf、 automake 和 libtools。每个包都有较小的子包,包括 autoheader、 aclocal、 autoscan 等。我不会讨论所有软件包的细节; 相反,我只关注 autoconf 如何在构建链中发挥作用。

译者注: 对于OpenJDK的编译这些知识足矣. 其编译也没有用Gnu AutoTools的全家桶.

主要用于生成configure脚本。configure 是一个 shell 脚本,用于检测构建环境,并将适当的构建标志输出到 Makefile,将预处理器宏(如 HAVE_ALLOCA_H)输出到 config.h。然而,编写一个好的可移植、可扩展的 shell 脚本并不容易。这就是 gnu m4宏 的用武之地。Gnu m4宏是传统 UNIX 宏处理器的实现。通过使用 m4,您可以轻松地创建可移植的 shell 脚本,包含不同的预定义宏,并轻松地定义自己的扩展。

简而言之,autoconf 语法是由 gnu m4宏 包装的 shell 脚本。

译者注:
autoconf的宏的内核是用M4宏实现的. 但是在使用上,还是很大的区别. 比如逃逸字符,M4使用的是:前引号: 是反引号, 后引号是单引号这样的语法.

define(HELLO,`hello')

而对于AutoConf版本的宏有两种,可以看到分隔的逃逸字符是使用的是[ ]

# 单纯的把HELLO 扩展为 hello world
m4_define([HELLO],[hello world])

# 定义一个可被其它m4脚本使用的宏.可以认为是注册为一个全局的宏. 
AC_DEFUN([MY_MACRO], [ABC])

在早期,编写可移植的 shell 脚本并不容易。例如,并非所有 mkdir support -p 选项,并非所有 shell 都是 bash 兼容的,等等。使用 m4宏 来执行常规的 shell 逻辑,比如 AS_IF 而不是 if: if [[]]; then...AS_MKDIR_P 而不是 mkdir -pAS_ CASE 而不是 case... esac 使你的配置脚本在所有 unix/unix like 的环境下工作得更好,也更加常规。大多数情况下,您使用的是宏,而不是赤裸裸的 shell 脚本,但请记住,幕后的最终输出仍然是 shell 脚本

M4宏基础

虽然第一次看到 m4宏是非常陌生和不友好的,但它只包含两个基本概念:

  • Macro expansion: 宏观扩张
  • Quoting: 引用

你可以这样定义一个宏:

# define a macro MY_MACRO that expands to text ABC
m4_define([MY_MACRO], [ABC])
MY_MACRO => ABC

# define a macro that is visible to other m4 scripts
AC_DEFUN([MY_MACRO], [ABC])
MY_MACRO => ABC

它非常类似于 c 宏或 Lisp 宏。宏在编译时扩展(configure.ac = > configure)。您可以定义一个宏 my_macro,该宏可以扩展为一个 shell 脚本片段。这里我们只是将其扩展为 ABC,它在 shell 脚本中没有任何意义,并且可能会触发错误。

Every symbol in your script is expandable. For example if you simply write ABC in your script, is it a shell symbol, or is it a m4 symbol that needs to expand? The m4 system uses quoting to differentiate the two. The default quoting in autoconf is square brackets [, ]. Though you can change it, but it is highly unrecommended.

脚本中的每个符号都是可扩展的。例如,如果您只是在脚本中编写 ABC,那么它是 shell 符号,还是需要展开的 m4符号?M4系统使用引号来区分两者。Autoconf 中的默认引号是方括号[ ,]。虽然你可以改变它,但是强烈不推荐(修改它)。

ABC   # m4 would try to find *macro* definition of ABC and try to expand it
[ABC] # shell symbol ABC

为什么这很重要? 想想这两个例子

ABC="hello world"     # m4 would try to expand ABC, hello, and world
[ABC="hello world"]   # m4 would just produce ABC="hello world" to the output

# m4 will expand MY_MACRO and BODY *before* defining MY_MACRO as a symbol to
# BODY.
AC_DEFUN(MY_MACRO, BODY)

# safe
AC_DEFUN([MY_MACRO], [BODY])

This is the base of all m4 macros. To recap, always quote the arguments for the macros, including symbols, expressions, or body statements . (I skipped some edge cases that requires double quoting or escapes, for the curious please check the autoconf language).

这是所有 m4宏 的基础。总结一下,始终引用宏的参数,包括符号、表达式或主体语句。(我跳过了一些需要双引号或转义的边缘案例,因为好奇的人请检查 autoconf 语言)。

译者注:
的确,这是最核心的M4宏的所有, 掌握这些绝对够你理解OpenJDK中定义的宏. 不过你仍然会遇到非常多的AC开关的autoconf自定义的内置宏.这些还是需要重新再学习它.并且大部分的时间,你都在与他们打交道.

打印消息

现在我们知道了 m4 的基本语法,让我们看看它提供了哪些函数。在配置脚本中,如果直接调用 echo,输出将被重定向到不同的位置。在 autoconf 中打印消息的惯例是使用 AC_MSG_* 宏。下面是两个最常用的宏:

# Printing regular message
AC_MSG_NOTICE([Greetings from Autoconf])

# Prints an error message and stops the configure script
AC_MSG_ERROR([We have an error here!]

为满足更多的好奇心,请查看 autoconf 手册中的打印消息部分:Printing Messages

条件语句: If-condition

要在 autoconf 中编写 if 条件,只需调用 AS_IF(test-1, [run-if-true-1], ..., [run-if-false])。最好的方法是通过一个例子来看看它是如何工作的:

abc="yes"
def="no"
AS_IF([test "X$abc" = "Xyes"],             # test condition
      [AC_MSG_NOTICE([abc is yes])],       # then case
      [test "X$def" = "Xyes"],             # else if
      [AC_MSG_NOTICE([def is yes])],
      [AC_MSG_ERROR([abc check failed])]   # else case
     )

# expands to the following shell script
abc="yes"
def="no"
if test "X$abc" = "Xyes"; then :
  # test condition
       $as_echo "$as_me: abc is yes" >&6
elif # then case
       test "X$def" = "Xyes"; then :
  # else if
       $as_echo "$as_me: def is yes" >&6
else
  as_fn_error $? "abc check failed"   # else case
fi

注意,我们没有使用通用的 shell 测试操作符[[]] ,而是使用 test,因为宏展开时保留了方括号。建议的调用test的方法是测试 “ x $variable” = “ Xvalue”。这就是我们如何避免 shell 变量的 null 情况。

另一个常见的分支函数是AS_CASE(word, [pattern1], [if-matched1], ..., [default]) ,其逻辑几乎是相同的。

关于 autoconf,我们需要了解的所有基本知识,让我们休息一下,切换到 automake。

Automake

与 autoconf 一样,automake 是在另一种现有语言(Makefile 语法)之上的额外语义。与 autoconf 不同,它不使用 m4扩展语法。它使用一个转换成实际逻辑的变数命名原则。大多数情况下,我们只需要使用以下两个规则,我们将详细讨论这两个规则。

  • where_PRIMARY = targets
  • target_SECONDARY = inputs

where_PRIMARY = targets

This syntax has three parts, targets, type PRIMARY, and where to install where.

这个语法有三个部分:

  • 目标: target
  • 类型(type) PRIMARY
  • 以及在哪里安装(where)
# target "hello" is a program that will be installed in $bindir
bin_PROGRAMS = hello

# target "libabc" is a library that will be installed in $libdir
lib_LIBRARIES = libabc.la

The targets is a list of targets with the type PRIMARY. Depending on what PRIMARY is, it can be a program, a library, a shell script, or whatever PRIMARY supports. The current primary names are “PROGRAMS”, “LIBRARIES”, “LTLIBRARIES”, “LISP”, “PYTHON”, “JAVA”, “SCRIPTS”, “DATA”, “HEADERS”, “MANS”, and “TEXINFOS”.

targettype为 PRIMARY 的目标列表。取决于 PRIMARY 是什么,它可以是程序(program)库(library)、 shell 脚本或者 PRIMARY 支持的任何东西。目前的主要名称是

  • “ PROGRAMS”、
  • “ LIBRARIES”、
  • “ LTLIBRARIES”、
  • “ LISP”、
  • “ PYTHON”、
  • “ JAVA”、
  • “ SCRIPTS”、
  • “ DATA”、
  • “ HEADERS”、
  • “ MANS”和
  • “ TEXINFOS”。

您可以在 where 子句中放入三种可能的变量类型。

  • GNU 标准目录变量(bindrsbindirincludedir 等) 省略了后缀“ dir”。有关预定义目录的列表,请参阅 GNU 编码标准-目录变量 GNU Coding Standard - Directory Variables。Automake 通过 pkgdatadirpkgincludedirpkglibdirpkglibexecdir 扩展了这个列表,它将检查您的目标是否有效,以便安装您指定的目录。
  • 自定义目录(Self-defined directories)。您可以通过定义自己的目录来自动进行默认类型检查。不要这样做,除非你有一个很好的理由!
# Work around forbidden directory combinations.  Do not use this
# without a very good reason!
my_execbindir = $(pkglibdir)
my_doclibdir = $(docdir)
my_execbin_PROGRAMS = foo
my_doclib_LIBRARIES = libquux.a
  • 特殊前缀 noinst_, check_, dist_, nodist_, nobase_, and notrans_noinst_ 指示不想安装的目标; check_ 用于单元测试。对于其他不太常见的,请查阅automake使用手册的细节。

target_SECONDARY = inputs

根据您的 PRIMARY 类型,可以使用不同的 SECONDARY 类型进行进一步的逻辑。常见的 SECONDARY 类型是

  • _SOURCES 定义 primary type _PROGRAMS or _LIBRARIES 的源代码

  • _CFLAGS, _LDFLAGS, etc. 编译器标志,用于 primary type _PROGRAMES or _LIBRARIES

    注意,目标名称中的无效字符将被下划线替换 , 下面的例子对这个规则进行了阐述.

lib_LTLIBRARIES = libgettext.la
# the dot got substituted with underscore
libgettext_la_SOURCES = gettext.c gettext.h
include_HEADERS = gettext.h

上面的例子需要 libtool,你需要在你的 configure.ac 中声明 AC_PROG_LIBTOOL才能正常工作。

Wraps it up - A X11 example program

到目前为止,我们已经掌握了所有的知识,现在让我们编写一个更复杂的 autoconf 程序。这是一个非常简单的 x11程序,目标是在安装有效 x11的所有现有平台上都可移植。为了测试是否安装了 X11,我们使用宏 AC_PATH_XTRA,这个宏的手册是在 autoconf 现有的 autoconf existing test for system services.中定义的。

The manual says: An enhanced version of AC_PATH_X. It adds the C compiler flags that X needs to output variable X_CFLAGS, and the X linker flags to X_LIBS. Define X_DISPLAY_MISSING if X is not available. And in the AC_PATH_X it states “If this method fails to find the X Window System … set the shell variable no_x to ‘yes’; otherwise set it to the empty string”. We can use the logic and write our configure.ac script as following:

手册上说: 一个增强版的 AC_PATH_X。它添加了 (软件)X 需要的C编译器标志到输出变量: X_CFLAGS,以及 X(软件)连接器标志X_LIBS。如果 X 不可用,定义X_DISPLAY_MISSING 。在 AC_PATH_X中,它表明 “是否这个方法找不到 x Window System... ... 将 shell 变量 no_x 设置为‘ yes’; 否则将其(no_x)设置为空字符串”。我们可以使用这个逻辑编,并编写如下的 configure.ac 脚本:

AC_INIT([x11-example], [1.0])

# safety check in case user overwritten --srcdir
AC_CONFIG_SRCDIR([x11-example.c])

AC_CONFIG_AUX_DIR([build-aux])

AM_INIT_AUTOMAKE([-Wall -Werror foreign])

# Check for C compiler
AC_PROG_CC

# Check for X11
# It exports variable X_CFLAGS and X_LIBS
AC_PATH_XTRA

# AC_PATH_XTRA doesn't error out by default,
# hence we need to do it manually
AS_IF([test "X$no_x" = "Xyes"],
  [AC_MSG_ERROR([Could not find X11])])

AC_CONFIG_FILES([Makefile])

AC_OUTPUT

注意 AC_PATH_XTRA 导出变量 X_CFLAGS and X_LIBS。To use these variables in Makefile.am, just surround it with @.

bin_PROGRAMS = x11-example

x11_example_SOURCES = x11-example.c
x11_example_CFLAGS = @X_CFLAGS@
# AX_PATH_XTRA only specify the root of X11
# we still have to include -lX11 ourselves
x11_example_LDFLAGS = @X_LIBS@ -lX11

这是所有我们需要的以建立一个平台独立的 x11程序!检查 github 上的完整源代码。X11示例程序是由 Brian Hammond 编写的,编写时间是96年2月9日。他慷慨地把这个公布于众,供任何人使用。

这个程序可以很容易地在 Linux 上工作。我将使用 OSX 作为一个跨平台工作的例子。在运行该示例之前,请确保安装了 XQuartz

cd example-2
autoreconf -vif # shortcut of --verbose --install --force
./configure --with-x --x-includes=/opt/X11/include/ --x-libraries=/opt/X11/lib
make
./x11-example

如果你将 xquartz 安装到不同的位置,则将--x-includes and --x-libraries 更改为(相应的)目录。

我只为 autoconf (if-else,print message)和 automatake (primary/secondary,使用导出变量 @ )引入了很少的语法。但是仅仅使用这些基本组件已经足够编写传统的构建脚本了。怎么做?检查[existing tests provided by autoconf][exsisting test]。以下是一些最常用的现有检查:

对于默认的 autoconf 包中不包含的检查,它可能存在于扩展包 autoconf archive中,我将在下一篇文章中介绍。

评论

发表评论


取消