docs/Makefile.am
docsdir = $(pkgdatadir)/docs docs_DATA = gkai00mp.txt gpl.html readme.txt EXTRA_DIST = $(docs_DATA)
fonts/Makefile.am
fontsdir = $(pkgdatadir)/fonts fonts_DATA = brush.ttf gkai00mp.ttf EXTRA_DIST = $(fonts_DATA)
images/Makefile.am
imagesdir = $(pkgdatadir)/images
images_DATA = bgame.jpg \
mjgirl1a.jpg \
mjgirl2a.jpg \
mjgirl3a.jpg \
mjgirl4a.jpg \
tiles.jpg \
electron.jpg \
mjgirl1b.jpg \
mjgirl2b.jpg \
mjgirl3b.jpg \
mjgirl4b.jpg \
gameover.jpg \
mjgirl1c.jpg \
mjgirl2c.jpg \
mjgirl3c.jpg \
mjgirl4c.jpg
EXTRA_DIST = $(images_DATA)
music/Makefile.am
musicdir = $(pkgdatadir)/music
music_DATA = bet.ogg \
bonus.ogg \
music.ogg \
musicb.ogg \
musice.ogg \
win.ogg \
bgame.ogg \
gameover.ogg \
music1.ogg \
musicc.ogg \
musicp.ogg
EXTRA_DIST = $(music_DATA)
sound/Makefile.am
sounddir = $(pkgdatadir)/sound
sound_DATA = boom.wav \
ding.wav \
discard.wav \
discard2.wav \
flash.wav \
snd1.wav \
snd2.wav \
snd3.wav \
snd4.wav
EXTRA_DIST = $(sound_DATA)
运行 autotools
准备好 configure.ac 和 Makefile.am,就可以用 autotools 的命令处理这些文件。开始可能会出现错误,不过没关系,可以按照错误信息的提示逐步进行修正。
首先要使用的是 aclocal 命令,它根据 configure.ac 的定义,将需要使用的 m4 宏定义复制到 aclocal.m4 里面。缺省时,搜索 m4 宏是从 autoconf 的安装目录和系统的 aclocal 目录。如果需要使用其他路径下的宏,可以通过命令行的 -I 选项指定。
接着使用 autoheader 命令,它负责生成 config.h.in 文件,这里面的 C 语言宏定义也是通过解析 configure.ac 产生。
下来运行 automake 命令处理 Makefile.am,生成 Makefile.in。GNU 对自己发布的软件有严格的规范,比如必须附带许可证声明文件 COPYING 等等,否则 automake 执行时会报错。automake 提供了三种软件等级: foreign、gnu 和 gnits,让用户选择采用,默认等级为 gnu。本例使用 foreign 等级,它只检测必须的文件。有一些必需的脚本文件可以从 automake 软件包里复制过来,在执行时使用 --add-missing 选项可以让 automake 自动添加,默认方式是采用符号链接,如加上 --copy 选项则可以使用复制方式。本例中,automake 的命令如下:
$ automake --foreign --add-missing --copy
最后,使用 autoconf 命令生成 configure 脚本文件。
SDL 库的侦测
这个麻将游戏是基于 SDL 库开发的,一般系统默认不会安装,因此 configure 脚本的一个任务就是检查用户的系统中是否有该软件包。
autoconf 提供了很多宏可以实现侦测功能,但首先应该查看 SDL 软件包是否已经提供相应的宏。通过 pkgsrc 的工具可以看到:
$ pkg_info -L SDL|grep m4 /usr/pkg/share/aclocal/sdl.m4
即 SDL 软件包提供了一个 sdl.m4 宏,放在系统的 aclocal 目录下。
在这个宏文件的注释中说明了使用的方法:
dnl AM_PATH_SDL([MINIMUM-VERSION,[ACTION-IF-FOUND[,ACTION-IF-NOT-FOUND]]]) dnl Test for SDL, and define SDL_CFLAGS and SDL_LIBS
也就是说在 configure.ac 里面调用 AM_PATH_SDL 宏,就可以侦测 SDL。找到 SDL 库以后,该宏还输出 SDL_CFLAGS 和 SDL_LIBS 编译连接选项,它们实际上就是调用 `sdl-config --cflags` 和 `sdl-config --libs`。
于是在 configure.ac 里面加入 AM_PATH_SDL 宏
# Checks for libraries.
SDL_VERSION=1.2.0
AM_PATH_SDL($SDL_VERSION,
:,
AC_MSG_ERROR([*** SDL version $SDL_VERSION not found!])
)
当前 SDL 的版本为 1.2.9,于是 MINIMUM-VERSION 就设为 1.2.0。如果在系统中侦测到需要的库,没什么额外的操作,假如没有找到,则给出错误信息。
AM_PATH_SDL 输出 SDL_CFLAGS 和 SDL_LIBS 编译参数,需要添加到 src/Makefile.am 里面:
mj_CPPFLAGS = @SDL_CFLAGS@ mj_LDFLAGS = @SDL_LIBS@
用 `@' 包围的变量会在 configure 执行时被替换。
从 mahjong 的 Makefile 中看到,这个软件还要使用 SDL_image、SDL_mixser 和 SDL_ttf 库,但它们不属于 SDL 软件包,需要另外安装。由于这些库在 sdl.m4 中也没有进行侦测,所以自己要写一些脚本。
autotools 提供了一个 AC_CHECK_LIB 宏可以用来检测库,现在就使用它来检测这几个 SDL 库。该宏的语法为:
AC_CHECK_LIB (LIBRARY, FUNCTION, [ACTION-IF-FOUND],
[ACTION-IF-NOT-FOUND], [OTHER-LIBRARIES])
第一个参数是库名,第二个参数是库中的一个函数,第三个参数是检测到以后进行的动作,第四个参数是未检测到以后的动作,第五个参数是其他的库。
对于 SDL_image、SDL_mixer 和 SDL_ttf 对应的使用方法如下:
# Check for SDL_image library AC_CHECK_LIB(SDL_image, IMG_Load, , AC_MSG_ERROR([ *** Unable to find SDL_image libary with PNG support (http://www.libsdl.org/projects/SDL_image/) ]), `sdl-config --libs`) # Check for SDL_mixer library AC_CHECK_LIB(SDL_mixer, Mix_LoadMUS, , AC_MSG_ERROR([ *** Unable to find SDL_mixer libary with OGG support (http://www.libsdl.org/projects/SDL_mixer/) ]), `sdl-config --libs`) # Check for SDL_ttf library AC_CHECK_LIB(SDL_ttf, TTF_OpenFont, , AC_MSG_ERROR([ *** Unable to find SDL_ttf libary (http://www.libsdl.org/projects/SDL_ttf/) ]), `sdl-config --libs`)
其中 IMG_Load、Mix_LoadMUS 和 TTF_OpenFont 分别是源码中调用的函数。
软件使用的数据文件
原来 mj 读取数据是从执行时目录的子目录中读取,但现在将数据放到 $prefix/share/majiang 目录下,需要通过一种途径让程序可以知道数据文件被安放的位置。
要达到这个目的有很多方法,这里采用最直接的一种:将数据文件安装目录变量通过 CPPFLAGS 编译参数传递给程序。
于是修改 src/Makefile.am 的 CPPFLAGS:
mj_CPPFLAGS = @SDL_CFLAGS@ -DDATA_DIR=\"${datadir}/majiang\"
相应地修改 src 目录下的源码,在读取数据文件的地方,将读取的路径改成 DATA_DIR 里对应的子目录。例如,原先 config.cpp 中是:
void LoadCfg()
{
cfg.Load("data/mj.ini");
}
现改成:
void LoadCfg()
{
cfg.Load(DATA_DIR"data/mj.ini");
}
configure 选项
原来 mahjong 的 Makefile 第 22 行定义了 debug 调试选项,虽然也可以照样放到 src/Makefile.am 的 CPPFLAGS 里面实现,但 autotools 提供了一种更灵活的机制。
configure 脚本可以通过选项来设置编译参数,现增加一个 --enable-debug 选项,需要 DEBUG 时,在命令行上加上它来打开,默认则关闭。
这项功能是使用 AC_ARG_ENABLE 宏实现:
AC_ARG_ENABLE (FEATURE, HELP-STRING, [ACTION-IF-GIVEN],
[ACTION-IF-NOT-GIVEN])
其中 FEATURE 是名称,HELP_STRING 为说明信息,在使用 ./configure --help 时可以看到。最后两个分别对应打开和关闭时的操作。
现在将 DEBUG 功能加入 configure.ac:
AC_ARG_ENABLE(debug, [ --enable-debug turn on debug], CXXFLAGS="$CXXFLAGS -g3 -D_DEBUG=1")
autotools 脚本
每次修改了 configure.ac 或 Makefile.am 等 autotools 输入文件后都需要再次运行 aclocal、automake、autoconf 这些命令,为了方便起见,可以将他们放到一个 shell 脚本里面,例如:
#! /bin/sh set -x aclocal autoheader automake --foreign --add-missing --copy autoconf
将上面内容保存到 autogen.sh 文件,并修改文件属性为 755。每次需要重新生成 configure 脚本时,执行 ./autogen.sh 即可。
使用 configure 产生的 Makefile
现在执行 ./autogen.sh 得到的 configure 脚本已经可以正常工作了,进入 ~/work/majiang 目录,执行 ./configure,可以看到它检查系统的过程,包括 SDL 和 SDL_image 等库的侦测结果。使用 ./configure -help 可以看到 autotools 提供的帮助信息。
configure 执行的完毕,输出软件根目录和几个子目录下面的 Makefile 文件。这些 Makefile 有几个常用的 target:
- make all
不加任何 target,默认就是 all,作用是编译软件
- make install
安装软件包,如果安装到系统目录,需要 root 权限
- make clean
清除编译产生的目标文件
- make distclean
可以同时清除编译的结果和 configure 输出的文件
- make tags
生成 etags 使用的 TAGS 文件
- make dist
生成软件发布包,为 tar.gz 格式的压缩包,文件名由软件包名和版本组成。
最终的 configure.ac 文件
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
AC_INIT([majiang], [1.0])
AC_CONFIG_SRCDIR([src/main.cpp])
AC_CONFIG_HEADER([config.h])
AC_CANONICAL_HOST
AC_CANONICAL_TARGET
AM_INIT_AUTOMAKE
# Checks for programs.
AC_PROG_CXX
AC_PROG_CC
AC_LANG(C++)
# Checks for libraries.
SDL_VERSION=1.2.0
AM_PATH_SDL($SDL_VERSION,
:,
AC_MSG_ERROR([*** SDL version $SDL_VERSION not found!])
)
# Check for SDL_image library
AC_CHECK_LIB(SDL_image, IMG_LoadPNG_RW, , AC_MSG_ERROR([
*** Unable to find SDL_image libary with PNG support
(http://www.libsdl.org/projects/SDL_image/)
]), `sdl-config --libs`)
# Check for SDL_mixer library
AC_CHECK_LIB(SDL_mixer, Mix_LoadOGG_RW, , AC_MSG_ERROR([
*** Unable to find SDL_mixer libary with OGG support
(http://www.libsdl.org/projects/SDL_mixer/)
]), `sdl-config --libs`)
# Check for SDL_ttf library
AC_CHECK_LIB(SDL_ttf, TTF_OpenFont, , AC_MSG_ERROR([
*** Unable to find SDL_ttf libary
(http://www.libsdl.org/projects/SDL_ttf/)
]), `sdl-config --libs`)
# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([limits.h malloc.h stdlib.h string.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_HEADER_STDBOOL
AC_C_CONST
AC_C_INLINE
# Checks for library functions.
AC_FUNC_MALLOC
AC_FUNC_REALLOC
AC_CHECK_FUNCS([memset strcasecmp strchr strdup])
AC_ARG_ENABLE(debug,
[ --enable-debug turn on debug],
CXXFLAGS="$CXXFLAGS -g3 -D_DEBUG=1")
AC_CONFIG_FILES([Makefile
src/Makefile
data/Makefile
docs/Makefile
fonts/Makefile
images/Makefile
music/Makefile
sound/Makefile])
AC_OUTPUT
结束语
GNU 的很多工具经常给人一种感觉: 功能很强大,但也很难学。autotools 可以说是这类工具的一个典型,它需要用户对 shell、make、软件编译、m4 宏语言,以及 Unix/Linux 操作系统各方面知识都有一定的了解。使用时又要 autoconf、automake、libtool 多个工具相互配合,如果要给软件增加国际化功能,还要再了解和掌握 gettext、po 等工具和规则。
与学习其他知识一样,所谓难,其实是不了解,不熟悉。本文通过一个范例演示使用 autotools 的过程,是让不了解的人熟悉这个工具。但真正的理解,还需要将它运用到自己的软件项目当中,不断地实践,不断地思考和总结。








