Autoconf 教程 Part-2 [翻译]
这是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 -p
,AS_ 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”.
target
是type
为 PRIMARY 的目标列表。取决于 PRIMARY 是什么,它可以是程序(program)、库(library)、 shell 脚本或者 PRIMARY 支持的任何东西。目前的主要名称是
- “ PROGRAMS”、
- “ LIBRARIES”、
- “ LTLIBRARIES”、
- “ LISP”、
- “ PYTHON”、
- “ JAVA”、
- “ SCRIPTS”、
- “ DATA”、
- “ HEADERS”、
- “ MANS”和
- “ TEXINFOS”。
您可以在 where 子句中放入三种可能的变量类型。
- GNU 标准目录变量(
bindr
、sbindir
、includedir
等) 省略了后缀“ dir”。有关预定义目录的列表,请参阅 GNU 编码标准-目录变量 GNU Coding Standard - Directory Variables。Automake 通过pkgdatadir
、pkgincludedir
、pkglibdir
和pkglibexecdir
扩展了这个列表,它将检查您的目标是否有效,以便安装您指定的目录。 - 自定义目录(
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_
, andnotrans_
。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]。以下是一些最常用的现有检查:
- Library checks:
AC_CHECK_LIB
AC_SEARCH_LIBS
. library documentation. - Header checks:
AC_CHECK_HEADER[S]
. header documentation - Compiler characteristics 编译器特性.
对于默认的 autoconf 包中不包含的检查,它可能存在于扩展包 autoconf archive中,我将在下一篇文章中介绍。