ci: 🎡 -o1 & test workers

This commit is contained in:
Istvan Matejcsok 2026-06-16 12:55:01 +02:00
commit 5df7f40b3c
4 changed files with 55 additions and 14 deletions

View file

@ -1,5 +1,5 @@
# The build + e2e CI: full KiCad WASM build (all 6 tools, Binaryen 130, host-side # The build + e2e CI: full KiCad WASM build (all 6 tools, Binaryen 130, host-side
# asyncify only — the -O2 pass is skipped via ASYNCIFY_ONLY=1) + wxWidgets e2e # asyncify + a lighter wasm-opt -O1 shrink pass via BINARYEN_OPT_LEVEL) + wxWidgets e2e
# (291 tests) + KiCad e2e (38 tests) on a Ubicloud runner. Runs on pushes to # (291 tests) + KiCad e2e (38 tests) on a Ubicloud runner. Runs on pushes to
# main/ubicloud-ci and on PRs against main. # main/ubicloud-ci and on PRs against main.
# Replaced the ephemeral-Hetzner-VM workflows (ci-full-build.yml / ci.yml, # Replaced the ephemeral-Hetzner-VM workflows (ci-full-build.yml / ci.yml,
@ -38,10 +38,14 @@ jobs:
env: env:
KICAD_LOG_NESTED: "1" KICAD_LOG_NESTED: "1"
BINARYEN_VERSION: ${{ inputs.binaryen_version || '130' }} BINARYEN_VERSION: ${{ inputs.binaryen_version || '130' }}
# Skip the heavy wasm-opt -O2 pass (scripts/common/apply-asyncify.sh:124): # Use the lighter wasm-opt -O1 shrink pass instead of -O2
# stops after the asyncify pass. The full e2e suite below validates that # (scripts/common/apply-asyncify.sh). -O1 still runs CoalesceLocals, so the
# the non-O2 wasm runs correctly in Chrome/Firefox. # asyncify-instrumented coroutine functions stay under V8's per-function
ASYNCIFY_ONLY: "1" # locals limit: it produces the same ~187 MB pcbnew.wasm as -O2 and is green
# on the load-pcb chromium-ci spec, but is a faster pass. (Skipping the
# optimization entirely via ASYNCIFY_ONLY=1 shipped a 336 MB wasm that
# crashed headless Chromium — that flag is benchmark-only, not for e2e.)
BINARYEN_OPT_LEVEL: "-O1"
# Stable docker-compose project → deterministic build-cache volume name # Stable docker-compose project → deterministic build-cache volume name
# (kicad-wasm-ci_kicad-build-cache) for the deps cache below. # (kicad-wasm-ci_kicad-build-cache) for the deps cache below.
COMPOSE_PROJECT_NAME: kicad-wasm-ci COMPOSE_PROJECT_NAME: kicad-wasm-ci
@ -86,7 +90,7 @@ jobs:
# Same build recipe as ci-full-build.yml: full KiCad WASM build of ALL 6 # Same build recipe as ci-full-build.yml: full KiCad WASM build of ALL 6
# tools, host-side asyncify on Binaryen v130 (BINARYEN_VERSION in env → # tools, host-side asyncify on Binaryen v130 (BINARYEN_VERSION in env →
# get-wasm-opt.sh); the -O2 pass is skipped (ASYNCIFY_ONLY=1 in job env). # get-wasm-opt.sh) + a lighter -O1 shrink pass (BINARYEN_OPT_LEVEL in job env).
# 'all' = pcbnew eeschema calculator pl_editor symbol_editor gerbview, # 'all' = pcbnew eeschema calculator pl_editor symbol_editor gerbview,
# built in sequence. --build-deps short-circuits on a cache hit. # built in sequence. --build-deps short-circuits on a cache hit.
- name: Build all KiCad tools WASM with Binaryen ${{ env.BINARYEN_VERSION }} - name: Build all KiCad tools WASM with Binaryen ${{ env.BINARYEN_VERSION }}
@ -117,6 +121,25 @@ jobs:
docker run --rm -v kicad-wasm-ci_kicad-build-cache:/bw -v "$PWD/deps-cache":/cache \ docker run --rm -v kicad-wasm-ci_kicad-build-cache:/bw -v "$PWD/deps-cache":/cache \
alpine sh -c 'cd /bw && tar czf /cache/deps.tar.gz sysroot stamps' alpine sh -c 'cd /bw && tar czf /cache/deps.tar.gz sysroot stamps'
# Cache the host wxWidgets build (build-wasm/wxwidgets, ~11 min, incremental
# but uncached) keyed on the wx submodule SHA + the build script/versions.
# On a hit, touch the tree so make treats the restored objects as current
# (same SHA ⇒ identical sources), so build-wx-wasm.sh just relinks fast.
- name: wx submodule sha
id: wxsha
run: echo "sha=$(git -C wxwidgets rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Restore wx build cache
id: wx-cache
uses: actions/cache@v4
with:
path: build-wasm/wxwidgets
key: wx-${{ runner.os }}-${{ steps.wxsha.outputs.sha }}-${{ hashFiles('scripts/build-wx-wasm.sh','scripts/common/versions.sh') }}
- name: Mark restored wx objects current
if: steps.wx-cache.outputs.cache-hit == 'true'
run: find build-wasm/wxwidgets -exec touch {} +
# Host-side wxWidgets build producing the wx test apps for `npm run test` # Host-side wxWidgets build producing the wx test apps for `npm run test`
# (separate from the in-docker wxWidgets that KiCad links against). # (separate from the in-docker wxWidgets that KiCad links against).
# Auto-installs emsdk into tools/ on first run. # Auto-installs emsdk into tools/ on first run.

View file

@ -128,18 +128,30 @@ if [[ "${ASYNCIFY_ONLY:-0}" == "1" ]]; then
exit 0 exit 0
fi fi
# Post-asyncify shrink pass. Asyncify emits deliberately verbose instrumentation
# (spills every live local) and relies on the optimizer to coalesce it back down
# under V8's per-function locals limit — without it, large coroutine-entry
# functions silently stall (and on newer Chromium hard-crash the renderer while
# loading a heavy board) in Chrome's V8. See docs/debugging/DEBUG.md §6-7.
#
# Level is configurable via BINARYEN_OPT_LEVEL: -O1 already runs CoalesceLocals
# and is sufficient (measured: same ~187 MB / 64 MB-gzip output as -O2 on pcbnew,
# validated green on the load-pcb chromium-ci spec — but a lighter, faster pass,
# which is why CI sets -O1). -O2 stays the default for the more thorough rewrite.
# Do NOT use -Os/-Oz here: they break the asyncify runtime (Binaryen #4484).
BINARYEN_OPT_LEVEL="${BINARYEN_OPT_LEVEL:--O2}"
echo "" echo ""
echo "Running wasm-opt -O2 on the asyncified wasm..." echo "Running wasm-opt ${BINARYEN_OPT_LEVEL} on the asyncified wasm..."
echo " Purpose: shrink asyncify-instrumented functions back under V8's" echo " Purpose: shrink asyncify-instrumented functions back under V8's"
echo " per-function locals limit (otherwise large coroutine-entry and" echo " per-function locals limit (otherwise large coroutine-entry and"
echo " similar functions silently stall in Chrome's V8). See docs/debugging/DEBUG.md §7" echo " similar functions stall/crash in Chrome's V8). See docs/debugging/DEBUG.md §6-7."
echo " and memory/bundle-size-asyncify-optimization.md." echo " This pass takes several minutes and ~10-15 GB RAM."
echo " This pass also takes several minutes and ~10-15 GB RAM."
echo " BINARYEN_CORES=${BINARYEN_CORES}" echo " BINARYEN_CORES=${BINARYEN_CORES}"
echo " LD_PRELOAD=${WASM_OPT_PRELOAD:-<none>}" echo " LD_PRELOAD=${WASM_OPT_PRELOAD:-<none>}"
"${PRELOAD_CMD[@]}" "${TIME_CMD[@]}" "${WASM_OPT}" -O2 "${OUTPUT_WASM}" -o "${OUTPUT_WASM}" "${PRELOAD_CMD[@]}" "${TIME_CMD[@]}" "${WASM_OPT}" "${BINARYEN_OPT_LEVEL}" "${OUTPUT_WASM}" -o "${OUTPUT_WASM}"
echo "" echo ""
echo "Asyncify + -O2 complete: ${OUTPUT_WASM}" echo "Asyncify + ${BINARYEN_OPT_LEVEL} complete: ${OUTPUT_WASM}"
ls -lh "${OUTPUT_WASM}" ls -lh "${OUTPUT_WASM}"

View file

@ -97,7 +97,10 @@ export default defineConfig({
// as playwright.config.ts): the load-pcb post-load clipboard crash that can // as playwright.config.ts): the load-pcb post-load clipboard crash that can
// close the page on Firefox, and the calculator first-run-wizard timing race. // close the page on Firefox, and the calculator first-run-wizard timing race.
retries: process.env.CI ? 2 : 1, retries: process.env.CI ? 2 : 1,
workers: process.env.CI ? 1 : undefined, // Run parallel workers on CI too (Playwright default ≈ 50% of cores), same as
// local — the serial CI run was the dominant wall-clock cost. Cap (e.g. '50%'
// or a fixed count) if contention OOMs/flakes; retries:2 covers transient.
workers: undefined,
reporter: 'html', reporter: 'html',
timeout: 180000, // KiCad WASM needs more time to load (3 minutes) timeout: 180000, // KiCad WASM needs more time to load (3 minutes)

View file

@ -72,7 +72,10 @@ export default defineConfig({
// 1 local retry absorbs transient `npx serve` connection refusals under heavy // 1 local retry absorbs transient `npx serve` connection refusals under heavy
// parallel load (many workers fetching large WASM bundles at once). // parallel load (many workers fetching large WASM bundles at once).
retries: process.env.CI ? 2 : 1, retries: process.env.CI ? 2 : 1,
workers: process.env.CI ? 1 : undefined, // Run parallel workers on CI too (Playwright default ≈ 50% of cores), same as
// local — the serial CI run was the dominant wall-clock cost. Cap (e.g. '50%'
// or a fixed count) if contention OOMs/flakes; retries:2 covers transient.
workers: undefined,
reporter: 'html', reporter: 'html',
timeout: 60000, // WASM can be slow to load timeout: 60000, // WASM can be slow to load