diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs index c4ada9c76539..6092eac3c96f 100644 --- a/crates/base-db/src/input.rs +++ b/crates/base-db/src/input.rs @@ -386,6 +386,11 @@ impl CrateGraph { self.arena.alloc(data) } + pub fn duplicate(&mut self, id: CrateId) -> CrateId { + let data = self[id].clone(); + self.arena.alloc(data) + } + pub fn add_dep( &mut self, from: CrateId, @@ -572,6 +577,12 @@ impl ops::Index for CrateGraph { } } +impl ops::IndexMut for CrateGraph { + fn index_mut(&mut self, crate_id: CrateId) -> &mut CrateData { + &mut self.arena[crate_id] + } +} + impl CrateData { fn add_dep(&mut self, dep: Dependency) { self.dependencies.push(dep) diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index c8e83a687e02..61e30f3a9e1e 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -2,7 +2,12 @@ //! metadata` or `rust-project.json`) into representation stored in the salsa //! database -- `CrateGraph`. -use std::{collections::VecDeque, fmt, fs, process::Command, sync::Arc}; +use std::{ + collections::{hash_map::Entry, VecDeque}, + fmt, fs, + process::Command, + sync::Arc, +}; use anyhow::{format_err, Context, Result}; use base_db::{ @@ -858,32 +863,6 @@ fn cargo_to_crate_graph( for pkg in cargo.packages() { has_private |= cargo[pkg].metadata.rustc_private; - let cfg_options = { - let mut cfg_options = cfg_options.clone(); - - // Add test cfg for local crates - if cargo[pkg].is_local { - cfg_options.insert_atom("test".into()); - } - - let overrides = match override_cfg { - CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff), - CfgOverrides::Selective(cfg_overrides) => cfg_overrides.get(&cargo[pkg].name), - }; - - if let Some(overrides) = overrides { - // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen - // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while - // working on rust-lang/rust as that's the only time it appears outside sysroot). - // - // A more ideal solution might be to reanalyze crates based on where the cursor is and - // figure out the set of cfgs that would have to apply to make it active. - - cfg_options.apply_diff(overrides.clone()); - }; - cfg_options - }; - let mut lib_tgt = None; for &tgt in cargo[pkg].targets.iter() { if cargo[tgt].kind != TargetKind::Lib && !cargo[pkg].is_member { @@ -894,7 +873,7 @@ fn cargo_to_crate_graph( // https://github.com/rust-lang/rust-analyzer/issues/11300 continue; } - let Some(file_id) = load(&cargo[tgt].root) else { continue }; + let Some(file_id) = load(&cargo[tgt].root) else { continue }; let crate_id = add_target_crate_root( crate_graph, @@ -922,15 +901,19 @@ fn cargo_to_crate_graph( pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, cargo[tgt].kind)); } + let Some(targets) = pkg_crates.get(&pkg) else { continue }; // Set deps to the core, std and to the lib target of the current package - for &(from, kind) in pkg_crates.get(&pkg).into_iter().flatten() { + for &(from, kind) in targets { // Add sysroot deps first so that a lib target named `core` etc. can overwrite them. public_deps.add_to_crate_graph(crate_graph, from); // Add dep edge of all targets to the package's lib target if let Some((to, name)) = lib_tgt.clone() { - if to != from && kind != TargetKind::BuildScript { - // (build script can not depend on its library target) + if to != from { + if kind == TargetKind::BuildScript { + // build script can not depend on its library target + continue; + } // For root projects with dashes in their name, // cargo metadata does not do any normalization, @@ -942,6 +925,40 @@ fn cargo_to_crate_graph( } } + // Map from crate id to it's dev-dependency clone id + let mut test_dupes = FxHashMap::default(); + let mut work = vec![]; + + // Get all dependencies of the workspace members that are used as dev-dependencies + for pkg in cargo.packages() { + for dep in &cargo[pkg].dependencies { + if dep.kind == DepKind::Dev { + work.push(dep.pkg); + } + } + } + while let Some(pkg) = work.pop() { + let Some(&to) = pkg_to_lib_crate.get(&pkg) else { continue }; + match test_dupes.entry(to) { + Entry::Occupied(_) => continue, + Entry::Vacant(v) => { + for dep in &cargo[pkg].dependencies { + if dep.kind == DepKind::Normal { + work.push(dep.pkg); + } + } + v.insert({ + let duped = crate_graph.duplicate(to); + if let Some(proc_macro) = proc_macros.get(&to).cloned() { + proc_macros.insert(duped, proc_macro); + } + crate_graph[duped].cfg_options.insert_atom("test".into()); + duped + }); + } + } + } + // Now add a dep edge from all targets of upstream to the lib // target of downstream. for pkg in cargo.packages() { @@ -955,12 +972,65 @@ fn cargo_to_crate_graph( if (dep.kind == DepKind::Build) != (kind == TargetKind::BuildScript) { continue; } + add_dep( + crate_graph, + from, + name.clone(), + if dep.kind == DepKind::Dev { + // point to the test enabled duplicate for dev-dependencies + test_dupes.get(&to).copied().unwrap() + } else { + to + }, + ); + + if dep.kind == DepKind::Normal { + // Also apply the dependency as a test enabled dependency to the test duplicate + if let Some(&dupe) = test_dupes.get(&from) { + let to = test_dupes.get(&to).copied().unwrap_or_else(|| { + panic!( + "dependency of a dev dependency did not get duplicated! {:?} {:?}", + crate_graph[to].display_name, crate_graph[from].display_name, + ) + }); + add_dep(crate_graph, dupe, name.clone(), to); + } + } + } + } + } - add_dep(crate_graph, from, name.clone(), to) + for (&pkg, targets) in &pkg_crates { + for &(krate, _) in targets { + if test_dupes.get(&krate).is_some() { + continue; + } + let cfg_options = &mut crate_graph[krate].cfg_options; + + // Add test cfg for local crates + if cargo[pkg].is_local { + cfg_options.insert_atom("test".into()); } + + let overrides = match override_cfg { + CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff), + CfgOverrides::Selective(cfg_overrides) => cfg_overrides.get(&cargo[pkg].name), + }; + + if let Some(overrides) = overrides { + // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen + // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while + // working on rust-lang/rust as that's the only time it appears outside sysroot). + // + // A more ideal solution might be to reanalyze crates based on where the cursor is and + // figure out the set of cfgs that would have to apply to make it active. + + cfg_options.apply_diff(overrides.clone()); + }; } } + // FIXME: Handle rustc private crates properly when used as dev-dependencies if has_private { // If the user provided a path to rustc sources, we add all the rustc_private crates // and create dependencies on them for the crates which opt-in to that @@ -1325,10 +1395,12 @@ fn sysroot_to_crate_graph( (public_deps, libproc_macro) } +#[track_caller] fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) { add_dep_inner(graph, from, Dependency::new(name, to)) } +#[track_caller] fn add_dep_with_prelude( graph: &mut CrateGraph, from: CrateId, @@ -1339,13 +1411,18 @@ fn add_dep_with_prelude( add_dep_inner(graph, from, Dependency::with_prelude(name, to, prelude)) } +#[track_caller] fn add_proc_macro_dep(crate_graph: &mut CrateGraph, from: CrateId, to: CrateId, prelude: bool) { add_dep_with_prelude(crate_graph, from, CrateName::new("proc_macro").unwrap(), to, prelude); } +#[track_caller] fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) { if let Err(err) = graph.add_dep(from, dep) { - tracing::error!("{}", err) + if cfg!(test) { + panic!("{}", err); + } + tracing::error!("{}", err); } } diff --git a/crates/project-model/test_data/cargo_dev_dependencies-crate-graph.txt b/crates/project-model/test_data/cargo_dev_dependencies-crate-graph.txt index 914c7db038c4..4b99c7dd8eb7 100644 --- a/crates/project-model/test_data/cargo_dev_dependencies-crate-graph.txt +++ b/crates/project-model/test_data/cargo_dev_dependencies-crate-graph.txt @@ -224,7 +224,6 @@ CrateGraph { cfg_options: CfgOptions( [ "debug_assertions", - "test", ], ), potential_cfg_options: None, @@ -259,7 +258,16 @@ CrateGraph { }, Dependency { crate_id: CrateId( - 5, + 11, + ), + name: CrateName( + "ra_playground", + ), + prelude: true, + }, + Dependency { + crate_id: CrateId( + 7, ), name: CrateName( "regex", @@ -300,7 +308,6 @@ CrateGraph { cfg_options: CfgOptions( [ "debug_assertions", - "test", ], ), potential_cfg_options: None, @@ -344,7 +351,7 @@ CrateGraph { }, Dependency { crate_id: CrateId( - 3, + 11, ), name: CrateName( "ra_playground", @@ -353,7 +360,7 @@ CrateGraph { }, Dependency { crate_id: CrateId( - 5, + 7, ), name: CrateName( "regex", @@ -580,5 +587,487 @@ CrateGraph { ), channel: None, }, + CrateId( + 7, + ): CrateData { + root_file_id: FileId( + 6, + ), + edition: Edition2018, + version: Some( + "1.7.3", + ), + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "regex", + ), + canonical_name: "regex", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "feature=aho-corasick", + "feature=default", + "feature=memchr", + "feature=perf", + "feature=perf-cache", + "feature=perf-dfa", + "feature=perf-inline", + "feature=perf-literal", + "feature=std", + "feature=unicode", + "feature=unicode-age", + "feature=unicode-bool", + "feature=unicode-case", + "feature=unicode-gencat", + "feature=unicode-perl", + "feature=unicode-script", + "feature=unicode-segment", + "test", + ], + ), + potential_cfg_options: Some( + CfgOptions( + [ + "debug_assertions", + "feature=aho-corasick", + "feature=default", + "feature=memchr", + "feature=pattern", + "feature=perf", + "feature=perf-cache", + "feature=perf-dfa", + "feature=perf-inline", + "feature=perf-literal", + "feature=std", + "feature=unicode", + "feature=unicode-age", + "feature=unicode-bool", + "feature=unicode-case", + "feature=unicode-gencat", + "feature=unicode-perl", + "feature=unicode-script", + "feature=unicode-segment", + "feature=unstable", + "feature=use_std", + ], + ), + ), + env: Env { + entries: { + "CARGO_PKG_LICENSE": "", + "CARGO_PKG_VERSION_MAJOR": "1", + "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-1.7.3", + "CARGO_PKG_VERSION": "1.7.3", + "CARGO_PKG_AUTHORS": "", + "CARGO_CRATE_NAME": "regex", + "CARGO_PKG_LICENSE_FILE": "", + "CARGO_PKG_HOMEPAGE": "", + "CARGO_PKG_DESCRIPTION": "", + "CARGO_PKG_NAME": "regex", + "CARGO_PKG_VERSION_PATCH": "3", + "CARGO": "cargo", + "CARGO_PKG_REPOSITORY": "", + "CARGO_PKG_VERSION_MINOR": "7", + "CARGO_PKG_VERSION_PRE": "", + }, + }, + dependencies: [ + Dependency { + crate_id: CrateId( + 10, + ), + name: CrateName( + "aho_corasick", + ), + prelude: true, + }, + Dependency { + crate_id: CrateId( + 9, + ), + name: CrateName( + "memchr", + ), + prelude: true, + }, + Dependency { + crate_id: CrateId( + 8, + ), + name: CrateName( + "regex_syntax", + ), + prelude: true, + }, + ], + origin: Library { + repo: Some( + "https://github.com/rust-lang/regex", + ), + name: "regex", + }, + is_proc_macro: false, + target_layout: Err( + "target_data_layout not loaded", + ), + channel: None, + }, + CrateId( + 8, + ): CrateData { + root_file_id: FileId( + 7, + ), + edition: Edition2018, + version: Some( + "0.6.29", + ), + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "regex_syntax", + ), + canonical_name: "regex-syntax", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "feature=default", + "feature=unicode", + "feature=unicode-age", + "feature=unicode-bool", + "feature=unicode-case", + "feature=unicode-gencat", + "feature=unicode-perl", + "feature=unicode-script", + "feature=unicode-segment", + "test", + ], + ), + potential_cfg_options: Some( + CfgOptions( + [ + "debug_assertions", + "feature=default", + "feature=unicode", + "feature=unicode-age", + "feature=unicode-bool", + "feature=unicode-case", + "feature=unicode-gencat", + "feature=unicode-perl", + "feature=unicode-script", + "feature=unicode-segment", + ], + ), + ), + env: Env { + entries: { + "CARGO_PKG_LICENSE": "", + "CARGO_PKG_VERSION_MAJOR": "0", + "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/regex-syntax-0.6.29", + "CARGO_PKG_VERSION": "0.6.29", + "CARGO_PKG_AUTHORS": "", + "CARGO_CRATE_NAME": "regex_syntax", + "CARGO_PKG_LICENSE_FILE": "", + "CARGO_PKG_HOMEPAGE": "", + "CARGO_PKG_DESCRIPTION": "", + "CARGO_PKG_NAME": "regex-syntax", + "CARGO_PKG_VERSION_PATCH": "29", + "CARGO": "cargo", + "CARGO_PKG_REPOSITORY": "", + "CARGO_PKG_VERSION_MINOR": "6", + "CARGO_PKG_VERSION_PRE": "", + }, + }, + dependencies: [], + origin: Library { + repo: Some( + "https://github.com/rust-lang/regex", + ), + name: "regex-syntax", + }, + is_proc_macro: false, + target_layout: Err( + "target_data_layout not loaded", + ), + channel: None, + }, + CrateId( + 9, + ): CrateData { + root_file_id: FileId( + 3, + ), + edition: Edition2018, + version: Some( + "2.5.0", + ), + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "memchr", + ), + canonical_name: "memchr", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "feature=default", + "feature=std", + "test", + ], + ), + potential_cfg_options: Some( + CfgOptions( + [ + "debug_assertions", + "feature=compiler_builtins", + "feature=core", + "feature=default", + "feature=libc", + "feature=rustc-dep-of-std", + "feature=std", + "feature=use_std", + ], + ), + ), + env: Env { + entries: { + "CARGO_PKG_LICENSE": "", + "CARGO_PKG_VERSION_MAJOR": "2", + "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.5.0", + "CARGO_PKG_VERSION": "2.5.0", + "CARGO_PKG_AUTHORS": "", + "CARGO_CRATE_NAME": "memchr", + "CARGO_PKG_LICENSE_FILE": "", + "CARGO_PKG_HOMEPAGE": "", + "CARGO_PKG_DESCRIPTION": "", + "CARGO_PKG_NAME": "memchr", + "CARGO_PKG_VERSION_PATCH": "0", + "CARGO": "cargo", + "CARGO_PKG_REPOSITORY": "", + "CARGO_PKG_VERSION_MINOR": "5", + "CARGO_PKG_VERSION_PRE": "", + }, + }, + dependencies: [], + origin: Library { + repo: Some( + "https://github.com/BurntSushi/memchr", + ), + name: "memchr", + }, + is_proc_macro: false, + target_layout: Err( + "target_data_layout not loaded", + ), + channel: None, + }, + CrateId( + 10, + ): CrateData { + root_file_id: FileId( + 1, + ), + edition: Edition2018, + version: Some( + "0.7.20", + ), + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "aho_corasick", + ), + canonical_name: "aho_corasick", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "feature=default", + "feature=std", + "test", + ], + ), + potential_cfg_options: Some( + CfgOptions( + [ + "debug_assertions", + "feature=default", + "feature=std", + ], + ), + ), + env: Env { + entries: { + "CARGO_PKG_LICENSE": "", + "CARGO_PKG_VERSION_MAJOR": "0", + "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/aho-corasick-0.7.20", + "CARGO_PKG_VERSION": "0.7.20", + "CARGO_PKG_AUTHORS": "", + "CARGO_CRATE_NAME": "aho_corasick", + "CARGO_PKG_LICENSE_FILE": "", + "CARGO_PKG_HOMEPAGE": "", + "CARGO_PKG_DESCRIPTION": "", + "CARGO_PKG_NAME": "aho-corasick", + "CARGO_PKG_VERSION_PATCH": "20", + "CARGO": "cargo", + "CARGO_PKG_REPOSITORY": "", + "CARGO_PKG_VERSION_MINOR": "7", + "CARGO_PKG_VERSION_PRE": "", + }, + }, + dependencies: [ + Dependency { + crate_id: CrateId( + 9, + ), + name: CrateName( + "memchr", + ), + prelude: true, + }, + ], + origin: Library { + repo: Some( + "https://github.com/BurntSushi/aho-corasick", + ), + name: "aho-corasick", + }, + is_proc_macro: false, + target_layout: Err( + "target_data_layout not loaded", + ), + channel: None, + }, + CrateId( + 11, + ): CrateData { + root_file_id: FileId( + 4, + ), + edition: Edition2021, + version: Some( + "0.1.0", + ), + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "ra_playground", + ), + canonical_name: "ra-playground", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "test", + ], + ), + potential_cfg_options: None, + env: Env { + entries: { + "CARGO_PKG_LICENSE": "", + "CARGO_PKG_VERSION_MAJOR": "0", + "CARGO_MANIFEST_DIR": "$ROOT$ra-playground", + "CARGO_PKG_VERSION": "0.1.0", + "CARGO_PKG_AUTHORS": "", + "CARGO_CRATE_NAME": "ra_playground", + "CARGO_PKG_LICENSE_FILE": "", + "CARGO_PKG_HOMEPAGE": "", + "CARGO_PKG_DESCRIPTION": "", + "CARGO_PKG_NAME": "ra-playground", + "CARGO_PKG_VERSION_PATCH": "0", + "CARGO": "cargo", + "CARGO_PKG_REPOSITORY": "", + "CARGO_PKG_VERSION_MINOR": "1", + "CARGO_PKG_VERSION_PRE": "", + }, + }, + dependencies: [ + Dependency { + crate_id: CrateId( + 12, + ), + name: CrateName( + "la_arena", + ), + prelude: true, + }, + ], + origin: Local { + repo: None, + name: Some( + "ra-playground", + ), + }, + is_proc_macro: false, + target_layout: Err( + "target_data_layout not loaded", + ), + channel: None, + }, + CrateId( + 12, + ): CrateData { + root_file_id: FileId( + 2, + ), + edition: Edition2021, + version: Some( + "0.3.0", + ), + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "la_arena", + ), + canonical_name: "la-arena", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + "test", + ], + ), + potential_cfg_options: None, + env: Env { + entries: { + "CARGO_PKG_LICENSE": "", + "CARGO_PKG_VERSION_MAJOR": "0", + "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/la-arena-0.3.0", + "CARGO_PKG_VERSION": "0.3.0", + "CARGO_PKG_AUTHORS": "", + "CARGO_CRATE_NAME": "la_arena", + "CARGO_PKG_LICENSE_FILE": "", + "CARGO_PKG_HOMEPAGE": "", + "CARGO_PKG_DESCRIPTION": "", + "CARGO_PKG_NAME": "la-arena", + "CARGO_PKG_VERSION_PATCH": "0", + "CARGO": "cargo", + "CARGO_PKG_REPOSITORY": "", + "CARGO_PKG_VERSION_MINOR": "3", + "CARGO_PKG_VERSION_PRE": "", + }, + }, + dependencies: [], + origin: Library { + repo: Some( + "https://github.com/rust-lang/rust-analyzer/tree/master/lib/la-arena", + ), + name: "la-arena", + }, + is_proc_macro: false, + target_layout: Err( + "target_data_layout not loaded", + ), + channel: None, + }, }, } \ No newline at end of file