GNU Autotools Automake and Autoconf
Automake解释
在 GNU 构建系统中,.am 后缀代表 Automake 模板文件。
它是 GNU Autotools 工具链中的核心组件。简单来说,Makefile.am 是由开发者编写的“高阶”描述文件,最终会被处理成我们常用的 Makefile。
为什么不直接写 Makefile?
直接编写 Makefile 需要处理复杂的跨平台兼容性(例如不同系统的编译器参数、库路径、安装路径等)。Makefile.am 的出现是为了让开发者只需关注“我要编译什么”,而把“怎么兼容各种系统”交给工具去处理。
Automake是如何演变成 Makefile 的?
一个 .am 文件要变成最终的 Makefile,通常需要经历以下流程:
automake阶段:automake工具读取Makefile.am,并结合configure.ac中的配置,生成一个名为Makefile.in的文件(这是一个符合 GNU 标准的 Makefile 模板)。configure阶段:用户运行./configure脚本。该脚本会探测当前系统的环境(如 GCC 版本、是否存在某个库),然后根据Makefile.in生成最终的Makefile。
Makefile.am 的基本结构
它的语法非常简洁,通常由一些预定义的变量组成:
# 定义要生成的二进制程序名
bin_PROGRAMS = hello
# 定义编译 hello 程序所需的源代码文件
hello_SOURCES = main.c hello.c hello.h
# 定义编译参数(可选)
hello_CFLAGS = -Wall -O2
Autoconf介绍
在 GNU Autotools 工具链中,configure.ac 的 .ac 后缀是 Autoconf 的缩写。
它是 autoconf 工具的核心输入文件,主要用于描述项目对系统环境的依赖需求。
configure.ac 是一个使用 M4 宏语言 编写的脚本。它的目的是自动化地生成著名的 configure 外壳脚本(Shell Script)。
通过 configure.ac,你可以告诉工具:
* 这个项目叫什么名字,版本是多少?
* 它需要什么版本的编译器(如 GCC)?
* 它依赖哪些头文件(如 stdio.h)?
* 它需要哪些外部库(如 libssl)?
Autoconf演变过程
在早期的 Autotools 版本中,这个文件被命名为 configure.in(取 “Input” 之意),但为了更清晰地表示它是专门给 Autoconf 使用的文件,后来官方统一改为了 configure.ac。
构建全流程图解
在一个标准的 GNU 项目中,文件的转换关系如下:
- 开发者编写:
configure.ac和Makefile.am。 - 运行 Autoconf:
autoconf将configure.ac转换为configure脚本。 - 运行 Automake:
automake将Makefile.am转换为Makefile.in。 - 用户运行
./configure:探测环境,将Makefile.in转换为最终的Makefile。
一个典型的 configure.ac 内容示例
# 初始化项目信息
AC_INIT([my-project], [1.0], [bug@example.com])
# 检查 C 编译器是否存在
AC_PROG_CC
# 检查某个特定的头文件
AC_CHECK_HEADERS([unistd.h])
# 检查某个库中的函数
AC_CHECK_LIB([m], [cos])
# 生成最终的输出文件(通常是 Makefile)
AC_OUTPUT
系统工程师的视角
当你看到 .am 文件时,意味着这个项目使用的是经典的 GNU Build System。
* 如果你想修改编译参数:你应该修改 Makefile.am 然后重新运行 autoreconf 或 automake。
* 如果你只是想编译安装:你通常不需要理会 .am 文件,直接执行 ./configure && make 即可。
* .ac 是源头:如果你发现 ./configure 运行报错(比如探测不到某个库),你不应该修改 configure 脚本(它是生成的,改了也会被覆盖),而应该去修改 configure.ac。
* M4 宏:以 AC_ 开头的都是 Autoconf 预定义的 M4 宏。
* 跨平台利器:configure.ac 最大的价值在于它能生成极其复杂的 Shell 脚本,这些脚本甚至能在没有任何开发工具、只有基本 /bin/sh 的古老 Unix 系统上运行。
这种机制虽然看起来繁琐,但在处理 C/C++ 程序的跨平台分发(尤其是跨各种 Unix/Linux 变体)时,具有极强的鲁棒性。
常见文件后缀对比
在 Autotools 构建的项目中,你会看到一系列相似的文件,它们的职责如下:
| 文件名 | 后缀含义 | 角色 |
|---|---|---|
Makefile.am |
Automake | 开发者编写的原始模板,描述构建逻辑。 |
Makefile.in |
Input | automake 生成的中间文件,作为 configure 的输入。 |
Makefile |
(无) | configure 根据当前系统环境生成的最终构建脚本。 |
configure.ac |
Autoconf Config | 描述项目的配置需求(检查哪些库、头文件等)。 |
Autotools 完整工具链映射
这一套流程涉及到了三个核心工具:
| 工具名称 | 输入文件 | 输出文件 | 角色 |
|---|---|---|---|
| Autoconf | configure.ac |
configure (脚本) |
环境探测器的生成器 |
| Automake | Makefile.am |
Makefile.in (模板) |
构建逻辑的抽象器 |
| GNU Make | Makefile |
二进制程序 / 库 | 真正的执行者 |
CMake和CMakeLists.txt
- 工具性质:构建系统生成器(Build System Generator)。
- 核心文件:
CMakeLists.txt。 - 工作原理:
cmake不负责具体的编译工作。它读取CMakeLists.txt,然后根据你当前的系统环境,“翻译”出对应的构建脚本。- 在 Linux 上,它通常翻译成
Makefile。 - 在 Windows 上,它翻译成 Visual Studio 的工程文件 (
.sln)。 - 它还可以翻译成
Ninja构建文件。
- 在 Linux 上,它通常翻译成
- 优势:跨平台能力极强,语法比
Makefile更具可读性。
使用 CMake(现代主流方式):
- 开发者编写
CMakeLists.txt。 - 用户输入
cmake .-> 这一步生成了 Makefile。 - 用户输入
make-> 这步才是真正的编译。
make和CMake的关键差异
| 特性 | Make | CMake |
|---|---|---|
| 层级 | 底层(直接调用编译器) | 高层(生成底层构建文件) |
| 配置文件 | Makefile |
CMakeLists.txt |
| 可读性 | 较低(充满了 Tab 和 Shell 命令) | 较高(类似脚本语言的宏命令) |
| 跨平台性 | 差(针对 Unix/Linux 设计) | 强(支持 Windows, macOS, Linux) |
| 依赖发现 | 需手动指定路径 | 强大的 find_package 功能自动找库 |
从架构层级上来说,CMake 和 Automake 属于同一类东西:它们都是构建系统生成器(Build System Generators)。
它们都不直接负责编译代码,而是负责“写”出让 make(或其他构建工具)执行的脚本。
虽然它们的目标一致,但实现哲学和技术代差非常明显。我们可以把它们看作是“老一代行业标准”与“现代工业标准”的竞争。
Automake (GNU 风格)
- 组合拳:它通常不是单独行动,而是和
Autoconf一起组成 Autotools。 - 流程:
configure.ac+Makefile.am–>configure+Makefile.in–>Makefile。 - 特点:深度绑定 Unix/Linux。它是为了解决 C 语言在各种古老的 Unix 变体(Solaris, AIX, HP-UX, Linux)之间的兼容性而生的。
CMake (现代跨平台风格)
- 组合拳:通常单枪匹马,只需要一个
CMakeLists.txt。 - 流程:
CMakeLists.txt–>Makefile(或其他如 Ninja, VS Solution)。 - 特点:天生支持 Windows、macOS 和 Linux。它是为了解决“一份代码在所有主流操作系统上都能编译”而设计的。
项目使用
-
老牌开源项目(如
git,curl,bash,coreutils)通常依然坚持使用 Autotools。这是因为这些项目有着沉重的历史包袱,且必须保证在最极端的、最古老的 Unix 服务器上也能跑通。 -
现代高性能项目(如
LLVM,MySQL,PyTorch,Qt,Swift)几乎清一色选择了 CMake。因为它对现代 C++ 支持更好,且让开发者在 Windows 电脑上调试代码变得异常简单。
