天天看点

大型项目使用Automake/Autoconf完成编译配置(2)——步步为营

大型项目使用automake/autoconf完成编译配置(2)——步步为营

在第一篇里面,我们已经提纲挈领的讲述了操作步骤,整个过程步骤有8步,但其中有5步你只需要简单的敲一个命令即可,只有剩下的三步需要你动手写一些东西,对应上面步骤中的蓝色黑体字部分,而本篇的重点就是如何在大型项目中完成这三歩。

步步为营:三步完成编译配置

【第一步:修改configure.ac文件】

从上面的步骤可以看到,使用autoscan工具扫描后就会生成一个简单的configure.ac文件,这已经是一个完整的configure.ac文件框架了,但还不足以达到我们的要求,因此我们要在框架里面添加一些东西:

1.1 添加am_init_automake宏

在ac_init 宏下一行添加am_init_automake([foreign -wall -werror]),中括号里面的选项可以根据需要来修改,具体请看automake手册关于这个宏的说明。

1.2 如果需要,添加ac_config_headers([config.h])宏

添加这个宏很简单,但关键是“如果需要”,什么情况下需要这个宏呢?

这个宏的目的是输出config.h,这是一个c的头文件,里面主要是包含很多宏定义#define,说到这里其实就很明确了,输出这个文件的目的就是提供各种相关的宏,而宏在代码中的作用就是#ifdef,也就是说:如果你的代码需要用到宏开关进行控制,那么就要输出这个文件。具体的使用方法如下:

1) 首先确定代码中需要使用什么宏来进行开关定制,确定宏的名称,编写和宏相关的代码,且要包含config.h的头文件;

2) 在configure.ac中的各种处理(例如ac_check_***,ac_arg_***)中使用ac_define宏定义c/c++的宏,名称和上面的相同;如果是使用ac_check_headers,会自动添加宏定义;

3) 执行完第7歩后,autoconf就会自动生成config .h文件

1.3添加编译链接需要的程序

编译链接需要用到的程序需要添加在# checks for programs.注释后面。对于c/c++来说,最常见的就是gcc, g++, 静态库编译、动态库编译,对应的选项如下:

ac_prog_cxx

ac_prog_cc

ac_prog_ranlib

如果使用libtool编译,则选项如下,注意使用了libtool则需要将ac_prog_ranlib去掉

lt_init

1.4 在configure.ac代码中各个部分添加自己的检测处理

这一步是我们的主要工作,需要根据自己的项目具体情况来编写。至于具体添加在哪个地方,configure.ac中的注释已经清楚的告诉你了,例如:

# checks for libraries.

# checks for library functions.

1.5 在ac_output上一行添加ac_config_files宏

添加这个宏的目的是制定autoconf输出哪些文件,常见的文件就是makefile文件,config.h在ac_config_headers宏里面指定了,这里不需要再次指定。例如:

ac_config_files([makefile tools/makefile common/makefile worker/makefile])

【第二步:编写自定义的autoconf宏】

autoconf虽然提供了很多内置的宏,但在实际项目中,这些宏不可能满足所有的要求,有的处理还是要自己完成。虽然在configure.ac文件中可以直接编写各种处理代码,但这样做有几个缺点:

1) 很不美观:打开configure.ac文件,密密麻麻的一大段花花绿绿的shell代码,看着眼花缭乱;

2) 修改起来很麻烦:要找半天才能找到要修改的位置,一不小心就改错了;

就像写c/c++代码要进行封装一样,autoconf的处理也需要进行封装,这个封装就是自定宏,定义完成后在configure.ac中调用,看起来很清爽,修改也很简单。

下面我们来看如何自定义宏:

2.1 新建一个单独的目录,用于存放自定义宏,一般定义为m4

2.2 新建自定义宏文件

建议每个宏一个文件,文件必须以.m4结尾,文件名就是宏名(当然如果你非要不这么做也可以,文件名随便取)

2.3 编写autoconf宏

具体的编写方式请参考autoconf的手册,最好边看手册边对照一个开源软件的样例,这样效果最好了。这里说明几个需要注意的地方:

1)m4宏不是shell,请不要直接在文件中写shell代码,而要在宏的各个部分里面写代码;

最常见的就是if-else判断,如果要在代码中编写if-else判断,需要使用as_if宏,或者在其它宏里面写,例如ac_arg_with, ac_cache_check;

2)ac_defun是定义autoconf的宏,ac_define是定义c/c++的config .h里面的宏,不要混淆了;

2.4 运行aclocal工具,生成aclocal.m4

由于自定义宏是放在我们新建的目录中的,configure.ac并没有像c/c++那样的include语句可用,因此也就找不到这些宏,这时就需要aclocal工具了:aclocal会将自定义宏编译成configure.ac可用的宏,保存在和configure.ac同级目录下的aclocal.m4文件中,这样在configure.ac就能够直接使用了。具体的编译方法如下(m4就是你的目录):

aclocal -i m4

同时需要在根目录下的makefile.am中添加aclocal_amflags = -i m4。

还有一种方法是将所有的自定义宏都放入到一个acinclude.m4文件中,不过不推荐这种方法,原因是因为这种方法的缺点和直接将所有自定义宏放入configure.ac中没有多大差别。

【第三步:编写makefile.am文件】

对于大型项目来说,代码一般都是分目录存放的,而不会像hello world样例那样简单的就几个文件,因此写makefile.am就麻烦一些,但其实主要是工作量增加了,原则都是一样的:

原则1:每个目录一个makefile.am文件;同时在configure.ac的ac_config_files宏中指定输出所有的makefile文件,例如:

原则2:父目录需要包含子目录

在父目录下的makefile.am中添加: subdirs = 所有子目录,例如subdirs=test tools

原则3:makefile.am中指明当前目录如何编译

前两个原则很简单,这里就不多说了,重点说一下如何编写makefile.am。

编写makefile.am主要是完成3件事情:编译(make)、安装(make install)、打包(make dist),下面我们一一来进行讲解。

3.1 编译安装

编译和安装的规则是绑定在一起的,通过同一条语句同时指定了编译和安装的处理方式,具体的格式为:安装目录_编译类型=编译目标

3.1.1【安装目录】

例如:bin_programs = hello subdir/goodbye,其中安装目录是bin,编译类型是programs,编译目标是两个程序hello, goodbye.

常用缺省的安装目录如下

目录

makefile.am中的变量

使用方式

prefix

/usr/local

安装目录,通过--prefix指定

exec_prefix

${prefix}

同prefix

bindir

${exec_prefix}/bin

bin_编译类型

libdir

${exec_prefix}/lib

lib_编译类型

includedir

${prefix}/include

include_编译类型

noinstdir

noinst_编译类型,特殊的目录,表示编译目标不安装。

除了常用的缺省目录外,有时候我们还需要自定义目录,例如日志目录log,配置目录config,这种目录可以通过自定义目录方式定义,然后按照缺省目录的使用方式使用即可。例如:自定义config目录的方式如下:

configdir=${prefix}/log  => 定义一个自定义的目录名称config,注意dir后缀是固定的

config_data=config/test.ini  => 使用自定义的目录config,必须要有这句,否则目录不会创建, =号后面如果有对应的文件,安装时会将对应的文件拷贝到config目录下。

3.1.2【编译类型】

常见编译类型如下,没有自定义编译类型

类型

说明

programs

可执行程序

bin_programs

libraries

库文件

lib_libraries

ltlibraries (libtool libraries)

libtool库文件

lib_ltlibraries

headers

头文件

include_headers

scripts

脚本文件,有可执行权限

test_scripts(需要自定义test目录)

data

数据文件,无可执行权限

conf_data(需要自定义conf目录)

3.1.3【编译目标】

编译目标其实就是编译类型对应的具体文件,其中需要make生成的文件主要有如下几个:可执行程序_programs,普通库文件_libraries,libtool库文件_ltlibraries,其它类型对应的编译目标不需要编译,源文件就是目标文件。

Ø  标准的编译配置

如果你熟悉gcc的编译命令写法,那么automake的makefile.am编译过程就很好写了。因为automake只是将写在一行gcc命令里的各个不同部分的信息分开定义而已。我们来看具体是如何定义的:

_sources:对应gcc命令中的源代码文件

_libadd:编译链接库时需要链接的其它库,对应gcc命令中的*.a, *.so等文件

_ldadd:编译链接程序时需要链接的其他库,对应gcc命令中的*.a, *.so等文件

_ldflags:链接选项,对应gcc命令中的-l, -l, -shared, -fpic等选项

_libtoolflags:libtool编译时的选项

**flags(例如_cflags/_cxxflags):编译选项,对应gcc命令中的-o2, -g, -i等选项

举例如下:

#不同的编译类型只是第一句不一样,后面的编译配置都是一样的

bin_programs= myproject

myproject_sources = main.c

myproject_ldadd   = ./utils/libutils.a ./module1/libmodule1.a ./core1/libcore.a

myproject_ldflags = -l/home/test/local -lmemcached

myproject_cflags  = -i./core1/ -i./module1/ -i./utils/ -o2 -g

Ø  如何编译可执行程序

对于大型项目来说,代码基本上都是分目录存放的,如果是直接写makefile文件,一般都是将所有源文件首先编译成*.o的文件,再链接成最终的二进制文件。但在automake里面这样是行不通的,因为你只要仔细看编译类型表格就会发现,并没有一种编译类型能够编译*.o文件,无法像常规makefile那样来编写,所以就需要采取一些技巧。

其实这个技巧也很简单:将非main函数所在目录的文件编译成静态链接库,然后采用链接静态库的方式编译可执行程序。

样例如下:

=================根目录makefile.am======================

#对应makefile.am原则2

subdirs = tools common worker

=================tool目录makefile.am======================

#只是为了编译而生成的.a库文件,没有必要安装, 所以是noinst

noinst_libraries=libtools.a

libtools_a_sources=./urlcode.h /

                   ./stringtools.cpp /

                   ./stringtools.h /

                   ./urlcode.c

===============common目录makefile.am======================

noinst_libraries=libcommon.a

libcommon_a_sources=./iniparser.c /

                    (省略很多文件, 实际使用时要一一填写)

                    ./exception.h /

==============worker目录makefile.am============================

bin_programs=worker

worker_sources=./workeralgorithm.cpp /

                ./worker.cpp /

                (省略很多文件, 实际使用时要一一填写)

               ./worker.h

#通过_ldadd告诉automake需要链接哪些库

worker_ldadd=../tools/libtools.a ../common/libcommon.a

Ø  如何编译静态库

automake天然支持编译静态库,只需要将编译类型指定为_libraries即可。

Ø  如何编译动态库

需要注意的是:_libraries只支持静态库(即*.a文件),而不支持编译动态库(*.so)文件,要编译动态链接库,需要使用_programs。除此之外,还需要采用自定义目录的方式避开automake的两个隐含的限制:

1)      如果使用bin_programs, 则库文件会安装到bin目录下,这个不符合我们对动态库的要求;

2)      automake不允许用lib_ programs

下面假设将utils编译成so,采用自定义目录的方式,修改makefile.am如下:

mylibdir=$libdir         #$libdir其实就是lib目录,请参考【安装目录】表格

mylib_programs= libutils.so

libutils_so_sources = utils.c utils.h

libutils_so_ldflags = -shared –fpic  #这个就是gcc编译动态库的选项

Ø  如何编译libtool库

对于跨平台可移植的库来说,推荐使用libtool编译,而且automake内置了libtool的支持,只需要将编译类型修改为_ltlibraries即可。

需要注意的是:如果要使用libtool编译,需要在configure.ac中添加lt_init宏,同时注释掉ac_prog_ranlib,因为使用了lt_init后,ac_prog_ranlib就没有作用了。

3.2 打包

automake缺省情况下会自动打包,自动打包包含如下内容:

1)      所有源文件

2)      所有makefile.am/makefile.in文件

3)      configure读取的文件

4)      makefile.am’s (using include) 和configure.ac’ (using m4_include)包含的文件

5)      缺省的文件,例如readme, changelog, news, authors

如果除了这些缺省的文件外,你还想将其它文件打包,有如下两种方法:

(1)   粗粒度方式:通过extra_dist来指定,例如:

extra_dist=conf/config.ini  test/test.php  tools/initialize.sh

(2)   细粒度方式:在“安装目录_编译类型=编译目标”前添加dist(表示需要打包), 或者nodist(不需要打包),例如:

#将data_data= distribute-this打包

dist_data_data = distribute-this

#foo_ sources不打包

bin_programs = foo

nodist_foo_sources = do-not-distribute.c

【后记】

gnu autotool工具博大精深,我也是结合项目的实际应用来使用的,并没有完整的研究所有的工具,因此难免存在瑕疵和纰漏,如果大家发现有疑问或者问题的地方,欢迎大家指正。当然,gnu自己的手册是最权威的,如果你有疑问的话,参考手册,以手册为准。

同时感谢我的同事胡大侠,他写了一份很好的入门的材料,为我提供了很大的帮助。

【参考资料】

1. 入门材料:http://sources.redhat.com/autobook/autobook/autobook_toc.html 。

2. autoconf手册:http://www.gnu.org/software/autoconf/manual/autoconf.html 。

3. automake手册:http://sources.redhat.com/automake/automake.html 。

4. libtool手册:http://www.gnu.org/software/libtool/manual/libtool.html

5. tutorial:http://www.lrde.epita.fr/~adl/dl/autotools.pdf 。

继续阅读