From e956a6039cd4067c3f55d1cafb7d4295d5848909 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Fri, 31 Jul 2026 08:03:00 +0800 Subject: [PATCH] ci: publish to a Homebrew tap after each release (#313) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds Homebrew as a distribution channel. The formula lives in a dedicated tap repo, mcpp-community/homebrew-mcpp; this side only pings it. brew install mcpp-community/mcpp/mcpp-m One command: a tap whose repo is named homebrew- is cloned on demand by `brew install //`, so no separate `brew tap` step is needed. That auto-tap is also why the formula does not live in this repo — tapping a repo that isn't named homebrew-* requires the user to pass its URL, which turns installation into two commands and a URL nobody will remember. Two things the tap had to get right, both learned from scripts/aur: - mcpp derives MCPP_HOME from the *canonicalized* path of the running binary (home.cppm + platform/fs.cppm resolve symlinks). Linked straight into bin, that path is the Cellar, so the registry sandbox, caches and every downloaded toolchain would land in a versioned directory that `brew upgrade` deletes. The formula keeps the release tree under libexec and ships a launcher that pins MCPP_HOME/MCPP_VENDORED_XLINGS, same shape as scripts/aur/mcpp-bin/mcpp.sh. - The name. homebrew-core owns `mcpp` (Matsui's C preprocessor, ~675 installs/yr, not deprecated), exactly as extra/mcpp does on Arch. The formula is `mcpp-m`, with Aliases/{mcpp,mcpp-bin} pointing at it; the installed command is still `mcpp`. The workflow does not write the formula: it sends a repository_dispatch and the tap rewrites itself from the release's .sha256 sidecars. It hangs off `workflow_run: [release] completed` rather than `release: published` for the reason aur-publish.yml already documents — the macOS and aarch64 assets are uploaded by later jobs, and the tap needs every sidecar to exist. HOMEBREW_TAP_TOKEN is optional by design: without it this job logs a notice and exits 0, and the tap's daily schedule picks the release up within 24h. A release should not fail over a credential it doesn't itself need. --- .agents/skills/mcpp-release/SKILL.md | 18 ++++++ .github/workflows/homebrew-publish.yml | 83 ++++++++++++++++++++++++++ README.md | 20 ++++++- README.zh-CN.md | 20 ++++++- 4 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/homebrew-publish.yml diff --git a/.agents/skills/mcpp-release/SKILL.md b/.agents/skills/mcpp-release/SKILL.md index 60d44656..9be37e0f 100644 --- a/.agents/skills/mcpp-release/SKILL.md +++ b/.agents/skills/mcpp-release/SKILL.md @@ -231,6 +231,24 @@ git commit -am "ci: workspace mcpp bootstrap pin -> $NEW_VERSION (released, mirr 资产 CDN 上可能还要几分钟才更新。紧接着跑的 CI 可能仍拿到旧索引并报 `package 'mcpp@X.Y.Z' not found` —— 这不是 release 坏了,等指针稳定后重跑即可。 +### 下游分发渠道(都是自动的,只需核验) + +两条渠道都挂在 `release` workflow 的 `workflow_run: completed` 上,不需要人工推: + +| 渠道 | workflow | 核验 | +|------|----------|------| +| AUR (`mcpp-bin` / `mcpp-m`) | `.github/workflows/aur-publish.yml` | `gh run list --workflow aur-publish.yml --limit 1` | +| Homebrew tap (`mcpp-m`) | `.github/workflows/homebrew-publish.yml` → ping [`mcpp-community/homebrew-mcpp`](https://github.com/mcpp-community/homebrew-mcpp) | `gh api repos/mcpp-community/homebrew-mcpp/contents/Formula/mcpp-m.rb --jq .content \| base64 -d \| grep '^ version'` | + +Homebrew 那条**不写公式**,只发一个 `repository_dispatch`;公式重写由 tap 仓库自己的 +`bump-formula.yml` 完成(它读 release 的 `.sha256` 边车)。这条 ping 依赖仓库 secret +`HOMEBREW_TAP_TOKEN`;**没配也不会让发布失败** —— tap 有每日 schedule 兜底,24h 内自己跟上。 +想立刻跟上就手动触发一次: + +```bash +gh workflow run bump-formula.yml -R mcpp-community/homebrew-mcpp +``` + ### 常见失败原因 | 症状 | 原因 | 修复 | diff --git a/.github/workflows/homebrew-publish.yml b/.github/workflows/homebrew-publish.yml new file mode 100644 index 00000000..858e6e49 --- /dev/null +++ b/.github/workflows/homebrew-publish.yml @@ -0,0 +1,83 @@ +name: homebrew-publish + +# Tell the Homebrew tap (mcpp-community/homebrew-mcpp) that a release is out. +# +# The tap owns the formula rewrite — it reads the .sha256 sidecars from the +# release and commits the new url/version itself. All this workflow does is +# fire the starting gun, so nothing here needs to know what a formula is. +# +# Triggers on COMPLETION of the `release` workflow rather than on +# `release: published`, for the same reason aur-publish.yml does: release.yml +# creates the GitHub Release in its first job but uploads the macOS / aarch64 +# assets in later jobs, and the tap needs every sidecar to exist. +# +# Requires one repository secret: +# HOMEBREW_TAP_TOKEN — fine-grained PAT scoped to mcpp-community/homebrew-mcpp +# with "Contents: read and write" (repository_dispatch +# is a write-level API). +# +# The secret is OPTIONAL. Without it this workflow logs a notice and exits 0; +# the tap runs the same bump on a daily schedule, so a missing token costs +# freshness (up to 24h), not correctness. That keeps a release from failing +# over a credential the release itself doesn't need. + +on: + workflow_run: + workflows: [release] + types: [completed] + workflow_dispatch: + inputs: + version: + description: "Version to publish (default: [package].version in mcpp.toml)" + required: false + +concurrency: + group: homebrew-publish + cancel-in-progress: false + +jobs: + notify-tap: + runs-on: ubuntu-latest + # On the workflow_run trigger, only proceed if the release actually + # succeeded (skip failed/cancelled release runs). + if: >- + github.event_name == 'workflow_dispatch' || + github.event.workflow_run.conclusion == 'success' + steps: + - name: Checkout released commit + uses: actions/checkout@v4 + with: + # workflow_run: the exact commit the release was built from. + # workflow_dispatch: default ref (HEAD of the branch). + ref: ${{ github.event.workflow_run.head_sha || github.ref }} + + - name: Resolve version + id: resolve + run: | + VER="${{ github.event.inputs.version }}" + if [ -z "$VER" ]; then + # mcpp.toml at the released commit carries the right version. + VER=$(grep -m1 -E '^\s*version\s*=' mcpp.toml | sed -E 's/.*"([^"]+)".*/\1/') + fi + [ -n "$VER" ] || { echo "cannot resolve version"; exit 1; } + echo "version=$VER" >> "$GITHUB_OUTPUT" + echo ":: version $VER" + + - name: Ping the tap + env: + TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + VER: ${{ steps.resolve.outputs.version }} + run: | + set -eu + if [ -z "${TAP_TOKEN}" ]; then + echo "::notice::HOMEBREW_TAP_TOKEN is not configured; skipping the ping. mcpp-community/homebrew-mcpp bumps itself on a daily schedule, so ${VER} reaches the tap within 24h." + exit 0 + fi + curl -fsS -X POST \ + -H "Authorization: Bearer ${TAP_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/mcpp-community/homebrew-mcpp/dispatches \ + -d "{\"event_type\":\"mcpp-release\",\"client_payload\":{\"version\":\"${VER}\"}}" + echo ":: dispatched mcpp-release ${VER} to mcpp-community/homebrew-mcpp" + echo "Pinged the Homebrew tap for **${VER}**." >> "$GITHUB_STEP_SUMMARY" diff --git a/README.md b/README.md index 316c17a0..b3351cf3 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,23 @@ Installs into `~/.mcpp/` and adds it to your shell PATH. Deleting `~/.mcpp` unin
-Option 2 — Arch Linux (AUR) +Option 2 — Homebrew (macOS / Linux) + +```bash +brew install mcpp-community/mcpp/mcpp-m +``` + +One command — it taps [`mcpp-community/homebrew-mcpp`](https://github.com/mcpp-community/homebrew-mcpp) +and installs the same prebuilt release binary. macOS needs Apple silicon + +macOS 14; per-user data lives in `~/.mcpp/`. + +Homebrew's `mcpp` is an unrelated C preprocessor, hence the `mcpp-m` formula +name — the command it installs is still `mcpp`. + +
+ +
+Option 3 — Arch Linux (AUR) ```bash yay -S mcpp-bin # prebuilt release binary @@ -95,7 +111,7 @@ On Arch the name `mcpp` is an unrelated C preprocessor, so the packages are
-Option 3 — let an AI assistant install it for you +Option 4 — let an AI assistant install it for you Copy the following prompt to your AI coding assistant (Claude Code / Cursor / Copilot, etc.): diff --git a/README.zh-CN.md b/README.zh-CN.md index 8271378f..db1601b4 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -81,7 +81,23 @@ curl -fsSL https://github.com/mcpp-community/mcpp/releases/latest/download/insta
-方式 2 — Arch Linux(AUR) +方式 2 — Homebrew(macOS / Linux) + +```bash +brew install mcpp-community/mcpp/mcpp-m +``` + +一条命令即可,会自动 tap [`mcpp-community/homebrew-mcpp`](https://github.com/mcpp-community/homebrew-mcpp) +并安装同一份预编译 release 二进制。macOS 需要 Apple 芯片 + macOS 14; +每个用户的数据仍在各自的 `~/.mcpp/`。 + +Homebrew 上 `mcpp` 属于一个无关的 C 预处理器,所以公式名是 `mcpp-m`, +装出来的命令仍然是 `mcpp`。 + +
+ +
+方式 3 — Arch Linux(AUR) ```bash yay -S mcpp-bin # 预编译 release 二进制 @@ -95,7 +111,7 @@ Arch 上 `mcpp` 这个名字属于一个无关的 C 预处理器,所以包名
-方式 3 — 让 AI 助手帮你安装 +方式 4 — 让 AI 助手帮你安装 将以下提示词复制给你的 AI 编码助手(Claude Code / Cursor / Copilot 等):