2025-12-08 12:07:01 +01:00
#!/bin/bash
2026-05-29 12:01:44 +02:00
# Build a KiCad app (pcbnew, eeschema, calculator) inside Docker, then run
jspi: retire the asyncify pipeline — knob, post-link tail, binaryen hooks
Phase 8 in the parent repo. Deleted: asyncify-scheduler.js, apply-asyncify.sh,
apply-finalize.sh, inject-dyncall-shims.sh, asyncify-imports/removelist.txt,
the wasm-opt/finalize stub pair, scripts/binaryen-hoist-pass/ (the fork stays
a dormant submodule; removal is a follow-up), bench/wasm-opt-bench.sh (README
marked historical), wasm/shims/context_sleep.cpp, and the sched-context
harness app + Makefile targets.
PCBJAM_ASYNC_BACKEND is gone: build-wx-wasm.sh hardcodes the jspi stamp
(still force-cleans pre-migration trees), build-kicad-target.sh gives editors
the JSPI link surface and the CLIs nothing (they pin ASYNCIFY=0), the stub
dance is replaced by an unconditional .real-restore, build-wasm-test.sh lost
its whole post-link loop, docker/build.sh's postprocess is the ENV shim only,
and Makefile.wasm links every app JSPI with the scheduler shim as a tracked
prerequisite. pcbjam_async_policy.h keys on __EMSCRIPTEN__.
jspi-scheduler.js: wxWasmMainLoopPump dropped from the wrap census (the
export died with the D5 detach); inert [TRACE] instrumentation removed.
CI: wasm-build.yml rewritten for the single-cache pipeline (one output cache
keyed on compile inputs; post-processed bytes cached after the shim);
opt_level input removed from both callers. wasm-cache-hash.mjs inputs now
cover patch-env-shim.mjs + jspi-scheduler.js + jspi-exports.txt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDeBaKKhQztd8KiVtHuyXr
2026-08-13 08:39:12 +02:00
# the ENV shim on the host.
2026-05-29 06:39:23 +02:00
#
# Usage:
2026-06-11 07:46:48 +02:00
# ./docker/build.sh <app>[,<app>...] [args...]
2026-05-29 12:01:44 +02:00
#
# Apps:
2026-07-02 14:48:11 +02:00
# kicad_editor merged PCB + schematic editor image — serves all four editors
# (PCB / Footprint / Schematic / Symbol) via runtime --frame
2026-06-02 13:35:37 +02:00
# calculator PCB calculator
# pl_editor drawing-sheet editor
2026-06-03 14:23:57 +02:00
# gerbview Gerber viewer
2026-06-11 07:46:48 +02:00
# all build all of the above
2026-07-02 14:48:11 +02:00
# pcbnew standalone PCB engine (debug aid; not deployed — kicad_editor is)
# eeschema standalone schematic engine (debug aid; not deployed)
2026-06-11 07:46:48 +02:00
#
# A comma-separated list builds just those apps in order (e.g.
# "calculator,pl_editor" — used to exercise the multi-app pipeline cheaply).
2026-05-29 12:01:44 +02:00
#
# Any extra args are forwarded to scripts/kicad/build-<app>.sh (e.g. -j 8,
# --full, --release, --diag=gal).
2025-12-13 22:13:31 +01:00
#
# The build is split into two phases:
jspi: retire the asyncify pipeline — knob, post-link tail, binaryen hooks
Phase 8 in the parent repo. Deleted: asyncify-scheduler.js, apply-asyncify.sh,
apply-finalize.sh, inject-dyncall-shims.sh, asyncify-imports/removelist.txt,
the wasm-opt/finalize stub pair, scripts/binaryen-hoist-pass/ (the fork stays
a dormant submodule; removal is a follow-up), bench/wasm-opt-bench.sh (README
marked historical), wasm/shims/context_sleep.cpp, and the sched-context
harness app + Makefile targets.
PCBJAM_ASYNC_BACKEND is gone: build-wx-wasm.sh hardcodes the jspi stamp
(still force-cleans pre-migration trees), build-kicad-target.sh gives editors
the JSPI link surface and the CLIs nothing (they pin ASYNCIFY=0), the stub
dance is replaced by an unconditional .real-restore, build-wasm-test.sh lost
its whole post-link loop, docker/build.sh's postprocess is the ENV shim only,
and Makefile.wasm links every app JSPI with the scheduler shim as a tracked
prerequisite. pcbjam_async_policy.h keys on __EMSCRIPTEN__.
jspi-scheduler.js: wxWasmMainLoopPump dropped from the wrap census (the
export died with the D5 detach); inert [TRACE] instrumentation removed.
CI: wasm-build.yml rewritten for the single-cache pipeline (one output cache
keyed on compile inputs; post-processed bytes cached after the shim);
opt_level input removed from both callers. wasm-cache-hash.mjs inputs now
cover patch-env-shim.mjs + jspi-scheduler.js + jspi-exports.txt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDeBaKKhQztd8KiVtHuyXr
2026-08-13 08:39:12 +02:00
# 1. Docker: Compile KiCad to WASM (fully finalized; JSPI links in-container)
# 2. Host: ENV merge shim on the glue (patch-env-shim.mjs)
2026-06-11 07:46:48 +02:00
#
# KICAD_PIPELINE=1 (multi-app builds only): run phase 2 of each app in the
# background while the next app compiles in the container. wasm-opt is
# Amdahl-capped at ~4 effective cores, so on a many-core CI box the container
# would otherwise sit idle for the 1-2h of host-side wasm-opt (run 27226030304:
# 103 min of the 4h was tools serialized behind each other's wasm-opt). At most
2026-07-01 12:18:57 +02:00
# KICAD_PIPELINE_JOBS (default 2) postprocesses run concurrently — pcbnew's wasm-opt
# pass peaks ~34 GB RSS, so 2 fits the 128 GB CI box but NOT a dev Mac: leave
2026-06-11 07:46:48 +02:00
# KICAD_PIPELINE unset locally.
2025-12-13 22:13:31 +01:00
#
# Binaryen is downloaded automatically - no prerequisites needed.
2026-06-01 17:31:03 +02:00
# Auto-launch the live progress dashboard in this terminal (handled by logging.sh,
# which owns the TTY before it re-execs us with output redirected). Set KICAD_NO_MONITOR=1
# to disable. MUST be set before sourcing logging.sh — that's where the dashboard is
# launched, in the pre-re-exec process.
export KICAD_MONITOR = 1
2026-05-29 12:01:44 +02:00
# Redirect all output to a log file (re-execs script with redirection).
# MUST be sourced before arg parsing — the re-exec relies on the original
# "$@", so any shifts before this point would strip args from the re-exec.
2026-05-29 06:39:23 +02:00
source " $( dirname " $0 " ) /../scripts/common/logging.sh "
2026-06-01 17:31:03 +02:00
# Build-progress markers (parsed by scripts/build-monitor.sh).
source " $( dirname " $0 " ) /../scripts/common/stages.sh "
2026-06-29 19:50:18 +02:00
# Pinned toolchain version (single source of truth). Exported so the compose build.args can pass it
# into the Docker image's emsdk install — bumping the toolchain is then a one-line edit in versions.sh.
source " $( dirname " $0 " ) /../scripts/common/versions.sh "
export EMSCRIPTEN_VERSION
2025-12-08 12:07:01 +01:00
set -e
2026-07-03 14:42:38 +02:00
# Stop the builder container when the build ends (any exit path). A lingering
# idle builder keeps the Docker Desktop VM ballooned — each project's builder
# is capped at ${KICAD_DOCKER_MEM:-32G}, and with per-worktree compose projects
# two forgotten containers once read as ~56 GB of host RAM (page cache +
# high-water ballooning). The container is pure scaffolding: all caches live in
# the named volumes and `up -d` restarts it in seconds. Set
# KICAD_KEEP_CONTAINER=1 to keep it running for interactive exec/debugging.
_COMPOSE_FILE = " $( cd " $( dirname " $0 " ) " && pwd ) /docker-compose.yml "
stop_builder( ) {
if [ [ " ${ KICAD_KEEP_CONTAINER :- 0 } " != "1" ] ] ; then
docker compose -f " ${ _COMPOSE_FILE } " stop >/dev/null 2>& 1 || true
fi
}
2026-06-01 17:31:03 +02:00
# Emit a completion/failure marker no matter how the build ends, so the monitor
# can stop on a clean "done" or show an aborted state instead of hanging.
2026-07-03 14:42:38 +02:00
trap '_rc=$?; if [ $_rc -eq 0 ]; then kw_done; else kw_fail $_rc; fi; stop_builder' EXIT
2026-06-01 17:31:03 +02:00
# On Ctrl-C, mark the build aborted so the final dashboard frame shows failed
# (not a stale "running" state). The EXIT trap above also fires; the monitor reads
# the last marker, so the duplicate is harmless.
trap 'kw_fail 130; exit 130' INT TERM
2025-12-08 12:07:01 +01:00
cd " $( dirname " $0 " ) /.. "
2026-07-19 15:59:21 +02:00
VALID_APPS = "kicad_editor | pcbnew | eeschema | calculator | pl_editor | gerbview | kicad_tools | occ_service | ngspice_service | all"
2026-05-29 12:01:44 +02:00
usage( ) {
2026-06-11 07:46:48 +02:00
echo "Usage: ./docker/build.sh <app>[,<app>...] [args...]" >& 2
2026-05-29 12:01:44 +02:00
echo " <app>: ${ VALID_APPS } " >& 2
echo " args: forwarded to scripts/kicad/build-<app>.sh (e.g. -j 8, --release)" >& 2
}
# First positional arg must be the app name. No default — picking one would
# silently build the wrong thing for someone who forgot the argument.
if [ [ " ${ 1 :- } " = = "-h" ] ] || [ [ " ${ 1 :- } " = = "--help" ] ] ; then
usage
exit 0
fi
if [ [ $# -lt 1 ] ] || [ [ " $1 " = = -* ] ] ; then
echo "Error: missing <app> argument" >& 2
usage
exit 1
2026-05-29 06:39:23 +02:00
fi
2026-05-29 12:01:44 +02:00
APP_NAME = " $1 "
shift
2026-05-29 06:39:23 +02:00
2026-06-11 07:46:48 +02:00
# Expand the app argument into APPS[]: "all", a single app, or a comma list.
2026-07-02 14:48:11 +02:00
# kicad_editor first in "all" — the merged image is the largest bundle, so its
# host-side wasm-opt chain is the critical path and must start as early as
# possible (especially with KICAD_PIPELINE=1). pcbnew/eeschema stay buildable as
# standalone debug aids but are not part of "all" (not deployed).
2026-07-14 15:12:17 +02:00
# kicad_tools joined "all" for the runner-image CI (tasks-runner 0001 R2) —
# it finalizes in-container (no host wasm-opt tail), so it never contends
# with the editor's critical path.
2026-06-11 07:46:48 +02:00
if [ [ " $APP_NAME " = = "all" ] ] ; then
2026-07-19 15:59:21 +02:00
APPS = ( kicad_editor occ_service ngspice_service calculator pl_editor gerbview kicad_tools)
2026-06-11 07:46:48 +02:00
else
IFS = ',' read -r -a APPS <<< " $APP_NAME "
for app in " ${ APPS [@] } " ; do
case " $app " in
2026-07-19 15:59:21 +02:00
kicad_editor| pcbnew| eeschema| calculator| pl_editor| gerbview| kicad_tools| occ_service| ngspice_service) ; ;
2026-06-11 07:46:48 +02:00
*)
echo " Error: unknown app ' $app ' (expected: ${ VALID_APPS } ) " >& 2
usage
exit 1
; ;
esac
done
fi
2026-05-29 06:39:23 +02:00
2026-06-01 16:30:29 +02:00
# Use branch name as Docker Compose project name for isolated containers/volumes.
# Honor a pre-set COMPOSE_PROJECT_NAME so a build can target an existing volume
# (e.g. reuse another branch's already-provisioned deps).
2026-01-03 07:31:46 +01:00
BRANCH_NAME = $( git rev-parse --abbrev-ref HEAD | tr '/' '-' | tr '[:upper:]' '[:lower:]' )
2026-06-01 16:30:29 +02:00
export COMPOSE_PROJECT_NAME = " ${ COMPOSE_PROJECT_NAME :- kicad -wasm- ${ BRANCH_NAME } } "
2026-01-03 07:31:46 +01:00
echo " Using Docker project: ${ COMPOSE_PROJECT_NAME } "
2026-05-29 06:39:23 +02:00
echo " Building app: ${ APP_NAME } "
2026-01-03 07:31:46 +01:00
2026-06-22 14:07:50 +02:00
# Build phase (cache split). The container compile produces an OPT-INDEPENDENT
# base wasm; the only opt-DEPENDENT work is the final `wasm-opt -O$LEVEL` shrink
jspi: retire the asyncify pipeline — knob, post-link tail, binaryen hooks
Phase 8 in the parent repo. Deleted: asyncify-scheduler.js, apply-asyncify.sh,
apply-finalize.sh, inject-dyncall-shims.sh, asyncify-imports/removelist.txt,
the wasm-opt/finalize stub pair, scripts/binaryen-hoist-pass/ (the fork stays
a dormant submodule; removal is a follow-up), bench/wasm-opt-bench.sh (README
marked historical), wasm/shims/context_sleep.cpp, and the sched-context
harness app + Makefile targets.
PCBJAM_ASYNC_BACKEND is gone: build-wx-wasm.sh hardcodes the jspi stamp
(still force-cleans pre-migration trees), build-kicad-target.sh gives editors
the JSPI link surface and the CLIs nothing (they pin ASYNCIFY=0), the stub
dance is replaced by an unconditional .real-restore, build-wasm-test.sh lost
its whole post-link loop, docker/build.sh's postprocess is the ENV shim only,
and Makefile.wasm links every app JSPI with the scheduler shim as a tracked
prerequisite. pcbjam_async_policy.h keys on __EMSCRIPTEN__.
jspi-scheduler.js: wxWasmMainLoopPump dropped from the wrap census (the
export died with the D5 detach); inert [TRACE] instrumentation removed.
CI: wasm-build.yml rewritten for the single-cache pipeline (one output cache
keyed on compile inputs; post-processed bytes cached after the shim);
opt_level input removed from both callers. wasm-cache-hash.mjs inputs now
cover patch-env-shim.mjs + jspi-scheduler.js + jspi-exports.txt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDeBaKKhQztd8KiVtHuyXr
2026-08-13 08:39:12 +02:00
# Splitting compile and postprocess lets CI cache the expensive compile once
# and re-run just the host tail — see .github/workflows/.
2026-06-22 14:07:50 +02:00
# (default) both — compile in-container, then host post-process.
# --compile-only — only the in-container compile → base wasm in output/.
jspi: retire the asyncify pipeline — knob, post-link tail, binaryen hooks
Phase 8 in the parent repo. Deleted: asyncify-scheduler.js, apply-asyncify.sh,
apply-finalize.sh, inject-dyncall-shims.sh, asyncify-imports/removelist.txt,
the wasm-opt/finalize stub pair, scripts/binaryen-hoist-pass/ (the fork stays
a dormant submodule; removal is a follow-up), bench/wasm-opt-bench.sh (README
marked historical), wasm/shims/context_sleep.cpp, and the sched-context
harness app + Makefile targets.
PCBJAM_ASYNC_BACKEND is gone: build-wx-wasm.sh hardcodes the jspi stamp
(still force-cleans pre-migration trees), build-kicad-target.sh gives editors
the JSPI link surface and the CLIs nothing (they pin ASYNCIFY=0), the stub
dance is replaced by an unconditional .real-restore, build-wasm-test.sh lost
its whole post-link loop, docker/build.sh's postprocess is the ENV shim only,
and Makefile.wasm links every app JSPI with the scheduler shim as a tracked
prerequisite. pcbjam_async_policy.h keys on __EMSCRIPTEN__.
jspi-scheduler.js: wxWasmMainLoopPump dropped from the wrap census (the
export died with the D5 detach); inert [TRACE] instrumentation removed.
CI: wasm-build.yml rewritten for the single-cache pipeline (one output cache
keyed on compile inputs; post-processed bytes cached after the shim);
opt_level input removed from both callers. wasm-cache-hash.mjs inputs now
cover patch-env-shim.mjs + jspi-scheduler.js + jspi-exports.txt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDeBaKKhQztd8KiVtHuyXr
2026-08-13 08:39:12 +02:00
# --postprocess-only — only the host post-process (ENV merge shim) on the
2026-06-22 14:07:50 +02:00
# existing output/ base wasm; NO container needed
2026-06-29 19:50:18 +02:00
# (build-wasm-opt.sh self-provisions the Binaryen submodule).
2026-06-22 14:07:50 +02:00
# Extracted here so they are NOT forwarded to the inner build-<app>.sh scripts.
PHASE = "both"
_FILTERED = ( )
for _arg in " $@ " ; do
case " $_arg " in
--compile-only) PHASE = "compile" ; ;
--postprocess-only) PHASE = "postprocess" ; ;
*) _FILTERED += ( " $_arg " ) ; ;
esac
done
set -- " ${ _FILTERED [@]+ " ${ _FILTERED [@] } " } "
2025-12-15 11:47:50 +01:00
# Add -j 10 by default if no -j flag is given
ARGS = ( " $@ " )
if [ [ ! " ${ ARGS [*] } " = ~ " -j " ] ] ; then
ARGS += ( "-j" "10" )
fi
2026-06-22 14:07:50 +02:00
# Container compile + source sync — skipped entirely for --postprocess-only,
# which is pure host work on the already-built base wasm in output/.
if [ [ " $PHASE " != "postprocess" ] ] ; then
2026-06-29 19:50:18 +02:00
# Start container if not running. --build so the image is rebuilt when the pinned EMSCRIPTEN_VERSION
# (build-arg from versions.sh) changes; Docker layer-caches it to a near no-op when unchanged.
docker compose -f docker/docker-compose.yml up -d --build
2025-12-08 12:07:01 +01:00
2026-01-05 10:20:08 +01:00
# Sync source code to container volume (fixes macOS Docker VirtioFS issues)
# Use --checksum to only transfer files with different CONTENT, not timestamps.
# This avoids the timestamp mismatch cycle that caused full rebuilds every time.
# Transferred files get current container time, so make detects them correctly.
2026-06-01 17:31:03 +02:00
kw_stage container-sync
jspi: migration phases 0-7 — build knob, scheduler shim, test successor suite
Toolchain: emsdk 6.0.6 (versions.sh; cache-hash keys on it). Build knob
PCBJAM_ASYNC_BACKEND=jspi|asyncify: build-kicad-target.sh links editors with
-sJSPI + -sJSPI_EXPORTS=@scripts/common/jspi-exports.txt + --pre-js
jspi-scheduler.js (no DYNCALLS, no post-link asyncify pipeline); wx build
stamps the backend and forces clean on flip or unknown provenance;
docker/build.sh passes the knob, seeds the emscripten ports cache from the
volume every launch, jspi postprocess = patch-env-shim only.
scripts/common/shims/jspi-scheduler.js: the JSPI successor scheduler —
token-wait registry, resume turnstile (one armed resume between engine
re-entries, SP swaps only at microtask boundaries), green-region spill
stacks (16-aligned tops), S1 embind mutator FIFO lane + parker wraps, S6
shutdown, libctx integration hooks (suspend/end/quarantine + g_current
arm/clear), SuspendError attributor, lost-wake + stuck-window watchdogs,
__wxWaitDump observability.
Embind: PARKER registrations get emscripten::async() under PCBJAM_JSPI
(wasm/bindings/pcbjam_async_policy.h). nanosleep yields route via the shim.
Tests: tests/asyncify -> tests/jspi successor suite (jspi-stack red/green
shadow-stack battery, jspi-coroutine MiniCoro harness, suspend-races
semantic scenarios + __wxWaitDump books coherence); projects jspi-firefox/
jspi-chrome (asyncify-webkit retired — no JSPI in WebKit); unconditional
Firefox JSPI pref; guard-beacons -> wait-beacons (+wxScheduler/libctxJspi
families); Makefile.wasm links test apps against JSPI with the shim as a
tracked link prerequisite.
Web: WasmTool setRo await + __wxWaitDump forensics, open-flow contained
promise, scheduler-shim.test.ts retargeted (8 green).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDeBaKKhQztd8KiVtHuyXr
2026-08-13 07:06:24 +02:00
# Re-seed the emscripten ports cache from the persistent volume: /emsdk lives
# in the container layer, so any image-context edit recreates the container
# and wipes the cache — and the in-container github fetch is flaky. Seeds are
# staged once into the kicad-build-cache volume (emcache/ports); this copy
# survives every recreation. No-op when the staging dir is absent.
docker compose -f docker/docker-compose.yml exec kicad-wasm-builder bash -c \
' if [ -d /workspace/build-wasm/emcache/ports ] ; then \
mkdir -p /emsdk/upstream/emscripten/cache/ports && \
cp -a /workspace/build-wasm/emcache/ports/. /emsdk/upstream/emscripten/cache/ports/ && \
echo "seeded emscripten ports cache from volume" ; fi ' || true
2025-12-27 01:07:44 +01:00
echo "Syncing source code to container..."
2026-05-28 17:21:26 +02:00
# rsync into the macOS-backed volume intermittently hits transient VirtioFS glitches:
# temp-file rename failures (exit 23) or vanished-source files (exit 24, harmless).
# --inplace avoids the temp-file+rename pattern that triggers exit 23; retry up to 3x
# for any residual flakiness (--checksum makes each retry skip already-synced files).
sync_rc = 0
for sync_attempt in 1 2 3; do
if docker compose -f docker/docker-compose.yml exec kicad-wasm-builder \
rsync -r --delete --checksum --inplace \
--exclude= "build-wasm" \
--exclude= "output" \
--exclude= ".git" \
--exclude= "logs" \
--exclude= ".idea" \
--exclude= "node_modules" \
--exclude= "tools/emsdk" \
/workspace-host/ /workspace/
then
sync_rc = 0
else
sync_rc = $?
fi
{ [ $sync_rc -eq 0 ] || [ $sync_rc -eq 24 ] ; } && break
echo " rsync attempt ${ sync_attempt } failed (exit ${ sync_rc } ); retrying in 2s... "
sleep 2
done
if [ $sync_rc -ne 0 ] && [ $sync_rc -ne 24 ] ; then
echo " ERROR: source sync failed after retries (exit ${ sync_rc } ) " ; exit 1
fi
2025-12-27 01:07:44 +01:00
2026-06-22 14:07:50 +02:00
fi # end: container compile + sync guard ("$PHASE" != postprocess)
2026-05-29 12:01:44 +02:00
# Map an app name to its inner CMake build subdirectory. Most apps share their
# subdir name with the app name; pcb_calculator emits OUTPUT_NAME=calculator
# but lives under the pcb_calculator/ subtree.
kicad_subdir_for( ) {
case " $1 " in
2026-06-14 06:22:40 +02:00
calculator) echo "pcb_calculator" ; ;
pl_editor) echo "pagelayout_editor" ; ;
*) echo " $1 " ; ;
2026-05-29 12:01:44 +02:00
esac
}
2026-06-11 07:46:48 +02:00
# Phase 1 of one app: compile in the container and copy the output to ./output.
2026-06-01 17:31:03 +02:00
# Args: <app> [index] [total] — index/total drive the monitor's app counter.
2026-06-11 07:46:48 +02:00
compile_app( ) {
2026-05-29 06:39:23 +02:00
local app = " $1 "
2026-06-01 17:31:03 +02:00
local index = " ${ 2 :- 1 } "
local total = " ${ 3 :- 1 } "
2026-05-29 12:01:44 +02:00
local subdir
subdir = $( kicad_subdir_for " $app " )
2026-06-01 17:31:03 +02:00
kw_app " $app " " $index " " $total "
2026-05-29 06:39:23 +02:00
echo ""
2026-06-01 17:31:03 +02:00
echo " === Building ${ app } ( ${ index } / ${ total } ) === "
2026-05-29 06:39:23 +02:00
2026-06-12 10:18:16 +02:00
local out_dir = "output"
2026-06-11 00:19:53 +02:00
local kicad_build = " kicad- ${ app } "
2026-05-29 12:01:44 +02:00
# Run build inside the container.
2026-05-29 06:39:23 +02:00
# -e EMSDK=/emsdk: `docker compose exec` bypasses the entrypoint that sources
2026-05-29 12:01:44 +02:00
# emsdk_env.sh, so the build shell would lack emcc/embuilder on PATH. Setting
# EMSDK lets scripts/common/env.sh source /emsdk/emsdk_env.sh and activate the toolchain.
2026-07-10 13:13:51 +02:00
# BUILD_3D_VIEWER passes through EMPTY when the host didn't set it, so
# build-kicad-target.sh can apply per-app defaults (ON for editors, OFF
2026-07-10 17:09:35 +02:00
# for headless CLIs like kicad_tools — the gl1 shim needs glm).
2026-06-12 10:18:16 +02:00
docker compose -f docker/docker-compose.yml exec -e EMSDK = /emsdk \
2026-07-10 13:13:51 +02:00
-e BUILD_3D_VIEWER = " ${ BUILD_3D_VIEWER :- } " \
jspi: migration phases 0-7 — build knob, scheduler shim, test successor suite
Toolchain: emsdk 6.0.6 (versions.sh; cache-hash keys on it). Build knob
PCBJAM_ASYNC_BACKEND=jspi|asyncify: build-kicad-target.sh links editors with
-sJSPI + -sJSPI_EXPORTS=@scripts/common/jspi-exports.txt + --pre-js
jspi-scheduler.js (no DYNCALLS, no post-link asyncify pipeline); wx build
stamps the backend and forces clean on flip or unknown provenance;
docker/build.sh passes the knob, seeds the emscripten ports cache from the
volume every launch, jspi postprocess = patch-env-shim only.
scripts/common/shims/jspi-scheduler.js: the JSPI successor scheduler —
token-wait registry, resume turnstile (one armed resume between engine
re-entries, SP swaps only at microtask boundaries), green-region spill
stacks (16-aligned tops), S1 embind mutator FIFO lane + parker wraps, S6
shutdown, libctx integration hooks (suspend/end/quarantine + g_current
arm/clear), SuspendError attributor, lost-wake + stuck-window watchdogs,
__wxWaitDump observability.
Embind: PARKER registrations get emscripten::async() under PCBJAM_JSPI
(wasm/bindings/pcbjam_async_policy.h). nanosleep yields route via the shim.
Tests: tests/asyncify -> tests/jspi successor suite (jspi-stack red/green
shadow-stack battery, jspi-coroutine MiniCoro harness, suspend-races
semantic scenarios + __wxWaitDump books coherence); projects jspi-firefox/
jspi-chrome (asyncify-webkit retired — no JSPI in WebKit); unconditional
Firefox JSPI pref; guard-beacons -> wait-beacons (+wxScheduler/libctxJspi
families); Makefile.wasm links test apps against JSPI with the shim as a
tracked link prerequisite.
Web: WasmTool setRo await + __wxWaitDump forensics, open-flow contained
promise, scheduler-shim.test.ts retargeted (8 green).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDeBaKKhQztd8KiVtHuyXr
2026-08-13 07:06:24 +02:00
-e PCBJAM_ASYNC_BACKEND = " ${ PCBJAM_ASYNC_BACKEND :- } " \
2026-06-11 00:19:53 +02:00
kicad-wasm-builder \
2026-05-29 06:39:23 +02:00
" /workspace/scripts/kicad/build- ${ app } .sh " " ${ ARGS [@] } "
# Copy output to host-accessible directory.
# ${app}.wasm.debug.wasm contains DWARF debug info (when built with -gseparate-dwarf).
2026-06-01 17:31:03 +02:00
kw_stage copy-output
2026-06-11 00:19:53 +02:00
echo " Copying ${ app } build output to ./ ${ out_dir } /... "
2026-05-29 06:39:23 +02:00
docker compose -f docker/docker-compose.yml exec kicad-wasm-builder \
2026-06-11 00:19:53 +02:00
bash -c " mkdir -p /workspace/ ${ out_dir } && \
cp /workspace/build-wasm/${ kicad_build } /${ subdir } /${ app } .{ js,wasm,wasm.debug.wasm,wasm.map,worker.js} /workspace/${ out_dir } / 2>/dev/null || \
cp /workspace/build-wasm/${ kicad_build } /${ subdir } /${ app } .{ js,wasm} /workspace/${ out_dir } /; \
cp /workspace/build-wasm/${ kicad_build } /resources/images.tar.gz /workspace/${ out_dir } / 2>/dev/null || true; \
2026-06-12 10:18:16 +02:00
cp /workspace/wxwidgets/build/wasm/wx.js /workspace/${ out_dir } / 2>/dev/null || true; \
cp /workspace/wxwidgets/build/wasm/wx-dom.js /workspace/${ out_dir } / 2>/dev/null || true"
2026-05-29 06:39:23 +02:00
2026-06-11 07:46:48 +02:00
# The container runs as root, so files in the bind-mounted ./output land
# root-owned on the host. macOS Docker Desktop remaps ownership to the host
jspi: retire the asyncify pipeline — knob, post-link tail, binaryen hooks
Phase 8 in the parent repo. Deleted: asyncify-scheduler.js, apply-asyncify.sh,
apply-finalize.sh, inject-dyncall-shims.sh, asyncify-imports/removelist.txt,
the wasm-opt/finalize stub pair, scripts/binaryen-hoist-pass/ (the fork stays
a dormant submodule; removal is a follow-up), bench/wasm-opt-bench.sh (README
marked historical), wasm/shims/context_sleep.cpp, and the sched-context
harness app + Makefile targets.
PCBJAM_ASYNC_BACKEND is gone: build-wx-wasm.sh hardcodes the jspi stamp
(still force-cleans pre-migration trees), build-kicad-target.sh gives editors
the JSPI link surface and the CLIs nothing (they pin ASYNCIFY=0), the stub
dance is replaced by an unconditional .real-restore, build-wasm-test.sh lost
its whole post-link loop, docker/build.sh's postprocess is the ENV shim only,
and Makefile.wasm links every app JSPI with the scheduler shim as a tracked
prerequisite. pcbjam_async_policy.h keys on __EMSCRIPTEN__.
jspi-scheduler.js: wxWasmMainLoopPump dropped from the wrap census (the
export died with the D5 detach); inert [TRACE] instrumentation removed.
CI: wasm-build.yml rewritten for the single-cache pipeline (one output cache
keyed on compile inputs; post-processed bytes cached after the shim);
opt_level input removed from both callers. wasm-cache-hash.mjs inputs now
cover patch-env-shim.mjs + jspi-scheduler.js + jspi-exports.txt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDeBaKKhQztd8KiVtHuyXr
2026-08-13 08:39:12 +02:00
# user, but on a Linux CI runner the host-side ENV-shim step can't write
# into ./output. Hand ownership back.
2026-06-11 07:46:48 +02:00
docker compose -f docker/docker-compose.yml exec kicad-wasm-builder \
chown -R " $( id -u) : $( id -g) " /workspace/output || true
}
jspi: retire the asyncify pipeline — knob, post-link tail, binaryen hooks
Phase 8 in the parent repo. Deleted: asyncify-scheduler.js, apply-asyncify.sh,
apply-finalize.sh, inject-dyncall-shims.sh, asyncify-imports/removelist.txt,
the wasm-opt/finalize stub pair, scripts/binaryen-hoist-pass/ (the fork stays
a dormant submodule; removal is a follow-up), bench/wasm-opt-bench.sh (README
marked historical), wasm/shims/context_sleep.cpp, and the sched-context
harness app + Makefile targets.
PCBJAM_ASYNC_BACKEND is gone: build-wx-wasm.sh hardcodes the jspi stamp
(still force-cleans pre-migration trees), build-kicad-target.sh gives editors
the JSPI link surface and the CLIs nothing (they pin ASYNCIFY=0), the stub
dance is replaced by an unconditional .real-restore, build-wasm-test.sh lost
its whole post-link loop, docker/build.sh's postprocess is the ENV shim only,
and Makefile.wasm links every app JSPI with the scheduler shim as a tracked
prerequisite. pcbjam_async_policy.h keys on __EMSCRIPTEN__.
jspi-scheduler.js: wxWasmMainLoopPump dropped from the wrap census (the
export died with the D5 detach); inert [TRACE] instrumentation removed.
CI: wasm-build.yml rewritten for the single-cache pipeline (one output cache
keyed on compile inputs; post-processed bytes cached after the shim);
opt_level input removed from both callers. wasm-cache-hash.mjs inputs now
cover patch-env-shim.mjs + jspi-scheduler.js + jspi-exports.txt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDeBaKKhQztd8KiVtHuyXr
2026-08-13 08:39:12 +02:00
# Phase 2 of one app: host-side post-processing (ENV merge shim). Pure host
# work on output/${app}.* — independent of the
2026-06-11 07:46:48 +02:00
# container, which is what makes it safe to run in the background while the
# next app compiles.
postprocess_app( ) {
local app = " $1 "
2026-06-12 10:18:16 +02:00
local out_dir = "output"
2026-06-11 07:46:48 +02:00
2026-07-19 15:59:21 +02:00
# The headless CLI and the OCC/ngspice services are finalized in-container
# (real tools, small -g0 wasm) and build with ASYNCIFY=0, so they need no
# host post-processing (no dyncall shims, no finalize, no asyncify).
if [ " $app " = "kicad_tools" ] || [ " $app " = "occ_service" ] || [ " $app " = "ngspice_service" ] ; then
2026-06-18 12:21:15 +02:00
echo " Skipping host post-processing for ${ app } (finalized in-container) "
return 0
fi
jspi: retire the asyncify pipeline — knob, post-link tail, binaryen hooks
Phase 8 in the parent repo. Deleted: asyncify-scheduler.js, apply-asyncify.sh,
apply-finalize.sh, inject-dyncall-shims.sh, asyncify-imports/removelist.txt,
the wasm-opt/finalize stub pair, scripts/binaryen-hoist-pass/ (the fork stays
a dormant submodule; removal is a follow-up), bench/wasm-opt-bench.sh (README
marked historical), wasm/shims/context_sleep.cpp, and the sched-context
harness app + Makefile targets.
PCBJAM_ASYNC_BACKEND is gone: build-wx-wasm.sh hardcodes the jspi stamp
(still force-cleans pre-migration trees), build-kicad-target.sh gives editors
the JSPI link surface and the CLIs nothing (they pin ASYNCIFY=0), the stub
dance is replaced by an unconditional .real-restore, build-wasm-test.sh lost
its whole post-link loop, docker/build.sh's postprocess is the ENV shim only,
and Makefile.wasm links every app JSPI with the scheduler shim as a tracked
prerequisite. pcbjam_async_policy.h keys on __EMSCRIPTEN__.
jspi-scheduler.js: wxWasmMainLoopPump dropped from the wrap census (the
export died with the D5 detach); inert [TRACE] instrumentation removed.
CI: wasm-build.yml rewritten for the single-cache pipeline (one output cache
keyed on compile inputs; post-processed bytes cached after the shim);
opt_level input removed from both callers. wasm-cache-hash.mjs inputs now
cover patch-env-shim.mjs + jspi-scheduler.js + jspi-exports.txt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDeBaKKhQztd8KiVtHuyXr
2026-08-13 08:39:12 +02:00
# JSPI: the app links fully finalized with the real in-container tools —
# no dyncall shims, no host finalize, no asyncify pass. Only the ENV merge
# shim remains: the emscripten glue never merges Module.ENV into the
# runtime ENV (?trace= would be a silent no-op — see docs/features/libs/0013).
kw_stage env-shim
2026-07-01 11:01:10 +02:00
node ./scripts/common/patch-env-shim.mjs " ${ out_dir } / ${ app } .js "
2026-05-29 06:39:23 +02:00
}
2026-06-11 07:46:48 +02:00
# --- Pipelined driver state (KICAD_PIPELINE=1) ---
# One background postprocess per app; logs + rc files land in logs/build/ so the
# interleaved output stays readable and failures survive until the final wait.
PIPELINE_PIDS = ( )
PIPELINE_APPS_BG = ( )
PIPELINE_LOG_DIR = "logs/build"
PIPELINE_TS = " $( date +%Y%m%d-%H%M%S) "
pipeline_running_count( ) {
local n = 0 pid
for pid in " ${ PIPELINE_PIDS [@] } " ; do
kill -0 " $pid " 2>/dev/null && n = $(( n + 1 ))
done
echo " $n "
}
# Launch postprocess_app in the background, capped at KICAD_PIPELINE_JOBS
2026-07-01 12:18:57 +02:00
# concurrent jobs (default 2: pcbnew's wasm-opt pass peaks ~34 GB RSS; two postprocesses
2026-06-11 07:46:48 +02:00
# plus the container compile fit the 128 GB CI box). Portable poll loop instead
# of `wait -n` (absent in macOS bash 3.2).
pipeline_postprocess( ) {
local app = " $1 "
local max_jobs = " ${ KICAD_PIPELINE_JOBS :- 2 } "
while [ " $( pipeline_running_count) " -ge " $max_jobs " ] ; do
sleep 10
done
local log_file = " ${ PIPELINE_LOG_DIR } /postprocess- ${ app } - ${ PIPELINE_TS } .log "
echo " Pipelining host-side postprocess of ${ app } (log: ${ log_file } ) "
(
postprocess_app " $app " >" $log_file " 2>& 1
echo $? >" ${ log_file } .rc "
) &
PIPELINE_PIDS += ( $! )
PIPELINE_APPS_BG += ( " $app " )
}
# Wait for all background postprocesses, replay their logs into the main log,
# and fail if any of them failed.
pipeline_wait_all( ) {
local failed = 0 i pid app log_file rc
for i in " ${ !PIPELINE_PIDS[@] } " ; do
pid = " ${ PIPELINE_PIDS [ $i ] } "
app = " ${ PIPELINE_APPS_BG [ $i ] } "
log_file = " ${ PIPELINE_LOG_DIR } /postprocess- ${ app } - ${ PIPELINE_TS } .log "
wait " $pid " || true
rc = " $( cat " ${ log_file } .rc " 2>/dev/null || echo 1) "
echo ""
echo " === Postprocess ${ app } (rc= ${ rc } ) — ${ log_file } === "
cat " $log_file " 2>/dev/null || true
if [ " $rc " != "0" ] ; then
echo " ERROR: postprocess of ${ app } failed (rc= ${ rc } ) "
failed = 1
fi
done
return " $failed "
}
TOTAL_APPS = " ${# APPS [@] } "
2026-06-22 14:07:50 +02:00
# Shared pipeline trap: on a failure, kill orphaned background wasm-opt jobs
# (each ~30 GB) and keep the monitor's done/fail marker from the EXIT trap.
_install_pipeline_trap( ) {
2026-07-03 14:42:38 +02:00
trap '_rc=$?; for p in "${PIPELINE_PIDS[@]}"; do kill "$p" 2>/dev/null || true; done; if [ $_rc -eq 0 ]; then kw_done; else kw_fail $_rc; fi; stop_builder' EXIT
2026-06-22 14:07:50 +02:00
}
if [ [ " $PHASE " = = "compile" ] ] ; then
# --compile-only: produce the opt-independent base wasm; no host post-process.
idx = 1
for app in " ${ APPS [@] } " ; do
compile_app " $app " " $idx " " $TOTAL_APPS "
idx = $(( idx + 1 ))
done
elif [ [ " $PHASE " = = "postprocess" ] ] ; then
# --postprocess-only: pure host post-process on the existing output/ base
# wasm (no container). Parallelize across apps when pipelining.
if [ [ " ${ KICAD_PIPELINE :- 0 } " = = "1" ] ] && [ " $TOTAL_APPS " -gt 1 ] ; then
mkdir -p " $PIPELINE_LOG_DIR "
_install_pipeline_trap
for app in " ${ APPS [@] } " ; do
pipeline_postprocess " $app "
done
pipeline_wait_all
else
for app in " ${ APPS [@] } " ; do
postprocess_app " $app "
done
fi
elif [ [ " ${ KICAD_PIPELINE :- 0 } " = = "1" ] ] && [ " $TOTAL_APPS " -gt 1 ] ; then
# both, pipelined: overlap app[i+1]'s container compile with app[i]'s host
# post-process (KICAD_PIPELINE=1).
2026-06-11 07:46:48 +02:00
mkdir -p " $PIPELINE_LOG_DIR "
2026-06-22 14:07:50 +02:00
_install_pipeline_trap
2026-06-11 07:46:48 +02:00
idx = 1
for app in " ${ APPS [@] } " ; do
compile_app " $app " " $idx " " $TOTAL_APPS "
pipeline_postprocess " $app "
idx = $(( idx + 1 ))
done
pipeline_wait_all
2026-05-29 06:39:23 +02:00
else
2026-06-22 14:07:50 +02:00
# both, sequential.
2026-06-11 07:46:48 +02:00
idx = 1
for app in " ${ APPS [@] } " ; do
compile_app " $app " " $idx " " $TOTAL_APPS "
postprocess_app " $app "
idx = $(( idx + 1 ))
done
2026-05-29 06:39:23 +02:00
fi
2025-12-13 22:13:31 +01:00
echo ""
2025-12-08 12:07:01 +01:00
echo "Build complete. Output files in ./output/"
2025-12-13 22:13:31 +01:00
ls -lh output/