ci: drop zig
This commit is contained in:
@@ -131,9 +131,6 @@ jobs:
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3"
|
||||
- uses: mlugg/setup-zig@v2
|
||||
with:
|
||||
version: 0.15.2
|
||||
- run: bun install --frozen-lockfile
|
||||
- name: Install cross-compilation toolchain
|
||||
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
||||
@@ -184,9 +181,6 @@ jobs:
|
||||
- uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: nextest
|
||||
- uses: mlugg/setup-zig@v2
|
||||
with:
|
||||
version: 0.15.2
|
||||
- name: Cache bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -240,9 +234,6 @@ jobs:
|
||||
cache-on-failure: true
|
||||
save-if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) }}
|
||||
cache-workspace-crates: true
|
||||
- uses: mlugg/setup-zig@v2
|
||||
with:
|
||||
version: 0.15.2
|
||||
- name: Cache bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -373,8 +364,6 @@ jobs:
|
||||
merge-multiple: true
|
||||
run-id: ${{ steps.source.outputs.run-id }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Verify native addons
|
||||
run: bun run ci:release:verify-natives
|
||||
- name: Stage native addons for release
|
||||
run: cp packages/natives/native/*.node packages/coding-agent/binaries/
|
||||
- name: Build release archives
|
||||
|
||||
@@ -13,7 +13,6 @@ It follows the architecture terms from `docs/natives-architecture.md`:
|
||||
- `packages/natives/scripts/build-native.ts`
|
||||
- `packages/natives/scripts/embed-native.ts`
|
||||
- `packages/natives/scripts/gen-enums.ts`
|
||||
- `packages/natives/scripts/zig-safe-wrapper.ts`
|
||||
- `packages/natives/package.json`
|
||||
- `packages/natives/native/index.js`
|
||||
- `packages/natives/native/loader-state.js`
|
||||
@@ -101,8 +100,6 @@ Runtime x64 candidate order also includes the unsuffixed default filename after
|
||||
- baseline: `-C target-cpu=x86-64-v2`
|
||||
- non-x64 / no variant: `-C target-cpu=native`
|
||||
- if already set, script does not override.
|
||||
- `ZIG`: optional real Zig path used when the host Zig CPU contract wrapper is enabled.
|
||||
- `PI_NATIVE_REAL_ZIG`, `PI_NATIVE_ZIG_TARGET`, `PI_NATIVE_ZIG_CPU`: set internally for `zig-safe-wrapper.ts` when building local x64 Linux/macOS artifacts with Zig available.
|
||||
|
||||
## Build state/lifecycle transitions
|
||||
|
||||
@@ -114,7 +111,7 @@ Runtime x64 candidate order also includes the unsuffixed default filename after
|
||||
- x64 + `TARGET_VARIANT` → explicit variant;
|
||||
- x64 cross-build without `TARGET_VARIANT` → hard error;
|
||||
- x64 local build without override → detect host AVX2.
|
||||
3. **CPU policy**: set `RUSTFLAGS` if allowed; optionally route Zig through `zig-safe-wrapper.ts` to avoid leaking newer host CPU instructions into x64 artifacts.
|
||||
3. **CPU policy**: set `RUSTFLAGS` for the resolved variant unless the caller already provided one.
|
||||
4. **Compile**: run napi-rs against `crates/pi-natives` into an isolated output directory.
|
||||
5. **Locate artifact**: accept the canonical filename or a single napi-rs-generated `pi_natives.<platform>-<arch>*.node` candidate.
|
||||
6. **Install**: copy/rename addon into `packages/natives/native`.
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
"ci:test:full": "bun run test",
|
||||
"ci:test:smoke": "bun packages/coding-agent/src/cli.ts --version && bun packages/coding-agent/src/cli.ts --help && bun packages/coding-agent/src/cli.ts stats --help",
|
||||
"ci:test:install-methods": "bash scripts/install-tests/run-ci.sh",
|
||||
"ci:release:verify-natives": "bun scripts/ci-release-verify-natives.ts",
|
||||
"ci:release:build-binaries": "bun scripts/ci-release-build-binaries.ts",
|
||||
"ci:release:build-archives": "bun scripts/ci-release-build-archives.ts",
|
||||
"ci:release:publish": "bun scripts/ci-release-publish.ts",
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
- Simplified native build profile suffix formatting without changing `local` and `ci` values
|
||||
- Changed the native build output behavior to avoid setting an isolated Cargo target directory automatically
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed the host Zig CPU contract wrapper (`zig-safe-wrapper.ts`) and its `ZIG`/`PI_NATIVE_REAL_ZIG`/`PI_NATIVE_ZIG_TARGET`/`PI_NATIVE_ZIG_CPU` env handling, since the `zlob` Rust dependency that required Zig is gone
|
||||
- Removed the `ci-release-verify-natives` script and its AVX-512 marker scan from the release pipeline
|
||||
|
||||
## [14.5.12] - 2026-04-30
|
||||
### Breaking Changes
|
||||
|
||||
|
||||
@@ -17,13 +17,6 @@ const isCrossCompile = Boolean(crossTarget) || targetPlatform !== process.platfo
|
||||
|
||||
type X64Variant = "modern" | "baseline";
|
||||
|
||||
interface SafeHostZigBuildConfig {
|
||||
wrapperPath: string;
|
||||
realZigPath: string;
|
||||
target: string;
|
||||
cpu: string;
|
||||
}
|
||||
|
||||
let configuredVariant: X64Variant | undefined;
|
||||
if (configuredVariantRaw) {
|
||||
if (targetArch !== "x64") {
|
||||
@@ -46,36 +39,8 @@ function resolveEffectiveVariant(): X64Variant | null {
|
||||
const effectiveVariant = resolveEffectiveVariant();
|
||||
const variantSuffix = effectiveVariant ? `-${effectiveVariant}` : "";
|
||||
|
||||
function resolveLinuxHostZigTarget(): "x86_64-linux-gnu" | "x86_64-linux-musl" {
|
||||
const report = process.report?.getReport?.() as { header?: { glibcVersionRuntime?: string } } | undefined;
|
||||
return report?.header?.glibcVersionRuntime ? "x86_64-linux-gnu" : "x86_64-linux-musl";
|
||||
}
|
||||
|
||||
function resolveSafeHostZigBuildConfig(): SafeHostZigBuildConfig | null {
|
||||
if (isCrossCompile || targetArch !== "x64" || !effectiveVariant) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (targetPlatform !== "linux" && targetPlatform !== "darwin") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const realZigPath = Bun.env.ZIG ?? Bun.which("zig");
|
||||
if (!realZigPath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
wrapperPath: path.join(import.meta.dir, "zig-safe-wrapper.ts"),
|
||||
realZigPath,
|
||||
target: targetPlatform === "linux" ? resolveLinuxHostZigTarget() : "x86_64-macos",
|
||||
cpu: effectiveVariant === "modern" ? "x86_64_v3" : "x86_64_v2",
|
||||
};
|
||||
}
|
||||
|
||||
// Keep host-built Zig dependencies on the same ISA floor as the Rust addon.
|
||||
// zlob's build.rs defaults host builds to `native`, which can leak newer CPU
|
||||
// instructions into release artifacts even when Rust itself targets x86-64-v2/v3.
|
||||
// Pin Rust target-cpu so x64 baseline/modern variants get a reproducible ISA floor
|
||||
// instead of inheriting the host CPU when RUSTFLAGS is unset.
|
||||
if (!isCrossCompile && !Bun.env.RUSTFLAGS) {
|
||||
if (effectiveVariant === "modern") {
|
||||
Bun.env.RUSTFLAGS = "-C target-cpu=x86-64-v3";
|
||||
@@ -235,17 +200,6 @@ if (!napiBin) {
|
||||
throw new Error("Could not locate @napi-rs/cli `napi` binary in node_modules/.bin");
|
||||
}
|
||||
|
||||
const safeHostZigBuildConfig = resolveSafeHostZigBuildConfig();
|
||||
if (safeHostZigBuildConfig) {
|
||||
Bun.env.ZIG = safeHostZigBuildConfig.wrapperPath;
|
||||
Bun.env.PI_NATIVE_REAL_ZIG = safeHostZigBuildConfig.realZigPath;
|
||||
Bun.env.PI_NATIVE_ZIG_TARGET = safeHostZigBuildConfig.target;
|
||||
Bun.env.PI_NATIVE_ZIG_CPU = safeHostZigBuildConfig.cpu;
|
||||
console.log(
|
||||
`Pinning host Zig CPU contract: ${safeHostZigBuildConfig.target} ${safeHostZigBuildConfig.cpu} (${effectiveVariant})`,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const buildResult = await $`${napiBin} ${napiArgs}`.nothrow();
|
||||
if (buildResult.exitCode !== 0) {
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
export function buildZigArgs(args: string[], overrides: { target?: string; cpu?: string }): string[] {
|
||||
const nextArgs = [...args];
|
||||
if (nextArgs[0] !== "build") {
|
||||
return nextArgs;
|
||||
}
|
||||
|
||||
const target = overrides.target?.trim();
|
||||
const cpu = overrides.cpu?.trim();
|
||||
|
||||
if (target && !nextArgs.some(arg => arg.startsWith("-Dtarget="))) {
|
||||
nextArgs.push(`-Dtarget=${target}`);
|
||||
}
|
||||
if (cpu && !nextArgs.some(arg => arg.startsWith("-Dcpu="))) {
|
||||
nextArgs.push(`-Dcpu=${cpu}`);
|
||||
}
|
||||
|
||||
return nextArgs;
|
||||
}
|
||||
|
||||
export async function main(
|
||||
argv: string[] = process.argv.slice(2),
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): Promise<number> {
|
||||
const realZigPath = env.PI_NATIVE_REAL_ZIG;
|
||||
if (!realZigPath) {
|
||||
throw new Error("PI_NATIVE_REAL_ZIG is required when using zig-safe-wrapper.ts");
|
||||
}
|
||||
|
||||
const child = Bun.spawn(
|
||||
[realZigPath, ...buildZigArgs(argv, { target: env.PI_NATIVE_ZIG_TARGET, cpu: env.PI_NATIVE_ZIG_CPU })],
|
||||
{
|
||||
stdin: "inherit",
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
},
|
||||
);
|
||||
return await child.exited;
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
const exitCode = await main();
|
||||
process.exit(exitCode);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { hasAvx512Markers } from "../../../scripts/ci-release-verify-natives";
|
||||
import { buildZigArgs } from "../scripts/zig-safe-wrapper";
|
||||
|
||||
describe("native build safety", () => {
|
||||
describe("buildZigArgs", () => {
|
||||
it("pins host zig build to the requested cpu contract", () => {
|
||||
expect(
|
||||
buildZigArgs(["build", "-Doptimize=ReleaseFast"], { target: "x86_64-linux-gnu", cpu: "x86_64_v2" }),
|
||||
).toEqual(["build", "-Doptimize=ReleaseFast", "-Dtarget=x86_64-linux-gnu", "-Dcpu=x86_64_v2"]);
|
||||
});
|
||||
|
||||
it("does not override explicit zig target or cpu flags", () => {
|
||||
expect(
|
||||
buildZigArgs(["build", "-Dtarget=x86_64-linux-gnu", "-Dcpu=x86_64_v3"], {
|
||||
target: "x86_64-linux-gnu",
|
||||
cpu: "x86_64_v2",
|
||||
}),
|
||||
).toEqual(["build", "-Dtarget=x86_64-linux-gnu", "-Dcpu=x86_64_v3"]);
|
||||
});
|
||||
|
||||
it("leaves non-build zig commands untouched", () => {
|
||||
expect(buildZigArgs(["version"], { target: "x86_64-linux-gnu", cpu: "x86_64_v2" })).toEqual(["version"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasAvx512Markers", () => {
|
||||
it("flags AVX-512 register markers in disassembly", () => {
|
||||
expect(hasAvx512Markers("60ba1df:\tc4 c1 78 92 c9\t\tkmovw %r9d,%k1")).toBe(true);
|
||||
expect(hasAvx512Markers("123456:\t62 f1 7d 48 6f c0\tvmovdqa32 %zmm0,%zmm1")).toBe(true);
|
||||
});
|
||||
|
||||
it("flags EVEX-encoded AVX-512 even without zmm or mask registers", () => {
|
||||
expect(hasAvx512Markers("401000:\t62 f3 75 28 25 c2 96\tvpternlogd $0x96,%ymm2,%ymm1,%ymm0")).toBe(true);
|
||||
});
|
||||
|
||||
it("ignores ordinary x86-64 disassembly", () => {
|
||||
expect(hasAvx512Markers("401000:\t48 89 e5\t\tmov %rsp,%rbp")).toBe(false);
|
||||
expect(hasAvx512Markers("401004:\tc5 f5 fe c2\tvpaddd %ymm2,%ymm1,%ymm0")).toBe(false);
|
||||
expect(hasAvx512Markers("58b83d7:\t62 00 00 00 ")).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import * as path from "node:path";
|
||||
import { $ } from "bun";
|
||||
import { detectHostAvx2Support } from "./host-detect"
|
||||
|
||||
interface NativeBuildVariant {
|
||||
name: "baseline" | "modern";
|
||||
@@ -11,8 +10,6 @@ interface NativeBuildVariant {
|
||||
|
||||
const repoRoot = path.join(import.meta.dir, "..");
|
||||
const isDryRun = process.argv.includes("--dry-run");
|
||||
const targetPlatform = Bun.env.TARGET_PLATFORM || process.platform;
|
||||
const targetArch = Bun.env.TARGET_ARCH || process.arch;
|
||||
const variantConfigs: Record<NativeBuildVariant["name"], NativeBuildVariant> = {
|
||||
baseline: {
|
||||
name: "baseline",
|
||||
@@ -37,18 +34,6 @@ function parseTargetVariants(): NativeBuildVariant[] {
|
||||
});
|
||||
}
|
||||
|
||||
function resolveExpectedAddons(variants: NativeBuildVariant[]): string[] {
|
||||
if (variants.length > 0) {
|
||||
return variants.map(variant => `${targetPlatform}-${targetArch}-${variant.name}`);
|
||||
}
|
||||
|
||||
if (targetArch === "x64") {
|
||||
return [`${targetPlatform}-${targetArch}-${detectHostAvx2Support() ? "modern" : "baseline"}`];
|
||||
}
|
||||
|
||||
return [`${targetPlatform}-${targetArch}`];
|
||||
}
|
||||
|
||||
async function runNativeBuild(env: Record<string, string | undefined>, label: string): Promise<void> {
|
||||
if (isDryRun) {
|
||||
const variant = env.TARGET_VARIANT ? ` TARGET_VARIANT=${env.TARGET_VARIANT}` : "";
|
||||
@@ -61,25 +46,10 @@ async function runNativeBuild(env: Record<string, string | undefined>, label: st
|
||||
await $`bun --cwd=packages/natives run build`.cwd(repoRoot).env(env);
|
||||
}
|
||||
|
||||
async function verifyBuiltAddons(expectedAddons: string[]): Promise<void> {
|
||||
if (isDryRun) {
|
||||
console.log(`DRY RUN bun scripts/ci-release-verify-natives.ts PI_NATIVE_EXPECTED_ADDONS=${expectedAddons.join(" ")}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await $`bun scripts/ci-release-verify-natives.ts`
|
||||
.cwd(repoRoot)
|
||||
.env({
|
||||
...Bun.env,
|
||||
PI_NATIVE_EXPECTED_ADDONS: expectedAddons.join(" "),
|
||||
});
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const variants = parseTargetVariants();
|
||||
if (variants.length === 0) {
|
||||
await runNativeBuild(Bun.env, "default");
|
||||
await verifyBuiltAddons(resolveExpectedAddons([]));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,8 +63,6 @@ async function main(): Promise<void> {
|
||||
variant.name,
|
||||
);
|
||||
}
|
||||
|
||||
await verifyBuiltAddons(resolveExpectedAddons(variants));
|
||||
}
|
||||
|
||||
await main();
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
|
||||
const repoRoot = path.join(import.meta.dir, "..");
|
||||
const nativeDir = path.resolve(Bun.env.PI_NATIVE_VERIFY_DIR ?? path.join(repoRoot, "packages", "natives", "native"));
|
||||
const defaultExpectedAddons = [
|
||||
"linux-x64-modern",
|
||||
"linux-x64-baseline",
|
||||
"linux-arm64",
|
||||
"darwin-x64-modern",
|
||||
"darwin-x64-baseline",
|
||||
"darwin-arm64",
|
||||
"win32-x64-modern",
|
||||
"win32-x64-baseline",
|
||||
] as const;
|
||||
const x64LinuxIsaContracts = [
|
||||
{ addon: "linux-x64-baseline", filename: "pi_natives.linux-x64-baseline.node", label: "x86-64-v2" },
|
||||
{ addon: "linux-x64-modern", filename: "pi_natives.linux-x64-modern.node", label: "x86-64-v3" },
|
||||
] as const;
|
||||
const AVX512_REGISTER_PATTERN = /\bzmm\d+\b|\bk[0-7]\b/;
|
||||
|
||||
export function hasAvx512Markers(disassembly: string): boolean {
|
||||
return disassembly.split("\n").some(line => {
|
||||
if (AVX512_REGISTER_PATTERN.test(line)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const columns = line.split("\t");
|
||||
if (columns.length < 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bytes = columns[1]?.trim() ?? "";
|
||||
const instruction = columns[2]?.trim() ?? "";
|
||||
return bytes.startsWith("62 ") && /^[a-z]/i.test(instruction) && !instruction.startsWith(".byte");
|
||||
});
|
||||
}
|
||||
|
||||
function disassemble(binaryPath: string): string {
|
||||
const objdumpPath = Bun.which("objdump");
|
||||
if (!objdumpPath) {
|
||||
throw new Error("objdump is required to verify linux-x64 native ISA contracts.");
|
||||
}
|
||||
|
||||
const result = Bun.spawnSync([objdumpPath, "-d", binaryPath], { stdout: "pipe", stderr: "pipe" });
|
||||
if (result.exitCode !== 0) {
|
||||
const stderr = result.stderr.toString("utf-8").trim();
|
||||
throw new Error(`objdump failed for ${binaryPath}${stderr ? `:\n${stderr}` : ""}`);
|
||||
}
|
||||
|
||||
return result.stdout.toString("utf-8");
|
||||
}
|
||||
|
||||
function resolveExpectedAddons(): string[] {
|
||||
const configured = (Bun.env.PI_NATIVE_EXPECTED_ADDONS ?? "").trim();
|
||||
if (!configured) {
|
||||
return [...defaultExpectedAddons];
|
||||
}
|
||||
|
||||
return configured.split(/[\s,]+/).filter(Boolean);
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const entries = await fs.readdir(nativeDir);
|
||||
const expectedAddons = resolveExpectedAddons();
|
||||
|
||||
console.log(`Native addons downloaded from ${nativeDir}:`);
|
||||
for (const entry of entries.sort((a, b) => a.localeCompare(b))) {
|
||||
console.log(` ${entry}`);
|
||||
}
|
||||
console.log();
|
||||
console.log(`Expected addons: ${expectedAddons.join(", ")}`);
|
||||
|
||||
const missingAddons = expectedAddons.filter((platform) => !entries.includes(`pi_natives.${platform}.node`));
|
||||
if (missingAddons.length > 0) {
|
||||
for (const platform of missingAddons) {
|
||||
console.error(`MISSING pi_natives.${platform}.node`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
for (const platform of expectedAddons) {
|
||||
console.log(`OK pi_natives.${platform}.node`);
|
||||
}
|
||||
|
||||
const isaFailures: string[] = [];
|
||||
for (const contract of x64LinuxIsaContracts) {
|
||||
if (!expectedAddons.includes(contract.addon)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const binaryPath = path.join(nativeDir, contract.filename);
|
||||
const disassembly = disassemble(binaryPath);
|
||||
if (hasAvx512Markers(disassembly)) {
|
||||
isaFailures.push(`${contract.filename} contains AVX-512 markers; ${contract.label} artifacts must stay below x86-64-v4.`);
|
||||
continue;
|
||||
}
|
||||
console.log(`OK ${contract.filename} contains no AVX-512 markers`);
|
||||
}
|
||||
|
||||
if (isaFailures.length > 0) {
|
||||
for (const failure of isaFailures) {
|
||||
console.error(failure);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
await main();
|
||||
}
|
||||
Reference in New Issue
Block a user