Binary release 是一條直接把預編譯執行檔掛在 GitHub Release 下供使用者下載的發版通道,跳過 package registry。它解決的問題是:當套件不是函式庫而是 CLI binary,下游不需要重新編譯、也不一定有對應語言的 toolchain 時,需要一條「平台無關、即拿即用」的安裝路線。本篇用 zhtw-mcp 為陪跑案例,公開協作軌跡可直接對照 issue #35PR #40

為什麼需要這條通道

CLI binary 跟函式庫的下游使用脈絡不同。函式庫需要被同語言專案 import,自然走 registry(npm installpip installcargo add)。CLI binary 的目標讀者是「只想跑這個工具」的人,他們不一定有對應 toolchain、不想花時間編譯,也不會接受「先裝開發環境才能用」的入場門檻。

Binary release 的契約是:上游負責編譯、下游負責下載。這條契約成立需要三個前提同時滿足:

  1. CI 能在多平台 cross-compile 出可執行檔(macOS x64/arm64、Linux x64/arm64、Windows x64)
  2. 編譯產物有穩定 URL,下游可以用一行 shell 命令取得
  3. 安裝過程不依賴開發環境(不需要 git clone、不需要 build toolchain)

達成這三點需要一個 release 工具鏈把 build matrix、artifact 上傳、installer script 產生包成一個 tag-driven 的 workflow。Rust 生態用 cargo-dist、Go 生態用 goreleaser、語言中性的方案則是手刻 GitHub Actions matrix。三者觸發條件相同(push semver tag)、產物落點相同(GitHub Release assets),只在 build pipeline 細節有差。

Tag-driven release 的鏈路

Tag-driven 的核心設計:push tag 是發版意圖的唯一訊號。這條因果鏈每一環都要實作起來才會通:

1維護者 push tag vX.Y.Z         ↓
2                                →  release.yml workflow 觸發(tag pattern 匹配)
3                                →  cross-compile to N platforms(GitHub Actions matrix)
4                                →  打包成 <pkg>-x86_64-apple-darwin.tar.xz 等 N 個 archive
5                                →  產生 <pkg>-installer.sh / .ps1(內嵌指向上述 archive 的 download URL)
6                                →  建立 GitHub Release vX.Y.Z
7                                →  上傳所有 archive + installer 為 release assets
8                                →  GitHub 自動把 vX.Y.Z 的 assets 也鏡射到 /releases/latest/download/

這條鏈路上每個節點都是一塊要設定的工作:

  • Tag pattern:cargo-dist 預設匹配 **[0-9]+.[0-9]+.[0-9]+*,符合 semver 才會觸發
  • Build matrix:在 Cargo.toml[workspace.metadata.dist] 宣告 targets = [...],cargo-dist 會展開成對應的 GitHub Actions runners
  • Pre-build hooks:如果編譯前需要產生程式碼或下載資料,要透過 github-build-setup 注入(zhtw-mcp 的案例就是要先跑 gen-s2t-tables.py 產生 s2t_data.rs
  • Installer 範本:cargo-dist 內建 shell / powershell / homebrew / npm 等多種 installer 產生器,在 installers = [...] 設定
  • /releases/latest/download/ alias:GitHub 自動提供,指向 latest non-prerelease release 的 asset;prerelease 不會更新這個 alias

這也解釋了為什麼 git tag dev 或單純 commit 到 main 都不會發版 — 那不符合 tag pattern、不是發版意圖。

第一次搭 cargo-dist 的實作步驟

從零開始的維護者視角,Rust binary 專案要搭 cargo-dist 大致是這幾步:

  1. 裝 cargo-dist CLIcargo install cargo-dist(或從它自家的 installer 裝)
  2. dist init:互動式問答,選 targets、installers、CI provider(GitHub Actions),它會在 Cargo.toml 寫入 [workspace.metadata.dist] 並產生 .github/workflows/release.yml
  3. 檢查產出release.yml 是 auto-generated、開頭會標 # This file was autogenerated by dist不要手改,下次 dist generate 會被覆蓋
  4. 設定 pre-build hook(如果需要):在 Cargo.tomlgithub-build-setup = "build-setup.yml",把編譯前要跑的步驟寫在 .github/build-setup.yml(這個檔不會被 dist generate 覆蓋)
  5. 設定 preflight gate(重要):把現有的 main CI workflow 加上 workflow_call trigger,在 Cargo.tomlplan-jobs = ["./.github/workflows/main.yml"],讓 release pipeline 在 cross-compile 前先確認測試全綠
  6. 推第一個 prerelease tag 試水溫git tag v0.1.0-alpha.1 && git push origin v0.1.0-alpha.1,看 release.yml 跑出來的 matrix 是不是全綠
  7. 確認 installer script 可用:在乾淨機器上跑 curl ... /releases/download/v0.1.0-alpha.1/<pkg>-installer.sh | sh(注意 prerelease 要用完整 tag URL、不是 latest
  8. 推第一個正式 tag:跑 v0.1.0,這時 /releases/latest/download/ alias 才會生效
  9. 更新 README:把 installer 安裝命令寫上去;正式版發出後就能用 latest URL,prerelease 階段要寫完整 tag URL
  10. 後續維護:bump version → tag → push,cargo-dist 自動處理;只有改 [workspace.metadata.dist] 時才需要重跑 dist generate

第 5 步的 preflight gate 是新手最容易漏的關。沒有它的話、main 紅燈時你還是能 push tag、cargo-dist 還是會跑 cross-compile、爛 binary 還是會推到所有人。workflow_call 反向 reuse 這個 pattern 在 CI gate 與 workflow 邊界 有更完整討論。

Installer script 模式的契約

curl ... | sh 是這條通道的常見下游入口。這個入口要成立,前提是上游提供可驗證產物、下游執行前有最小安全檢查。

cargo-dist 產生的 installer 命令長這樣:

1curl --proto '=https' --tlsv1.2 -LsSf \
2  https://github.com/<owner>/<repo>/releases/latest/download/<pkg>-installer.sh | sh

逐項拆解 curl 的 flag:

片段用途
--proto '=https'限制只走 HTTPS,避免被中間人 downgrade 到 HTTP
--tlsv1.2拒絕舊版 TLS
-L跟隨 redirect(GitHub 的 latest alias 是 302)
-sS安靜但保留錯誤訊息
-fHTTP 錯誤時 curl 自己 exit non-zero(不把 404 HTML 當內容 pipe 進 sh)
| sh把腳本內容餵給 shell 執行

-f 那個 flag 是這條鏈路的安全點:沒有它的話、如果 release URL 暫時 404,GitHub 的 404 HTML 會被 pipe 到 sh 然後爆出一堆語法錯誤;有 -f 時 curl 會直接 exit 22、sh 不會被呼叫,使用者看到的是清楚的錯誤碼。這就是為什麼 cargo-dist 產生的範本預設帶 -f、不能省。

PowerShell 版本(irm | iex)的等價契約相同 — Invoke-RestMethod 對 404 也會丟 exception、不會把 HTML 餵給 Invoke-Expression

Installer script 自己的內部行為:偵測平台、下載對應 archive、解壓、放到 ~/.local/bin~/.cargo/bin、視需要更新 PATH。這部分由 cargo-dist 範本生成、跨專案幾乎一致、維護者不需要手寫。

最小安全基線(教學案例版)

教學案例可以示範 curl | sh,但可維護版本要同時提供「下載、驗證、執行」路徑,讓使用者在高風險環境可切換到可審計流程。

 1# 1) 下載 installer 與 checksum
 2curl --proto '=https' --tlsv1.2 -LsSf \
 3  -o /tmp/<pkg>-installer.sh \
 4  https://github.com/<owner>/<repo>/releases/download/vX.Y.Z/<pkg>-installer.sh
 5curl --proto '=https' --tlsv1.2 -LsSf \
 6  -o /tmp/<pkg>-checksums.txt \
 7  https://github.com/<owner>/<repo>/releases/download/vX.Y.Z/<pkg>-checksums.txt
 8
 9# 2) 驗證 checksum(sha256sum 或 shasum 擇一)
10sha256sum -c /tmp/<pkg>-checksums.txt --ignore-missing
11# shasum -a 256 -c /tmp/<pkg>-checksums.txt
12
13# 3) 執行 installer
14sh /tmp/<pkg>-installer.sh

這條路徑的責任分工是:

  1. 上游:發布 installer 與對應 checksum(或 provenance)。
  2. 下游:先驗證再執行。
  3. 文件:同時提供快速路徑與可審計路徑,並標明適用情境。

Pre-release(early adopter)通道

第一個正式 release 之前,pipeline 本身需要先被驗證。這時 prerelease tag(v0.1.0-alpha.1v0.1.0-rc1 之類)就派上用場:

  • 作為 pipeline 自身的測試:tag 推下去能跑出多平台 binary,代表 cargo-dist 設定正確
  • 給 early adopter 試用:願意當先驅者的使用者可以用完整 tag URL 取得 binary
  • 不污染 latest alias:GitHub 的 releases/latest/download/ 只指向 non-prerelease,所以 prerelease 不會「假發版」

代價是 prerelease 沒有 stable URL — 每個版本要寫完整 tag、不能用 latest。所以 README 安裝段落在 v0.1.0 出來之前要寫:

1# Pre-release example(給 early adopter)
2curl --proto '=https' --tlsv1.2 -LsSf \
3  https://github.com/<owner>/<repo>/releases/download/v0.1.0-alpha.1/<pkg>-installer.sh | sh

正式 v0.1.0 出來之後再切回 latest URL。這是 zhtw-mcp issue #35 討論裡 hydai 提的折衷方案,能讓社群在 pipeline 完備前先試用、又不誤導不明就裡的使用者以為正式版已就位。

zhtw-mcp 案例:社群協作把 release pipeline 搭起來

zhtw-mcp 的 issue #35PR #40 是這條搭建過程的活案例。整個討論的時間軸:

  1. dlackty 提 issue #35:建議導入 cargo-dist + Homebrew、列出建議 targets、指出 s2t_data.rs 需要 pre-build hook
  2. 作者 jserv 回應:認同方向,但坦承自己 Rust 經驗有限、這個專案部分目的就是為了學 Rust 生態,邀請社群提 PR 推進
  3. hydai 開 PR #40:第一次用 cargo-dist,自己也在學,誠實表示「想知道方向對不對,希望熟手能接手」,並引用自己之前用 knope 手刻 release 的另一個 repo 作為對照
  4. jserv 提到 installer URL 失效:README 已經寫了 releases/latest/download/...,但還沒有正式 release,建議用 pre-release 給 early adopter
  5. hydai 提議 v0.1.0-alpha.1:作為 early adopter 通道、提醒 prerelease 沒有 latest alias、要用完整 tag URL

這個討論留下幾個值得學的點:

  • 公開承認還在學是好事:jserv 直接說「我 Rust 經驗有限、我也在學」、hydai 說「我第一次用 cargo-dist」,這比假裝專家有效率多了。社群協作的核心是大家都看到同一個未完成狀態、一起補。
  • README 先寫安裝命令再補 release 是常見順序:把 release 路線當作目標釘出來、再倒推實作,是刻意的設計。先寫文件再補 pipeline 的順序也讓 issue #35 / PR #40 更容易聚焦。
  • 特殊 build hook 是 cargo-dist 的明確支援點:zhtw-mcp 需要在編譯前跑 gen-s2t-tables.py 產生 s2t_data.rs,這正好是 github-build-setup 設計給的場景。如果你的 repo 有類似「編譯前要產生程式碼/下載資料」的需求、不必為此放棄 cargo-dist。
  • Pre-release 是 pipeline 學習期的合理工具:先用 v0.1.0-alpha.1 把 pipeline 跑通、把問題暴露出來,比等到一切完美才發版更有效率。

跟著這個 issue 串看完一輪、可以得到一個從零搭 cargo-dist 的真實參照框架,比官方文件更貼近實際遇到的問題。

Homebrew 通道:cargo-dist 怎麼幫你出 formula

brew install 是 macOS 使用者最熟的安裝路線,但 Homebrew 有兩種發版形式:

形式怎麼裝維護成本
Homebrew corebrew install <pkg>高 — 要過 homebrew-core 的 PR review,門檻嚴
Homebrew tapbrew install <user>/<tap>/<pkg>低 — 在自己的 GitHub repo homebrew-<tap> 放 formula

cargo-dist 預設支援的是後者(tap)。設定方式是在 [workspace.metadata.dist] 加:

1installers = ["shell", "powershell", "homebrew"]
2tap = "<your-github-username>/homebrew-<tap-name>"

然後在 GitHub 開一個叫 homebrew-<tap-name> 的 repo(命名規則是 Homebrew 強制的),cargo-dist 會在每次 release 自動 push 一個更新過的 formula 到那個 repo。下游使用者只要:

1brew tap <your-github-username>/<tap-name>
2brew install <pkg>

要走 homebrew-core 是另一個層級的事 — 需要套件夠成熟、有穩定使用者基數、有清楚的 license、過 homebrew-core maintainer 的 review。多數新專案先做 tap、累積使用者跟成熟度後再考慮 core。

上線前的最後檢查

第一個正式 v0.1.0 推出去之前最後跑一遍:

  • Prerelease tag(v0.1.0-alpha.1 之類)跑過 release.yml、cross-compile matrix 全綠
  • 從乾淨機器跑 README 寫的 installer 命令、從下載到執行整條順
  • Pre-build hook(如果有)在所有 platform 都能跑、不依賴特定 OS
  • Preflight gate 的 workflow_call reuse 確實 block 住紅燈 main
  • README 的 installer URL 跟實際 asset 命名規則一致(cargo-dist 會用 <pkg>-installer.sh、不要寫成 install.sh
  • Changelog 跟 tag 對齊(cargo-dist 會把 changelog 抓進 release notes)
  • 有提供可審計安裝路徑(下載 + checksum/provenance 驗證 + 執行)

第一條 v0.1.0 推出去後 releases/latest/download/... alias 才會生效、那時就能把 README 改成 latest URL、徹底完成這條通道的搭建。

來源與規格

下一步路由