成熟 Autotools 项目的工作远超过把 C 或 C++ 变成二进制文件。它的 configure.ac、Makefile.am 文件与 Libtool 规则共同规定功能选择:哪些依赖属于可选项,config.h 中会出现哪些预处理器符号,哪些测试允许跳过。它们还决定哪些头文件与库进入暂存根目录,以及下游 pkg-config 用户会看到什么;共享库名称与发布 tarball 的内容也由这些文件和规则确定。
Meson 可以用少得多的繁复配置描述同一产品,迁移工作的重点仍然超出语法转换。一份新的 meson.build 能够运行到 meson compile -C build,只证明某一台机器可以生成若干目标。迁移完成的标准是:相同的功能选择对应相同的公共制品、测试、安装布局、发布归档与交叉构建行为,下游打包维护者也能够解释每一处有意保留的差异。
因此,速度适合放在迁移论证接近末尾的位置。早期有人在 Nexus 4 上试验 Meson,对 GLib 做了部分移植:配置耗时约 20 秒,Autotools 约为 220 秒;空操作检查约为 0.25 秒,Autotools 则为 14 秒。Meson 自己的报告明确说明,两套构建尚未完全等价。[12] 这些数字显出了机会,等价性仍需由后续工作建立。
配图说明:封面画面记录了 Meson 创建者 Jussi Pakkanen 在 2017 年 All Systems Go! 大会演讲后回答提问;那场演讲讨论了项目逐步离开旧式构建系统的趋势。构建系统迁移归根结底要处理维护者与下游之间的协同,速度更快的本地命令只覆盖其中一小部分,因此这张照片与本文主题相合。[13]
从产品着手,再看构建文件
添加 meson.build 之前,先从一次干净的 Autotools 发布构建中整理迁移台账。保存 ./configure --help、每个受支持软件包所用的准确 configure 命令、生成的 config.h、测试清单与结果,以及详细的编译器和链接器命令。把安装暂存到一个空目录,并记录路径、文件模式、符号链接、公共头文件、手册页、本地化数据、插件、静态归档、共享库名称和每个 .pc 文件。
开发者默认配置之外也要留下同样记录。至少应覆盖一个关闭可选集成的 minimal 构建,一个把所有承诺依赖设为必需项的 full 构建,以及每种受支持的共享库/静态库组合。可移植库还应保留一个已知可用的交叉构建。假如 ./configure 接受 24 个开关,CI 只检验其中 3 个,其余 21 个不会自动成为需求,它们代表尚未厘清的承诺。进入转换工作前,逐项归类为保留、改名、弃用或移除。
暂存安装是最有用的基线,因为它描述用户实际收到的产品,而没有停留在维护者的生成过程上。它还会显出一些不显眼的契约,例如兼容性头文件、带版本号的符号链接、位于 libdir 的插件,或单元测试从未提及的 Requires.private 行。正因如此,Meson 自己的移植指南同时列出配置头文件、依赖发现、生成源码、库、已安装头文件、测试、内省数据、设置 schema 与翻译文件;构建图覆盖的范围远超编译。[1]
回滚也要落到可复现的操作上。在 Meson 软件包通过下游测试之前,保持最后一版 Autotools 发布配方、由它生成的 tarball 以及对应打包补丁都能重现。“旧文件还在 Git 里”本身无法构成回滚;一旦已经没有人能重建当初让这些文件生效的发布环境,回滚条件也就消失了。
把决策译入 Meson,略去 M4 外壳
顶层 Meson 文件首先声明项目,以及维护者实际测试的最低 Meson 版本:
project(
'libsample', 'c',
version: '2.4.0',
meson_version: '>=1.4.0',
default_options: ['warning_level=2'],
)
从这里开始,要把每项 Autotools 决策对应到 Meson 概念,舍弃 shell 层面的机械做法。AC_CONFIG_HEADERS 对应一个 configuration_data() 对象和 configure_file();PKG_CHECK_MODULES 的结果对应一个通过 dependencies: 传入的 dependency() 对象;生成文件成为 custom_target() 的输出,再由目标明确消费;安装头文件使用 install_headers()。库目标应有意设定 version 与 soversion,避免无意间沿用 Libtool 的版本算术。[1]
可选依赖需要格外仔细。Meson 的 feature 选项有三种状态:enabled、disabled 和 auto。把它传给 dependency(..., required: get_option('feature_name')) 后,三种状态分别表示“缺少时失败”“跳过探测”和“找到就使用”。打包维护者还可以通过 auto_features 强制开启或关闭其余所有自动功能。[2]
# meson.options
option('tls', type: 'feature', value: 'auto', description: 'TLS support')
# meson.build
tls_dep = dependency('openssl', required: get_option('tls'))
conf.set10('HAVE_TLS', tls_dep.found())
这段配置的意义超过语法便利。它能防止一种常见的打包故障:某个构建环境恰好装着一项可选库,产品内容便在没有提示的情况下扩大。CI 应运行 -Dauto_features=enabled,验证完整依赖契约;同时运行 -Dauto_features=disabled,验证最小契约。凡是已经承诺的功能,它的“enabled”通道在依赖缺失时必须明确失败。
手写编译器探测代码历史悠久,并不足以构成保留理由。确实需要检测某项能力时,使用 cc.has_header()、cc.has_function() 和 cc.links() 之类的编译器对象;成熟的平台契约则交给 Meson 的依赖对象。项目 FAQ 特别提醒开发者避开手工查找 libpthread,改用 dependency('threads'),因为线程标志在跨平台环境中有许多边缘情形。[1]
两套流程并行验证,并为重叠期设定截止日
Meson 尚未完备时,继续以 Autotools 为权威;随后让两套系统针对同一 commit 与同一功能矩阵运行。典型的 Meson 通道有意保持朴素:
meson setup build-meson \
--prefix=/usr \
--buildtype=debugoptimized \
-Dauto_features=enabled
meson compile -C build-meson
meson test -C build-meson --print-errorlogs
meson install -C build-meson --destdir "$PWD/stage-meson"
比较时间或输出之前,先对齐编译器、优化设置、调试信息、依赖版本与生成输入。缺失功能、不同编译器标志或较少的测试也会制造出“Meson 改进”的表象。
双轨维护只用于验证,必须有终点。GLib 在 2018 年公布了一段有期限的安排:先发布一个默认采用 Meson、同时保留 Autotools 的版本,请发行版和较少见工具链的用户参与测试,随后在下一个开发周期移除 Autotools。[9] GStreamer 更早的实验性推广同样以 Autotools 为主,同时逐步补齐 Linux、Windows、GCC、Clang 与 MSVC 覆盖。其维护者报告,在那位开发者的机器上,一个小型示例用 Meson 完成配置、构建和安装不到 4 秒;Autotools 的配置约需 17 秒,构建还需 10 秒。[11]
这些带有年代背景的计时数据服务于同一做法:明确重叠期需要发现什么,并约定项目何时切换或放弃尝试。LWN 对 Mesa 讨论过程的记录,从另一侧呈现了同样的压力。开发者希望构建更快、更简单,发行版与非 Linux 流程也属于决策的一部分;当时提出的方案是让一个发布版本同时携带两套系统,随后作出明确选择,避免无限期重复维护。[10]
比较暂存安装树,绿色编译日志还不够
把两套构建分别安装到空根目录:
make DESTDIR="$PWD/stage-auto" install
meson install -C build-meson --destdir "$PWD/stage-meson"
diff -ruN stage-auto stage-meson
对比重点应放在产品层;嵌入的构建路径、时间戳或调试段会让逐字节一致失去意义。检查每个文件的路径和模式、库符号链接与 SONAME、导出符号、公共头文件内容、插件发现、本地化文件位置、手册页,以及下游的编译和链接行为。Meson 只有在目标或文件被标记为安装项时才会安装它,而 DESTDIR 支持本来就是为软件包暂存设计的。[3]
使用 meson introspect build-meson --install-plan 检查计划中的安装内容,再用 --buildoptions、--dependencies、--targets 和 --tests 让机器与人都能审查新的构建图。[4] 对于库,应先生成并检查 .pc 文件,不能直接假定 pkg.generate() 已推导出预期的公共/私有划分。requires、requires_private、libraries 与 libraries_private 字段对动态链接和静态链接使用者的影响各不相同。[8]
共享库需要 ABI 门禁,单看文件名覆盖不了这项检查。对比 SONAME 与导出符号集合,再使用暂存头文件和库编译一个很小的下游使用程序。如果项目使用符号版本脚本、Darwin 兼容版本或 Windows 导出定义,就在各自原生平台上检验。Meson 构建即使生成了 libsample.so,一旦同时改变 libsample.so.2、丢掉某个导出符号,或遗漏静态链接所需的私有依赖,就没有达到软件包对等。
保留测试套件原有的含义
Meson 默认并发运行测试。旧测试套件若共享固定 socket、数据库、临时文件名或 D-Bus 名称,即使每项测试都曾在串行 Automake harness 下正常工作,也会因此变得不稳定。用 is_parallel: false 标记确实需要独占资源的测试;等到各自状态已经隔离,再移除这项限制。[5]
还要比较各类结果,最终退出状态只覆盖一部分信息。Meson 把退出码 77 识别为跳过,把 99 识别为严重的设置错误;超时、预期失败、测试 wrapper、环境变量与 suite 归属仍需明确映射。[5] 针对每个功能通道,记录测试总数、通过数、跳过数与预期失败数。某个交叉构建若因所有需要运行程序的测试都被跳过而显示“通过”,它提供的证据弱于只出现一次失败的原生构建。
测试应使用 Meson 当次构建的产物,避免误取系统已经安装的副本。找出误报成功的移植,最快的办法是把测试放进干净的容器或 chroot,其中唯一的项目文件来自当前构建与暂存根目录。
交叉构建会暴露用错机器的错误
Autotools 项目经常把多年积累的 build/host 知识藏在三元组(triplet)、环境变量和自定义宏中。Meson 把这些知识移入交叉编译文件(cross file):[binaries] 下列出编译器与 binutils,另有 sysroot、目标平台的 pkg-config 搜索路径、可选的 exe_wrapper,以及 [host_machine] 下的系统、CPU family 和字节序。设置时通过 meson setup --cross-file ... 选择这个文件。[7]
风险最高之处在生成代码。编译后要在构建期间运行的程序需要设为 native: true;最终交付的库或程序使用 host 工具链。混淆这两类角色后,原生构建依旧能通过,首次 ARM、Windows 或嵌入式构建却会尝试在构建机上运行目标平台二进制文件。cross file 中缺少 exe_wrapper 时,Meson 会跳过那些需要运行 host 二进制文件的测试,因此验收记录必须包含跳过数。[7]
对于由两个人维护、只面向 Linux 的命令行工具,一个原生 GCC 通道和一个 Clang 通道可以构成与风险相称的门槛。发行版使用的可移植库需要覆盖更多内容:至少一个真实 cross file、条件允许时的原生或模拟执行、下游打包,以及针对构建期生成程序的明确测试。验收矩阵应由实际运维成熟度决定,流行度不能替代证据。
发布归档通过验收,切换才有依据
meson dist -C build-meson 会打包版本控制中的最新 commit,移除仓库元数据,再用生成的归档执行一次编译、测试和安装周期,最后创建 SHA-256 文件。这与 Autotools 面向源码树的 dist 不同:尚未提交的生成文件不会悄然进入 Meson 归档。[6]
在离线的干净环境中构建 tarball。检查生成手册、语言绑定、翻译产物与版本文件,确认其中哪些过去由 Autotools 随包提供,让终端用户可以免装维护工具。若发布包必须包含 subprojects,应明确 --include-subprojects 是否属于契约。[6] 从 Git checkout 构建与从已发布 tarball 构建,是两项独立的验收测试。
工具链最低要求也会在这里落实。Meson 需要 Python,常用的 Ninja backend 又增加了一项 bootstrap 依赖。项目若必须在旧式 Unix 系统上构建,而这些系统没有可支持的 Python/Meson 方案,继续采用 Autotools 是合理选择。稳定库若面对的下游打包维护者尚且无法复现它的平台探测或 ABI,也可以作出同样决定。迁移属于运维决策,语法是否清爽不能代替这项权衡。
删除前的六道门槛
以下六项全部成立后,才能移除 Autotools:
- 每个保留的 configure 功能都有明确的 Meson 选项,minimal 与 full 矩阵的行为均与文档一致。
- 原生构建编译相同目标,编译器与链接器差异均已审查。
- 测试清单、通过数、跳过数与预期失败数一致,或每一项差异都有各方接受的理由。
- 暂存安装在路径、模式、符号链接、元数据、ABI 与下游使用行为上保持一致。
- 受支持的交叉构建把 build-machine 工具与 host 制品分开,意外跳过均已排除。
- 发布归档能够在干净的离线环境中完成构建、测试与安装,并且至少有一位下游打包维护者成功复现。
反证条件也很清楚:若 Meson 流程反复需要项目专用衔接代码,而且它比被替换的 Autotools 逻辑更难解释,或者受支持的下游环境无法复现同一产品,维护成本降低这一承诺就尚未兑现。可以保留实验、补齐缺口,也可以中止迁移。迁移决定不能靠删除已知可行的流程来迫使各方接受。
六道门槛通过后,应及时删除 Autotools。两份构建描述会让新增源文件、选项、测试或安装制品多出一倍的漂移位置。第一次亚秒级空操作构建只是早期信号;真正值得庆祝的时刻,要等到首个发布版本让维护者、CI 与打包维护者都使用同一张清晰可读的构建图,并由安装树交付他们早已了解且信任的同一产品。
来源
- Meson 文档,“Porting from Autotools”——涵盖配置头文件、依赖、生成源码、库、已安装数据、测试和 GNOME 专用构建产物之间的对应关系。
- Meson 文档,“Build options”——介绍
meson.options、三态 feature 选项,以及面向打包维护者的auto_features覆盖设置。 - Meson 文档,“Installing”——介绍安装声明、标准安装目录、自定义脚本,以及通过
DESTDIR暂存软件包的行为。 - Meson 文档,“Command-line commands”——介绍 setup、configure、compile、install,以及针对选项、依赖、目标、测试和安装计划的 JSON 内省。
- Meson 文档,“Unit tests”——介绍并行执行、测试环境、日志、跳过与严重错误退出码、suites 和 wrappers。
- Meson 文档,“Creating releases”——介绍基于 VCS 的
meson dist、归档的编译/测试/安装验证、校验和,以及捆绑 subproject 的行为。 - Meson 文档,“Cross compilation”——介绍 cross files、build/host/target 术语、程序 wrapper、sysroot、目标平台
pkg-config路径与原生构建期生成程序。 - Meson 文档,“Pkgconfig module”——介绍生成的
.pc文件、公共/私有 requirements 与 libraries、安装位置和未安装元数据。 - Emmanuele Bassi,“News from GLib 2.58”,GTK Development Blog,2018 年 7 月 11 日——介绍项目以一个发布版本为期、默认采用 Meson 的重叠安排,面向发行版的测试请求,以及移除 Autotools 的计划。
- Jonathan Corbet,“Moving Mesa to Meson”,LWN.net,2017 年 3 月 29 日——独立报道迁移收益、发行版顾虑,以及为双构建发布设定明确期限的理由。
- Tim Müller,“Experimental build using the Meson build system”,GStreamer-devel,2016 年 9 月 2 日——对分阶段推广、平台与工具链目标、已知缺口和本地计时比较的第一手记录。
- Meson 项目,“Arm performance test”——记录 Nexus 4 上 GLib 实验的年代、配置与空操作测量结果,以及两套构建尚不等价的明确说明。
- media.ccc.de,“Meson and the changing Linux build landscape”,All Systems Go! 2017——本文封面截图所取自的官方存档录像及活动出处。