AutoTools学习笔记
开发一个C程序时,为了在不同环境下运行,可能需要考虑这些问题:
- 某些函数在某些环境下不存在,例如strtod()
- 同样功能的函数,名字不同,例如strchr() 与 index()
- 函数名称冲突,例如int setpgrp(void)、setpgrp(int, int)
- 函数在不同环境下行为不同,例如malloc(0)
- 函数可能定义在不同的头文件中,例如string.h 、 strings.h 、memory.h
为了解决这些问题,可以:
- 创建替换宏(substitution macros)
- 创建替换函数(substitution functions)
手工处理这类问题非常繁琐,1991年人们开始编写Shell脚本,来猜测GNU环境的设置,最终形成了configure这个工具:
- 探测系统中所需的函数、库、工具是否存在,根据结果生成config.h这个头文件,里面包含很多#define
- 生成构建软件包的Makefile文件
现在configure文件一般由AutoConf生成,默认情况下遵循如下的目录约定,这些目录决定如何安装程序的可执行文件、库、文档等内容:
变量 | 默认值 |
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 |
注:上述变量均可以作为configure的命令行参数使用,例如 ./configure --bindir=$HOME/bin
执行 ./configure --help 可以看到configure的完整帮助,下面给出一些示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
./configure #指定目录前缀 ./configure --prefix ~/usr #指定程序前后缀 ./configure --program-prefix=hello --program-suffix=exe #configure自动检测很多设置,可以强制使用配置变量: # CC C compiler command # CFLAGS C compiler flags # CXX C++ compiler command #CXXFLAGS C++ compiler flags # LDFLAGS linker flags #CPPFLAGS C/C++ preprocessor flags #使用./configure --help可获取完整列表 ./configure --prefix ~/usr CC=gcc-3 CPPFLAGS=-I$HOME/usr/include LDFLAGS=-L$HOME/usr/lib #配置交叉编译 #--build=BUILD,软件包在其上被编译的系统,一般就是当前系统,由config.guess猜测即可 #--host=HOST,编译好的库、可执行文件在其上运行的系统。只有在交叉编译的时候,才和--builid即本机不同 #--target=TARGET,只有在建立交叉编译环境时才用得到,正常编译/交叉编译都用不到。用于编译一套编译器,该编译器编译出的程序在target机器上运行 ./configure --build i686-pc-linux-gnu --host i586-mingw32msvc #checking for i586-mingw32msvc-strip... i586-mingw32msvc-strip #checking for i586-mingw32msvc-gcc... i586-mingw32msvc-gcc #checking for C compiler default output file name... a.exe #checking whether the C compiler works... yes #checking whether we are cross compiling... yes #checking for suffix of executables... .exe #checking for suffix of object files... o #checking whether we are using the GNU C compiler... yes #checking whether i586-mingw32msvc-gcc accepts -g... yes #checking for i586-mingw32msvc-gcc option to accept ANSI C... #依赖追踪仅仅在源代码改变以后需要,在install时可以安全禁用 .configure --disable-dependency-tracking |
假设在src目录下有如下一简单C程序:
1 2 3 4 5 6 7 |
#include #include int main(void) { puts("HelloWorld!"); return 0; } |
相关文件将按如下依赖关系生成:
输入文件列表:
1 2 3 4 5 6 7 8 9 10 11 12 |
#初始化Autoconf,指定软件包的名称、版本号、缺陷报告地址 AC_INIT([amhello], [1.0], [bug-report@gmem.cc]) #初始化Automake,启用Automake警告,并将其报告为错误 AM_INIT_AUTOMAKE([foreign -Wall -Werror]) #检查C编译器 AC_PROG_CC #声明config.h为输出头文件 AC_CONFIG_HEADERS([config.h]) #声明Makefile、src/Makefile为输出文件 AC_CONFIG_FILES([Makefile src/Makefile]) #输出所有声明的文件 AC_OUTPUT |
1 2 3 |
#在src目录下递归的构建,可以声明多个SUBDIR,顺序非常重要 SUBDIRS = src #对于根目录,无任何其他声明 |
1 2 3 4 5 6 |
#PROGRAMS表示在构建某个程序 #bin表示程序将安装在bin目录(默认/usr/local/bin) #仅有一个名为hello的程序需要构建 bin_PROGRAMS = hello #为了构建程序hello,需要编译main.c hello_SOURCES = main.c |
生成configure、Makefile已经构建、打包的命令示意:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
cd ~/amhello autoreconf --install #输出内容: # configure.ac:2: installing './install-sh' # configure.ac:2: installing './missing' # src/Makefile.am: installing './depcomp' #自动生成以下文件: #Makefile.in、config.h.in、Makefile.in、configure*是配置模板文件 #aclocal.m4是configure.ac中使用的第三方宏定义 #depcomp* install-sh* missing*是构建过程中的辅助工具 #Autotools缓存文件:autom4te.cache/ : output.0 requests traces.1 output.1 traces.0 ./configure #checking... #生成Makefile #configure: creating ./config.status #config.status: creating Makefile #config.status: creating src/Makefile #config.status: creating config.h #config.status: executing depfiles commands make make distcheck #======================================== #amhello archives ready for distribution: #amhello-1.0.tar.gz #======================================== tar ztf amhello-1.0.tar.gz |
工具 | 说明 |
autoconf |
一个宏处理器(底层是GNU M4,但是带有很多预定义宏),从configure.ac(一个带有宏指令的Shell脚本)生成configure(一个完整的Shell脚本) |
autoheader | 从configure.ac生成config.h.in |
autoreconf | 按正确顺序运行所有工具 |
autoscan | 扫描源代码以发现常见的可移植性问题、以及缺失的configure.ac中的宏定义 |
autoupdate | 更新configure.ac中废弃的宏定义 |
ifnames | 从#if/#ifdef/...指令中收集标识符 |
autom4te | Autoconf的核心,驱动M4、实现上面工具需要的特性 |
Autoconf提供多种宏来进行常见的配置检查,仅包含宏定义、没有任何Shell语句的congure.ac也是常见的。
Autoconf与M4
Autoconf底层的宏处理器是M4,下面是其一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#test.m4 m4_define(NAME1, Harry) #宏参数会被处理。宏会被展开 m4_define(NAME2, Sally) #宏参数参数可以用引号保护,例如 'Sally Wang' m4_define(MET, $1 met $2) #宏名称和参数之间不能有空格。引号前面的空格被忽略,引号中间、后面的空格被作为参数的内容 MET(NAME1, NAME2) #测试 m4 -P test.m4 #输出 # # # #Harry met Sally |
由于引号'的特殊用途,因此,在Autoconf中,使用[]来代替'',Shell中不能使用[进行条件判断,而需要使用if test语句。
1 2 3 4 5 6 7 8 |
#Autoconf中的条件测试: if test "$x" = "$y"; then ... #使用AC DEFUN可以定义宏 AC_DEFUN([NAME1], [Harry, Jr.]) AC_DEFUN([NAME2], [Sally]) AC_DEFUN([MET], [$1 met $2]) MET([NAME1], [NAME2]) |
configure.ac的组成与预定义宏
组成 | 说明以及重要的宏 | ||
准备(Prelude) |
|
||
检查程序 |
|
||
检查库 |
|
||
检查头文件 |
|
||
输出文件 |
|
||
动作 |
|
工具 | 说明 |
automake | 从Makefile.am、configure.ac推导出Makefile.in |
aclocal | 扫描configure.ac中使用的第三方宏定义、收集aclocal.m4中的定义 |
Automake辅助生成可移植的、GNU标准兼容的Makefile,它从简单的Makefile.am文件生成复杂的Makefile.in文件。
Makefile.am具有和Makefile类似的语法形式,但是通常只包含变量定义,Automake根据这些定义创建构建规则。在Makefile.am中包含额外的Makefile规则是允许的,Automake会将其保留,并输出到最终的Makefile。
在configure.ac中声明使用Automake
使用宏AM_INIT_AUTOMAKE([OPTIONS...]),可以在configure.ac中声明Automake,OPTIONS包括:
选项 | 说明 |
-Wall | 开启所有警告 |
-Werror | 将警告报告为错误 |
foreign | 放松某些GNU标准的要求 |
1.11.1 | 要求最低1.11.1版本的Automake |
dist-bzip2 | 在执行make dist、make distcheck时自动创建tar.bz2文件 |
tar-ustar | 使用ustar格式创建tar归档文件 |
使用宏AC_CONFIG_FILES(FILES...)声明输出文件
Automake会为每一个存在FILE.am的FILE创建对应的FILE.in(模板)文件
1 2 |
#自动生成Makefile.am、sub/Makefile.am文件 AC_CONFIG_FILES([Makefile sub/Makefile]) |
Makefile.am中的where_PRIMARY惯例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#使用如下形式(下划线分隔的三段字符串)声明目标 option_where_PRIMARY = targets ... #其中option: #dist 分发targets,默认 #nodist 不分发targets #其中where: targets将被安装在(遵从标准文件系统层次结构) #bin $(bindir) #lib $(libdir) #custom $(customdir ) 自定义目录 #noinst 不进行安装 #check 以 make check方式构建 #其中PRIMARY: targets被构建为何种组件 #PROGRAMS 可执行程序 #LIBRARIES 库 #LTLIBRARIES Libtool库 #HEADERS 头文件 #SCRIPTS 脚本 #DATA 数据 |
按目标设置标记
1 2 3 4 5 6 7 8 9 10 11 |
#假设foo是一个程序或者库 #默认值:foo_XXXFLAGS = $(AM_XXXFLAGS) foo_CFLAGS #额外的C编译器标记 foo_CPPFLAGS #额外的预处理器标记(-I、-D) foo_LDADD #foo为程序时:额外的链接对象、-l、-L foo_LIBADD #foo为库时:额外的链接对象、-l、-L foo_LDFLAGS #额外的连接器标记 #对于软件包内部的额外链接对象,应当保留原始的文件名 #-l、-L仅用于外部库 run_me_LDADD = ../lib/libcompat.a |
声明源代码文件
1 2 3 4 5 6 7 8 9 10 11 |
#目标foo、run-me编译为可执行程序,安装到bin目录 bin_PROGRAMS = foo run-me #foo的源代码列表 foo_SOURCES = foo.c foo.h print.c print.h #run-me的源代码列表(注意非alphanumeric字符-映射为_) run_me_SOURCES = run.c run.h print.c #注意: #Automake自动计算从这些文件构建、链接的object列表 #头文件不是必须的,只是为了分发的需要,Automake不会分发不知道的文件 #可以为两个程序使用同样的Source #根据.h、.c扩展名来推断是在编译、链接阶段使用某个原文件 |
Automake举例:静态库
1 2 3 4 5 6 7 |
#目标libfoo.a、libbar.a作为库安装到$(libdir) lib_LIBRARIES = libfoo.a libbar.a #注意名称必须满足lib*.a形式 #s声明两个目录的源代码文件 libfoo_a_SOURCES = foo.c privfoo.h libbar_a_SOURCES = bar.c privbar.h #目标foo.h、bar.h(公共头文件)被安装到$(includedir) include_HEADERS = foo.h bar.h |
Makefile.am的目录布局
注意每个目录下均需要一个Makefile文件,因此也就对应了Makefile.am文件。并且这些Makefile必须在configure.ac中一一声明,例如:
1 |
AC_CONFIG_FILES([Makefile lib/Makefile src/Makefile src/dira/Makefile src/dirb/Makefile]) |
make命令是在根目录运行的,Makefile.am可以声明构建子目录的顺序:
1 |
SUBDIRS = lib src |
1 2 3 |
SUBDIRS = dira dirb . #注意,默认当前目录在子目录构建完成后构建 #可以移动当前目录(.)的位置,改变此默认行为 |
Parallel Build Trees (即VPATH Builds)
VPATH用于指定一组make需要搜索的目录的列表。
obj文件、程序、库在configure运行的$CWD下构建,但是源代码不一定需要在$CWD下——所谓构建树(build tree)、源码树(source tree):
- Makefile、obj文件等处于构建树
- Makefile.in、Makefile.am、源代码等位于源码树
如果./configure在$CWD中运行,则两棵树一致,每个Makefile的config.status会定义$(srcdir),用来指定对应的源码目录
辅助库(Convenience Libraries)
所谓辅助库,是指仅仅在构建过程中使用的临时库:
1 2 |
noinst_LIBRARIES = libcompat.a #noinst表示其为临时的 libcompat_a_SOURCES = xalloc.c xalloc.h |
1 2 3 4 5 6 7 8 |
LDADD = ../lib/libcompat.a #使用临时库,LDADD在链接所有程序时自动添加 AM_CPPFLAGS = -I$(srcdir)/../lib #AM_CPPFLAGS包含额外的预处理标记 bin_PROGRAMS = foo run-me foo_SOURCES = foo.c foo.h print.c print.h run_me_SOURCES = run.c run.h print.c #注意LDADD、AM_CPPFLAGS 也可以按目标逐一指定,以应用于一个程序 run_me_LDADD = ../lib/libcompat.a run_me_CPPFLAGS = -I$(srcdir)/../lib |
检查依赖库
1 2 3 4 5 6 |
#检查一个依赖库中的某个函数是否存在,并采取相应的动作 AC_CHECK_LIB(LIBRARY, FUNCT, [ACT-IF-FOUND], [ACT-IF-NOT]) #举例: AC_CHECK_LIB([efence], [malloc], [EFENCELIB=-lefence]) #如果存在,则定义变量 AC_SUBST([EFENCELIB]) #在Makefile中声明变量 |
1 |
run_me_LDADD = ../lib/libcompat.a $(EFENCELIB) |
哪些文件被分发(Distributed)
- 所有使用***_SOURCES声明的源代码
- 所有使用***_HEADERS声明的头文件
- 所有使用dist_***_ SCRIPTS声明的脚本文件
- 所有使用dist_***_ DATA声明的数据文件
- 诸如ChangeLog, NEWS之类的常规文件
- EXTRA_DIST中列出的额外目录或者文件
条件构建(Conditional builds)
1 2 3 4 5 6 7 8 |
bin_PROGRAMS = foo #如果此变量为真,构建bar程序。但即使为假,也会分发 #WANT_BAR必须在configure.ac中声明和使用 if WANT_BAR bin_PROGRAMS += bar endif foo_SOURCES = foo.c bar_SOURCES = bar.c |
1 2 3 4 5 6 |
#条件构建的声明,如果CONDITION指定的Shell指令成功,则NAME为真 AM_CONDITIONAL(NAME, CONDITION) #举例: AC_CHECK_HEADER([bar.h], [use_bar=yes]) AM_CONDITIONAL([WANT_BAR], [test "$use_bar" = yes]) |
扩展Automake规则
- Makefile.am中的内容几乎是逐字的复制到Makefile.in
- automake添加新的规则、变量到Makefile.in,以实现必须定义的变量的语义
- 某些较小的Makefile.in重写是通过条件构建来实现的
- 在Makefile.am中添加自己的规则是可以的,例如这些规则:
- 对维护有意义的目标的规则:例如make style-check
- 用于构建特殊目标的规则:例如为某些文件生成FAQ
- 可以定义对Automake无意义的变量,例如在自定义规则中使用
make命令失败时该怎么办
1 2 3 4 5 6 7 8 9 10 11 |
#在必要时尝试下面的方法 #手工运行autoreconf autoreconf --install #仍然无效,可以尝试 autoreconf --install --force #甚至 make -k maintainer-clean autoreconf --install --force |
Leave a Reply