返回 2026-04-14
🛠 工具 / 开源

站在Homebrew的肩膀上重写简单部分Standing on the shoulders of Homebrew

nesbitt.io·2026-04-14

作者着手重写Homebrew的部分基础组件,目标是简化其内部实现,使其更易维护和扩展。此举并非推翻原有架构,而是聚焦于那些‘容易出错却常被忽略’的底层细节,以提升包管理工具的稳定性与开发者体验。

Andrew Nesbitt

zerobrew 和 nanobrew 作为 Homebrew 的快速替代品迅速流行起来,前者用 Rust 编写,宣传语为“Homebrew 包的 uv 风格架构”,后者则用 Zig 实现,提供 1.2MB 的静态二进制文件,并通过基准测试表格显示其性能优于前者。一旦你略过速度提升的数据,它们都会坦诚地说明:它们依赖 homebrew-core 解析依赖关系,下载 Homebrew CI 构建好的 bottles(预编译包),并使用 Homebrew 的带宽资源,同时解析由 Homebrew 贡献者维护的 cask 定义。

它们是别人注册表(registry)的替代客户端,这本身是完全合理的产品方向,但将其包装成“替代品”的说法,掩盖了运行系统包管理器实际上涉及的内容。

nanobrew 的 README 中有一个“尚未支持的功能”章节,列出了 Ruby post_install 钩子、自定义选项下从源码构建、Brewfile 中的条件块以及任何复杂的 Ruby DSL,而 zerobrew 通过回退到“Homebrew 的 Ruby DSL”来处理源码构建,我理解为它实际上是在调用它本应取代的那个工具。

它们跳过的部分正是那些之所以慢是有原因的环节:执行任意 Ruby 代码以确定软件包的需求、运行会按特定方式操作文件系统的 post-install 钩子,以及处理无法简化为“下载此 tarball 并链接到前缀目录”的大量公式(formulae)。仅实现 bottle 路径并将其余部分划为“不在范围内”,恰好覆盖了大多数容易处理的 80% 软件包,也解释了它们在基准测试中领先的原因。

zerobrew 的测试数据显示,在缓存已预热的情况下安装 ffmpeg 可提速 4.4 倍,nanobrew 则将同一操作压缩至 287 毫秒。我反复想象着那个频繁在同一台机器上安装又卸载 ffmpeg,以至于“缓存已预热时的重新安装速度”成为他们关注指标的开发者形象。

所谓“预热安装”,其实是在测量如何快速从一个内容寻址存储中克隆目录,这固然值得优化,但对设置新笔记本或添加昨天还不存在的工具的用户体验几乎毫无影响。冷缓存(cold-cache)情况下的数据则更接近,有时甚至比 Homebrew 更慢——尤其是当 bottle 很大时——因为这时大家都在等待同一个 CDN,没有任何巧妙的数据结构能让字节更快到达。

几个月前我曾写过关于 uv 为何如此之快的原因。语言重写并不是故事中最有趣的部分。uv 之所以快,是因为 PEP 658 终于让 Python 解析器能在不执行 setup.py 的情况下获取包元数据,还因为它舍弃了 eggs、pip.conf 以及其他十几种 pip 仍保留的遗留路径。Homebrew 早已在其 formula.json API 中实现了 PEP 658 的等效功能,而这正是 zerobrew 和 nanobrew 得以实现的基础——它们并未解决“无需 Ruby 求值即可获取元数据”的问题,因为 Homebrew 早已为它们解决了这个问题。

zerobrew 的内容寻址存储与 APFS clonefile 技巧完全可以用 Ruby 实现,nanobrew 的并行下载功能自去年 11 月 Homebrew 4.7.0 起就已默认开启。这些架构选择确实是实实在在的改进,但它们不是“我们用 Zig 重写了整个项目”那种意义上的改进;而且,无论哪种方式都要下载 40MB 的文件,一个零启动时间的二进制文件在此场景下意义就小多了。

包管理器的大部分工作都集中在长尾问题上:需要在旧版 macOS 上安装特定版本的 libiconv,处理需要公证的 cask,以及那些在预知范围之外修改配置文件的 post-install 脚本。这些都不涉及基准测试。一年后,当这些问题在追踪器中堆积如山时,这两个项目是否仍有维护者关注仍是一个未知数。此外,两者都选择了 Apache-2.0 许可证而非继承 Homebrew 的 BSD-2-Clause,这在法律上是可行的,也表明作者将自己定位为构建独立项目,而非为依赖的生态系统做出贡献。

公式格式是图灵完备的 Ruby,这意味着包定义和解释它的客户端实际上是同一个产物,任何向声明式包数据迈进的尝试,要么破坏现有公式,要么永远在每个客户端中附带一个 Ruby 解释器。

当前公式 API 列出了 homebrew-core 中的 8,308 个公式,cask API 另有 7,617 个 cask,再加上 GitHub 上约 34,000 个看起来像第三方 tap 的 homebrew-* Ruby 仓库,它们都是基于一个从未打算作为稳定交换格式的内部 DSL 编写的。快速客户端通过声明该问题不在其作用范围内来规避这个问题,这是它们所依赖的项目所不具备的自由。

瓶颈不在于 Rust 或 Ruby,而在于缺乏一个稳定的声明式包模式。在这一点实现之前,每个快速客户端之所以快,是因为 Homebrew 已经完成了缓慢的工作。

需要完整排版与评论请前往来源站点阅读。