Skip to content

ci: publish to a Homebrew tap after each release (#313) - #322

Merged
speak-agent merged 1 commit into
mainfrom
ci/homebrew-tap
Jul 31, 2026
Merged

ci: publish to a Homebrew tap after each release (#313)#322
speak-agent merged 1 commit into
mainfrom
ci/homebrew-tap

Conversation

@speak-agent

@speak-agent speak-agent commented Jul 31, 2026

Copy link
Copy Markdown
Member

Closes part of #313 (the "增加 Homebrew 作为分发渠道" item).

What this adds

brew install mcpp-community/mcpp/mcpp-m

One command — brew install <user>/<repo>/<formula> auto-taps a repo named
homebrew-<repo>, so there is no separate brew tap step.

The formula itself lives in a new dedicated tap repo,
mcpp-community/homebrew-mcpp
(already created and working). This PR is the upstream half: a workflow that
pings the tap after a release, plus docs.

Why the formula is not in this repo

Homebrew only scans three locations in a tap (tap.rb:950): Formula/,
HomebrewFormula/, repo root — scripts/ is not one of them. And a tap whose
repo isn't named homebrew-* can only be tapped by passing its URL, which
makes installation two commands instead of one and kills the auto-tap path.

Why mcpp-m and not mcpp

homebrew-core owns mcppMatsui's C preprocessor
(675 installs/yr, not deprecated, autobump on). Same collision as extra/mcpp
on Arch, same resolution as scripts/aur: the formula is mcpp-m, with
Aliases/{mcpp,mcpp-bin} pointing at it. The installed command is still mcpp.

Why a launcher, not a symlink

mcpp resolves MCPP_HOME from the canonicalized path of the running binary
(src/home.cppm:77 + src/platform/fs.cppm:97). Linked straight into bin,
that path is the Cellar — the registry sandbox, caches and every downloaded
toolchain would live in a versioned directory that brew upgrade removes. The
formula keeps the tree under libexec and ships a launcher pinning
MCPP_HOME / MCPP_VENDORED_XLINGS, mirroring scripts/aur/mcpp-bin/mcpp.sh.

How the sync works

homebrew-publish.yml never writes a formula. It sends a
repository_dispatch and the tap rewrites itself from the release's .sha256
sidecars. Trigger is workflow_run: [release] completed, not
release: published — the macOS/aarch64 assets are uploaded by later jobs
(same reasoning aur-publish.yml documents).

Trigger on the tap side When
repository_dispatch this workflow, seconds after a release finishes
schedule (daily) catch-up when no token is configured
workflow_dispatch manual pin / re-run

Configuration needed

Repository secret HOMEBREW_TAP_TOKEN — fine-grained PAT scoped to
mcpp-community/homebrew-mcpp, permission Contents: read and write.

It is optional. Without it this job logs a notice and exits 0; the tap's
daily schedule lands the release within 24h. A release shouldn't fail over a
credential it doesn't itself need.

Verified

  • Tap repo created, formula + aliases + bump script pushed; symlink modes
    (120000) survived the push.
  • The bump workflow ran end to end on GitHub twice: pinned 2026.7.30.2 from
    a dispatch payload, then resolved and restored 2026.7.30.3 from upstream's
    latest release — commit and push both succeeded despite the org's read-only
    default GITHUB_TOKEN (the workflow declares permissions: contents: write).
  • update-formula.sh rewrite is byte-identical to the hand-written formula
    and idempotent on re-run; shellcheck clean; ruby -c OK.
  • The published sha256 was checked against the real macOS asset
    (1659be17…4e18), and the tarball layout matches what the formula installs.
  • check_version_pins.sh still OK; both workflow files parse.

Also verified by installing for real (throwaway Homebrew prefix, Linux x86_64):

$ brew install mcpp-community/mcpp/mcpp-m
Tapped 1 formula (20 files, 127.6KB).
==> Installing mcpp-m from mcpp-community/mcpp
🍺  .../Cellar/mcpp-m/2026.7.30.3: 8 files, 15.0MB

The auto-tap path works from cold (no prior brew tap), the layout comes out
as intended (bin/mcpp launcher, libexec/{bin,registry}), the launcher's
MCPP_HOME defaults to $HOME/.mcpp and not the Cellar, mcpp --version
prints mcpp 2026.7.30.3, and brew uninstall is clean. All four assertions
in the formula's test block were evaluated by hand against the installed keg
and pass. Fixing a bin.mkpath bug (Homebrew's Pathname#write does not
create parent directories) came out of this — it would have failed the very
first install.

Still unexercised: the macOS-specific lines (depends_on arch: :arm64 /
macos: :sonoma and the arm64 asset), which need an Apple-silicon host.

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-<name> is cloned on demand
by `brew install <user>/<repo>/<formula>`, 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.
@speak-agent
speak-agent merged commit f4a7568 into main Jul 31, 2026
15 checks passed
@speak-agent
speak-agent deleted the ci/homebrew-tap branch July 31, 2026 00:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants