GNU AutoMake,Autoconf完成编译配置详解

——学习笔记

Posted by Samuel on July 31, 2017

目录

Autotools使用流程

使用autotools工具主要两部分:

  1. 按照顺序调用各个工具
  2. 修改configure.ac, m4, Makefile.am三个文件

流程图如下:

image

具体操作流程如下:

  1. 源码根目录调用autoscan脚本,生成configure.scan文件,然后将此文件重命名为configure.ac(或configure.in,早期使用.in后缀)
  2. 修改【configure.ac】,利用autoconf提供的各种M4宏,配置项目需要的各种自动化探测项目
  3. 编写【自定义宏】,建议每个宏一个单独的*.m4文件;
  4. 调用aclocal收集configure.ac中用到的各种非Autoconf的宏,包括自定义宏;
  5. 调用autoheader,扫描configure.ac(configure.in)、acconfig.h(如果存在),生成config.h.in宏定义文件,里面主要是根据configure.ac中某些特定宏(如AC_DEFINE)生成的#define和#undefine宏,configure在将根据实际的探测结果决定这些宏是否定义(具体见后面例子)。
  6. 按照automake规定的规则和项目的目录结构,编写一个或多个【Makefile.am】(Makefile.am数目和存放位置和源码目录结构相关),Makefile.am主要写的就是编译的目标及其源码组成。
  7. 调用automake,将每个Makefile.am转化成Makefile.in,同时生成满足GNU编码规范的一系列文件(带-a选项自动添加缺少的文件,但有几个仍需要自己添加,在执行automake前需执行touch NEWS README AUTHORS ChangeLog)。如果configure.ac配置了使用libtool(定义了AC_PROG_LIBTOOL宏(老版本)或LT_INIT宏),需要在此步骤前先在项目根目录执行libtoolize –automake –copy –force,以生成ltmain.sh,供automake和config.status调用。
  8. 调用autoconf,利用M4解析configure.ac,生成shell脚本configure。以上几步完成后,开发者的工作就算完成了,后面的定制就由开源软件的用户根据需要给configure输入不同的参数来完成。
  9. 用户调用configure,生成Makefile,然后make && make install。

一个例子

下面,我们会通过Autoconf手册中的amhello例子,来具体学习如何编写这些配置文件。

configure

由Autoconf创建的配置脚本通常写作configure,运行的时候,configure创建几个文件,用适当的值替换配置中的参数,这些文件分别是:

  1. 一个或者多个Makefile文件,通常每个源码包及其子目录中都会包含一个。
  2. 一个可选、可配置的的C头文件,通常包含#define
  3. 一个config.status的文件,标识配置的状态。运行时,会重新创建上述几个文件
  4. 一个通常写为config.cache的shell脚本,(在调用configure –config-cache时生成),它保存了运行configure中多项测试结果(在configure中会使用test来检测各种配置环境)。
  5. 一个config.log文件,包含了多项编译器的log信息,来帮助调试configure时候会出错。

通过Autoconf来创建configure文件,我们需要编写一个辅助Autoconf运行的输入文件configure.ac(这个大家应该常见),然后再运行autoconf。如果我们需要编写自己的自定义的测试特征(自定义宏)来补充Autoconf,我们应该写一个文件aclocal.m4和acsite.m4。如果我们使用了c头文件来包含这些#define的宏定义的话,我们还需要运行autoheader,然后就会生成一个config.h的头文件。在下面的例子中我们会比较明显的感受到,这个头文件基本包含在各个源码文件中供我们直接使用宏。

configure的运行过程

下面图表显示了配置文件使如何产生的,(图示中后缀为*的表示运行程序,可选文件使用闭合的[]),autoconf和autoheader同样需要读取Autoconf的宏文件(通过阅读autoconf.m4)。

你的源文件 --> [autoscan*] --> [configure.scan] --> configure.in

configure.in --.   .------> autoconf* -----> configure
               +---+
[aclocal.m4] --+   `---.
                       |
                       +--> [autoheader*] -> [config.h.in]
                       |
[acsite.m4]      +-----'


Makefile.in 

如果我们使用了Automake,会有下面的过程

[acinlcude.m4] --.
                 |
[local macros] --+--> aclocal* --> aclocal.m4
                 |
configure.ac ----'

configure.ac --.
               +--> automake* --> Makefile.in
Makefile.am ---'

下述文件是软件包配置过程中用到的文件(简介中提到的几个文件)

                       .-------------> [config.cache]
configure* ------------+-------------> config.log
                       |
[config.h.in] -.       v            .-> [config.h] -.
               +--> config.status* -+               +--> make*
Makefile.in ---'                    `-> Makefile ---'

上述的过程中,很多的文件我们都可以通过Autoconf, Automake, configure来生成,直接得到我们需要的Makefile文件。我们需要编写一般有:configure.ac, makfile.am,和自定义宏m4。按照顺序,下一节,我们从configure.ac来说起。

自动生成configure.ac

configure.ac的标准结构

每个configure.ac开头必须包含一个称作AC_INIT的宏,并且末尾必须包含一个AC_OUTPUT的宏。另外,对于一些被其他宏依赖的宏,必须出现在前面先被调用,因为这些宏需要检查一些变量的值来确定接下来该怎么做。而且,如果顺序不正确的话在configure的过程中会有警告。 当然,为了保持书写过程中的一致性,这里有一个建议的Autoconf宏的顺序。通常来说,出现的后面的宏一般都可以依赖前面出现的宏。比如说,库函数会受到一些类型和库影响。

下面是一个例子

AC_INIT(package, version, bug-report-address )
information on the package
checks for programs
checks for libraries
checks for header files
checks for types
checks for structures
checks for compiler characteristics
checks for library functions
checks for system services
AC_CONFIG_FILES([file...])
AC_OUTPUT

具体:

#                      -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.49])
AC_INIT([HelloWorld], [1], [564628276@qq.com])
AM_INIT_AUTOMAKE([foreign])
LT_INIT
AC_CONFIG_SRCDIR([HelloWorld/my_module.cc])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIR([m4])

# Checks for programs.
AC_PROG_CXX
AC_PROG_LIBTOOL
AC_PROG_RANLIB
AM_PROG_CC_C_O

# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_OUTPUT([Makefile HelloWorld/Makefile container/Makefile HelloWorld/proto/Makefile])

使用autoscan来创建configure.ac

autoscan程序可以帮助我们创建或者维护一个软件包中configure.ac的文件。autoscan自动扫描检查源文件根目录下的目录树(如果指定目录)或者是当前的目录(未指定)。autoscan通过搜索元文件查找常见的移植性文件,并且创建configure.scan文件,这个文件就是configure.ac的前身。并且检查已有的configure.ac是否完整。

当然,在使用了autoscan创建configure.scan文件后,我们在重命名为configure.ac前最好手动检查下,有时候会需要一个调整。autoscan偶尔也会输出一些错误顺序的宏,然后autoconf就会发出警告,这时候我们需要手动调整下顺序。如果我们想在源码包中使用配置头文件(通常为config.h),我们必须添加一个AC_CONFIG_HEADER宏,我们也可能会需要增加或修改一些#if条件定义,来帮助Autoconf完成工作。

在使用autoscan来生成configure.ac的时候,通常简单的增加一些建议的条件,在autoscan.log中包含了每个宏为什么是必须的。 autoscan在源码包中发现特殊的标识时会使用多个数据文件来决定输出哪些宏(macro),这些数据文件的格式使一致的:每行包含一个标识,一个 或者多个空格,如果遇到一个标识就输出相应的宏,行首#表示注释。

使用ifnames来输出条件

ifnames可以帮助我们为一些源码包编写configure.ac,它可以打印出这个包中已有的正在使用的C预处理器条件,它可以帮助填写一些有autoscan生成的configure.ac的不足。ifnames扫描所有的c源文件,并按顺序以#if,#elif,#ifdef,#ifndef的形式输出。

使用autoconf创建configure

使用autoconf程序执行(不带参数)autoconf会使用m4宏处理器和Autoconf宏来处理configure.ac,如果加参数执行的话,autoconf会读取参数指定的文件而不是configure.ac,如果参数是 - 的话,会从标准输入中读取并输出到标准输出。 Autoconf宏定义在多个文件中,有些文件使Autoconf生成的,由autoconf优先读取,然后autoconf再检查acsite.m4文件查看包含Autoconf的其他文件。

使用autoreconf来更新configure脚本文件

为GNU Build System安装不同的组件却是很烦人,比如运行autopoint来安装Gettext,在各个目录下automake来生成Makefile.in等等,但是有时候这些都是必须的,如果因为一些改动比如说-更新configure.ac了,还得重新再来一次。 autoreconf可以看做是autoconf、autoheader、acloacl、automake、libtoolize、autopoint的组合体,而且会以合适的顺序来执行。 貌似到这里还是不知道configure怎么来的,基础概念及过程清楚了,我们下面一节就通过automake自带的例子amhello-1.0.tar.gz,来看看怎么实现自动化编译配置的。

GNU Build System使用案例Hello World!

这个例子来自于automake安装时自带的示例程序,在 /usr/share/doc/automake/amhello-1.0.tar.gz,使用linux系统的童鞋可以找找。我们也可以完全自己来创建。

准备文件

这个hello world 例子非常简单,只需要写五个文件:

首先创建源码目录cd && makdir amhello,作为源码根目录。 进入cd amhello, 创建源码目录mkdir src 此时目录结构为 ~/amhello/src ,在src目录下创建第一个文件main.c,源码如下

#include <config.h>
#include <stdio.h>
int main (void)
{
 puts ("Hello World!");
 puts ("This is " PACKAGE_STRING ".");
 return 0;
}

在amhello目录下创建项目描述文件README,内容随意了….

Makefile.am和 src/Makefile.am包含了Automake在这两个目录的说明,所以在amhello目录下创建Makefile.am,在src/下创建Makefile.am.内容分别如下:

  1. amhello/src/Makefile.am
    bin_PROGRAMS = hello
    hello_SOURCES = main.c
    
  2. amhello/Makefile.am
    SUBDIRS = src
    dist_doc_DATA = README
    

    最后创建configure.ac,来辅助Autoconf生成configure脚本。内容如下:

    AC_INIT([amhello], [1.0], [bug-automake@gnu.org])
    AM_INIT_AUTOMAKE([-Wall -Werror foreign])
    AC_PROG_CC
    AC_CONFIG_HEADERS([config.h])
    AC_CONFIG_FILES([
    Makefile
    src/Makefile
    ])
    AC_OUTPUT
    

执行编译安装过程

上述五个文件准备好之后,我们在amhello目录下执行 autoreconf –install (前面文章讲过autoreconf的作用,相当于aclocal,autoconf等程序的作用)。会看到如下结果:

~/amhello % autoreconf --install
configure.ac: installing ’./install-sh’
configure.ac: installing ’./missing’
configure.ac: installing ’./compile’
src/Makefile.am: installing ’./depcomp

这就表示构建完成。 除了看到在输出中三个脚本文件,我们可以看到autoreconf创建了另外四个文件:configure,config.h,Makefile.in和src/Maiefile.in,我们会发现有Makefile.am的目录下都会生成Makefile.in。后三个文件是configure脚本生成适应系统config.h,Makefile,src/Makefile的模版。

执行./congfigure

checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for gawk... no
checking for mawk... mawk
checking whether make sets $(MAKE)... yes
checking for gcc... gcc
checking for C compiler default output file name... a.out
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables...
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking for style of include used by make... GNU
checking dependency style of gcc... gcc3
configure: creating ./config.status
config.status: creating Makefile
config.status: creating src/Makefile
config.status: creating config.h
config.status: executing depfiles commands

然后我们就会看到生成了Makefile,src/Makefile和config.h,现在我们就可以运行我们期望生成的目标了。 在amhello目录下执行make,然后会在src目录下按照配置的目标生成src/hello,执行src/hello

Hello World!
This is amhello 1.0.

image

amhello’s 中configure.ac配置解释

我们回顾下configure.ac的内容:

AC_INIT([amhello], [1.0], [bug-automake@gnu.org])
AM_INIT_AUTOMAKE([-Wall -Werror foreign])
AC_PROG_CC
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([
Makefile
src/Makefile
])
AC_OUTPUT

这个文件需要被autoconf(创建configure)和 automake(创建不同的Makefile.am s)。它包含了一些列的M4宏,这些会被扩展为shell代码然后最终组织到configure脚本中,关于这个文件具体语法Autoconf手册都有。 我们可以看到这个文件中的宏的前缀都为AC_,这些都是Autoconf宏,关于每个宏的作用可以查询Autoconf手册。

AC_INIT

前两行

AC_INIT([amhello], [1.0], [bug-automake@gnu.org])
AM_INIT_AUTOMAKE([-Wall -Werror foreign])

作为Autoconf 和Automake的初始化。AC_INIT 需要的参数分别为,AC_INIT([包名],[版本号],[报告bug的邮箱]),这个邮箱地址我们可以在执行./configure –help命令查看到。版本号和包名在打包的时候会用到,通过执行 make dist可以把源码中主要的文件打包成 包名-版本号.tar.gz 。

AC_INIT_AUTOMAKE

AM_INIT_AUTOMAKW参数是一组关于automake执行的选项。-Wall 和-Werror 让automake 打开所有的警告和报告错误。这里说道的警告比如说在Makefile.am中的可疑指令,这些指令和将怎么调用编译器没任何关系,即使是可以通过类似命名的支持的选项。对于我们新手来说,通过使用 -Wall -Werror 是对待包构建的一个非常安全的设置,我们并不想错过任何的问题。熟练之后,我们可以适当放宽,知道哪些是可以留下的。foreign选项通知Automake程序这个包的构建不遵循Gnu 标准。因为在Gnu 标准中必须有这几个文件比如说:Changelog,AUTHORS等等,和我们平常使用的github项目类似,一般要求有一个README.md的文件来描述项目。我们不希望 automake 报告缺失这些文件。

AC_PROG_CC

AC_PORG_CC这行最终的作用是让configure脚本来在系统中搜索c 编译器,并且把c编译器定义为变量CC ,然后src/Makefile.in文件就可以通过Automake使用CCa 来生成执行程序hello,所以当 configure 通过 src/Makefile.in 创建了 src/Makefile 文件的时候,它就会把找到的C编译器定义为CC , 如果要求Automake通过CC创建Makefile.in,而 configure.ac中并没有定义CC ,它就会提示我们增加一条 AC_PROC_CC.

AC_CONFIG_HEADER

AC_CONGIF_HEADER([config.h]),这个宏主要是用来创建config.h,它会把configure.ac中的被其它宏定义的宏以#define的形式集中到config.h中。在我们这个例子中,AC_INIT宏已经定义了一些宏了。这里是执行./configure后的config.h的内容:

/* config.h.  Generated from config.h.in by configure.  */
/* config.h.in.  Generated from configure.ac by autoheader.  */
/* Name of package */
#define PACKAGE "amhello"
/* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT "bug-automake@gnu.org"
/* Define to the full name of this package. */
#define PACKAGE_NAME "amhello"
/* Define to the full name and version of this package. */
#define PACKAGE_STRING "amhello 1"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "amhello"
/* Define to the home page for this package. */
#define PACKAGE_URL ""
/* Define to the version of this package. */
#define PACKAGE_VERSION "1"
/* Version number of package */
#define VERSION "1"

我们会发现,src/main.c中头文件中包含了config.h,所以这个时候main.c就可以直接使用PACKAGE_STRING。在实际项目中,config.h会非常大,几乎系统每个特性都会有一个#define定义。

AC_CONFIG_FILES

AC_CONFIG_FILES宏声明了configure 通过Makefile.ins 需要生成的一系Makefile文件。Automake 同样要扫描这个列表来查找需要处理的Makefile.am。(特别强调:一旦我们在工程中增加了新的目录,我们应该把该目录下的Makefile增加到这个列表中,否则的话,即使我们在这个目录下谢了Makefile.am,Automake也 不会处理)。

AC_OUTPUT

AC_OUTPUT 是一个结束命令,实际上是AC_CONFIG_HEADERS和AC_CONFIG_FILES命令完成的结束,并将这俩处理后产生的文件输出。 对于一个新的工程的话,我们最好是以这样一个简单的的configure.ac文件开始,然后逐渐增加测试需求。autoscan命令同样可以帮助我们增加一些需要的测试需求。手动的话也可以先执行autoscan,然后再修改,注意文件中不同测试宏的顺序。

amhello 中Makefile.am配置解释

我们现在看下src/Makefile.am

bin_PROGRAMS = hello
hello_SOURCES = main.c

Makefile.am的语法和Makefile文件的语法是一样的。automake处理Makefile.am的时候它就把Makefile.am完全拷贝到Makefile.in中(Makefile.in文件会在之后被configure处理为Makefile),但是会根据一些构建规则和变量改变一些变量的定义。通常Makefile.ams 只包含一组上述例子中的变量定义,但是他们同样可以包含一些变量和规则定义,这些会通过automake不加解释地传递进来。

bin_PROGRAMS

以_PROGRAMS为后缀的在变量列表中属于特殊的变量,它们是Makefile 最终需要生成的。我们示例中最后的科执行文件就是hello。从Automake角度看,将bin_PROGRAMS分为两部分,_PROGRAMS后缀结束的变量为主要变量,Automake识别其他的主要变量比如说 _SCRIPTS,_DATA,_LIBRARIES等等,分别对应不同的文件。

而bin_PROGRAMS中的bin部分使用来告诉automake最终编译生成的程序应该安装到bindir.

在GNU Build System中使用了一系列变量来提供目标目录的位置,并且允许用户自定义这些路径的位置。任何这样的变量都可以用这种形式来指定不同的文件放到哪些位置。 比如:

  1. bindir 用于安装由用户运行的可执行文件的目录。
  2. datadir 用于安装只读的与结构无关的数据的目录。
  3. includedir 用于安装C头文件的目录。

可以参见GNU编码标准中这些变量的定义。

而且,Automake同样知道在创建打包文件时需要将不同类型的文件以分布式的形式放开,而hello_SOURCES的副作用就是在执行make dist是main.c成为tarball的一部分。 现在我们再看下工程目录下的Makefile.am。

SUBDIRS = src
dist_doc_DATA = README
SUBDIRS

SUBDIR 是一个特殊的变量,列出了make在处理当前目录之前递归处理的目录。所以在这个例子中,先用make处理src/,然后再处理amhello下的文件。同样在make install的时候先安装src/README,然后再安装src/hello。

dist_doc_DATA

dist_doc_DATA = README,因为README将要被安装到docdir中,由于_DATA后的文件在dist打包的时候不会被创建进去,所以我们在_DATA前加了一个dist 前缀,这个README并不是必须的。比较重要的影响是在make install 的时候把README安装进去。