前言
最近正在学习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=/"[email protected]/" -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也没找到答案。
后记
对于最后一个问题各位谁有这方面的经验可以不吝指教。有其他想法也可以通过[email protected]或留言,期待与各位交流。