Skip to content

Commit b6bfa67

Browse files
committed
homebrew tap for mcpp: prebuilt release formula + automated bump
Formula/mcpp-m.rb ships the same prebuilt artifacts install.sh downloads, verified against upstream's .sha256 sidecars. Two things this repo exists to get right: - The formula installs the self-contained tree under libexec and puts a launcher on PATH. mcpp derives MCPP_HOME from the canonicalized path of the running binary, so a plain symlink into bin would make the Cellar version directory its home -- registry, caches and every downloaded toolchain would be discarded by the next 'brew upgrade'. - The name. homebrew-core owns 'mcpp' (Matsui's C preprocessor), so the formula is mcpp-m, with Aliases/{mcpp,mcpp-bin} pointing here. Same collision, same resolution as upstream's AUR packages.
0 parents  commit b6bfa67

8 files changed

Lines changed: 620 additions & 0 deletions

File tree

.github/workflows/bump-formula.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: bump-formula
2+
3+
# Keep Formula/mcpp-m.rb pointed at the newest mcpp release.
4+
#
5+
# Three ways in, deliberately:
6+
# repository_dispatch — upstream's release pipeline pings us the moment a
7+
# release finishes (needs a PAT on the upstream side).
8+
# schedule — daily catch-up, so the tap self-heals even with no
9+
# token configured upstream. This is the safety net,
10+
# not the primary path.
11+
# workflow_dispatch — manual pin / re-run.
12+
#
13+
# All three converge on scripts/update-formula.sh, which reads the sha256
14+
# sidecars upstream publishes and refuses to write a half-rewritten formula.
15+
16+
on:
17+
repository_dispatch:
18+
types: [mcpp-release]
19+
workflow_dispatch:
20+
inputs:
21+
version:
22+
description: "mcpp version to pin (blank = upstream latest)"
23+
required: false
24+
schedule:
25+
- cron: "23 4 * * *"
26+
27+
permissions:
28+
contents: write
29+
30+
concurrency:
31+
group: bump-formula
32+
cancel-in-progress: false
33+
34+
jobs:
35+
bump:
36+
runs-on: ubuntu-latest
37+
steps:
38+
- uses: actions/checkout@v4
39+
40+
- name: Resolve target version
41+
id: ver
42+
run: |
43+
# Empty on the schedule trigger — update-formula.sh then resolves
44+
# upstream's latest release itself.
45+
V="${{ github.event.client_payload.version }}"
46+
[ -n "$V" ] || V="${{ github.event.inputs.version }}"
47+
echo "version=${V}" >> "$GITHUB_OUTPUT"
48+
echo ":: requested version: ${V:-<latest>}"
49+
50+
- name: Update the formula
51+
env:
52+
GH_TOKEN: ${{ github.token }}
53+
run: ./scripts/update-formula.sh ${{ steps.ver.outputs.version }}
54+
55+
- name: Commit and push
56+
run: |
57+
set -eu
58+
if git diff --quiet -- Formula; then
59+
echo ":: formula already current — nothing to commit"
60+
exit 0
61+
fi
62+
VER=$(sed -nE 's/^[[:space:]]*version "([^"]+)".*/\1/p' Formula/mcpp-m.rb | head -1)
63+
git config user.name "mcpp-ci"
64+
git config user.email "x.d2learn.org@gmail.com"
65+
git commit -qam "mcpp ${VER}"
66+
git push
67+
echo ":: pushed mcpp ${VER}"
68+
echo "Bumped the formula to **${VER}**." >> "$GITHUB_STEP_SUMMARY"

.github/workflows/ci.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: ci
2+
3+
# Cheap structural checks. A tap's real test is `brew install`, which needs a
4+
# macOS runner and downloads a toolchain — too heavy to run per push, so it
5+
# stays a manual `brew install --verbose --debug` before publishing changes.
6+
7+
on:
8+
push:
9+
branches: [main]
10+
pull_request:
11+
12+
jobs:
13+
lint:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Formula parses as Ruby
19+
run: ruby -c Formula/*.rb
20+
21+
- name: Shell scripts lint clean
22+
run: shellcheck scripts/*.sh
23+
24+
- name: Aliases resolve to a real formula
25+
run: |
26+
set -eu
27+
for a in Aliases/*; do
28+
target=$(readlink "$a") || { echo "error: $a is not a symlink"; exit 1; }
29+
[ -f "Aliases/$target" ] || { echo "error: $a -> $target is dangling"; exit 1; }
30+
echo ":: $a -> $target"
31+
done

Aliases/mcpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../Formula/mcpp-m.rb

Aliases/mcpp-bin

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../Formula/mcpp-m.rb

Formula/mcpp-m.rb

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# mcpp — modern C++23 build & package management tool.
2+
#
3+
# This formula ships the SAME prebuilt release artifacts that upstream's
4+
# `install.sh` one-liner downloads (mcpp-community/mcpp → release.yml).
5+
#
6+
# Why `mcpp-m` and not `mcpp`: homebrew-core already owns the name `mcpp`
7+
# (Matsui's C preprocessor). Same collision as Arch, where upstream ships
8+
# `mcpp-bin` / `mcpp-m`. `Aliases/` maps both of those spellings here, so
9+
# `brew install mcpp-community/mcpp/mcpp` works too once the tap is present.
10+
#
11+
# version / url / sha256 are rewritten by scripts/update-formula.sh — the
12+
# bump workflow owns those three lines, so don't hand-edit them.
13+
class McppM < Formula
14+
desc "Modern C++23 build and package management tool (module-first)"
15+
homepage "https://github.com/mcpp-community/mcpp"
16+
version "2026.7.30.3"
17+
license "Apache-2.0"
18+
19+
# mcpp keeps its package index in sync over git.
20+
depends_on "git"
21+
22+
on_macos do
23+
# Upstream publishes an arm64-only macOS artifact, built with minos 14.0
24+
# (release.yml asserts it). Intel Macs have no asset — build from source
25+
# via `xlings install mcpp` or the install.sh one-liner instead.
26+
depends_on arch: :arm64
27+
depends_on macos: :sonoma
28+
29+
url "https://github.com/mcpp-community/mcpp/releases/download/v2026.7.30.3/mcpp-2026.7.30.3-macosx-arm64.tar.gz"
30+
sha256 "1659be17b3f54c495c224179a570dd220fcfead25bbcda0c6279b20afa6a4e18"
31+
end
32+
33+
on_linux do
34+
on_intel do
35+
url "https://github.com/mcpp-community/mcpp/releases/download/v2026.7.30.3/mcpp-2026.7.30.3-linux-x86_64.tar.gz"
36+
sha256 "115cc537b17bfcaf5b5d13e0c7fcf9a591864cb6f18d10244e3828cfc1f8e1c4"
37+
end
38+
on_arm do
39+
url "https://github.com/mcpp-community/mcpp/releases/download/v2026.7.30.3/mcpp-2026.7.30.3-linux-aarch64.tar.gz"
40+
sha256 "aff78e9e04d0dfc09ef828b9a5aad3e1e4afff19de47b493a88bbbf803a00d14"
41+
end
42+
end
43+
44+
livecheck do
45+
url :homepage
46+
strategy :github_latest
47+
end
48+
49+
def install
50+
# The release tarball is a self-contained tree. Keep it whole under
51+
# libexec and put a launcher on PATH — see the wrapper comment below for
52+
# why a plain `bin.install` would break every write mcpp makes.
53+
libexec.install "bin"
54+
libexec.install "registry" if File.directory?("registry")
55+
56+
# mcpp WRITES at runtime: registry sandbox, BMI/metadata caches, logs and
57+
# every toolchain it downloads (hundreds of MB). It resolves MCPP_HOME
58+
# from the running binary's *real* path (canonical, symlinks resolved), so
59+
# a bare symlink into the Cellar would make MCPP_HOME the Cellar version
60+
# dir — `brew upgrade` would then silently drop every installed toolchain.
61+
# Pin the two env knobs the binary honors, deferring to values the user
62+
# already exported. Mirrors the Arch launcher (upstream scripts/aur).
63+
(bin/"mcpp").write <<~SH
64+
#!/bin/sh
65+
export MCPP_HOME="${MCPP_HOME:-$HOME/.mcpp}"
66+
export MCPP_VENDORED_XLINGS="${MCPP_VENDORED_XLINGS:-#{libexec}/registry/bin/xlings}"
67+
exec "#{libexec}/bin/mcpp" "$@"
68+
SH
69+
chmod 0755, bin/"mcpp"
70+
71+
prefix.install "LICENSE" if File.exist?("LICENSE")
72+
doc.install "README.md" if File.exist?("README.md")
73+
end
74+
75+
def caveats
76+
<<~EOS
77+
mcpp keeps per-user state in ~/.mcpp (registry, caches, toolchains).
78+
Override with MCPP_HOME=<dir>; remove ~/.mcpp to reset.
79+
80+
The first `mcpp build` downloads a toolchain (LLVM on macOS) and may
81+
take a while. It is cached in ~/.mcpp and survives `brew upgrade`.
82+
EOS
83+
end
84+
85+
test do
86+
assert_match version.to_s, shell_output("#{bin}/mcpp --version")
87+
assert_match "Usage:", shell_output("#{bin}/mcpp --help")
88+
89+
# The launcher must pin MCPP_HOME outside the Cellar, or `brew upgrade`
90+
# would take the user's toolchains with it. Asserted statically: the
91+
# commands that would print the resolved home (`mcpp self env`) bootstrap
92+
# the sandbox over the network, which has no place in a formula test.
93+
launcher = (bin/"mcpp").read
94+
assert_match(/MCPP_HOME="\$\{MCPP_HOME:-\$HOME\/\.mcpp\}"/, launcher)
95+
assert_predicate libexec/"bin/mcpp", :executable?
96+
end
97+
end

0 commit comments

Comments
 (0)