如何让 Firefox 构建速度提升 17%?
2026 年 4 月 10 日,在之前的文章中提到,与 ccache 和 sccache 相比,buildcache 有独特特性,其 Lua 插件系统可编写自定义包装器。随着 Bug 2027655 合并,可利用此特性缓存 Firefox 的 WebIDL 绑定代码生成过程。
WebIDL 步骤是什么?
构建 Firefox 时,早期步骤之一是运行 `python3 -m mozbuild.action.webidl`,从 `.webidl` 文件生成 C++ 绑定代码,会生成数千个输出文件。此步骤本身不慢,但每次完全重建都会运行,且输入相同输出确定,是缓存的理想选择。不过,编译器缓存从未应用到这一步骤,Buildcache 之前仅包装实际编译器调用,不涉及 Python 代码生成过程。
改进之处
Bug 2027655 中的修复很简单。在 `dom/bindings/Makefile.in` 中,有条件地将 `$(CCACHE)` 作为命令包装器传递给 `py_action` 调用。`config/makefiles/functions.mk` 中的 `py_action` 宏用于运行 Python 构建操作,此 Bug 还引入将命令包装器作为第四个参数传递的功能。当 buildcache 被配置为编译器缓存时,webidl 操作将以 `buildcache python3 -m mozbuild.action.webidl ...` 形式调用,这样 buildcache 就能拦截该操作。注意 `ifdef MOZ_USING_BUILDCACHE` 这个条件判断,这是 buildcache 特有的,因为 ccache 和 sccache 没有缓存任意命令的机制,而 buildcache 可以通过其 Lua 包装器实现。
Lua 包装器
Buildcache 的 Lua 插件系统允许编写脚本处理本身不理解的程序。WebIDL 代码生成的包装器 `webidl.lua` 需要为 buildcache 解答几个问题:能否处理这个命令,通过匹配参数列表中的 `mozbuild.action.webidl` 判断;输入有哪些,包括所有 `.webidl` 源文件以及 Python 代码生成脚本,信息来自 `file-lists.json` 和 `codegen.json`;输出有哪些,包括所有生成的绑定头文件、cpp 文件、事件文件以及代码生成状态文件,同样从 `file-lists.json` 中获取。有了这些信息,buildcache 可以对输入进行哈希处理,检查缓存,然后要么重放缓存的输出,要么运行实际命令并存储结果。该包装器使用了 buildcache 的 `direct_mode` 功能,直接对输入文件进行哈希处理,不依赖预处理输出,这在这里是合适的,因为处理的是读取 `.webidl` 文件的 Python 脚本。
数据对比
以下是在 Linux 上使用 `./mach build` 进行构建的时间,对比了不同的编译器缓存工具。每行显示了一次清空缓存的完全重建(冷启动),以及一次缓存已填充的完全重建(热启动):
| 工具 | 冷启动 | 热启动 | 使用插件 |
| --- | --- | --- | --- |
| 无 | 5 分 35 秒 | 无 | 无 |
| ccache | 5 分 42 秒 | 3 分 21 秒 | 无 |
| sccache | 9 分 38 秒 | 2 分 49 秒 | 无 |
| buildcache | 5 分 43 秒 | 1 分 27 秒 | 1 分 12 秒 |
“使用插件” 列是启用了 `webidl.lua` 包装器的 buildcache,它又节省了 15 秒,使总时间降至 1 分 12 秒。这展示了这种机制的有效性,WebIDL 步骤只是第一个采用这种处理方式的 Python 操作,构建过程中还有其他代码生成步骤也能从相同的方法中受益。更广泛地说,这些数据表明 buildcache 在热启动构建方面表现出色,从 5 分 35 秒的全新构建到 1 分 12 秒的缓存重建,大大改善了编辑 - 编译 - 测试的循环过程。这些只是在一台机器上的单次运行结果,并非严格的基准测试,但趋势已经很明显。
设置方法
如果已经在 `mach` 中使用 buildcache,更新到最新的中央版本后,Makefile 的更改就会生效。要启用 Lua 包装器,克隆 `buildcache-wrappers` 仓库,并通过 `~/.buildcache/config.json` 中的 `lua_paths` 指向它:
```json
{
"lua_paths": ["/path/to/buildcache-wrappers/mozilla"],
"max_cache_size": 10737418240,
"max_local_entry_size": 2684354560
}
```
或者,也可以设置 `BUILDCACHE_LUA_PATH` 环境变量,一个方便的设置位置是在 mozconfig 中:
```makefile
mk_add_options "export BUILDCACHE_LUA_PATH=/path/to/buildcache-wrappers/mozilla/"
```
较大的 `max_local_entry_size`(2.5 GB)是必要的,因为一些 Rust 包会生成非常大的缓存条目。
下一步计划
这里有趣的部分是 Lua 插件系统。WebIDL 包装器只是一个概念验证,但同样的技术适用于任何具有已知输入和输出的确定性构建步骤。Firefox 构建中还有其他代码生成操作也可以采用相同的处理方式,接下来打算探索这些操作。