One build flow, one cache, one secondary index.
All packages use the same build flow. The variations are:
- Cache key: what identifies a cached build
- Context: where the package builds
| Type | Source | Context | Cache Key |
|---|---|---|---|
| Workspace | Local dune project | default |
-- (not cached) |
| Vendored | duniverse/ |
default |
-- (not cached) |
| Locked | dune.lock |
default |
<name>.<ver>-<bid8> |
| Toolchain | dune.lock (compiler) |
default |
<name>.<ver>-<bid8> |
| Dev tool | Auto-solved | tools-<name> |
See below |
Where <bid8> = 8-char build_id (Merkle hash of opam content + deps' build_ids).
Vendored packages are never cached because users may edit them.
| Type | Key | Example |
|---|---|---|
| Compiler-independent | <name>.<ver>-<checksum8> |
ocamlformat.0.26.2-a1b2c3d4 |
| Compiler-dependent | <name>.<ver>-<bid8> |
odoc.2.4.0-f7e8d9c0 |
Compiler-independent tools (ocamlformat, dune-release) produce identical binaries regardless of project OCaml version. Using source checksum enables cross-project sharing.
Compiler-dependent tools (odoc, merlin, utop) interact with compiler internals. Their build_id already includes the compiler as a dependency, ensuring correct cache separation.
~/.cache/dune/
├── db/files/v5/<hash>/ # Content-addressed cache (existing)
└── index/ # Secondary index (new)
├── fmt.0.9.0-a1b2c3d4 → ../db/files/v5/deadbeef...
├── ocamlformat.0.26.2-b3c4d5e6 → ../db/files/v5/cafebabe...
└── odoc.2.4.0-f7e8d9c0 → ../db/files/v5/12345678...
The content-addressed cache keys on rule hash (all inputs). This prevents sharing:
Project A (OCaml 5.2) + ocamlformat 0.26.2 → rule hash X
Project B (OCaml 4.14) + ocamlformat 0.26.2 → rule hash Y
Different rule hashes, but identical binary. The secondary index uses simpler keys that capture only what matters for correctness.
- No duplication: one copy in content-addressed store
- Atomic:
symlink()is atomic on POSIX - Simple check: symlink exists = cached
- Self-validating: target hash ensures correctness
compute cache_key
│
├── None (workspace/vendored)
│ │
│ ▼
│ build normally ──────────────────┐
│ │
└── Some key │
│ │
┌───────┴───────┐ │
▼ ▼ │
symlink exists symlink missing │
│ │ │
▼ ▼ │
restore from build normally │
cache store + create symlink │
│ │ │
└───────┬───────┘ │
▼ │
install to target_dir ◄─────────────┘
Multiple dune processes may cache the same package simultaneously:
- Build to temporary directory
- Store in content-addressed cache (atomic via rename)
- Create symlink (atomic, idempotent)
If two processes race, both build (wasted work, but correct). First to finish creates symlink; second sees it exists and skips.
dune cache trim # Remove least-recently-used entries
dune cache trim --size 10G # Keep only 10GB
dune cache clear # Remove everythingDev tool sources without checksums (git pins, local packages) fall back to build_id:
ocamlformat.0.26.2-<bid8> # Fallback
Reduces sharing but maintains correctness.
Cache keys don't include platform. This assumes:
- Compiler-independent tools produce identical binaries (pure OCaml)
- Compiler-dependent tools include platform via deps' build_ids
If needed, add platform suffix: ocamlformat.0.26.2-<checksum8>-macos-arm64