brew -- 用 brew-publish-formula 发布 Formula

摘要

  • 手写 Formula、创建 Tap 的过程见 brew -- 创建自己的 Formula。版本发上去之后,每次还要自己算下一个版本、推 tag、下载 GitHub 源码包、改 url / sha256,再把 formula 提交进 Tap。
  • brew-publish-formula 把这几步收成项目根目录里的一条命令。它读当前仓库的配置、formula 和 git 信息,算出新版本,推送 tag,更新 formula,再把文件同步到本地 Tap。
  • 正确的 sha256 要等 tag 到达 GitHub 之后才能算。脚本写进项目里的 .formula,再复制到 Tap 并在 Tap 仓库提交。Homebrew 读的是 Tap 里的那份。.formula 不进项目仓库。
  • brew 本身的安装和日常命令见 MacOS软件包管理器--brew。

它做了什么

  • 当前工作目录必须是要发布的项目根目录,并且是 git 仓库根目录。origin 必须指向 GitHub。源码包地址和 tag 推送都走这个远程:

1
https://github.com/<owner>/<repo>/archive/refs/tags/vX.Y.Z.tar.gz
  • 一次完整发布按这个顺序走:

    1. 确认当前目录是 git 根目录。
    2. 读取 .formula/brew-publish.conf。根目录的 .brew-publish.conf 也可以,--config 指定的文件优先。都没有就停下来,提示先 --init。
    3. 确认配置里的 Tap 已经在本机。没有则先 brew tap-new。
    4. 从 origin 解析 GitHub 仓库,定位 .formula/*.rb。
    5. 用最新语义化 tag 加 bump 类型算出新版本,或直接用 --version。
    6. 发布本仓库时,对主文件跑 shellcheck。--skip-check 可以跳过。
    7. 把本地 formula 的 url 改成新 tag。发布本仓库时还会改脚本里的 TOOL_VERSION,提交这一处改动,再打 annotated tag,例如 v1.0.6。
    8. 下载该 tag 的 GitHub 源码包,计算 sha256。
    9. 把 url 和 sha256 写回本地 .formula。这一步不提交进项目仓库。
    10. 把 formula 复制到本地 Tap,跑 brew style --fix 和 brew audit。
    11. brew style 如果改写了 formula,再抄回本地 .formula。
    12. 在 Tap 仓库提交并推送。提交信息是 <formula名> <tag>。
  • 命令先打印发布计划,确认后才执行。--yes 跳过确认。

安装

  • 依赖:git、curl、ruby、Homebrew,以及 shasum 或 sha256sum。发布本仓库时还要用 shellcheck。

从 GitHub 安装

  • 克隆仓库,确认脚本可执行,并把目录加进 PATH:

1
2
git clone https://github.com/hanqunfeng/brew-publish-formula.git ~/brew-publish-formula
chmod +x ~/brew-publish-formula/brew-publish-formula
  • 在 ~/.zshrc 里加上:

1
export PATH="$HOME/brew-publish-formula:$PATH"
  • 新开一个终端后确认:

1
brew-publish-formula --help --zh
  • --help 是英文说明。--help --zh 是中文说明。其他命令加上 --zh,运行时的提示也用中文。

从 Homebrew 安装

  • formula 已经发布到 Tap 之后:

1
2
brew tap hanqunfeng/tap
brew install brew-publish-formula
  • 也可以一行安装:

1
brew install hanqunfeng/tap/brew-publish-formula
  • 升级:

1
brew update && brew upgrade brew-publish-formula
  • .formula/*.rb 里的 sha256 在第一次成功发布前可以是占位值。发布命令会在 tag 推送后写成真实哈希,再把这份文件复制到 Tap 并提交。

初始化

  • 在项目根目录执行:

1
brew-publish-formula --init
  • --init 只准备文件,不发布。它会生成 .formula/brew-publish.conf,并把 .formula 写进 .gitignore。没有 .gitignore 时会创建。已有配置和已有 .rb 不会被覆盖。

  • formula= 只写文件名。公式文件固定放在 .formula/ 下。可以用 --tap user/homebrew-tap 指定写入新配置的 Tap。不指定时,Tap 默认是 <GitHub 用户名>/homebrew-tap。

  • 目标 .rb 还不存在时:

    1. 先看本地有没有配置里的 Tap。没有则询问是否执行 brew tap-new。
    2. Tap 里已有同名 .rb 时,复制到 .formula/。
    3. Tap 里也没有时,询问是否生成公式模板。
  • 模板里的 sha256 是 64 个 0。有语义化 tag 时,url 用最新 tag;没有 tag 时用 0.1.0。生成后要按项目改 desc、license 和 install。模板假定仓库根目录有一个与 formula 同名的可执行文件,直接 bin.install。命令在子目录里,或者还要编译,就改 install。本仓库生成出来的公式是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 由 brew-publish-formula --init 生成。
# 请按项目修改 desc、license 和 install。
class BrewPublishFormula < Formula
desc "Install brew-publish-formula"
homepage "https://github.com/hanqunfeng/brew-publish-formula"
url "https://github.com/hanqunfeng/brew-publish-formula/archive/refs/tags/v1.0.4.tar.gz"
sha256 "869138be3f7fa365858e193c899002b60f46cad2bde39f1ec183dbf087508d63"
license "MIT"

def install
bin.install "brew-publish-formula"
end

test do
assert_match "brew-publish-formula", shell_output("#{bin}/brew-publish-formula --help")
end
end

配置

  • 配置放在要发布的仓库根目录,优先顺序是 --config、.formula/brew-publish.conf、.brew-publish.conf。确认当前目录是 git 根目录后,命令立刻检查这些位置,没有配置就停止。

1
2
3
4
# 由 brew-publish-formula --init 生成。发布前请按项目修改。
tap=hanqunfeng/homebrew-tap
formula=brew-publish-formula.rb
tag_prefix=v
  • 三个键:

    • tap:user/homebrew-tap 或 user/tap。两种写法都会规范成同一个 Tap。发布前检查本地是否已有;没有就先执行 brew tap-new user/homebrew-tap。
    • formula:文件名,例如 brew-publish-formula.rb。实际路径是 .formula/ 加上这个文件名。省略时,.formula/ 下只能有一个 *.rb,多了就无法决定用哪一个。
    • tag_prefix:可选,默认 v,生成 v1.2.3。
  • url 里直接写 tag,例如 v1.2.3。Homebrew 从这段地址识别版本,公式里不再单独写 version。再写一行相同的 version 时,brew audit 会报 redundant。发布时会把 url 里的 tag 改成新版本,并更新 sha256。公式里只保留一对顶层 url 和一对顶层 sha256。

版本怎么来

  • 版本号优先取最新的语义化 tag。没有 tag 时从 formula 的 url 读取。两处都没有时,按 0.0.0 再递增。第一次打 tag 可以用 --version 指定版本,不必先有历史 tag。

  • patch、minor、major 只动对应那一段,后面的段归零。1.0.5 分别变成 1.0.6、1.1.0、2.0.0。

  • 新版本不能和最新 tag 相同,也不能比它低。

发布

  • 在项目根目录执行:

1
2
3
4
5
6
brew-publish-formula patch --dry-run
brew-publish-formula patch # 1.0.5 → 1.0.6
brew-publish-formula minor # 1.0.5 → 1.1.0
brew-publish-formula major # 1.0.5 → 2.0.0
brew-publish-formula --version 1.2.0
brew-publish-formula --resume 1.0.6 --yes
  • 全量发布要求工作区干净。.formula 被忽略时,里面的修改不会出现在 git status 里。--dry-run 发现工作区不干净时只警告,正式发布会停住。

  • 选项:

    • --dry-run:只打印将执行的操作,不改文件,不推送。
    • --yes / -y:跳过交互确认。
    • --skip-check:发布本仓库时跳过 shellcheck。
    • --skip-tap-audit:跳过 brew style / brew audit。
    • --no-push:不推远程。本地 tag 创建后会停止,因为这时还不能从 GitHub 下载源码包,也就算不出 sha256。
    • --resume X.Y.Z:tag 已经推送时,从下载源码包、计算 sha256 继续。不要再次 bump。
    • --tap、--formula、--tag-prefix、--config:覆盖配置文件里的对应项。
    • --zh:提示用中文。
  • 正确的 sha256 只能在 tag 到达 GitHub 之后得到。脚本把它写入本地 .formula,再复制到 Tap。提交发生在 Tap 仓库。发布本仓库是例外:改 TOOL_VERSION 的那一次提交在项目仓库里,发生在打 tag 之前。Homebrew 读取 Tap 里的 formula。

  • tag 已推送但后面的步骤失败时,用 --resume 接着做。--resume 时,若公式文件仍被 git 跟踪,只允许它和 brew-publish-formula 已修改。

  • 结束时会打印版本、源码包地址、sha256、项目路径、Tap 里的公式路径,以及给用户的安装和升级命令。

发布后验证

1
2
brew update
brew upgrade <formula-name>
  • 也可以用项目里的 formula 文件做一次本地安装:

1
brew install --build-from-source ./.formula/<formula-name>.rb

接到已有项目

  • 以 java-project-generator 为例。仓库根目录已经是 git 根目录,origin 指向 GitHub。新增 .formula/brew-publish.conf:

1
2
3
tap=hanqunfeng/homebrew-tap
formula=java-project-generator.rb
tag_prefix=v
  • 本地还没有这份 .rb 时,先 brew-publish-formula --init。Tap 里已经有同名公式,会被复制进 .formula/;没有则按提示生成模板,再改 desc、license 和 install。

  • 之后在该仓库根目录执行:

1
2
brew-publish-formula patch --dry-run
brew-publish-formula patch

开发本仓库

  • 改脚本后先检查:

1
shellcheck brew-publish-formula
  • 发布本仓库时会执行同样的检查。--skip-check 可以跳过。

后记