实战演练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=/"[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]或留言,期待与各位交流。

 

你可能感兴趣的:(server,脚本,工具,makefile,编译器,preprocessor)