<!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:Verdana; panose-1:2 11 6 4 3 5 4 4 2 4; mso-font-charset:0; mso-generic-font-family:swiss; mso-font-pitch:variable; mso-font-signature:536871559 0 0 0 415 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} p {mso-margin-top-alt:auto; margin-right:0cm; mso-margin-bottom-alt:auto; margin-left:0cm; mso-pagination:widow-orphan; font-size:12.0pt; font-family:宋体; mso-bidi-font-family:宋体;} pre {margin:0cm; margin-bottom:.0001pt; mso-pagination:widow-orphan; tab-stops:45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt; font-size:12.0pt; font-family:宋体; mso-bidi-font-family:宋体;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:36.0pt; mso-footer-margin:36.0pt; mso-paper-source:0;} div.Section1 {page:Section1;} -->
HelloWorld
automake 比起 IDE 要复杂很多,这里我们先写一个 Hello World 例子,明白其中的基本概念后,再用它来管理实际的工程。
o 目录结构
最顶层目录名用模块名称,这里是 helloworld 。
源文件放在模块下的 src 子目录里,即 helloworld/src 。
这是惯例。有多个子模块时,各个子模块的源代码放在各自的目录里。
o 创建源文件
在 src 下创建源文件 main.c ,内容就是一个简单的 Hello World 程序。
o 创建 Makefile 模板
创建 helloworld/Makefile.am ,内容为:
SUBDIRS=src
这里只有简单的一行代码,表示其下有一个 src 的子目录,如果有多个子目录,用空格分开就行了。
创建 helloworld/src/Makefile.am ,内容为:
bin_PROGRAMS=helloworld
helloworld_SOURCES=main.c
这里表示有一个可执行文件 helloworld , helloworld 由源文件 main.c 编译而来。
PROGRAMS 表示要产生的可执行文件,有多个可执行文件时,用空格分开,而 bin 表示可执行文件要安装的目录。 SOURCES 表示生成可执行文件需要的源文件,有多个源文件时,也用空格分开。
.am 扩展名是 automake 的简称,它是 automake 用来产生 Makefile.in 文件的模板。
o 创建 autoconf 的模板。
在 helloworld 下运行 autoscan ,生成文件 configure.scan ,把它改名为 configure.in 。这是 autoconf 的模板文件,它的内容大概为:
#
-*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.61)
AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([Makefile
src/Makefile])
AC_OUTPUT
这个文件由一系列的宏组成,这些宏最终由命令 m4 展开,得到一个脚本文件 configure 。 configure 的主要功能是探测系统的配置,然后 根据这些配置来产生相应的 Makefile 文件。比如 AC_PROG_CC 是用来检测编译器的, AC_CONFIG_FILES 和 AC_OUTPUT 是用来产生 Makefile 和其它数据文件的。
不过这个模板文件还不能直接使用,需要做下列修改:
把:
AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)
改为:
AC_INIT(helloworld, 0.1, xianjimli@hotmail.com)
FULL-PACKAGE-NAME 是模块的名称。
VERSION 是模块的版本号,初始版本号都用 0.1 。对小模块来说用两级版本号就够了,小数点前的为主版本号,只有重大更新时才升级主版本号。小数点后的为次版本号,每次发布都应该升级它。一般升级到 0.9 后,可以继续升级到 0.10 、 0.11 等。
BUG-REPORT-ADDRESS 是作者或维护者的邮件地址。
再加上一行 automake 的初始化脚本:
AM_INIT_AUTOMAKE(helloworld, 0.1)
helloworld 是模块的名称。
0.1 是模块的版本号。
这里和前面的参数是重复的, AC_INIT 是初始化 autoconf 的, AM_INIT_AUTOMAKE 是初始 automake 的。在有的情况下,只是产生数据文件,而不需要编译文件时,那就不需要 AM_INIT_AUTOMAKE 了。
最后得到下面的文件:
#
-*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.61)
AC_INIT(helloworld, 0.1, xianjimli@hotmail.com)
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADER([config.h])
AM_INIT_AUTOMAKE(helloworld, 0.1)
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([Makefile
src/Makefile])
AC_OUTPUT
o 拷贝所用到宏。
运行: aclocal
前面说了, configure.in 里是一系列的宏,这些宏由命令 m4 负责展开。 m4 实际上就是 macro 的简称, 4 代表 m 后面省略了 4 个字母。类似的还有 I18n (Internationalization ) 和 L10n(Localization) ,其中的数字都是代表所省略的字母个数。
AC_PROG_CC 之类的宏是标准的宏 ( 或说是内置的宏 ) ,不需要我们自己去写它,但我们需要运行命令 aclocal , aclocal 把 configure.in 中所用到的 宏全部拷贝到我们的工程里来。在 helloworld 目录下运行 aclocal 之后,当前目录下出现了:
autom4te.cache 这是一个临时目录,只是用来加快宏展开的。
aclocal.m4 是 configure.in 中用到的宏的定义,有兴趣的读者可以看看。
o 产生配置头文件的模板。
运行: autoheader
配置头文件 (config.h) 是用来定义在 C/C++ 程序中可以引用的宏,像模块的名称和版本号等等。这些宏由 configure 脚本产生,但我 们要提供一个模板文件。这个模板文件可以用命令 autoheader 产生出来。在 helloworld 目录下运行 autoheader 之后,当前目录下产 生 config.h.in ,一般情况不用修改它。
o 创建几个必要的文件。
README :描述模块的功能、用法和注意事项等。
NEWS :描述模块最新的动态。
AUTHORS :模块的作者及联系方式。
ChangeLog :记录模块的修改历史,它有固定的格式:
1. 最新修改放在最上面。
2. 对于每条记录,第一行写日期,修改者和联系方式。第二行开始以 tab 开头,再加一个星号,后面再写修改的原因和位置等。如:
2009-03-29 Li XianJing
* Created
o 生成 Makefile.in 和所需要的脚本。
运行: automake -a
这个命令会建立 COPYING depcomp INSTALL install-sh missing 几个文件的链接,这些文件指向系统中的文件。 automake 最重要的功能是以 Makefile.am 为模板产生 Makefile.in 文 件, Makefile.in 相对于 Makefile.am 要复杂很多倍了,所幸的是我们不需要了解它。
o 产生 configure 脚本。
运行: autoconf
autoconf 的功能是调用 m4 展开 configure.in 中的宏,生成 configure 脚本,这个脚本是最终运行的脚本。
o 产生最终的 Makefile 。
运行: ./configure
configure 有两个常用的参数:
–prefix 用来指定安装目录, Linux 下默认的安装目录是 /usr/local 。
–host 用于交叉编译,比如 x86 的 PC 机上编译在 ARM 板上运行的程序。
如: ./configure –prefix=/home/lixianjing/work/arm-root/usr –host=arm-linux
o 编译
运行: make
o 安装
运行: make install
o 发布软件包
运行: make dist 或者 make distcheck
make dist 用来生成一个发布软件包,这里会产生一个名为 helloworld-0.1.tar.gz 的文件。通常,源代码管理系统 (cvs/svn/git) 中的源代码是处于开发中的,是不稳定的,而发布的软件包则是稳定的,可供用户使用的。
怎样,是不是有点晕了?这里主要是想读者了解其中的原理,在实际操作中,我们可以把 make 之前的部分动作放到一个脚本文件中,这个脚本文件通常取名为 autogen.sh 或者 bootstrap
函数库
现在我们用 automake 来管理我们前面所建立的函数库,这是一个基础的函数库,我们就把它命名为 base 吧。
o 目录结构
base 根目录
base/src 源代码目录
o 创建 Makefile 模板
base/Makefile.am 内容为:
SUBDIRS=src
base/src/Makefile.am 内容为:
lib_LTLIBRARIES=libbase.la
libbase_la_SOURCES= darray.c /
darray.h /
dlist.c /
dlist.h /
darray_iterator.h /
dlist_iterator.h /
hash_table.c /
hash_table.h /
invert.c /
iterator.h /
linear_container_darray.c /
linear_container_darray.h /
linear_container_dlist.c /
linear_container_dlist.h /
linear_container.h /
queue.c /
queue.h /
sort.c /
sort.h /
stack.c /
stack.h /
typedef.h
libbase_la_LDFLAGS=-lpthread
noinst_PROGRAMS=darray_test dlist_test
darray_test_SOURCES=darray.c
darray_test_CFLAGS=-DDARRAY_TEST
dlist_test_SOURCES=dlist.c
dlist_test_CFLAGS=-DDLIST_TEST
basedir=$(includedir)/base
base_HEADERS=darray.h dlist.h iterator.h linear_container_dlist.h typedef.h /
darray_iterator.h
dlist_iterator.h
linear_container_darray.h
/
linear_container.h
EXTRA_DIST=/
linear_container_test.c /
invert_ng.c /
darray_iterator.c /
dlist_iterator.c /
test_helper.c
LTLIBRARIES 是关键字。 LT 代表 libtool , libtool 是用来封装共享库在不同平台上差异的脚本,其具体实现我们不用关心。
libbase.la 是函数库的名称,扩展名用 .la 而不是 .so 或 .a ,同时会生成共享库和静态库。 libbase_la_SOURCES 是生成 libbase.la 所需要的源文件。
LDFLAGS 是关键字,用来指定链接时需要的参数, -lpthread 表示要链接 libpthread.so 。
noinst_PROGRAMS 是关键字,表示不需要安装的可执行文件,通常是测试程序。为了简单明了,这里没有写出全部的测试程序。
CFLAGS 是关键字,用来指定编译和预处理时的参数。
HEADERS 是关键字,列出所要安装的头文件。 xxx_HEADERS 和 xxxdir 要配套使用,后者表示要安装的位置。这里在 base_HEADERS 中列出的头文件会安装到 basedir 目录里。
o 创建 autoconf 的模板。
运行:
autoscan
mv configure.scan configure.in
然后按前面介绍的方法修改 configure.in ,得到下面的内容:
AC_PREREQ(2.61)
AC_INIT(base, 0.1, xianjimli@hotmail.com)
AC_CONFIG_SRCDIR([src/invert.c])
AC_CONFIG_HEADER([config.h])
AM_INIT_AUTOMAKE(base, 0.1)
# Checks for programs.
AC_PROG_CC
AC_PROG_LIBTOOL
# Checks for libraries.
# FIXME: Replace `main' with a function in `-lpthread':
AC_CHECK_LIB([pthread], [main])
# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([stdlib.h string.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_C_INLINE
AC_TYPE_SIZE_T
# Checks for library functions.
AC_FUNC_MALLOC
AC_FUNC_REALLOC
AC_CONFIG_FILES([Makefile
src/Makefile])
AC_OUTPUT
与前面不同的是:
AC_PROG_LIBTOOL 用来检查 libtool 脚本。
AC_CHECK_LIB 用来检查共享库是否存在。
AC_CHECK_HEADERS 用来检查头文件是否存在。
AC_FUNC_MALLOC 用来检查标准的 malloc 函数是否存在。
o 收集用到的 m4 宏。
运行: aclocal
o 产生配置头文件的模板。
运行: autoheader
o 创建 README 、 NEWS 、 ChangeLog 和 AUTHORS 几个文件。
o 生成 libtool 需要的文件。
运行: libtoolize –force –copy
这个命令的主要功能是生成 ltmain.sh ,而 ltmain.sh 用来产生 libtool 脚本。
o 生成 Makefile.in 和需要的脚本。
运行: automake -a
o 产生 configure 脚本。
运行: autoconf
o 产生最终的 Makefile 。
运行: ./configure –prefix=$HOME/usr
o 编译运行: make
o 安装
运行: make install
o 发布软件包
运行: make dist
我们编译好的文件安装到 /home/lixianjing/usr/lib/ 目录下了:
libbase.a libbase.la libbase.so libbase.so.0 libbase.so.0.0.0
静态库: libbase.a
动态库: libbase.so
libtool 的包装: libbase.la
头文件和库都安装好了,调用者还需要知道下列信息才能使用:
头文件和库安装在哪里?
还依赖哪些其它模块?
为了解决这个问题,我们需要借助另外一个名为 pkg-config 的工具。 pkg 是 package 的简写, pkg-config 负责查询指定软件包 的配置信息,如软件包的名称、说明、版本号、头文件、库和依赖关系等等。为了让 pkg-config 能正常工作,软件包的实现者需要提供一个扩展名为 pc 的配置文件。
系统中的 pkg-config 配置文件通常放在 /usr/lib/pkgconfig/ 和 /usr/local/lib/pkgconfig/ 下,下面是 gtk+-2.0.pc :
prefix=/usr
exec_prefix=/usr
libdir=/usr/lib
includedir=/usr/include
target=x11
gtk_binary_version=2.10.0
gtk_host=i386-redhat-linux-gnu
Name: GTK+
Description: GIMP Tool Kit (${target} target)
Version: 2.12.10
Requires: gdk-${target}-2.0 atk cairo
Libs: -L${libdir} -lgtk-${target}-2.0
Cflags: -I${includedir}/gtk-2.0
前面部分是定义的一些变量,后面是一些关键字:
Name: 名称
Description: 功能描述
Version: 版本号
Requires: 所依赖的软件包
Libs: 调用者的链接参数。
Cflags: 调用者的编译参数。
由于 prefix 之类的变量是在软件包 configure 时才决定的,不能直接写死在 pc 文件中。我们可以让 configure 根据模板文件来产生。
模板文件名为 base.pc.in ,内容为:
prefix=@prefix@
exec_prefix=${prefix}
libdir=${prefix}/lib
includedir=${prefix}/include
Name: @PACKAGE_NAME@
Description: a basic library.
Version: @VERSION@
Requires:
Libs: -L${libdir} -lbase
Cflags: -I${includedir}/base
这个模板文件和 Makefile.in 的替换规则一样,用两个 @@ 括起来的变量会替换成 configure 检测出来的值, @prefix@ 等变量是标准的变量。
修改一下 base/Makefile.am ,增加下列两行代码:
pkgconfigdir=${libdir}/pkgconfig
pkgconfig_DATA=base.pc
这是安装数据文件的方法, pkgconfig 不是关键字,取个描述性的名称就好了。 dir 和 _DATA 是关键字,它们有相同的前缀,前者表示安装的目录,后者表示要安装的文件。按照惯例, pc 文件安装到 ${libdir}/pkgconfig 下。
修改 configure.in ,增加输入出文件 base.pc
AC_OUTPUT([base.pc])
放到 AC_CONFIG_FILES 也可以,它告诉 configure 脚本要产生的文件。
重新运行 configure 后会生成 base.pc ,内容为:
prefix=/home/lixianjing/usr/local
exec_prefix=${prefix}
libdir=${prefix}/lib
includedir=${prefix}/include
Name: base
Description: a basic library.
Version: 0.1
Requires:
Libs: -L${libdir} -lbase
Cflags: -I${includedir}/base
(prefix 与 configure 时指定的 prefix 参数一致。 )
在下一节中,我们再学习调用者如何使用 pc 文件。
应用程序
前面我们创建的 helloworld 是一个应用程序工程,它很简单,只使用了标准 C 的函数。现在我们要建立一个应用程序工程,它将使用前面所写的 libbase 函数库。
o 目录结构
最顶层目录名用模块名称,这里用 appdemo 。
源文件放在模块下的 src 子目录里,即 appdemo/src 。
o 创建源文件
在 src 下创建源文件 main.c ,内容只是简单的调用一下 libbase 里的函数。
#include <dlist.h>
int main(int argc, char* argv[])
{
DList* dlist = dlist_create(NULL, NULL);
dlist_destroy(dlist);
return 0;
}
o 创建 Makefile 模板
创建 helloworld/Makefile.am ,内容为:
SUBDIRS=src
这里只有简单的一行代码,表示其下有一个 src 的子目录,如果有多个子目录,用空格分开就行了。
创建 helloworld/src/Makefile.am ,内容为:
bin_PROGRAMS=appdemo
appdemo_SOURCES=main.c
appdemo_CFLAGS=@BASE_CFLAGS@
appdemo_LDFLAGS=@BASE_LIBS@
appdemo_CFLAGS 指定了编译 appdemo 时需要的参数, @BASE_CFLAGS@ 将替换成实际 configure 时所得到的参数。
appdemo_LDFLAGS 是链接 appdemo 时需要的参数。 @BASE_LIBS@ 将替换成实际 configure 时所得到的参数。
至于 @BASE_CFLAGS@ 和 @BASE_LIBS@ 是怎么得到的,后面我们再讲。
o 创建 autoconf 的模板。
在 appdemo 下运行 autoscan ,生成文件 configure.scan ,把它改名为 configure.in 。这是 autoconf 的模板文件,它的内容大概为:
#
-*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.61)
AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([Makefile
src/Makefile])
AC_OUTPUT
按照前面介绍的方法,把 configure.in 的内容修改为:
#
-*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.61)
AC_INIT(appdemo, 0.1, xianjimli@hotmail.com)
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADER([config.h])
AM_INIT_AUTOMAKE(appdemo, 0.1)
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([Makefile
src/Makefile])
AC_OUTPUT
与前面不同的是,我们还需要在 # Checks for libraries. 之后加检查 libbase 参数的宏:
PKG_CHECK_MODULES(BASE, ["base"])
AC_SUBST(BASE_CFLAGS)
AC_SUBST(BASE_LIBS)
PKG_CHECK_MODULES(BASE, ["base"]) 的功能是检查软件包 base ,通过调用前面所讲的 pkg-config ,生成 BASE_CFLAGS 和 BASE_LIBS 两个变量。
AC_SUBST(BASE_CFLAGS) 的功能是把所有对 BASE_CFLAGS 的引用替换成实际的参数。
o 拷贝所用到宏。
运行: aclocal
o 产生配置头文件的模板。
运行: autoheader
o 创建几个必要的文件。
README :描述模块的功能、用法和注意事项等。
NEWS :描述模块最新的动态。
AUTHORS :模块的作者及联系方式。
ChangeLog :记录模块的修改历史,它有固定的格式:
o 生成 Makefile.in 和所需要的脚本。
运行: automake -a
o 产生 configure 脚本。
运行: autoconf
o 产生最终的 Makefile 。
运行: ./configure –prefix=$HOME/usr
这时会出现错误:
No package 'base' found
Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.
在默认情况下, pkg-config 只是在 /usr/lib/pkgconfig 下查找相关的 pc 文件。如果 pc 文件安装在其它目录,需要设置环境变量 PKG_CONFIG_PATH 。
export PKG_CONFIG_PATH=$HOME/usr/lib/pkgconfig
重新 configure ,一切正常了。
o 编译
运行: make
o 安装
运行: make install
o 发布软件包
运行: make dist 或者 make distcheck
本章所讲的内容中可应付 90% 的情况了,如果需要使用更高级的特性,可以阅读相应的手册。