Autotools上手指南
本文转自:DCCMX
Autotools上手指南1——autoconf基本思想
要想弄懂Autotools并使用它,必须先要了解一下M4这个怪物。
那么何为M4呢,M4的名称取自Macro(M后面跟4个字母…)。它和C预处理器里的宏是一个概念(其实,M4和C预处理器都K&R操刀设计的!!),用来处理文本替换。也就是说,M4是bash里的预处理器。
取自维基的例子:
divert(-1) This `divert' discards this text. Note that I had to quote the `divert' in the comment so it wouldn't get undiverted. This starts the count at ONE as the incr is a preincrement. define(`H2_COUNT', 0) The define H2_COUNT is redefined every time the H2 macro is used. The dnl deletes everything on the line after it in the output (ie this bit) define(`H2', `define(`H2_COUNT', incr(H2_COUNT))'dnl `<h2>H2_COUNT. $1</h2>') divert(0)dnl diversion to 0 means back to normal dnl removes this line. H2(First Section) H2(Second Section) H2(Conclusion)
这段M4宏用m4处理输出的结果就是:
<h2>1. First Section</h2> <h2>2. Second Section</h2> <h2>3. Conclusion</h2>
简单说下:
1.M4的语法跟C里面宏的语法差不多,都很像函数,名字加括号。
2.M4里的参数即使声明了也是可以忽略的,如果一个宏一个参数都不加的话,括号都可以忽略。
懂了吧。就当你懂了。
autoconf就是基于M4这个工具来生成configure脚本的。
我们来看一个最简单的autoconf输入,将下面代码存为configure.ac:
AC_INIT([test], [1.0]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT
再新建一个文件Makefile.in内容如下:
all: @PACKAGE_NAME@_bin @PACKAGE_NAME@_bin: test.c gcc -o @PACKAGE_NAME@_bin test.c
好,下面执行命令:
$ autoconf $ ./configure
看看,是不是生成了Makefile,是不是下面这样:
all: test_bin test_bin: test.c gcc -o test_bin test.c
中间发生了什么?停我慢慢道来。
首先,autoconf读取configure.ac里的宏,并调用M4处理这些宏,做些检查,如果你很感兴趣,这些宏的实现可以在/usr /share/autoconf/autoconf/*.m4里找到这些宏的定义(看了不要发疯)。检查完会生成两个文件:configure。执行 configure脚本生成config.status和config.log。config.log里面是执行的记录,config.status是下 一步执行的脚本,用来生成Makefile,configure生成完这个脚本会自动调用的。
那Makefile.in又是什么呢?那是第二个宏AC_CONFIG_FILES([Makefile])执行时的默认输入。方括号是用来表示参 数的。就像上面的‘`’一样。Makefile.in就是一个模板,用来生成Makefile。执行configure脚本时(实际上是 config.status脚本),里面的%变量%会被替换成相应的内容。有哪些变量?看看config.log就知道了。
好了,autoconf的基本原理就这样了,本质上就做了件检查环境和变量替换的事。其他的事情以后慢慢讲
Autotools上手指南2——autoscan生成configure.ac
前面说了autotools的基本原理:将configure.ac里的宏展开,运行,生成Makefile。其实,对于大多数项目来 说,configure.ac里的内容基本框架都差不多。既然都差不多那么有没有什么工具可以帮我们做这些基本的事情呢?别忘了你在linux下,有!
autoscan就是干这事的。
下面我们建个test项目吧。目录如下:
test ├── README └── src ├── client.c └── server.c
在test目录下运行autoscan看看。是不是生成了如下的configure.scan呢(autoscan.log被我们无情的忽略了):
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.68])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
AC_CONFIG_SRCDIR([src/server.c])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
AC_CHECK_HEADERS([netinet/in.h stdlib.h strings.h sys/socket.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_TYPE_PID_T
# Checks for library functions.
AC_FUNC_FORK
AC_FUNC_MALLOC
AC_CHECK_FUNCS([bzero socket])
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
我们把这个文件稍微改改,即可用来当做configure.ac了,先将它重命名为configure.ac吧。
我们解释一下这个文件里的一些宏。
首先是,AC_PREREQ([2.68]),这个宏用来检查你机器上的autoconf版本的。这里是我机器上的版本号2.68。
然后AC_INIT宏,这里你要将里面的内容改改,里面的提示你懂的。我们改成下面这样:
AC_INIT([test], [0.1], [dccmx@test.com])
紧跟着是AC_CONFIG_SRCDIR([src/server.c]),这是autoconf用来定位自己的所在目录的宏,里面的内容是所在目录下的任意一个文件就可以了,你可以改成一个更能代表你项目的文件(这样不会更其他项目冲突),也可以不动。
下面是AC_CONFIG_HEADERS([config.h]) 这个是用来生成标准的config.h头文件的宏,这个宏的意思是,从将模板里的@***@变量替换掉生成config.h头文件。模板是什么?默认 是*.in,你记得的(Makefile.in),这里就是config.h.in。我们稍后还会提到。
下面就是一系列的check了,最后是生成Makefile的文件,我们介绍过。
好了,改完了,autoconf命令看看吧。
autoconf如愿生成了configure,运行configure,是不是很标准的输出呢,但是最后提示错了,因为我们还没有添加config.h.in和Makefile.in
Makefile.in文件还像以前的:
all: @PACKAGE_NAME@_client @PACKAGE_NAME@_server @PACKAGE_NAME@_client: gcc -o @PACKAGE_NAME@_client src/client.c @PACKAGE_NAME@_server: gcc -o @PACKAGE_NAME@_server src/server.c
config.h.in呢?我们写一个吧,从上面autoscan生成的configure.ac来看,宏 AC_CHECK_HEADERS([netinet/in.h stdlib.h strings.h sys/socket.h unistd.h])告诉我们的程序调用了这些头文件,那么我们的config.h.in就这样写:
#undef HAVE_NETINET_IN_H #undef HAVE_STDINT_H #undef HAVE_STDIO_H #undef HAVE_STDLIB_H #undef HAVE_STRING_H #undef HAVE_SYS_SOCKET_H #undef HAVE_UNISTD_H #undef HAVE_NONEXIST_H
看到啦,我们先把这些头文件全都按规则undef掉,我们还加了个系统不存在的头文件试试,现在在运行configure脚本看看,是不是出现了下面的config.h头文件呢:
/* config.h. Generated from config.h.in by configure. */ #define HAVE_NETINET_IN_H 1 #define HAVE_STDINT_H 1 #define HAVE_STDIO_H 1 #define HAVE_STDLIB_H 1 #define HAVE_STRING_H 1 #define HAVE_SYS_SOCKET_H 1 #define HAVE_UNISTD_H 1 /* #undef HAVE_NONEXIST_H */
看,所有存在的头文件都被define成了1,不存在的被注释掉。好吧,在你的程序里尽情的include吧,生活真美好。现在可以make生成可执行文件了。test_client和test_server是不是如愿生成了呢?应该是的!
现在我们项目中仍然有两个.in文件要我们自己写:config.h.in和Makefile.in,关于这两个文件的工具,我们后面再谈。拉上老婆出去逛逛吧。歇会儿。
Autotools上手指南3——autoheader和automake
前面提到,config.h.in和Makefile.in还要手写,现在我们就来看看,Autotools里有那些工具帮助我们完成这些体力活。
首先是config.h.in。这个非常简单,弄好configure.ac后,直接在项目根目录运行下面命令
autoheader
看看,是不是生成了config.h.in了,看看内容。真轻松啊,autoheader工具分析了configure.ac里面所有要检查的东西,然后生成了相应的宏,好了,可以用了。
Makefile.in的生成就比较复杂了,毕竟Makefile是个复杂的东东。
我坦白,要自动生成Makefile.in以便让configure自动生成Makefile你必须再手动写个Makefile.am文件(这就是灵活的代价和unix的哲学)。
所谓Makefile.am其实就是automake用来生成Makefile.in的模板,里面就像一般的Makefile,只不过加了一些automake宏。
回顾前面的目录结构,我们需要在根目录和所有源码目录添加Makefile.am文件,如下:
test ├── README ├── configure.ac ├── Makefile.am └── src ├── Makefile.am ├── client.c └── server.c
先来看根目录下的Makefile.am文件:
SUBDIRS = src
灰常简单!
再看src目录下的Makefile.am文件:
bin_PROGRAMS = client server client_SOURCES = client.c server_SOURCES = server.c
同样灰常简单,这个文件,不解释,你懂的。现在应该淡定些了。
好,现在我们还需要建一些文件AUTHORS、COPYING、NEWS、ChangeLog和README,这些文件都是gnu项目的标准文件,automake会检查这些文件的。所以,如果不想要的话先建个空文件搪塞一下吧,等automake过了在删也不迟!!
最后一步,在configure.ac里面加上automake支持的宏
# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ([2.68]) AC_INIT([test], [1.0], [test@dccmx.com]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([src/server.c]) AC_CONFIG_HEADERS([config.h]) # Checks for programs. AC_PROG_CC # Checks for libraries. # Checks for header files. AC_CHECK_HEADERS([netinet/in.h stdlib.h strings.h sys/socket.h unistd.h]) # Checks for typedefs, structures, and compiler characteristics. AC_TYPE_PID_T # Checks for library functions. AC_FUNC_FORK AC_FUNC_MALLOC AC_CHECK_FUNCS([bzero socket]) AC_CONFIG_FILES([Makefile src/Makefile]) AC_OUTPUT
看到了,我们现在初始化部分加了AM_INIT_AUTOMAKE 来初始化automake,在最后又改了AC_CONFIG_FILES([Makefile src/Makefile]) ,来吧src目录下的Makefile也加入管理。
好了,都准备好了,开始干活:
dccmx@~/projects/console/test$ aclocal dccmx@~/projects/console/test$ autoconf dccmx@~/projects/console/test$ autoheader dccmx@~/projects/console/test$ automake –a –c dccmx@~/projects/console/test$ make
神清气爽啊。在src目录里生成两个二进制文件了。make distclean看看?一切恢复如初!make dist看看,自动打包发布。生活真是美好啊。
等等,第一个命令是干什么的呢,这个命令是用来生成automake依赖的宏的,供autoconf调用(因为automake其实不是autoconf 的一部分,只相当于插件)。automake后面的两个参数又是干什么的呢?因为automake生成的Makefile需要一些外部脚本辅助,而项目里 是没有的,所以-a就是把这些文件自动添加就来,-c就是指定用复制的方式添加,否则是符号链接。
好了,基本的autotools的介绍就这些了。更高级的话题,后面有时间再谈吧。洗洗睡了。
Autotools上手指南4——深加工
有了前面几篇文章介绍的几招,基本的构建系统就算完成了。开始离专业水平还有一定距离。我们现在看看一些后续的方法,让我们的构建脚本更加专业。
1.设置库依赖和预处理宏:
设置依赖库和宏的方法很简单,只要维护gcc的参数就ok了。这些参数在Makefile.am里面维护。
先在configure.ac里面加上相关的宏:AM_PROG_CC_C_O。
要添加预处理宏的话(比如_GNU_SOURCE宏)只要在Makefile.am里面添加xxx_CFLAGS = -D_GNU_SOURCE。好了。重新configure吧。
依赖的库呢?你猜对了,在Makefile.am里面添加xxx_LDFLAGS = -lssl -lidn -lz就ok了。生活。。。真美好!
2.检查目标系统上的库:
如何检查目标系统上有没有我们程序依赖的库呢。很简单,在configure.ac里面加上AC_CHECK_LIB宏。其实autoscan会检 查Makefile.am中的_LDFLAGS而自动在configure.ac里面添加相关的check宏的。这个宏的原型如下:
AC_CHECK_LIB(library, function, [action-if-found], [action-if-not-found], [other-libraries])
中间的function可以选择lib里面最典型的一个函数,用来测试找到的lib是不是你要的lib,你懂的。举个例子:
AC_CHECK_LIB([ssl], [SSL_get_peer_certificate], [have_ssl=yes])
在在下面加入:
if test "x${have_ssl}" = xno; then AC_MSG_ERROR([ ------------------------------------------ Unable to find ssl on this system. ------------------------------------------]) fi
好了,找不到libssl,或者libssl不对(里面没有SSL_get_peer_certificate),就会提示了。
检查头文件呢?你猜对了。
AC_CHECK_HEADER(header-file, [action-if-found], [action-if-not-found], [includes])
3.部署文件:
目前为止,默认make install已经可以将我们的bin文件安装到/usr/local/bin下了(可以用–prefix改)。如果我们要安装其他文件呢,比如默认配置文件啊等等。
比如我们在xml目录下有a.xml b.xml要安装到默认的@data@/xml目录下(默认是/usr/local/share)。
第一步,在xml目录下建Makefile.am。内容如下:
xmldir = $(datadir)/xml xml_DATA = a.xml b.xml
第二步,在configure.ac里面的AC_CONFIG_FILES([Makefile src/Makefile]) 加上这个Makefile,改成AC_CONFIG_FILES([Makefile src/Makefile xml/Makefile])
第三步,重新autoconf,automake。其实只要一个命令autoreconf就行了。这个命令会替你调用autoheader和automake的。
想要make dist的时候包含到压缩包里?在xml_DATA前面加上dist变成:dist_xml_DATA就ok了。
好了,make install看看。