Skip to content

Feasibility: real SCons engine for extra_scripts beyond the native flag-mutation shim #553

Description

@zackees

Goal

Investigate whether fbuild can offer real SCons execution as a platformio.ini backwards-compatibility mode for the dynamic extra_scripts behaviors that the current native shim deliberately rejects — and document the engine required and the architectural cost.

Framing note: The original request assumed "fbuild does not have this feature." That is only partly true. fbuild already ships a constrained SCons-compatibility layer (a Python MockEnv subprocess that resolves flag/path/lib mutations) — see crates/fbuild-build/src/script_runtime.rs and crates/fbuild-build/src/script_runtime_harness.py. This issue is about the remaining gap: scripts that need a real SCons engine, not a mock. The earlier journey is closed under #34#38#39#41#43; this issue picks up where #43's "explicit non-goals" left off.

Current state (what already works)

fbuild's native path resolves a narrow subset of extra_scripts without SCons:

  1. PlatformIOConfig::get_extra_scripts() reads pre:/post: entries (crates/fbuild-config/src/ini_parser/mod.rs).
  2. resolve_extra_script_overlay() spawns a Python subprocess running script_runtime_harness.py, which executes the user's scripts against a mock SCons env/projenv (MockEnv) and serializes the resulting construction-variable scopes to JSON (crates/fbuild-build/src/script_runtime.rs:29).
  3. The Rust side translates those scopes (CPPDEFINES, CPPPATH, CCFLAGS, CFLAGS, CXXFLAGS, ASFLAGS, LINKFLAGS, LIBPATH, LIBS) into compile/link flag overlays and feeds them into the normal native pipeline.

Supported shims today: Append/AppendUnique/Prepend/Replace over the known scopes, plus read-only/no-op helpers Dump, BoardConfig, PioPlatform, Flatten, VerboseAction, Execute (no-op), IsCleanTarget, IsIntegrationDump, subst, GetProjectOption. Anything else hard-fails with "Recommendation: use --platformio."

Per the #43 real-world sample (25 GitHub repos), this covers 8/25 projects natively. The remaining misses that need genuine SCons semantics cluster as:

  • Effectful Execute(...) — scripts that actually run a tool to generate sources/headers/firmware images at build time (e.g. merge_firmware.py, generate_includes.py).
  • SConscript(...) — pulling in additional SCons build fragments.
  • AddBuildMiddleware / custom builders / custom targets — Marlin-class build stacks.
  • Construction-variable rewrites beyond flag scopesPROGNAME, MKSPIFFSTOOL, custom env[...] tool substitutions.
  • env.get(...) / GetProjectConfig / GetBuildType / ParseFlagsExtended used to drive dynamic logic.

The actual open question

"Full SCons support" can mean three architecturally different things. The earlier issues (#39, #41) already established that PlatformIO's __idedata / pio project metadata export is insufficient to faithfully replay the build graph (no full link line, no library archive ordering, no action hooks). So the realistic options are:

Option A — Host a real SCons engine in-process/vendored

Vendor SCons (pure-Python, MIT license) plus PlatformIO's builder/ tools (the platform build scripts that define the actual compile/link/upload builders). Run the genuine construction-environment build far enough to either (a) emit a resolved build spec or (b) drive compilation itself.

  • Engine required: a Python interpreter at build time (already a soft dependency for the current shim) and the full PlatformIO Core + platform packages, because extra_scripts run inside PlatformIO's builder, not bare SCons. There is no "bare SCons" PlatformIO project — the SConstruct is PlatformIO's builder/main.py.
  • Implication: This is effectively becoming a PlatformIO host. At that point the marginal value over the existing --platformio passthrough is small, and we'd own a vendored copy of two large Python codebases plus their version skew across platform packages.
  • Cost: Very high. Cross-platform Python packaging in a Rust/PyPI-distributed tool, version-matrix maintenance against PlatformIO releases, and the philosophical reversal of "we don't run SCons ourselves."

Option B — Delegate the resolution phase to PlatformIO, then compile natively (the #41 "state-dump bridge", upgraded from mock to real)

Instead of MockEnv, invoke the real pio run with an injected final POST script that runs after all user scripts, serializes the fully-resolved env/projenv state (tool paths, all scopes, PIOBUILDFILES, linker script, flash-image steps) to JSON, and exits before compilation. fbuild then performs native compile/link/deploy from that spec.

  • Engine required: PlatformIO Core installed/available (same prerequisite as --platformio), invoked once per env to produce the dump. No vendoring.
  • Covers: dynamic flag computation, env.get, BoardConfig/PioPlatform for real, build_unflags, and Execute(...) side effects (since real PlatformIO actually runs them, generated sources land on disk before the dump).
  • Does NOT cleanly cover: custom builders/targets and middleware whose behavior is only known at action-execution time (Prototype a PlatformIO/SCons state-dump bridge for scripted-project compatibility #41 failure criteria), and anything that requires the actual SCons DAG to drive incremental rebuilds.
  • Cost: Medium. New "resolve via pio, build via fbuild" mode + the injected dump harness + a translation layer from PlatformIO's resolved state to fbuild's BuildContext. Risk: divergence between dumped state and what PlatformIO would actually execute. Loses fbuild's caching/speed advantage for the resolution step (one pio invocation per env).

Option C — Keep the MockEnv shim, widen it opportunistically (status quo trajectory)

Continue adding read-only/deterministic shims as real repos demand them, keep hard-failing on effectful behavior with the --platformio recommendation.

  • Engine required: none beyond the current Python subprocess.
  • Cost: Low and incremental, but structurally cannot cover effectful Execute, SConscript, or custom builders — those need a real engine. Ceiling is roughly the "simple flag-file" class of scripts.

Option D — Status quo fallback only

--platformio already delegates the entire build to real PlatformIO for anything unsupported. Cost: zero; UX cost: user loses fbuild's speed for those projects.

Feasibility assessment

  • Is real SCons support feasible? Technically yes via Option A, but it is architecturally self-defeating: hosting SCons means hosting PlatformIO, which is what --platformio already does. Not recommended.
  • Is there a feasible middle path? Option B is the only one that meaningfully extends coverage (especially generator scripts via real Execute side effects) without permanently owning a vendored Python build system. It was prototyped-on-paper in Prototype a PlatformIO/SCons state-dump bridge for scripted-project compatibility #41 but never built; Research whether PlatformIO __idedata / project metadata can support a scripted-project bridge #39 confirmed the metadata-export shortcut alone is insufficient, so Option B specifically requires the injected-dump variant, not pio project metadata.
  • What's the cheapest high-value next step? Likely Option C for a couple more deterministic shims, with Option B as the strategic investment if/when generator-script projects become a priority.

Proposed scope for THIS issue

This is a feasibility/architecture decision issue, not an implementation issue. Deliverables:

  • Confirm the engine analysis above against the current PlatformIO Core (builder/main.py, builder/tools/piointegration.py) — verify there is no bare-SCons entry point that avoids hosting PlatformIO.
  • Build a throwaway Option B spike: inject a real final-POST dump script into pio run, capture resolved env/projenv + tool paths + PIOBUILDFILES for one generator-script repo from Real-world platformio.ini sample reveals remaining native compatibility gaps #43 (e.g. UtilitechAS/amsreader-firmware, which uses Execute+VerboseAction) and one synthetic fixture.
  • Record whether the dump is rich enough to drive a native compile+link without a second pio invocation (the Prototype a PlatformIO/SCons state-dump bridge for scripted-project compatibility #41 success criterion).
  • Decide: pursue Option B, stay on Option C, or formally declare effectful SCons a permanent --platformio-only path and document it.

Recommendation (for discussion)

Decline Option A (becoming a SCons host duplicates --platformio). Treat Option B as the only feasible way to close the generator-script gap, gated behind a spike that proves the injected-dump is replay-sufficient. Until then, keep widening the deterministic MockEnv shim (Option C) and keep effectful scripts on the --platformio fallback (Option D).

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestquestionFurther information is requested

    Type

    No type

    Projects

    Status
    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions