autoreconf -i 看懂 Autoconf:以 Lely CANopen 交叉编译为例

本文面向第一次接触 Autotools / Autoconf 的读者。目标不是背命令,而是看懂:autoreconf -i 为什么要执行、从哪个文件开始、会生成什么文件、这些文件又怎样参与 Lely CANopen 的交叉编译。

在这里插入图片描述

@[toc]

先给结论

autoreconf -i 的作用是:根据源码中的 Autotools 描述文件,生成后续构建需要的 configure 脚本、Makefile.in 模板、config.h.in 模板和一批辅助脚本

它不是编译命令,也不会生成 ARM/aarch64 目标程序。真正编译 Lely 的动作发生在后面的 make 阶段。

完整链路如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
flowchart TD
A[configure.ac<br/>Autoconf 入口] --> B[autoreconf -i]
C[Makefile.am<br/>Automake 输入] --> B
D[m4/*.m4<br/>宏文件] --> B

B --> E[configure<br/>配置脚本]
B --> F[Makefile.in<br/>Makefile 模板]
B --> G[config.h.in<br/>配置头模板]
B --> H[辅助脚本<br/>config.guess / config.sub / depcomp / ltmain.sh]

E --> I[../configure --host=&lt;TARGET_TRIPLET&gt;]
F --> I
G --> I

I --> J[Makefile]
I --> K[config.h]
I --> L[libtool]
I --> M[pkgconfig/*.pc]

J --> N[make]
N --> O[.o / .so / .a / coctl]
O --> P[make install DESTDIR=stage]

一句话记忆:

1
2
3
4
5
6
7
configure.ac + Makefile.am
↓ autoreconf -i
configure + Makefile.in
↓ ../configure --host=...
Makefile + config.h
↓ make
真正编译 Lely

为什么会有 Autoconf

C/C++ 项目在不同平台上构建时,不能只靠一份固定 Makefile。原因包括:

  • 编译器名字可能不同,例如 gccclangaarch64-poky-linux-gcc
  • 系统头文件可能不同,例如 Linux 下才有 SocketCAN 相关头文件。
  • 库的名字和链接参数可能不同,例如 pthreadrt
  • 目标平台可能不是当前主机,例如 x86 Ubuntu 上交叉编译到 ARM64 Linux。
  • 项目可能要按配置开启或关闭功能,例如 Python、Cython、测试、共享库、静态库。

GNU Autoconf 官方说明:Autoconf 是一套 M4 宏,用来生成可以自动配置源码包的 shell 脚本;这些脚本会根据不同类 Unix 系统的特性调整构建配置。参考来源:GNU Autoconf introduction

所以 Autoconf 的核心工作不是“编译”,而是生成并运行一个配置脚本:

1
2
3
Autoconf 输入:configure.ac
Autoconf 输出:configure
configure 输出:Makefile、config.h 等真正构建文件

autoreconf -i 不是一个工具在单干

autoreconf 可以理解成 Autotools 的总调度器。它会按照项目需要调用多个工具。

GNU Automake 手册说明,autoreconf 会以正确顺序调用 autoconfautomake 和相关命令;核心分工是:Autoconf 根据 configure.ac 生成 configure,Automake 根据 Makefile.amconfigure.ac 生成 Makefile.in。参考来源:GNU Automake: Creating amhello

在 Lely 这类项目中,可以把 autoreconf -i 简化理解成:

1
2
3
4
5
6
7
8
9
10
11
12
flowchart LR
A[autoreconf -i] --> B[aclocal]
A --> C[libtoolize]
A --> D[autoheader]
A --> E[autoconf]
A --> F[automake]

B --> B1[生成 aclocal.m4]
C --> C1[复制 ltmain.sh 和 m4/libtool*.m4]
D --> D1[生成 config.h.in]
E --> E1[生成 configure]
F --> F1[生成 Makefile.in]

各工具职责如下:

工具 输入 输出 作用
aclocal configure.acm4/*.m4、系统宏 aclocal.m4 收集宏定义,供 Autoconf 使用
libtoolize configure.ac 中的 Libtool 宏 ltmain.shm4/libtool*.m4 准备动态库/静态库构建支持
autoheader configure.ac config.h.in 生成配置头文件模板
autoconf configure.acaclocal.m4 configure 生成配置脚本
automake Makefile.amconfigure.ac Makefile.in 生成 Makefile 模板

-i 是什么作用

-i--install 的短写。

它的作用是:发现项目缺少标准辅助文件时,自动复制这些文件到源码树中

GNU Autoconf 手册说明,autoreconf --install 会安装缺失的标准辅助文件;默认是复制文件,也可以配合 --symlink 改成符号链接。它还可能触发 automake --add-missinglibtoolize 等动作。参考来源:GNU Autoconf: autoreconf invocation

典型辅助文件包括:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
compile
config.guess
config.sub
install-sh
missing
depcomp
test-driver
tap-driver.sh
ltmain.sh
m4/libtool.m4
m4/ltoptions.m4
m4/ltsugar.m4
m4/ltversion.m4
m4/lt~obsolete.m4

这些文件大多不是项目业务源码,而是构建系统为了跨平台、依赖跟踪、测试和库构建而使用的脚本或宏文件。

什么时候需要执行 autoreconf -i

通常在这些情况下需要执行:

场景 是否需要 原因
从 Git 仓库克隆 Lely 源码后首次构建 需要 源码树可能没有现成 configure,需要先生成
修改了 configure.ac 需要 configure 由它生成,入口变了要重新生成
修改了某个 Makefile.am 需要 Makefile.in 由它生成,模板变了要重新生成
更新了 Autoconf / Automake / Libtool 版本 建议 生成文件可能需要按新工具刷新
只是重新执行 make 通常不需要 已有 Makefile 时,直接编译即可
只是换一个 --host 重新配置 不一定需要 一般重新跑 ../configure --host=... 即可

Lely 仓库说明:项目使用 GNU Build System,即 configuremakemake install;初次 clone 或下载源码后,需要在项目根目录运行 autoreconf -i 生成 configure 脚本,这一步在 configure.acMakefile.am 变化时才需要重复。参考来源:Lely core README

补充:M4 宏到底是什么

前面多次提到 m4/*.m4,这里单独解释。先给结论:M4 是一个文本宏处理器;.m4 文件就是给 Autoconf 使用的宏定义文件。

GNU M4 手册对 M4 的定义是:M4 会把输入复制到输出,同时展开遇到的宏;宏可以是内置宏,也可以是用户自定义宏,并且可以带参数。参考来源:GNU M4 manual

可以先把它理解成“比 C 预处理器更通用的文本展开器”:

1
2
3
输入文本里写:某个宏名(参数1, 参数2)
↓ M4 展开
输出文本里变成:一段更长、更具体的文本

在 Autoconf 体系里,M4 的输出通常不是 C 代码,而是 shell 脚本片段。例如:

1
AC_CHECK_HEADER([linux/can.h])

这不是 shell 命令本身,而是一个 Autoconf 宏调用。Autoconf 会借助 M4 把它展开成一段 shell 检测逻辑,用来判断目标构建环境里是否有 linux/can.h 这个头文件。

所以关系是:

1
2
3
4
5
flowchart LR
A[configure.ac<br/>写着 AC_ / AM_ / AX_ / LT_ 宏] --> B[Autoconf]
C[m4/*.m4<br/>宏定义] --> B
D[aclocal.m4<br/>宏汇总] --> B
B --> E[configure<br/>shell 脚本]

M4 宏和 C 宏有什么区别

对比项 C 预处理宏 M4 / Autoconf 宏
常见文件 .h.c.cpp .m4configure.ac
处理工具 C/C++ 预处理器 m4 / autoconf / aclocal
输入 C/C++ 源码 Autoconf 配置描述文本
输出 预处理后的 C/C++ 源码 configure shell 脚本、aclocal.m4
用途 条件编译、常量、代码替换 生成平台检测逻辑、生成构建脚本

一句话:C 宏服务于编译源码;M4 宏服务于生成构建脚本。

为什么 Lely 的 m4/ 目录里有这些 .m4 文件

你看到的目录内容类似:

1
2
3
4
5
6
7
8
9
10
-rw-rw-r--  1 <USER> <GROUP>   1415 <DATE> ax_check_python.m4
-rw-rw-r-- 1 <USER> <GROUP> 12111 <DATE> ax_code_coverage.m4
-rw-rw-r-- 1 <USER> <GROUP> 21126 <DATE> ax_cxx_compile_stdcxx.m4
-rw-rw-r-- 1 <USER> <GROUP> 22556 <DATE> ax_pthread.m4
-rw-rw-r-- 1 <USER> <GROUP> 8939 <DATE> ax_valgrind_check.m4
-rw-r--r-- 1 <USER> <GROUP> 306675 <DATE> libtool.m4
-rw-r--r-- 1 <USER> <GROUP> 6140 <DATE> lt~obsolete.m4
-rw-r--r-- 1 <USER> <GROUP> 14514 <DATE> ltoptions.m4
-rw-r--r-- 1 <USER> <GROUP> 4384 <DATE> ltsugar.m4
-rw-r--r-- 1 <USER> <GROUP> 699 <DATE> ltversion.m4

这些文件大体分两类:

文件类型 示例 来源 作用
项目自带 / 第三方 Autoconf 宏 ax_check_python.m4ax_pthread.m4ax_cxx_compile_stdcxx.m4 Lely 仓库中已有,或来自 Autoconf Archive / 项目维护者 configure.ac 提供额外检测能力
Libtool 宏 libtool.m4ltoptions.m4ltsugar.m4ltversion.m4lt~obsolete.m4 libtoolize 从系统 Libtool 安装目录复制 支持生成跨平台库构建逻辑

Lely 的 configure.ac 中指定了:

1
AC_CONFIG_MACRO_DIR([m4])

这表示:项目本地的 Autoconf 宏目录是 m4/ Autoconf/Automake/Libtool 在处理这个项目时,会把这个目录作为项目宏目录使用。Lely 仓库的 m4/ 目录中列出了 ax_check_python.m4ax_code_coverage.m4ax_cxx_compile_stdcxx.m4ax_pthread.m4ax_valgrind_check.m4 等文件。参考来源:lely-core/m4

这些 .m4 文件是怎么“生成”或“复制”出来的

这里要区分三件事:

1
2
3
项目已有的 .m4 文件
自动复制进来的 .m4 文件
汇总生成的 aclocal.m4 文件

1. ax_*.m4:通常不是本次 autoreconf -i 临时生成的

像这些文件:

1
2
3
4
5
ax_check_python.m4
ax_code_coverage.m4
ax_cxx_compile_stdcxx.m4
ax_pthread.m4
ax_valgrind_check.m4

一般属于 Autoconf 扩展宏

它们的名字以 AX_ 开头,常见来源有两种:

  1. 项目维护者自己写的宏。
  2. 从 GNU Autoconf Archive 复制进项目的宏。

GNU Autoconf Archive 是一个收集大量可复用 Autoconf 宏的项目;其仓库说明它收集了 500 多个由社区贡献的 GNU Autoconf 宏。参考来源:GNU Autoconf Archive mirror

在 Lely 里,ax_check_python.m4 是 Lely 自己维护的宏文件,文件说明中写明它定义 AX_CHECK_PYTHON(MAJOR-VERSION, ...),用于检查指定 Python 主版本是否可用,并设置 PYTHON2PYTHON3 这类输出变量。参考来源:lely-core/m4/ax_check_python.m4

ax_pthread.m4 这类宏的典型作用是检测 POSIX threads。GNU Autoconf Archive 对 AX_PTHREAD 的说明是:它会判断如何构建使用 POSIX threads 的 C 程序,并设置 PTHREAD_LIBSPTHREAD_CFLAGS 等输出变量。参考来源:Autoconf Archive: AX_PTHREAD

2. libtool.m4 / lt*.m4:通常是 libtoolize 复制进来的

你看到的这些文件:

1
2
3
4
5
libtool.m4
ltoptions.m4
ltsugar.m4
ltversion.m4
lt~obsolete.m4

来自 GNU Libtool。

Lely 的 configure.ac 里有:

1
LT_INIT([win32-dll])

这个宏表示项目启用 Libtool 支持。autoreconf -i 看到项目使用 Libtool 后,会调用 libtoolizelibtoolize 会把 Libtool 所需文件复制到项目中,例如:

1
2
3
4
5
6
ltmain.sh
m4/libtool.m4
m4/ltoptions.m4
m4/ltsugar.m4
m4/ltversion.m4
m4/lt~obsolete.m4

GNU Libtool 手册说明,Libtool 用一个通用接口隐藏不同平台构建共享库和静态库时的差异。参考来源:GNU Libtool manual

所以这几个文件不是 Lely 业务代码,也不是编译产物,而是 Libtool 的宏和脚本支持文件

3. aclocal.m4:这是 aclocal 汇总生成的

m4/*.m4 是单个宏文件,而:

1
aclocal.m4

aclocal 生成的宏汇总文件。

处理关系如下:

1
2
3
4
5
6
7
8
9
flowchart TD
A[configure.ac<br/>使用 AC_ / AM_ / AX_ / LT_ 宏] --> B[aclocal -I m4]
C[m4/ax_*.m4<br/>项目宏] --> B
D[m4/libtool.m4 + lt*.m4<br/>Libtool 宏] --> B
E[系统 aclocal 宏目录<br/>Automake / Autoconf Archive 宏] --> B
B --> F[aclocal.m4<br/>宏汇总]
F --> G[autoconf]
A --> G
G --> H[configure]

因此可以这样记:

1
2
3
m4/*.m4      是宏来源文件
aclocal.m4 是宏汇总文件
configure 是宏展开后生成的 shell 配置脚本

libtoolize 又是哪来的

libtoolize 是 GNU Libtool 软件包提供的命令,不是 Lely 自己写的命令。

可以用下面命令确认它来自哪里:

1
2
which libtoolize
libtoolize --version

常见位置类似:

1
/usr/bin/libtoolize

它通常由系统包管理器安装,例如 Ubuntu/Debian 系统中常由 libtool 包提供。

它被调用的条件通常是:

  1. 项目 configure.ac 中使用了 LT_INIT 等 Libtool 宏。
  2. 执行 autoreconf -i
  3. autoreconf 判断需要初始化/刷新 Libtool 支持文件。

Lely 中正是因为 configure.ac 里有 LT_INIT([win32-dll]),并且设置了 AC_CONFIG_MACRO_DIR([m4]),所以 libtoolize 会把 Libtool 的宏复制到 m4/,把 ltmain.sh 复制到源码根目录。

这些文件逐个是干嘛的

文件 类别 主要作用 在 Lely 构建中的意义
ax_check_python.m4 Lely 自定义 AX 宏 检查指定 Python 主版本,设置 Python 解释器变量 支持 Lely 配置 Python 相关工具;交叉编译时可用 --disable-python 降低复杂度
ax_code_coverage.m4 AX 扩展宏 增加代码覆盖率相关配置和 make 规则 主要服务测试/覆盖率,不是目标板运行 coctl 的核心依赖
ax_cxx_compile_stdcxx.m4 AX 扩展宏 检查 C++ 标准支持,必要时添加 -std=... Lely 有 C++ 接口,例如 liblely-coapp,所以需要检测 C++ 编译能力
ax_pthread.m4 AX 扩展宏 检查 pthread 编译/链接参数 Lely 在 Linux 下通常需要线程支持
ax_valgrind_check.m4 AX 扩展宏 增加 Valgrind 测试相关规则 主要用于开发测试,不是部署到目标板的核心依赖
libtool.m4 Libtool 宏 定义 Libtool 主宏,例如 LT_INIT 相关展开逻辑 支持生成跨平台库构建逻辑
ltoptions.m4 Libtool 宏 处理 Libtool 配置选项 支持 --enable-shared--enable-static 等库构建选项
ltsugar.m4 Libtool 宏 Libtool 内部辅助宏 给 Libtool 宏展开使用,通常不手工改
ltversion.m4 Libtool 宏 记录 Libtool 宏版本信息 用于版本一致性检查
lt~obsolete.m4 Libtool 宏 兼容旧 Libtool 宏 避免旧宏调用导致配置失败

这些 .m4 文件在 Lely 中怎么被用起来

结合 Lely,完整链路是:

1
2
3
4
5
6
7
8
9
10
11
flowchart TD
A[configure.ac] --> B[声明 AC_CONFIG_MACRO_DIR\([m4]\)]
A --> C[调用 AX_CHECK_PYTHON / AX_PTHREAD / LT_INIT 等宏]
D[m4/ax_*.m4] --> E[aclocal 收集]
F[m4/libtool.m4 + lt*.m4] --> E
E --> G[aclocal.m4]
G --> H[autoconf]
A --> H
H --> I[configure]
I --> J[../configure --host=&lt;TARGET_TRIPLET&gt;]
J --> K[Makefile / config.h / libtool / pkgconfig/*.pc]

分阶段看:

阶段 读哪些文件 生成什么 说明
libtoolize configure.ac ltmain.shm4/libtool.m4m4/lt*.m4 因为 Lely 使用 LT_INIT
aclocal configure.acm4/*.m4 aclocal.m4 把宏定义汇总起来
autoconf configure.acaclocal.m4 configure 把 M4 宏展开成 shell 脚本
automake Makefile.amconfigure.ac Makefile.in 生成 Makefile 模板
../configure configureMakefile.inconfig.h.in Makefileconfig.hlibtool.pc 按目标平台生成最终构建文件

为什么有些 .m4 看起来像“生成的”

判断一个 .m4 是项目自带还是工具复制,不能只看时间戳。你执行 autoreconf -i 后,很多文件的修改时间会变成同一时间,所以容易误以为都是“生成”的。

更准确的判断方法是:

1
2
3
4
5
6
7
8
9
# 如果是 Git 仓库,先看文件是否被版本管理
git ls-files m4/*.m4

# 看执行 autoreconf 后哪些文件变成未跟踪或被修改
git status --short

# 看某个文件来源和头部说明
head -40 m4/ax_check_python.m4
head -40 m4/libtool.m4

常见结论:

现象 更可能的原因
ax_*.m4 本来就在仓库里 项目维护者把宏随源码一起提交,保证别人构建时不依赖系统额外宏包
libtool.m4lt*.m4 执行后出现或刷新 libtoolize 从系统 Libtool 安装目录复制
aclocal.m4 执行后生成或刷新 aclocal 汇总所有需要的宏
configure 执行后生成或刷新 autoconf 根据 configure.ac 和宏定义生成

对你这批文件的直接结论

你列出来的文件可以这样理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
m4/ax_*.m4
作用:给 configure.ac 提供额外检测宏
来源:Lely 自带或来自 Autoconf Archive / 项目维护者
是否编译进程序:否
是否部署到板端:否

m4/libtool.m4 和 m4/lt*.m4
作用:给 Libtool 提供宏定义
来源:libtoolize 从系统 Libtool 复制
是否编译进程序:否
是否部署到板端:否

aclocal.m4
作用:汇总 m4/*.m4 和系统宏
来源:aclocal 生成
是否编译进程序:否
是否部署到板端:否

这些文件最终只服务于一件事:autoconf 能够生成正确的 configure,再让 configure 生成适合当前目标平台的构建文件。

Lely 中的入口文件是 configure.ac

在 Lely 源码根目录中,Autotools 的主入口是:

1
<LELY_SRC>/configure.ac

Lely 的 configure.ac 中包含这些关键宏:

1
2
3
4
5
6
7
8
9
10
11
12
13
AC_PREREQ([2.69])
LT_PREREQ(2.4.2)
AC_INIT([Lely core libraries], [2.4.0], [], [lely-core])
AC_CONFIG_MACRO_DIR([m4])
AC_CANONICAL_TARGET
AM_INIT_AUTOMAKE([foreign subdir-objects])
AC_LANG([C])
AC_PROG_CC_STDC
AC_LANG([C++])
AC_PROG_CXX
LT_INIT([win32-dll])
...
AC_OUTPUT

这些宏决定了 Lely 的配置系统要做什么。Lely 当前公开仓库中的 configure.ac 确实包含 AC_INITAC_CONFIG_MACRO_DIR([m4])AC_CANONICAL_TARGETAM_INIT_AUTOMAKEAC_PROG_CXXLT_INIT([win32-dll]) 等宏。参考来源:lely-core/configure.ac

关键宏解释

在 Lely 中的作用
AC_INIT 定义包名、版本等基础信息
AC_CONFIG_MACRO_DIR([m4]) 指定额外宏目录是 m4/
AC_CANONICAL_TARGET 启用平台三元组识别,例如 <TARGET_TRIPLET>
AM_INIT_AUTOMAKE 启用 Automake,根据 Makefile.am 生成 Makefile.in
AC_PROG_CC_STDC 检查 C 编译器能力
AC_PROG_CXX 检查 C++ 编译器能力
LT_INIT 启用 Libtool,用于构建库文件
AC_OUTPUT 触发输出文件生成,例如 Makefile、config.status 等

GNU Autoconf 手册说明,每个 configure 脚本必须调用 AC_INIT,另一个必需宏是 AC_OUTPUTAC_OUTPUT 会生成并运行 config.status,由它创建 Makefile 和其他配置输出文件。参考来源:GNU Autoconf: Initializing configureGNU Autoconf: Outputting files

Lely 中还有哪些输入文件

除了 configure.ac,Lely 还会使用多个 Makefile.am。这些文件是 Automake 的输入。

GNU Automake 手册说明,Automake 会从 Makefile.am 自动生成 Makefile.inMakefile.am 本质上是一系列 make 变量定义,偶尔带有规则。参考来源:GNU Automake introduction

在 Lely 中,常见输入关系如下:

1
2
3
4
5
6
7
8
9
10
11
<LELY_SRC>/configure.ac
<LELY_SRC>/Makefile.am
<LELY_SRC>/src/can/Makefile.am
<LELY_SRC>/src/co/Makefile.am
<LELY_SRC>/src/coapp/Makefile.am
<LELY_SRC>/src/io/Makefile.am
<LELY_SRC>/src/io2/Makefile.am
<LELY_SRC>/tools/Makefile.am
<LELY_SRC>/test/Makefile.am
<LELY_SRC>/unit-tests/Makefile.am
<LELY_SRC>/m4/*.m4

这些输入文件经过 autoreconf -i 后,会产生对应的模板文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<LELY_SRC>/configure
<LELY_SRC>/Makefile.in
<LELY_SRC>/src/can/Makefile.in
<LELY_SRC>/src/co/Makefile.in
<LELY_SRC>/src/coapp/Makefile.in
<LELY_SRC>/src/io/Makefile.in
<LELY_SRC>/src/io2/Makefile.in
<LELY_SRC>/tools/Makefile.in
<LELY_SRC>/test/Makefile.in
<LELY_SRC>/unit-tests/Makefile.in
<LELY_SRC>/config.h.in
<LELY_SRC>/aclocal.m4
<LELY_SRC>/ltmain.sh
<LELY_SRC>/m4/libtool*.m4

Lely 中执行 autoreconf -i 的位置

在 Lely 源码根目录执行:

1
2
cd <LELY_SRC>
autoreconf -i

注意:必须在有 configure.ac 的目录执行。不要在 build 目录里执行。

正确顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 1. 进入源码目录
cd <LELY_SRC>

# 2. 生成 configure 和 Makefile.in 等文件
autoreconf -i

# 3. 再创建独立构建目录
mkdir -p <BUILD_DIR>
cd <BUILD_DIR>

# 4. 执行 configure,生成真正 Makefile
../configure \
--host=<TARGET_TRIPLET> \
--prefix=<INSTALL_PREFIX> \
--disable-python \
--disable-cython

# 5. 真正开始编译
make -j"$(nproc)"

# 6. 安装到临时 staging 目录
make install DESTDIR="${PWD}/stage"

Lely 官方交叉编译文档给出的流程也是:clone 源码后进入 lely-core,执行 autoreconf -i,创建 build 目录,然后执行 ../configure --host=HOST --disable-python,最后 makemake install。参考来源:Lely CANopen cross-compilation

autoreconf -i 之后文件怎样流动

下面这张图从“文件输入输出”的角度看整个流程:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
flowchart TD
subgraph Source[源码目录:<LELY_SRC>]
A[configure.ac]
B[Makefile.am]
C[src/*/Makefile.am]
D[m4/*.m4]
end

subgraph Bootstrap[执行 autoreconf -i]
E[aclocal]
F[autoconf]
G[autoheader]
H[automake]
I[libtoolize]
end

subgraph Generated[源码目录中新生成或更新]
J[configure]
K[aclocal.m4]
L[config.h.in]
M[Makefile.in]
N[src/*/Makefile.in]
O[ltmain.sh / m4/libtool*.m4]
P[config.guess / config.sub / depcomp 等]
end

subgraph Build[构建目录:<BUILD_DIR>]
Q[../configure --host=<TARGET_TRIPLET>]
R[Makefile]
S[config.h]
T[libtool]
U[pkgconfig/*.pc]
V[make]
W[目标库和工具]
end

A --> E
D --> E
E --> K
A --> F
K --> F
F --> J
A --> G
G --> L
B --> H
C --> H
H --> M
H --> N
A --> I
I --> O
I --> P

J --> Q
M --> Q
N --> Q
L --> Q
Q --> R
Q --> S
Q --> T
Q --> U
R --> V
V --> W

你看到的输出是什么意思

示例输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
libtoolize: putting auxiliary files in '.'.
libtoolize: copying file './ltmain.sh'
libtoolize: putting macros in AC_CONFIG_MACRO_DIRS, 'm4'.
libtoolize: copying file 'm4/libtool.m4'
libtoolize: copying file 'm4/ltoptions.m4'
libtoolize: copying file 'm4/ltsugar.m4'
libtoolize: copying file 'm4/ltversion.m4'
libtoolize: copying file 'm4/lt~obsolete.m4'
configure.ac:17: installing './compile'
configure.ac:6: installing './config.guess'
configure.ac:6: installing './config.sub'
configure.ac:7: installing './install-sh'
configure.ac:7: installing './missing'
configure.ac:614: installing './tap-driver.sh'
src/can/Makefile.am: installing './depcomp'
parallel-tests: installing './test-driver'

这些不是错误,是正常的“补齐构建系统文件”日志。

输出 含义
copying file './ltmain.sh' Lely 使用 Libtool,需要复制 Libtool 主脚本
copying file 'm4/libtool.m4' 把 Libtool 的 M4 宏放到 m4/ 目录
installing './compile' 安装通用编译包装脚本
installing './config.guess' 安装平台识别脚本,用于猜测系统类型
installing './config.sub' 安装平台三元组规范化脚本
installing './install-sh' 安装通用安装脚本
installing './missing' 安装缺失工具提示脚本
installing './tap-driver.sh' 安装 TAP 测试驱动脚本
installing './depcomp' 安装依赖跟踪脚本,用于头文件依赖分析
installing './test-driver' 安装 Automake 测试驱动脚本

如果这一步失败,通常后面不会有可执行的 configure。如果这一步成功,下一步才是执行 ../configure

../configure --host=... 又做什么

autoreconf -i 生成的是通用模板,尚未绑定你的交叉编译目标。真正指定目标架构的是:

1
../configure --host=<TARGET_TRIPLET> ...

它会做几类事情:

  1. 检查交叉编译器是否存在。
  2. 判断目标平台类型。
  3. 根据 --enable-* / --disable-* 参数决定功能开关。
  4. 根据 Makefile.in 生成当前构建目录下的 Makefile
  5. 根据 config.h.in 生成 config.h
  6. 生成 libtool,用于后续构建库文件。
  7. 生成 pkgconfig/*.pc,供后续应用通过 pkg-config 链接 Lely。

输出关系如下:

1
2
3
4
5
6
7
8
9
10
11
flowchart LR
A[configure] --> D[../configure --host=<TARGET_TRIPLET>]
B[Makefile.in] --> D
C[config.h.in] --> D

D --> E[Makefile]
D --> F[config.h]
D --> G[config.log]
D --> H[config.status]
D --> I[libtool]
D --> J[pkgconfig/liblely-*.pc]

这里容易混淆的是:

文件 谁生成 什么时候生成 作用
Makefile.am 开发者写 源码中已有 Automake 输入
Makefile.in automake 生成 autoreconf -i 阶段 Makefile 模板
Makefile configure 生成 ../configure 阶段 make 真正读取的构建文件
config.h.in autoheader 生成 autoreconf -i 阶段 配置头模板
config.h configure 生成 ../configure 阶段 C/C++ 源码实际包含的配置头

make 阶段才是真正编译

执行:

1
make -j"$(nproc)"

这一步会读取 <BUILD_DIR>/Makefile,然后调用交叉编译器,例如:

1
2
<TARGET_TRIPLET>-gcc
<TARGET_TRIPLET>-g++

生成 Lely 的库和工具。典型产物包括:

1
2
3
4
5
liblely-*.so
liblely-*.a
coctl
其他工具程序
中间目标文件 .o / .lo

所以如果你要判断是否真的编译到了目标架构,应检查 make 之后的产物,而不是检查 autoreconf -i 的输出。

例如:

1
file <BUILD_DIR>/stage/<INSTALL_PREFIX>/bin/coctl

期望看到类似:

1
ELF 64-bit LSB executable, ARM aarch64

make install DESTDIR=... 是收集产物

执行:

1
make install DESTDIR="${PWD}/stage"

它会把编译出的文件安装到一个临时目录,而不是直接写入系统目录。

可以理解成:

1
2
3
4
5
--prefix=<INSTALL_PREFIX>
DESTDIR=<BUILD_DIR>/stage

最终安装位置:
<BUILD_DIR>/stage/<INSTALL_PREFIX>/...

典型结构:

1
2
3
4
5
6
7
8
9
10
11
<BUILD_DIR>/stage/<INSTALL_PREFIX>/
├── bin/
│ └── coctl
├── lib/
│ ├── liblely-*.so
│ ├── liblely-*.a
│ └── pkgconfig/
│ └── liblely-*.pc
├── include/
│ └── lely/
└── share/ 或 etc/

这样做的好处是不会污染构建主机的 /usr/usr/local,也便于后续打包部署到目标板。

在 Lely 交叉编译中,推荐按这个顺序执行

下面是脱敏后的完整命令模板:

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
# 1. 准备环境变量
export SDK_ROOT=<SDK_ROOT>
export WORK_ROOT=<WORK_ROOT>
export LELY_SRC=${WORK_ROOT}/lely-core
export BUILD_DIR=${LELY_SRC}/build-target
export INSTALL_PREFIX=<INSTALL_PREFIX>

# 2. 加载交叉编译 SDK 环境
source ${SDK_ROOT}/environment-setup-<target-env>

# 3. 获取并进入 Lely 源码
cd ${WORK_ROOT}
git clone https://gitlab.com/lely_industries/lely-core.git
cd ${LELY_SRC}

# 4. 生成 configure 和 Makefile.in
autoreconf -i

# 5. 创建独立构建目录
mkdir -p ${BUILD_DIR}
cd ${BUILD_DIR}

# 6. 配置交叉编译
../configure \
--host=<TARGET_TRIPLET> \
--prefix=${INSTALL_PREFIX} \
--disable-python \
--disable-cython

# 7. 编译
make -j"$(nproc)"

# 8. 安装到 stage
make install DESTDIR="${PWD}/stage"

如果 <TARGET_TRIPLET> 是 ARM64 Linux 工具链,可替换为类似:

1
aarch64-poky-linux

但本文不写入真实 SDK 路径和真实部署目录。

常见误解

误解 1:autoreconf -i 是交叉编译

不是。autoreconf -i 只是生成构建系统。它不会调用目标交叉编译器,也不会产生 ARM ELF。

真正和目标平台有关的是:

1
2
../configure --host=<TARGET_TRIPLET>
make

误解 2:configure.ac 会直接被 make 使用

不会。make 读的是 Makefile

关系是:

1
configure.ac → configure → Makefile → make

误解 3:Makefile.amMakefile 是一回事

不是。

1
2
3
Makefile.am   人写的 Automake 输入
Makefile.in autoreconf/automake 生成的模板
Makefile configure 生成的最终构建文件

误解 4:执行一次 autoreconf -i 后永远不用再执行

也不是。修改 configure.acMakefile.am 后,应该重新执行。更换交叉编译目标时,通常只需要重新建构建目录并执行 ../configure --host=...,不一定要重新 autoreconf -i

误解 5:看到 installing 就是安装到了系统

不是。autoreconf -i 输出里的 installing './config.sub' 这类信息,是把构建辅助脚本复制到源码树,不是把 Lely 安装到系统。

真正安装 Lely 产物的是:

1
make install DESTDIR="${PWD}/stage"

排查时优先看这些文件

当 Lely 构建失败时,不同阶段看不同文件。

阶段 关注文件 常见问题
autoreconf -i configure.acMakefile.amm4/*.m4 宏缺失、Autotools 版本不足、Libtool 文件缺失
../configure config.log 编译器找不到、头文件检测失败、库检测失败
make 编译日志、对应 Makefile C/C++ 编译错误、链接错误、目标库缺失
make install stage/ 目录 安装路径不符合预期、.pc 文件缺失、工具未安装
运行目标程序 filelddLD_LIBRARY_PATH 架构错误、动态库找不到、运行环境缺失

建议检查顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 检查 configure 是否生成
ls -l <LELY_SRC>/configure

# 检查 Makefile.in 是否生成
find <LELY_SRC> -name Makefile.in | head

# 检查 configure 结果
less <BUILD_DIR>/config.log

# 检查最终 Makefile
ls -l <BUILD_DIR>/Makefile

# 检查目标程序架构
file <BUILD_DIR>/stage/<INSTALL_PREFIX>/bin/coctl

最小心智模型

如果只记一张图,记这张:

1
2
3
4
5
6
7
8
9
flowchart TD
A[人维护<br/>configure.ac + Makefile.am] --> B[autoreconf -i]
B --> C[生成<br/>configure + Makefile.in]
C --> D[../configure --host=目标平台]
D --> E[生成<br/>Makefile + config.h]
E --> F[make]
F --> G[生成目标平台产物]
G --> H[make install DESTDIR=stage]
H --> I[打包部署]

对应到 Lely:

1
2
3
4
5
6
7
8
9
Lely 源码目录
↓ autoreconf -i
得到 configure
↓ ../configure --host=<TARGET_TRIPLET>
得到交叉编译用 Makefile
↓ make
得到 Lely 库和 coctl
↓ make install DESTDIR=stage
得到可打包部署目录

参考来源