Skip to content

Commit f182c33

Browse files
committed
fix: Prevent lockfile overwrite loops on metadata fetch
1 parent f76d2ef commit f182c33

File tree

1 file changed

+45
-6
lines changed

1 file changed

+45
-6
lines changed

crates/project-model/src/cargo_workspace.rs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use base_db::Env;
88
use cargo_metadata::{CargoOpt, MetadataCommand};
99
use la_arena::{Arena, Idx};
1010
use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf};
11-
use rustc_hash::{FxHashMap, FxHashSet};
11+
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
1212
use serde_derive::Deserialize;
1313
use serde_json::from_value;
1414
use span::Edition;
@@ -552,6 +552,7 @@ impl CargoWorkspace {
552552

553553
pub(crate) struct FetchMetadata {
554554
command: cargo_metadata::MetadataCommand,
555+
manifest_path: ManifestPath,
555556
lockfile_path: Option<Utf8PathBuf>,
556557
kind: &'static str,
557558
no_deps: bool,
@@ -655,7 +656,15 @@ impl FetchMetadata {
655656
}
656657
.with_context(|| format!("Failed to run `{cargo_command:?}`"));
657658

658-
Self { command, lockfile_path, kind: config.kind, no_deps, no_deps_result, other_options }
659+
Self {
660+
manifest_path: cargo_toml.clone(),
661+
command,
662+
lockfile_path,
663+
kind: config.kind,
664+
no_deps,
665+
no_deps_result,
666+
other_options,
667+
}
659668
}
660669

661670
pub(crate) fn no_deps_metadata(&self) -> Option<&cargo_metadata::Metadata> {
@@ -672,8 +681,15 @@ impl FetchMetadata {
672681
locked: bool,
673682
progress: &dyn Fn(String),
674683
) -> anyhow::Result<(cargo_metadata::Metadata, Option<anyhow::Error>)> {
675-
let Self { mut command, lockfile_path, kind, no_deps, no_deps_result, mut other_options } =
676-
self;
684+
let Self {
685+
manifest_path,
686+
mut command,
687+
lockfile_path,
688+
kind,
689+
no_deps,
690+
no_deps_result,
691+
mut other_options,
692+
} = self;
677693

678694
if no_deps {
679695
return no_deps_result.map(|m| (m, None));
@@ -682,8 +698,31 @@ impl FetchMetadata {
682698
let mut using_lockfile_copy = false;
683699
// The manifest is a rust file, so this means its a script manifest
684700
if let Some(lockfile) = lockfile_path {
685-
let target_lockfile =
686-
target_dir.join("rust-analyzer").join("metadata").join(kind).join("Cargo.lock");
701+
// When multiple workspaces share the same target dir, they might overwrite into a
702+
// single lockfile path.
703+
// See https://github.com/rust-lang/rust-analyzer/issues/20189#issuecomment-3073520255
704+
let manifest_path_hash = std::hash::BuildHasher::hash_one(
705+
&std::hash::BuildHasherDefault::<FxHasher>::default(),
706+
manifest_path,
707+
);
708+
let disambiguator = format!(
709+
"{}_{manifest_path_hash}",
710+
no_deps_result
711+
.as_ref()
712+
.ok()
713+
.and_then(|m| m.workspace_root.components().next_back())
714+
.map_or("", |c| c.as_str())
715+
);
716+
717+
// https://github.com/rust-lang/rust-analyzer/issues/20189
718+
// `**/Cargo.lock` files are watched by VFS and might trigger infinite loops of metadata fetch.
719+
// We use different filename here to prevent VFS events.
720+
let target_lockfile = target_dir
721+
.join("rust-analyzer")
722+
.join("metadata")
723+
.join(disambiguator)
724+
.join(kind)
725+
.join("Cargo_lock");
687726
match std::fs::copy(&lockfile, &target_lockfile) {
688727
Ok(_) => {
689728
using_lockfile_copy = true;

0 commit comments

Comments
 (0)