今时今日,好多人聊起autotools觉得过时了,CMAKE等编译工具很流行。但是我看到好多库还是在使用autotools,比如现在在使用的ngtcp2,nghttp3等。autotools常见于C或者C++的工程,用户侧编译和安装库的时候实际上使用autotools:
bash
autoreconf -i # generate configure file by configure.ac and Makefile.am
./configure --prefix=/home/xyx/sw # generate Makefile by Makefile.in
make # build by Makefile
make check # test
sudo make install # install package
make installcheck # check installed package
今天记录下这几天调研autotools的内容,希望读者有一定的编译库的经验。
问题
当我们开始使用C/C++开发程序时,一般使用Makefile文件去编译和安装
bash
test:
@gcc -g2 -O3 -Wall -Werror -pedantic -o test test.c
run:
@./test
debug:
@gdb test
但是随着程序变大,依赖库和调用的函数变多,每个平台比如unix和linux的各个发行版本环境不一样导致的调用方式不同,比如
strtod()
不是每个版本都存在strchr()
vs.index()
int setpgrp(void);
vs.int setpgrp(int, int);
pow()
到底使用libm.so
还是libc.so
- 等等
一般可以使用预编译宏比如#if/#else
,还可以使用自己编写的替代函数解决。这样很可能好多地方要写一大坨这种判断,而且Makefile里面也需要根据各种判断添加链接库,对于大型项目来说是很痛苦的。那有没有一种工具可以自动化这些判断做配置呢?autotool就是这种情况下诞生的。
Autotools
刚开始的时候,大家使用shell脚本去测试各种判断,比如configure检测当前平台是否支持函数,依赖库和需要的工具等,然后生成config.h头文件供应用程序使用,同时生成Makefile去编译程序。
bash
# 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 --prefix=~/usr CC=gcc-3 \
CPPFLAGS=-I$HOME/usr/include LDFLAGS=-L$HOME/usr/lib
但是发现写configure也很头痛,就开始做各种工具,具体历史这里不讲。autotools是工具集,包括autoscan, autoconf, automake等工具,每个工具在编译链中担任不同角色,库开发者需要关注configure.ac和Makefile.am。 autoreconf命令将很多工具安装正确的流程执行,避免用户每次执行,避免出现执行顺序错误。每个流程使用不同命令,这个大家可以参考各个文档和流程的图。
如上图所示,绿色的需要库作者提供的,使用autoreconf命令将这些转化为configure文件和一些模板文件。 如下图所示,由configure文件生成的config.status
才是最终执行文件,将模板文件生成最终的Makefile和config.h
。
arduino
autoscan 扫描源代码和相关的宏生成configure.scan, 一般直接修改为configure.ac
autoconf 使用configure.ac生成configure文件
autoheader 根据configure.ac生成config.h.in
autoupdate 更新configure.ac中过时的宏
ifnames 从#if/#ifdef等命令收集标识符
autom4te 使用M4宏处理上述各个文件,替代里面的宏变量,这个是autoconf的核心,生成包括configure等文件
automake 根据Makefile.am和configre.ac生成Makefile.in
aclocal 扫描configure.ac文件,根据文件中使用到的第三方宏,收集后放到aclocal.m4
上述各种流程虽然复杂,但是提供了autoreconf -i
帮助用户自动安装正确顺序执行上述过程。
Hello例子
bash
# configure.ac
AC_INIT([amhello], [1.0], [bug-report-email@address]) # 初始化autoconf
AM_INIT_AUTOMAKE([foreign -Wall -Werror]) # 初始化automake, foreign表示这个不是标准的GNU项目,允许缺少各种比如README等文件
AC_PROG_CC # 检测平台的C编译器
AC_CONFIG_HEADERS([config.h]) # 根据各种检测结果生成config.h
AC_CONFIG_FILES([Makefile src/Makefile]) # 根据Makefile.am使用automake工具最终生成Makefile
AC_OUTPUT # 输出上述的各种文件
bash
# Makefile.am
SUBDIRS = src # 告知需要递归编译src目录
bash
# src/Makefile.am
bin_PROGRAMS = hello # 生成可执行文件hello,注意这里使用bin
hello_SOURCES = main.c # 可执行文件hello的源代码是main.c
上述configure.ac文件之所以出现各种[],是因为configure.ac实际上是使用M4宏的shell脚本,为了避免与宏操作冲突,所以需要使用[]包围参数。所以使用判断时候不能使用[], 可以使用提供的宏封装了判断,比如AS_IF,或者使用test命令
bash
if test "$x" = "$y"; then ...
Autoconf
一些常用的宏
bash
AC_DEFUN([VARIABLE], [VALUE]) # 定义一个宏变量
AC_DEFINE([VARIABLE], [VALUE], [DESCRIPTION])# 定义一个宏变量,会将定义写入config headers, 比如config.h, `#define VARIABLE VALUE`
AC_SUBST(VARIABLE, [VALUE]) # 将本地的变量全局化,其他文件可以引用,比如in文件中可以使用, Makefile.am也可以使用, $(VARIABLE)
# or
FOO=BAR
AC_SUBST([FOO])
# or
AC_SUBST([FOO])
FOO=BAR
AC_PREREQ([VERSION]) # require a minimal autoconf version
AC_CONFIG_SRCDIR([src/main.c]) # a safety check. file应该是一个分发的源文件,保证configure处于正确的位置
AC_CONFIG_AUX_DIR([build-aux]) # 中间生成的脚本文件放到build-aux文件夹中
AC_PROG_CC, AC_PROG_CXX # compiler checks
AC_PROG_SED, AC_PROG_YACC, ... # 应用程序检测
AC_CHECK_PROGS([VAR], [PROGS], [VAL if not found])
AC_CHECK_PROGS([TAR], [tar gtar], [:])
if test "$TAR" = :; then
AC_MSG_ERROR([This package needs tar.]) # print error msg and abort 'configure'
fi
# check whether LIBRARY exists and contains FUNCT, 如果成功,但是ACT_IF_FOUND没有set,那将会设置LIBS="-lLIBRARY $LIBS"和写入config.h判断#define HAVE LIBLIBRARY
AC_CHECK_LIB([LIBRARY], [FUNCT], [ACT_IF_FOUND], [ACT_IF_NOT])
AC_CHECK_LIB([efence], [malloc], [EFENCELIB=-lefence])
AC_SUBST([EFENCELIB])
AC_CHECK_HEADERS([HEADERS]) # 检测头文件,如果存在,就在config.h文件中定义 #define HAVE_[HEADER]_H, /会被替换成_
AC_CHECK_HEADER([HEADER], [ACT_IF_FOUND], [ACT_IF_NOT])
# 根据HEADERS.in生成HEADER,AC_DEFINE定义的变量会出现在HEADERS文件中,也可以修改输入文件
AC_CONFIG_HEADERS([HEADERS])
AC_CONFIG_HEADERS([config.h:config.hin])
# 根据FILE.in生成FILE,里面可以使用AC_SUBST定义的变量, in文件中使用@VAR@引用这些变量, Makefile.in除了使用这种方式外,还可以使用$(VAR)引用,因为automake做了处理,VAR=@VAR@
AC_CONFIG_FILES([FILES...])
AC_CONFIG_FILES([Makefile sub/Makefile script.sh:script.in])
Automake
一般不直接使用automake命令,因为automake命令主要是将Makefile.am转化为Makefile.in,而后者是很复杂的脚本,不建议阅读。下面记录下automake相关的配置。
bash
# configure.ac
AM_INIT_AUTOMAKE([OPTIONS...])
#OPTIONS: -Wall -Werror foreign 1.11.1(minimal version of automake required) dist-bzip2 tar-ustar
AC_CONFIG_FILES([FILES...]) # 上面已解释
AC_CHECK_HEADER([bar.h], [use_bar=yes])
AM_CONDITIONAL([WANT_BAR], [test "$use_bar" = yes)
bash
# Makefile.am
# where_PRIMARY = targets ...
# PRIMARY(targets应该怎么build): _PROGRAMS _LIBRARIES _LTLIBRARIES(libtool llibraries) _HEADERS _SCRIPTS _DATA
# where(代表target被安装在哪儿): bin_ $(bindir), lib_ $(libdir), check_, dist_ nodist
bash
bin_PROGRAMS = foo run-me
if WANT_BAR
bin_PROGRAMS += bar
endif
foo_SOURCES = foo.c foo.h print.c print.h
run_me_SOURCES = run.c run.h print.c
注意非字母会被转成_; 上述header文件不会编译,但是会被加入到分发包里面。
静态库
- Add
AC_PROG_RANLIB
toconfigure.ac
- 添加如下到Makefile.am
bash
lib_LIBRARIES = libfoo.a libbar.a libfoo_a_SOURCES = foo.c privfoo.h libbar_a_SOURCES = bar.c privbar.h include_HEADERS = foo.h bar.h
# These libraries will be installed in $(libdir).
# Library names must match lib*.a.
# Public headers will be installed in $(includedir).
# Private headers are not installed, like ordinary source files.
Convenience Libraries
有时候只是不想发布静态库,只是想编译时候使用到,可以这么整
bash
# lib/Makefile.am
noinst_LIBRARIES = libcompat.a
libcompat_a_SOURCES = xalloc.c xalloc.h
bash
# src/Makefile.am
bin_PROGRAMS = foo run-me
foo_SOURCES = foo.c foo.h print.c print.h
run_me_SOURCES = run.c run.h print.c run_me_LDADD = ../lib/libcompat.a
run_me_CPPFLAGS = -I$(srcdir)/../lib $(EFENCELIB) # EFENCELIB变量前面使用AC_CHECK_LIB定义过
Per-Target Flags
bash
foo_CFLAGS Additional C compiler flags
foo_CPPFLAGS Additional preprocessor flags (-Is and -Ds)
foo_LDADD Additional link objects, -ls and -Ls (if foo is a program)
foo_LIBADD Additional link objects, -ls and -Ls (if foo is a library)
foo_LDFLAGS Additional linker flags
The default value for foo XXXFLAGS is $(AM_XXXFLAGS), 注意这里的AM_XXXFLAGS跟XXXFLAGS的区别。一般在配置文件中使用比如AM_CFLAGS=-g2 -03
, CFLAGS留给用户去覆盖,比如./configure CFLAGS="=-g -02"
, AM_CFLAGS
和CFLAGS
是同时在comiper配置中生效的。
打包分发
make dist和make distcheck会生成tarball,那么哪些文件会添加进来呢?
- ..._SOURCES 定义的源文件
- ..._HEADERS 定义的头文件
- dist_..._SCRIPTS 定义的脚本文件
- dist_..._DATA 定义的数据文件
- EXTRAS_DIST变量定义的文件
- 公共文件,比如ChangeLog, NEWS等