ci: drop zig

This commit is contained in:
can1357
2026-04-30 14:25:01 +02:00
parent bfddf9bf47
commit 7015f6dfd2
9 changed files with 8 additions and 297 deletions
-11
View File
@@ -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
+1 -4
View File
@@ -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`.
-1
View File
@@ -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",
+5
View File
@@ -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
+2 -48
View File
@@ -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);
});
});
});
-32
View File
@@ -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();
-113
View File
@@ -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();
}