<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Binary on Tarragon</title><link>https://tarrragon.github.io/blog/tags/binary/</link><description>Recent content in Binary on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 06 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/binary/index.xml" rel="self" type="application/rss+xml"/><item><title>Binary release 與 installer 模式</title><link>https://tarrragon.github.io/blog/ci/package-library-release/binary-release-and-installer/</link><pubDate>Wed, 06 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ci/package-library-release/binary-release-and-installer/</guid><description>&lt;p>Binary release 是一條直接把預編譯執行檔掛在 GitHub Release 下供使用者下載的發版通道，跳過 package registry。它解決的問題是：當套件不是函式庫而是 CLI binary，下游不需要重新編譯、也不一定有對應語言的 toolchain 時，需要一條「平台無關、即拿即用」的安裝路線。本篇用 &lt;a href="https://github.com/sysprog21/zhtw-mcp">&lt;code>zhtw-mcp&lt;/code>&lt;/a> 為陪跑案例，公開協作軌跡可直接對照 &lt;a href="https://github.com/sysprog21/zhtw-mcp/issues/35">issue #35&lt;/a> 與 &lt;a href="https://github.com/sysprog21/zhtw-mcp/pull/40">PR #40&lt;/a>。&lt;/p>
&lt;h2 id="為什麼需要這條通道">為什麼需要這條通道&lt;/h2>
&lt;p>CLI binary 跟函式庫的下游使用脈絡不同。函式庫需要被同語言專案 import，自然走 registry（&lt;code>npm install&lt;/code>、&lt;code>pip install&lt;/code>、&lt;code>cargo add&lt;/code>）。CLI binary 的目標讀者是「只想跑這個工具」的人，他們不一定有對應 toolchain、不想花時間編譯，也不會接受「先裝開發環境才能用」的入場門檻。&lt;/p>
&lt;p>Binary release 的契約是：&lt;strong>上游負責編譯、下游負責下載&lt;/strong>。這條契約成立需要三個前提同時滿足：&lt;/p>
&lt;ol>
&lt;li>CI 能在多平台 cross-compile 出可執行檔（macOS x64/arm64、Linux x64/arm64、Windows x64）&lt;/li>
&lt;li>編譯產物有穩定 URL，下游可以用一行 shell 命令取得&lt;/li>
&lt;li>安裝過程不依賴開發環境（不需要 git clone、不需要 build toolchain）&lt;/li>
&lt;/ol>
&lt;p>達成這三點需要一個 release 工具鏈把 build matrix、artifact 上傳、installer script 產生包成一個 tag-driven 的 workflow。Rust 生態用 &lt;a href="https://opensource.axo.dev/cargo-dist/">cargo-dist&lt;/a>、Go 生態用 &lt;a href="https://goreleaser.com/">goreleaser&lt;/a>、語言中性的方案則是手刻 GitHub Actions matrix。三者觸發條件相同（push semver tag）、產物落點相同（GitHub Release assets），只在 build pipeline 細節有差。&lt;/p>
&lt;h2 id="tag-driven-release-的鏈路">Tag-driven release 的鏈路&lt;/h2>
&lt;p>Tag-driven 的核心設計：&lt;strong>push tag 是發版意圖的唯一訊號&lt;/strong>。這條因果鏈每一環都要實作起來才會通：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">維護者 push tag vX.Y.Z ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> → release.yml workflow 觸發（tag pattern 匹配）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> → cross-compile to N platforms（GitHub Actions matrix）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> → 打包成 &amp;lt;pkg&amp;gt;-x86_64-apple-darwin.tar.xz 等 N 個 archive
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> → 產生 &amp;lt;pkg&amp;gt;-installer.sh / .ps1（內嵌指向上述 archive 的 download URL）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> → 建立 GitHub Release vX.Y.Z
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> → 上傳所有 archive + installer 為 release assets
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> → GitHub 自動把 vX.Y.Z 的 assets 也鏡射到 /releases/latest/download/&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這條鏈路上每個節點都是一塊要設定的工作：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Tag pattern&lt;/strong>：cargo-dist 預設匹配 &lt;code>**[0-9]+.[0-9]+.[0-9]+*&lt;/code>，符合 semver 才會觸發&lt;/li>
&lt;li>&lt;strong>Build matrix&lt;/strong>：在 &lt;code>Cargo.toml&lt;/code> 的 &lt;code>[workspace.metadata.dist]&lt;/code> 宣告 &lt;code>targets = [...]&lt;/code>，cargo-dist 會展開成對應的 GitHub Actions runners&lt;/li>
&lt;li>&lt;strong>Pre-build hooks&lt;/strong>：如果編譯前需要產生程式碼或下載資料，要透過 &lt;code>github-build-setup&lt;/code> 注入（zhtw-mcp 的案例就是要先跑 &lt;code>gen-s2t-tables.py&lt;/code> 產生 &lt;code>s2t_data.rs&lt;/code>）&lt;/li>
&lt;li>&lt;strong>Installer 範本&lt;/strong>：cargo-dist 內建 &lt;code>shell&lt;/code> / &lt;code>powershell&lt;/code> / &lt;code>homebrew&lt;/code> / &lt;code>npm&lt;/code> 等多種 installer 產生器，在 &lt;code>installers = [...]&lt;/code> 設定&lt;/li>
&lt;li>&lt;strong>&lt;code>/releases/latest/download/&lt;/code> alias&lt;/strong>：GitHub 自動提供，指向 latest non-prerelease release 的 asset；prerelease 不會更新這個 alias&lt;/li>
&lt;/ul>
&lt;p>這也解釋了為什麼 &lt;code>git tag dev&lt;/code> 或單純 commit 到 main 都不會發版 — 那不符合 tag pattern、不是發版意圖。&lt;/p></description><content:encoded><![CDATA[<p>Binary release 是一條直接把預編譯執行檔掛在 GitHub Release 下供使用者下載的發版通道，跳過 package registry。它解決的問題是：當套件不是函式庫而是 CLI binary，下游不需要重新編譯、也不一定有對應語言的 toolchain 時，需要一條「平台無關、即拿即用」的安裝路線。本篇用 <a href="https://github.com/sysprog21/zhtw-mcp"><code>zhtw-mcp</code></a> 為陪跑案例，公開協作軌跡可直接對照 <a href="https://github.com/sysprog21/zhtw-mcp/issues/35">issue #35</a> 與 <a href="https://github.com/sysprog21/zhtw-mcp/pull/40">PR #40</a>。</p>
<h2 id="為什麼需要這條通道">為什麼需要這條通道</h2>
<p>CLI binary 跟函式庫的下游使用脈絡不同。函式庫需要被同語言專案 import，自然走 registry（<code>npm install</code>、<code>pip install</code>、<code>cargo add</code>）。CLI binary 的目標讀者是「只想跑這個工具」的人，他們不一定有對應 toolchain、不想花時間編譯，也不會接受「先裝開發環境才能用」的入場門檻。</p>
<p>Binary release 的契約是：<strong>上游負責編譯、下游負責下載</strong>。這條契約成立需要三個前提同時滿足：</p>
<ol>
<li>CI 能在多平台 cross-compile 出可執行檔（macOS x64/arm64、Linux x64/arm64、Windows x64）</li>
<li>編譯產物有穩定 URL，下游可以用一行 shell 命令取得</li>
<li>安裝過程不依賴開發環境（不需要 git clone、不需要 build toolchain）</li>
</ol>
<p>達成這三點需要一個 release 工具鏈把 build matrix、artifact 上傳、installer script 產生包成一個 tag-driven 的 workflow。Rust 生態用 <a href="https://opensource.axo.dev/cargo-dist/">cargo-dist</a>、Go 生態用 <a href="https://goreleaser.com/">goreleaser</a>、語言中性的方案則是手刻 GitHub Actions matrix。三者觸發條件相同（push semver tag）、產物落點相同（GitHub Release assets），只在 build pipeline 細節有差。</p>
<h2 id="tag-driven-release-的鏈路">Tag-driven release 的鏈路</h2>
<p>Tag-driven 的核心設計：<strong>push tag 是發版意圖的唯一訊號</strong>。這條因果鏈每一環都要實作起來才會通：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">維護者 push tag vX.Y.Z         ↓
</span></span><span class="line"><span class="ln">2</span><span class="cl">                                →  release.yml workflow 觸發（tag pattern 匹配）
</span></span><span class="line"><span class="ln">3</span><span class="cl">                                →  cross-compile to N platforms（GitHub Actions matrix）
</span></span><span class="line"><span class="ln">4</span><span class="cl">                                →  打包成 &lt;pkg&gt;-x86_64-apple-darwin.tar.xz 等 N 個 archive
</span></span><span class="line"><span class="ln">5</span><span class="cl">                                →  產生 &lt;pkg&gt;-installer.sh / .ps1（內嵌指向上述 archive 的 download URL）
</span></span><span class="line"><span class="ln">6</span><span class="cl">                                →  建立 GitHub Release vX.Y.Z
</span></span><span class="line"><span class="ln">7</span><span class="cl">                                →  上傳所有 archive + installer 為 release assets
</span></span><span class="line"><span class="ln">8</span><span class="cl">                                →  GitHub 自動把 vX.Y.Z 的 assets 也鏡射到 /releases/latest/download/</span></span></code></pre></div><p>這條鏈路上每個節點都是一塊要設定的工作：</p>
<ul>
<li><strong>Tag pattern</strong>：cargo-dist 預設匹配 <code>**[0-9]+.[0-9]+.[0-9]+*</code>，符合 semver 才會觸發</li>
<li><strong>Build matrix</strong>：在 <code>Cargo.toml</code> 的 <code>[workspace.metadata.dist]</code> 宣告 <code>targets = [...]</code>，cargo-dist 會展開成對應的 GitHub Actions runners</li>
<li><strong>Pre-build hooks</strong>：如果編譯前需要產生程式碼或下載資料，要透過 <code>github-build-setup</code> 注入（zhtw-mcp 的案例就是要先跑 <code>gen-s2t-tables.py</code> 產生 <code>s2t_data.rs</code>）</li>
<li><strong>Installer 範本</strong>：cargo-dist 內建 <code>shell</code> / <code>powershell</code> / <code>homebrew</code> / <code>npm</code> 等多種 installer 產生器，在 <code>installers = [...]</code> 設定</li>
<li><strong><code>/releases/latest/download/</code> alias</strong>：GitHub 自動提供，指向 latest non-prerelease release 的 asset；prerelease 不會更新這個 alias</li>
</ul>
<p>這也解釋了為什麼 <code>git tag dev</code> 或單純 commit 到 main 都不會發版 — 那不符合 tag pattern、不是發版意圖。</p>
<h2 id="第一次搭-cargo-dist-的實作步驟">第一次搭 cargo-dist 的實作步驟</h2>
<p>從零開始的維護者視角，Rust binary 專案要搭 cargo-dist 大致是這幾步：</p>
<ol>
<li><strong>裝 cargo-dist CLI</strong>：<code>cargo install cargo-dist</code>（或從它自家的 installer 裝）</li>
<li><strong>跑 <code>dist init</code></strong>：互動式問答，選 targets、installers、CI provider（GitHub Actions），它會在 <code>Cargo.toml</code> 寫入 <code>[workspace.metadata.dist]</code> 並產生 <code>.github/workflows/release.yml</code></li>
<li><strong>檢查產出</strong>：<code>release.yml</code> 是 auto-generated、開頭會標 <code># This file was autogenerated by dist</code>，<strong>不要手改</strong>，下次 <code>dist generate</code> 會被覆蓋</li>
<li><strong>設定 pre-build hook（如果需要）</strong>：在 <code>Cargo.toml</code> 加 <code>github-build-setup = &quot;build-setup.yml&quot;</code>，把編譯前要跑的步驟寫在 <code>.github/build-setup.yml</code>（這個檔不會被 <code>dist generate</code> 覆蓋）</li>
<li><strong>設定 preflight gate（重要）</strong>：把現有的 main CI workflow 加上 <code>workflow_call</code> trigger，在 <code>Cargo.toml</code> 設 <code>plan-jobs = [&quot;./.github/workflows/main.yml&quot;]</code>，讓 release pipeline 在 cross-compile 前先確認測試全綠</li>
<li><strong>推第一個 prerelease tag 試水溫</strong>：<code>git tag v0.1.0-alpha.1 &amp;&amp; git push origin v0.1.0-alpha.1</code>，看 release.yml 跑出來的 matrix 是不是全綠</li>
<li><strong>確認 installer script 可用</strong>：在乾淨機器上跑 <code>curl ... /releases/download/v0.1.0-alpha.1/&lt;pkg&gt;-installer.sh | sh</code>（注意 prerelease 要用完整 tag URL、不是 <code>latest</code>）</li>
<li><strong>推第一個正式 tag</strong>：跑 <code>v0.1.0</code>，這時 <code>/releases/latest/download/</code> alias 才會生效</li>
<li><strong>更新 README</strong>：把 installer 安裝命令寫上去；正式版發出後就能用 <code>latest</code> URL，prerelease 階段要寫完整 tag URL</li>
<li><strong>後續維護</strong>：bump version → tag → push，cargo-dist 自動處理；只有改 <code>[workspace.metadata.dist]</code> 時才需要重跑 <code>dist generate</code></li>
</ol>
<p>第 5 步的 preflight gate 是新手最容易漏的關。沒有它的話、main 紅燈時你還是能 push tag、cargo-dist 還是會跑 cross-compile、爛 binary 還是會推到所有人。<code>workflow_call</code> 反向 reuse 這個 pattern 在 <a href="../../ci-gate-workflow-boundary/">CI gate 與 workflow 邊界</a> 有更完整討論。</p>
<h2 id="installer-script-模式的契約">Installer script 模式的契約</h2>
<p><code>curl ... | sh</code> 是這條通道的常見下游入口。這個入口要成立，前提是上游提供可驗證產物、下游執行前有最小安全檢查。</p>
<p>cargo-dist 產生的 installer 命令長這樣：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">curl --proto <span class="s1">&#39;=https&#39;</span> --tlsv1.2 -LsSf <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  https://github.com/&lt;owner&gt;/&lt;repo&gt;/releases/latest/download/&lt;pkg&gt;-installer.sh <span class="p">|</span> sh</span></span></code></pre></div><p>逐項拆解 curl 的 flag：</p>
<table>
  <thead>
      <tr>
          <th>片段</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>--proto '=https'</code></td>
          <td>限制只走 HTTPS，避免被中間人 downgrade 到 HTTP</td>
      </tr>
      <tr>
          <td><code>--tlsv1.2</code></td>
          <td>拒絕舊版 TLS</td>
      </tr>
      <tr>
          <td><code>-L</code></td>
          <td>跟隨 redirect（GitHub 的 latest alias 是 302）</td>
      </tr>
      <tr>
          <td><code>-sS</code></td>
          <td>安靜但保留錯誤訊息</td>
      </tr>
      <tr>
          <td><code>-f</code></td>
          <td>HTTP 錯誤時 curl 自己 exit non-zero（不把 404 HTML 當內容 pipe 進 sh）</td>
      </tr>
      <tr>
          <td><code>| sh</code></td>
          <td>把腳本內容餵給 shell 執行</td>
      </tr>
  </tbody>
</table>
<p><code>-f</code> 那個 flag 是這條鏈路的安全點：沒有它的話、如果 release URL 暫時 404，GitHub 的 404 HTML 會被 pipe 到 sh 然後爆出一堆語法錯誤；有 <code>-f</code> 時 curl 會直接 exit 22、<code>sh</code> 不會被呼叫，使用者看到的是清楚的錯誤碼。這就是為什麼 cargo-dist 產生的範本預設帶 <code>-f</code>、不能省。</p>
<p>PowerShell 版本（<code>irm | iex</code>）的等價契約相同 — <code>Invoke-RestMethod</code> 對 404 也會丟 exception、不會把 HTML 餵給 <code>Invoke-Expression</code>。</p>
<p>Installer script 自己的內部行為：偵測平台、下載對應 archive、解壓、放到 <code>~/.local/bin</code> 或 <code>~/.cargo/bin</code>、視需要更新 PATH。這部分由 cargo-dist 範本生成、跨專案幾乎一致、維護者不需要手寫。</p>
<h2 id="最小安全基線教學案例版">最小安全基線（教學案例版）</h2>
<p>教學案例可以示範 <code>curl | sh</code>，但可維護版本要同時提供「下載、驗證、執行」路徑，讓使用者在高風險環境可切換到可審計流程。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 1) 下載 installer 與 checksum</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">curl --proto <span class="s1">&#39;=https&#39;</span> --tlsv1.2 -LsSf <span class="se">\
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="se"></span>  -o /tmp/&lt;pkg&gt;-installer.sh <span class="se">\
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="se"></span>  https://github.com/&lt;owner&gt;/&lt;repo&gt;/releases/download/vX.Y.Z/&lt;pkg&gt;-installer.sh
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">curl --proto <span class="s1">&#39;=https&#39;</span> --tlsv1.2 -LsSf <span class="se">\
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="se"></span>  -o /tmp/&lt;pkg&gt;-checksums.txt <span class="se">\
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="se"></span>  https://github.com/&lt;owner&gt;/&lt;repo&gt;/releases/download/vX.Y.Z/&lt;pkg&gt;-checksums.txt
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 2) 驗證 checksum（sha256sum 或 shasum 擇一）</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">sha256sum -c /tmp/&lt;pkg&gt;-checksums.txt --ignore-missing
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># shasum -a 256 -c /tmp/&lt;pkg&gt;-checksums.txt</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 3) 執行 installer</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">sh /tmp/&lt;pkg&gt;-installer.sh</span></span></code></pre></div><p>這條路徑的責任分工是：</p>
<ol>
<li>上游：發布 installer 與對應 checksum（或 provenance）。</li>
<li>下游：先驗證再執行。</li>
<li>文件：同時提供快速路徑與可審計路徑，並標明適用情境。</li>
</ol>
<h2 id="pre-releaseearly-adopter通道">Pre-release（early adopter）通道</h2>
<p>第一個正式 release 之前，pipeline 本身需要先被驗證。這時 prerelease tag（<code>v0.1.0-alpha.1</code>、<code>v0.1.0-rc1</code> 之類）就派上用場：</p>
<ul>
<li><strong>作為 pipeline 自身的測試</strong>：tag 推下去能跑出多平台 binary，代表 cargo-dist 設定正確</li>
<li><strong>給 early adopter 試用</strong>：願意當先驅者的使用者可以用完整 tag URL 取得 binary</li>
<li><strong>不污染 latest alias</strong>：GitHub 的 <code>releases/latest/download/</code> 只指向 non-prerelease，所以 prerelease 不會「假發版」</li>
</ul>
<p>代價是 prerelease 沒有 stable URL — 每個版本要寫完整 tag、不能用 <code>latest</code>。所以 README 安裝段落在 v0.1.0 出來之前要寫：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Pre-release example（給 early adopter）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">curl --proto <span class="s1">&#39;=https&#39;</span> --tlsv1.2 -LsSf <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  https://github.com/&lt;owner&gt;/&lt;repo&gt;/releases/download/v0.1.0-alpha.1/&lt;pkg&gt;-installer.sh <span class="p">|</span> sh</span></span></code></pre></div><p>正式 v0.1.0 出來之後再切回 <code>latest</code> URL。這是 zhtw-mcp issue #35 討論裡 hydai 提的折衷方案，能讓社群在 pipeline 完備前先試用、又不誤導不明就裡的使用者以為正式版已就位。</p>
<h2 id="zhtw-mcp-案例社群協作把-release-pipeline-搭起來">zhtw-mcp 案例：社群協作把 release pipeline 搭起來</h2>
<p>zhtw-mcp 的 <a href="https://github.com/sysprog21/zhtw-mcp/issues/35">issue #35</a> 跟 <a href="https://github.com/sysprog21/zhtw-mcp/pull/40">PR #40</a> 是這條搭建過程的活案例。整個討論的時間軸：</p>
<ol>
<li><strong>dlackty 提 issue #35</strong>：建議導入 cargo-dist + Homebrew、列出建議 targets、指出 <code>s2t_data.rs</code> 需要 pre-build hook</li>
<li><strong>作者 jserv 回應</strong>：認同方向，但坦承自己 Rust 經驗有限、這個專案部分目的就是為了學 Rust 生態，邀請社群提 PR 推進</li>
<li><strong>hydai 開 PR #40</strong>：第一次用 cargo-dist，自己也在學，誠實表示「想知道方向對不對，希望熟手能接手」，並引用自己之前用 knope 手刻 release 的另一個 repo 作為對照</li>
<li><strong>jserv 提到 installer URL 失效</strong>：README 已經寫了 <code>releases/latest/download/...</code>，但還沒有正式 release，建議用 pre-release 給 early adopter</li>
<li><strong>hydai 提議 <code>v0.1.0-alpha.1</code></strong>：作為 early adopter 通道、提醒 prerelease 沒有 latest alias、要用完整 tag URL</li>
</ol>
<p>這個討論留下幾個值得學的點：</p>
<ul>
<li><strong>公開承認還在學是好事</strong>：jserv 直接說「我 Rust 經驗有限、我也在學」、hydai 說「我第一次用 cargo-dist」，這比假裝專家有效率多了。社群協作的核心是大家都看到同一個未完成狀態、一起補。</li>
<li><strong>README 先寫安裝命令再補 release 是常見順序</strong>：把 release 路線當作目標釘出來、再倒推實作，是刻意的設計。先寫文件再補 pipeline 的順序也讓 issue #35 / PR #40 更容易聚焦。</li>
<li><strong>特殊 build hook 是 cargo-dist 的明確支援點</strong>：zhtw-mcp 需要在編譯前跑 <code>gen-s2t-tables.py</code> 產生 <code>s2t_data.rs</code>，這正好是 <code>github-build-setup</code> 設計給的場景。如果你的 repo 有類似「編譯前要產生程式碼／下載資料」的需求、不必為此放棄 cargo-dist。</li>
<li><strong>Pre-release 是 pipeline 學習期的合理工具</strong>：先用 <code>v0.1.0-alpha.1</code> 把 pipeline 跑通、把問題暴露出來，比等到一切完美才發版更有效率。</li>
</ul>
<p>跟著這個 issue 串看完一輪、可以得到一個從零搭 cargo-dist 的真實參照框架，比官方文件更貼近實際遇到的問題。</p>
<h2 id="homebrew-通道cargo-dist-怎麼幫你出-formula">Homebrew 通道：cargo-dist 怎麼幫你出 formula</h2>
<p><code>brew install</code> 是 macOS 使用者最熟的安裝路線，但 Homebrew 有兩種發版形式：</p>
<table>
  <thead>
      <tr>
          <th>形式</th>
          <th>怎麼裝</th>
          <th>維護成本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Homebrew core</td>
          <td><code>brew install &lt;pkg&gt;</code></td>
          <td>高 — 要過 homebrew-core 的 PR review，門檻嚴</td>
      </tr>
      <tr>
          <td>Homebrew tap</td>
          <td><code>brew install &lt;user&gt;/&lt;tap&gt;/&lt;pkg&gt;</code></td>
          <td>低 — 在自己的 GitHub repo <code>homebrew-&lt;tap&gt;</code> 放 formula</td>
      </tr>
  </tbody>
</table>
<p>cargo-dist 預設支援的是後者（tap）。設定方式是在 <code>[workspace.metadata.dist]</code> 加：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">installers</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;shell&#34;</span><span class="p">,</span> <span class="s2">&#34;powershell&#34;</span><span class="p">,</span> <span class="s2">&#34;homebrew&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">tap</span> <span class="p">=</span> <span class="s2">&#34;&lt;your-github-username&gt;/homebrew-&lt;tap-name&gt;&#34;</span></span></span></code></pre></div><p>然後在 GitHub 開一個叫 <code>homebrew-&lt;tap-name&gt;</code> 的 repo（命名規則是 Homebrew 強制的），cargo-dist 會在每次 release 自動 push 一個更新過的 formula 到那個 repo。下游使用者只要：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">brew tap &lt;your-github-username&gt;/&lt;tap-name&gt;
</span></span><span class="line"><span class="ln">2</span><span class="cl">brew install &lt;pkg&gt;</span></span></code></pre></div><p>要走 homebrew-core 是另一個層級的事 — 需要套件夠成熟、有穩定使用者基數、有清楚的 license、過 homebrew-core maintainer 的 review。多數新專案先做 tap、累積使用者跟成熟度後再考慮 core。</p>
<h2 id="上線前的最後檢查">上線前的最後檢查</h2>
<p>第一個正式 v0.1.0 推出去之前最後跑一遍：</p>
<ul>
<li><input disabled="" type="checkbox"> Prerelease tag（<code>v0.1.0-alpha.1</code> 之類）跑過 release.yml、cross-compile matrix 全綠</li>
<li><input disabled="" type="checkbox"> 從乾淨機器跑 README 寫的 installer 命令、從下載到執行整條順</li>
<li><input disabled="" type="checkbox"> Pre-build hook（如果有）在所有 platform 都能跑、不依賴特定 OS</li>
<li><input disabled="" type="checkbox"> Preflight gate 的 <code>workflow_call</code> reuse 確實 block 住紅燈 main</li>
<li><input disabled="" type="checkbox"> README 的 installer URL 跟實際 asset 命名規則一致（cargo-dist 會用 <code>&lt;pkg&gt;-installer.sh</code>、不要寫成 <code>install.sh</code>）</li>
<li><input disabled="" type="checkbox"> Changelog 跟 tag 對齊（cargo-dist 會把 changelog 抓進 release notes）</li>
<li><input disabled="" type="checkbox"> 有提供可審計安裝路徑（下載 + checksum/provenance 驗證 + 執行）</li>
</ul>
<p>第一條 v0.1.0 推出去後 <code>releases/latest/download/...</code> alias 才會生效、那時就能把 README 改成 <code>latest</code> URL、徹底完成這條通道的搭建。</p>
<h2 id="來源與規格">來源與規格</h2>
<ul>
<li>cargo-dist 官方文件：<a href="https://opensource.axo.dev/cargo-dist/">https://opensource.axo.dev/cargo-dist/</a></li>
<li>cargo-dist GitHub Action / 生成流程：<a href="https://github.com/axodotdev/cargo-dist">https://github.com/axodotdev/cargo-dist</a></li>
<li>GitHub Releases 與 latest 行為：<a href="https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases">https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases</a></li>
<li>zhtw-mcp 案例 issue：<a href="https://github.com/sysprog21/zhtw-mcp/issues/35">https://github.com/sysprog21/zhtw-mcp/issues/35</a></li>
<li>zhtw-mcp 案例 PR：<a href="https://github.com/sysprog21/zhtw-mcp/pull/40">https://github.com/sysprog21/zhtw-mcp/pull/40</a></li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想理解整體 release 類型分類：回 <a href="../">Package / Library Release CI/CD</a>。</li>
<li>想理解 workflow_call 的反向 reuse：讀 <a href="../../ci-gate-workflow-boundary/">CI gate 與 workflow 邊界</a>。</li>
<li>想理解 release workflow 紅燈時的處理：讀 <a href="../../github-actions-failure-flow/">CI 失敗到修復發布流程</a>。</li>
<li>想理解 artifact 可信度：讀 <a href="/blog/backend/knowledge-cards/artifact-provenance/" data-link-title="Artifact Provenance" data-link-desc="說明交付物的來源、完整性與簽章關聯如何建立信任">Artifact Provenance</a>。</li>
</ul>
]]></content:encoded></item></channel></rss>