Skip to content

Commit cb752ee

Browse files
committed
Handle dev-dependency cycles
1 parent 81c1147 commit cb752ee

File tree

3 files changed

+618
-38
lines changed

3 files changed

+618
-38
lines changed

crates/base-db/src/input.rs

+13
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,14 @@ impl CrateGraph {
384384
crate_id
385385
}
386386

387+
pub fn duplicate(&mut self, id: CrateId) -> CrateId {
388+
let crate_id = CrateId(self.arena.len() as u32);
389+
let data = self[id].clone();
390+
let prev = self.arena.insert(crate_id, data);
391+
assert!(prev.is_none());
392+
crate_id
393+
}
394+
387395
pub fn add_dep(
388396
&mut self,
389397
from: CrateId,
@@ -569,6 +577,11 @@ impl ops::Index<CrateId> for CrateGraph {
569577
&self.arena[&crate_id]
570578
}
571579
}
580+
impl ops::IndexMut<CrateId> for CrateGraph {
581+
fn index_mut(&mut self, crate_id: CrateId) -> &mut CrateData {
582+
self.arena.get_mut(&crate_id).unwrap()
583+
}
584+
}
572585

573586
impl CrateId {
574587
fn shift(self, amount: u32) -> CrateId {

crates/project-model/src/workspace.rs

+111-33
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
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+
panic::Location,
9+
process::Command,
10+
sync::Arc,
11+
};
612

713
use anyhow::{format_err, Context, Result};
814
use base_db::{
@@ -858,32 +864,6 @@ fn cargo_to_crate_graph(
858864
for pkg in cargo.packages() {
859865
has_private |= cargo[pkg].metadata.rustc_private;
860866

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-
887867
let mut lib_tgt = None;
888868
for &tgt in cargo[pkg].targets.iter() {
889869
if cargo[tgt].kind != TargetKind::Lib && !cargo[pkg].is_member {
@@ -894,7 +874,7 @@ fn cargo_to_crate_graph(
894874
// https://github.com/rust-lang/rust-analyzer/issues/11300
895875
continue;
896876
}
897-
let Some(file_id) = load(&cargo[tgt].root) else { continue };
877+
let Some(file_id) = load(&cargo[tgt].root) else { continue };
898878

899879
let crate_id = add_target_crate_root(
900880
crate_graph,
@@ -922,15 +902,19 @@ fn cargo_to_crate_graph(
922902
pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, cargo[tgt].kind));
923903
}
924904

905+
let Some(targets) = pkg_crates.get(&pkg) else { continue };
925906
// 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() {
907+
for &(from, kind) in targets {
927908
// Add sysroot deps first so that a lib target named `core` etc. can overwrite them.
928909
public_deps.add_to_crate_graph(crate_graph, from);
929910

930911
// Add dep edge of all targets to the package's lib target
931912
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)
913+
if to != from {
914+
if kind == TargetKind::BuildScript {
915+
// build script can not depend on its library target
916+
continue;
917+
}
934918

935919
// For root projects with dashes in their name,
936920
// cargo metadata does not do any normalization,
@@ -942,6 +926,40 @@ fn cargo_to_crate_graph(
942926
}
943927
}
944928

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

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

1034+
// FIXME: Handle rustc private crates properly when used as dev-dependencies
9641035
if has_private {
9651036
// If the user provided a path to rustc sources, we add all the rustc_private crates
9661037
// and create dependencies on them for the crates which opt-in to that
@@ -1325,10 +1396,12 @@ fn sysroot_to_crate_graph(
13251396
(public_deps, libproc_macro)
13261397
}
13271398

1399+
#[track_caller]
13281400
fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
13291401
add_dep_inner(graph, from, Dependency::new(name, to))
13301402
}
13311403

1404+
#[track_caller]
13321405
fn add_dep_with_prelude(
13331406
graph: &mut CrateGraph,
13341407
from: CrateId,
@@ -1339,13 +1412,18 @@ fn add_dep_with_prelude(
13391412
add_dep_inner(graph, from, Dependency::with_prelude(name, to, prelude))
13401413
}
13411414

1415+
#[track_caller]
13421416
fn add_proc_macro_dep(crate_graph: &mut CrateGraph, from: CrateId, to: CrateId, prelude: bool) {
13431417
add_dep_with_prelude(crate_graph, from, CrateName::new("proc_macro").unwrap(), to, prelude);
13441418
}
13451419

1420+
#[track_caller]
13461421
fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) {
13471422
if let Err(err) = graph.add_dep(from, dep) {
1348-
tracing::error!("{}", err)
1423+
if cfg!(test) {
1424+
panic!("{}", err);
1425+
}
1426+
tracing::error!("{}", err);
13491427
}
13501428
}
13511429

0 commit comments

Comments
 (0)