From ee10f9f5cdcecd18845e6b516c1f1a5ab5afba41 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 19 Apr 2024 10:41:08 +0200 Subject: [PATCH] Cleanup cfg and env handling in project-model --- crates/base-db/src/input.rs | 7 +- crates/project-model/src/build_scripts.rs | 4 +- crates/project-model/src/cargo_workspace.rs | 32 ++++ .../project-model/src/{cfg_flag.rs => cfg.rs} | 27 +++- crates/project-model/src/env.rs | 84 ++++++++++ crates/project-model/src/lib.rs | 6 +- crates/project-model/src/project_json.rs | 2 +- crates/project-model/src/rustc_cfg.rs | 2 +- crates/project-model/src/workspace.rs | 143 +++++------------- 9 files changed, 193 insertions(+), 114 deletions(-) rename crates/project-model/src/{cfg_flag.rs => cfg.rs} (72%) create mode 100644 crates/project-model/src/env.rs diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs index 6a8ab71624..e136d8e915 100644 --- a/crates/base-db/src/input.rs +++ b/crates/base-db/src/input.rs @@ -331,10 +331,11 @@ impl CrateGraph { version: Option, cfg_options: Arc, potential_cfg_options: Option>, - env: Env, + mut env: Env, is_proc_macro: bool, origin: CrateOrigin, ) -> CrateId { + env.entries.shrink_to_fit(); let data = CrateData { root_file_id, edition, @@ -651,8 +652,8 @@ impl FromIterator<(String, String)> for Env { } impl Env { - pub fn set(&mut self, env: &str, value: String) { - self.entries.insert(env.to_owned(), value); + pub fn set(&mut self, env: &str, value: impl Into) { + self.entries.insert(env.to_owned(), value.into()); } pub fn get(&self, env: &str) -> Option { diff --git a/crates/project-model/src/build_scripts.rs b/crates/project-model/src/build_scripts.rs index 26f8f52bbc..fbd423c9ea 100644 --- a/crates/project-model/src/build_scripts.rs +++ b/crates/project-model/src/build_scripts.rs @@ -23,16 +23,18 @@ use serde::Deserialize; use toolchain::Tool; use crate::{ - cfg_flag::CfgFlag, utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation, + cfg::CfgFlag, utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation, InvocationStrategy, Package, Sysroot, TargetKind, }; +/// Output of the build script and proc-macro building steps for a workspace. #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct WorkspaceBuildScripts { outputs: ArenaMap, error: Option, } +/// Output of the build script and proc-macro building step for a concrete package. #[derive(Debug, Clone, Default, PartialEq, Eq)] pub(crate) struct BuildScriptOutput { /// List of config flags defined by this package's build script. diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs index 82fee7112f..fd898ffa5c 100644 --- a/crates/project-model/src/cargo_workspace.rs +++ b/crates/project-model/src/cargo_workspace.rs @@ -135,6 +135,20 @@ pub struct PackageData { pub active_features: Vec, /// String representation of package id pub id: String, + /// Authors as given in the `Cargo.toml` + pub authors: Vec, + /// Description as given in the `Cargo.toml` + pub description: Option, + /// Homepage as given in the `Cargo.toml` + pub homepage: Option, + /// License as given in the `Cargo.toml` + pub license: Option, + /// License file as given in the `Cargo.toml` + pub license_file: Option, + /// Readme file as given in the `Cargo.toml` + pub readme: Option, + /// Rust version as given in the `Cargo.toml` + pub rust_version: Option, /// The contents of [package.metadata.rust-analyzer] pub metadata: RustAnalyzerPackageMetaData, } @@ -225,6 +239,10 @@ impl TargetKind { } TargetKind::Other } + + pub fn is_executable(self) -> bool { + matches!(self, TargetKind::Bin | TargetKind::Example) + } } // Deserialize helper for the cargo metadata @@ -330,6 +348,13 @@ impl CargoWorkspace { repository, edition, metadata, + authors, + description, + homepage, + license, + license_file, + readme, + rust_version, .. } = meta_pkg; let meta = from_value::(metadata).unwrap_or_default(); @@ -358,6 +383,13 @@ impl CargoWorkspace { is_member, edition, repository, + authors, + description, + homepage, + license, + license_file, + readme, + rust_version, dependencies: Vec::new(), features: features.into_iter().collect(), active_features: Vec::new(), diff --git a/crates/project-model/src/cfg_flag.rs b/crates/project-model/src/cfg.rs similarity index 72% rename from crates/project-model/src/cfg_flag.rs rename to crates/project-model/src/cfg.rs index 6192e18da0..b409bc1ce7 100644 --- a/crates/project-model/src/cfg_flag.rs +++ b/crates/project-model/src/cfg.rs @@ -3,7 +3,8 @@ //! rustc main.rs --cfg foo --cfg 'feature="bar"' use std::{fmt, str::FromStr}; -use cfg::CfgOptions; +use cfg::{CfgDiff, CfgOptions}; +use rustc_hash::FxHashMap; use serde::Serialize; #[derive(Clone, Eq, PartialEq, Debug, Serialize)] @@ -70,3 +71,27 @@ impl fmt::Display for CfgFlag { } } } + +/// 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, +} + +impl CfgOverrides { + pub fn len(&self) -> usize { + self.global.len() + self.selective.values().map(|it| it.len()).sum::() + } + + pub fn apply(&self, cfg_options: &mut CfgOptions, name: &str) { + if !self.global.is_empty() { + cfg_options.apply_diff(self.global.clone()); + }; + if let Some(diff) = self.selective.get(name) { + cfg_options.apply_diff(diff.clone()); + }; + } +} diff --git a/crates/project-model/src/env.rs b/crates/project-model/src/env.rs new file mode 100644 index 0000000000..541298585a --- /dev/null +++ b/crates/project-model/src/env.rs @@ -0,0 +1,84 @@ +use base_db::Env; +use rustc_hash::FxHashMap; +use toolchain::Tool; + +use crate::{utf8_stdout, ManifestPath, PackageData, Sysroot, TargetKind}; + +/// Recreates the compile-time environment variables that Cargo sets. +/// +/// Should be synced with +/// +/// +/// FIXME: ask Cargo to provide this data instead of re-deriving. +pub(crate) fn inject_cargo_package_env(env: &mut Env, package: &PackageData) { + // FIXME: Missing variables: + // CARGO_BIN_NAME, CARGO_BIN_EXE_ + + let manifest_dir = package.manifest.parent(); + env.set("CARGO_MANIFEST_DIR", manifest_dir.as_str()); + + 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", package.authors.join(":").clone()); + + env.set("CARGO_PKG_NAME", package.name.clone()); + env.set("CARGO_PKG_DESCRIPTION", package.description.as_deref().unwrap_or_default()); + env.set("CARGO_PKG_HOMEPAGE", package.homepage.as_deref().unwrap_or_default()); + env.set("CARGO_PKG_REPOSITORY", package.repository.as_deref().unwrap_or_default()); + env.set("CARGO_PKG_LICENSE", package.license.as_deref().unwrap_or_default()); + env.set( + "CARGO_PKG_LICENSE_FILE", + package.license_file.as_ref().map(ToString::to_string).unwrap_or_default(), + ); + env.set( + "CARGO_PKG_README", + package.readme.as_ref().map(ToString::to_string).unwrap_or_default(), + ); + + env.set( + "CARGO_PKG_RUST_VERSION", + package.rust_version.as_ref().map(ToString::to_string).unwrap_or_default(), + ); +} + +pub(crate) fn inject_cargo_env(env: &mut Env) { + env.set("CARGO", Tool::Cargo.path().to_string()); +} + +pub(crate) fn inject_rustc_tool_env(env: &mut Env, cargo_name: &str, kind: TargetKind) { + _ = kind; + // FIXME + // if kind.is_executable() { + // env.set("CARGO_BIN_NAME", cargo_name); + // } + env.set("CARGO_CRATE_NAME", cargo_name.replace('-', "_")); +} + +pub(crate) fn cargo_config_env( + cargo_toml: &ManifestPath, + extra_env: &FxHashMap, + sysroot: Option<&Sysroot>, +) -> FxHashMap { + 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 { + 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() +} diff --git a/crates/project-model/src/lib.rs b/crates/project-model/src/lib.rs index 28696aa327..7f3e35ca5d 100644 --- a/crates/project-model/src/lib.rs +++ b/crates/project-model/src/lib.rs @@ -19,7 +19,8 @@ mod build_scripts; mod cargo_workspace; -mod cfg_flag; +mod cfg; +mod env; mod manifest_path; mod project_json; mod rustc_cfg; @@ -47,10 +48,11 @@ pub use crate::{ CargoConfig, CargoFeatures, CargoWorkspace, Package, PackageData, PackageDependency, RustLibSource, Target, TargetData, TargetKind, }, + cfg::CfgOverrides, manifest_path::ManifestPath, project_json::{ProjectJson, ProjectJsonData}, sysroot::Sysroot, - workspace::{CfgOverrides, PackageRoot, ProjectWorkspace}, + workspace::{FileLoader, PackageRoot, ProjectWorkspace}, }; #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs index a8e30d2267..fac6eb8ad3 100644 --- a/crates/project-model/src/project_json.rs +++ b/crates/project-model/src/project_json.rs @@ -55,7 +55,7 @@ use rustc_hash::FxHashMap; use serde::{de, Deserialize, Serialize}; use span::Edition; -use crate::cfg_flag::CfgFlag; +use crate::cfg::CfgFlag; /// Roots and crates that compose this Rust project. #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/crates/project-model/src/rustc_cfg.rs b/crates/project-model/src/rustc_cfg.rs index 501b1fdc8c..194bae5570 100644 --- a/crates/project-model/src/rustc_cfg.rs +++ b/crates/project-model/src/rustc_cfg.rs @@ -4,7 +4,7 @@ use anyhow::Context; use rustc_hash::FxHashMap; use toolchain::Tool; -use crate::{cfg_flag::CfgFlag, utf8_stdout, ManifestPath, Sysroot}; +use crate::{cfg::CfgFlag, utf8_stdout, ManifestPath, Sysroot}; /// Determines how `rustc --print cfg` is discovered and invoked. pub(crate) enum RustcCfgConfig<'a> { diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index 6a063905ca..0baf90e54e 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,7 +21,8 @@ use triomphe::Arc; use crate::{ build_scripts::BuildScriptOutput, cargo_workspace::{DepKind, PackageData, RustLibSource}, - cfg_flag::CfgFlag, + 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}, @@ -30,29 +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, -} - -impl CfgOverrides { - pub fn len(&self) -> usize { - self.global.len() + self.selective.values().map(|it| it.len()).sum::() - } - - fn apply(&self, cfg_options: &mut CfgOptions, name: &str) { - if !self.global.is_empty() { - cfg_options.apply_diff(self.global.clone()); - }; - if let Some(diff) = self.selective.get(name) { - cfg_options.apply_diff(diff.clone()); - }; - } -} +pub type FileLoader<'a> = &'a mut dyn for<'b> FnMut(&'b AbsPath) -> Option; /// `PackageRoot` describes a package root folder. /// Which may be an external dependency, or a member of @@ -69,29 +48,43 @@ 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>, + /// The rustc workspace loaded for this workspace. An `Err(None)` means loading has been + /// disabled or was otherwise not requested. rustc: Result, Option>, /// 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, + /// A set of cfg overrides for this workspace. cfg_overrides: CfgOverrides, + /// The toolchain version used by this workspace. toolchain: Option, + /// The target data layout queried for workspace. target_layout: TargetLayoutLoadResult, + /// Environment variables set in the `.cargo/config` file. cargo_config_extra_env: FxHashMap, }, /// 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>, /// 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, + /// The toolchain version used by this workspace. toolchain: Option, + /// The target data layout queried for workspace. target_layout: TargetLayoutLoadResult, }, // FIXME: The primary limitation of this approach is that the set of detached files needs to be fixed at the beginning. @@ -105,12 +98,18 @@ 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 { + /// The set of detached files. files: Vec, + /// The sysroot loaded for this workspace. sysroot: Result>, /// 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, + /// The toolchain version used by this workspace. toolchain: Option, + /// The target data layout queried for workspace. target_layout: TargetLayoutLoadResult, }, } @@ -723,7 +722,7 @@ impl ProjectWorkspace { pub fn to_crate_graph( &self, - load: &mut dyn FnMut(&AbsPath) -> Option, + load: FileLoader<'_>, extra_env: &FxHashMap, ) -> (CrateGraph, ProcMacroPaths) { let _p = tracing::span!(tracing::Level::INFO, "ProjectWorkspace::to_crate_graph").entered(); @@ -874,7 +873,7 @@ impl ProjectWorkspace { fn project_json_to_crate_graph( rustc_cfg: Vec, - load: &mut dyn FnMut(&AbsPath) -> Option, + load: FileLoader<'_>, project: &ProjectJson, sysroot: Option<&Sysroot>, extra_env: &FxHashMap, @@ -976,7 +975,7 @@ fn project_json_to_crate_graph( } fn cargo_to_crate_graph( - load: &mut dyn FnMut(&AbsPath) -> Option, + load: FileLoader<'_>, rustc: Option<&(CargoWorkspace, WorkspaceBuildScripts)>, cargo: &CargoWorkspace, sysroot: Option<&Sysroot>, @@ -1166,7 +1165,7 @@ fn cargo_to_crate_graph( fn detached_files_to_crate_graph( rustc_cfg: Vec, - load: &mut dyn FnMut(&AbsPath) -> Option, + load: FileLoader<'_>, detached_files: &[AbsPathBuf], sysroot: Option<&Sysroot>, ) -> (CrateGraph, ProcMacroPaths) { @@ -1216,7 +1215,7 @@ fn handle_rustc_crates( crate_graph: &mut CrateGraph, proc_macros: &mut ProcMacroPaths, pkg_to_lib_crate: &mut FxHashMap, - load: &mut dyn FnMut(&AbsPath) -> Option, + load: FileLoader<'_>, rustc_workspace: &CargoWorkspace, cargo: &CargoWorkspace, public_deps: &SysrootPublicDeps, @@ -1350,23 +1349,19 @@ 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()), Arc::new(cfg_options), potential_cfg_options.map(Arc::new), @@ -1405,7 +1400,7 @@ fn sysroot_to_crate_graph( crate_graph: &mut CrateGraph, sysroot: &Sysroot, rustc_cfg: Vec, - load: &mut dyn FnMut(&AbsPath) -> Option, + load: FileLoader<'_>, ) -> (SysrootPublicDeps, Option) { let _p = tracing::span!(tracing::Level::INFO, "sysroot_to_crate_graph").entered(); match sysroot.mode() { @@ -1480,7 +1475,6 @@ fn sysroot_to_crate_graph( .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( @@ -1490,7 +1484,7 @@ fn sysroot_to_crate_graph( None, cfg_options.clone(), None, - env, + Env::default(), false, CrateOrigin::Lang(LangCrateOrigin::from(&*stitched[krate].name)), ); @@ -1549,70 +1543,9 @@ fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) { } } -/// Recreates the compile-time environment variables that Cargo sets. -/// -/// Should be synced with -/// -/// -/// 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_ - - 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) -> 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, - sysroot: Option<&Sysroot>, -) -> FxHashMap { - 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 { - 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() -}