From 89a433fda93cf50ba4ca5c7762f6b01328eb295e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20T=C3=B6rcsv=C3=A1ri?= Date: Wed, 1 Jul 2026 11:01:10 +0200 Subject: [PATCH] fix(trace): make ?trace= work by merging Module.ENV in the glue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The emscripten glue's `var ENV = {};` never merged Module.ENV, so boot.ts's Module.ENV.KICAD_TRACE was a silent no-op — and since environ_get on a pthread proxies to the main thread, the app/UI thread's getenv read the (empty) main ENV. Net: KICAD_TRACE never arrived and every KI_TRACE was a no-op. scripts/common/patch-env-shim.mjs post-processes each generated .js to merge Module.ENV into ENV (idempotent; runtime no-op when unset), wired into docker/build.sh's per-app host post-process next to the dyncall-shim inject. Replaces a fragile manual glue edit re-applied after every build. Verified: symbol-editor eager load now emits KI_TRACE_SYM_CHOOSER fatLoad timing. Co-Authored-By: Claude Opus 4.8 (1M context) --- docker/build.sh | 7 ++++ scripts/common/patch-env-shim.mjs | 56 +++++++++++++++++++++++++++++++ web/standalone/src/wasm/boot.ts | 9 +++-- 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 scripts/common/patch-env-shim.mjs diff --git a/docker/build.sh b/docker/build.sh index 1aa0a6e..a0b7857 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -266,6 +266,13 @@ postprocess_app() { kw_stage dyncall-shims ./scripts/common/inject-dyncall-shims.sh "${out_dir}/${app}.js" + # Merge Module.ENV into the runtime ENV: the emscripten glue never merges it, + # so ?trace= (boot.ts sets Module.ENV.KICAD_TRACE) was a silent no-op — + # environ_get on the app pthread proxies to the main thread, whose ENV stayed + # empty. Replaces a manual per-build glue edit. Idempotent; runtime no-op when + # Module.ENV is unset. See docs/features/libs/0013. + node ./scripts/common/patch-env-shim.mjs "${out_dir}/${app}.js" + # Apply wasm-emscripten-finalize on host (skipped in Docker due to memory limits) kw_stage finalize ./scripts/common/apply-finalize.sh "${out_dir}/${app}.wasm" "${out_dir}/${app}.wasm" diff --git a/scripts/common/patch-env-shim.mjs b/scripts/common/patch-env-shim.mjs new file mode 100644 index 0000000..3c76295 --- /dev/null +++ b/scripts/common/patch-env-shim.mjs @@ -0,0 +1,56 @@ +// Patch an emscripten glue `.js` so the runtime `ENV` merges `Module.ENV`. +// +// Why: emscripten's generated glue does `var ENV = {};` and (in this toolchain) +// never merges the caller's `Module.ENV`, so setting `Module.ENV` from JS is a +// silent no-op. The standalone's boot.ts uses `Module.ENV.KICAD_TRACE` to drive +// the `?trace=` profiling harness. Crucially, `_environ_get` on a pthread PROXIES +// TO THE MAIN THREAD, so `getenv()` on the app pthread reads the MAIN thread's +// `ENV` via `getEnvStrings()` — which stays empty without this merge. Result: +// `KICAD_TRACE` never reaches `TRACE_MANAGER`, and every `KI_TRACE(...)` is a +// silent no-op. Merging `Module.ENV` makes `?trace=` (and any future Module.ENV +// use) actually work. Safe no-op when `Module.ENV` is unset (normal boots). +// +// This replaces a fragile manual edit that had to be re-applied to the generated +// glue after every build (docs/features/libs/0013). Idempotent; run per app in +// the build's host post-process. Usage: node patch-env-shim.mjs [...] + +import { readFileSync, writeFileSync } from "node:fs"; + +// Guard on `typeof Module` (glue-local on main; undefined in some worker realms — +// harmless there, since environ_get proxies to main anyway). Marker keeps it +// idempotent and lets the region-replace below re-normalize a prior insert. +const SHIM = + 'try { if (typeof Module !== "undefined" && Module && Module.ENV)' + + " for (var _k in Module.ENV) ENV[_k] = Module.ENV[_k]; }" + + " catch (e) {} /*PCBJAM_ENV_SHIM*/"; + +// Match `var ENV = {};` plus whatever sits between it and the next glue function +// (`var getExecutableName`), so re-running normalizes an earlier insert instead +// of stacking. The `getExecutableName` anchor is stable across emscripten builds. +const REGION = /var ENV = \{\};[\s\S]*?\n(?=var getExecutableName)/; + +let failed = 0; +for (const file of process.argv.slice(2)) { + let src; + try { + src = readFileSync(file, "utf8"); + } catch (e) { + console.error(`[env-shim] cannot read ${file}: ${e.message}`); + failed++; + continue; + } + if (!REGION.test(src)) { + console.error(`[env-shim] no \`var ENV = {};\` anchor in ${file} — skipped`); + failed++; + continue; + } + const patched = src.replace(REGION, `var ENV = {};\n${SHIM}\n\n`); + if (patched === src) { + console.log(`[env-shim] ${file} already current`); + continue; + } + writeFileSync(file, patched); + console.log(`[env-shim] patched ${file}`); +} + +process.exit(failed ? 1 : 0); diff --git a/web/standalone/src/wasm/boot.ts b/web/standalone/src/wasm/boot.ts index 301888c..9d204ca 100644 --- a/web/standalone/src/wasm/boot.ts +++ b/web/standalone/src/wasm/boot.ts @@ -190,9 +190,12 @@ async function doBoot(opts: BootOptions): Promise { // Dev/diagnostics: ?trace= turns on a KiCad trace channel for // this boot (e.g. ?trace=KI_TRACE_SYM_CHOOSER for symbol-chooser timing). Set on - // the page's Module.ENV (main thread) AND seeded into each pthread worker via - // pthreadWorkerScript (the app/UI thread under PROXY_TO_PTHREAD); the glue's - // ENV-merge shim feeds it into the C environ that wxGetEnv reads. + // the page's Module.ENV (main thread). Under PROXY_TO_PTHREAD the app/UI thread + // is a pthread, but `environ_get` on a pthread proxies to the MAIN thread — so + // the main thread's ENV is what `getenv("KICAD_TRACE")` reads. The build's + // patch-env-shim.mjs makes the glue merge Module.ENV into that ENV (emscripten's + // glue otherwise ignores Module.ENV); the per-worker seed below is a harmless + // belt-and-suspenders. See docs/features/libs/0013. const traceMask = new URLSearchParams(window.location.search).get("trace"); // libs: install the provider (must exist before any plugin call can suspend