One cut, no dual path. Closes ALIGNMENT defect 3 ("Arduino-CLI build
machinery is actively hostile") and vision bullet 4.
platformio.ini carries 16 [env:], one per variant, each passing
-DMEMLNAUT_MODE_TYPE; selftest passes -DNISPS_SELFTEST=1 instead. The env list
IS the registry now — the .ino comment-registry and the NISPS_ST_* token-paste
table are deleted rather than migrated. L12 noted that table was already
silently missing the currently-shipped SLPWorkshop variant, which is the whole
argument against having a second list.
Also deleted: the Python/sed machinery that rewrote the COMMITTED .ino on every
build, the sketch symlink forest, the global TFT_eSPI User_Setup.h mutation
(now -D flags — TFT_eSPI's own documented PlatformIO recipe), the UF2
boot-mount detection stack (upload_protocol=picotool talks to the bootloader
directly), and build-firmware-arch.sh entirely. Scripts 683 -> 435 lines.
memllib is vendored at lib/memllib/ from upstream e291192; no submodules
remain. VENDORED.md records provenance and the re-sync procedure.
S9: a firmware-build CI job compiles three representative envs against a cached
toolchain and reports per-variant flash/RAM. Firmware is in an automated gate
for the FIRST time. The old ci.yml comment justified excluding it as "low
verification value" — an assessment that did not survive contact, since the
SelfTest variant sat broken for an unknown period calling a DisplayDriver
method that did not exist at the pinned memllib commit, and nothing noticed
because nothing built it.
Verified: all 16 envs build from an empty cache, each within ~520 bytes of the
arduino-cli binary it replaces, flash and RAM. Measured as .text+.rodata /
.data+.bss+vector+uninitialized — NOT PlatformIO's console line, which
double-counts .data on this board. This does not prove the hardware boots; no
flash+smoke test was possible and that stays an operator chokepoint.
slpworkshop 248232/145028 pafsynth 256880/149716 selftest 216228/17960
(all 16 in the CI log format; none exceeds 2% of a 16 MB flash)
Two traps recorded so nobody rediscovers them: vendoring memllib's subdirs
without a src/ wrapper makes PlatformIO's library builder silently compile
NOTHING while still linking; and project build_flags land BEFORE the
framework's own -std=gnu++17 -Os, so build_unflags is required.
CORRECTION carried in this commit: the firmware sizes in c19d846's message and
the first version of the memllib recon doc were wrong — SLPWorkshop 145348,
PAFSynth 145300, SelfTest 141840. They came from building variants in sequence
through a SHARED incremental arduino-cli build directory, which reused stale
objects and under-reported by ~75 KB. Clean-cache rebuilds of the identical
commit give 216736/18492 for SelfTest. The real cost of the memllib upstream
bump is +216 bytes flash, not +316. Never measure firmware size through a
reused build dir.
HISTORY NOTE: this commit and the docs commit before it were rebuilt (force-push,
2026-07-21) so that each contains only what its message describes. The first
versions had the firmware deletions stranded in the docs commit by a shared-index
race between concurrent agents; content is byte-identical to the originals.
Gates: run-all-tests.sh ALL GREEN (nisps/ untouched by this change beyond
include paths); 16/16 pio envs build.
214 lines
7.6 KiB
YAML
214 lines
7.6 KiB
YAML
name: CI
|
|
|
|
# Stream 11 verification pipeline.
|
|
#
|
|
# Three parallel jobs:
|
|
# * cpp-tests — builds nisps host C++ tests, builds nisps.wasm, runs
|
|
# the parity check, runs the lint script.
|
|
# * manifold-tests — typechecks the React manifold app, runs bun unit
|
|
# tests, builds the production bundle, runs Playwright
|
|
# e2e tests.
|
|
#
|
|
# * firmware-build — compiles three representative PlatformIO envs for the
|
|
# RP2350 target.
|
|
#
|
|
# Firmware entered CI for the first time with the Phase 4 PlatformIO migration
|
|
# (plan §5, S9). It was previously excluded because arduino-cli + the rp2040
|
|
# board package cost ~2 minutes per run for "low verification value" — an
|
|
# assessment that did not survive contact: the SelfTest variant sat broken for
|
|
# an unknown period, calling a DisplayDriver method that did not exist at the
|
|
# pinned memllib commit, and nothing noticed because nothing built it. PIO's
|
|
# toolchain caches cleanly, so the cost is now a cache restore.
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
cpp-tests:
|
|
name: C++ tests + WASM + parity + lint
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install build deps
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends \
|
|
cmake ninja-build build-essential perl
|
|
|
|
- name: Setup Emscripten
|
|
# mymindstorm/setup-emsdk caches the SDK between runs. Pin to a
|
|
# known-working version; bump deliberately.
|
|
uses: mymindstorm/setup-emsdk@v14
|
|
with:
|
|
version: '3.1.69'
|
|
actions-cache-folder: 'emsdk-cache'
|
|
|
|
- name: Verify emcc
|
|
run: emcc --version
|
|
|
|
- name: Setup Node (for parity_wasm.mjs)
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '20'
|
|
|
|
- name: Build C++ tests + run ctest
|
|
env:
|
|
# CI is non-interactive — turn off colour for log readability.
|
|
CMAKE_BUILD_PARALLEL_LEVEL: '4'
|
|
run: bash scripts/build-cpp-tests.sh
|
|
|
|
# Freshness gate. manifold/public/nisps.{js,wasm} are TRACKED artifacts:
|
|
# the VPS webhook builds only manifold/, so vite copies whatever is
|
|
# committed into the production bundle. Prove the committed artifact
|
|
# still agrees with the C++ we just built, BEFORE the rebuild below
|
|
# overwrites it. A stale commit fails here instead of silently shipping.
|
|
- name: WASM freshness gate (committed artifact vs native)
|
|
env:
|
|
NISPS_PARITY_NO_BUILD: '1'
|
|
run: bash scripts/parity-check.sh
|
|
|
|
- name: Build WASM
|
|
# setup-emsdk puts emcc on PATH; build-wasm.sh picks it up via
|
|
# `command -v emcc`. No EMCC override needed.
|
|
run: bash scripts/build-wasm.sh
|
|
|
|
- name: Parity check (native vs WASM)
|
|
env:
|
|
NISPS_PARITY_NO_BUILD: '1' # we just built; don't re-build
|
|
run: bash scripts/parity-check.sh
|
|
|
|
- name: Lint
|
|
run: bash scripts/lint-cpp.sh
|
|
|
|
- name: Upload parity blobs on failure
|
|
if: failure()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: parity-blobs
|
|
path: |
|
|
tests/cpp/parity_native.bin
|
|
tests/cpp/parity_wasm.bin
|
|
if-no-files-found: ignore
|
|
retention-days: 7
|
|
|
|
manifold-tests:
|
|
name: Manifold typecheck + unit + e2e
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@v2
|
|
with:
|
|
bun-version: latest
|
|
|
|
# Enforces the "schema changes ship with regenerated C++ AND TypeScript
|
|
# in the same change" rule. Regenerate from schemas/ and fail if the
|
|
# committed outputs differ; then run the codegen golden test.
|
|
- name: Codegen is committed and idempotent
|
|
run: |
|
|
cd codegen
|
|
bun install --frozen-lockfile
|
|
bun run generate.ts
|
|
bun run generate-midi-devices.ts
|
|
bun run tests/golden_test.ts
|
|
cd ..
|
|
GEN_DIRS="nisps/modes/generated nisps/midi/generated nisps/ml/generated \
|
|
manifold/src/modes/generated manifold/src/midi-devices/generated"
|
|
git add -N $GEN_DIRS
|
|
if ! git diff --exit-code -- $GEN_DIRS; then
|
|
echo "::error::Generated outputs are stale. Run codegen and commit the result." >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Install manifold deps
|
|
working-directory: manifold
|
|
run: bun install --frozen-lockfile
|
|
|
|
- name: Typecheck
|
|
working-directory: manifold
|
|
run: bun run typecheck
|
|
|
|
- name: Unit tests
|
|
working-directory: manifold
|
|
run: bun run test
|
|
|
|
- name: Build manifold bundle
|
|
working-directory: manifold
|
|
run: bun run build
|
|
|
|
- name: Install Playwright browsers
|
|
working-directory: manifold
|
|
run: bunx playwright install --with-deps chromium
|
|
|
|
- name: Run Playwright tests
|
|
working-directory: manifold
|
|
run: bunx playwright test
|
|
|
|
- name: Upload Playwright report on failure
|
|
if: failure()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: playwright-report
|
|
path: manifold/playwright-report/
|
|
retention-days: 7
|
|
|
|
firmware-build:
|
|
name: Firmware (RP2350, PlatformIO)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.12'
|
|
|
|
# The platform wrapper and the arduino-pico framework are fetched from
|
|
# git and total ~1-2 GB. Key on platformio.ini because that file pins
|
|
# both versions — change a pin, get a fresh toolchain.
|
|
- name: Cache PlatformIO toolchain
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.platformio
|
|
~/.cache/pip
|
|
key: pio-${{ runner.os }}-${{ hashFiles('firmware/MEMLNaut-NISPS/platformio.ini') }}
|
|
restore-keys: pio-${{ runner.os }}-
|
|
|
|
- name: Install PlatformIO
|
|
run: pip install --upgrade platformio
|
|
|
|
# Three representative envs rather than all sixteen: they differ only in
|
|
# which mode type is instantiated, so a compile break is almost always
|
|
# common to all. slpworkshop is the shipped default, pafsynth is the
|
|
# heaviest RAM user, and selftest takes the separate NISPS_SELFTEST fork
|
|
# through main.cpp — the one that broke unnoticed before.
|
|
- name: Build representative firmware variants
|
|
working-directory: firmware/MEMLNaut-NISPS
|
|
run: pio run -e slpworkshop -e pafsynth -e selftest
|
|
|
|
# Sizes are reported, not asserted. A threshold would either be slack
|
|
# enough to be meaningless or tight enough to fail on unrelated work;
|
|
# this puts the numbers in the log so a jump is visible in review.
|
|
# Flash = .text + .rodata, RAM = .data + .bss + vector table +
|
|
# uninitialized — matching arduino-cli's convention, NOT PlatformIO's
|
|
# own console line, which double-counts .data on this board.
|
|
- name: Report flash/RAM per variant
|
|
working-directory: firmware/MEMLNaut-NISPS
|
|
run: |
|
|
SIZE=$(find ~/.platformio/packages -name 'arm-none-eabi-size' | head -1)
|
|
for e in slpworkshop pafsynth selftest; do
|
|
"$SIZE" -A ".pio/build/$e/firmware.elf" | awk -v e="$e" '
|
|
/^\.text/{t=$2} /^\.rodata/{r=$2} /^\.data/{d=$2}
|
|
/^\.bss/{b=$2} /^\.ram_vector_table/{v=$2}
|
|
/^\.uninitialized_data/{u=$2}
|
|
END{printf "%-14s flash=%-8d ram=%-8d\n", e, t+r, d+b+v+u}'
|
|
done
|