14、Autotools入门:GNU构建系统概述、autoconf/automake/libtool关系、configure.ac基本结构、AM_INIT_AUTOMAKE

说到Autotools,很多刚接触Linux下C/C++开发的朋友会觉得头大。说实话,我当年第一次看到configure.ac和Makefile.am的时候,也是一脸懵。但用久了你会发现,这套工具虽然学习曲线陡峭,但确实是跨平台构建的利器。

今天我们就来聊聊GNU构建系统,也就是大家常说的Autotools。我会从整体架构讲起,然后深入到具体的配置文件中。

GNU构建系统:到底解决了什么问题?

你想想看,一个C/C++项目要在Linux、macOS、各种BSD系统上编译,每个系统的编译器路径、库文件位置、头文件目录都不一样。手动写Makefile?那得写多少条件判断啊。

GNU构建系统的核心思路很简单:先探测环境,再生成Makefile。说白了就是三步走:

  1. configure:扫描系统,看看有什么编译器、什么库、什么头文件
  2. make:根据探测结果编译代码
  3. make install:把编译好的东西装到系统里

我在项目中遇到过最典型的场景:一个库在Ubuntu上编译得好好的,换到CentOS上就报错。用Autotools之后,configure脚本会自动检测系统差异,省去了大量手工调试的时间。

核心思想:Autotools不是帮你写Makefile,而是帮你生成Makefile。它把「平台差异」这个脏活累活揽到自己身上了。

autoconf / automake / libtool 三兄弟

这三个工具各司其职,配合起来才能完成整个构建流程。我习惯把它们比作一个施工队:

工具 职责 输入文件 输出文件
autoconf 系统探测与配置 configure.ac configure(shell脚本)
automake Makefile模板生成 Makefile.am Makefile.in
libtool 共享库构建管理 (由configure.ac调用) libtool脚本

autoconf 是老大,负责摸清系统的底细。它读取configure.ac,生成一个可执行的configure脚本。这个脚本跑起来之后,会检查编译器、头文件、库函数等等。

automake 是老二,负责把Makefile.am转换成Makefile.in。Makefile.am的语法比纯Makefile简洁得多,你只需要告诉它「我要编译哪些源文件」「链接哪些库」,剩下的依赖关系automake帮你搞定。

libtool 是小弟,专门处理共享库(.so/.dylib/.dll)的创建。不同系统创建共享库的命令完全不同,libtool把这些差异都封装起来了。

我的经验:刚开始用Autotools时,可以先只学autoconf+automake,暂时跳过libtool。等需要构建共享库时再引入libtool,这样学习曲线会平缓很多。

configure.ac 基本结构

configure.ac是Autotools的入口文件。说白了,它就是一份「系统探测清单」,告诉autoconf你要检查哪些东西。

一个最简的configure.ac长这样:

AC_INIT([hello], [1.0], [bug@example.com])
AM_INIT_AUTOMAKE([foreign])
AC_PROG_CC
AC_CONFIG_FILES([Makefile])
AC_OUTPUT

我们来逐行拆解一下:

  • AC_INIT:初始化,指定项目名、版本号、bug报告邮箱。这是必须的,autoconf靠它生成一些宏定义。
  • AM_INIT_AUTOMAKE:启用automake支持。后面的[foreign]参数表示放宽检查——默认automake要求你提供AUTHORS、NEWS等文件,加上foreign就不强制了。
  • AC_PROG_CC:检查C编译器。autoconf会依次找gcc、cc等,找不到就报错退出。
  • AC_CONFIG_FILES:指定要生成的Makefile。这里写Makefile,configure就会把Makefile.in处理成Makefile。
  • AC_OUTPUT:执行输出。告诉autoconf「检查完了,生成文件吧」。

注意:AC_INIT和AC_OUTPUT是成对出现的。我曾经见过有人忘了写AC_OUTPUT,结果configure脚本跑完啥也没生成,排查了半天才发现是这个问题。

AM_INIT_AUTOMAKE 详解

这个宏是连接autoconf和automake的桥梁。它的参数决定了automake的行为风格。

常用的参数有:

  • foreign:宽松模式,不强制要求GNU编码规范中的辅助文件
  • gnu:严格模式(默认),要求提供AUTHORS、NEWS、README等文件
  • gnits:更严格的模式,连文件格式都有要求
  • subdir-objects:允许源文件放在子目录中,编译出的.o文件也放在对应子目录

我个人习惯用AM_INIT_AUTOMAKE([foreign subdir-objects])。为什么?因为大多数项目不需要那些GNU规范文件,而且源文件放在子目录里更整洁。

举个例子,如果你的项目结构是:

project/
├── src/
│   ├── main.c
│   └── util.c
└── configure.ac

没有subdir-objects的话,编译出来的.o文件会全部堆在项目根目录,乱得很。加上之后,.o文件会乖乖待在src/目录下。

完整的构建流程图

下面这张图展示了从源码到最终可执行文件的完整流程。我特意把autoconf、automake、libtool的分工画清楚了:

Autotools 构建系统流程图 configure.ac Makefile.am *.c / *.h autoconf automake libtool configure(脚本) Makefile.in libtool(脚本) Makefile + 可执行文件 autoconf 处理 automake 处理 libtool 处理 生成 生成 生成

从图上可以看得很清楚:configure.ac是核心输入,它驱动autoconf生成configure脚本;Makefile.am驱动automake生成Makefile.in;而libtool则是在configure脚本运行时被调用,生成libtool脚本。最终,用户执行./configure && make,所有东西就串起来了。

一个完整的实战例子

光说不练假把式。我们来看一个完整的例子,假设你要构建一个简单的hello程序:

第一步:创建项目目录

mkdir hello-world
cd hello-world

第二步:编写configure.ac

AC_INIT([hello-world], [1.0], [dev@example.com])
AM_INIT_AUTOMAKE([foreign])
AC_PROG_CC
AC_CONFIG_FILES([Makefile])
AC_OUTPUT

第三步:编写Makefile.am

bin_PROGRAMS = hello
hello_SOURCES = main.c

这里bin_PROGRAMS表示要生成一个可执行文件,hello_SOURCES指定源文件。automake的命名规则是:目标名_SOURCES

第四步:编写main.c

#include <stdio.h>

int main(void) {
    printf("Hello, Autotools!\n");
    return 0;
}

第五步:生成构建系统

autoreconf -i

这个命令会自动调用autoconf、automake、aclocal等工具,生成完整的构建系统。-i参数表示复制缺失的辅助文件(如config.sub、install-sh等)。

第六步:编译安装

./configure
make
sudo make install

避坑指南:我曾经在macOS上执行autoreconf -i,结果报错说缺少某些文件。后来发现是Xcode Command Line Tools没装全。运行xcode-select --install装一下就好了。Linux上一般装autoconf automake libtool这三个包就行。

小结

Autotools的学习曲线确实陡,但它的设计思路其实很清晰:把平台差异抽象出来,让开发者专注于业务代码。configure.ac就是这份抽象的描述文件,AM_INIT_AUTOMAKE是连接autoconf和automake的桥梁。

刚开始用的时候,你可能会觉得「写个Makefile多简单,干嘛要绕这么大一圈」。但当你需要在5个不同的Linux发行版、3个BSD系统、再加上macOS上构建同一个项目时,你就会明白Autotools的价值了。

嗯,这一章就到这里。记住:configure.ac是灵魂,Makefile.am是骨架,autoreconf -i是启动命令。先把这三样东西刻在脑子里,后面的内容就好理解了。


公众号:蓝海资料掘金营,微信deep3321