Skip to content

Commit 20f2c76

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

File tree

2 files changed

+52
-6
lines changed

2 files changed

+52
-6
lines changed

crates/project-model/src/cargo_workspace.rs

Lines changed: 39 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,25 @@ 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+
manifest_path.components().nth_back(1).map_or("", |c| c.as_str())
711+
);
712+
713+
let target_lockfile = target_dir
714+
.join("rust-analyzer")
715+
.join("metadata")
716+
.join("lockfile_copies")
717+
.join(disambiguator)
718+
.join(kind)
719+
.join("Cargo.lock");
687720
match std::fs::copy(&lockfile, &target_lockfile) {
688721
Ok(_) => {
689722
using_lockfile_copy = true;

crates/rust-analyzer/src/reload.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,19 @@ pub(crate) fn should_refresh_for_change(
945945
None => return false,
946946
};
947947

948+
// We ignore `rust-analyzer/metadata/copied_lockfiles/**/Cargo.lock` files
949+
// because they're watched by the VFS, and these copied lockfiles could
950+
// otherwise trigger infinite metadata fetch loops.
951+
// See: https://github.com/rust-lang/rust-analyzer/issues/20189
952+
if file_name == "Cargo.lock"
953+
&& path.components().tuple_windows().any(|(c1, c2, c3)| {
954+
(c1.as_str(), c2.as_str(), c3.as_str())
955+
== ("rust-analyzer", "metadata", "lockfile_copies")
956+
})
957+
{
958+
return false;
959+
}
960+
948961
if let "Cargo.toml" | "Cargo.lock" = file_name {
949962
return true;
950963
}

0 commit comments

Comments
 (0)