Linux 自动编译 AutoTools

2015-11-30 language linux c/cpp

在 Linux 中,经常使用 GNU 构建系统,也就是利用脚本和 make 程序在特定平台上构建软件,这种方式几乎成为一种习惯,被广泛使用。

这里简单介绍下 AutoTools 一系列工具的使用。

Autotools

在 Linux 平台上,经常使用 configure->make->make install 从源码开始编译安装,也就是 GNU 构建系统,利用脚本和 make 程序在特定平台上编译软件,也就是利用 autotools 创建构建系统的项目。

注意,有些程序虽然也是采用上述的三步,但并未采用 autotools 实现,如 nginx 是作者编写的构建程序。

在 CentOS 中可以直接通过 yum install automake autoconf 安装相关的文件;利用 autotools 生成 Makefile 的过程如下图所示。

autotools

其中用到的核心工具包括了 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