cache-manager
Directory-based cache and artifact path management with crate-root discovery, grouped cache paths, and optional eviction on directory initialization.
This crate is intentionally tool-agnostic — it only manages cache/artifact directory layout and paths and does not assume or depend on any specific consumer tooling. Any tool or library that reads or writes files can use cache-manager to compute/manage project-scoped cache paths and apply eviction rules.
It has zero runtime dependencies (standard library only for library consumers).
It is suitable for:
- Artifact storage (build outputs, generated files, intermediate data, etc.).
- Monorepos or multi-crate workspaces that need centralized cache/artifact management via a shared root (for example with
CacheRoot::from_root(...)).
Tested on macOS, Linux, and Windows.
Usage
Basic terminology and examples showing common operations:
CacheRoot
The primary root type is CacheRoot, which represents a filesystem root
under which cache groups (CacheGroup) live.
CacheGroup
A CacheGroup represents a subdirectory under a CacheRoot and manages
cache entries stored in that directory.
Discovering cache paths
Discover a cache path for the current crate/workspace and resolve an entry path.
Note:
discover_cache_pathonly computes a filesystem path — it does not create directories or files.
Behavior:
- Searches upward from the current working directory for a
Cargo.tomland uses that crate root when found; otherwise it falls back to the current working directory. - The discovered root is canonicalized when possible to avoid surprising differences between logically-equal paths.
- If the
relative_pathargument is absolute, it is returned unchanged.
use CacheRoot;
use Path;
// Compute a path like <crate-root>/.cache/tool/data.bin without creating it.
let cache_path = discover_cache_path;
println!;
// Expected relative location under the discovered crate root:
assert!;
// The call only computes the path; it does not create files or directories.
assert!;
// If you already have an absolute entry path, it's returned unchanged:
let absolute = from;
let kept = discover_cache_path;
assert_eq!;
Filesystem effects
- Pure (no I/O):
CacheRoot::discover,CacheRoot::discover_cache_path,CacheRoot::cache_path,CacheRoot::group,CacheGroup::entry_path,CacheGroup::subgroup - Create dirs:
CacheRoot::ensure_group,CacheGroup::ensure_dir - Create dirs + optional eviction:
CacheRoot::ensure_group_with_policy,CacheGroup::ensure_dir_with_policy - Create file (creates parents as needed):
CacheGroup::touch
Note: eviction only runs when you pass a policy to the
*_with_policymethods.
Eviction Policy
Use EvictPolicy with:
CacheGroup::ensure_dir_with_policy(...)CacheRoot::ensure_group_with_policy(...)CacheGroup::eviction_report(...)to preview which files would be evicted.
Policy fields:
max_age: remove files older than or equal to the age threshold.max_files: keep at most N files.max_bytes: keep total file bytes at or below the threshold.
Eviction order is always:
max_agemax_filesmax_bytes
For max_files and max_bytes, files are evicted oldest-first by modified time (ascending), then by path for deterministic tie-breaking.
eviction_report(...) and ensure_*_with_policy(...) use the same selection logic.
How max_bytes works
- Scans regular files recursively under the managed directory.
- Sums
metadata.len()across those files. - If total exceeds
max_bytes, removes files oldest-first until total is<= max_bytes. - Directories are not counted as bytes.
- Enforcement happens only during policy-aware
ensure_*_with_policycalls (not continuously in the background).
More examples
Create a CacheRoot from an explicit path and apply an eviction policy to a group:
use ;
use Duration;
let root = from_root;
let group = root.group;
let policy = EvictPolicy ;
group.ensure_dir_with_policy.expect;
Preview which files would be removed without applying deletions:
use ;
let root = from_root;
let group = root.group;
let policy = EvictPolicy ;
let report = group.eviction_report.expect;
for p in report.marked_for_eviction
Create or update a cache entry (ensures parent directories exist):
use CacheRoot;
let root = from_root;
let group = root.group;
let entry = group.touch.expect;
println!;
Per-subdirectory policies
Different subdirectories under the same CacheRoot can use independent policies; call ensure_dir_with_policy on each CacheGroup separately to apply per-group rules.
Get the root path
To obtain the underlying filesystem path for a CacheRoot, use path():
use CacheRoot;
let root = from_root;
let root_path = root.path;
println!;
Also obtain a CacheGroup path and resolve an entry path under that group:
use CacheRoot;
let root = from_root;
let group = root.group;
let group_path = group.path;
println!;
let entry_path = group.entry_path;
println!;
License
cache-manager is primarily distributed under the terms of both the MIT license and the Apache License (Version 2.0).
See LICENSE-APACHE and LICENSE-MIT for details.