Reach for this doc before you construct, join, compare, or hash any filesystem path — especially cache directories, build directories, and anything that feeds a cache key. Getting the prefix root wrong is silent: the code compiles, the build "works", and the only symptom is a cache that never hits (or, worse, a cache that hits when it shouldn't). Two real bugs in #952 came from exactly this.
Every fbuild path is rooted at a prefix chosen by a factory function. There are two families, and they are not interchangeable:
| Prefix root | Factory | Example contents |
|---|---|---|
Global cache — ~/.fbuild/{dev|prod}/cache/ |
fbuild_paths::get_cache_root() → Cache::new(project_dir).*_dir() |
packages/, toolchains/, platforms/, libraries/, core/ |
Global fbuild root — ~/.fbuild/{dev|prod}/ |
fbuild_paths::get_fbuild_root() |
daemon/, cache/, tmp/, zccache/ |
Per-project build — <project>/.fbuild/build/{env}/{profile}/ |
fbuild_paths::BuildLayout / get_project_build_dir(project_dir) |
core/, src/, fw_libs/, firmware.elf, firmware.bin |
Load-bearing rules:
- The
{dev\|prod}segment is chosen byis_dev_mode()inside the factory. Never hardcodeprod(ordev) into a path — call the factory so dev-mode isolation (FBUILD_DEV_MODE=1→ port 8865,~/.fbuild/dev/) keeps working. - Global caches are keyed by content/signature, not by project.
Cache::new(project_dir).core_artifacts_dir()returns~/.fbuild/{dev|prod}/cache/coreand ignoresproject_dir— theproject_dirargument is only there so the sameCachehandle can also resolve per-project dirs. If you want a cache shared across projects, it must live underget_cache_root(), and its key must not encode the project directory (see next section). - Never fabricate a placeholder root like
/projector/tmp/xin code — even in tests. Usetempfile::TempDirfor a real, normalized, auto-cleaned path. A literal/…root reads as production intent and hides spelling/prefix bugs.
The #942/#952 caching bug: two builds of the same project from different
directories (/tmp/nds vs /tmp/nds2) produced different cache keys, so the
global cache never hit. Root cause: project-specific -I<dir> include flags
leaked into the signature, and a raw Path::starts_with(project_dir) failed
to strip them because the include-dir spelling didn't match the
canonicalized project_dir spelling.
When a value must be compared against, or excluded relative to, a root:
- Normalize both sides through the same factory before comparing. Use
fbuild_core::path::normalize_for_key(strips\\?\, folds separators to/, case-folds on Windows/macOS) or compareNormalizedPathvalues. Do not hand-rollstarts_withon rawPaths that may be spelled differently (canonicalized vs not,\\?\prefix, trailing slash, case) — that is exactly the comparison that silently missed and defeated the cache. - A cross-project cache key must be project-independent. Before hashing,
strip (or relativize) anything rooted under
project_dir— include dirs,-I/-isystem/-iquoteflags, and build-dir paths. A key that encodes the project directory gives every project directory a distinct key. - A zccache-visible compile key is workspace-relative or it won't hit
across project dirs. Compiles routed through
crate::compiler::compile_sourcerelativize source/-o/-Ito the workspace root viazccache::compile_cwd_from_output+path_arg_for_compile_cwd+normalize_flags_for_compile_cwd, and run withcwd = workspace. An invocation that passes absolute paths withcwd = None(as the legacylibrary_compilerfw-libs path did) bakes the project directory into the key and misses the cache on every fresh project.
- Which prefix root is this? Did you get it from a factory, or hardcode it?
- Is
{dev|prod}resolved byis_dev_mode(), not a literal? - If it's a cache key: does it accidentally encode the project directory, the build directory, or an absolute machine-specific path?
- If you compare paths: are both sides normalized through
normalize_for_key/ aNormalizedPath? - In tests: real
TempDir, never a/placeholderroot.
See crates/fbuild-paths/src/lib.rs (roots + BuildLayout),
crates/fbuild-packages/src/cache.rs (cache subdir layout),
crates/fbuild-core/src/path.rs (NormalizedPath, normalize_for_key),
and crates/fbuild-build/src/zccache.rs (workspace relativization).