Skip to content

Commit 56de543

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

File tree

3 files changed

+750
-533
lines changed

3 files changed

+750
-533
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

+117-34
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,43 @@ fn cargo_to_crate_graph(
942925
}
943926
}
944927

928+
// We now need to duplicate workspace members that are used as dev-dependencies to prevent
929+
// cycles from forming.
930+
931+
// Map from crate id to it's dev-dependency clone id
932+
let mut test_dupes = FxHashMap::default();
933+
let mut work = vec![];
934+
935+
// Get all dependencies of the workspace members that are used as dev-dependencies
936+
for pkg in cargo.packages() {
937+
for dep in &cargo[pkg].dependencies {
938+
if dep.kind == DepKind::Dev {
939+
work.push(dep.pkg);
940+
}
941+
}
942+
}
943+
while let Some(pkg) = work.pop() {
944+
let Some(&to) = pkg_to_lib_crate.get(&pkg) else { continue };
945+
match test_dupes.entry(to) {
946+
Entry::Occupied(_) => continue,
947+
Entry::Vacant(v) => {
948+
for dep in &cargo[pkg].dependencies {
949+
if dep.kind == DepKind::Normal && cargo[dep.pkg].is_member {
950+
work.push(dep.pkg);
951+
}
952+
}
953+
v.insert({
954+
let duped = crate_graph.duplicate(to);
955+
if let Some(proc_macro) = proc_macros.get(&to).cloned() {
956+
proc_macros.insert(duped, proc_macro);
957+
}
958+
crate_graph[duped].cfg_options.insert_atom("test".into());
959+
duped
960+
});
961+
}
962+
}
963+
}
964+
945965
// Now add a dep edge from all targets of upstream to the lib
946966
// target of downstream.
947967
for pkg in cargo.packages() {
@@ -955,12 +975,66 @@ fn cargo_to_crate_graph(
955975
if (dep.kind == DepKind::Build) != (kind == TargetKind::BuildScript) {
956976
continue;
957977
}
978+
add_dep(
979+
crate_graph,
980+
from,
981+
name.clone(),
982+
if dep.kind == DepKind::Dev {
983+
// point to the test enabled duplicate for dev-dependencies
984+
test_dupes.get(&to).copied().unwrap_or(to)
985+
} else {
986+
to
987+
},
988+
);
958989

959-
add_dep(crate_graph, from, name.clone(), to)
990+
if dep.kind == DepKind::Normal && cargo[dep.pkg].is_member {
991+
// Also apply the dependency as a test enabled dependency to the test duplicate
992+
if let Some(&dupe) = test_dupes.get(&from) {
993+
let to = test_dupes.get(&to).copied().unwrap_or_else(|| {
994+
panic!(
995+
"dependency of a dev dependency did not get duplicated! {:?} {:?}",
996+
crate_graph[to].display_name, crate_graph[from].display_name,
997+
)
998+
});
999+
add_dep(crate_graph, dupe, name.clone(), to);
1000+
}
1001+
}
9601002
}
9611003
}
9621004
}
9631005

1006+
for (&pkg, targets) in &pkg_crates {
1007+
for &(krate, _) in targets {
1008+
if test_dupes.get(&krate).is_some() {
1009+
// if the crate got duped as a dev-dep the dupe already has test set
1010+
continue;
1011+
}
1012+
let cfg_options = &mut crate_graph[krate].cfg_options;
1013+
1014+
// Add test cfg for local crates
1015+
if cargo[pkg].is_local {
1016+
cfg_options.insert_atom("test".into());
1017+
}
1018+
1019+
let overrides = match override_cfg {
1020+
CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff),
1021+
CfgOverrides::Selective(cfg_overrides) => cfg_overrides.get(&cargo[pkg].name),
1022+
};
1023+
1024+
if let Some(overrides) = overrides {
1025+
// FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
1026+
// in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
1027+
// working on rust-lang/rust as that's the only time it appears outside sysroot).
1028+
//
1029+
// A more ideal solution might be to reanalyze crates based on where the cursor is and
1030+
// figure out the set of cfgs that would have to apply to make it active.
1031+
1032+
cfg_options.apply_diff(overrides.clone());
1033+
};
1034+
}
1035+
}
1036+
1037+
// FIXME: Handle rustc private crates properly when used as dev-dependencies
9641038
if has_private {
9651039
// If the user provided a path to rustc sources, we add all the rustc_private crates
9661040
// and create dependencies on them for the crates which opt-in to that
@@ -1084,7 +1158,9 @@ fn handle_rustc_crates(
10841158
continue;
10851159
}
10861160
for dep in &rustc_workspace[pkg].dependencies {
1087-
queue.push_back(dep.pkg);
1161+
if dep.kind == DepKind::Normal {
1162+
queue.push_back(dep.pkg);
1163+
}
10881164
}
10891165

10901166
let mut cfg_options = cfg_options.clone();
@@ -1325,10 +1401,12 @@ fn sysroot_to_crate_graph(
13251401
(public_deps, libproc_macro)
13261402
}
13271403

1404+
#[track_caller]
13281405
fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
13291406
add_dep_inner(graph, from, Dependency::new(name, to))
13301407
}
13311408

1409+
#[track_caller]
13321410
fn add_dep_with_prelude(
13331411
graph: &mut CrateGraph,
13341412
from: CrateId,
@@ -1339,13 +1417,18 @@ fn add_dep_with_prelude(
13391417
add_dep_inner(graph, from, Dependency::with_prelude(name, to, prelude))
13401418
}
13411419

1420+
#[track_caller]
13421421
fn add_proc_macro_dep(crate_graph: &mut CrateGraph, from: CrateId, to: CrateId, prelude: bool) {
13431422
add_dep_with_prelude(crate_graph, from, CrateName::new("proc_macro").unwrap(), to, prelude);
13441423
}
13451424

1425+
#[track_caller]
13461426
fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) {
13471427
if let Err(err) = graph.add_dep(from, dep) {
1348-
tracing::error!("{}", err)
1428+
if cfg!(test) {
1429+
panic!("{}", err);
1430+
}
1431+
tracing::error!("{}", err);
13491432
}
13501433
}
13511434

0 commit comments

Comments
 (0)