Skip to content

Commit 8219584

Browse files
fix(macos): Homebrew 6 第三方 tap 信任门 —— 文档补 brew trust,fresh-install 加 brew 验证段 (#420)
## 报错 Refusing to load formula mcpp-community/mcpp/mcpp-m from untrusted tap mcpp-community/mcpp. ## 为什么一直没被发现 Homebrew 6 拒绝从未信任的第三方 tap 加载公式,但 `brew install <user>/<repo>/<formula>` 是豁免的 —— 它被读作显式意图。 README 文档化的正是这条全限定命令,**所以它一直是绿的**,而用户真正会敲的 其它每一种拼写都是坏的。 真机实测(macOS 14.8.7 / 15.7.7 / 26.5.2,Homebrew 6.0.5 / 6.0.12 / 6.0.13, 临时探针 PR #419): | 命令 | rc | |---|---| | `brew install mcpp-community/mcpp/mcpp-m`(未 tap / 已 tap) | 0 / 0 | | `brew install mcpp-m`(短名) | **1** | | `brew install mcpp-community/mcpp/mcpp`(alias) | **1** | | `brew trust mcpp-community/mcpp` | 0 | | trust 后:短名 / alias / upgrade / new+run | 全 0 | alias 本身没坏:报错里点名的是它解析到的 `mcpp-m`,被拒的是**信任**。 ## 改动 - README.md / README.zh-CN.md:安装段写清信任门与 `brew trust` 一行修法 - ci-fresh-install.yml 新增 `macos-brew-fresh` job ## 为什么是独立 job 而不是加进 macos-fresh 那个 job 已经有一份经 xlings 装的 mcpp 在 PATH 上,再 brew 装一份会让下面每 一条断言都说不清自己量的是哪个二进制。 它也**不需要 `wait-index`**:brew 直接装 GitHub release tarball,而 tap 的 bump 工作流比 release 晚几分钟 —— 在这里断言"刚发布的版本"会因为与 mcpp 无关 的原因变 flaky。版本断言改成**自洽**的:tapped 公式声明什么版本,装出来的二进制 就必须报什么版本。 ## 这个 job 从两侧钉住信任门 只断言"trust 后短名能用"是不够的 —— 那分不清"门被正确处理了"和"这个 Homebrew 根本没有门",而一个不可能失败的测试与没在跑的测试无从区分。所以先 断言**未信任时被拒、且拒绝理由确实是 untrusted tap**(不是别的新缺陷),再断言 trust 之后短名/alias/upgrade 全通。将来 Homebrew 撤掉这道门,这一步会变红并说明 原因,而不是继续假装在保护什么。 另加两条只有真实用户会碰到的判据:装完能 `mcpp new` + `mcpp run` 真跑起来 (过 launcher 的 MCPP_HOME/MCPP_VENDORED_XLINGS 钉法与工具链自举),以及 per-user 状态确实落在 ~/.mcpp 而不是 Cellar(否则 `brew upgrade` 会把用户装 的工具链一起带走)。 tap 侧同步:mcpp-community/homebrew-mcpp@e5bdc21(README 那句 "short forms work too" 与 alias 宣传都已改准,公式 caveats 补了 brew trust)。 Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent 92a2af5 commit 8219584

3 files changed

Lines changed: 153 additions & 0 deletions

File tree

.github/workflows/ci-fresh-install.yml

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,129 @@ jobs:
404404
mcpp clean
405405
mcpp run
406406
407+
# ──────────────────────────────────────────────────────────────────
408+
# macOS via HOMEBREW — the other fresh-install channel
409+
#
410+
# A SEPARATE JOB, not extra steps in macos-fresh: that job already has an
411+
# mcpp on PATH from xlings, and a second one from brew would make every
412+
# assertion below ambiguous about which binary it measured.
413+
#
414+
# It also does NOT need `wait-index`. Homebrew installs the GitHub release
415+
# tarball directly, and the tap's own bump workflow lags the release by
416+
# minutes — asserting the just-released version here would be racy for a
417+
# reason that has nothing to do with mcpp. The version assertion is instead
418+
# made SELF-CONSISTENT: whatever version the tapped formula declares is the
419+
# version the installed binary must report.
420+
#
421+
# WHAT THIS EXISTS TO CATCH (measured on macOS 14.8.7 / 15.7.7 / 26.5.2,
422+
# Homebrew 6.0.5 / 6.0.12 / 6.0.13):
423+
#
424+
# Homebrew 6 refuses to load a formula from an untrusted third-party tap.
425+
# `brew install <user>/<repo>/<formula>` is exempt — it reads as explicit
426+
# intent — so the one-liner in README.md kept passing while EVERY other
427+
# spelling a user reaches for was broken:
428+
#
429+
# brew install mcpp-m → exit 1, "untrusted tap"
430+
# brew install mcpp-community/mcpp/mcpp → exit 1, "untrusted tap"
431+
#
432+
# A CI job that only ran the documented one-liner would have reported this
433+
# channel healthy the entire time. So the trust gate itself is asserted
434+
# from BOTH sides: refused before `brew trust`, accepted after.
435+
macos-brew-fresh:
436+
name: macOS fresh install (Homebrew)
437+
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
438+
# Same floor as macos-fresh: the formula declares `depends_on macos:
439+
# :sonoma` + arm64, and macos-14 is the oldest image satisfying it.
440+
runs-on: macos-14
441+
timeout-minutes: 30
442+
steps:
443+
- name: Environment
444+
run: |
445+
echo "macOS $(sw_vers -productVersion) | $(uname -m) | $(brew --version | head -1)"
446+
447+
# ① The command README.md documents, on a machine that has never tapped.
448+
# Fully qualified, so Homebrew treats it as explicit intent.
449+
- name: "Documented one-liner installs"
450+
run: brew install mcpp-community/mcpp/mcpp-m
451+
452+
# ② The launcher is the whole point of the formula: mcpp WRITES at
453+
# runtime, so a bare symlink into the Cellar would make MCPP_HOME the
454+
# versioned Cellar dir and `brew upgrade` would drop every installed
455+
# toolchain. Assert the binary on PATH is brew's and reports the
456+
# version the FORMULA declares — not a hardcoded one, which would go
457+
# stale on every release.
458+
- name: "Installed binary is brew's, and agrees with the formula"
459+
run: |
460+
set -euo pipefail
461+
which mcpp
462+
case "$(which mcpp)" in
463+
"$(brew --prefix)"/bin/mcpp) ;;
464+
*) echo "FAIL: mcpp on PATH is not the brew one"; exit 1 ;;
465+
esac
466+
formula_version="$(brew info --json=v2 mcpp-community/mcpp/mcpp-m \
467+
| python3 -c 'import json,sys; print(json.load(sys.stdin)["formulae"][0]["versions"]["stable"])')"
468+
echo "formula declares: $formula_version"
469+
mcpp --version
470+
mcpp --version | grep -Fq "$formula_version"
471+
472+
# ③ THE TRUST GATE, from the failing side first.
473+
#
474+
# Asserted before the fix is applied, because "short form works" alone
475+
# cannot distinguish "the gate is handled" from "this Homebrew has no
476+
# gate" — and a test that cannot fail is indistinguishable from one
477+
# that is not running. If a future Homebrew drops the gate, this step
478+
# turns red and says so, rather than silently protecting nothing.
479+
- name: "Untrusted tap: short form is refused (states its own premise)"
480+
run: |
481+
set +e
482+
brew uninstall --force mcpp-m > /dev/null 2>&1
483+
out="$(brew install mcpp-m 2>&1)"; rc=$?
484+
echo "$out" | tail -5
485+
if [ $rc -eq 0 ]; then
486+
echo "NOTE: this Homebrew did not gate the tap — the trust step below"
487+
echo " is now a no-op and README's warning can be revisited."
488+
exit 0
489+
fi
490+
echo "$out" | grep -Fq 'untrusted tap' || {
491+
echo "FAIL: short-form install failed for some OTHER reason than the"
492+
echo " trust gate — that is a new bug, not the known one."
493+
exit 1
494+
}
495+
496+
# ④ …and from the working side. `brew trust` is what README tells users
497+
# to run, so it is what CI runs.
498+
- name: "brew trust unlocks the short form, the alias, and upgrade"
499+
run: |
500+
set -euo pipefail
501+
brew trust mcpp-community/mcpp
502+
brew uninstall --force mcpp-m
503+
brew install mcpp-m # short form
504+
brew uninstall --force mcpp-m
505+
brew install mcpp-community/mcpp/mcpp # the `mcpp` alias
506+
brew upgrade mcpp-m || true # no-op when current; must not be refused
507+
508+
# ⑤ The only assertion a user actually cares about: it builds and runs.
509+
# Exercises the launcher's MCPP_HOME/MCPP_VENDORED_XLINGS pinning,
510+
# the bundled xlings, and a real toolchain bootstrap.
511+
- name: "Real use: mcpp new → run (toolchain bootstrap)"
512+
run: |
513+
set -euo pipefail
514+
cd "$(mktemp -d)"
515+
mcpp new brewhello
516+
cd brewhello
517+
mcpp run | tee run.log
518+
grep -Fq 'Hello from brewhello' run.log
519+
520+
# ⑥ Per-user state must live outside the Cellar, or `brew upgrade` takes
521+
# the user's toolchains with it. §⑤ just created it — prove where.
522+
- name: "State lives in ~/.mcpp, not the Cellar"
523+
run: |
524+
set -euo pipefail
525+
test -d "$HOME/.mcpp"
526+
if find "$(brew --prefix)/Cellar/mcpp-m" -name 'toolchain*' -o -name 'xpkgs' 2>/dev/null | grep -q .; then
527+
echo "FAIL: toolchain state landed inside the Cellar"; exit 1
528+
fi
529+
407530
# ──────────────────────────────────────────────────────────────────
408531
# Windows WITH Visual Studio: llvm@20.1.7 + MSVC STL
409532
#

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,21 @@ macOS 14; per-user data lives in `~/.mcpp/`.
128128
Homebrew's `mcpp` is an unrelated C preprocessor, hence the `mcpp-m` formula
129129
name — the command it installs is still `mcpp`.
130130

131+
**Homebrew 6 gates third-party taps.** The fully-qualified command above is
132+
read as explicit intent and works as-is, but every *other* spelling — the short
133+
`brew install mcpp-m`, the `mcpp` alias, and later upgrades — is refused with:
134+
135+
```
136+
Refusing to load formula mcpp-community/mcpp/mcpp-m from untrusted tap
137+
mcpp-community/mcpp.
138+
```
139+
140+
Trust the tap once and all of them work:
141+
142+
```bash
143+
brew trust mcpp-community/mcpp
144+
```
145+
131146
</details>
132147

133148
<details>

README.zh-CN.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,21 @@ brew install mcpp-community/mcpp/mcpp-m
126126
Homebrew 上 `mcpp` 属于一个无关的 C 预处理器,所以公式名是 `mcpp-m`
127127
装出来的命令仍然是 `mcpp`
128128

129+
**Homebrew 6 对第三方 tap 加了信任门。** 上面那条全限定命令会被当作显式意图、
130+
可以直接用;但**其它任何拼写**——短名 `brew install mcpp-m``mcpp` 别名、
131+
以及之后的升级——都会被拒:
132+
133+
```
134+
Refusing to load formula mcpp-community/mcpp/mcpp-m from untrusted tap
135+
mcpp-community/mcpp.
136+
```
137+
138+
信任这个 tap 一次,它们就都能用了:
139+
140+
```bash
141+
brew trust mcpp-community/mcpp
142+
```
143+
129144
</details>
130145

131146
<details>

0 commit comments

Comments
 (0)