news 2026/9/8 20:40:34

Homebrew 发布流程全解:brew release 命令、release.yml 工作流与主次版本发布规范

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Homebrew 发布流程全解:brew release 命令、release.yml 工作流与主次版本发布规范

Homebrew 发布流程全解:brew release 命令、release.yml 工作流与主次版本发布规范

【免费下载链接】brew🍺 The Package Manager for Everywhere项目地址: https://gitcode.com/GitHub_Trending/br/brew

Homebrew 用户通过 GitHub release tag 获取新版 Homebrew/brew,而发布本身由拥有写权限的维护者通过brew release命令与 GitHub Actions 工作流共同完成。本文基于仓库中的 发布流程文档 展开,结合 release 命令实现、release 工作流 与 release 命令测试,完整讲解发布前检查、版本预览与草稿创建、主次版本的强制操作清单,以及发布背后的实现细节,帮助维护者或深度使用者理解并复现一次规范的 Homebrew 发布。

发布模型:谁可以发、用户如何拿到新版

  • 用户从 GitHub release tag 接收 Homebrew/brew 的新版本。
  • 只有对 Homebrew/brew 有写权限的维护者才能创建 release。
  • 常规更新使用 patch 版本(x.y.z递增 z),重大变更走 major/minor 版本,且受“一个月冷却期”限制。

从源码结构看,整个发布链路由三个环节构成:brew release命令做本地检查与预览 → 通过workflow_dispatch触发.github/workflows/release.yml→ 工作流构建、测试并上传 macOS 安装包,最后用gh release create --draft生成草稿 release,由维护者在网页端确认后正式发布。

发布前准备(Prepare the release)

文档列出了五步检查清单,全部通过后才能进入创建阶段:

  1. 检查是否有必须在发布前解决的紧急工作
    • Homebrew/brew 的 pull requests 与 issues;
    • Homebrew/homebrew-core 的 issues;
    • Homebrew 官方 Discussions。
  2. 给阻塞性 issue/PR 打标签
    • 必须在任何 release 前解决的,标记release blocker
    • 必须在下一个 major 或 minor release 前解决的,标记major/minor release blocker
    • 只要有处于 open 状态的 issue 或 PR 带有相应标签,brew release命令和 release 工作流都会拒绝创建发布。
  3. 确认 CI 通过:Homebrew/brew 的main分支 workflows 全部通过,且至少有一个近期 Homebrew/homebrew-core 的 PR 成功跑完 CI。
  4. 留出回归检测时间:最后一次代码改动之后,要留足时间以便发现回归问题。
  5. 确认main分支当前状态适合发布

文档特别强调两条硬性约束:

  • 不要从main的较旧 commit 上创建 release。
  • 如果紧急 patch release 必须排除某些未发布变更,正确做法是:先 revert 这些变更 → 走完整发布流程 → 发布后再把变更应用回来,而不是从旧 commit 打 tag。

源码印证:blocker 标签检查如何落地

在 Library/Homebrew/dev-cmd/release.rb#L47-L59 中,命令默认检查release blocker标签;当传入--major--minor时,额外加入major/minor release blocker标签,通过 GitHub API 查询 open 状态的 issues/PRs,一旦发现 blocker 即打印其 URL 列表并odie退出:

blocking_labels = ["release blocker"] blocking_labels << "major/minor release blocker" if args.major? || args.minor? release_blockers = blocking_labels.flat_map do |label| GitHub.issues(repo: "Homebrew/brew", state: "open", labels: label) end

这段逻辑被注释要求与工作流中的 "Check for release blockers" 步骤保持同步,对应 .github/workflows/release.yml#L47-L73:工作流侧用gh api做同样的标签检查,并对 tag 形如^[0-9]+\.[0-9]+\.0$(即 major/minor 版本号)的情况追加检查major/minor release blocker。因此无论本地命令还是 CI 任一侧,只要存在未解决的 blocker,发布都会被拒绝。

测试文件 Library/Homebrew/test/dev-cmd/release_spec.rb 用 RSpec 固定了这些行为:存在 open 的release blocker时命令以SystemExit退出并在 stderr 输出 blocker URL;major release 时还会单独校验major/minor release blocker

创建发布:brew release 命令

第一步:预览版本与 release notes

brew release

默认预览 patch 版本:读取最近一次 release 的 tag,把 patch 位加一,生成候选版本号,并调用 GitHub 的generate-release-notes能力生成 notes 后打印到终端。此时不会创建 release,也不会触发工作流。

需要 major 或 minor 预览时,加上对应参数:

brew release --major brew release --minor

两个参数互斥(源码中conflicts "--major", "--minor")。对于 major/minor 发布,命令会额外输出一份面向博客的 notes(见 dev-cmd/release.rb#L91-L103):取上一 major/minor 版本到新版本之间的 release notes,过滤出* 描述 by @用户名 in PR链接格式的行,转成不带用户名的- 描述列表并排序——这就是后续官网 release notes 帖子的基础。

版本号的计算规则

dev-cmd/release.rb#L83-L89 给出了三类版本的推导逻辑,其中latest_version来自GitHub.get_latest_release "Homebrew", "brew"

new_version = if args.major? Version.new "#{latest_version.major.to_i + 1}.0.0" elsif args.minor? Version.new "#{latest_version.major}.#{latest_version.minor.to_i + 1}.0" else Version.new "#{latest_version.major}.#{latest_version.minor}.#{latest_version.patch.to_i + 1}" end.to_s

主次版本的一个月冷却期

Homebrew 会拒绝在上一个 major 或 minor release 距今不足一个月时创建新的 major/minor release。实现位于 dev-cmd/release.rb#L68-L81:

one_month_ago = Date.today << 1 latest_major_minor_release = begin GitHub.get_release "Homebrew", "brew", "#{latest_version.major_minor}.0" rescue GitHub::API::HTTPNotFoundError nil end if latest_major_minor_release.blank? opoo "Unable to determine the release date of the latest major/minor release." elsif Date.parse(latest_major_minor_release["published_at"]) > one_month_ago odie "The latest major/minor release was less than one month ago." end

它通过查询当前 major.minor 对应.0版本号的 release 发布时间,与“一个月前”比较:若发布日期晚于一个月前则直接报错;若查不到该 release 则仅打印警告。patch release 不受此限制。

第二步:创建草稿并触发工作流

确认预览无误后:

brew release --force

必要时同样附带--major--minor--force会真正执行:创建草稿 release 并触发 release 工作流。

--force之后命令还会做三重防护(见 dev-cmd/release.rb#L127-L163):

  1. 重复 release 检查:查询同版本号的既有 release。若已存在草稿,提示先在网页界面删除;若已存在已发布的同号 release,则提示应运行brew update而不是重新发布。
  2. main 分支同步检查:比较本地origin/main的 SHA 与 upstreamHomebrew/brewmaincommit,不一致时报错Run brew update before brew release --force。测试用例 release_spec.rb#L11-L34 专门验证了这一点:当本地 SHA 与 upstream SHA 不同时,命令终止且不会调用workflow_dispatch_event
  3. 触发并轮询工作流:以tag: new_version为输入 dispatchrelease.yml,随后轮询 workflow run(首次等待 15 秒,之后每 5 秒一次,最多 180 次约 15 分钟),期间打印运行页面地址供监控;run 结论不是success时命令以错误退出。成功后打印新 release 的 URL 并用浏览器打开。

完成后,按文档要求:在 GitHub 的 draft release 列表中审阅草稿,确认版本号与 release notes 无误后手动发布(publish)。这一步由维护者在网页端完成,命令本身不会自动发布。

release.yml 工作流:从 tag 到安装包

.github/workflows/release.yml 定义了三段式流水线buildtestupload

触发条件

  • workflow_dispatch:输入必填参数tag(release 的 git tag),这是brew release --force的入口;
  • pushpackage/**Library/Homebrew/utils/macos_user.sh或工作流自身改动时也触发(用于验证安装包相关变更),但此时不会创建草稿 release。

build 任务(macos 运行器)

  1. workflow_dispatch时先做与本地命令同款的 release blocker 标签检查(见上文);
  2. 清除本地 API 缓存,安装 Pandoc;
  3. 从 secrets 恢复 Apple Developer 证书到临时 keychain;
  4. checkout 完整仓库(fetch-depth: 0)后,若输入了TAG则执行git tag,并用git describe --tags校验 tag 与实际版本一致;
  5. 准备随包 API 缓存(cache_api目录);
  6. 检查安装包脚本的 Git 安全性(postinstall 中的特权 git 操作必须走git_no_hooks);
  7. pkgbuild构建 Homebrew 组件包,签名(--sign),过滤测试 fixtures,最低系统版本由HOMEBREW_MACOS_OLDEST_SUPPORTED控制;
  8. productbuild生成最终Homebrew-<version>.pkg
  9. actions/attest生成构建溯源(build provenance),上传产物到 Actions artifacts。

test 任务(macos-15 / macos-26 矩阵)

下载安装包 → 清掉已有 Homebrew 安装 → 用sudo installer真实安装 → 校验brew --version输出等于Homebrew <TAG>brew config/brew doctor→ 再次卸载重装并复测,验证安装/升级两条路径。

upload 任务

  1. xcrun notarytool submit --wait对 pkg 做 Apple 公证;
  2. 仅对workflow_dispatch事件执行gh release create "${TAG}" --draft --generate-notes --fail-on-no-commits,即草稿 release 由工作流创建
  3. 把 pkg 重命名为无版本号的Homebrew.pkg并上传到该 release,作为固定路径的安装入口。

可以看到,用户侧的“安装/升级”(brew update)与工作流侧的“构建/发布”是同一 tag 语义的两端:工作流保证 tag 对应一个经过签名、公证并在真实安装器上验证过的安装包。

major / minor 发布的强制操作

patch release 不需要以下操作;创建 major 或 minor release之前必须完成(对应 Releases.md 的 Major and minor releases 小节):

  1. 删除标记为odisabled的代码
  2. 把标记为odeprecated的代码改为odisabled
  3. 把标记为# odeprecated的注释代码取消注释(应进入废弃周期的);
  4. 加入计划中的odeprecations
  5. 删除仍带replacement:参数的命令参数定义(即上一周期已废弃的 CLI flag,其替代方案已稳定,可清理过渡参数)。

底层机制:odeprecated / odisabled 的实现

Homebrew 自身代码的废弃生命周期(deprecate → disable → remove)由odeprecated/odisabled支撑,完整规范见 Deprecating, Disabling and Removing 文档。实现位于 Library/Homebrew/utils/output.rb#L157-L246:

  • odeprecated(method, replacement:, disable:, disable_on:, disable_for_developers:):向调用者打印 “Calling X is deprecated/disabled!” 及替代方案。在开发模式(HOMEBREW_DEVELOPER=1disable_for_developers默认为true)下会抛出异常而非仅警告,保证维护者第一时间感知废弃调用;disable_on到期后自动升级为禁用。
  • odisabled(method, replacement:):内部就是odeprecated(..., disable: true),对所有用户直接报错。

这与文档表格中的四阶段一致:# odeprecated注释占位 → 取消注释生效(用户见警告)→ 改为odisabled(用户见错误)→ 随下一个 minor/major release 删除代码与replacement:参数。每次状态迁移都绑定在 minor/major 发布上,patch release 不参与——这正是把上述操作清单挂在“major/minor 发布之前”的原因。

release notes 与对外公告

  • brew release [--major|--minor]的输出作为官网 release notes 帖子的底稿,并编辑 notes,解释变更的目的与用户影响,而不只是罗列改了什么。major/minor 版本的 release notes 正文会指向 Homebrew 博客上的对应帖子(见 dev-cmd/release.rb#L106-L110)。
  • release 与帖子发布后,通过项目当前维护的官方渠道公告;只有在预期触达规模与审核成本匹配时,才考虑更广泛的公告渠道。

操作速查与适用前提

场景命令 / 动作
预览 patch releasebrew release
预览 major / minor releasebrew release --major/brew release --minor
创建草稿 + 触发工作流brew release --force(按需加--major/--minor
草稿审阅后在 GitHub releases 页面确认版本与 notes,手动 publish
major/minor 发布前完成odisabled删除、odeprecatedodisabled升级、# odeprecated启用、新增 deprecations、清理replacement:五步

适用前提与限制:

  • 执行者需要对 Homebrew/brew 仓库有写权限(命令帮助文本明确说明 “Requires write access to the Homebrew/brew repository”,且该命令hide_from_man_page!,不在公开 manpage 中展示);
  • 执行前必须保证本地origin/main与 upstreammain一致,否则需先brew update
  • 存在release blocker(或主次版本场景下的major/minor release blocker)open 问题时,命令与工作流双侧都会拒绝发布;
  • 上一 major/minor release 距今天数不足一个月时,新的 major/minor release 会被命令直接拒绝;
  • patch release 只能基于main的最新 commit;需要剔除变更时用 revert-发布-再应用 的流程,而不是从旧 commit 打 tag。

小结

Homebrew 的发布流程把“人”的检查与“机器”的防线叠在一起:文档层面用 blocker 标签、CI 状态、冷却期约束人的决策;brew release 命令 层面用版本推导、重复 release 检查、SHA 一致性校验和轮询机制约束执行;release.yml 工作流 层面则保证每个 tag 背后是一个签名、公证并在真实安装器上回归过的安装包。理解这条链路后,无论是执行一次 patch 发布,还是主导一次 major/minor 发布(含废弃生命周期的状态迁移),都有明确、可验证的步骤可依。

【免费下载链接】brew🍺 The Package Manager for Everywhere项目地址: https://gitcode.com/GitHub_Trending/br/brew

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 20:40:20

LivePortrait 实操指南:三条命令把静态照片变成动态肖像

LivePortrait 实操指南&#xff1a;三条命令把静态照片变成动态肖像 【免费下载链接】LivePortrait Bring portraits to life! 项目地址: https://gitcode.com/GitHub_Trending/li/LivePortrait LivePortrait 是一个基于 PyTorch 的开源人像动画工具&#xff1a;它从一段…

作者头像 李华
网站建设 2026/9/8 20:38:25

Windows Terminal 自动补全实战:PSReadLine 与 Clink 组合配置指南

1. 先搞明白&#xff1a;Windows Terminal 的自动补全到底缺什么这些年不管是从 cmd 迁移过来&#xff0c;还是从 macOS 的 iTerm2 转战 Windows&#xff0c;很多人装上 Windows Terminal 的第一反应都是&#xff1a;界面是漂亮了&#xff0c;字体渲染也舒服了&#xff0c;可这…

作者头像 李华
网站建设 2026/9/8 20:37:03

嵌入式音频解码中心SDK解析:标准C实现多路输入路由与缓冲机制

简介&#xff1a;这套C语言编写的声道解码SDK&#xff0c;面向音频设备开发与嵌入式软件工程师&#xff0c;解决HDMI、光纤、同轴、模拟、U盘、TF/SD卡及话筒输入等多类音源信号的统一解码问题。压缩包共46个文件&#xff0c;既包含C源码头文件与静态库&#xff0c;也附带PDF用…

作者头像 李华