pcbjam/web/standalone/scripts/link-wasm.mjs
Viktor Vaczi 9c475a804e jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose
The runtime is JSPI-only; this removes everything that still pretended
otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove
the inventory; every deletion verified by grep closure + full gates.

Broken-right-now fixes:
- deploy-staging.yml passed the retired opt_level input — the workflow
  could not even start. Removed.
- env.sh carried dead exports with a live -sASYNCIFY=1 inside
  (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the
  WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason.
- docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone.

Dead weight removed:
- binaryen submodule (nothing builds or invokes it), wasm-opt-bench
  workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines
  of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess
  scaffolding (existed to parallelize the deleted wasm-opt phase; the
  postprocess is a seconds-long node script and now runs inline),
  build-monitor's dead asyncify rows, sched-context orphan build
  output, dead .gitignore entries, the .jspi-assets spike dir (the two
  wf-result research JSONs moved to docs/features/async/migration-evidence/).
- bindings: fiber_park.h + its 12 embind registrations (broken-if-
  called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route,
  main_stack_runner.h + 5 includes, the always-null context-sleep weak
  hook in nanosleep_yield.c.
- shim: the backend field (installed-flag idempotency instead),
  noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the
  WasmTool fallback and string-dump normalize branch).
- web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts
  (gerber-demo keeps it: it loads the deployed CDN release, which
  predates emscripten 6 — noted inline).

Conditionals: all 'backend === jspi' checks reduced to scheduler-
presence checks; races_quiescent re-keyed from Asyncify.state (vacuous)
to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive,
which is the probing activation's own window by definition).

Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→
JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS,
kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests),
collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→
wasmTrapSignatures (lists byte-identical).

Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused
asserts re-keyed to live JSPI beacons; eeschema-load's failure message
no longer sends the developer to a deleted script; wait-beacons' dead
families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is
unconstructible); the embind test.fail re-gated with the JSPI reason
(plain embind invokers cannot suspend — verified still failing);
lint-determinism now scans tests/jspi (166 files clean);
eeschema-collab local-move gated to chromium (~50% flaky on FF even
solo; pcbnew twin covers both engines).

Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md
describes the single-phase build; docs/features/async/README.md
banner-marked historical and repointed at the NEW
23-jspi-runtime.md (current architecture: export census, turnstile,
libcontext ownership + refusal contract, embind call shapes, the
em-pthread service-wrapper trick, exception policy, known gaps).

Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the
quiescence-probe fix; the 3 other reds were verified contention flakes
solo-green or the documented FF gate), web 76/0, jspi 18/18 both
engines, vitest 295/295 + 17/17, all lints green, live-app census
clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00

105 lines
3.9 KiB
JavaScript

#!/usr/bin/env node
// Make the WASM runtime artifacts available SAME-ORIGIN to the app without a
// third copy: symlink standalone/public/wasm -> the synced artifact dir
// (tests/apps/kicad, populated by tests/scripts/setup-kicad-wasm.sh). Vite then
// serves them at /wasm from the app's own origin — required because KiCad WASM
// (a COEP/cross-origin-isolated document) refuses to load its
// glue/wasm from a different origin.
//
// No bytes are duplicated; the link points straight at the synced dir.
// Override the target with WASM_SRC_DIR (absolute, or relative to cwd).
import { spawnSync } from "node:child_process";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const publicDir = path.resolve(scriptDir, "../public");
const linkPath = path.join(publicDir, "wasm");
// scriptDir = web/standalone/scripts → repo root is three levels up.
const repoRoot = path.resolve(scriptDir, "../../..");
const setupScript = path.join(repoRoot, "tests/scripts/setup-kicad-wasm.sh");
const targetAbs = process.env.WASM_SRC_DIR
? path.resolve(process.cwd(), process.env.WASM_SRC_DIR)
: path.join(repoRoot, "tests/apps/kicad");
// Sync freshly-built artifacts from output/ into tests/apps/kicad before
// linking, so `npm run dev` always serves the latest build instead of whatever
// was synced last. Skipped when WASM_SRC_DIR overrides the target: the setup
// script only writes to tests/apps/kicad, so running it would be a no-op there
// (and would mislead by syncing a dir we're not even linking to).
// Non-fatal: a missing/empty build must not block the dev server from starting.
// The setup script exits non-zero when no artifacts exist anywhere; we warn and
// fall through to the symlink, which already degrades gracefully (see below).
function syncArtifacts() {
if (process.env.WASM_SRC_DIR) {
console.log("[link-wasm] WASM_SRC_DIR set — skipping output/ sync");
return;
}
const res = spawnSync("bash", [setupScript], { stdio: "inherit" });
if (res.error) {
console.warn(
`[link-wasm] could not run setup-kicad-wasm.sh: ${res.error.message}`,
);
} else if (res.status !== 0) {
console.warn(
`[link-wasm] setup-kicad-wasm.sh exited ${res.status}` +
`serving whatever is already in ${path.relative(repoRoot, targetAbs)}`,
);
}
}
// Relative link so it stays valid if the repo moves.
const targetRel = path.relative(publicDir, targetAbs);
async function statOrNull(p, { follow = true } = {}) {
try {
return follow ? await fs.stat(p) : await fs.lstat(p);
} catch {
return null;
}
}
async function main() {
syncArtifacts();
await fs.mkdir(publicDir, { recursive: true });
const link = await statOrNull(linkPath, { follow: false });
if (link?.isSymbolicLink()) {
const current = await fs.readlink(linkPath);
if (path.resolve(publicDir, current) === targetAbs) {
// already correct
} else {
await fs.rm(linkPath);
await fs.symlink(targetRel, linkPath, "dir");
}
} else if (link) {
// A real dir/file (e.g. an earlier copy) — remove and link.
await fs.rm(linkPath, { recursive: true, force: true });
await fs.symlink(targetRel, linkPath, "dir");
} else {
await fs.symlink(targetRel, linkPath, "dir");
}
const target = await statOrNull(targetAbs);
if (!target?.isDirectory()) {
console.warn(
`[link-wasm] target not found: ${targetAbs}\n` +
`[link-wasm] run tests/scripts/setup-kicad-wasm.sh (or set WASM_SRC_DIR). ` +
`App will run but tools won't load.`,
);
return;
}
const hasWx = await statOrNull(path.join(targetAbs, "wx.js"));
console.log(
`[link-wasm] public/wasm -> ${path.relative(repoRoot, targetAbs)}` +
(hasWx ? "" : " (warning: wx.js missing in target)"),
);
}
main().catch((err) => {
console.error("[link-wasm]", err);
process.exit(1);
});