ADR-0021: Consolidate build output into a single build/ tree
Date: 2026-06-14 / Status: Adopted
Decision
Consolidate all build artifacts into a single build/ tree at the repository root.
build/
├── engine/ # cargo target-dir for the engine workspace
├── xtask/ # cargo target-dir for the xtask workspace
├── app/ # C# bin output (FindMyFiles / FindMyFiles.Tests)
├── dist/FindMyFiles/ # publish bundle
├── package/ # release zip + SHA256SUMS.txt
├── sbom/ # CycloneDX SBOM (release.yml)
├── site/ # GitHub Pages assembly (landing + book + doc)
└── docs-book/ # mdBook output
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 (build_root/dist_dir/package_dir/engine_release_dir/site_dir). - 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 CLAUDE.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. - 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).