diff --git a/crates/fbuild-core/src/path.rs b/crates/fbuild-core/src/path.rs index b1711c26..02141dcc 100644 --- a/crates/fbuild-core/src/path.rs +++ b/crates/fbuild-core/src/path.rs @@ -407,12 +407,52 @@ pub fn normalize_flags_for_compile_cwd(flags: &[String], cwd: &Path) -> Vec &'static std::sync::RwLock> { + static MEMO: std::sync::OnceLock< + std::sync::RwLock>, + > = std::sync::OnceLock::new(); + MEMO.get_or_init(|| std::sync::RwLock::new(std::collections::HashMap::new())) +} + /// Canonicalize an existing path (stripping the Windows `\\?\` prefix), /// falling back to canonicalizing the parent + rejoining the file name when /// the full path does not yet exist. Returns `None` if neither resolves. fn canonicalize_lexical(path: &Path) -> Option { + if let Some(hit) = canonicalize_memo() + .read() + .unwrap_or_else(|e| e.into_inner()) + .get(path) + { + return Some(hit.clone()); + } if let Ok(canonical) = path.canonicalize() { - return Some(strip_unc_prefix(&canonical)); + let resolved = strip_unc_prefix(&canonical); + let mut memo = canonicalize_memo() + .write() + .unwrap_or_else(|e| e.into_inner()); + if memo.len() >= CANONICALIZE_MEMO_CAPACITY { + memo.clear(); + } + memo.insert(path.to_path_buf(), resolved.clone()); + return Some(resolved); } let parent = path.parent()?.canonicalize().ok()?; let joined = match path.file_name() { @@ -834,6 +874,33 @@ mod tests { assert_eq!(arg, "src/sketch/main.cpp"); } + #[test] + fn canonicalize_lexical_memoizes_resolved_paths() { + let tmp = tempfile::TempDir::new().unwrap(); + let dir = tmp.path().join("include"); + std::fs::create_dir_all(&dir).unwrap(); + let resolved = dir.canonicalize().unwrap(); + assert_eq!(canonicalize_lexical(&dir), Some(resolved.clone())); + // A second call is served from the memo rather than a fresh realpath. + assert_eq!(canonicalize_lexical(&dir), Some(resolved)); + } + + #[test] + fn canonicalize_lexical_does_not_memoize_the_missing_parent_fallback() { + let tmp = tempfile::TempDir::new().unwrap(); + let missing = tmp.path().join("later"); + // First call takes the parent-plus-filename fallback. + let before = canonicalize_lexical(&missing).unwrap(); + assert!(before.ends_with("later")); + std::fs::create_dir_all(&missing).unwrap(); + // Once the directory exists the real path must win, so the fallback + // result cannot have been memoized. + assert_eq!( + canonicalize_lexical(&missing), + Some(missing.canonicalize().unwrap()) + ); + } + #[test] fn path_arg_for_compile_cwd_returns_dot_for_workspace_root() { let tmp = tempfile::TempDir::new().unwrap();