ADR-0021: Consolidate build output into a single build/ tree
Date: 2026-06-14 / Status: Adopted
Decision
Consolidate all build artifacts — both cargo target dirs, C# bin output, the
publish bundle, the release package, the SBOM, and the assembled docs — into a
single build/ tree at the repository root. The individual subdirectory names
are not restated here; xtask/src/paths.rs is their source of truth.
Mechanism (all means that do not violate the prohibition rules):
- Rust: per-workspace
[build] target-dirin.cargo/config.toml(engine/.cargo→../build/engine,xtask/.cargo→../build/xtask). Relative paths resolve against the.cargo/parent (confirmed empirically withcargo metadata). A single config at the repository root is rejected (both workspaces would share one target and break the ADR-0018 separation rule). - C# bin: each csproj’s
BaseOutputPath(..\..\build\app\<proj>\). - dist/package/site:
xtask/src/paths.rsas the single source of truth — every derived path is a function there, so no other file (including this one) may spell one out. - mdBook:
build.build-dir = ../build/docs-bookindocs/book.toml.
Rationale
- Artifacts were scattered across
engine/target,xtask/target,app/**/bin, rootdist/, root zip, root SBOM, andsite/, making them costly to track and clean. A singlebuild/means “delete it and everything is gone” plus an effectively one-line.gitignore. - The target-dir in
.cargo/config.tomlis not a toolchain pin, so it does not violate the rule against placingrust-toolchain.toml/global.json(avoiding double management with mise).
Consequences
- C# obj stays put (
app/**/obj/). Relocating obj requiresBaseIntermediateOutputPathto take effect during pre-restore evaluation, which effectively requiresDirectory.Build.props, but AGENTS.md prohibits that file (it silently shadows the analyzer injection ofwinapp run). obj is intermediate output and already gitignored, so there is no real harm. - The dev-tree
fmf-service.exelookup (ServiceSetup.csproduction + pipe/contract tests) followsbuild/engine/release. - Bundle internals: the
dist/FindMyFiles/root holds only the launcher +README.txt; the self-contained app and engine binaries publish intodist/FindMyFiles/app/(the .NET apphost must stay co-located with its runtime DLLs, so it cannot move to the root). The rootFindMyFiles.exeis a tiny native launcher (thefmf-launchercrate) that spawnsapp/FindMyFiles.exe— so a downloaded/extracted zip has one obvious thing to run.paths::app_dir()is the single source for the subfolder; the app’s own relative discovery (AppPaths,ServiceSetup.LocateServiceExe) is unchanged because everything it needs stays beside the apphost inapp/. - The test-tmp fallback default in
testutil.rsisbuild/engine(because the config.toml target-dir does not set theCARGO_TARGET_DIRenv var). - CI (ci/release/pages) artifact, SBOM, package, and Pages paths are all updated to under
build/.site/remains the committed landing source; assembly output goes tobuild/site. - Tools that assumed the old
engine/targetetc. (rust-analyzer, etc.) follow because they respect config.toml (reload if needed).
Re-examination triggers
- If demand to also remove C# obj from the root grows strong and the
winapp runanalyzer-injection mechanism changes to no longer depend onDirectory.Build.props(re-evaluate whether to allow props).