2006年5月22日星期一

使用automake和autoconf管理项目的上手指南(旧文转贴)





使用automake和autoconf管理项目的上手指南





Tiger Dong












前言





本文肯定不是一个有关automake和autoconf的权威指南。如果你想找这种文档,可以参看autobook。 本文记录了作者在一个小程序中,用automake和autoconf管理项目的心得。因为作者也是初学乍练,所以本文既不能保证全面,也不能保证准确。 但是对新手而言,本文可以帮你避开冗长难懂的autobook(特别是对英文不好的朋友:),让你迅速上手,用automake和autoconf提高工 作效率,写出更好更规范的软件。





automake和autoconf干吗使的





你编译过啥GNU的程序没?除了象mozilla, firefox, linux kernel或openoffice这些恐龙级的程序有自己一套代码编译发布系统外,大部分的GNU软件的源代码包都是用下面几步来编译安装的:


  1. 执行./configure。这是一个shell脚本,它会很专业的检查系统环境,比如使用什么编译器,有没有哪个库之类,它还能带各种各样的参数,如果各项检查通过,它就会生成Makefile;

  2. 执行make进行编译;

  3. make install。这就把编好的程序安装到系统里去了。



很酷是吧?很专业是吧?这背后就是automake和autoconf的功劳。这套autotools工具大大减少了Makefile编写的工作量。下面就一步步的讲解它们的用法。





Step by Step









项目介绍





用来操刀演练的程序是作者写的一个小游戏(雏形),源文件大约10来个,还包括一些数据文件,主要是 一些图片。在此处可以下载。

解开后的目录解构图如下所示:
/ ---  src  (保存源代码)
|
|_ gamedata (保存数据文件) ---- sprite (精灵动画) --- drop (往下掉的动画)
|_ left (往左跑的动画)
|_ right (往右跑的动画)
|_ stand (站着的动画)






先写Makefile.am





除 了程序本身用到的文件,在每层目录下还有给automake使用的Makefile.am,Makefile.am是automake的输入,可以认为它 是更高层次的Makefile。坏消息是:这些Makefile.am你可得自己写,电脑不是你肚子里的蛔虫:)好消息是,写完这些就差不多大功告成了, 呵呵。

根目录下的Makefile.am内容很简单,很明显就是告诉automake继续处理这两个子目录,以此类推,有子目录的目录中的Makefile.am都免不了这么一句。
SUBDIRS = src gamedata

稍微复杂点的是$ROOT/gamedata/sprite/left下的Makefile.am,要告诉automake把数据文件装到哪:
EXTRA_DIST=1.bmp 2.bmp info
leftdir = $(datadir)/runner/sprite/left/
left_DATA = $(EXTRA_DIST)

leftdir指明数据安装的路径,left_DATA指明要安装哪些数据文件。

更复杂点的呢是$ROOT/src下的Makefile.am:


INCLUDES = -I/usr/include/SDL -D_REENTRANT -I/usr/include/sigc++-2.0 -I/usr/lib/sigc++-2.0/include
bin_PROGRAMS = runner
runner_SOURCES =
ai.cpp animation.cpp anisprite.cpp gamestage.cpp gamesys.cpp
main.cpp sprite.cpp spriteone.cpp stagetwo.cpp
ai.h animation.h anisprite.h common.h gamestage.h gamesys.h sprite.h spriteone.h stagetwo.h
runner_LDFLAGS = -lSDL -lpthread -lsigc-2.0
AM_CPPFLAGS = -g -O0 -DDATADIR=\"$(datadir)\"

这里用bin_PROGRAMS指定了最终要生成的可执行文件,用runner_SOURCES指定生成可执行文件runner需要的源文件。除此之外还有编译连接时的参数。大家可以看出,这其实就是Makefile的另外一种写法。





生成configure.in





configure.in是autoconf的输入,autoconf的输出是configure脚本。

笔 者开始傻傻的手写configure.in,后来才知道在$ROOT下执行一次autoscan就可以生成这个文件(叫configure.scan)。 autoscan会根据Makefile.am来生成configure.scan,所以在执行autoscan之前一定要先写好 Makefile.am,这样能省不少事哦。还有,debian下的autoscan运行时会报找不到configure.ac的错,其实不影响使用。

自 动生成的configure.scan还需要修改才能用。前文说道,笔者是个菜鸟,因此别指望我把这个configure.in的用法解释得清清楚楚。不 过希望我说得够你用了:)下面就是修改前后的configure.in的对照,前面有-号表示要被删掉的行,+号表示增加的行,中文的注释是偶写的。
AC_PREREQ(2.59)
-AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)
+AC_INIT(runner, 0.1, idlecat511@yahoo.com.cn)
#下面这句一定要加
+AM_INIT_AUTOMAKE(runner, 1.0)
AC_CONFIG_SRCDIR([src/ai.cpp])
#config.h我还没研究,只好把它注释掉先。
-AC_CONFIG_HEADER([config.h])

# Checks for programs.
AC_PROG_CXX
AC_PROG_CC

# Checks for libraries.
# FIXME: Replace `main' with a function in `-lSDL':
# 注意这里,autoscan在src/Makefile.am中找到-lSDL这个选项,然后生成了下面这个语句来测试
# SDL的可用性。不过它不知道SDL里面有啥函数,所以要你手工把main换成SDL里的一个函数
-AC_CHECK_LIB([SDL], [main])
+AC_CHECK_LIB([SDL], [SDL_Init])
# FIXME: Replace `main' with a function in `-lpthread':
# 同SDL,奇怪的是,-lsigc-2.0为什么没有对应的项?
-AC_CHECK_LIB([pthread], [main])
+AC_CHECK_LIB([pthread], [pthread_create])

# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([stdlib.h])

# Checks for typedefs, structures, and compiler characteristics.
AC_HEADER_STDBOOL
AC_C_CONST

# Checks for library functions.
# 因为源代码里调用了floor函数,autoscan就生成了这项,看起来autoscan做的工作不少啊!
AC_CHECK_FUNCS([floor])

AC_CONFIG_FILES([Makefile
gamedata/Makefile
gamedata/sprite/Makefile
gamedata/sprite/drop/Makefile
gamedata/sprite/left/Makefile
gamedata/sprite/right/Makefile
gamedata/sprite/stand/Makefile
src/Makefile])
AC_OUTPUT






大功告成





到这就算完了,剩下你要做的就是执行:aclocal;autoconf;automake --add-missing。这样你就会发现目录下面多了好多文件,其中就有你日思夜想的configure。






补充问题









使用预定义的宏





在正文的例子中,我在前面的src/Makefile.am 里写死了SDL和sigc++这两个库的编译参数,包括CPPFLAGS、LDFLAGS。这么做其实有违auto工具的初衷,因为如果碰到的系统,它的 SDL和sigc++安装路径与Makefile.am所写不一致,程序就没法编译了。正确的作法是让configure脚本来检测这些参数。

难道要自己写脚本?这又是件让很多人头大的事情。幸运的是autotools已经提供了很多宏帮助你完成大部分这类工作。正好SDL和libsig++代表了两种不同的情况,下面就继续拿正文中的例子做示范,来讲解宏的使用。

先看看修改后的src/Makefile.am:
bin_PROGRAMS = runner
runner_SOURCES = ai.cpp animation.cpp anisprite.cpp gamestage.cpp gamesys.cpp
main.cpp sprite.cpp spriteone.cpp stagetwo.cpp
ai.h animation.h anisprite.h common.h gamestage.h gamesys.h sprite.h spriteone.h stagetwo.h
runner_LDADD = $(SDL_LIBS)
$(SIGCPP_LIBS)
AM_CPPFLAGS = -g -O0 -DDATADIR=\"$(datadir)\"
$(SDL_CFLAGS)
$(SIGCPP_CFLAGS)

可以看到,所有写死的参数定义都被去掉了,增加的是四个变量:$SDL_LIBS, $SDL_CFLAGS, $SIGCPP_CFLAGS和$SIGCPP_LIBS。可是这四个变量是如何被正确赋值的呢?那就要在configure.in中做手脚了,下面是修 改后的configure.in文件:
AC_PREREQ(2.59)
AC_INIT(runner, 1.0, x@y.z)
AM_INIT_AUTOMAKE(runner, 1.0)
AC_CONFIG_SRCDIR([src/ai.cpp])
#AC_CONFIG_HEADER([config.h])

# Checks for programs.
AC_PROG_CXX
AC_PROG_CC

# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([stdlib.h])

# Checks for typedefs, structures, and compiler characteristics.
AC_HEADER_STDBOOL
AC_C_CONST

# Checks for library functions.
AC_CHECK_FUNCS([floor])

AM_PATH_SDL(1.2.7,,AC_MSG_ERROR([
*** SDL 1.2.7 is required to build runner
]))

PKG_CHECK_MODULES(SIGCPP, sigc++-2.0,, AC_MSG_ERROR([
*** sigc++-2.0 is required to build runner
]))
AC_SUBST(SIGCPP_CFLAGS)
AC_SUBST(SIGCPP_LIBS)

AC_CONFIG_FILES([Makefile
gamedata/Makefile
gamedata/sprite/Makefile
gamedata/sprite/drop/Makefile
gamedata/sprite/left/Makefile
gamedata/sprite/right/Makefile
gamedata/sprite/stand/Makefile
src/Makefile])
AC_OUTPUT

先看对SDL的处理,我们使用了一个宏AM_PATH_SDL,这个宏的定义在/usr/share/aclocal/sdl.m4中(这 个文件是包含在sdl的开发包中的,我使用的是Debian系统,可能在别的系统路径会有所不同)。这个宏展开后就是一段检测SDL库并设置$ SDL_LIBS, $SDL_CFLAGS的脚本,如果有兴趣可以查看一下./configure文件,看看这些宏展开后都生成了什么脚本。

sigc++的情况跟SDL有所不同,在/usr/share/aclocal下并没有sigc++.m4文件。但是在 /usr/share/aclocal/pkg.m4中定义了宏PKG_CHECK_MODULES,用来处理所有可以用pkg-config处理的库。 这个宏和AM_PATH_SDL差不多方便,只不过要多两句AC_SUBST(SIGCPP_CFLAGS)和AC_SUBST (SIGCPP_LIBS),否则这两个值没法被传递到Makefile中去。





如何为./configure增加参数





未完待续:)


没有评论: