Autotools: Autoconf, Automake and Libtool

来源:互联网 发布:淘宝茶叶生产许可证 编辑:程序博客网 时间:2024/05/21 17:26
原文: http://blog.chinaunix.net/uid-22028566-id-2973016.html

       在Automake发明之前,Autoconf是单独被使用的。有很多遗留下来的开源项目并没有完成转换成基于Autotool工具的形式。所以如果你看到一个开源项目包含了一个名为configure.in和一个手工的Makefile.in文件,你不应该感到奇怪。

Autoconf工作方式 ---- configure脚本
    
后面我们将会发现,花点时间去只单独使用Autoconf是大有裨益的。通过这种方式(不使用Automake等其它Autotool工具)我们可以认清被Automake这些后来的工具所隐藏的Autoconf的操作原理。
     Autoconf的输入是shell脚本,这可能会让你大跌眼镜!当然这个输入并不是纯shell脚本。而是一个包含宏的shell脚本,加上一些宏定义文件。这些宏定义文件一些来自Autoconf工具本身,一些是你或我(当然也有可能是其它人)编写的。使用的宏语言叫做M4(名字有点搞笑,它其实表示了Macro,即M后面跟了四个字母,类似的词有i18n,表示internationalization,l10n表示localization)。M4工具是一个通用宏语言处理器,由Brian Kernighan和Dennis Ritchie 在1977年编写的。
某些形式的M4宏语言处理器在几乎所有的Unix和Linux平台上都支持,这也是为什么Autoconf使用它的原因。Autoconf设计目的就是要能在所有系统上都可以正常使用,而不需要附加的复杂的工具集。Autoconf只依赖于很少的一些工具,包括m4,sed,在新的2.26版本上需要awk。大多数Autotool(Autoconf除外)工具需要用到perl处理器。
注意,不要把Autotool的需求依赖和Autotool生成的脚本以及makefiles的需求依赖混淆在一起。Autotools是项目维护人员的工具,而它们生成的脚本和makefiles则是给用户的工具。

configure.ac是由掺杂着M4语法的shell脚本语句组成,关于如何正确地使用M4宏处理器,我们在这里先不做详细地介绍,留到第七章再讲。这一章的主要目的是帮助你理解Autoconf的概念,然而也需要提及小部分的M4的内容。

最小的configure.ac文件最小的configure.ac可能只有两行:
$ cat configure.ac
AC_INIT([jupiter], [1.0])
AC_OUTPUT
$
相对于Autoconf的其它新玩意,这两行语句看起来像是某个晦涩难懂的计算机语言中的函数调用,但是不要被表面误导---这两句是宏扩展。Autoconf发布包中的文件中定义了这两个宏。其中$PREFIX/share/autoconf/autoconf/general.m4定义了AC_INIT,同目录下的status.m4定义了AC_OUTPUT。
M4宏类似于C语言的预处理宏,C预处理器也是一个文本替换工具。这其实没有什么奇怪的,因为C预处理器和M4处理器都是Kernighan和Ritchie设计的。
Autoconf中宏的参数用中括号([,])引用。其实有时候中括号并不需要,只要M4处理器可以以唯一的方式理解这个宏。当然在这里我们不需要讨论这些细节,只要记得在所有的参数都用中括号括起来。
和C预处理宏一样,M4宏也有可能不带参数。但是如果一旦带有参数,就得使用圆括号将参数括起来,并且左圆括号必须紧跟在宏名字后面,中间不能有空格。如果没带参数,圆括号会被忽略。和CPP(c preprocessor)不一样的是,M4可以有可选(optional)参数,在这种情况下,如果你选择不传这个参数,那么你就可以不用写圆括号。

把autoconf.ac传递给Autoconf后会生成同样的文件(这时候叫做configure),只是这两个宏已经被完全扩展开了。如果你打开configure文件,会发现扩展完后的configure文件超过2200行,大小超过60K!而且有趣的是,如果你去查看这些宏的定义,你也看不出这些庞大的内容是如何得到的,因为这两个宏的定义也非常短。这是因为这些宏是以模块化的方式编写的,每个宏扩展成多个宏,而这些宏再扩展成其它更多的宏,如此反复,直至不能再扩展为止。

执行Autoconf
执行autoconf是非常简单的事情,只需要在configure.ac所在目录下运行autoconf程序。在这一章里,每个示例我都可以运行autoconf,但我最愿意使用工具autoreconf(注意中间的"re")。autoreconf能做autoconf同样的事情,但当你开始添加Automake和Libtool功能时,它同时会做另外一些“恰到好处"的事情。autoreconf是用来执行Autotool工具链的最理想的选择,它足够的智能,只执行你所需要的工具,并且以你所需要的顺序来执行这些工具,连带一些你需要的选项(有一个例外,稍后会提到)。
$autoreconf
$ ls -lp
autom4te.cache/
configure
configure.ac
$

请注意,Autoconf生成了一个叫做autom4te.cache的目录。这个一个autom4te(念做automate)的缓存目录。这个缓存可以使后续的Autotool工具调用能更快地访问configure.ac文件。我会在第9章中详细地详解autom4te,在那一章里,我将展示如何添加我们自己的Autoconf宏---环境友好宏。

执行configure
在第2章最后一节中我们提到,GNU编码标准中指出,configure应该会生成一个名为config.status的脚本,它的工作是从模板中生成文件。Autoconf生成的configure文件恰好有这样的功能。一个Autoconf生成的configure脚本主要有两个任务:
1  完成一些检查
2  生成然后调用config.status

所有检查的结果会以环境变量的形式写入到config.status文件的头部,config.status会查找模板文件(Makefile.in,config.h.in,etc)中的Autoconf替换变量(substitution variables),然后将这些替换变量的值替换成生成的环境变量值。
当你执行configure时,它会告诉你它正在生成config.status文件,事实上,它也会生成一个叫做config.log的文件,这个文件有几个重要的属性。
$ ./configure
configure: creating ./config.status
$
$ ls -lp
autom4te.cache/
config.log
config.status
configure
configure.ac
$

config.log文件包含以下的信息:
1  调用configure的命令行
2  configure执行所在的平台的信息
3  configure执行的核心测试的信息
4  在configure中config.status被生成和被调用的行号

config.log中记录config.status被创建的行号后,config.status接管了生成后面的log的任务---- 它在其后增加了调用config.status的命令行。config.status生成了所有从模板文件转换的文件后退出,把控制权交还给configure,configure再接着添加下面的信息到config.log中:
1  config.status完成它的任务时所使用的缓存变量(cache variables)
2  在模板文件中可能被替换的输出变量列表
3  configure返回给shell的退出状态码
这些log信息在调试configure以及和它相关的configure.ac文件时很有帮助。

执行config.status
既然你知道了configure是如何工作的,你可能会想是不是能够只执行config.status,而不去执行configure,避免configure总是首先做那些费时的检查工作。事实上你是对的。这也是Autoconf设计者们的意图,也是GNU编码标准的作者们的意图,最初这个设计目标是由他们提出来的。
事实上,有时候你只是想从对应的模板文件中生成所有你想要的输出文件。更重要的是,有时候config.status也可以被Makefiles用来单独地从它们的模板文件中生成它们自己,当make发现某个模板文件内容发生改变的时候。
如果只有模板文件发生了改变,而环境没有改变,你不需要重新运行configure来做无用的检查工作。生成的makefiles应该能够确保生成的文件会依赖于对应的模板文件。如果一个模板文件改变了(比如说,你修改了一个Makefile.in文件),make会调用config.status来重新生成对应的文件。一旦Makefile被重新生成,make会重新执行最初的make命令行----基本上它会重启它自己。这实际上也是make工具的一个特点。

让我们来看一看Makefile.in模板文件中相关的部分内容:
Makefile: Makefile.in config.status
./config.status Makefile

另外一个make工具的有趣特点是,它总是会查找一个名为Makefile的目标所对应的规则。这样的规则会让make工具重新生成makefile文件,当模板文件发生改变的时候。make会在用户指定的目标,以及默认目标前先执行Makefile目标对应的规则。
上面的例子表明,Makefile依赖于Makefile.in。注意Makefile也依赖于config.status文件。毕竟如果config.status被configure重新生成,那么config.status可能会生成一个不同的makefile----可能是编译环境发生改变了,例如一个新的包添加进了系统,这样configure可能就能找到之前找不到的库和头文件,在这种情况下,Autoconf替换变量可能会有不同的值。因此当Makefile.in和config.status之一发生改变时,makefile都应该重新生成。
因为config.status本身也是一个生成的文件,同样的规则可以应用到config.status目标上,扩展后的例子如下:
Makefile: Makefile.in config.status
./config.status $@
config.status: configure
./config.status --recheck

添加一些有用的功能
好了,现在我们该往configure.ac中添加一些真正的功能。前面我一直在说config.status生成makefile文件。其实下面的代码才能让config.status生成makefile,在之前的两个宏之间添加了另外一个宏:
$ cat configure.ac
AC_INIT([jupiter],[1.0])
AC_CONFIG_FILES([Makefile
src/Makefile])
AC_OUTPUT
$

上面的代码假定了我们的源码树里有Makefile和src/Makefile的模板文件,分别叫做Makefile.in和src/Makefile.in。这些模板文件看起来和Makefile差不多,唯一的区别就是,在这些模板文件里,任何需要Autoconf来替换的文本都应该标记成Autoconf替换变量,使用@variable@的语法。
为了创建这些文件,我在之前存在的Makefile的基础上,将它们重命名为Makefile.in。然后我添加了一些Autoconf替换变量,用来替换我的初始默认值。事实上,我在文件的顶部,在一个Makefile的注释符#的后面,添加了一个Autoconf特定的替换变量,叫做@configure_input@,这个注释行在生成的Makefile中将变成下面的文本:
# "Makefile.   Generated from Makefile.in by conf...

顶层Makefile.in的内容如下:
Makefile.in
# @configure_input@
# Package-related substitution variables
package        = @PACKAGE_NAME@
version        = @PACKAGE_VERSION@
tarname        = @PACKAGE_TARNAME@
distdir        = $(tarname)-$(version)
# Prefix-related substitution variables
prefix         = @prefix@
exec_prefix    = @exec_prefix@
bindir         = @bindir@
...
$(distdir):
mkdir -p $(distdir)/src
cp configure $(distdir)
cp Makefile.in $(distdir)
cp src/Makefile.in $(distdir)/src
cp src/main.c $(distdir)/src

distcheck: $(distdir).tar.gz
gzip -cd $+ | tar xvf -
cd $(distdir); ./configure
$(MAKE) -C $(distdir) all check
$(MAKE) -C $(distdir) \
DESTDIR=$${PWD}/$(distdir)/_inst \
install uninstall
$(MAKE) -C $(distdir) clean
rm -rf $(distdir)
@echo "*** Package $(distdir).tar.gz is\
ready for distribution."

Makefile: Makefile.in config.status
./config.status $@

config.status: configure
./config.status --recheck
...


src下的Makefile.in如下:
src/Makefile.in
# @configure_input@
# Package-related substitution variables
package        = @PACKAGE_NAME@
version        = @PACKAGE_VERSION@
tarname        = @PACKAGE_TARNAME@
distdir        = $(tarname)-$(version)
# Prefix-related substitution variables
prefix         = @prefix@
exec_prefix    = @exec_prefix@
bindir         = @bindir@
...
Makefile: Makefile.in ../config.status
cd .. && ./config.status $@
../config.status: ../configure
cd .. && ./config.status --recheck
...

我在顶层目录的Makefile.in文件的变量前,删掉了export。然后把所有这些变量都复制到了src/Makefile.in文件的顶部。这是因为config.status会同时生成这两个Makefile,我可以让config.status来替换这两个文件中的所有替换变量。这样做的好处是,我现在可以在任意目录下执行make了,而不用管环境变量是否已从上层Makefile中传递过来。
另外我也修改了发布目标(distribution target),之前我是需要发布Makefile,现在我需要发布的是Makefile.in,以及configure文件。distcheck也做了一点修改,让它在尝试运行make之前先执行configure,这样才能生成Makefile。

0 0