Repository navigation
Description
Context
Soldr and zccache have completed the same architectural upgrade, and running-process is executing its adaptation now:
- refactor: centralize Soldr host platform code behind crate::platform and cfg_select! forge-ci/soldr#2493 centralized host-platform mechanics behind one
cfg_select!boundary. - refactor: centralize host-platform mechanics behind zccache-platform and crate::platform forge-ci/zccache#1365 adapted that rule to its compiler-target, IPC, and embedding constraints.
- refactor: research and centralize host-platform mechanics behind a single cfg_select! boundary zackees/running-process#965 is the current research-first version, with an exact-occurrence ledger and capability-by-capability migration phases.
fbuild currently pins Rust/MSRV 1.94.1, while std::cfg_select! is stable in Rust 1.95.0. A preliminary source scan found 386 host-cfg match lines across 93 Rust files under crates/. The largest concentrations are fbuild-toolchain (85), fbuild-core (68), fbuild-deploy (59), fbuild-daemon (46), fbuild-cli (37), and fbuild-serial (34). Direct native API usage appears in 21 files, including:
fbuild-core::{containment,path,process_identity,subprocess}- daemon lifecycle/locking
- CLI IDE and USB recovery
- serial port classification/scanning/recovery
- RP2040, probe-rs, WCH, and WLink deployment
- QEMU/toolchain host integration
Native dependencies are also distributed: windows-sys is declared in fbuild-cli, fbuild-deploy, and fbuild-serial; libc is declared in fbuild-core and fbuild-daemon; and interprocess is consumed by fbuild-daemon.
This is not all one category. fbuild must keep three concepts distinct:
- Host mechanics ? what OS/architecture the fbuild executable is running on; this belongs behind the platform boundary.
- Host artifact policy ? which compiler, emulator, deploy tool, or archive must be downloaded for that host; product code owns the policy but must consume neutral host facts instead of compile-time cfg.
- Embedded build target ? the PlatformIO/board/MCU target being compiled or flashed;
fbuild_core::Platform, board policy, toolchain target triples, and orchestrator dispatch remain outside the host-platform layer.
The existing monocrate rule forbids adding a fbuild-platform crate for new functionality. fbuild-core is dependency-cycle-safe and is already the foundational dependency for the workspace, so the fbuild-specific adaptation should be an internal fbuild_core::platform module.
Proposal
Adopt the rule:
One module selects the host. Every fbuild crate consumes a neutral platform facade.
1. Raise the toolchain to Rust 1.95.x
Update every declaration of fbuild's own MSRV/toolchain from 1.94.1 to the same pinned Rust 1.95.x release:
- workspace
rust-version rust-toolchain.toml- MSRV/fmt/Dylint/native-build workflows
- Docker and serial-test bootstrap scripts
- repository guidance and workflow documentation
- focused regression coverage that prevents the declarations drifting
Do not rewrite fixtures that intentionally model arbitrary user or embedded toolchains.
2. Bootstrap the single host-selection boundary in fbuild-core
Create fbuild_core::platform as a module, not a new workspace member. Its root contains the only production host-selection site, using std::cfg_select! with explicit Windows, Linux, and macOS arms and no fallback arm.
The research phase must lock the exact layout, but the neutral capability surface should cover at least:
platform::process: command configuration, background spawn, containment, termination, PID/image inspection, and exit interpretation; delegate to running-process wherever it already owns the portable primitive.platform::fs: file identity, links/reparse behavior, permissions, atomic replacement, volume/mount facts, and native error normalization.platform::ipc: fbuild-owned endpoint/listener/peer mechanics not already abstracted by running-process/interprocess.platform::executable: native executable names, PATH/PATHEXT candidates, current/sibling image discovery, and runnable-image materialization.platform::host: neutralHostOs,HostArch, path-list separator, home/runtime facts, and host keys consumed by toolchain/package/deploy artifact policy.platform::device: serial-port, USB/PnP, sysfs/IOKit/SetupAPI, removable-volume, and topology primitives required byfbuild-serialand deployers.
Neutral facade types must prevent raw handles/fds, Win32 structs, Unix extension traits, concrete socket/pipe types, and concrete OS modules from escaping into shared callers. Every new directory must include its required README.md.
Do not duplicate platform behavior already provided by running-process, serialport, interprocess, or other dependencies. Wrap/delegate where their neutral API is sufficient; own only fbuild-specific seams and types.
3. Research, freeze, and ratchet the migration inventory
Before moving implementations:
- Inventory host selectors, native imports/paths, target-specific dependency tables, compile-time host facts, and concrete platform-module references across all handwritten Rust sources, including inactive cfg branches, inline tests, integration tests, examples, benches, and
build.rs. - Reconcile Windows, Linux, and macOS inventories.
- Classify every occurrence as:
- host mechanic to migrate;
- host artifact policy that should consume neutral runtime/const host facts;
- embedded build-target policy that must remain outside this refactor;
- generated/vendor source;
- narrowly justified specialized artifact, if one truly cannot use the facade.
- Freeze an exact-occurrence ledger keyed by normalized repo-relative path, finding kind, construct, and ordinal.
- Make ordinary migration PRs delete ledger rows only. A stale row, new unlisted occurrence, duplicate row, changed total, or Dylint/scanner disagreement must fail CI.
The preliminary 386-line scan is discovery evidence, not the authoritative ledger total.
4. Migrate by capability
Split implementation into ordered, reviewable phases after the ledger is frozen. Each phase must:
- begin with focused RED characterization or boundary evidence;
- move one coherent capability behind
fbuild_core::platform; - replace caller-side
cfg!/#[cfg]decisions with the neutral facade; - remove the exact corresponding ledger rows in the same PR;
- report before/after global and affected-crate counts;
- prove the stale-row and new-occurrence checks;
- finish GREEN on focused tests plus the repository validation appropriate to the touched crates.
Suggested order: host/executable facts used by artifact selection; process/containment; filesystem/path mechanics; daemon IPC/lifecycle; serial/USB/device mechanics; specialized deploy/emulator cases; final zero-baseline enforcement.
5. Permanently enforce the boundary
Add a pre-expansion Dylint plus an independent whole-tree scanner, following the soldr/zccache/running-process precedent.
Outside the selector and private Windows/Linux/macOS implementation trees, reject:
- host selectors in
#[cfg],#[cfg_attr],cfg!(),cfg_if!, and macro token trees; - compile-time host escape routes such as
std::env::consts::{OS, ARCH}orCARGO_CFG_TARGET_*when used to select host mechanics; - native imports/paths such as
std::os::*,windows-sys,libc, native interprocess transports, raw handles/fds, and concrete platform modules; - direct references to the selected implementation alias;
- new target-specific native dependency tables outside the platform implementation boundary.
The checker must distinguish explicit embedded compiler/build-target policy from host selection. Tests are not an escape hatch: generic tests exercise the neutral facade, while implementation-specific tests live beside the relevant private implementation.
At zero, delete the transitional ledger/dump escape hatch and retain the permanent zero-violation Dylint, whole-tree scanner, manifest checker, and three-host behavior coverage.
Acceptance criteria
- RED: focused fixtures prove current private/inactive host cfg, native API access, concrete implementation references, and test-only occurrences evade the proposed boundary.
- fbuild's MSRV and every canonical toolchain declaration are updated consistently from 1.94.1 to the selected Rust 1.95.x release, with drift coverage.
- No new workspace crate is added; the boundary lives in
fbuild_core::platformin accordance with the monocrate policy. - Exactly one production
cfg_select!selects Windows, Linux, or macOS, with no fallback arm. - The reviewed three-host inventory classifies host mechanics, host artifact policy, embedded target policy, and any specialized artifacts without conflating them.
- All shared callers use neutral platform facade types/functions and cannot name native OS APIs, raw native types, or concrete implementations.
- Existing running-process broker/containment behavior, daemon compatibility, serial/USB recovery, deploy behavior, toolchain resolution, and diagnostics remain compatible unless a separately approved issue changes them.
- A Linux-hosted build regression proves that selecting a Windows/macOS embedded toolchain artifact or firmware target does not select non-Linux host mechanics.
- Windows, Linux, and macOS CI exercise representative process, filesystem, executable/host, IPC, serial/USB/device, deploy, and emulator behavior through the same facade paths.
- The exact-occurrence ledger monotonically reaches zero; the permanent Dylint, scanner, and manifest checker reject new host-platform leakage.
- GREEN: focused tests and the repository's full validation entrypoints pass for each migration phase and at final closeout.
Decisions
- Priority: P2 architectural debt; behavior works today, but the breadth of distributed host selection makes portability changes and review increasingly risky.
- Use
fbuild-core, not a newfbuild-platformcrate, because the repository explicitly keeps functionality in the existing crate set and the dependency graph makesfbuild-corethe cycle-safe shared home. - Require Rust 1.95.x because stable
std::cfg_select!is the mechanism used by the soldr/zccache precedent and gives an exhaustive compile-time selector. - Treat the six capability namespaces as the initial architecture; the frozen inventory may merge an empty namespace or split a demonstrably overloaded one without weakening the single-selector rule.
- Keep embedded target selection and package/tool artifact policy in their current product owners; only raw host facts and mechanics move behind the facade.
- Use an exact-occurrence ratchet rather than a file allowlist so migrated files cannot silently acquire new platform debt.
Related issues
- refactor: research and centralize host-platform mechanics behind a single cfg_select! boundary zackees/running-process#965 ? active research-first adaptation and enforcement model.
- refactor: centralize Soldr host platform code behind crate::platform and cfg_select! forge-ci/soldr#2493 ? original single-
cfg_select!host-platform architecture. - refactor: centralize host-platform mechanics behind zccache-platform and crate::platform forge-ci/zccache#1365 ? completed adaptation for a multi-crate compiler/cache application.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTriage
Activity
zackees commentedon Aug 20, 2026
Broken into nine ordered native sub-issues:
Each phase is independently closable only by a merged PR with recorded RED -> GREEN evidence. Scope/order in phases 3-8 is subject to the reviewed dependency edges from #1307 without weakening the one-selector or zero-debt end state.
fix(ci): gate the ETXTBSY tests to Linux and route through the platfo…
fix(ci): retry ETXTBSY spawns and stop reporting them as missing libr…
ci(dylint): add a Windows leg so Windows-only code is actually linted (…