Skip to content

stm32: inline stm32_cfg into the family class (local-keep terminal state) - #128

Open
cumin777 wants to merge 44 commits into
Seeed-Studio:devfrom
cumin777:feature/stm32-family-migration
Open

cumin777 wants to merge 44 commits into
Seeed-Studio:devfrom
cumin777:feature/stm32-family-migration

Conversation

@cumin777

@cumin777 cumin777 commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Review only this PR's own commit (2eab1ac). Stack: #126 (samd) → #127 (renesas) → this PR.

stm32 family closeout (guide execution card 4, XS): the 76 lines of platform_cfg/stm32_cfg.py are inlined into families/stm32.py (same pattern as #125 esp / #126 samd / #127 renesas), and the file is deleted. Only siliconlab remains in platform_cfg/.

Delta attribution

Item Ownership Notes
Package matrix (zephyr toolset + toolchain ~1.120301.0 pin), stlink debug-tools entry, debug session inlined line by line cfg's self=platform → explicit platform; no dead branches to cut
tool-jlink pruning now via the drop_package guard owned_packages=("tool-jlink",) (declaration unchanged, registry discipline)
configure_debug_session old wiring fixed One argument passed to a two-parameter module function (TypeError whenever a debug session was configured — same latent bug as renesas); the inline restores the intended body
stm32_build.py / UF2 chain (uf2conv + uf2upload incl. 1200bps touch) / stlink / cfg untouched local-keep is already the terminal form; no upstream (C5 is not in platform-ststm32), no wrapper, no upstream anchors

Verification record

  • A/B byte-identical (baseline = pre-migration code at 624edf8, symlinked workspace, routing print verified on both sides family 'stm32' -> ...stm32_build.py; assets under pre-migration-624edf8-stm32\): the four zephyr-blink artifacts firmware.elf/.bin/.hex/.uf2 all have identical SHA256 (the UF2 is the real upload payload, Family ID 0x00C5C5C5 / 186 blocks)
  • pytest tests/: 46 passed, 11 skipped (anchor network skips)
  • python scripts/ci/verify_board_manifests.py: 24 manifests, 0 errors
  • On hardware (user, optional smoke): UF2 drag-and-drop / stlink

Co-Authored-By: Claude noreply@anthropic.com

cumin777 and others added 30 commits October 29, 2026 10:06
Routing contract: every boards/<id>.json declares build.chip_family and
ChipFamily.resolve() maps it to a family instance — no board-id substring
matching anywhere. Registration happens at class definition
(__init_subclass__); a new family is one module plus one import line in
families/__init__.py.

Esp32Family owns the ESP32 tool-expansion logic moved from platform.py
(prefer-local tools, idf_tools.py expansion, esptoolpy runtime deps).
Other families are thin delegates over platform_cfg/*_cfg for now.
The package imports without PlatformIO installed so CI verifiers can
read the registry.

Co-Authored-By: Claude <noreply@anthropic.com>
Every manifest gets build.chip_family (esp32/nrf/samd/rpi/renesas/
siliconlab/stm32) inserted at its alphabetical position in the build
section. New verifier rule R7 fails a board whose value is missing or
matches no registered family, taking valid values from the families/
registry (single source of truth).

Co-Authored-By: Claude <noreply@anthropic.com>
configure_default_packages, _add_dynamic_options and configure_debug_
session all resolve the family from the board manifest's
build.chip_family and delegate to it. The global Architecture variable,
both board-id substring blocks, and the ESP32 install-time methods
(moved to families/espressif.py) are gone; platform.py keeps only
zephyr per-board package routing and board listing.

Behavior is unchanged: same package flips, same debug tools, same
debug-session tweaks, same ESP tool expansion sequencing via the
family's configure/post_configure hooks.

Co-Authored-By: Claude <noreply@anthropic.com>
builder/main.py and the Arduino framework wrapper resolve the family
from the board manifest and SConscript whatever the family returns —
a local board_build/<family>/ script today, an upstream-platform
script later — then apply the family's build hooks. The Zephyr builder
replaces its two board-name substring PIOPLATFORM overrides/restores
with family.zephyr_pioplatform(). The repo root is tail-appended to
sys.path at SCons time (never shadowing stdlib platform).

Co-Authored-By: Claude <noreply@anthropic.com>
The espressif32 upstream platform (pioarduino/platform-espressif32,
locked at 53.03.13 = d2f1607c) is now declared as an optional package
in platform.json and activated by Esp32Family.required_packages when
Arduino is requested. The Arduino framework wrapper SConscripts the
upstream package's builder/frameworks/arduino.py directly; the local
esp_arduino.py copy (a reformatted 53.03.13 snapshot with Tasmota
pip deps and an embedded-libs SDK dir tweak) is deleted.

Known deltas vs the deleted copy: python deps return to the upstream
set (PyYAML/intelhex instead of zopfli/tasmota-metrics) and the
Windows include-shortening middleware resolves against the separate
framework-arduinoespressif32-libs package layout, degrading to
unshortened includes on this platform's embedded-libs layout.

Co-Authored-By: Claude <noreply@anthropic.com>
The tool-package channel requires a package.json manifest, which
pioarduino does not ship (it only has platform.json), so activating
the espressif32 entry through the regular optional=False path fails
with MissingPackageManifestError. PlatformPackageManager accepts
platform.json manifests and installs into the platforms dir instead:
Esp32Family now installs the locked upstream there (dependencies
skipped — this platform owns its packages) during package
configuration, and framework_script resolves the upstream builder
from the platforms dir at SCons time. The espressif32 entry stays
optional in platform.json as the locked-version declaration.

Co-Authored-By: Claude <noreply@anthropic.com>
Registry completeness and singleton behavior, resolve() three states
(missing field names the board file, unknown value lists valid ids),
every boards/*.json routing by manifest field (not id substring — the
stm32c5 board would misroute under substring routing), in-process
platform delegation for configure_default_packages and
_add_dynamic_options, and family-owned build script mapping.

Co-Authored-By: Claude <noreply@anthropic.com>
The espressif32 entry in platform.json's packages table was never a
core-installable package: the tool-package channel requires a
package.json manifest pioarduino does not ship, and the family installs
the locked upstream through PlatformPackageManager instead. Mixing a
non-installable source reference into the packages table (where every
other entry means 'core installs this') was misleading.

The locked spec now lives on Esp32Family.upstream_version next to its
consumer — source reference, installer, and script path resolution in
one place. Bumping it is the maintainer-driven upstream upgrade point.

Co-Authored-By: Claude <noreply@anthropic.com>
….03.312

The upstream lock moves from 53.03.13 (the Arduino snapshot origin) to
55.03.312, whose espidf.py is the direct origin of the deleted local
2817-line snapshot (real local delta was 24+/8-; the rest was snapshot
age). Reinstall detection now compares the installed checkout's git
HEAD against the locked commit (the manifest version is only the
upstream tag string), and uninstall passes skip_dependencies so
pioarduino's uninstall cannot remove this platform's own packages.

builder/frameworks/espidf.py becomes a thin wrapper like the Arduino
one; the local snapshot and its now-orphaned board_build/esp helpers
(ulp.py, _embed_files.py) are removed. pioarduino's espidf.py loads
component_manager from the HOST platform dir by
env.PioPlatform().get_dir(), so builder/frameworks/component_manager.py
is now a forwarding shim to the locked upstream copy — no code
duplicated. ESP-IDF builds also activate tool-cmake/tool-ninja, which
no local code ever did (there was no espidf consumer before).

The package matrix stays on the existing registry versions
(framework-espidf ~3.50503.0, toolchains 14.2.0+20251107): the 55.03.312
matrix (IDF 5.5.5 + shell zips + build-time tl-install bootstrap)
conflicts with this platform's esp_install expansion channel.

Validated on Windows PowerShell: espidf c3 smoke build (full IDF cmake/
ninja/ldgen chain) and all five esp32 Arduino boards; the earlier ldgen
parse failures were an MSYS-environment artifact, not a version
mismatch. Not yet validated on real hardware.

Co-Authored-By: Claude <noreply@anthropic.com>
The local copy of the stub-expansion logic (idf_tools.py invocation,
core/tools staging, file:// link-back) is replaced by a call into the
locked upstream platform's own API: PlatformFactory.new() on the
installed espressif32 package, then Espressif32Platform.install_tool()
for each required tool whose installed package still carries a
tools.json (the stub marker). Class-level reuse per the handbook —
upstream upgrades now carry their own expansion fixes.

This also fixes the stub-shadowing gap the local copy had: upstream
install_tool removes the stub before installing the expanded payload
back into the packages dir, so tool-esptoolpy now ends up as the real
payload in packages (esptool.py present, tools.json gone) instead of a
stub there with the payload stranded in core/tools. The manifest entry
is then linked to the local directory so the core never re-downloads
the stub.

Verified on Windows PowerShell: fresh expansion of tool-esptoolpy from
a clean packages dir, UPLOADER now resolves inside packages/ (no
core/tools fallback hit), idempotent rebuild, all five esp32 Arduino
boards pass. Not validated on hardware upload.

Co-Authored-By: Claude <noreply@anthropic.com>
Esp32Family.build_script now points at the upstream pioarduino
builder/main.py (55.03.312): env assembly, upload matrix, filesystem
images, and diagnostics all come from the locked upstream package. The
local board_build/esp/esp_build.py — an official-v6.9.0-era copy of the
same file, plus its orphaned _bare.py — is deleted, together with
its two companion orphans builder/penv_setup.py and builder/sizedata.py
(consumed only by the retired copy; the live penv dependency
declaration is read from the upstream package — see
_upstream_python_deps). The ESP32 builder
layer is now a single upstream anchor (312) across arduino, espidf and
board-level scripts.

Five host-side adaptations make the upstream script runnable here, all
additive on our side, none touching the upstream package:

1. SeeedstudioPlatform.setup_python_env forwards to the family adapter
   (upstream main.py calls platform.setup_python_env on its host).
2. Esp32Family.setup_python_env returns the working (penv python,
   esptool) pair WITHOUT running pioarduino's penv machinery — its uv
   sync owns the shared penv and removed PIO core packages (certifi,
   uvicorn) when tried. esptool resolves to the penv esptool.exe: the
   upstream feeds the value straight into OBJCOPY, and a bare .py path
   cannot be spawned on Windows.
3. seed_penv_deps idempotently pre-installs the module-level imports
   the upstream builder expects (littlefs-python, fatfs-ng).
4. builder/spiffsgen.py is a forwarding shim: upstream loads spiffsgen
   from the HOST platform dir (same host-path expectation as
   component_manager / penv_setup).
5. toolchain stubs expand through the upstream installer API (previous
   commit).

Validated on Windows PowerShell: all five esp32 Arduino boards, the
espidf c3 smoke build (full cmake/ninja/ldgen chain), UPLOADER resolves
to a spawnable esptool.exe, pytest 15/15, board verifier clean.
Hardware upload not yet validated.

Co-Authored-By: Claude <noreply@anthropic.com>
The upstream pioarduino builders locate their companion modules
(spiffsgen.py, penv_setup.py, component_manager.py, boards/<id>.py)
through env.PioPlatform().get_dir() — the HOST platform directory.
Two static forwarding shims existed to satisfy that expectation
(builder/spiffsgen.py, builder/frameworks/component_manager.py).

This replaces them with a runtime proxy handed to the upstream scripts
for the duration of their SConscript call: get_dir() resolves to the
upstream package so every such lookup lands inside the locked upstream
copy, while everything else (packages, install_tool, debug) forwards to
the real host platform. Any future 'load from host dir' expectation is
covered automatically — no new shim files per upstream addition.

The three dispatchers (builder/main.py, the arduino and espidf
wrappers) call the family's wrap_platform hook before SConscripting an
upstream script and restore the original afterwards.

Validated on Windows PowerShell: all five esp32 Arduino boards, the
espidf c3 smoke build, pytest 15/15, board verifier clean.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
required_packages() only activated the espressif32 upstream platform when
the board declared the arduino framework, but framework_script() demands
it for espidf builds as well. Existing boards all declare both frameworks
so this never surfaced; an espidf-first board (the planned S31) would
have failed loudly on its first build. Activate on either framework.

Found in review.

Co-Authored-By: Claude <noreply@anthropic.com>
…CI cache key

Three review findings on the upstream-platform dependency:

1. _ensure_upstream_platform is only invoked when a build requires an
   ESP32 framework script, so a failed install is now fatal at package
   configuration (message carries the locked spec and the pre-seed
   remedy) instead of degrading to a missing-builder error deep inside
   SCons.

2. Lock detection now writes and prefers a .upstream-lock marker file
   over the .git checkout: copied/deployed platforms dirs (offline
   distribution, CI cache extraction) have no .git and were
   misidentified as not installed, forcing a network reinstall on
   offline machines. rev-parse remains the fallback for pre-marker
   installs.

3. build-arduino-examples.yml keyed its whole ~/.platformio cache on
   platform source files (platform.py, builder/**...), so every PR push
   invalidated the platform/package cache and forced a fresh upstream
   git clone per run. Platforms+packages now cache under a key derived
   from the package matrix and the upstream lock only; core tools keep
   the old key.

Note: PlatformIO only runs configure_default_packages when it must make
install decisions, so on an already-satisfied machine the marker is
written on the first real decision point (fresh machine / CI / cache
restore), not on every build — verified by direct configure invocation.

Co-Authored-By: Claude <noreply@anthropic.com>
CI (bare runner python: platformio + rich-click + intelhex only) failed
with ModuleNotFoundError: No module named 'yaml' — upstream arduino.py
imports component_manager, which imports yaml at module level. The
local penv had PyYAML incidentally (installed by the zephyr builder's
self-healing pip), masking the gap.

Full closure audit of the upstream builder's third-party imports:
main.py -> littlefs/fatfs (already seeded), arduino.py ->
component_manager -> yaml (this fix); click and semantic_version are
PlatformIO core dependencies present wherever core runs; spiffsgen and
arduino_relinker are stdlib-only. The seed list is now complete.

Co-Authored-By: Claude <noreply@anthropic.com>
The seeded penv package list no longer pins its own version
constraints: seed_penv_deps now loads the upstream builder's
penv_setup.py (declarations only, no top-level side effects) and reads
its python_deps dict, so version floors travel with the locked
upstream.

Two explicit maps keep us in control: PENV_SEED (what we install, pip
name -> import name) and PENV_SEED_EXEMPT (audited deps deliberately
not seeded: managed separately or transitively present). A dependency
that upstream adds to python_deps but appears in neither map prints a
build-time warning — new upstream requirements surface for an explicit
decision instead of a CI failure or a silent gap.

Co-Authored-By: Claude <noreply@anthropic.com>
_ensure_esptoolpy_runtime_dependencies (rich_click<2 + intelhex, a
point-fix predating the seed list) ran right next to seed_penv_deps in
the same hook with identical mechanics — a first-generation leftover
that the list-based track superseded. Its rich_click<2 pin had no
upstream basis (upstream declares rich-click>=1.8.6 unbounded and its
ecosystem runs unpinned) and was the only divergence between the two
tracks.

Both packages now live in PENV_SEED with constraints from the upstream
list; the method, its flag, and the call line are gone. Other family
classes are unaffected — they carry no penv handling.

Co-Authored-By: Claude <noreply@anthropic.com>
The ESP32 adapters depend on implementation details of the locked
pioarduino package (companion-file lookups via get_dir(), the
platform.setup_python_env host-method call, install_tool, the
python_deps declaration). A lock bump can silently invalidate any of
them — worst case the proxy keeps 'working' with wrong semantics.

tests/test_upstream_anchors.py fetches the anchor files at the LOCKED
commit from GitHub raw and asserts each detail still holds; failures
name the drifted anchor and point at the adapter list in the
Esp32Family class header. Network-dependent tests skip cleanly
offline.

build-arduino-examples.yml now runs pytest (tests were local-only
before), so a lock bump that breaks an anchor turns the PR red.

Co-Authored-By: Claude <noreply@anthropic.com>
_upstream_python_deps exec'd the upstream penv_setup.py module on the
assumption (stated in its own docstring) that it has no top-level side
effects — one more unguarded implementation-detail dependency, and the
exact failure mode the adapters exist to prevent: an upstream refactor
moving logic to module level would run it on user machines at package
configuration.

The dependency declaration is now parsed with ast and literal_eval'd —
zero upstream code execution. If upstream ever makes the dict
non-literal or moves it, seeding degrades with a warning instead of
executing anything; no anchor needed because there is nothing left to
assume about the file.

Review finding.

Co-Authored-By: Claude <noreply@anthropic.com>
The unclassified-upstream-dep check lived as a build-time Warning in
seed_penv_deps — the wrong layer twice over: nobody audits build logs
for warnings, and anchors already fail hard on the same kind of drift
from the same data source (the locked commit, fetched over network).
Two standards for one concern.

The check is now test_penv_deps_fully_classified in the anchor suite:
exact-set match of upstream python_deps keys against PENV_SEED ∪
PENV_SEED_EXEMPT (both directions — unclassified upstream additions
and stale local entries fail). The AST extractor moved to a module-
level pure function (_parse_python_deps) so the family and the test
share one implementation.

Review finding.

Co-Authored-By: Claude <noreply@anthropic.com>
Speculative interface from the original ADR-0001 sketch: no dispatch
site in platform.py, builder/main.py, or the framework wrappers calls
any of the three, and no family overrides them. ESP32 -- the one
family fully on the registry -- needs none of them (flags and protocol
come from the board manifest; env tweaks flow through get_build_hooks,
which does have real consumers), proving them non-universal. If a
future migrated family genuinely needs a hook, add it at that family
with its own signature; promote to the base class only when a second
family and a shared dispatch site appear.

Co-Authored-By: Claude <noreply@anthropic.com>
CI (Linux) failed all five ESP32 boards at the bootloader step with
`sh: 1: esptool: Permission denied`. Three facts compound:

1. PIO core prepends the tool-esptoolpy package ROOT to $PATH for
   uploader packages; the pioarduino esptool.zip payload has no bin/,
   and its `esptool` is the Python package DIRECTORY — execve on it
   is EACCES, which dash reports as "Permission denied".
2. PIO 6.2's where_is_program() returns the BARE program name when
   lookup fails, so the previous `where_is_program("esptool") or ""`
   guard never fell through and OBJCOPY became the bare name.
3. Windows masked both: a leftover penv esptool.exe (or the .py file
   association) satisfied the lookup locally.

setup_python_env now validates the lookup result (regular executable
file only) and otherwise generates a launcher under the core cache
dir that runs tool-esptoolpy/esptool.py with the penv python; the
package's esptool.py itself is unpacked without the exec bit on
Linux, so it cannot be returned directly either. The wrapper embeds
both paths and rewrites itself when they move.

seed_penv_deps probed importability in the PIO host process but
installed into the penv — a system python preseeded on CI (the old
workflow line) masked a penv that lacked the packages for real. The
probe now runs inside the penv interpreter, and the workflow
preseed of rich-click/intelhex is dropped.

Tests cover: bare-name lookup must not leak out (CI regression),
a real $PATH entry point wins without a wrapper, the executable
predicate, and the penv-side import probe.

Co-Authored-By: Claude <noreply@anthropic.com>
Mechanical follow-up to the nordic migration: the ESP32 package
matrix, debug-tool injection and flash-image load commands move from
platform_cfg/esp_cfg.py into the family class (the per-family
product-decision layer lives with its family; the platform_cfg
string-hook shell keeps shrinking until the remaining five families
migrate). Bodies move verbatim - the pseudo-self functions become
real methods - and the debug-tool tests load Esp32Family instead of
the standalone module.

Co-Authored-By: Claude <noreply@anthropic.com>
The per-board renamed packages (framework-zephyr-nrf54l15 @ 4.2.1,
framework-zephyr-nrf54lm20 @ 4.4.0) were both registry framework-zephyr
tarballs installed under aliases; nothing consumes the difference, so
the aliasing only multiplies package management surface. Unify on the
registry identity framework-zephyr @ 3.40400.260428 (4.4.0) - the
version 4 of the 5 zephyr boards already build against.

seeed-xiao-nrf54l15 jumps 4.2.1 -> 4.4.0: build-verified for this PR
(zephyr-blink), hardware verification pending. boards/*.json drop
build.zephyr.package; variant/board_name/bootloader stay. Zephyr
version bumps are now platform-level decisions only.

Co-Authored-By: Claude <noreply@anthropic.com>
The per-board multiplexing had three layers: the build.zephyr.package
field in each board manifest, the SCons-side lookups consuming it
(nrf_build.py, zephyr.py, stm32_build.py), and the platform-side
validation (platform.py''s _configure_zephyr_package_for_board, the
zephyr-package half of verifier rule R1, and the READMEs documenting
the field). With a single pinned package all three collapse:

- builders reference "framework-zephyr" literally - the same form
  upstream builders use;
- _patch_platformio_framework_package_name and the
  framework_package_name variable are gone: the patch existed solely
  to translate that literal into the per-board alias inside the
  framework package''s own scripts, and with the unified name its
  replacement map is the identity;
- the configure-time validation and CI rule have nothing left to
  check; the READMEs document the single-package reality.

Co-Authored-By: Claude <noreply@anthropic.com>
The upstream-platform machinery in Esp32Family (UpstreamPlatformProxy,
the git-lock installer with its .upstream-lock marker, wrap_platform)
is family-agnostic: it only consumes upstream_platform/upstream_version
and the platforms dir. The nrf family is about to become the second
caller, which is the point where the bottom-up rule says the capability
moves to the base class. Bodies move verbatim (docstrings lose their
pioarduino-specific wording); espressif.py keeps only its esp-specific
adapters (penv seeding, esptool resolution, stub expansion).

Co-Authored-By: Claude <noreply@anthropic.com>
The board-level builder for every nrf board is now the official
platform-nordicnrf52 builder/main.py (locked at v11.0.0 through the
ChipFamily upstream loader hoisted earlier), executed by
families/nordic/build.py with env.PioPlatform() wrapped in an
UpstreamPlatformProxy. The old nrf_build.py keeps serving the local
upload matrix until upload.py lands in the next commits.

The wrapper exposed three proxy-window leaks, fixed here:
- framework script paths resolved against the upstream dir (PIO core
  falls back to get_dir() for relative scripts): platform.py now
  rewrites every frameworks[*].script against the host repo at
  configure time;
- zephyr.py / arduino.py anchored host paths on get_dir()/__file__:
  host paths now come from family.host_dir() (new on ChipFamily;
  SCons deletes __file__ inside SConscript frames);
- families/nordic becomes a package (class in __init__.py, the
  wrapper beside it) to keep family code and its build assets in one
  place.

Verified with clean builds against PR-1 baselines: nrf54lm20a (zephyr)
hex, afruit + mbed (arduino) hex and DFU-zip contents byte-identical.

Co-Authored-By: Claude <noreply@anthropic.com>
The 346 upload-specific lines of the retired nrf_build.py move to the
family directory (mcumgrupload.py comes along). The wrapper in
build.py drives them around the upstream skeleton:

- BEFORE the upstream script: upload.handles() picks the protocols
  where our behavior differs (nrfutil-mcumgr / pyocd / probe-rs,
  OpenOCD-class on nRF MCUs with RRAM/halt sequences, the bossac fork
  for sam-ba, jlink on DFU-bootloader builds); upload.configure()
  builds the full uploader command and UPLOAD_PROTOCOL is rewritten to
  "custom", whose upstream branch is a pure $UPLOADCMD passthrough;
- AFTER it: upload.graft_artifacts() attaches the real flash target
  (imgtool-signed MCUboot image, raw bin, SoftDevice bootloader pair)
  to the upload/buildprog aliases - the upstream custom default is a
  plain hex - and repoints UPLOADCMD at it.

Verified against PR-1 baselines: nrf54lm20b signed.bin and nrf54lm20a
hex byte-identical; the cmsis-dap passthrough runs the full OpenOCD
RRAM chain up to the physical probe (no hardware attached); upstream-
native protocols (nrfutil/mbed) untouched.

Co-Authored-By: Claude <noreply@anthropic.com>
…-owned

The retired nrf_arduino.py was the upstream adafruit.py plus two deltas;
the board now SConscripts the upstream script and the deltas return as
post-script hooks (families/nordic/arduino_hooks.py): BSP library
directories join LIBPATH, and the malloc/free-only wrap set is kept by
filtering the realloc/calloc wraps the upstream script adds. Verified
byte-identical (hex and DFU-zip contents) against the PR-1 baseline.

arduino-core-mbed.py moves to families/nordic/ as a family-owned fork
instead: it derives from an older upstream revision and carries two
real fixes (variant-dir linker-script resolution, FPU flags disabled)
whose upstream adoption changes behavior - deliberately deferred, the
file header records the reasoning. _bare.py is gone; the upstream
skeleton already dispatches its own copy for framework-less builds.

Co-Authored-By: Claude <noreply@anthropic.com>
cumin777 and others added 14 commits October 29, 2026 10:06
The 157-line product-decision layer (package matrix, default debug
tools, debug session tweaks) moves from platform_cfg/nrf_cfg.py into
NordicFamily - the per-family decisions now live with their family
(the platform_cfg string-hook shell is on its way out; cross-family
dedup of the debug table is deferred until the next family migrates).
The J-Link package drop goes through the owned drop_package guard.

The remaining board_build/nrf assets relocate: the OpenOCD configs
(nrf54l.cfg, nrf54lm20a.cfg) to families/nordic/ with the four board
manifests' openocd_target repointed, and nrf_build.py itself is gone -
the upstream skeleton plus build.py/upload.py own everything it did.

Co-Authored-By: Claude <noreply@anthropic.com>
Pin the upstream shapes the nordic family adapters rely on: the
"custom" upload branch staying a pure $UPLOADCMD passthrough, the
dispatch reading $UPLOAD_PROTOCOL from the env, the unconditional
ElfToBin builder (graft chains), the framework-zephyr build-pre load
(single-package key), the upstream _bare.py dispatch, the inherited
J-Link quoting fix, and adafruit.py resolving its BSP package through
get_package_dir. A lock bump that breaks one of these turns CI red
before the behavior diff ships.

Co-Authored-By: Claude <noreply@anthropic.com>
families/ is the Core-process import surface: one family, one module,
platform-side only. The SCons-process assets (build wrapper, upload
layer, arduino hooks and mbed fork, mcumgrupload.py, nRF54 OpenOCD
configs) move to builder/families/nordic/ — the vertical axis next to
builder/frameworks/ — mirroring esp, which has no family dir precisely
because its SCons entry lives in the upstream package.

Import mechanics: builder/ is not a package; sibling modules come into
reach the way builder/frameworks/zephyr.py reaches zephyr_fixes — the
family's builder dir on sys.path (SCons deletes __file__ in SConscript
frames, so host paths derive from family.host_dir()).

Also repoints the four nRF54 board manifests' openocd_target, the
anchor-test docstrings, and the factory_reset scripts (whose
board_build/nrf/ paths had been dangling since the nrf migration).

Layout decision record: handover-nrf-layout-revision.md (D3' replaces
D3;责任在决策侧).

Co-Authored-By: Claude <noreply@anthropic.com>
D3' finishes the axis move: every family's SCons-side assets now live
under builder/families/<family>/ (renesas/rpi/samd/siliconlab/stm32
move now, nordic landed in the previous commit), so board_build/ as a
mixed old-world name disappears in one stroke instead of lingering as
a half-migrated third location. AGENTS.md and the family classes point
at the new path; renesas_cfg's ra4m1.cfg reference follows the move.

build_lib/ is a proven orphan: its only consumer was the old espidf.py
snapshot deleted with Seeed-Studio#121, and the upstream pioarduino package ships
its own copy that the proxy resolves to (CI espidf smoke proves it).

Remaining `board_build.*` mentions are PlatformIO project option names
(board_build.mcu / .ldscript / .filesystem_size), not paths.

Co-Authored-By: Claude <noreply@anthropic.com>
The upload layer's if/elif dispatch (handles + configure +
graft_artifacts) becomes a table: PROTOCOLS maps each takeover
protocol to its gate, uploader-command configuration and artifact
graft, with the openocd entry serving every debug.tools key on nRF
MCUs and the jlink entry prefix-matching. resolve() walks the table
and returns a Takeover binding the concrete protocol name, so
build.py collapses to a straight line: resolve -> configure ->
rewrite UPLOAD_PROTOCOL to "custom" -> SConscript the upstream
skeleton -> graft.

Equivalence checked against the retired chain over a 90-case truth
table (protocol x board tools x mcu x DFUBOOTHEX): zero mismatches,
including the jlink-before-openocd-class ordering. Configure bodies
move verbatim behind a uniform (env, platform, board, protocol)
signature; graft bodies verbatim behind (env, board).

Co-Authored-By: Claude <noreply@anthropic.com>
framework-zephyr 4.4's bluetooth controller hal/debug.h emits
__asm__ inline volatile(...), which GCC < 9 rejects with
"expected '(' before 'inline'" — every BT-enabled seeed-xiao-nrf54l15
sample failed CI on exactly that. Move the nordic and stm32 zephyr
pins from ~1.80201.0 (GCC 8.2.1) to ~1.120301.0 (GCC 12.3), the
version both upstream platform-nordicnrf52 and platform-ststm32 pin
for Zephyr, and advertise it in platform.json optionalVersions.
Arduino/mbed pins are untouched.

Co-Authored-By: Claude <noreply@anthropic.com>
Zephyr renamed the SSD1306 bindings (solomon,ssd1306fb* ->
solomon,ssd1306*) before 4.4; the shared app.overlay still declared
the old compatible, which only the retired framework-zephyr-nrf54l15
(4.2.1) package understood. On the unified 4.4.0 package the node
stays unbound: oled fails to link (__device_dts_ord_127 undefined,
no driver instance) and oled-lvgl fails on the dropped width/height
properties. The nrf54lm20a board overlays already used the new name;
align app.overlay with them.

Co-Authored-By: Claude <noreply@anthropic.com>
families/ now owns the per-framework package pins (the toolchain
version lives there), so changes under it must both re-run the
example builds and rotate the PlatformIO package cache.

Co-Authored-By: Claude <noreply@anthropic.com>
The board-level skeleton (environment assembly, toolchain selection,
target_firm matrix, upload dispatch) is now the locked
maxgerhardt/platform-raspberrypi builder/main.py @ 4d1ad09 (the fork
point; official platformio/platform-raspberrypi is an mbed shell with
no earlephilhower/RP2350/XIAO), executed by the thin wrapper in
builder/families/rpi/build.py.

Deltas vs the retired local snapshot (rpi_build/rpi_arduino/_bare):
- generate_uf2 bare picotool + openocd bare UPLOADER: previously
  absolute paths in the snapshot; now healed by PATH-prepending the
  earlephilhower tool packages in the wrapper post section
- Import("env")/DefaultEnvironment and the _bare path: regressed to
  the upstream forms (nordic-verified)
- msdlabels 1-tuple bugfix: dropped (upstream bug, PR to mg pending)
- 230-line rpi_cfg inlined into families/rpi.py (debug prints and a
  bare except dropped as cosmetics); toolchain-gccarmnoneeabi
  cross-family protection comment carried verbatim

CI: arduino workflow now triggers on families/** and keys the package
cache on families/*.py (covers every family lock, not just espressif).

Verified: rp2040/rp2350/samd arduino-blink artifacts byte-identical
against the pre-migration baseline; 7 upstream anchors pinned in
tests/test_upstream_anchors_rpi.py.

Co-Authored-By: Claude <noreply@anthropic.com>
Port of d466200 (from the FamilyConfig line, which this tree does not
carry) back to the function form: remove the type=uploader pruning loop
entirely. samd's own uploaders carry no type field, so the loop
collected and deleted esp/renesas/nrf uploaders from the shared
packages dict, breaking later in-process configuration of those
families; the deleted packages are optional and never install for samd
builds anyway. Also drops two stray debug prints.

Co-Authored-By: Claude <noreply@anthropic.com>
The gpio-all-pins-cdc example still mapped D8 to PA15 (the BAT_EN net);
the board dtsi and the xiao-stm32c5-test-plan sample fixed this to PE2
(schematic D8 net) but the example was never updated. Port the fix.

The board re-spin renamed the PB14 header net and silkscreen from D15
to D18. The example now displays D18 and accepts both d18 and d15
commands (matched before the generic dN path, since bare numeric 18 is
BOOT0); board-file comments follow the rename. The connector gpio-map
index stays 15 for API compatibility.

Co-Authored-By: Claude <noreply@anthropic.com>
The board-level skeleton (environment assembly, toolchain defaults,
target_firm matrix, upload dispatch) is now the locked official
platform-atmelsam v9.0.0 builder/main.py, executed by the thin wrapper
in builder/families/samd/build.py; the arduino framework chain is the
upstream arduino.py -> arduino/arduino-samd.py -> arduino_common.py
(the retired samd_arduino.py was its semantically identical flattened
copy; _bare.py was byte-identical).

Deltas vs the retired local snapshot:
- bossac hardening (7b3b9e5) re-applied as a wrapper post-graft:
  absolute tool-bossac executable, quoted abspath SOURCE in UPLOADCMD,
  and -U/--offset as separate flags. Cannot be a get_build_hooks graft:
  hooks run before main.py's sam-ba branch whose env.Replace would
  overwrite them. The graft rewrites the upstream "-U true|false"
  pair back into the hardened form; other sam-ba branches (samd51
  fork, sam3x8e --boot) already matched the snapshot and stay
  untouched.
- zephyr build-pre branch and stk500v2 hex selection (samd21 dead
  code): regressed to upstream by construction.
- platform_cfg/samd_cfg.py inlined into families/samd.py in the PR-122
  form (uploader pruning already removed); its cross-family comment
  carried.

Verified: samd arduino-blink elf/bin byte-identical against the
pre-migration baseline; SCons env dump identical on the whole upload
chain (UPLOADER/UPLOADERFLAGS/UPLOADCMD zero diff); remaining dump
deltas are inert duplicates (ASPPFLAGS/LIBPATH/LIBSOURCE_DIRS) proven
so by the identical artifacts, plus BUILD_SCRIPT bookkeeping and the
timestamp. rp2040 rebuild on this branch still matches its baseline
(cross-family intact). 5 upstream anchors pinned in
tests/test_upstream_anchors_samd.py.

Co-Authored-By: Claude <noreply@anthropic.com>
Inline platform_cfg/renesas_cfg.py into families/renesas.py (package
matrix, debug-tools table, debug session; owned_packages now guards
the uploader pruning) and declare the upstream lock. The board-level
skeleton is the upstream builder/main.py executed via the thin
builder/families/renesas/build.py wrapper; framework_script() points
at the upstream arduino.py (the Seeed framework tarball rides under
the upstream package name). Retire the renesas_build.py /
renesas_arduino.py / _bare.py snapshots (_bare was byte-identical).

Family deltas live in builder/families/renesas/upload.py:
- pyocd: upstream has no pyocd branch, so the protocol is taken over
  (rewrite to the custom passthrough) and the hex artifact chain is
  grafted afterwards — the hex records embed the 0x4000 app offset,
  while pyocd flash -a 0x0 on the upstream default .bin would write
  the app over the bootloader. The artifact enters via $FLASH_IMAGE
  instead of $SOURCE (same runtime command line; the upstream alias
  source is always the .bin). Installers moved verbatim, still
  unpinned (debt R1).
- sam-ba: rides the upstream branch (its BeforeUpload port/touch
  sequence is missing from the custom passthrough) and is re-hardened
  after the fact: explicit bossac path, quoted absolute SOURCE,
  --verify, conditional --usb-port and --offset (upstream hard-codes
  bare bossac and no offset; XIAO RA4M1 needs 0x4000).

configure_debug_session's old cfg wiring passed one argument to a
two-parameter module function (TypeError whenever a debug session was
configured); the inline restores the intended body.

Lock pins the v1.9.0 commit 14e6238 (the guide's 34112f8 is the tag
object; _upstream_matches_lock compares HEAD SHAs). Anchor tests in
tests/test_upstream_anchors_renesas.py pin the upstream shapes both
deltas ride on, plus the no-pyocd-branch absence.

A/B vs the 624edf8 baseline: firmware.elf/.bin (dfu build) and
firmware.hex (pyocd build) byte-identical; SCons env dumps for
dfu/pyocd/sam-ba differ only in UNIX_TIME plus the two intentional
pyocd mechanism lines (FLASH_IMAGE, UPLOAD_PROTOCOL='custom').

Co-Authored-By: Claude <noreply@anthropic.com>
…ate)

platform_cfg/stm32_cfg.py (76 lines) moves into families/stm32.py as
real methods (the esp/rpi/samd/renesas pattern): package matrix,
stlink debug-tools entry, debug session. The tool-jlink prune now goes
through the owned drop_package guard.

configure_debug_session's old cfg wiring passed one argument to a
two-parameter module function (TypeError whenever a debug session was
configured -- same latent bug the renesas inline just fixed); the
inline restores the intended body.

Local-keep is already the terminal form: no upstream (STM32C5 is not
in platform-stm32), stm32_build.py and the UF2/stlink chain are
untouched, no wrapper, no upstream anchors.

A/B vs the pre-migration baseline: zephyr-blink firmware.elf/.bin/
.hex/.uf2 (UF2 = the real upload payload) byte-identical; pytest 46
passed; verify_board_manifests 24/0.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant