crepuscularity_core/cache.rs
1//! Turbopack-style content-addressed output cache for the `.crepus` driver.
2//!
3//! [`DriverCache`] stores a SHA-256 digest of each previously written output
4//! under `.crepus-cache/`. Before the driver writes a generated file it calls
5//! [`DriverCache::is_up_to_date`]; if the new output would be byte-for-byte
6//! identical to the last write it skips the write entirely. This prevents
7//! `rustc` from seeing a bumped mtime on generated `.rs` files and triggering
8//! an unnecessary incremental rebuild.
9//!
10//! The cache is purely additive and safe to delete at any time — a cold cache
11//! just means one extra write per entry on the next run.
12
13#[cfg(not(target_arch = "wasm32"))]
14use std::path::{Path, PathBuf};
15
16#[cfg(not(target_arch = "wasm32"))]
17use sha2::{Digest, Sha256};
18
19#[cfg(not(target_arch = "wasm32"))]
20use crate::analysis::Fingerprint;
21#[cfg(not(target_arch = "wasm32"))]
22use crate::util::bytes_to_hex;
23
24/// An on-disk content-addressed cache for driver pipeline outputs.
25///
26/// Not available on `wasm32` targets (no filesystem).
27#[cfg(not(target_arch = "wasm32"))]
28pub struct DriverCache {
29 cache_dir: PathBuf,
30}
31
32#[cfg(not(target_arch = "wasm32"))]
33impl DriverCache {
34 /// Open (or create) a cache rooted at `project_root/.crepus-cache/`.
35 pub fn open(project_root: &Path) -> Self {
36 let cache_dir = project_root.join(".crepus-cache");
37 std::fs::create_dir_all(&cache_dir).ok();
38 Self { cache_dir }
39 }
40
41 /// Returns `true` if `output` is byte-for-byte identical to the last
42 /// recorded output for `fp`. Returns `false` on any I/O error or cache miss.
43 pub fn is_up_to_date(&self, fp: &Fingerprint, output: &str) -> bool {
44 let entry_path = self.entry_path(fp);
45 match std::fs::read_to_string(&entry_path) {
46 Ok(stored) => stored == self.output_hash(output),
47 Err(_) => false,
48 }
49 }
50
51 /// Record the output hash for `fp` so future calls to [`Self::is_up_to_date`]
52 /// can detect unchanged output.
53 ///
54 /// Call this **after** successfully writing the actual output file.
55 pub fn record(&self, fp: &Fingerprint, output: &str) {
56 let entry_path = self.entry_path(fp);
57 let _ = std::fs::write(&entry_path, self.output_hash(output));
58 }
59
60 fn entry_path(&self, fp: &Fingerprint) -> PathBuf {
61 self.cache_dir.join(fp.cache_key())
62 }
63
64 fn output_hash(&self, output: &str) -> String {
65 let mut h = Sha256::new();
66 h.update(output.as_bytes());
67 bytes_to_hex(h.finalize().as_ref())
68 }
69}