22//! metadata` or `rust-project.json`) into representation stored in the salsa
33//! database -- `CrateGraph`.
44
5- use std:: { collections:: VecDeque , fmt, fs, process:: Command , sync:: Arc } ;
5+ use std:: {
6+ collections:: { hash_map:: Entry , VecDeque } ,
7+ fmt, fs,
8+ process:: Command ,
9+ sync:: Arc ,
10+ } ;
611
712use anyhow:: { format_err, Context , Result } ;
813use base_db:: {
@@ -844,12 +849,12 @@ fn cargo_to_crate_graph(
844849 None => ( SysrootPublicDeps :: default ( ) , None ) ,
845850 } ;
846851
847- let cfg_options = {
852+ let cfg_options = forced_cfg . clone ( ) . unwrap_or_else ( || {
848853 let mut cfg_options = CfgOptions :: default ( ) ;
849854 cfg_options. extend ( rustc_cfg) ;
850855 cfg_options. insert_atom ( "debug_assertions" . into ( ) ) ;
851856 cfg_options
852- } ;
857+ } ) ;
853858
854859 // Mapping of a package to its library target
855860 let mut pkg_to_lib_crate = FxHashMap :: default ( ) ;
@@ -861,32 +866,6 @@ fn cargo_to_crate_graph(
861866 for pkg in cargo. packages ( ) {
862867 has_private |= cargo[ pkg] . metadata . rustc_private ;
863868
864- let cfg_options = forced_cfg. clone ( ) . unwrap_or_else ( || {
865- let mut cfg_options = cfg_options. clone ( ) ;
866-
867- // Add test cfg for local crates
868- if cargo[ pkg] . is_local {
869- cfg_options. insert_atom ( "test" . into ( ) ) ;
870- }
871-
872- let overrides = match override_cfg {
873- CfgOverrides :: Wildcard ( cfg_diff) => Some ( cfg_diff) ,
874- CfgOverrides :: Selective ( cfg_overrides) => cfg_overrides. get ( & cargo[ pkg] . name ) ,
875- } ;
876-
877- if let Some ( overrides) = overrides {
878- // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
879- // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
880- // working on rust-lang/rust as that's the only time it appears outside sysroot).
881- //
882- // A more ideal solution might be to reanalyze crates based on where the cursor is and
883- // figure out the set of cfgs that would have to apply to make it active.
884-
885- cfg_options. apply_diff ( overrides. clone ( ) ) ;
886- } ;
887- cfg_options
888- } ) ;
889-
890869 let mut lib_tgt = None ;
891870 for & tgt in cargo[ pkg] . targets . iter ( ) {
892871 if cargo[ tgt] . kind != TargetKind :: Lib && !cargo[ pkg] . is_member {
@@ -897,7 +876,7 @@ fn cargo_to_crate_graph(
897876 // https://github.com/rust-lang/rust-analyzer/issues/11300
898877 continue ;
899878 }
900- let Some ( file_id) = load ( & cargo[ tgt] . root ) else { continue } ;
879+ let Some ( file_id) = load ( & cargo[ tgt] . root ) else { continue } ;
901880
902881 let crate_id = add_target_crate_root (
903882 crate_graph,
@@ -925,15 +904,19 @@ fn cargo_to_crate_graph(
925904 pkg_crates. entry ( pkg) . or_insert_with ( Vec :: new) . push ( ( crate_id, cargo[ tgt] . kind ) ) ;
926905 }
927906
907+ let Some ( targets) = pkg_crates. get ( & pkg) else { continue } ;
928908 // Set deps to the core, std and to the lib target of the current package
929- for & ( from, kind) in pkg_crates . get ( & pkg ) . into_iter ( ) . flatten ( ) {
909+ for & ( from, kind) in targets {
930910 // Add sysroot deps first so that a lib target named `core` etc. can overwrite them.
931911 public_deps. add_to_crate_graph ( crate_graph, from) ;
932912
933913 // Add dep edge of all targets to the package's lib target
934914 if let Some ( ( to, name) ) = lib_tgt. clone ( ) {
935- if to != from && kind != TargetKind :: BuildScript {
936- // (build script can not depend on its library target)
915+ if to != from {
916+ if kind == TargetKind :: BuildScript {
917+ // build script can not depend on its library target
918+ continue ;
919+ }
937920
938921 // For root projects with dashes in their name,
939922 // cargo metadata does not do any normalization,
@@ -945,6 +928,43 @@ fn cargo_to_crate_graph(
945928 }
946929 }
947930
931+ // We now need to duplicate workspace members that are used as dev-dependencies to prevent
932+ // cycles from forming.
933+
934+ // Map from crate id to it's dev-dependency clone id
935+ let mut test_dupes = FxHashMap :: default ( ) ;
936+ let mut work = vec ! [ ] ;
937+
938+ // Get all dependencies of the workspace members that are used as dev-dependencies
939+ for pkg in cargo. packages ( ) {
940+ for dep in & cargo[ pkg] . dependencies {
941+ if dep. kind == DepKind :: Dev {
942+ work. push ( dep. pkg ) ;
943+ }
944+ }
945+ }
946+ while let Some ( pkg) = work. pop ( ) {
947+ let Some ( & to) = pkg_to_lib_crate. get ( & pkg) else { continue } ;
948+ match test_dupes. entry ( to) {
949+ Entry :: Occupied ( _) => continue ,
950+ Entry :: Vacant ( v) => {
951+ for dep in & cargo[ pkg] . dependencies {
952+ if dep. kind == DepKind :: Normal && cargo[ dep. pkg ] . is_member {
953+ work. push ( dep. pkg ) ;
954+ }
955+ }
956+ v. insert ( {
957+ let duped = crate_graph. duplicate ( to) ;
958+ if let Some ( proc_macro) = proc_macros. get ( & to) . cloned ( ) {
959+ proc_macros. insert ( duped, proc_macro) ;
960+ }
961+ crate_graph[ duped] . cfg_options . insert_atom ( "test" . into ( ) ) ;
962+ duped
963+ } ) ;
964+ }
965+ }
966+ }
967+
948968 // Now add a dep edge from all targets of upstream to the lib
949969 // target of downstream.
950970 for pkg in cargo. packages ( ) {
@@ -958,12 +978,66 @@ fn cargo_to_crate_graph(
958978 if ( dep. kind == DepKind :: Build ) != ( kind == TargetKind :: BuildScript ) {
959979 continue ;
960980 }
981+ add_dep (
982+ crate_graph,
983+ from,
984+ name. clone ( ) ,
985+ if dep. kind == DepKind :: Dev {
986+ // point to the test enabled duplicate for dev-dependencies
987+ test_dupes. get ( & to) . copied ( ) . unwrap_or ( to)
988+ } else {
989+ to
990+ } ,
991+ ) ;
961992
962- add_dep ( crate_graph, from, name. clone ( ) , to)
993+ if dep. kind == DepKind :: Normal && cargo[ dep. pkg ] . is_member {
994+ // Also apply the dependency as a test enabled dependency to the test duplicate
995+ if let Some ( & dupe) = test_dupes. get ( & from) {
996+ let to = test_dupes. get ( & to) . copied ( ) . unwrap_or_else ( || {
997+ panic ! (
998+ "dependency of a dev dependency did not get duplicated! {:?} {:?}" ,
999+ crate_graph[ to] . display_name, crate_graph[ from] . display_name,
1000+ )
1001+ } ) ;
1002+ add_dep ( crate_graph, dupe, name. clone ( ) , to) ;
1003+ }
1004+ }
1005+ }
1006+ }
1007+ }
1008+
1009+ for ( & pkg, targets) in & pkg_crates {
1010+ for & ( krate, _) in targets {
1011+ if test_dupes. get ( & krate) . is_some ( ) {
1012+ // if the crate got duped as a dev-dep the dupe already has test set
1013+ continue ;
1014+ }
1015+ let cfg_options = & mut crate_graph[ krate] . cfg_options ;
1016+
1017+ // Add test cfg for local crates
1018+ if cargo[ pkg] . is_local {
1019+ cfg_options. insert_atom ( "test" . into ( ) ) ;
9631020 }
1021+
1022+ let overrides = match override_cfg {
1023+ CfgOverrides :: Wildcard ( cfg_diff) => Some ( cfg_diff) ,
1024+ CfgOverrides :: Selective ( cfg_overrides) => cfg_overrides. get ( & cargo[ pkg] . name ) ,
1025+ } ;
1026+
1027+ if let Some ( overrides) = overrides {
1028+ // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
1029+ // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
1030+ // working on rust-lang/rust as that's the only time it appears outside sysroot).
1031+ //
1032+ // A more ideal solution might be to reanalyze crates based on where the cursor is and
1033+ // figure out the set of cfgs that would have to apply to make it active.
1034+
1035+ cfg_options. apply_diff ( overrides. clone ( ) ) ;
1036+ } ;
9641037 }
9651038 }
9661039
1040+ // FIXME: Handle rustc private crates properly when used as dev-dependencies
9671041 if has_private {
9681042 // If the user provided a path to rustc sources, we add all the rustc_private crates
9691043 // and create dependencies on them for the crates which opt-in to that
@@ -1087,7 +1161,9 @@ fn handle_rustc_crates(
10871161 continue ;
10881162 }
10891163 for dep in & rustc_workspace[ pkg] . dependencies {
1090- queue. push_back ( dep. pkg ) ;
1164+ if dep. kind == DepKind :: Normal {
1165+ queue. push_back ( dep. pkg ) ;
1166+ }
10911167 }
10921168
10931169 let mut cfg_options = cfg_options. clone ( ) ;
@@ -1397,10 +1473,12 @@ fn handle_hack_cargo_workspace(
13971473 . collect ( )
13981474}
13991475
1476+ #[ track_caller]
14001477fn add_dep ( graph : & mut CrateGraph , from : CrateId , name : CrateName , to : CrateId ) {
14011478 add_dep_inner ( graph, from, Dependency :: new ( name, to) )
14021479}
14031480
1481+ #[ track_caller]
14041482fn add_dep_with_prelude (
14051483 graph : & mut CrateGraph ,
14061484 from : CrateId ,
@@ -1411,13 +1489,18 @@ fn add_dep_with_prelude(
14111489 add_dep_inner ( graph, from, Dependency :: with_prelude ( name, to, prelude) )
14121490}
14131491
1492+ #[ track_caller]
14141493fn add_proc_macro_dep ( crate_graph : & mut CrateGraph , from : CrateId , to : CrateId , prelude : bool ) {
14151494 add_dep_with_prelude ( crate_graph, from, CrateName :: new ( "proc_macro" ) . unwrap ( ) , to, prelude) ;
14161495}
14171496
1497+ #[ track_caller]
14181498fn add_dep_inner ( graph : & mut CrateGraph , from : CrateId , dep : Dependency ) {
14191499 if let Err ( err) = graph. add_dep ( from, dep) {
1420- tracing:: error!( "{}" , err)
1500+ if cfg ! ( test) {
1501+ panic ! ( "{}" , err) ;
1502+ }
1503+ tracing:: error!( "{}" , err) ;
14211504 }
14221505}
14231506
0 commit comments