Agents: for task-specific routing (which
fbuildsubcommand, how deploy actually works, DTR/RTS rules), see the root../CLAUDE.mdrouting table and../agents/docs/. This doc covers crate boundaries and dependency policy — not "how to do task X".
fbuild is kept as close to a monocrate as possible. The set of crates below is
intentionally fixed. New functionality is folded into an existing crate as a
module, never introduced as a new crate. Examples that already follow this:
per-platform build orchestrators are modules under fbuild-build, and the
running-process v1 broker adoption is a module under fbuild-daemon/src/broker/
(it was deliberately not kept as a standalone fbuild-broker crate —
#560).
When something is needed by two crates that cannot depend on each other (the
classic case: fbuild-cli is a thin HTTP client and must not depend on
fbuild-daemon), put the shared, dependency-free pieces in a crate both already
depend on — fbuild-core or fbuild-paths — and keep the heavy/transport code
in the consuming crate. (That is exactly how the broker's display constants and
CacheRoots live in fbuild-paths::running_process while the prost/session
machinery stays in fbuild-daemon.)
This is enforced by CI: crate-gate.yml runs ci/check_workspace_crates.py,
which fails if [workspace] members gains an entry outside the approved
allowlist. A genuinely-justified new crate requires adding it to that allowlist
in the same PR with a maintainer-reviewed rationale.
fbuild-daemon (bin) ──────────────────────────────────────────┐
├─ fbuild-build ─── fbuild-config ─── fbuild-core │
│ ├── fbuild-paths ─── fbuild-core │
│ └── fbuild-packages ─── fbuild-paths │
├─ fbuild-deploy ── fbuild-serial ─── fbuild-core │
│ ├── fbuild-config │
│ └── fbuild-paths │
├─ fbuild-serial ── fbuild-core (DashMap, broadcast, tokio) │
└─ fbuild-packages ── fbuild-config, fbuild-paths │
│
fbuild-cli (bin: "fbuild") ──────────────────────────────────┤
├─ fbuild-core │
├─ fbuild-config │
└─ fbuild-paths │
│
fbuild-python (cdylib: PyO3 bindings) ───────────────────────┤
├─ fbuild-core │
├─ fbuild-serial │
└─ fbuild-paths │
│
fbuild-test-support (test utilities) ────────────────────────┘
- fbuild-core —
FbuildError/Result,BuildProfile,Platform,SizeInfo,DaemonState. USB identity catalogues are fetched from FastLED/boards and must never be generated or embedded here outside test fixtures. - fbuild-config —
PlatformIOConfig(INI parser withextendsinheritance),BoardConfig,McuSpec - fbuild-paths — Dev/prod path isolation (
~/.fbuild/{dev|prod}/), version+identity-keyed daemon endpoint (daemon_endpoint_key/default_daemon_port, Windows 10000–49151 to avoid excluded dynamic ports, 49152–65535 elsewhere; #1009), cache dirs - fbuild-packages — URL-based package downloads, toolchain resolution, library manager, parallel pipeline
- fbuild-serial —
SharedSerialManager(centralized serial I/O), deploy preemption protocol, WebSocket messages, USB-CDC retry logic - fbuild-build —
BuildOrchestratortrait, per-platform orchestrators (AVR, ESP32, ESP8266, RP2040, STM32, Teensy, WASM) - fbuild-deploy —
Deployertrait, esptool/avrdude/picotool invocation, firmware upload - fbuild-daemon — Axum HTTP/WS server, request processors, device lease manager, lock manager, emulator runners (avr8js, simavr, QEMU)
- fbuild-cli — Clap CLI: build, deploy, test-emu, monitor, purge subcommands. Thin HTTP client to daemon.
- fbuild-python — PyO3 bindings:
SerialMonitor,Daemon,DaemonConnection,connect_daemon - fbuild-test-support —
create_test_project(), temp dir fixtures
Daemon-centric: All serial access routes through SharedSerialManager in the daemon. No OS-level port locks. Multiple readers (broadcast), exclusive writer (condition variable).
Deploy preemption: Deploy forcibly closes serial sessions → flash via esptool → 2s USB re-enumeration delay → monitors auto-reconnect. Monitors with auto_reconnect=true pause during preemption.
HTTP API boundary: CLI sends JSON requests to daemon over HTTP. Build output streams via WebSocket. Serial monitor data streams via /ws/serial-monitor. All endpoints match the Python FastAPI daemon's contract.
Diagnostic subcommand exception: A small, growing set of fbuild-cli subcommands (clang-tidy, clang-query, iwyu, mcp, lnk, lib-select) run in-process and intentionally bypass the daemon. They are read-only diagnostics that don't need build orchestration, so a round-trip through the HTTP API would only add latency. install also runs in-process: it only resolves and downloads packages into the cache (no compile, no build lock), and CI runs it as its own step before any build (#1433). The "thin HTTP client" rule still applies to every command that touches the build pipeline (build, deploy, monitor, test-emu, etc.).
PyO3 consumer contract: FastLED imports SerialMonitor as a Python context manager with read_lines(), write(), write_json_rpc(). The fbuild-python crate must preserve this API exactly.
Workspace scaffolded with all crate dependencies, types, and traits. All 11 crates compile clean (clippy -D warnings). Implementation starts with fbuild-serial (highest risk, most complex).