Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/project-model/src/workspace.rs')
| -rw-r--r-- | crates/project-model/src/workspace.rs | 586 |
1 files changed, 323 insertions, 263 deletions
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index b8c5885108..98c5a02dcd 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -2,7 +2,7 @@ //! metadata` or `rust-project.json`) into representation stored in the salsa //! database -- `CrateGraph`. -use std::{collections::VecDeque, fmt, fs, iter, str::FromStr, sync}; +use std::{collections::VecDeque, fmt, fs, iter, sync}; use anyhow::{format_err, Context}; use base_db::{ @@ -21,8 +21,9 @@ use triomphe::Arc; use crate::{ build_scripts::BuildScriptOutput, cargo_workspace::{DepKind, PackageData, RustLibSource}, - cfg_flag::CfgFlag, - project_json::Crate, + cfg::{CfgFlag, CfgOverrides}, + env::{cargo_config_env, inject_cargo_env, inject_cargo_package_env, inject_rustc_tool_env}, + project_json::{Crate, CrateArrayIdx}, rustc_cfg::{self, RustcCfgConfig}, sysroot::{SysrootCrate, SysrootMode}, target_data_layout::{self, RustcDataLayoutConfig}, @@ -30,20 +31,7 @@ use crate::{ ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts, }; -/// A set of cfg-overrides per crate. -#[derive(Default, Debug, Clone, Eq, PartialEq)] -pub struct CfgOverrides { - /// A global set of overrides matching all crates. - pub global: CfgDiff, - /// A set of overrides matching specific crates. - pub selective: FxHashMap<String, CfgDiff>, -} - -impl CfgOverrides { - pub fn len(&self) -> usize { - self.global.len() + self.selective.values().map(|it| it.len()).sum::<usize>() - } -} +pub type FileLoader<'a> = &'a mut dyn for<'b> FnMut(&'b AbsPath) -> Option<FileId>; /// `PackageRoot` describes a package root folder. /// Which may be an external dependency, or a member of @@ -60,30 +48,46 @@ pub struct PackageRoot { pub enum ProjectWorkspace { /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. Cargo { + /// The workspace as returned by `cargo metadata`. cargo: CargoWorkspace, + /// The build script results for the workspace. build_scripts: WorkspaceBuildScripts, + /// The sysroot loaded for this workspace. sysroot: Result<Sysroot, Option<String>>, + /// The rustc workspace loaded for this workspace. An `Err(None)` means loading has been + /// disabled or was otherwise not requested. rustc: Result<Box<(CargoWorkspace, WorkspaceBuildScripts)>, Option<String>>, /// Holds cfg flags for the current target. We get those by running /// `rustc --print cfg`. - /// - /// FIXME: make this a per-crate map, as, eg, build.rs might have a - /// different target. + // FIXME: make this a per-crate map, as, eg, build.rs might have a + // different target. rustc_cfg: Vec<CfgFlag>, + /// A set of cfg overrides for this workspace. cfg_overrides: CfgOverrides, + /// The toolchain version used by this workspace. toolchain: Option<Version>, + /// The target data layout queried for workspace. target_layout: TargetLayoutLoadResult, + /// Environment variables set in the `.cargo/config` file. cargo_config_extra_env: FxHashMap<String, String>, }, /// Project workspace was manually specified using a `rust-project.json` file. Json { + /// The loaded project json file. project: ProjectJson, + /// The sysroot loaded for this workspace. sysroot: Result<Sysroot, Option<String>>, /// Holds cfg flags for the current target. We get those by running /// `rustc --print cfg`. + // FIXME: make this a per-crate map, as, eg, build.rs might have a + // different target. rustc_cfg: Vec<CfgFlag>, + /// The toolchain version used by this workspace. toolchain: Option<Version>, + /// The target data layout queried for workspace. target_layout: TargetLayoutLoadResult, + /// A set of cfg overrides for this workspace. + cfg_overrides: CfgOverrides, }, // FIXME: The primary limitation of this approach is that the set of detached files needs to be fixed at the beginning. // That's not the end user experience we should strive for. @@ -95,14 +99,24 @@ pub enum ProjectWorkspace { // // /// Project with a set of disjoint files, not belonging to any particular workspace. /// Backed by basic sysroot crates for basic completion and highlighting. - DetachedFiles { - files: Vec<AbsPathBuf>, + DetachedFile { + /// The file in question. + file: AbsPathBuf, + /// The sysroot loaded for this workspace. sysroot: Result<Sysroot, Option<String>>, /// Holds cfg flags for the current target. We get those by running /// `rustc --print cfg`. + // FIXME: make this a per-crate map, as, eg, build.rs might have a + // different target. rustc_cfg: Vec<CfgFlag>, + /// The toolchain version used by this workspace. toolchain: Option<Version>, + /// The target data layout queried for workspace. target_layout: TargetLayoutLoadResult, + /// A set of cfg overrides for the files. + cfg_overrides: CfgOverrides, + /// Is this file a cargo script file? + cargo_script: Option<CargoWorkspace>, }, } @@ -141,6 +155,7 @@ impl fmt::Debug for ProjectWorkspace { rustc_cfg, toolchain, target_layout: data_layout, + cfg_overrides, } => { let mut debug_struct = f.debug_struct("Json"); debug_struct.field("n_crates", &project.n_crates()); @@ -150,22 +165,28 @@ impl fmt::Debug for ProjectWorkspace { debug_struct .field("n_rustc_cfg", &rustc_cfg.len()) .field("toolchain", &toolchain) - .field("data_layout", &data_layout); + .field("data_layout", &data_layout) + .field("n_cfg_overrides", &cfg_overrides.len()); debug_struct.finish() } - ProjectWorkspace::DetachedFiles { - files, + ProjectWorkspace::DetachedFile { + file, sysroot, rustc_cfg, toolchain, target_layout, + cfg_overrides, + cargo_script, } => f .debug_struct("DetachedFiles") - .field("n_files", &files.len()) + .field("file", &file) + .field("cargo_script", &cargo_script.is_some()) .field("sysroot", &sysroot.is_ok()) + .field("cargo_script", &cargo_script.is_some()) .field("n_rustc_cfg", &rustc_cfg.len()) .field("toolchain", &toolchain) .field("data_layout", &target_layout) + .field("n_cfg_overrides", &cfg_overrides.len()) .finish(), } } @@ -219,6 +240,7 @@ impl ProjectWorkspace { project_json, config.target.as_deref(), &config.extra_env, + &config.cfg_overrides, ) } ProjectManifest::CargoToml(cargo_toml) => { @@ -360,6 +382,7 @@ impl ProjectWorkspace { project_json: ProjectJson, target: Option<&str>, extra_env: &FxHashMap<String, String>, + cfg_overrides: &CfgOverrides, ) -> ProjectWorkspace { let sysroot = match (project_json.sysroot.clone(), project_json.sysroot_src.clone()) { (Some(sysroot), Some(sysroot_src)) => { @@ -406,57 +429,86 @@ impl ProjectWorkspace { rustc_cfg, toolchain, target_layout: data_layout.map(Arc::from).map_err(|it| Arc::from(it.to_string())), + cfg_overrides: cfg_overrides.clone(), } } pub fn load_detached_files( detached_files: Vec<AbsPathBuf>, config: &CargoConfig, - ) -> anyhow::Result<ProjectWorkspace> { - let dir = detached_files - .first() - .and_then(|it| it.parent()) - .ok_or_else(|| format_err!("No detached files to load"))?; - let sysroot = match &config.sysroot { - Some(RustLibSource::Path(path)) => { - Sysroot::with_sysroot_dir(path.clone(), config.sysroot_query_metadata) - .map_err(|e| Some(format!("Failed to find sysroot at {path}:{e}"))) - } - Some(RustLibSource::Discover) => Sysroot::discover( - dir, - &config.extra_env, - config.sysroot_query_metadata, - ) - .map_err(|e| { - Some(format!("Failed to find sysroot for {dir}. Is rust-src installed? {e}")) - }), - None => Err(None), - }; + ) -> Vec<anyhow::Result<ProjectWorkspace>> { + detached_files + .into_iter() + .map(|detached_file| { + let dir = detached_file + .parent() + .ok_or_else(|| format_err!("detached file has no parent"))?; + let sysroot = match &config.sysroot { + Some(RustLibSource::Path(path)) => { + Sysroot::with_sysroot_dir(path.clone(), config.sysroot_query_metadata) + .map_err(|e| Some(format!("Failed to find sysroot at {path}:{e}"))) + } + Some(RustLibSource::Discover) => { + Sysroot::discover(dir, &config.extra_env, config.sysroot_query_metadata) + .map_err(|e| { + Some(format!( + "Failed to find sysroot for {dir}. Is rust-src installed? {e}" + )) + }) + } + None => Err(None), + }; - let sysroot_ref = sysroot.as_ref().ok(); - let toolchain = - match get_toolchain_version(dir, sysroot_ref, Tool::Rustc, &config.extra_env, "rustc ") - { - Ok(it) => it, - Err(e) => { - tracing::error!("{e}"); - None - } - }; + let sysroot_ref = sysroot.as_ref().ok(); + let toolchain = match get_toolchain_version( + dir, + sysroot_ref, + Tool::Rustc, + &config.extra_env, + "rustc ", + ) { + Ok(it) => it, + Err(e) => { + tracing::error!("{e}"); + None + } + }; - let rustc_cfg = rustc_cfg::get(None, &config.extra_env, RustcCfgConfig::Rustc(sysroot_ref)); - let data_layout = target_data_layout::get( - RustcDataLayoutConfig::Rustc(sysroot_ref), - None, - &config.extra_env, - ); - Ok(ProjectWorkspace::DetachedFiles { - files: detached_files, - sysroot, - rustc_cfg, - toolchain, - target_layout: data_layout.map(Arc::from).map_err(|it| Arc::from(it.to_string())), - }) + let rustc_cfg = + rustc_cfg::get(None, &config.extra_env, RustcCfgConfig::Rustc(sysroot_ref)); + let data_layout = target_data_layout::get( + RustcDataLayoutConfig::Rustc(sysroot_ref), + None, + &config.extra_env, + ); + + let cargo_script = ManifestPath::try_from(detached_file.clone()) + .ok() + .and_then(|file| { + CargoWorkspace::fetch_metadata( + &file, + file.parent(), + config, + sysroot_ref, + &|_| (), + ) + .ok() + }) + .map(CargoWorkspace::new); + + Ok(ProjectWorkspace::DetachedFile { + file: detached_file, + sysroot, + rustc_cfg, + toolchain, + target_layout: data_layout + .map(Arc::from) + .map_err(|it| Arc::from(it.to_string())), + cfg_overrides: config.cfg_overrides.clone(), + cargo_script, + }) + }) + .collect() } /// Runs the build scripts for this [`ProjectWorkspace`]. @@ -466,7 +518,13 @@ impl ProjectWorkspace { progress: &dyn Fn(String), ) -> anyhow::Result<WorkspaceBuildScripts> { match self { - ProjectWorkspace::Cargo { cargo, toolchain, sysroot, .. } => { + ProjectWorkspace::DetachedFile { + cargo_script: Some(cargo), + toolchain, + sysroot, + .. + } + | ProjectWorkspace::Cargo { cargo, toolchain, sysroot, .. } => { WorkspaceBuildScripts::run_for_workspace( config, cargo, @@ -478,9 +536,8 @@ impl ProjectWorkspace { format!("Failed to run build scripts for {}", cargo.workspace_root()) }) } - ProjectWorkspace::Json { .. } | ProjectWorkspace::DetachedFiles { .. } => { - Ok(WorkspaceBuildScripts::default()) - } + ProjectWorkspace::DetachedFile { cargo_script: None, .. } + | ProjectWorkspace::Json { .. } => Ok(WorkspaceBuildScripts::default()), } } @@ -536,11 +593,11 @@ impl ProjectWorkspace { } } - pub fn workspace_definition_path(&self) -> Option<&AbsPath> { + pub fn workspace_definition_path(&self) -> &AbsPath { match self { - ProjectWorkspace::Cargo { cargo, .. } => Some(cargo.workspace_root()), - ProjectWorkspace::Json { project, .. } => Some(project.path()), - ProjectWorkspace::DetachedFiles { .. } => None, + ProjectWorkspace::Cargo { cargo, .. } => cargo.workspace_root(), + ProjectWorkspace::Json { project, .. } => project.path(), + ProjectWorkspace::DetachedFile { file, .. } => file, } } @@ -548,10 +605,10 @@ impl ProjectWorkspace { match self { ProjectWorkspace::Cargo { sysroot: Ok(sysroot), .. } | ProjectWorkspace::Json { sysroot: Ok(sysroot), .. } - | ProjectWorkspace::DetachedFiles { sysroot: Ok(sysroot), .. } => { + | ProjectWorkspace::DetachedFile { sysroot: Ok(sysroot), .. } => { sysroot.discover_proc_macro_srv() } - ProjectWorkspace::DetachedFiles { .. } => { + ProjectWorkspace::DetachedFile { .. } => { Err(anyhow::format_err!("cannot find proc-macro server, no sysroot was found")) } ProjectWorkspace::Cargo { cargo, .. } => Err(anyhow::format_err!( @@ -611,6 +668,7 @@ impl ProjectWorkspace { rustc_cfg: _, toolchain: _, target_layout: _, + cfg_overrides: _, } => project .crates() .map(|(_, krate)| PackageRoot { @@ -681,15 +739,50 @@ impl ProjectWorkspace { })) .collect() } - ProjectWorkspace::DetachedFiles { files, sysroot, .. } => files - .iter() - .map(|detached_file| PackageRoot { + ProjectWorkspace::DetachedFile { file, cargo_script, sysroot, .. } => { + iter::once(PackageRoot { is_local: true, - include: vec![detached_file.clone()], + include: vec![file.clone()], exclude: Vec::new(), }) + .chain(cargo_script.iter().flat_map(|cargo| { + cargo.packages().map(|pkg| { + let is_local = cargo[pkg].is_local; + let pkg_root = cargo[pkg].manifest.parent().to_path_buf(); + + let mut include = vec![pkg_root.clone()]; + + // In case target's path is manually set in Cargo.toml to be + // outside the package root, add its parent as an extra include. + // An example of this situation would look like this: + // + // ```toml + // [lib] + // path = "../../src/lib.rs" + // ``` + let extra_targets = cargo[pkg] + .targets + .iter() + .filter(|&&tgt| matches!(cargo[tgt].kind, TargetKind::Lib { .. })) + .filter_map(|&tgt| cargo[tgt].root.parent()) + .map(|tgt| tgt.normalize().to_path_buf()) + .filter(|path| !path.starts_with(&pkg_root)); + include.extend(extra_targets); + + let mut exclude = vec![pkg_root.join(".git")]; + if is_local { + exclude.push(pkg_root.join("target")); + } else { + exclude.push(pkg_root.join("tests")); + exclude.push(pkg_root.join("examples")); + exclude.push(pkg_root.join("benches")); + } + PackageRoot { is_local, include, exclude } + }) + })) .chain(mk_sysroot(sysroot.as_ref())) - .collect(), + .collect() + } } } @@ -705,16 +798,17 @@ impl ProjectWorkspace { let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.num_packages()); cargo.packages().len() + sysroot_package_len + rustc_package_len } - ProjectWorkspace::DetachedFiles { sysroot, files, .. } => { + ProjectWorkspace::DetachedFile { sysroot, cargo_script, .. } => { let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.num_packages()); - sysroot_package_len + files.len() + sysroot_package_len + + cargo_script.as_ref().map_or(1, |cargo| cargo.packages().len()) } } } pub fn to_crate_graph( &self, - load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, + load: FileLoader<'_>, extra_env: &FxHashMap<String, String>, ) -> (CrateGraph, ProcMacroPaths) { let _p = tracing::span!(tracing::Level::INFO, "ProjectWorkspace::to_crate_graph").entered(); @@ -726,6 +820,7 @@ impl ProjectWorkspace { rustc_cfg, toolchain: _, target_layout: _, + cfg_overrides, } => ( project_json_to_crate_graph( rustc_cfg.clone(), @@ -733,6 +828,7 @@ impl ProjectWorkspace { project, sysroot.as_ref().ok(), extra_env, + cfg_overrides, ), sysroot, ), @@ -758,24 +854,39 @@ impl ProjectWorkspace { ), sysroot, ), - ProjectWorkspace::DetachedFiles { - files, + ProjectWorkspace::DetachedFile { + file, sysroot, rustc_cfg, toolchain: _, target_layout: _, + cfg_overrides, + cargo_script, } => ( - detached_files_to_crate_graph( - rustc_cfg.clone(), - load, - files, - sysroot.as_ref().ok(), - ), + if let Some(cargo) = cargo_script { + cargo_to_crate_graph( + &mut |path| load(path), + None, + cargo, + sysroot.as_ref().ok(), + rustc_cfg.clone(), + cfg_overrides, + &WorkspaceBuildScripts::default(), + ) + } else { + detached_file_to_crate_graph( + rustc_cfg.clone(), + load, + file, + sysroot.as_ref().ok(), + cfg_overrides, + ) + }, sysroot, ), }; - if matches!(sysroot.as_ref().map(|it| it.mode()), Ok(SysrootMode::Workspace(_))) + if matches!(sysroot.as_ref().map(|it| it.mode()), Ok(SysrootMode::Stitched(_))) && crate_graph.patch_cfg_if() { tracing::debug!("Patched std to depend on cfg-if") @@ -820,35 +931,56 @@ impl ProjectWorkspace { && cargo_config_extra_env == o_cargo_config_extra_env } ( - Self::Json { project, sysroot, rustc_cfg, toolchain, target_layout: _ }, + Self::Json { + project, + sysroot, + rustc_cfg, + toolchain, + target_layout: _, + cfg_overrides, + }, Self::Json { project: o_project, sysroot: o_sysroot, rustc_cfg: o_rustc_cfg, toolchain: o_toolchain, target_layout: _, + cfg_overrides: o_cfg_overrides, }, ) => { project == o_project && rustc_cfg == o_rustc_cfg && sysroot == o_sysroot && toolchain == o_toolchain + && cfg_overrides == o_cfg_overrides } ( - Self::DetachedFiles { files, sysroot, rustc_cfg, toolchain, target_layout }, - Self::DetachedFiles { - files: o_files, + Self::DetachedFile { + file, + sysroot, + rustc_cfg, + cargo_script, + toolchain, + target_layout, + cfg_overrides, + }, + Self::DetachedFile { + file: o_file, sysroot: o_sysroot, rustc_cfg: o_rustc_cfg, + cargo_script: o_cargo_script, toolchain: o_toolchain, target_layout: o_target_layout, + cfg_overrides: o_cfg_overrides, }, ) => { - files == o_files + file == o_file && sysroot == o_sysroot && rustc_cfg == o_rustc_cfg && toolchain == o_toolchain && target_layout == o_target_layout + && cfg_overrides == o_cfg_overrides + && cargo_script == o_cargo_script } _ => false, } @@ -865,10 +997,11 @@ impl ProjectWorkspace { fn project_json_to_crate_graph( rustc_cfg: Vec<CfgFlag>, - load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, + load: FileLoader<'_>, project: &ProjectJson, sysroot: Option<&Sysroot>, extra_env: &FxHashMap<String, String>, + override_cfg: &CfgOverrides, ) -> (CrateGraph, ProcMacroPaths) { let mut res = (CrateGraph::default(), ProcMacroPaths::default()); let (crate_graph, proc_macros) = &mut res; @@ -878,12 +1011,13 @@ fn project_json_to_crate_graph( let r_a_cfg_flag = CfgFlag::Atom("rust_analyzer".to_owned()); let mut cfg_cache: FxHashMap<&str, Vec<CfgFlag>> = FxHashMap::default(); - let crates: FxHashMap<CrateId, CrateId> = project + + let idx_to_crate_id: FxHashMap<CrateArrayIdx, CrateId> = project .crates() - .filter_map(|(crate_id, krate)| Some((crate_id, krate, load(&krate.root_module)?))) + .filter_map(|(idx, krate)| Some((idx, krate, load(&krate.root_module)?))) .map( |( - crate_id, + idx, Crate { display_name, edition, @@ -907,17 +1041,22 @@ fn project_json_to_crate_graph( None => &rustc_cfg, }; + let mut cfg_options = target_cfgs + .iter() + .chain(cfg.iter()) + .chain(iter::once(&r_a_cfg_flag)) + .cloned() + .collect(); + override_cfg.apply( + &mut cfg_options, + display_name.as_ref().map(|it| it.canonical_name()).unwrap_or_default(), + ); let crate_graph_crate_id = crate_graph.add_crate_root( file_id, *edition, display_name.clone(), version.clone(), - target_cfgs - .iter() - .chain(cfg.iter()) - .chain(iter::once(&r_a_cfg_flag)) - .cloned() - .collect(), + Arc::new(cfg_options), None, env, *is_proc_macro, @@ -939,13 +1078,13 @@ fn project_json_to_crate_graph( proc_macros.insert(crate_graph_crate_id, node); } } - (crate_id, crate_graph_crate_id) + (idx, crate_graph_crate_id) }, ) .collect(); - for (from, krate) in project.crates() { - if let Some(&from) = crates.get(&from) { + for (from_idx, krate) in project.crates() { + if let Some(&from) = idx_to_crate_id.get(&from_idx) { if let Some((public_deps, libproc_macro)) = &sysroot_deps { public_deps.add_to_crate_graph(crate_graph, from); if let Some(proc_macro) = libproc_macro { @@ -954,8 +1093,8 @@ fn project_json_to_crate_graph( } for dep in &krate.deps { - if let Some(&to) = crates.get(&dep.crate_id) { - add_dep(crate_graph, from, dep.name.clone(), to) + if let Some(&to) = idx_to_crate_id.get(&dep.krate) { + add_dep(crate_graph, from, dep.name.clone(), to); } } } @@ -964,7 +1103,7 @@ fn project_json_to_crate_graph( } fn cargo_to_crate_graph( - load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, + load: FileLoader<'_>, rustc: Option<&(CargoWorkspace, WorkspaceBuildScripts)>, cargo: &CargoWorkspace, sysroot: Option<&Sysroot>, @@ -981,7 +1120,7 @@ fn cargo_to_crate_graph( None => (SysrootPublicDeps::default(), None), }; - let cfg_options = create_cfg_options(rustc_cfg); + let cfg_options = CfgOptions::from_iter(rustc_cfg); // Mapping of a package to its library target let mut pkg_to_lib_crate = FxHashMap::default(); @@ -996,25 +1135,13 @@ fn cargo_to_crate_graph( let cfg_options = { let mut cfg_options = cfg_options.clone(); - // Add test cfg for local crates if cargo[pkg].is_local { + // Add test cfg for local crates cfg_options.insert_atom("test".into()); cfg_options.insert_atom("rust_analyzer".into()); } - if !override_cfg.global.is_empty() { - cfg_options.apply_diff(override_cfg.global.clone()); - }; - if let Some(diff) = override_cfg.selective.get(&cargo[pkg].name) { - // 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(diff.clone()); - }; + override_cfg.apply(&mut cfg_options, &cargo[pkg].name); cfg_options }; @@ -1150,6 +1277,7 @@ fn cargo_to_crate_graph( &pkg_crates, &cfg_options, override_cfg, + // FIXME: Remove this once rustc switched over to rust-project.json if rustc_workspace.workspace_root() == cargo.workspace_root() { // the rustc workspace does not use the installed toolchain's proc-macro server // so we need to make sure we don't use the pre compiled proc-macros there either @@ -1163,11 +1291,12 @@ fn cargo_to_crate_graph( res } -fn detached_files_to_crate_graph( +fn detached_file_to_crate_graph( rustc_cfg: Vec<CfgFlag>, - load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, - detached_files: &[AbsPathBuf], + load: FileLoader<'_>, + detached_file: &AbsPathBuf, sysroot: Option<&Sysroot>, + override_cfg: &CfgOverrides, ) -> (CrateGraph, ProcMacroPaths) { let _p = tracing::span!(tracing::Level::INFO, "detached_files_to_crate_graph").entered(); let mut crate_graph = CrateGraph::default(); @@ -1176,37 +1305,38 @@ fn detached_files_to_crate_graph( None => (SysrootPublicDeps::default(), None), }; - let mut cfg_options = create_cfg_options(rustc_cfg); + let mut cfg_options = CfgOptions::from_iter(rustc_cfg); + cfg_options.insert_atom("test".into()); cfg_options.insert_atom("rust_analyzer".into()); + override_cfg.apply(&mut cfg_options, ""); + let cfg_options = Arc::new(cfg_options); + + let file_id = match load(detached_file) { + Some(file_id) => file_id, + None => { + tracing::error!("Failed to load detached file {:?}", detached_file); + return (crate_graph, FxHashMap::default()); + } + }; + let display_name = detached_file + .file_stem() + .map(|file_stem| CrateDisplayName::from_canonical_name(file_stem.to_owned())); + let detached_file_crate = crate_graph.add_crate_root( + file_id, + Edition::CURRENT, + display_name.clone(), + None, + cfg_options.clone(), + None, + Env::default(), + false, + CrateOrigin::Local { + repo: None, + name: display_name.map(|n| n.canonical_name().to_owned()), + }, + ); - for detached_file in detached_files { - let file_id = match load(detached_file) { - Some(file_id) => file_id, - None => { - tracing::error!("Failed to load detached file {:?}", detached_file); - continue; - } - }; - let display_name = detached_file - .file_stem() - .map(|file_stem| CrateDisplayName::from_canonical_name(file_stem.to_owned())); - let detached_file_crate = crate_graph.add_crate_root( - file_id, - Edition::CURRENT, - display_name.clone(), - None, - cfg_options.clone(), - None, - Env::default(), - false, - CrateOrigin::Local { - repo: None, - name: display_name.map(|n| n.canonical_name().to_owned()), - }, - ); - - public_deps.add_to_crate_graph(&mut crate_graph, detached_file_crate); - } + public_deps.add_to_crate_graph(&mut crate_graph, detached_file_crate); (crate_graph, FxHashMap::default()) } @@ -1214,7 +1344,7 @@ fn handle_rustc_crates( crate_graph: &mut CrateGraph, proc_macros: &mut ProcMacroPaths, pkg_to_lib_crate: &mut FxHashMap<Package, CrateId>, - load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, + load: FileLoader<'_>, rustc_workspace: &CargoWorkspace, cargo: &CargoWorkspace, public_deps: &SysrootPublicDeps, @@ -1246,20 +1376,7 @@ fn handle_rustc_crates( } let mut cfg_options = cfg_options.clone(); - - if !override_cfg.global.is_empty() { - cfg_options.apply_diff(override_cfg.global.clone()); - }; - if let Some(diff) = override_cfg.selective.get(&rustc_workspace[pkg].name) { - // 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(diff.clone()); - }; + override_cfg.apply(&mut cfg_options, &rustc_workspace[pkg].name); for &tgt in rustc_workspace[pkg].targets.iter() { let kind @ TargetKind::Lib { is_proc_macro } = rustc_workspace[tgt].kind else { @@ -1361,26 +1478,22 @@ fn add_target_crate_root( }; let mut env = Env::default(); - inject_cargo_env(pkg, &mut env); - if let Ok(cname) = String::from_str(cargo_name) { - // CARGO_CRATE_NAME is the name of the Cargo target with - converted to _, such as the name of the library, binary, example, integration test, or benchmark. - env.set("CARGO_CRATE_NAME", cname.replace('-', "_")); - } + inject_cargo_package_env(&mut env, pkg); + inject_cargo_env(&mut env); + inject_rustc_tool_env(&mut env, cargo_name, kind); if let Some(envs) = build_data.map(|it| &it.envs) { for (k, v) in envs { env.set(k, v.clone()); } } - - let display_name = CrateDisplayName::from_canonical_name(cargo_name.to_owned()); let crate_id = crate_graph.add_crate_root( file_id, edition, - Some(display_name), + Some(CrateDisplayName::from_canonical_name(cargo_name.to_owned())), Some(pkg.version.to_string()), - cfg_options, - potential_cfg_options, + Arc::new(cfg_options), + potential_cfg_options.map(Arc::new), env, matches!(kind, TargetKind::Lib { is_proc_macro: true }), origin, @@ -1416,7 +1529,7 @@ fn sysroot_to_crate_graph( crate_graph: &mut CrateGraph, sysroot: &Sysroot, rustc_cfg: Vec<CfgFlag>, - load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, + load: FileLoader<'_>, ) -> (SysrootPublicDeps, Option<CrateId>) { let _p = tracing::span!(tracing::Level::INFO, "sysroot_to_crate_graph").entered(); match sysroot.mode() { @@ -1427,7 +1540,17 @@ fn sysroot_to_crate_graph( cargo, None, rustc_cfg, - &CfgOverrides::default(), + &CfgOverrides { + global: CfgDiff::new( + vec![ + CfgAtom::Flag("debug_assertions".into()), + CfgAtom::Flag("miri".into()), + ], + vec![], + ) + .unwrap(), + ..Default::default() + }, &WorkspaceBuildScripts::default(), ); @@ -1436,7 +1559,7 @@ fn sysroot_to_crate_graph( let diff = CfgDiff::new(vec![], vec![CfgAtom::Flag("test".into())]).unwrap(); for (cid, c) in cg.iter_mut() { // uninject `test` flag so `core` keeps working. - c.cfg_options.apply_diff(diff.clone()); + Arc::make_mut(&mut c.cfg_options).apply_diff(diff.clone()); // patch the origin if c.origin.is_local() { let lang_crate = LangCrateOrigin::from( @@ -1485,13 +1608,18 @@ fn sysroot_to_crate_graph( (SysrootPublicDeps { deps: pub_deps }, libproc_macro) } SysrootMode::Stitched(stitched) => { - let cfg_options = create_cfg_options(rustc_cfg); + let cfg_options = Arc::new({ + let mut cfg_options = CfgOptions::default(); + cfg_options.extend(rustc_cfg); + cfg_options.insert_atom("debug_assertions".into()); + cfg_options.insert_atom("miri".into()); + cfg_options + }); let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = stitched .crates() .filter_map(|krate| { let file_id = load(&stitched[krate].root)?; - let env = Env::default(); let display_name = CrateDisplayName::from_canonical_name(stitched[krate].name.clone()); let crate_id = crate_graph.add_crate_root( @@ -1501,7 +1629,7 @@ fn sysroot_to_crate_graph( None, cfg_options.clone(), None, - env, + Env::default(), false, CrateOrigin::Lang(LangCrateOrigin::from(&*stitched[krate].name)), ); @@ -1559,71 +1687,3 @@ fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) { tracing::error!("{}", err) } } - -/// Recreates the compile-time environment variables that Cargo sets. -/// -/// Should be synced with -/// <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates> -/// -/// FIXME: ask Cargo to provide this data instead of re-deriving. -fn inject_cargo_env(package: &PackageData, env: &mut Env) { - // FIXME: Missing variables: - // CARGO_BIN_NAME, CARGO_BIN_EXE_<name> - - let manifest_dir = package.manifest.parent(); - env.set("CARGO_MANIFEST_DIR", manifest_dir.as_str().to_owned()); - - // Not always right, but works for common cases. - env.set("CARGO", "cargo".into()); - - env.set("CARGO_PKG_VERSION", package.version.to_string()); - env.set("CARGO_PKG_VERSION_MAJOR", package.version.major.to_string()); - env.set("CARGO_PKG_VERSION_MINOR", package.version.minor.to_string()); - env.set("CARGO_PKG_VERSION_PATCH", package.version.patch.to_string()); - env.set("CARGO_PKG_VERSION_PRE", package.version.pre.to_string()); - - env.set("CARGO_PKG_AUTHORS", String::new()); - - env.set("CARGO_PKG_NAME", package.name.clone()); - // FIXME: This isn't really correct (a package can have many crates with different names), but - // it's better than leaving the variable unset. - env.set("CARGO_CRATE_NAME", CrateName::normalize_dashes(&package.name).to_string()); - env.set("CARGO_PKG_DESCRIPTION", String::new()); - env.set("CARGO_PKG_HOMEPAGE", String::new()); - env.set("CARGO_PKG_REPOSITORY", String::new()); - env.set("CARGO_PKG_LICENSE", String::new()); - - env.set("CARGO_PKG_LICENSE_FILE", String::new()); -} - -fn create_cfg_options(rustc_cfg: Vec<CfgFlag>) -> CfgOptions { - let mut cfg_options = CfgOptions::default(); - cfg_options.extend(rustc_cfg); - cfg_options.insert_atom("debug_assertions".into()); - cfg_options -} - -fn cargo_config_env( - cargo_toml: &ManifestPath, - extra_env: &FxHashMap<String, String>, - sysroot: Option<&Sysroot>, -) -> FxHashMap<String, String> { - let mut cargo_config = Sysroot::tool(sysroot, Tool::Cargo); - cargo_config.envs(extra_env); - cargo_config - .current_dir(cargo_toml.parent()) - .args(["-Z", "unstable-options", "config", "get", "env"]) - .env("RUSTC_BOOTSTRAP", "1"); - // if successful we receive `env.key.value = "value" per entry - tracing::debug!("Discovering cargo config env by {:?}", cargo_config); - utf8_stdout(cargo_config).map(parse_output_cargo_config_env).unwrap_or_default() -} - -fn parse_output_cargo_config_env(stdout: String) -> FxHashMap<String, String> { - stdout - .lines() - .filter_map(|l| l.strip_prefix("env.")) - .filter_map(|l| l.split_once(".value = ")) - .map(|(key, value)| (key.to_owned(), value.trim_matches('"').to_owned())) - .collect() -} |