diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs index 9e9691d11e85..b168a7c34b56 100644 --- a/crates/project-model/src/tests.rs +++ b/crates/project-model/src/tests.rs @@ -535,6 +535,78 @@ fn cargo_hello_world_project_model_with_selective_overrides() { arena: { CrateId( 0, + ): CrateData { + root_file_id: FileId( + 1, + ), + edition: Edition2018, + version: Some( + "0.1.0", + ), + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "hello_world", + ), + canonical_name: "hello-world", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + ], + ), + potential_cfg_options: CfgOptions( + [ + "debug_assertions", + ], + ), + target_layout: Err( + "target_data_layout not loaded", + ), + env: Env { + entries: { + "CARGO_PKG_LICENSE": "", + "CARGO_PKG_VERSION_MAJOR": "0", + "CARGO_MANIFEST_DIR": "$ROOT$hello-world", + "CARGO_PKG_VERSION": "0.1.0", + "CARGO_PKG_AUTHORS": "", + "CARGO_CRATE_NAME": "hello_world", + "CARGO_PKG_LICENSE_FILE": "", + "CARGO_PKG_HOMEPAGE": "", + "CARGO_PKG_DESCRIPTION": "", + "CARGO_PKG_NAME": "hello-world", + "CARGO_PKG_VERSION_PATCH": "0", + "CARGO": "cargo", + "CARGO_PKG_REPOSITORY": "", + "CARGO_PKG_VERSION_MINOR": "1", + "CARGO_PKG_VERSION_PRE": "", + }, + }, + dependencies: [ + Dependency { + crate_id: CrateId( + 5, + ), + name: CrateName( + "libc", + ), + prelude: true, + }, + ], + proc_macro: Err( + "crate has not (yet) been built", + ), + origin: CratesIo { + repo: None, + name: Some( + "hello-world", + ), + }, + is_proc_macro: false, + }, + CrateId( + 1, ): CrateData { root_file_id: FileId( 1, @@ -588,7 +660,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() { dependencies: [ Dependency { crate_id: CrateId( - 4, + 5, ), name: CrateName( "libc", @@ -608,7 +680,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() { is_proc_macro: false, }, CrateId( - 1, + 2, ): CrateData { root_file_id: FileId( 2, @@ -671,7 +743,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() { }, Dependency { crate_id: CrateId( - 4, + 5, ), name: CrateName( "libc", @@ -691,7 +763,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() { is_proc_macro: false, }, CrateId( - 2, + 3, ): CrateData { root_file_id: FileId( 3, @@ -711,13 +783,11 @@ fn cargo_hello_world_project_model_with_selective_overrides() { cfg_options: CfgOptions( [ "debug_assertions", - "test", ], ), potential_cfg_options: CfgOptions( [ "debug_assertions", - "test", ], ), target_layout: Err( @@ -754,7 +824,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() { }, Dependency { crate_id: CrateId( - 4, + 5, ), name: CrateName( "libc", @@ -774,7 +844,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() { is_proc_macro: false, }, CrateId( - 3, + 4, ): CrateData { root_file_id: FileId( 4, @@ -794,13 +864,11 @@ fn cargo_hello_world_project_model_with_selective_overrides() { cfg_options: CfgOptions( [ "debug_assertions", - "test", ], ), potential_cfg_options: CfgOptions( [ "debug_assertions", - "test", ], ), target_layout: Err( @@ -837,7 +905,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() { }, Dependency { crate_id: CrateId( - 4, + 5, ), name: CrateName( "libc", @@ -857,7 +925,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() { is_proc_macro: false, }, CrateId( - 4, + 5, ): CrateData { root_file_id: FileId( 5, @@ -944,6 +1012,78 @@ fn cargo_hello_world_project_model() { arena: { CrateId( 0, + ): CrateData { + root_file_id: FileId( + 1, + ), + edition: Edition2018, + version: Some( + "0.1.0", + ), + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "hello_world", + ), + canonical_name: "hello-world", + }, + ), + cfg_options: CfgOptions( + [ + "debug_assertions", + ], + ), + potential_cfg_options: CfgOptions( + [ + "debug_assertions", + ], + ), + target_layout: Err( + "target_data_layout not loaded", + ), + env: Env { + entries: { + "CARGO_PKG_LICENSE": "", + "CARGO_PKG_VERSION_MAJOR": "0", + "CARGO_MANIFEST_DIR": "$ROOT$hello-world", + "CARGO_PKG_VERSION": "0.1.0", + "CARGO_PKG_AUTHORS": "", + "CARGO_CRATE_NAME": "hello_world", + "CARGO_PKG_LICENSE_FILE": "", + "CARGO_PKG_HOMEPAGE": "", + "CARGO_PKG_DESCRIPTION": "", + "CARGO_PKG_NAME": "hello-world", + "CARGO_PKG_VERSION_PATCH": "0", + "CARGO": "cargo", + "CARGO_PKG_REPOSITORY": "", + "CARGO_PKG_VERSION_MINOR": "1", + "CARGO_PKG_VERSION_PRE": "", + }, + }, + dependencies: [ + Dependency { + crate_id: CrateId( + 5, + ), + name: CrateName( + "libc", + ), + prelude: true, + }, + ], + proc_macro: Err( + "crate has not (yet) been built", + ), + origin: CratesIo { + repo: None, + name: Some( + "hello-world", + ), + }, + is_proc_macro: false, + }, + CrateId( + 1, ): CrateData { root_file_id: FileId( 1, @@ -997,7 +1137,7 @@ fn cargo_hello_world_project_model() { dependencies: [ Dependency { crate_id: CrateId( - 4, + 5, ), name: CrateName( "libc", @@ -1017,7 +1157,7 @@ fn cargo_hello_world_project_model() { is_proc_macro: false, }, CrateId( - 1, + 2, ): CrateData { root_file_id: FileId( 2, @@ -1080,7 +1220,7 @@ fn cargo_hello_world_project_model() { }, Dependency { crate_id: CrateId( - 4, + 5, ), name: CrateName( "libc", @@ -1100,7 +1240,7 @@ fn cargo_hello_world_project_model() { is_proc_macro: false, }, CrateId( - 2, + 3, ): CrateData { root_file_id: FileId( 3, @@ -1120,13 +1260,11 @@ fn cargo_hello_world_project_model() { cfg_options: CfgOptions( [ "debug_assertions", - "test", ], ), potential_cfg_options: CfgOptions( [ "debug_assertions", - "test", ], ), target_layout: Err( @@ -1163,7 +1301,7 @@ fn cargo_hello_world_project_model() { }, Dependency { crate_id: CrateId( - 4, + 5, ), name: CrateName( "libc", @@ -1183,7 +1321,7 @@ fn cargo_hello_world_project_model() { is_proc_macro: false, }, CrateId( - 3, + 4, ): CrateData { root_file_id: FileId( 4, @@ -1203,13 +1341,11 @@ fn cargo_hello_world_project_model() { cfg_options: CfgOptions( [ "debug_assertions", - "test", ], ), potential_cfg_options: CfgOptions( [ "debug_assertions", - "test", ], ), target_layout: Err( @@ -1246,7 +1382,7 @@ fn cargo_hello_world_project_model() { }, Dependency { crate_id: CrateId( - 4, + 5, ), name: CrateName( "libc", @@ -1266,7 +1402,7 @@ fn cargo_hello_world_project_model() { is_proc_macro: false, }, CrateId( - 4, + 5, ): CrateData { root_file_id: FileId( 5, diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index 2a11f1e8eb82..81b58c4c87a0 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -9,7 +9,7 @@ use base_db::{ CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Edition, Env, FileId, LangCrateOrigin, ProcMacroLoadResult, TargetLayoutLoadResult, }; -use cfg::{CfgDiff, CfgOptions}; +use cfg::{CfgDiff, CfgExpr, CfgOptions}; use paths::{AbsPath, AbsPathBuf}; use rustc_hash::{FxHashMap, FxHashSet}; use semver::Version; @@ -813,29 +813,11 @@ fn cargo_to_crate_graph( let mut has_private = false; // Next, create crates for each package, target pair for pkg in cargo.packages() { - let mut cfg_options = cfg_options.clone(); - let overrides = match override_cfg { CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff), CfgOverrides::Selective(cfg_overrides) => cfg_overrides.get(&cargo[pkg].name), }; - // Add test cfg for local crates - if cargo[pkg].is_local { - cfg_options.insert_atom("test".into()); - } - - 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()); - }; - has_private |= cargo[pkg].metadata.rustc_private; let mut lib_tgt = None; for &tgt in cargo[pkg].targets.iter() { @@ -849,34 +831,92 @@ fn cargo_to_crate_graph( } if let Some(file_id) = load(&cargo[tgt].root) { - let crate_id = add_target_crate_root( - &mut crate_graph, - &cargo[pkg], - build_scripts.get_output(pkg), - cfg_options.clone(), - &mut |path| load_proc_macro(&cargo[tgt].name, path), - file_id, - &cargo[tgt].name, - cargo[tgt].is_proc_macro, - target_layout.clone(), - ); - if cargo[tgt].kind == TargetKind::Lib { - lib_tgt = Some((crate_id, cargo[tgt].name.clone())); - pkg_to_lib_crate.insert(pkg, crate_id); - } - // Even crates that don't set proc-macro = true are allowed to depend on proc_macro - // (just none of the APIs work when called outside of a proc macro). - if let Some(proc_macro) = libproc_macro { - add_dep_with_prelude( + let mut add_crate = |cfg_options: &CfgOptions| { + let crate_id = add_target_crate_root( &mut crate_graph, - crate_id, - CrateName::new("proc_macro").unwrap(), - proc_macro, + &cargo[pkg], + build_scripts.get_output(pkg), + cfg_options.clone(), + &mut |path| load_proc_macro(&cargo[tgt].name, path), + file_id, + &cargo[tgt].name, cargo[tgt].is_proc_macro, + target_layout.clone(), ); + // Even crates that don't set proc-macro = true are allowed to depend on proc_macro + // (just none of the APIs work when called outside of a proc macro). + if let Some(proc_macro) = libproc_macro { + add_dep_with_prelude( + &mut crate_graph, + crate_id, + CrateName::new("proc_macro").unwrap(), + proc_macro, + cargo[tgt].is_proc_macro, + ); + } + pkg_crates + .entry(pkg) + .or_insert_with(Vec::new) + .push((crate_id, cargo[tgt].kind)); + crate_id + }; + + let mut cfg_options = cfg_options.clone(); + // Enable cfg(test) for bin crates in the current workspace so that diagnostics + // are provided for tests. + // They have to be in the current workspace because tests are allowed to use dev + // depedencies, which cargo only resolves for crates in the current workspace. + // Lib crates are more complicated because the test version of the crate can + // actually depend on the normal version of the crate, which we handle below. + if cargo[pkg].is_member && cargo[tgt].kind == TargetKind::Bin { + cfg_options.insert_atom("test".into()); } - pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, cargo[tgt].kind)); + 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()); + }; + + let crate_id = add_crate(&cfg_options); + + if cargo[tgt].kind == TargetKind::Lib { + // Mark this as the lib crate for this package. + lib_tgt = Some((crate_id, cargo[tgt].name.clone())); + pkg_to_lib_crate.insert(pkg, crate_id); + + if cargo[pkg].is_member { + // Create another version of the crate with cfg(test) enabled. + // This, as for bin crates, is so that diagnostics are provided for tests, but + // it needs to be treated as a separate crate because it can actually depend on + // the normal version of the crate when it's specified as a dev dependency. + // + // We only do this if the package is a member of this workspace, because cargo + // doesn't resolve dev-dependencies for packages outside the workspace and so + // any unit tests that use them won't work properly. + cfg_options.insert_atom("test".into()); + + // Apply the overrides again, in case they disable cfg(test) + if let Some(overrides) = overrides { + cfg_options.apply_diff(overrides.clone()); + }; + + let test_cfg = CfgExpr::Atom(cfg::CfgAtom::Flag("test".into())); + // It the overrides disabled cfg(test), there's no reason for this extra version + // of the crate to exist. + if cfg_options + .check(&test_cfg) + .expect("cfg(test) was somehow an invalid cfg") + { + add_crate(&cfg_options); + } + } + } } } @@ -886,8 +926,9 @@ fn cargo_to_crate_graph( public_deps.add_to_crate_graph(&mut crate_graph, from); if let Some((to, name)) = lib_tgt.clone() { - if to != from && kind != TargetKind::BuildScript { - // (build script can not depend on its library target) + if !matches!(kind, TargetKind::BuildScript | TargetKind::Lib) { + // (build script can not depend on its library target, and library target does + // not depend on itself unless manually specified) // For root projects with dashes in their name, // cargo metadata does not do any normalization, @@ -915,6 +956,21 @@ fn cargo_to_crate_graph( continue; } + let crate_data = &crate_graph[from]; + let test_cfg = CfgExpr::Atom(cfg::CfgAtom::Flag("test".into())); + // Dev dependencies can be used by tests, examples, and benchmarks, + // as well as bin and lib crates when compiling in test mode. + let dev_deps_enabled = + matches!(kind, TargetKind::Test | TargetKind::Example | TargetKind::Bench) + || (matches!(kind, TargetKind::Bin | TargetKind::Lib) + && crate_data + .cfg_options + .check(&test_cfg) + .expect("cfg(test) was somehow an invalid cfg")); + if dep.kind == DepKind::Dev && !dev_deps_enabled { + continue; + } + add_dep(&mut crate_graph, from, name.clone(), to) } }