工程记录

用 Brewfile 固化云端 Mac 的 Homebrew 工具链

用 Brewfile 固化云端 Mac 的 Homebrew 工具链

一台云端 Mac 连续承担构建任务几周后,最常见的环境问题并不是工具没有安装,而是工具在不同时间由不同人安装:某个任务依赖 jq,另一个脚本默认存在 swiftlint,临时排障又留下数个公式。新机器接手时,只复制仓库无法复原这层系统依赖。Brewfile 适合解决“需要哪些工具”,但它不是精确版本锁文件。可靠做法是把声明、版本证据和清理流程分开管理。

先盘点机器,不要直接导出全部内容

先确认 Homebrew 路径和当前架构,避免脚本继续使用另一套历史路径。

command -v brew
brew --prefix
uname -m
brew doctor

随后分别查看顶层公式、全部版本和后台服务:

brew leaves
brew list --formula --versions
brew services list

brew leaves 比完整列表更适合作为声明起点,因为它主要显示主动安装的顶层工具。完整版本列表则应作为构建证据归档,而不是原样塞进 Brewfile。

不要在仍有构建任务运行时执行升级或清理。先停止任务分发,再确认没有编译器、数据库或辅助服务被进程占用。

如果机器已经使用较久,可以先导出候选文件,再逐行审查:

mkdir -p ci/homebrew
brew bundle dump --force --file=ci/homebrew/Brewfile.candidate
git diff -- ci/homebrew/Brewfile.candidate

候选文件可能包含个人工具、临时调试软件或与项目无关的图形应用。只有构建和排障确实需要的项目才应进入正式清单。

把 Brewfile 当作需求声明

一份面向 iOS 持续构建的基础清单可以保持很短:

brew "git"
brew "jq"
brew "swiftlint"
brew "xcbeautify"

将文件提交为 ci/homebrew/Brewfile,安装时显式指定路径:

brew bundle check --file=ci/homebrew/Brewfile
brew bundle install --file=ci/homebrew/Brewfile --no-upgrade

check 适合放在任务入口。它只验证声明是否满足,不应在每个构建任务中自动执行全量升级。install --no-upgrade 可补齐缺失工具,同时降低一次普通构建意外改动既有环境的概率。

按职责拆分清单

若同一台 VMKeep 云端 Mac 需要承担多类工作,建议拆成基础清单和项目清单。例如基础层只放 Git、JSON 处理和日志工具,项目层再放代码规范或发布辅助工具。安装顺序固定为基础层在前、项目层在后。

不要用一个持续膨胀的全局 Brewfile 覆盖所有仓库。它会让任何一次清理都难以判断影响范围,也会让新项目误以为历史工具属于自己的必要依赖。

单独记录版本与运行上下文

Brewfile 通常不会把普通公式锁到精确版本。仅仅看到两台机器拥有相同清单,不能证明它们会产生相同结果。每次环境变更后,应保存实际版本快照:

{
  date -u
  sw_vers
  xcodebuild -version
  brew --version
  brew list --formula --versions
} > ci/homebrew/toolchain.snapshot.txt

版本快照可以作为流水线产物,也可以在经过审查的环境升级后提交到运维记录。发生差异时,先比较 Xcode、macOS、Homebrew 和直接依赖版本,再检查项目锁文件,不要一开始就删除全部缓存。

明确版本边界

brew pin 只会阻止当前机器上的本地升级,不能保证另一台新机器仍能取得同一历史版本。因此它可以作为短期保护,但不能替代固定机器镜像、项目专用版本管理器或经过校验的二进制制品。

对于直接影响产物的工具,应让脚本在启动时检查可接受的版本范围,并在不满足时明确失败。对于只负责美化日志的工具,则可允许更宽的范围,避免把非关键差异升级成构建中断。

用预览和空闲窗口安全清理

正式清理前先查看 Brewfile 之外的项目:

brew bundle cleanup --file=ci/homebrew/Brewfile

这一步只用于审查输出。确认列表中的公式没有被其他项目、LaunchAgent 或后台服务依赖后,才执行:

brew bundle cleanup --file=ci/homebrew/Brewfile --force
brew autoremove
brew cleanup

共享机器尤其要谨慎:一个公式不在当前仓库的 Brewfile 中,不代表它没有被另一个任务使用。更稳妥的边界是一类长期工作负载对应一台独享物理节点,或者至少使用独立执行账户、独立工作目录和各自的清单。

清理后重新运行 brew bundle check,再执行一条最小验收链:打印工具版本、解析工程配置、完成一次不上传产物的构建。只有三步都通过,环境变更才算结束。

把环境变更变成可审查事件

稳定流程不应允许构建脚本随手执行 brew install。新增工具时,提交应同时包含 Brewfile 变更、用途说明、版本快照和回滚方式。升级则安排在独立窗口中完成,先验证代表性工程,再恢复任务分发。

建议把日常检查收敛成四项:

  1. brew bundle check 是否通过;
  2. Xcode 与关键公式版本是否符合预期;
  3. Brewfile 是否存在未审查的改动;
  4. 是否有未声明的后台服务持续运行。

Brewfile 的价值不在于把所有工具自动装满,而在于让系统依赖从“机器上的既成事实”变成仓库中可讨论、可比较、可回滚的声明。配合版本快照和谨慎清理,云端 Mac 的环境问题会从临时猜测转化为有证据的配置差异。

常见问题

Brewfile 能锁定每个 Homebrew 软件包的精确版本吗?

不能。Brewfile 主要声明需要哪些工具,默认公式仍随仓库更新。精确版本应通过版本快照、固定构建镜像或项目自己的版本管理器另行约束。

可以直接在共享云端 Mac 上执行 brew bundle cleanup --force 吗?

不建议。应先运行不带 --force 的预览,确认待删除工具不被其他任务使用,再在空闲时段清理;共享节点还应为不同项目使用独立执行账户或专用机器。

独享物理节点

在云端 Mac 上继续验证这套工作流

从三档 Apple Silicon 配置中选择合适的内存、存储、节点与租用周期。

选择配置并下单