fix(brew): unbreak the generated homebrew formula install

Render Formula/omp.rb with two changes so the formula actually installs:

- Stamp `using: :nounzip` on every per-platform `url` stanza so Homebrew
  leaves the downloaded bare Mach-O/ELF asset in the staging CWD; the
  default `CurlDownloadStrategy` was routing through
  `UnpackStrategy::Uncompressed#extract_nestedly`, nesting the file
  outside CWD so `Dir["omp-*"].first` returned `nil` and
  `bin.install nil => "omp"` raised.
- Wrap `generate_completions_from_executable` in
  `with_env(HOME: buildpath)` so the popened binary's `os.homedir()`
  resolves into the writable staging dir instead of the real
  `/Users/<user>/.omp`, which Homebrew's sandbox profile denies read
  access to.

Also export `renderFormula` (gated by `import.meta.main`) and add a
unit test that pins the rendered stanza shape for future regressions.

Fixes #2398
This commit is contained in:
roboomp
2026-06-12 14:59:45 +00:00
parent 4e4130ecd9
commit 389add428b
3 changed files with 76 additions and 7 deletions
+1
View File
@@ -5,6 +5,7 @@
### Fixed
- Collab links now dot-join the room secret (`<roomId>.<key>`, `host/r/<roomId>.<key>`) instead of using a second `#`: RFC 3986 forbids a raw `#` inside a URL fragment, so macOS Foundation (behind terminal click-to-open) percent-encoded the browser deep link's second `#` to `%23` and the web client rejected the session. `/join`, `omp join`, and the web client still accept legacy `#`-joined links and leniently decode `%23`-mangled ones
- Fixed `brew install can1357/tap/omp` failing on macOS with `sandbox-exec … exited with 1`: the generated `Formula/omp.rb` (rendered by `scripts/ci-update-brew-formula.ts`) now stamps `using: :nounzip` on every per-platform `url` so Homebrew leaves the bare Mach-O/ELF asset in the staging CWD (the previous `Dir["omp-*"].first` returned `nil` because `UnpackStrategy::Uncompressed#extract_nestedly` nested the file outside it), and wraps `generate_completions_from_executable` in `with_env(HOME: buildpath)` so the popened binary's `~/.omp` lookup goes to the writable staging dir instead of the sandbox-denied real home ([#2398](https://github.com/can1357/oh-my-pi/issues/2398))
## [15.12.1] - 2026-06-12
+50
View File
@@ -0,0 +1,50 @@
import { describe, expect, it } from "bun:test";
import { renderFormula } from "./ci-update-brew-formula";
const SUMS = {
"omp-darwin-arm64": "darwin_arm64_sha",
"omp-darwin-x64": "darwin_x64_sha",
"omp-linux-arm64": "linux_arm64_sha",
"omp-linux-x64": "linux_x64_sha",
};
describe("renderFormula", () => {
const formula = renderFormula("15.12.1", SUMS);
// Regression: bare-binary URLs must opt out of Homebrew's UnpackStrategy.
// Without `using: :nounzip` the default CurlDownloadStrategy nests the file
// outside the staging CWD, `Dir["omp-*"].first` returns `nil`, and
// `bin.install nil => "omp"` raises (issue #2398).
it("attaches `using: :nounzip` to every per-platform url stanza", () => {
const matches = formula.match(/using: :nounzip/g) ?? [];
expect(matches).toHaveLength(4);
for (const arch of ["omp-darwin-arm64", "omp-darwin-x64", "omp-linux-arm64", "omp-linux-x64"]) {
expect(formula).toMatch(
new RegExp(
`url "https://github\\.com/[^"]+/${arch}",\\s+using: :nounzip\\s+sha256 "${SUMS[arch as keyof typeof SUMS]}"`,
),
);
}
});
// Regression: completions generation must run with HOME redirected so the
// popened binary doesn't touch the real `~/.omp` (denied by Homebrew's
// sandbox profile) during the build (issue #2398).
it("wraps `generate_completions_from_executable` with a HOME redirect to buildpath", () => {
expect(formula).toMatch(
/with_env\(HOME: buildpath\) do\n\s+generate_completions_from_executable\(bin\/"omp", "completions", shells: \[:bash, :zsh, :fish\]\)\n\s+end/,
);
// And the bare form (which is what failed in the sandbox) must not appear
// outside the `with_env` block.
const blockless = formula.replace(/with_env\(HOME: buildpath\) do[\s\S]*?end/, "");
expect(blockless).not.toMatch(/generate_completions_from_executable/);
});
it("emits the expected per-asset sha256 next to each url", () => {
for (const name in SUMS) {
const sha = SUMS[name as keyof typeof SUMS];
expect(formula).toContain(`/${name}",`);
expect(formula).toContain(`sha256 "${sha}"`);
}
});
});
+24 -6
View File
@@ -54,7 +54,17 @@ function sha256For(assets: readonly ReleaseAsset[], name: string): string {
// `${...}` is JS interpolation; the literal `#{version}` / `#{bin}` below are
// Ruby interpolations Homebrew resolves when it evaluates the formula.
function renderFormula(version: string, sums: Record<string, string>): string {
export function renderFormula(version: string, sums: Record<string, string>): string {
// Each `url` carries `using: :nounzip` because the release assets are bare
// Mach-O/ELF executables, not archives. Without it Homebrew's default
// CurlDownloadStrategy routes through UnpackStrategy::Uncompressed#extract_nestedly,
// which nests the file outside the staging CWD; `Dir["omp-*"].first` then
// returns `nil` and `bin.install nil => "omp"` raises.
//
// `with_env(HOME: buildpath)` redirects the CLI's `os.homedir()` lookup to
// the writable staging dir so `generate_completions_from_executable` does
// not touch the real `/Users/<user>/.omp` (denied by Homebrew's sandbox
// profile, which would otherwise fail the popen).
return `class Omp < Formula
desc "${DESC}"
homepage "${HOMEPAGE}"
@@ -63,22 +73,26 @@ function renderFormula(version: string, sums: Record<string, string>): string {
on_macos do
on_arm do
url "https://github.com/${REPO}/releases/download/v#{version}/omp-darwin-arm64"
url "https://github.com/${REPO}/releases/download/v#{version}/omp-darwin-arm64",
using: :nounzip
sha256 "${sums["omp-darwin-arm64"]}"
end
on_intel do
url "https://github.com/${REPO}/releases/download/v#{version}/omp-darwin-x64"
url "https://github.com/${REPO}/releases/download/v#{version}/omp-darwin-x64",
using: :nounzip
sha256 "${sums["omp-darwin-x64"]}"
end
end
on_linux do
on_arm do
url "https://github.com/${REPO}/releases/download/v#{version}/omp-linux-arm64"
url "https://github.com/${REPO}/releases/download/v#{version}/omp-linux-arm64",
using: :nounzip
sha256 "${sums["omp-linux-arm64"]}"
end
on_intel do
url "https://github.com/${REPO}/releases/download/v#{version}/omp-linux-x64"
url "https://github.com/${REPO}/releases/download/v#{version}/omp-linux-x64",
using: :nounzip
sha256 "${sums["omp-linux-x64"]}"
end
end
@@ -86,8 +100,10 @@ function renderFormula(version: string, sums: Record<string, string>): string {
def install
bin.install Dir["omp-*"].first => "omp"
(bin/"omp").chmod 0555
with_env(HOME: buildpath) do
generate_completions_from_executable(bin/"omp", "completions", shells: [:bash, :zsh, :fish])
end
end
test do
assert_match version.to_s, shell_output("#{bin}/omp --version")
@@ -114,4 +130,6 @@ async function main(): Promise<void> {
}
}
await main();
if (import.meta.main) {
await main();
}