在 Linux 中,经常使用 GNU 构建系统,也就是利用脚本和 make 程序在特定平台上构建软件,这种方式几乎成为一种习惯,被广泛使用。
这里简单介绍下 AutoTools 一系列工具的使用。
Autotools
在 Linux 平台上,经常使用 configure->make->make install
从源码开始编译安装,也就是 GNU 构建系统,利用脚本和 make 程序在特定平台上编译软件,也就是利用 autotools 创建构建系统的项目。
注意,有些程序虽然也是采用上述的三步,但并未采用 autotools 实现,如 nginx 是作者编写的构建程序。
在 CentOS 中可以直接通过 yum install automake autoconf
安装相关的文件;利用 autotools 生成 Makefile 的过程如下图所示。
其中用到的核心工具包括了 Autoconf 和 Automake ,首先从用户和开发者角度看看两者的区别。关于 autotools 的简单处理流程可以参考 automake 中的 Setup Explained
内容。
用户视角
configure
脚本是由软件开发者维护并发布给用户使用的 shell 脚本,该脚本作用是检测系统环境,最终目的是生成 Makefile 和 config.h 两个文件。
开发者在分发源码包时,除了源代码 (.c .h),还有许多支撑软件构建的文件和工具,其中最重要的文件就是 Makefile.in 和 config.h.in 两个,在 configure 脚本执行成功后,将为每个 *.in
文件处理成对应的非 *.in
文件。
configure
configure 脚本会检查当前系统,而检查项的多少取决于开发者,一般来说,主要检查当前目标平台的程序、库、头文件、函数等的兼容性,而检查结果将作用于 config.h 和 Makefile 文件的生成,从而影响最终的编译。
用户可通过参数定制软件所需要包含的组件、安装路径等,一般会被五部分,可以通过 --help
参数查看当前软件提供了那些配置参数。
在 configure 在执行过程中,除了生成 Makefile 外,还会生成如下的临时文件:
- config.log 日志文件;
- config.cache 缓存,以提高下一次 configure 的速度,需通过 -C 来指定才会生成;
- config.status 实际调用编译工具构建软件的 shell 脚本。
如果软件通过 libtool 构建,还会生成 libtool 脚本,关于 libtool 脚本如何生成,详见如下。
开发者视角
开发者除了编写软件本身的代码外,还需要负责生成构建软件所需要文件和工具,通过 autotools 工具可以解决一些常见的平台问题,但是编写依旧复杂。为了生成 configure 脚本和 Makefile.in 等文件,开发者需要创建并维护一个 configure.ac 文件,以及一系列的 Makefile.am 文件。
Autoconf
该工具用于生成一个可在类 Unix 系统下工作的 Shell 脚本,该脚本可在不同 *nix 平台下自动配置软件源代码包,也就是 configure 文件,生成脚本后与 autoconf 再无关系。
Automake
该工具通过 Makefile.am 文件自动生成 Makefile.in 文件,而 Makefile.am 基本上是一系列 make 宏定义,该文件需要手动编辑,也就是将对 Makefile 的编辑转义到了 Makefile.am 文件。
常见文件
接下来看下一些常用的文件。
configure.ac
用于通过 autoconf 命令生成 configure 脚本,如下是一个 configure.ac 的示例:
AC_PREREQ([2.69])
AM_INIT_AUTOMAKE(hello, 1.0)
AC_INIT([hello], [1.0], [www.douniwan.com])
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADERS([src/config.h])
# Checks for programs.
AC_PROG_CC
AC_PROG_LIBTOOL
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([Makefile # 主要是通过*.in模板生成响应的文件
src/Makefile
src/a/Makefile
src/b/Makefile])
AC_OUTPUT
以 AC_
开头的是一些宏调用,与 C 中的宏概念类似,会被替换展开;很多以 AC_PROG_XXX 开头的宏用于检查所需要的程序是否存在,详细可以查看 Particular Program Checks;对于一些特殊的函数或者文件则可以通过 Generic Program and File Checks 中定义的宏进行检查。
而 m4 是一个经典的宏工具,autoconf 正是构建在 m4 之上,可以简单理解为 autoconf 预先实现了大量用于检测系统可移植性的宏,这些宏在展开后就是大量的 shell 脚本。所以编写 configure.ac 需要对这些宏熟练掌握,并且合理调用,有时,甚至可以自己实现自己的宏。
configure.scan
通过 autoscan 命令可以得到一个初始化的 configure.scan 文件,然后重命名为 configure.ac 后,在此基础上编辑 configure.ac ,而 autoscan 通常 只用于首次初始化 configure.ac 。
autoscan 会扫描源码,并生成一些通用的宏调用、输入的声明以及输出的声明。
config.h.in
可以通过 autoheader 命令扫描 configure.ac 中的内容,并生成 config.h.in 文件;每当 configure.ac 文件有所变化,都可以再次执行 autoheader 更新 config.h.in 。
在 configure.ac 中通过 AC_CONFIG_HEADERS([config.h])
告诉 autoheader 应当生成 config.h.in 的路径;在最后的实际编译阶段,生成的编译命令会加上 -DHAVE_CONFIG_H
定义宏。
/bin/sh ../../libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H ...
于是在代码中,可以通过下面代码安全的引用 config.h 。
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
config.h 包含了大量的宏定义,其中包括软件包的名字等信息,程序可以直接使用这些宏;更重要的是,程序可以根据其中的对目标平台的可移植性相关的宏,通过条件编译,动态的调整编译行为。
Makfile.am
手工编写 Makefile 是一件相当烦琐的事情,而且,如果项目复杂的话,编写难度将越来越大;为此,可以通过 automake+Makefile.am 生成 Makefile.in 文件,通常一个 Makefile.am 的示例如下。
SUBDIRS = a b
bin_PROGRAMS = st
st_SOURCES = main.c
st_LDADD = $(top_builddir)/src/a/liba.la $(top_builddir)/src/b/libb.la
通过 SUBDIRS 声明了两个子目录,子目录的中的构建需要靠 a/Makefile.am 和 b/Makefile.am 来进行,这样多目录组织起来就方便多了。
bin_PROGRAMS 声明一个可执行文件,st_SOURCES 指定所依赖的源代码文件,st_LDADD 声明了可执行文件在连接时,需要依赖的 Libtool 库文件。
由于 automake 晚于 autoconf,所以 automake 是作为 autoconf 的扩展来实现的,在 configure.ac 中需要通过声明 AM_INIT_AUTOMAKE 告诉 autoconf 需要配置和调用 automake 。
aclocal
如上所述,configure.ac 是依靠宏展开来得到 configure 的,因此,能否成功生成取决于宏定义能否找到;默认 autoconf 会从安装路径下来寻找事先定义好了宏,而对于像 automake、libtool 和 gettext 等第三方扩展宏,甚至是开发者自行编写的宏就一无所知了。
于是,存在这个工具 aclocal,将在 configure.ac 同一目录下生成 aclocal.m4,在扫描 configure.ac 的过程中,将第三方扩展和开发者自己编写的宏定义复制进去;这样,autoconf 在遇到不认识的宏时,就会从 aclocal.m4 中查找。
libtool
libtool 试图解决不同平台下库文件的差异,实际是一个 shell 脚本,实际工作过程中,调用了目标平台的 cc 编译器和链接器,以及给予合适的命令行参数,libtool 可以单独使用。
automake 支持 libtool 构建声明,在 Makefile.am 中,普通的库文件目标写作 xxx_LIBRARIES :
noinst_LIBRARIES = liba.a
liba_SOURCES = ao1.c ao2.c ao3.c
而对于 libtool 目标,写作 xxx_LTLIBRARIES,并以 .la 作为后缀声明库文件。
noinst_LTLIBRARIES = liba.la
liba_la_SOURCES = ao1.c ao2.c ao3.c
在 configure.ac 中需要声明 LT_INIT:
...
AM_INIT_AUTOMAKE([foreign])
LT_INIT
...
有时,如果要用到 libtool 中的某些宏,则推荐将这些宏复制到项目中。首先,通过 AC_CONFIG_MACRO_DIR([m4])
指定使用 m4 目录存放第三方宏;然后在最外层的 Makefile.am 中加入 ACLOCAL_AMFLAGS = -I m4
。