diff --git a/crates/ide-build/src/compile/mod.rs b/crates/ide-build/src/compile/mod.rs index fcfa45f07..91bbdbf35 100644 --- a/crates/ide-build/src/compile/mod.rs +++ b/crates/ide-build/src/compile/mod.rs @@ -821,13 +821,14 @@ fn launch_class_dirs(system: BuildSystem, module: &Path, test: bool) -> Vec ide_core::IdeR Ok(out) } +/// The form `normalize_linked_projects` saves `dir` in, also for a folder that no longer +/// exists, so a path the UI sends (`C:\…` on Windows) matches the saved entry. +pub fn linked_project_key(root: &Path, dir: &str) -> String { + match (dunce::canonicalize(root), dunce::canonicalize(root.join(dir))) { + (Ok(root), Ok(path)) => rel(&root, &path), + _ => dir.replace('\\', "/"), + } +} + /// Linked build identities without reading their scripts (also used by index hot paths). pub(crate) fn linked_dirs(root: &Path, settings: &ide_core::settings::GradleSettings) -> Vec { match &settings.linked_projects { @@ -675,6 +684,30 @@ pub(crate) fn has_war_plugin(script: &str) -> bool { #[cfg(test)] mod tests { + #[test] + fn a_path_from_the_ui_finds_the_saved_link() { + let temp = tempfile::tempdir().unwrap(); + let root = temp.path().join("workspace"); + let external = temp.path().join("external"); + for dir in [root.join("a"), external.clone()] { + std::fs::create_dir_all(&dir).unwrap(); + std::fs::write(dir.join("settings.gradle"), "").unwrap(); + } + let native = |p: &std::path::Path| p.to_string_lossy().into_owned(); + // Saved by linking; unlinking may send a native, absolute or non-canonical spelling. + for (dir, sent) in [ + ("a", native(&root.join("a"))), + ("a", "a".to_owned()), + ("a", native(&root.join("a").join("..").join("a"))), + ("../external", native(&external)), + ] { + let saved = super::normalize_linked_projects(&root, &[dir.into()]).unwrap(); + assert_eq!(super::linked_project_key(&root, &sent), saved[0], "{sent}"); + } + // A folder deleted since it was linked still matches its saved entry. + assert_eq!(super::linked_project_key(&root, r"gone\build"), "gone/build"); + } + #[test] fn explicit_linkage_preserves_default_discovery_and_can_unlink_every_build() { let temp = tempfile::tempdir().unwrap(); diff --git a/crates/ide-build/src/lib.rs b/crates/ide-build/src/lib.rs index e18682766..eb613c5cf 100644 --- a/crates/ide-build/src/lib.rs +++ b/crates/ide-build/src/lib.rs @@ -216,7 +216,7 @@ mod tests { }; assert_eq!( gradle_external_roots(&root, &temp.path().join("cache"), &settings), - [external.canonicalize().unwrap()] + [dunce::canonicalize(&external).unwrap()] ); settings.linked_projects = Some(vec![]); assert!(gradle_external_roots(&root, &temp.path().join("cache"), &settings).is_empty()); diff --git a/crates/ide-host/src/ops/build.rs b/crates/ide-host/src/ops/build.rs index 5d0fb052f..074cbcad5 100644 --- a/crates/ide-host/src/ops/build.rs +++ b/crates/ide-host/src/ops/build.rs @@ -137,7 +137,8 @@ fn linked_gradle_paths( } } } else { - linked.retain(|path| !paths.contains(path)); + let unlinked: Vec = paths.iter().map(|p| ide_build::gradle::linked_project_key(root, p)).collect(); + linked.retain(|path| !paths.contains(path) && !unlinked.contains(path)); } Ok(linked) }