Skip to content

Commit 1fa6d5f

Browse files
committed
Handle dev-dependency cycles
1 parent e88a0af commit 1fa6d5f

File tree

3 files changed

+615
-38
lines changed

3 files changed

+615
-38
lines changed

crates/base-db/src/input.rs

+11
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,11 @@ impl CrateGraph {
386386
self.arena.alloc(data)
387387
}
388388

389+
pub fn duplicate(&mut self, id: CrateId) -> CrateId {
390+
let data = self[id].clone();
391+
self.arena.alloc(data)
392+
}
393+
389394
pub fn add_dep(
390395
&mut self,
391396
from: CrateId,
@@ -572,6 +577,12 @@ impl ops::Index<CrateId> for CrateGraph {
572577
}
573578
}
574579

580+
impl ops::IndexMut<CrateId> for CrateGraph {
581+
fn index_mut(&mut self, crate_id: CrateId) -> &mut CrateData {
582+
&mut self.arena[crate_id]
583+
}
584+
}
585+
575586
impl CrateData {
576587
fn add_dep(&mut self, dep: Dependency) {
577588
self.dependencies.push(dep)

crates/project-model/src/workspace.rs

+110-33
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
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

712
use anyhow::{format_err, Context, Result};
813
use base_db::{
@@ -858,32 +863,6 @@ fn cargo_to_crate_graph(
858863
for pkg in cargo.packages() {
859864
has_private |= cargo[pkg].metadata.rustc_private;
860865

861-
let cfg_options = {
862-
let mut cfg_options = cfg_options.clone();
863-
864-
// Add test cfg for local crates
865-
if cargo[pkg].is_local {
866-
cfg_options.insert_atom("test".into());
867-
}
868-
869-
let overrides = match override_cfg {
870-
CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff),
871-
CfgOverrides::Selective(cfg_overrides) => cfg_overrides.get(&cargo[pkg].name),
872-
};
873-
874-
if let Some(overrides) = overrides {
875-
// FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
876-
// in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
877-
// working on rust-lang/rust as that's the only time it appears outside sysroot).
878-
//
879-
// A more ideal solution might be to reanalyze crates based on where the cursor is and
880-
// figure out the set of cfgs that would have to apply to make it active.
881-
882-
cfg_options.apply_diff(overrides.clone());
883-
};
884-
cfg_options
885-
};
886-
887866
let mut lib_tgt = None;
888867
for &tgt in cargo[pkg].targets.iter() {
889868
if cargo[tgt].kind != TargetKind::Lib && !cargo[pkg].is_member {
@@ -894,7 +873,7 @@ fn cargo_to_crate_graph(
894873
// https://github.com/rust-lang/rust-analyzer/issues/11300
895874
continue;
896875
}
897-
let Some(file_id) = load(&cargo[tgt].root) else { continue };
876+
let Some(file_id) = load(&cargo[tgt].root) else { continue };
898877

899878
let crate_id = add_target_crate_root(
900879
crate_graph,
@@ -922,15 +901,19 @@ fn cargo_to_crate_graph(
922901
pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, cargo[tgt].kind));
923902
}
924903

904+
let Some(targets) = pkg_crates.get(&pkg) else { continue };
925905
// Set deps to the core, std and to the lib target of the current package
926-
for &(from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
906+
for &(from, kind) in targets {
927907
// Add sysroot deps first so that a lib target named `core` etc. can overwrite them.
928908
public_deps.add_to_crate_graph(crate_graph, from);
929909

930910
// Add dep edge of all targets to the package's lib target
931911
if let Some((to, name)) = lib_tgt.clone() {
932-
if to != from && kind != TargetKind::BuildScript {
933-
// (build script can not depend on its library target)
912+
if to != from {
913+
if kind == TargetKind::BuildScript {
914+
// build script can not depend on its library target
915+
continue;
916+
}
934917

935918
// For root projects with dashes in their name,
936919
// cargo metadata does not do any normalization,
@@ -942,6 +925,40 @@ fn cargo_to_crate_graph(
942925
}
943926
}
944927

928+
// Map from crate id to it's dev-dependency clone id
929+
let mut test_dupes = FxHashMap::default();
930+
let mut work = vec![];
931+
932+
// Get all dependencies of the workspace members that are used as dev-dependencies
933+
for pkg in cargo.packages() {
934+
for dep in &cargo[pkg].dependencies {
935+
if dep.kind == DepKind::Dev {
936+
work.push(dep.pkg);
937+
}
938+
}
939+
}
940+
while let Some(pkg) = work.pop() {
941+
let Some(&to) = pkg_to_lib_crate.get(&pkg) else { continue };
942+
match test_dupes.entry(to) {
943+
Entry::Occupied(_) => continue,
944+
Entry::Vacant(v) => {
945+
for dep in &cargo[pkg].dependencies {
946+
if dep.kind == DepKind::Normal {
947+
work.push(dep.pkg);
948+
}
949+
}
950+
v.insert({
951+
let duped = crate_graph.duplicate(to);
952+
if let Some(proc_macro) = proc_macros.get(&to).cloned() {
953+
proc_macros.insert(duped, proc_macro);
954+
}
955+
crate_graph[duped].cfg_options.insert_atom("test".into());
956+
duped
957+
});
958+
}
959+
}
960+
}
961+
945962
// Now add a dep edge from all targets of upstream to the lib
946963
// target of downstream.
947964
for pkg in cargo.packages() {
@@ -955,12 +972,65 @@ fn cargo_to_crate_graph(
955972
if (dep.kind == DepKind::Build) != (kind == TargetKind::BuildScript) {
956973
continue;
957974
}
975+
add_dep(
976+
crate_graph,
977+
from,
978+
name.clone(),
979+
if dep.kind == DepKind::Dev {
980+
// point to the test enabled duplicate for dev-dependencies
981+
test_dupes.get(&to).copied().unwrap()
982+
} else {
983+
to
984+
},
985+
);
986+
987+
if dep.kind == DepKind::Normal {
988+
// Also apply the dependency as a test enabled dependency to the test duplicate
989+
if let Some(&dupe) = test_dupes.get(&from) {
990+
let to = test_dupes.get(&to).copied().unwrap_or_else(|| {
991+
panic!(
992+
"dependency of a dev dependency did not get duplicated! {:?} {:?}",
993+
crate_graph[to].display_name, crate_graph[from].display_name,
994+
)
995+
});
996+
add_dep(crate_graph, dupe, name.clone(), to);
997+
}
998+
}
999+
}
1000+
}
1001+
}
9581002

959-
add_dep(crate_graph, from, name.clone(), to)
1003+
for (&pkg, targets) in &pkg_crates {
1004+
for &(krate, _) in targets {
1005+
if test_dupes.get(&krate).is_some() {
1006+
continue;
1007+
}
1008+
let cfg_options = &mut crate_graph[krate].cfg_options;
1009+
1010+
// Add test cfg for local crates
1011+
if cargo[pkg].is_local {
1012+
cfg_options.insert_atom("test".into());
9601013
}
1014+
1015+
let overrides = match override_cfg {
1016+
CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff),
1017+
CfgOverrides::Selective(cfg_overrides) => cfg_overrides.get(&cargo[pkg].name),
1018+
};
1019+
1020+
if let Some(overrides) = overrides {
1021+
// FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
1022+
// in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
1023+
// working on rust-lang/rust as that's the only time it appears outside sysroot).
1024+
//
1025+
// A more ideal solution might be to reanalyze crates based on where the cursor is and
1026+
// figure out the set of cfgs that would have to apply to make it active.
1027+
1028+
cfg_options.apply_diff(overrides.clone());
1029+
};
9611030
}
9621031
}
9631032

1033+
// FIXME: Handle rustc private crates properly when used as dev-dependencies
9641034
if has_private {
9651035
// If the user provided a path to rustc sources, we add all the rustc_private crates
9661036
// and create dependencies on them for the crates which opt-in to that
@@ -1325,10 +1395,12 @@ fn sysroot_to_crate_graph(
13251395
(public_deps, libproc_macro)
13261396
}
13271397

1398+
#[track_caller]
13281399
fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
13291400
add_dep_inner(graph, from, Dependency::new(name, to))
13301401
}
13311402

1403+
#[track_caller]
13321404
fn add_dep_with_prelude(
13331405
graph: &mut CrateGraph,
13341406
from: CrateId,
@@ -1339,13 +1411,18 @@ fn add_dep_with_prelude(
13391411
add_dep_inner(graph, from, Dependency::with_prelude(name, to, prelude))
13401412
}
13411413

1414+
#[track_caller]
13421415
fn add_proc_macro_dep(crate_graph: &mut CrateGraph, from: CrateId, to: CrateId, prelude: bool) {
13431416
add_dep_with_prelude(crate_graph, from, CrateName::new("proc_macro").unwrap(), to, prelude);
13441417
}
13451418

1419+
#[track_caller]
13461420
fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) {
13471421
if let Err(err) = graph.add_dep(from, dep) {
1348-
tracing::error!("{}", err)
1422+
if cfg!(test) {
1423+
panic!("{}", err);
1424+
}
1425+
tracing::error!("{}", err);
13491426
}
13501427
}
13511428

0 commit comments

Comments
 (0)