diff --git a/Cargo.lock b/Cargo.lock index a699463ca491..c19e8471647f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1679,6 +1679,7 @@ dependencies = [ "serde_json", "span", "stdx", + "temp-dir", "toolchain", "tracing", "triomphe", @@ -2284,6 +2285,12 @@ dependencies = [ "tt", ] +[[package]] +name = "temp-dir" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83176759e9416cf81ee66cb6508dbfe9c96f20b8b56265a39917551c23c70964" + [[package]] name = "tenthash" version = "1.1.0" @@ -3103,6 +3110,7 @@ dependencies = [ "proc-macro2", "quote", "stdx", + "time", "ungrammar", "write-json", "xflags", diff --git a/Cargo.toml b/Cargo.toml index 700c116ec182..87202e86d3f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -156,6 +156,7 @@ smallvec = { version = "1.15.1", features = [ "const_generics", ] } smol_str = "0.3.2" +temp-dir = "0.1.16" text-size = "1.1.1" tracing = "0.1.41" tracing-tree = "0.4.0" diff --git a/crates/project-model/Cargo.toml b/crates/project-model/Cargo.toml index 27fe9f79bbc5..0dbb309a62a6 100644 --- a/crates/project-model/Cargo.toml +++ b/crates/project-model/Cargo.toml @@ -20,6 +20,7 @@ semver.workspace = true serde_json.workspace = true serde.workspace = true serde_derive.workspace = true +temp-dir.workspace = true tracing.workspace = true triomphe.workspace = true la-arena.workspace = true diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs index daadcd9d79a9..25abb19d9d55 100644 --- a/crates/project-model/src/cargo_workspace.rs +++ b/crates/project-model/src/cargo_workspace.rs @@ -552,6 +552,7 @@ impl CargoWorkspace { pub(crate) struct FetchMetadata { command: cargo_metadata::MetadataCommand, + manifest_path: ManifestPath, lockfile_path: Option, kind: &'static str, no_deps: bool, @@ -655,7 +656,15 @@ impl FetchMetadata { } .with_context(|| format!("Failed to run `{cargo_command:?}`")); - Self { command, lockfile_path, kind: config.kind, no_deps, no_deps_result, other_options } + Self { + manifest_path: cargo_toml.clone(), + command, + lockfile_path, + kind: config.kind, + no_deps, + no_deps_result, + other_options, + } } pub(crate) fn no_deps_metadata(&self) -> Option<&cargo_metadata::Metadata> { @@ -672,18 +681,47 @@ impl FetchMetadata { locked: bool, progress: &dyn Fn(String), ) -> anyhow::Result<(cargo_metadata::Metadata, Option)> { - let Self { mut command, lockfile_path, kind, no_deps, no_deps_result, mut other_options } = - self; + let Self { + mut command, + manifest_path, + lockfile_path, + kind, + no_deps, + no_deps_result, + mut other_options, + } = self; if no_deps { return no_deps_result.map(|m| (m, None)); } let mut using_lockfile_copy = false; + let mut _temp_dir_guard = None; // The manifest is a rust file, so this means its a script manifest if let Some(lockfile) = lockfile_path { - let target_lockfile = - target_dir.join("rust-analyzer").join("metadata").join(kind).join("Cargo.lock"); + _temp_dir_guard = temp_dir::TempDir::with_prefix("rust-analyzer").ok(); + let target_lockfile = _temp_dir_guard + .and_then(|tmp| tmp.path().join("Cargo.lock").try_into().ok()) + .unwrap_or_else(|| { + // When multiple workspaces share the same target dir, they might overwrite into a + // single lockfile path. + // See https://github.com/rust-lang/rust-analyzer/issues/20189#issuecomment-3073520255 + let manifest_path_hash = std::hash::BuildHasher::hash_one( + &std::hash::BuildHasherDefault::::default(), + &manifest_path, + ); + let disambiguator = format!( + "{}_{manifest_path_hash}", + manifest_path.components().nth_back(1).map_or("", |c| c.as_str()) + ); + + target_dir + .join("rust-analyzer") + .join("metadata") + .join(kind) + .join(disambiguator) + .join("Cargo.lock") + }); match std::fs::copy(&lockfile, &target_lockfile) { Ok(_) => { using_lockfile_copy = true; diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index 677f29e3c60a..655da118bbd4 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -1905,7 +1905,8 @@ fn cargo_target_dir( meta.manifest_path(manifest); // `--no-deps` doesn't (over)write lockfiles as it doesn't do any package resolve. // So we can use it to get `target_directory` before copying lockfiles - let mut other_options = vec!["--no-deps".to_owned()]; + meta.no_deps(); + let mut other_options = vec![]; if manifest.is_rust_manifest() { meta.env("RUSTC_BOOTSTRAP", "1"); other_options.push("-Zscript".to_owned());