You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Replace fbuild's external managed-zccache binary (zccache wrap … + zccache start + zccache fp …) with an in-process ZccacheService running inside fbuild-daemon, mirroring the embedded-backend model that soldr landed in zackees/soldr#977.
Current state
crates/fbuild-build/src/managed_zccache.rs pins MANAGED_ZCCACHE_VERSION
= 1.12.9 and downloads three binaries (zccache, zccache-daemon, zccache-fp) into ~/.fbuild/<mode>/bin/zccache-<ver>/.
crates/fbuild-build/src/zccache.rs resolves that binary (FBUILD_NO_ZCCACHE
→ FBUILD_ZCCACHE_BIN → managed → env PATH), then per compile:
prepends [\"zccache\", \"wrap\", …] via wrap_args,
shells out to zccache fp check / zccache fp mark-success for
workspace fingerprints.
Roughly half of zccache.rs (~300 lines) exists to keep wrapper-process
cache keys stable: workspace-relative cwd derivation
(compile_cwd_from_output), --sysroot / -I rewriting
(normalize_flags_for_compile_cwd), Windows UNC \?\ stripping. All of
it is only necessary because the cache lives behind a child process.
Why migrate (mirroring soldr's reasoning)
One tokio runtime. Embedded service shares fbuild-daemon's runtime,
so a single tokio-console attach spans build orchestration and the cache
layer. Today they are two independent processes with separate runtimes.
No per-compile exec + IPC overhead.compile_many.rs currently spawns
one wrapper process per TU; embedded dispatch is an in-process function
call.
Shrinks zccache.rs. Path/cwd/UNC normalization is a workaround for
wrapper-process semantics. Embedded API takes paths directly.
No managed-binary download to maintain. Removes the GitHub release
dependency, the lockstep version bumps in pyproject.toml / MANAGED_ZCCACHE_VERSION / the per-platform asset matrix, and the ~/.fbuild/<mode>/bin/zccache-<ver>/ install dance.
Removes the intentionally-detached daemon lifecycle. No need for zccache start to outlive fbuild-daemon — the cache is the daemon.
Soldr proved the model: CompileBackend::{Wrapped, Embedded} on daemon
state, runtime opt-in via SOLDR_ZCCACHE_EMBEDDED=1, wrapper path kept as
fallback. See soldr commit 41d4753 (zackees/soldr#977).
Add optional embedded Cargo feature on fbuild-build pulling in the zccache library as a git rev = \"…\" dep (start with a pinned rev; only
graduate to a _vender/zccache submodule if iteration friction shows up —
see "vendoring" below).
Add CompileBackend::{Wrapped, Embedded} enum, held on a daemon state
struct in fbuild-daemon. fbuild-build accesses it through a handle in
the same way orchestrators access other daemon-owned state today.
Runtime opt-in via FBUILD_ZCCACHE_EMBEDDED=1. Wrapper path stays
default — zero behavioral change without the flag.
New crates/fbuild-daemon/src/zccache_embedded.rs wraps ZccacheService
with fbuild's identity defaults + ~/.fbuild/<mode>/cache/ root.
Phase 2 — Route per-compile dispatch through embedded
compile_many.rs / compile_exec.rs (or equivalent) gain a path that
calls ZccacheService::compile(...) directly when CompileBackend::Embedded is active. Wrapped path untouched.
Verify cache-key compatibility: identical artifacts and hit rate between
wrapped and embedded on the same workspace, or document and justify any
divergence.
Phase 3 — Embedded fingerprint API
Migrate check_fingerprint / mark_fingerprint_success off the zccache fp CLI to the embedded equivalent. If upstream doesn't expose
it as a library API yet, file a zccache-side issue.
Phase 4 — Retire wrapper
Once embedded mode is the default for one release cycle without
regressions, delete managed_zccache.rs, the wrapper code path in zccache.rs, and the path-normalization helpers that only existed to
serve wrapper cache keys.
Drop the related env vars (FBUILD_ZCCACHE_BIN, FBUILD_ZCCACHE_EMBEDDED) once the wrapper path is gone.
Open design questions
Where in the dep graph does the embedded service live? Per the
monocrate policy, no new crate. Two viable spots: (a) a module under fbuild-daemon (matches soldr — long-lived daemon owns the service),
or (b) a module under fbuild-build behind the feature flag. Soldr put
it under soldr-cli/src/daemon/; in fbuild that maps to fbuild-daemon/src/zccache_embedded.rs, with fbuild-build getting a
handle through the daemon state struct it already consumes.
Vendoring. Soldr learned the hard way (f176c31 → c2014cc → 2f14453) that flat-vendoring zccache into _vender/zccache/ is wrong;
the working model is a git submodule for fast iteration or a plain
git-pinned Cargo dep when no upstream changes are needed. Recommend
starting at the plain git-pin and only adopting the submodule if a
cross-repo change forces it.
Cache-key compatibility. The wrapper-mode helpers
(compile_cwd_from_output, normalize_flags_for_compile_cwd, UNC
stripping) normalize keys to be workspace-relative. Embedded mode must
preserve that normalization (either inside ZccacheService or as a
preprocessing step in the fbuild call site) or hot caches built with the
wrapper will miss after switching.
Concurrency.compile_many.rs parallelizes compiles by spawning N
wrapper processes today. Embedded must support parallel in-process
compiles without forcing a single-mutex bottleneck — confirm ZccacheService is Sync and the appropriate handle pattern.
fbuild-cli boundary. The CLI is a thin HTTP client and must not
depend on fbuild-daemon. The embedded backend lives entirely on the
daemon side; the CLI sees no API change.
Test plan / acceptance
Default builds (no flag) continue to use the wrapper; all existing
tests in zccache.rs still pass.
New integration test: with FBUILD_ZCCACHE_EMBEDDED=1, build tests/platform/uno, verify a second build hits cache, and verify
via process snapshot that no zccache.exe was spawned.
Wrapped vs embedded produce identical object files on tests/platform/uno + tests/platform/esp32dev (or document the
diff with rationale).
Cold + warm timing comparison published in the PR — quantify the
per-TU wrapper-spawn overhead this removes.
soldr cargo clippy --workspace --all-targets -- -D warnings clean
with and without the embedded feature.
Goal
Replace fbuild's external managed-zccache binary (
zccache wrap …+zccache start+zccache fp …) with an in-processZccacheServicerunning insidefbuild-daemon, mirroring the embedded-backend model that soldr landed inzackees/soldr#977.
Current state
crates/fbuild-build/src/managed_zccache.rspinsMANAGED_ZCCACHE_VERSION= 1.12.9 and downloads three binaries (
zccache,zccache-daemon,zccache-fp) into~/.fbuild/<mode>/bin/zccache-<ver>/.crates/fbuild-build/src/zccache.rsresolves that binary (FBUILD_NO_ZCCACHE→
FBUILD_ZCCACHE_BIN→ managed → env PATH), then per compile:[\"zccache\", \"wrap\", …]viawrap_args,zccache startdaemon (ensure_running,intentionally detached per Daemon teardown hardening: process containment + socket lingering + console events #32 so it outlives the fbuild daemon),
zccache fp check/zccache fp mark-successforworkspace fingerprints.
zccache.rs(~300 lines) exists to keep wrapper-processcache keys stable: workspace-relative cwd derivation
(
compile_cwd_from_output),--sysroot/-Irewriting(
normalize_flags_for_compile_cwd), Windows UNC\?\stripping. All ofit is only necessary because the cache lives behind a child process.
Why migrate (mirroring soldr's reasoning)
fbuild-daemon's runtime,so a single tokio-console attach spans build orchestration and the cache
layer. Today they are two independent processes with separate runtimes.
compile_many.rscurrently spawnsone wrapper process per TU; embedded dispatch is an in-process function
call.
zccache.rs. Path/cwd/UNC normalization is a workaround forwrapper-process semantics. Embedded API takes paths directly.
dependency, the lockstep version bumps in
pyproject.toml/MANAGED_ZCCACHE_VERSION/ the per-platform asset matrix, and the~/.fbuild/<mode>/bin/zccache-<ver>/install dance.zccache startto outlivefbuild-daemon— the cache is the daemon.Soldr proved the model:
CompileBackend::{Wrapped, Embedded}on daemonstate, runtime opt-in via
SOLDR_ZCCACHE_EMBEDDED=1, wrapper path kept asfallback. See soldr commit
41d4753(zackees/soldr#977).Proposed approach (phased)
Phase 1 — Embedded backend prep (opt-in, default off)
embeddedCargo feature onfbuild-buildpulling in thezccachelibrary as agit rev = \"…\"dep (start with a pinned rev; onlygraduate to a
_vender/zccachesubmodule if iteration friction shows up —see "vendoring" below).
CompileBackend::{Wrapped, Embedded}enum, held on a daemon statestruct in
fbuild-daemon.fbuild-buildaccesses it through a handle inthe same way orchestrators access other daemon-owned state today.
FBUILD_ZCCACHE_EMBEDDED=1. Wrapper path staysdefault — zero behavioral change without the flag.
crates/fbuild-daemon/src/zccache_embedded.rswrapsZccacheServicewith fbuild's identity defaults +
~/.fbuild/<mode>/cache/root.Phase 2 — Route per-compile dispatch through embedded
compile_many.rs/compile_exec.rs(or equivalent) gain a path thatcalls
ZccacheService::compile(...)directly whenCompileBackend::Embeddedis active. Wrapped path untouched.wrapped and embedded on the same workspace, or document and justify any
divergence.
Phase 3 — Embedded fingerprint API
check_fingerprint/mark_fingerprint_successoff thezccache fpCLI to the embedded equivalent. If upstream doesn't exposeit as a library API yet, file a zccache-side issue.
Phase 4 — Retire wrapper
regressions, delete
managed_zccache.rs, the wrapper code path inzccache.rs, and the path-normalization helpers that only existed toserve wrapper cache keys.
FBUILD_ZCCACHE_BIN,FBUILD_ZCCACHE_EMBEDDED) once the wrapper path is gone.Open design questions
monocrate policy, no new crate. Two viable spots: (a) a module under
fbuild-daemon(matches soldr — long-lived daemon owns the service),or (b) a module under
fbuild-buildbehind the feature flag. Soldr putit under
soldr-cli/src/daemon/; in fbuild that maps tofbuild-daemon/src/zccache_embedded.rs, withfbuild-buildgetting ahandle through the daemon state struct it already consumes.
f176c31→c2014cc→2f14453) that flat-vendoring zccache into_vender/zccache/is wrong;the working model is a git submodule for fast iteration or a plain
git-pinned Cargo dep when no upstream changes are needed. Recommend
starting at the plain git-pin and only adopting the submodule if a
cross-repo change forces it.
(
compile_cwd_from_output,normalize_flags_for_compile_cwd, UNCstripping) normalize keys to be workspace-relative. Embedded mode must
preserve that normalization (either inside
ZccacheServiceor as apreprocessing step in the fbuild call site) or hot caches built with the
wrapper will miss after switching.
compile_many.rsparallelizes compiles by spawning Nwrapper processes today. Embedded must support parallel in-process
compiles without forcing a single-mutex bottleneck — confirm
ZccacheServiceisSyncand the appropriate handle pattern.fbuild-cliboundary. The CLI is a thin HTTP client and must notdepend on
fbuild-daemon. The embedded backend lives entirely on thedaemon side; the CLI sees no API change.
Test plan / acceptance
tests in
zccache.rsstill pass.FBUILD_ZCCACHE_EMBEDDED=1, buildtests/platform/uno, verify a second build hits cache, and verifyvia process snapshot that no
zccache.exewas spawned.tests/platform/uno+tests/platform/esp32dev(or document thediff with rationale).
per-TU wrapper-spawn overhead this removes.
soldr cargo clippy --workspace --all-targets -- -D warningscleanwith and without the
embeddedfeature.References
CompileBackend::{Wrapped, Embedded}enum,SOLDR_ZCCACHE_EMBEDDED=1runtime opt-inf176c31/c2014cc/2f14453— vendoring strategyevolution; flat-vendor is the anti-pattern, submodule + path-dep is the
destination
crates/fbuild-build/src/zccache.rs— current wrapper-mode call sitecrates/fbuild-build/src/managed_zccache.rs— current managed-binarydownload
zccache start(becomes moot under embedded)