fix(ci): add baseline CPU variant binaries for legacy hardware support

Closes #2121
This commit is contained in:
YeonGyu-Kim
2026-02-26 21:00:31 +09:00
parent c505989ad4
commit cc5e9d1e9b
8 changed files with 250 additions and 51 deletions

View File

@@ -35,15 +35,15 @@ jobs:
# - Uploads compressed artifacts for the publish job # - Uploads compressed artifacts for the publish job
# ============================================================================= # =============================================================================
build: build:
runs-on: ${{ matrix.platform == 'windows-x64' && 'windows-latest' || 'ubuntu-latest' }} runs-on: ${{ startsWith(matrix.platform, 'windows-') && 'windows-latest' || 'ubuntu-latest' }}
defaults: defaults:
run: run:
shell: bash shell: bash
strategy: strategy:
fail-fast: false fail-fast: false
max-parallel: 7 max-parallel: 11
matrix: matrix:
platform: [darwin-arm64, darwin-x64, linux-x64, linux-arm64, linux-x64-musl, linux-arm64-musl, windows-x64] platform: [darwin-arm64, darwin-x64, darwin-x64-baseline, linux-x64, linux-x64-baseline, linux-arm64, linux-x64-musl, linux-x64-musl-baseline, linux-arm64-musl, windows-x64, windows-x64-baseline]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -95,14 +95,18 @@ jobs:
case "$PLATFORM" in case "$PLATFORM" in
darwin-arm64) TARGET="bun-darwin-arm64" ;; darwin-arm64) TARGET="bun-darwin-arm64" ;;
darwin-x64) TARGET="bun-darwin-x64" ;; darwin-x64) TARGET="bun-darwin-x64" ;;
darwin-x64-baseline) TARGET="bun-darwin-x64-baseline" ;;
linux-x64) TARGET="bun-linux-x64" ;; linux-x64) TARGET="bun-linux-x64" ;;
linux-x64-baseline) TARGET="bun-linux-x64-baseline" ;;
linux-arm64) TARGET="bun-linux-arm64" ;; linux-arm64) TARGET="bun-linux-arm64" ;;
linux-x64-musl) TARGET="bun-linux-x64-musl" ;; linux-x64-musl) TARGET="bun-linux-x64-musl" ;;
linux-x64-musl-baseline) TARGET="bun-linux-x64-musl-baseline" ;;
linux-arm64-musl) TARGET="bun-linux-arm64-musl" ;; linux-arm64-musl) TARGET="bun-linux-arm64-musl" ;;
windows-x64) TARGET="bun-windows-x64" ;; windows-x64) TARGET="bun-windows-x64" ;;
windows-x64-baseline) TARGET="bun-windows-x64-baseline" ;;
esac esac
if [ "$PLATFORM" = "windows-x64" ]; then if [[ "$PLATFORM" == windows-* ]]; then
OUTPUT="packages/${PLATFORM}/bin/oh-my-opencode.exe" OUTPUT="packages/${PLATFORM}/bin/oh-my-opencode.exe"
else else
OUTPUT="packages/${PLATFORM}/bin/oh-my-opencode" OUTPUT="packages/${PLATFORM}/bin/oh-my-opencode"
@@ -119,7 +123,7 @@ jobs:
PLATFORM="${{ matrix.platform }}" PLATFORM="${{ matrix.platform }}"
cd packages/${PLATFORM} cd packages/${PLATFORM}
if [ "$PLATFORM" = "windows-x64" ]; then if [[ "$PLATFORM" == windows-* ]]; then
# Windows: use 7z (pre-installed on windows-latest) # Windows: use 7z (pre-installed on windows-latest)
7z a -tzip ../../binary-${PLATFORM}.zip bin/ package.json 7z a -tzip ../../binary-${PLATFORM}.zip bin/ package.json
else else
@@ -155,7 +159,7 @@ jobs:
fail-fast: false fail-fast: false
max-parallel: 2 max-parallel: 2
matrix: matrix:
platform: [darwin-arm64, darwin-x64, linux-x64, linux-arm64, linux-x64-musl, linux-arm64-musl, windows-x64] platform: [darwin-arm64, darwin-x64, darwin-x64-baseline, linux-x64, linux-x64-baseline, linux-arm64, linux-x64-musl, linux-x64-musl-baseline, linux-arm64-musl, windows-x64, windows-x64-baseline]
steps: steps:
- name: Check if already published - name: Check if already published
id: check id: check
@@ -184,7 +188,7 @@ jobs:
PLATFORM="${{ matrix.platform }}" PLATFORM="${{ matrix.platform }}"
mkdir -p packages/${PLATFORM} mkdir -p packages/${PLATFORM}
if [ "$PLATFORM" = "windows-x64" ]; then if [[ "$PLATFORM" == windows-* ]]; then
unzip binary-${PLATFORM}.zip -d packages/${PLATFORM}/ unzip binary-${PLATFORM}.zip -d packages/${PLATFORM}/
else else
tar -xzvf binary-${PLATFORM}.tar.gz -C packages/${PLATFORM}/ tar -xzvf binary-${PLATFORM}.tar.gz -C packages/${PLATFORM}/

View File

@@ -189,7 +189,7 @@ jobs:
VERSION="${{ steps.version.outputs.version }}" VERSION="${{ steps.version.outputs.version }}"
jq --arg v "$VERSION" '.version = $v' package.json > tmp.json && mv tmp.json package.json jq --arg v "$VERSION" '.version = $v' package.json > tmp.json && mv tmp.json package.json
for platform in darwin-arm64 darwin-x64 linux-x64 linux-arm64 linux-x64-musl linux-arm64-musl windows-x64; do for platform in darwin-arm64 darwin-x64 darwin-x64-baseline linux-x64 linux-x64-baseline linux-arm64 linux-x64-musl linux-x64-musl-baseline linux-arm64-musl windows-x64 windows-x64-baseline; do
jq --arg v "$VERSION" '.version = $v' "packages/${platform}/package.json" > tmp.json jq --arg v "$VERSION" '.version = $v' "packages/${platform}/package.json" > tmp.json
mv tmp.json "packages/${platform}/package.json" mv tmp.json "packages/${platform}/package.json"
done done

View File

@@ -3,8 +3,9 @@
// Wrapper script that detects platform and spawns the correct binary // Wrapper script that detects platform and spawns the correct binary
import { spawnSync } from "node:child_process"; import { spawnSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { createRequire } from "node:module"; import { createRequire } from "node:module";
import { getPlatformPackage, getBinaryPath } from "./platform.js"; import { getPlatformPackageCandidates, getBinaryPath } from "./platform.js";
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
@@ -26,55 +27,116 @@ function getLibcFamily() {
} }
} }
function supportsAvx2() {
if (process.arch !== "x64") {
return null;
}
if (process.env.OH_MY_OPENCODE_FORCE_BASELINE === "1") {
return false;
}
if (process.platform === "linux") {
try {
const cpuInfo = readFileSync("/proc/cpuinfo", "utf8").toLowerCase();
return cpuInfo.includes("avx2");
} catch {
return null;
}
}
if (process.platform === "darwin") {
const probe = spawnSync("sysctl", ["-n", "machdep.cpu.leaf7_features"], {
encoding: "utf8",
});
if (probe.error || probe.status !== 0) {
return null;
}
return probe.stdout.toUpperCase().includes("AVX2");
}
return null;
}
function getSignalExitCode(signal) {
const signalCodeByName = {
SIGINT: 2,
SIGILL: 4,
SIGKILL: 9,
SIGTERM: 15,
};
return 128 + (signalCodeByName[signal] ?? 1);
}
function main() { function main() {
const { platform, arch } = process; const { platform, arch } = process;
const libcFamily = getLibcFamily(); const libcFamily = getLibcFamily();
const avx2Supported = supportsAvx2();
// Get platform package name let packageCandidates;
let pkg;
try { try {
pkg = getPlatformPackage({ platform, arch, libcFamily }); packageCandidates = getPlatformPackageCandidates({
platform,
arch,
libcFamily,
preferBaseline: avx2Supported === false,
});
} catch (error) { } catch (error) {
console.error(`\noh-my-opencode: ${error.message}\n`); console.error(`\noh-my-opencode: ${error.message}\n`);
process.exit(1); process.exit(1);
} }
// Resolve binary path const resolvedBinaries = packageCandidates
const binRelPath = getBinaryPath(pkg, platform); .map((pkg) => {
try {
let binPath; return { pkg, binPath: require.resolve(getBinaryPath(pkg, platform)) };
try { } catch {
binPath = require.resolve(binRelPath); return null;
} catch { }
})
.filter((entry) => entry !== null);
if (resolvedBinaries.length === 0) {
console.error(`\noh-my-opencode: Platform binary not installed.`); console.error(`\noh-my-opencode: Platform binary not installed.`);
console.error(`\nYour platform: ${platform}-${arch}${libcFamily === "musl" ? "-musl" : ""}`); console.error(`\nYour platform: ${platform}-${arch}${libcFamily === "musl" ? "-musl" : ""}`);
console.error(`Expected package: ${pkg}`); console.error(`Expected packages (in order): ${packageCandidates.join(", ")}`);
console.error(`\nTo fix, run:`); console.error(`\nTo fix, run:`);
console.error(` npm install ${pkg}\n`); console.error(` npm install ${packageCandidates[0]}\n`);
process.exit(1); process.exit(1);
} }
// Spawn the binary for (let index = 0; index < resolvedBinaries.length; index += 1) {
const result = spawnSync(binPath, process.argv.slice(2), { const currentBinary = resolvedBinaries[index];
stdio: "inherit", const hasFallback = index < resolvedBinaries.length - 1;
}); const result = spawnSync(currentBinary.binPath, process.argv.slice(2), {
stdio: "inherit",
// Handle spawn errors });
if (result.error) {
console.error(`\noh-my-opencode: Failed to execute binary.`); if (result.error) {
console.error(`Error: ${result.error.message}\n`); if (hasFallback) {
process.exit(2); continue;
} }
// Handle signals console.error(`\noh-my-opencode: Failed to execute binary.`);
if (result.signal) { console.error(`Error: ${result.error.message}\n`);
const signalNum = result.signal === "SIGTERM" ? 15 : process.exit(2);
result.signal === "SIGKILL" ? 9 : }
result.signal === "SIGINT" ? 2 : 1;
process.exit(128 + signalNum); if (result.signal === "SIGILL" && hasFallback) {
continue;
}
if (result.signal) {
process.exit(getSignalExitCode(result.signal));
}
process.exit(result.status ?? 1);
} }
process.exit(result.status ?? 1); process.exit(1);
} }
main(); main();

14
bin/platform.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
export declare function getPlatformPackage(options: {
platform: string;
arch: string;
libcFamily?: string | null;
}): string;
export declare function getPlatformPackageCandidates(options: {
platform: string;
arch: string;
libcFamily?: string | null;
preferBaseline?: boolean;
}): string[];
export declare function getBinaryPath(pkg: string, platform: string): string;

View File

@@ -26,6 +26,50 @@ export function getPlatformPackage({ platform, arch, libcFamily }) {
return `oh-my-opencode-${os}-${arch}${suffix}`; return `oh-my-opencode-${os}-${arch}${suffix}`;
} }
/** @param {{ platform: string, arch: string, libcFamily?: string | null, preferBaseline?: boolean }} options */
export function getPlatformPackageCandidates({ platform, arch, libcFamily, preferBaseline = false }) {
const primaryPackage = getPlatformPackage({ platform, arch, libcFamily });
const baselinePackage = getBaselinePlatformPackage({ platform, arch, libcFamily });
if (!baselinePackage) {
return [primaryPackage];
}
return preferBaseline ? [baselinePackage, primaryPackage] : [primaryPackage, baselinePackage];
}
/** @param {{ platform: string, arch: string, libcFamily?: string | null }} options */
function getBaselinePlatformPackage({ platform, arch, libcFamily }) {
if (arch !== "x64") {
return null;
}
if (platform === "darwin") {
return "oh-my-opencode-darwin-x64-baseline";
}
if (platform === "win32") {
return "oh-my-opencode-windows-x64-baseline";
}
if (platform === "linux") {
if (libcFamily === null || libcFamily === undefined) {
throw new Error(
"Could not detect libc on Linux. " +
"Please ensure detect-libc is installed or report this issue."
);
}
if (libcFamily === "musl") {
return "oh-my-opencode-linux-x64-musl-baseline";
}
return "oh-my-opencode-linux-x64-baseline";
}
return null;
}
/** /**
* Get the path to the binary within a platform package * Get the path to the binary within a platform package
* @param {string} pkg Package name * @param {string} pkg Package name

View File

@@ -1,6 +1,6 @@
// bin/platform.test.ts // bin/platform.test.ts
import { describe, expect, test } from "bun:test"; import { describe, expect, test } from "bun:test";
import { getPlatformPackage, getBinaryPath } from "./platform.js"; import { getBinaryPath, getPlatformPackage, getPlatformPackageCandidates } from "./platform.js";
describe("getPlatformPackage", () => { describe("getPlatformPackage", () => {
// #region Darwin platforms // #region Darwin platforms
@@ -146,3 +146,58 @@ describe("getBinaryPath", () => {
expect(result).toBe("oh-my-opencode-linux-x64/bin/oh-my-opencode"); expect(result).toBe("oh-my-opencode-linux-x64/bin/oh-my-opencode");
}); });
}); });
describe("getPlatformPackageCandidates", () => {
test("returns x64 and baseline candidates for Linux glibc", () => {
// #given Linux x64 with glibc
const input = { platform: "linux", arch: "x64", libcFamily: "glibc" };
// #when getting package candidates
const result = getPlatformPackageCandidates(input);
// #then returns modern first then baseline fallback
expect(result).toEqual([
"oh-my-opencode-linux-x64",
"oh-my-opencode-linux-x64-baseline",
]);
});
test("returns x64 musl and baseline candidates for Linux musl", () => {
// #given Linux x64 with musl
const input = { platform: "linux", arch: "x64", libcFamily: "musl" };
// #when getting package candidates
const result = getPlatformPackageCandidates(input);
// #then returns musl modern first then musl baseline fallback
expect(result).toEqual([
"oh-my-opencode-linux-x64-musl",
"oh-my-opencode-linux-x64-musl-baseline",
]);
});
test("returns baseline first when preferBaseline is true", () => {
// #given Windows x64 and baseline preference
const input = { platform: "win32", arch: "x64", preferBaseline: true };
// #when getting package candidates
const result = getPlatformPackageCandidates(input);
// #then baseline package is preferred first
expect(result).toEqual([
"oh-my-opencode-windows-x64-baseline",
"oh-my-opencode-windows-x64",
]);
});
test("returns only one candidate for ARM64", () => {
// #given non-x64 platform
const input = { platform: "linux", arch: "arm64", libcFamily: "glibc" };
// #when getting package candidates
const result = getPlatformPackageCandidates(input);
// #then baseline fallback is not included
expect(result).toEqual(["oh-my-opencode-linux-arm64"]);
});
});

View File

@@ -77,11 +77,15 @@
"optionalDependencies": { "optionalDependencies": {
"oh-my-opencode-darwin-arm64": "3.8.5", "oh-my-opencode-darwin-arm64": "3.8.5",
"oh-my-opencode-darwin-x64": "3.8.5", "oh-my-opencode-darwin-x64": "3.8.5",
"oh-my-opencode-darwin-x64-baseline": "3.8.5",
"oh-my-opencode-linux-arm64": "3.8.5", "oh-my-opencode-linux-arm64": "3.8.5",
"oh-my-opencode-linux-arm64-musl": "3.8.5", "oh-my-opencode-linux-arm64-musl": "3.8.5",
"oh-my-opencode-linux-x64": "3.8.5", "oh-my-opencode-linux-x64": "3.8.5",
"oh-my-opencode-linux-x64-baseline": "3.8.5",
"oh-my-opencode-linux-x64-musl": "3.8.5", "oh-my-opencode-linux-x64-musl": "3.8.5",
"oh-my-opencode-windows-x64": "3.8.5" "oh-my-opencode-linux-x64-musl-baseline": "3.8.5",
"oh-my-opencode-windows-x64": "3.8.5",
"oh-my-opencode-windows-x64-baseline": "3.8.5"
}, },
"trustedDependencies": [ "trustedDependencies": [
"@ast-grep/cli", "@ast-grep/cli",

View File

@@ -2,7 +2,7 @@
// Runs after npm install to verify platform binary is available // Runs after npm install to verify platform binary is available
import { createRequire } from "node:module"; import { createRequire } from "node:module";
import { getPlatformPackage, getBinaryPath } from "./bin/platform.js"; import { getPlatformPackageCandidates, getBinaryPath } from "./bin/platform.js";
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
@@ -27,12 +27,28 @@ function main() {
const libcFamily = getLibcFamily(); const libcFamily = getLibcFamily();
try { try {
const pkg = getPlatformPackage({ platform, arch, libcFamily }); const packageCandidates = getPlatformPackageCandidates({
const binPath = getBinaryPath(pkg, platform); platform,
arch,
// Try to resolve the binary libcFamily,
require.resolve(binPath); });
console.log(`✓ oh-my-opencode binary installed for ${platform}-${arch}`);
const resolvedPackage = packageCandidates.find((pkg) => {
try {
require.resolve(getBinaryPath(pkg, platform));
return true;
} catch {
return false;
}
});
if (!resolvedPackage) {
throw new Error(
`No platform binary package installed. Tried: ${packageCandidates.join(", ")}`
);
}
console.log(`✓ oh-my-opencode binary installed for ${platform}-${arch} (${resolvedPackage})`);
} catch (error) { } catch (error) {
console.warn(`⚠ oh-my-opencode: ${error.message}`); console.warn(`⚠ oh-my-opencode: ${error.message}`);
console.warn(` The CLI may not work on this platform.`); console.warn(` The CLI may not work on this platform.`);