mirror of
https://github.com/wirenboard/agent-vm.git
synced 2026-07-09 16:00:54 +00:00
Why --- PLAN.md item "B2 — Cross-arch binaries" (section B, Distribution / release) calls for cross-arch builds and per-platform npm packaging, noting that "the package currently bundles a linux-x86_64 binary". As a result Apple Silicon and ARM Linux users get no native artifact and fall back to slow x86 emulation (Rosetta / qemu) — a poor fit for a tool whose job is to launch microVMs quickly. This change delivers the linux/aarch64 half of B2: a genuine aarch64 binary, built, packaged, and selected at install time, so ARM hosts run native. How --- Release workflow (.github/workflows/release-npm.yml): every build job (build-agent-vm, build-msb, build-libkrunfw) and the package job gain a linux-arm64 matrix leg. Because GitHub's hosted arm64 Linux runners aren't on the free tier, the arm64 leg cross-compiles on the x64 runner: a `cross: true` matrix flag drives a `CROSS` env switch that (a) enables the arm64 multiarch apt repo and installs the cross linker (gcc-aarch64-linux-gnu) plus the :arm64 dev libs agent-vm / msb link against (libcap-ng, libdbus, libsqlite3), and (b) exports PKG_CONFIG_ALLOW_CROSS / PKG_CONFIG_PATH / PKG_CONFIG_SYSROOT_DIR so the `pkg-config` crate resolves the target libs. The msb job also sets CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER directly, since it builds from vendor/microsandbox (a separate workspace not on the superproject's .cargo config search path). All of these guards are no-ops on the native x64 leg, so the existing x86_64 path is unchanged. The libkrunfw arm64 leg cross-builds the guest kernel with ARCH=arm64 / CROSS_COMPILE and fails fast with a clear message if its arm64 kbuild .config seed (libkrunfw-overrides/config-libkrunfw_aarch64.patch) hasn't been ported yet, rather than silently shipping a mis-configured kernel. The publish job downloads the new agent-vm-linux-arm64 artifact alongside the x64 one. Cross toolchain config (.cargo/config.toml, new): pins the aarch64-unknown-linux-gnu linker to aarch64-linux-gnu-gcc so a local cross build reproduces CI; the file documents the matching apt packages. npm launcher (npm-dist/agent-vm/bin/agent-vm.js, npm-dist/agent-vm/package.json): un-comment the linux-arm64 entry in the launcher's PLATFORM_PACKAGES map and add @wirenboard/agent-vm-linux-arm64 to the main package's optionalDependencies, so `npm install` pulls the arm64 subpackage on ARM hosts and the launcher dispatches to it. arm64 subpackage scaffold (npm-dist/agent-vm-linux-arm64/, new): mirrors the x64 subpackage layout — package.json (os linux / cpu arm64), README.md, and bin/.gitkeep + lib/.gitkeep placeholders — so the directory exists in the tree for the release workflow to drop the cross-built binary, msb, and libkrunfw into. Dispatch test (npm-dist/agent-vm/bin/agent-vm.dispatch.test.js, new): arm64 dispatch can't be exercised on an x86_64 CI host because node reports the host's real process.arch. The test re-derives the launcher's PLATFORM_PACKAGES map + bin-path logic from the source and asserts the linux-arm64 key resolves to the arm64 subpackage with the same bin/ layout as x64, so a future edit that forgets arm64 fails here instead of silently falling through to "no prebuilt binary" on ARM hardware. README (npm-dist/README.md): document that the package now ships both the linux-x64 and linux-arm64 per-platform subpackages. macOS / darwin and win32 cross builds remain out of scope for this change (still commented placeholders in the launcher) and are tracked under the rest of B2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
90 lines
3.8 KiB
JavaScript
90 lines
3.8 KiB
JavaScript
#!/usr/bin/env node
|
|
// Deterministic dispatch test for the npm launcher (bin/agent-vm.js).
|
|
//
|
|
// Why this exists: the launcher's job is to map the running
|
|
// platform/arch to a per-platform subpackage and then build the
|
|
// `bin/agent-vm` path inside it. arm64 was added to that mapping in
|
|
// B2, but arm64 dispatch cannot be exercised on an x86_64 CI host —
|
|
// `node` reports the host's real process.arch, so simply running the
|
|
// launcher there only ever exercises the linux-x64 leg. This test
|
|
// closes that gap without a real arm64 runtime by re-deriving the
|
|
// mapping + path logic from the launcher source and asserting the
|
|
// linux-arm64 key resolves to the expected package and to the same
|
|
// bin/ layout as linux-x64.
|
|
//
|
|
// Run standalone: `node bin/agent-vm.dispatch.test.js` (no test
|
|
// harness / deps — matches the repo convention of sanity-checking the
|
|
// launcher with plain `node`/`node --check`; there is no Node test
|
|
// runner or root package.json in this repo).
|
|
//
|
|
// It must stay in lockstep with the real launcher: it extracts the
|
|
// live PLATFORM_PACKAGES object out of agent-vm.js (rather than
|
|
// hard-coding a copy) so a future edit to the mapping that forgets
|
|
// arm64 — or renames the package — fails here instead of silently
|
|
// shipping a launcher that falls through to "unsupported platform"
|
|
// on arm64 hardware.
|
|
|
|
"use strict";
|
|
|
|
const assert = require("node:assert");
|
|
const path = require("node:path");
|
|
const fs = require("node:fs");
|
|
const vm = require("node:vm");
|
|
|
|
const launcherPath = path.join(__dirname, "agent-vm.js");
|
|
const src = fs.readFileSync(launcherPath, "utf8");
|
|
|
|
// Pull the `PLATFORM_PACKAGES = { ... };` object-literal out of the
|
|
// launcher source and evaluate just that literal in an isolated VM
|
|
// context. We deliberately do NOT `require()` the launcher: it runs
|
|
// its dispatch + process.exit() at module load, so requiring it would
|
|
// terminate this test process.
|
|
const m = src.match(/const\s+PLATFORM_PACKAGES\s*=\s*(\{[\s\S]*?\});/);
|
|
assert.ok(m, "could not locate PLATFORM_PACKAGES object literal in agent-vm.js");
|
|
const PLATFORM_PACKAGES = vm.runInNewContext(`(${m[1]})`);
|
|
|
|
// Mirror the launcher's binPath construction (the
|
|
// `path.join(dir, "bin", "agent-vm"+ext)` line) so we assert the SAME
|
|
// layout the launcher actually uses.
|
|
function binPathFor(pkgDir, platform) {
|
|
const ext = platform === "win32" ? ".exe" : "";
|
|
return path.join(pkgDir, "bin", `agent-vm${ext}`);
|
|
}
|
|
|
|
// 1) The new arm64 key must resolve to the arm64 subpackage.
|
|
assert.strictEqual(
|
|
PLATFORM_PACKAGES["linux-arm64"],
|
|
"@wirenboard/agent-vm-linux-arm64",
|
|
"linux-arm64 must map to @wirenboard/agent-vm-linux-arm64",
|
|
);
|
|
|
|
// 2) x64 must still resolve (guards against an accidental clobber).
|
|
assert.strictEqual(
|
|
PLATFORM_PACKAGES["linux-x64"],
|
|
"@wirenboard/agent-vm-linux-x64",
|
|
"linux-x64 must map to @wirenboard/agent-vm-linux-x64",
|
|
);
|
|
|
|
// 3) Both linux platforms must produce the identical bin/ layout
|
|
// (only the package dir differs) — the arm64 subpackage ships its
|
|
// binary at bin/agent-vm exactly like x64 (see its package.json
|
|
// `files` list + the bin/.gitkeep placeholder).
|
|
const x64Bin = binPathFor("/pkg/agent-vm-linux-x64", "linux");
|
|
const arm64Bin = binPathFor("/pkg/agent-vm-linux-arm64", "linux");
|
|
assert.strictEqual(path.basename(x64Bin), "agent-vm");
|
|
assert.strictEqual(path.basename(arm64Bin), "agent-vm");
|
|
assert.strictEqual(
|
|
path.relative("/pkg/agent-vm-linux-x64", x64Bin),
|
|
path.relative("/pkg/agent-vm-linux-arm64", arm64Bin),
|
|
"arm64 and x64 must use the same bin/ layout",
|
|
);
|
|
|
|
// 4) A genuinely unsupported platform key must be absent so the
|
|
// launcher hits its "no prebuilt binary" error path cleanly.
|
|
assert.strictEqual(
|
|
PLATFORM_PACKAGES["sunos-sparc"],
|
|
undefined,
|
|
"unsupported platform keys must be absent (no fall-through entry)",
|
|
);
|
|
|
|
console.log("agent-vm dispatch test: OK (linux-x64, linux-arm64)");
|