autotools

今时今日,好多人聊起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文件不会编译,但是会被加入到分发包里面。

静态库

  1. Add AC_PROG_RANLIB to configure.ac
  2. 添加如下到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_CFLAGSCFLAGS是同时在comiper配置中生效的。

打包分发

make dist和make distcheck会生成tarball,那么哪些文件会添加进来呢?

  • ..._SOURCES 定义的源文件
  • ..._HEADERS 定义的头文件
  • dist_..._SCRIPTS 定义的脚本文件
  • dist_..._DATA 定义的数据文件
  • EXTRAS_DIST变量定义的文件
  • 公共文件,比如ChangeLog, NEWS等
相关推荐
编程重生之路21 天前
今年2024的1024文章
ai·ai编程·编译器·cursor·1024程序员节
Thanks_ks22 天前
【第五章·选择控制结构】第一节:生活中与计算机中的问题求解方法
算法·编译器·机器语言·分治策略·c 语言程序设计·计算机程序·程序设计语言
Eloudy1 个月前
函数地址对齐 __attribute__((aligned(64))) 编译器选项 -falign-functions=4
算法·编译器
Trouvaille ~2 个月前
【C++篇】C++类与对象深度解析(六):全面剖析拷贝省略、RVO、NRVO优化策略
c++·c++20·编译原理·编译器·类和对象·rvo·nrvo
EleganceJiaBao3 个月前
【Story】编译器的基础概念与类型分类
java·c语言·c++·python·gnu·编译器·gcc
怜渠客3 个月前
VS2022快速搭建OLLVM
c++·ide·编译器
张一西3 个月前
ARM学习(31)编译器对overlay方式的支持
arm开发·编译器·overlay·动态加载·bank·armcc·armclang
harykali4 个月前
静态分析学习笔记02:程序中间表示(IR)
编译器
剑海风云4 个月前
GraalVM简介及使用
java·jvm·graalvm·编译器·本地镜像
flysnow0105 个月前
多版本GCC安装及切换
编译器·gcc·切换版本