实战演练Autotools (试发表)

非文学 创作
前言 最近正在学习C++,在Linux下进行开发(没有图形界面)。手头没有了像Eclipse[JavaIDE]般好用的工具之后感觉寸步难行,写完程序之后还要费半天劲搞个Makefile去编译链接。开始程序代码少的时候还好,可是当目录和源码变多之后,维护Makefile变成了一个比较痛苦的事情。不仅仅是因为源码和目录的管理,还有Makefile所需的各种配置,所使用的各种变量和参数也是越来越复杂。要能有个工具帮我把Makefile都搞定该多好啊!于是盯上了GNU的自动化Makefile生成工具。大概读了一下automake和autoconf的手册之后,觉得这手册实在就是个手册,当教程恐怕就.....于是自己上网上搜索加上自己动手实践,大概弄清了这一套工具的基本用法,应付普通的编译工作应该是没问题了。不过需要注意的是,虽然是(半)自动化的工具,操作起来还是需要花些时间的,如果您的程序结构不是很复杂的话就用不着了,用了反倒会感觉繁琐。 1. 准备知识 一般来说,一个新事物的诞生都是需求驱动的,Autotools也不例外。据手册上描述,是1991年一个叫做David J. MacKenzie的牛人对于在20个平台上调Makefile感到十分厌倦,就手写了一个名为“configure”的叫本来自动调整不同平台上的Makefile。虽然不知道他为啥需要在20个平台上编译同样的代码,但是其行为还是十分值得敬仰。后来这个工具被不断完善,最终形成了现在的Autotools工具集。它用来为我们的工程代码生成一个GNU Build System,这个所谓的System可以理解为符合GUN源码编写规范的工程。其中最重要的两个工具是autoconf和automake,前者主要用于生“configure”脚本(进行变量定义、测试及编译环境的设定等功能),而后者主要用户自动生成Makefile文件,这些工具需要配合使用才能达到最终目的。 (1)主要步骤及意义 对于一个带有源码的项目来说,要为其自动生成Makefile需要经历一下几个步骤 [1]在工程目录下运行autoscan命令,生成configure.scan文件 [2]将configure.scan改名为configure.ac,并手动修改其中的内容。该文件主要用于定义程序的基本信息(名字、版本号等)、在进行编译之前需 要进行的测试、需要在哪些目录生成Makefile文件等等 [3]在每个要生成Makefile的目录下创建Makefile.am文件,该文件用于生成创建Makefile的规则和变量。也就是说最终的Makefile文件是根据它 来生成的,虽然也需要手动在里面写入一些内容,但是比写Makefile要简单许多。 [4]在工程根目录下建立NEWS、 README、 ChangeLog 、AUTHORS这几个文件,这些文件和其他几个文件(如INSTALL等)都是GNU Build System规 范要求的,里面的内容可以自己写,不想写空着也行,但文件必须得存在。 [5]运行aclocal,这一步的作用是把各个地方定义的宏[就不去管到底有哪些宏了]集中到aclocal.m4里面 [6]运行autoconf命令,将configure.ac中的宏展开,生成configure脚本,中间可能会用到上一步生成的aclocal.m4脚本 [7]运行automake -a命令,作用是将我们自己定义的Makefile.am转换成Makefile.in,该文件会被configure脚本用来生成最终的Makefile文件。 此外,还会把automake安装目录下的一些文件,如果INSTALL,install-sh等作为软连接连到工程根目录(如果工程根目录本身没有这些文件的话) [8]运行./configure --prefix /you/project/path。进行预定义的测试和生成Makefile文件。注意由于prefix变量用于指定工程目录的前缀,例如项目在/usr/local/app/MyProj,那么这个变量的值就应该设为/usr/local/app。因为很多其他有关于目录的变量(如bindir,srcdird等)的值都是用这个变量定义的(bindir=${prefix}/bin等),所以最好在运行configure脚本时指定一下,不指定将会使用默认值/usr/local [9]运行make编译,make install进行安装,make clean是清理,这些都已经是定义好的了,不用再手动搞了。 (2)重要变量及宏定义的含义 [1]变量 CC [C代码编译的命令] CFLAGS [C编译器的Flag] CXX [C++代码编译的命令] CXXFLAGS [C++编译器的Flag] LDFLAGS [linker flags] CPPFLAGS [C/C++ preprocessor flags] 例如,如果想要编译的时候指定gcc-3编译器,在编译时使用“~/usr/include”目录下的头文件,以及在连接时“~/usr/lib”目录下的库文件的话就可是使用如下命令:./configure --prefix ~/usr CC=gcc-3 CPPFLAGS=-I$HOME/usr/include LDFLAGS=-L$HOME/usr/lib [2]预定义目录变量及其缺省值 prefix /usr/local exec_prefix ${prefix} bindir ${exec_prefix}/bin libdir ${exec_prefix}/lib . . . includedir ${prefix}/include datarootdir ${prefix}/share datadir ${datarootdir} mandir ${datarootdir}/man infodir ${datarootdir}/info docdir ${datarootdir}/doc/${PACKAGE} [3]automake后缀 automake缺省定义了几个后缀,如_PROGRAMS,_SCRIPTS, _DATA, _LIBRARIES,分别代表程序可执行文件,脚本,数据和库。他们的作用是可以与上面提到的目录变量的定义结合起来,作为文件安装的依据。例如,假设我们知道目录变量bindir=${prefix}/bin,该目录一般用于存放生成好的可执行程序文件。如果我们想要把编译生成好的程序文件MyApp最终复制到bindir所指向的目录,我们需要在Makefile.am中定义:bin_PROGRAMS=MyApp。这句话告诉automake把生成好的MyApp文件复制到bindir[注意命名时去掉dir]所指向的目录中去。再例如myappdocdir = ${prefix}/projdir和myappdoc_DATA = README,表示安装时要把README这个文件copy到myappdocdir所指定的目录中去。 2.实战演练 我们的目标是编译如下目录结构的工程,其中src是源码目录,server里面是server的实现代码,也就是需要用这里的代码生成可执行程序;wbl是我们内部的一个库的源码,最后要生成一个名为libwbl.a的库文件供server里的代码使用,子目录及其作用下面有提示。sgi_stl目录里面是sgi stl3.3的源码,本想不用系统自带的stl而使用自己下载的源码,可惜最后没成功。 httpsvr | +-- src | |-- server | | | | | `-- httpsvr_main.cpp | |-- wbl | | | | | +-- src[wbl库实现源码] | | | | | `-- wbl[wbl库header] | | | `- sgi_stl[里面是sgi stl3.3源码] | +-- bin | +-- conf | +-- logs | `-- scripts 第一阶段,起步!! (1)起始设置 我们初始的目录设定如下,httpsvr是指工程根目录,此时除了svr目录之外其他目录都是空的。这一阶段先不管其他目录,只把目光聚焦src/server目录。 httpsvr | +-- src | |-- server | | | | | `-- httpsvr_main.cpp | |-- wbl | | | `- sgi_stl | +-- bin | +-- conf | +-- logs | `-- scripts 此时server目录下只有一个源文件,我们需要做的是把httpsvr_main.cpp编译成httpsvr_main.o,然后生成可执行文件httpsvr。下面的行动按照本文一开始的步骤进行即可。在程序根目录运行autoconf扫描一遍目录,得到两个文件:autoscan.log和configure.scan运行结束出现两行提示,对于后续操作没有影响忽略即可。 autom4te: configure.ac: no such file or directory autoscan: /usr/bin/autom4te failed with exit status: 1 (2)生成configure.ac 将configure.scan更名为configure.ac,然后编辑一下其内容,主要添加以下内容 AM_INIT_AUTOMAKE(httpsvr,0.0.1) #指定应用程序名和版本号 AC_PROG_RANLIB #因为需要链接wbl代码生成的库,所以需要加上这个 #指定要生成的Makefile文件,根据我的程序结构,需要如下3个Makefile AC_OUTPUT([Makefile #在最外层,作为所谓的“总控”Makefile,除了编译之外还可以做一些其他操作,如文件复制、移动等等 src/Makefile #负责编译src目录下的所有源码,也可以成为“源码总控”的Makefile。其实没有也行,只看个人喜好。 src/server/Makefile #负责编译server的实现源码 src/wbl/Makefile]) #负责编译wbl库源码 (3)撰写Makefile.am 对于automake来说,一个Makfile.am就对应一个Makefile,只不过我们可以在Makefile.am里定义一些远比复杂的Makefile本身简单许多的rules。 httpsvr/Makefile.am: #目前只需要一条语句足矣,主要是告诉automake先去编译src目录下的东东 SUBDIRS=src httpsvr/src/Makefile.am #暂时也只有一条,指定先编译wbl下的源码,然后再编译server下的源码 SUBDIRS=wbl server httpsvr/src/wbl/Makefile.am #暂空 httpsvr/src/server/Makefile.am #定义一下bin目录,其实bindir是预定义变量,这里只是为了清晰重新设置一下。 #这里的目的是通过httpsvr_SOURCES源码生成可执行文件httpsvr,在运行make install的时候会把这个可执行文件放到bin目录下 bindir = ${prefix}/bin bin_PROGRAMS=httpsvr httpsvr_SOURCES=httpsvr_main.cpp 然后在工程根目录创建:NEWS,README,ChangeLog,AUTHORS,其他文件可以通过automake -a来自动添加。 (4)编译连接 这些工作完成之后就可以aclocal,autoconf,automake -a了,此时已经生成了Makefile.in. 接下来就是执行./configure --prefix /usr/local/app/httpsvr(注意最后不要带“/”),这一步主要是执行各种测试命令并生成Makefile文件 最后几句执行输出是: configure: creating ./config.status config.status: creating Makefile config.status: creating src/Makefile config.status: creating src/server/Makefile config.status: creating src/wbl/Makefile config.status: executing depfiles commands 看着没?生成好了我们在configure.ac中所希望的输出文件 下面就简单了,在工程根目录下执行make,然后再make install。我们就可以在输出目录bin下看到可执行文件httpsvr了,初期目标大功告成! 第二阶段,编译wbl!! httpsvr | +-- src | |-- server | | | | | `-- httpsvr_main.cpp | |-- wbl | | | | | +-- src | | | | | `-- wbl | | | `- sgi_stl | `-- etc.... (1)Makefile.am定义 wbl/wbl下是接口定义的头文件,src里面是实现代码。具体功能和内容忽略,只需要知道目录结构即可。 wbl/Makefile.am的定义如下: #定义Flag AM_CFLAGS = -I wbl AM_CXXFLAGS = -I wbl $(EXPAT_CFLAGS) #标识wbl的源码将会生成一个名为libwbl.a的库文件,该库文件仅用于编译链接而不是安装 noinst_LIBRARIES = libwbl.a #定义生成wbl库所需要的源码,注意名字 libwbl_a_SOURCES = / wbl/atomic_count.h/ 下略... #生成库文件所需要的flags libwbl_a_LIBFLAGS = $(EXPAT_LDFLAGS) $(EXPAT_LIBS) 【EXPAT_CFLAGS、EXPAT_LDFLAGS和EXPAT_LIBS这三个变量的意义还有待挖掘】 (2)编译时引用libwbl.a库文件 在编译src/server/下源码的时候,如果要引用刚刚生成的wbl库,需要在src/server/Makefile.am中加入: AM_CXXFLAGS = -I $(top_srcdir)/src/wbl -I . 这句的意思是编译的时候要引入httpsvr/src/wbl目录 httpsvr_LDADD=$(top_srcdir)/src/wbl/libwbl.a 这个意思是说链接的时候需要引入httpsvr/svc/wbl/libwbl.a库文件,后面还需要引入库文件的话可以用空格分隔即可。注意top_srcdir是一个预定义变量,指代源码的根目录,缺省值是${prefix}/src 【AM_CXXFLAGS的作用】 if g++ -DPACKAGE_NAME=/"httpsvr/" -DPACKAGE_TARNAME=/"httpsvr/" -DPACKAGE_VERSION=/"0.0.1/" -DPACKAGE_STRING=/"httpsvr/ 0.0.1/" - DPACKAGE_BUGREPORT=/"eddiexue@tencent.com/" -DPACKAGE=/"httpsvr/" -DVERSION=/"0.0.1/" -I. -I. -I /usr/local/app/httpsvr/src/wbl -I . -g -O2 -MT httpsvr_main.o -MD -MP -MF ".deps/httpsvr_main.Tpo" -c -o httpsvr_main.o httpsvr_main.cpp; then mv -f ".deps/httpsvr_main.Tpo" ".deps/httpsvr_main.Po"; else rm -f ".deps/httpsvr_main.Tpo"; exit 1; fi 【httpsvr_LDADD设定的作用】 g++ -I /usr/local/app/httpsvr/src/wbl -I . -g -O2 -o httpsvr httpsvr_main.o /usr/local/app/httpsvr/src/wbl/libwbl.a 第二阶段,编译sgi_stl 3.3!! 经过各种方法尝试之后发现似乎没办法用自带的stl源码覆盖系统的stl,当我在INCLUDE中定义-I${prefix}/src/sgi_stl之后,程序里无论是使用#include <vector>、#include "include" 还是#include "sgi_stl/vector"都会导致错误,看起来貌似是定义冲突之类的问题,难道系统的stl和程序里自带的stl不能共存吗?那stl源码是怎么测试的?这个问题问了无所不知的google也没找到答案。
© 版权声明:
本作品版权属于作者薛笛,并受法律保护。除非作品正文中另有声明,没有作者本人的书面许可任何人不得转载或使用整体或任何部分的内容。
最后更新 2012-05-28 12:50:25