diff --git a/package.json b/package.json index 3e309e970..65cfd7d67 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "build:native": "bun --cwd=packages/natives run build", "test": "bun scripts/ci-test-ts.ts local", "test:ts": "bun scripts/ci-test-ts.ts local-ts", - "test:scripts": "bun test scripts/ci-concurrency.test.ts scripts/ci-release-notes.test.ts scripts/fix-dts-extensions.test.ts scripts/link-omp.test.ts", + "test:scripts": "bun test scripts/ci-build-native.test.ts scripts/ci-concurrency.test.ts scripts/ci-release-build-binaries.test.ts scripts/ci-release-notes.test.ts scripts/fix-dts-extensions.test.ts scripts/link-omp.test.ts", "test:rs": "bun scripts/run-rs-task.ts test:rs", "check": "bun run --parallel check:ts check:rs", "check:ts": "bun run check:tools && bun run --workspaces --if-present check", diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 442d2c126..691736d82 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed native Windows binary installs on older Windows 10 CPUs by building the generic `omp-windows-x64.exe` release asset with Bun's baseline x64 runtime instead of the AVX2-only modern target. ([#5172](https://github.com/can1357/oh-my-pi/issues/5172)) + ## [16.4.3] - 2026-07-11 ### Added diff --git a/packages/coding-agent/scripts/build-binary.ts b/packages/coding-agent/scripts/build-binary.ts index c5659d02b..221ade91c 100644 --- a/packages/coding-agent/scripts/build-binary.ts +++ b/packages/coding-agent/scripts/build-binary.ts @@ -7,14 +7,16 @@ import { compileCodingAgent } from "./compile-binary"; const packageDir = path.join(import.meta.dir, ".."); const repoRoot = path.join(packageDir, "..", ".."); -interface CrossBuild { +/** Binary cross-compilation settings selected by `CROSS_TARGET`. */ +export interface CrossBuild { readonly id: string; readonly platform: string; readonly arch: string; readonly target: Bun.Build.CompileTarget; } -function resolveCrossBuild(value: string | undefined): CrossBuild | null { +/** Resolves a CROSS_TARGET value to the Bun compile target used by local binary builds. */ +export function resolveCrossBuild(value: string | undefined): CrossBuild | null { switch (value) { case undefined: case "": @@ -29,16 +31,12 @@ function resolveCrossBuild(value: string | undefined): CrossBuild | null { return { id: value, platform: "linux", arch: "x64", target: "bun-linux-x64-baseline" }; case "win32-x64": case "windows-x64": - return { id: value, platform: "win32", arch: "x64", target: "bun-windows-x64-modern" }; + return { id: value, platform: "win32", arch: "x64", target: "bun-windows-x64-baseline" }; default: throw new Error(`Unsupported CROSS_TARGET: ${value}`); } } -const crossBuild = resolveCrossBuild(Bun.env.CROSS_TARGET); -const outName = crossBuild ? `omp-${crossBuild.id}` : "omp"; -const outputPath = path.join(packageDir, "dist", outName); - // Transformers.js is an optional, native-heavy dependency that is never bundled // into the binary; the tiny-model worker `bun install`s it into a runtime cache // on first use. The `catalog:` spec cannot be resolved from inside the compiled @@ -55,7 +53,7 @@ if ( } const transformersVersion = transformersManifest.version; -function shouldAdhocSignDarwinBinary(): boolean { +function shouldAdhocSignDarwinBinary(crossBuild: CrossBuild | null): boolean { return process.platform === "darwin" && !crossBuild; } @@ -77,6 +75,9 @@ async function runCommand( } async function main(): Promise { + const crossBuild = resolveCrossBuild(Bun.env.CROSS_TARGET); + const outName = crossBuild ? `omp-${crossBuild.id}` : "omp"; + const outputPath = path.join(packageDir, "dist", outName); // Generate inside the try so the finally always restores the empty checked-in // placeholders (stats client archive, docs index) even on failure. try { @@ -99,11 +100,10 @@ async function main(): Promise { transformersVersion, target: crossBuild?.target, external: ["fastembed", "onnxruntime-node"], - skipBuiltinCodesign: shouldAdhocSignDarwinBinary(), + skipBuiltinCodesign: shouldAdhocSignDarwinBinary(crossBuild), }); - // Bun 1.3.12 emits a truncated Mach-O signature on darwin builds. - if (shouldAdhocSignDarwinBinary()) { + if (shouldAdhocSignDarwinBinary(crossBuild)) { await runCommand(["codesign", "--force", "--sign", "-", outputPath]); } } finally { @@ -115,4 +115,4 @@ async function main(): Promise { } } -await main(); +if (import.meta.main) await main(); diff --git a/packages/natives/CHANGELOG.md b/packages/natives/CHANGELOG.md index 93c477931..d7a2b06e9 100644 --- a/packages/natives/CHANGELOG.md +++ b/packages/natives/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed macOS `pi-natives` package installs depending on Homebrew's dynamic `libpcre2-8.0.dylib`; release builds now force pcre2-sys to link PCRE2 statically. ([#5172](https://github.com/can1357/oh-my-pi/issues/5172)) + ## [16.4.3] - 2026-07-11 ### Fixed diff --git a/packages/natives/scripts/build-native.ts b/packages/natives/scripts/build-native.ts index e9cfd936e..06760d4d3 100644 --- a/packages/natives/scripts/build-native.ts +++ b/packages/natives/scripts/build-native.ts @@ -4,6 +4,10 @@ import { $ } from "bun"; import { detectHostAvx2Support } from "../../../scripts/host-detect"; import { generateEnumExports } from "./gen-enums"; +// pcre2-sys prefers a system libpcre2 when pkg-config finds one. Release addons +// must not retain host Homebrew paths such as /opt/homebrew/opt/pcre2/*.dylib. +process.env.PCRE2_SYS_STATIC ??= "1"; + const repoRoot = path.join(import.meta.dir, "../../.."); const rustDir = path.join(repoRoot, "crates/pi-natives"); const nativeDir = path.join(import.meta.dir, "../native"); diff --git a/scripts/ci-build-native.test.ts b/scripts/ci-build-native.test.ts new file mode 100644 index 000000000..0782f4149 --- /dev/null +++ b/scripts/ci-build-native.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from "bun:test"; +import * as path from "node:path"; +import { $ } from "bun"; + +const repoRoot = path.join(import.meta.dir, ".."); + +async function runCiNativeDryRun(env: Record = {}): Promise { + const result = await $`bun scripts/ci-build-native.ts --dry-run` + .cwd(repoRoot) + .quiet() + .env({ + ...process.env, + PCRE2_SYS_STATIC: "0", + RUSTFLAGS: "", + TARGET_VARIANT: "", + TARGET_VARIANTS: "", + ...env, + }) + .nothrow(); + expect(result.exitCode).toBe(0); + return result.text(); +} + +describe("ci native build environment", () => { + it("prints static PCRE2 env for the default native build dry run", async () => { + await expect(runCiNativeDryRun()).resolves.toBe( + "DRY RUN bun --cwd=packages/natives run build [default] PCRE2_SYS_STATIC=1\n", + ); + }); + + it("prints static PCRE2 env without dropping x64 variant settings", async () => { + await expect(runCiNativeDryRun({ TARGET_VARIANTS: "baseline" })).resolves.toBe( + 'DRY RUN bun --cwd=packages/natives run build [baseline] PCRE2_SYS_STATIC=1 TARGET_VARIANT=baseline RUSTFLAGS="-C target-cpu=x86-64-v2"\n', + ); + }); +}); diff --git a/scripts/ci-build-native.ts b/scripts/ci-build-native.ts index 86dcae6c5..5f90bf562 100644 --- a/scripts/ci-build-native.ts +++ b/scripts/ci-build-native.ts @@ -21,6 +21,13 @@ const variantConfigs: Record = { }, }; +/** Adds release-portability env required by native addon builds. */ +export function withPortableNativeBuildEnv( + env: Record, +): Record { + return { ...env, PCRE2_SYS_STATIC: "1" }; +} + function parseTargetVariants(): NativeBuildVariant[] { const rawVariants = (Bun.env.TARGET_VARIANTS ?? "").trim(); if (!rawVariants) return []; @@ -35,15 +42,17 @@ function parseTargetVariants(): NativeBuildVariant[] { } async function runNativeBuild(env: Record, label: string): Promise { + const buildEnv = withPortableNativeBuildEnv(env); if (isDryRun) { - const variant = env.TARGET_VARIANT ? ` TARGET_VARIANT=${env.TARGET_VARIANT}` : ""; - const rustflags = env.RUSTFLAGS ? ` RUSTFLAGS=${JSON.stringify(env.RUSTFLAGS)}` : ""; - console.log(`DRY RUN bun --cwd=packages/natives run build [${label}]${variant}${rustflags}`); + const staticPcre = ` PCRE2_SYS_STATIC=${buildEnv.PCRE2_SYS_STATIC}`; + const variant = buildEnv.TARGET_VARIANT ? ` TARGET_VARIANT=${buildEnv.TARGET_VARIANT}` : ""; + const rustflags = buildEnv.RUSTFLAGS ? ` RUSTFLAGS=${JSON.stringify(buildEnv.RUSTFLAGS)}` : ""; + console.log(`DRY RUN bun --cwd=packages/natives run build [${label}]${staticPcre}${variant}${rustflags}`); return; } console.log(`Building natives [${label}]...`); - await $`bun --cwd=packages/natives run build`.cwd(repoRoot).env(env); + await $`bun --cwd=packages/natives run build`.cwd(repoRoot).env(buildEnv); } async function main(): Promise { @@ -65,4 +74,4 @@ async function main(): Promise { } } -await main(); +if (import.meta.main) await main(); diff --git a/scripts/ci-release-build-binaries.test.ts b/scripts/ci-release-build-binaries.test.ts new file mode 100644 index 000000000..19cb6583b --- /dev/null +++ b/scripts/ci-release-build-binaries.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from "bun:test"; +import * as path from "node:path"; +import { $ } from "bun"; +import { resolveCrossBuild } from "../packages/coding-agent/scripts/build-binary"; + +const repoRoot = path.join(import.meta.dir, ".."); + +describe("Windows release binary target", () => { + it("builds the generic Windows release asset with the baseline runtime", async () => { + const result = await $`bun scripts/ci-release-build-binaries.ts --dry-run --targets win32-x64` + .cwd(repoRoot) + .quiet() + .nothrow(); + expect(result.exitCode).toBe(0); + const output = result.text(); + + expect(output).toContain("Building packages/coding-agent/binaries/omp-windows-x64.exe..."); + expect(output).toContain( + "DRY RUN Bun.build target=bun-windows-x64-baseline outfile=packages/coding-agent/binaries/omp-windows-x64.exe", + ); + expect(output).not.toContain("bun-windows-x64-modern"); + }); + + it("uses the baseline runtime for local Windows cross-build aliases", () => { + expect(resolveCrossBuild("win32-x64")).toEqual({ + id: "win32-x64", + platform: "win32", + arch: "x64", + target: "bun-windows-x64-baseline", + }); + expect(resolveCrossBuild("windows-x64")).toEqual({ + id: "windows-x64", + platform: "win32", + arch: "x64", + target: "bun-windows-x64-baseline", + }); + }); +}); diff --git a/scripts/ci-release-build-binaries.ts b/scripts/ci-release-build-binaries.ts index 8ee6a8ec6..bcae77ebb 100644 --- a/scripts/ci-release-build-binaries.ts +++ b/scripts/ci-release-build-binaries.ts @@ -63,7 +63,7 @@ const targets: BinaryTarget[] = [ id: "win32-x64", platform: "win32", arch: "x64", - target: "bun-windows-x64-modern", + target: "bun-windows-x64-baseline", outfile: "packages/coding-agent/binaries/omp-windows-x64.exe", }, ]; diff --git a/scripts/ci-test-ts.ts b/scripts/ci-test-ts.ts index f01e21795..e43a75d12 100755 --- a/scripts/ci-test-ts.ts +++ b/scripts/ci-test-ts.ts @@ -111,6 +111,7 @@ const localOnlyWorkspacePackages = ["packages/mnemopi", "python/robomp/web"]; // silently ignores unmatched filters when at least one other filter matches.) const repoScriptTests = [ "scripts/ci-concurrency.test.ts", + "scripts/ci-build-native.test.ts", "scripts/ci-release-notes.test.ts", "scripts/fix-dts-extensions.test.ts", "scripts/link-omp.test.ts", @@ -345,6 +346,7 @@ async function commandsForMode(mode: Mode): Promise { "--parallel=4", ...onlyFailuresArgs, "scripts/ci-concurrency.test.ts", + "scripts/ci-build-native.test.ts", "scripts/fix-dts-extensions.test.ts", ], },