Skip to content

Commit 684ec28

Browse files
mmastracclaude
andcommitted
fix: handle directories in dep-info source file hashing
When proc_macro::tracked::path() registers a directory as a dependency, rustc's dep-info output includes the directory path. sccache previously crashed with "Is a directory" when trying to hash these paths. Now recursively hashes all files within the directory (sorted for determinism), using relative paths as delimiters. This correctly captures directory dependencies so cache invalidation works when any file in the tracked directory changes. Fixes: mozilla#2653 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 226e9df commit 684ec28

File tree

1 file changed

+45
-3
lines changed

1 file changed

+45
-3
lines changed

src/util.rs

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,55 @@ impl Digest {
9999
/// the actual hash computation on a background thread in `pool`.
100100
pub async fn reader(path: PathBuf, pool: &tokio::runtime::Handle) -> Result<String> {
101101
pool.spawn_blocking(move || {
102-
let reader = File::open(&path)
103-
.with_context(|| format!("Failed to open file for hashing: {:?}", path))?;
104-
Digest::reader_sync(reader)
102+
if path.is_dir() {
103+
// For directories (e.g., from proc_macro::tracked::path()),
104+
// recursively hash all file contents in sorted order.
105+
let mut entries = Vec::new();
106+
Self::collect_dir_entries(&path, &mut entries)?;
107+
entries.sort();
108+
let mut digest = Digest::new();
109+
for entry in &entries {
110+
// Hash the relative path as a delimiter
111+
let rel = entry.strip_prefix(&path).unwrap_or(entry);
112+
digest.update(rel.to_string_lossy().as_bytes());
113+
digest.update(b"\0");
114+
let mut file = File::open(entry)
115+
.with_context(|| format!("Failed to open file for hashing: {:?}", entry))?;
116+
let mut buf = vec![0u8; HASH_BUFFER_SIZE];
117+
loop {
118+
let n = file.read(&mut buf)?;
119+
if n == 0 {
120+
break;
121+
}
122+
digest.update(&buf[..n]);
123+
}
124+
}
125+
Ok(digest.finish())
126+
} else {
127+
let reader = File::open(&path)
128+
.with_context(|| format!("Failed to open file for hashing: {:?}", path))?;
129+
Digest::reader_sync(reader)
130+
}
105131
})
106132
.await?
107133
}
108134

135+
/// Recursively collect all file paths within a directory.
136+
fn collect_dir_entries(dir: &Path, entries: &mut Vec<PathBuf>) -> Result<()> {
137+
for entry in std::fs::read_dir(dir)
138+
.with_context(|| format!("Failed to read directory for hashing: {:?}", dir))?
139+
{
140+
let entry = entry?;
141+
let path = entry.path();
142+
if path.is_dir() {
143+
Self::collect_dir_entries(&path, entries)?;
144+
} else {
145+
entries.push(path);
146+
}
147+
}
148+
Ok(())
149+
}
150+
109151
pub fn update(&mut self, bytes: &[u8]) {
110152
self.inner.update(bytes);
111153
}

0 commit comments

Comments
 (0)