@@ -8,7 +8,7 @@ use base_db::Env;
8
8
use cargo_metadata:: { CargoOpt , MetadataCommand } ;
9
9
use la_arena:: { Arena , Idx } ;
10
10
use paths:: { AbsPath , AbsPathBuf , Utf8Path , Utf8PathBuf } ;
11
- use rustc_hash:: { FxHashMap , FxHashSet } ;
11
+ use rustc_hash:: { FxHashMap , FxHashSet , FxHasher } ;
12
12
use serde_derive:: Deserialize ;
13
13
use serde_json:: from_value;
14
14
use span:: Edition ;
@@ -552,6 +552,7 @@ impl CargoWorkspace {
552
552
553
553
pub ( crate ) struct FetchMetadata {
554
554
command : cargo_metadata:: MetadataCommand ,
555
+ manifest_path : ManifestPath ,
555
556
lockfile_path : Option < Utf8PathBuf > ,
556
557
kind : & ' static str ,
557
558
no_deps : bool ,
@@ -655,7 +656,15 @@ impl FetchMetadata {
655
656
}
656
657
. with_context ( || format ! ( "Failed to run `{cargo_command:?}`" ) ) ;
657
658
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
+ }
659
668
}
660
669
661
670
pub ( crate ) fn no_deps_metadata ( & self ) -> Option < & cargo_metadata:: Metadata > {
@@ -672,8 +681,15 @@ impl FetchMetadata {
672
681
locked : bool ,
673
682
progress : & dyn Fn ( String ) ,
674
683
) -> 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 ;
677
693
678
694
if no_deps {
679
695
return no_deps_result. map ( |m| ( m, None ) ) ;
@@ -682,8 +698,31 @@ impl FetchMetadata {
682
698
let mut using_lockfile_copy = false ;
683
699
// The manifest is a rust file, so this means its a script manifest
684
700
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" ) ;
687
726
match std:: fs:: copy ( & lockfile, & target_lockfile) {
688
727
Ok ( _) => {
689
728
using_lockfile_copy = true ;
0 commit comments