From 9358eecc042d8b551f58d2d5ddb9c88d258880c1 Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Thu, 28 Jan 2021 23:33:02 +0800 Subject: Async Loading outdir and proc-macro --- crates/project_model/src/build_data.rs | 288 +++++++++++++++++++-------------- 1 file changed, 168 insertions(+), 120 deletions(-) (limited to 'crates/project_model/src/build_data.rs') diff --git a/crates/project_model/src/build_data.rs b/crates/project_model/src/build_data.rs index 3ff347e2c..a5c564e0a 100644 --- a/crates/project_model/src/build_data.rs +++ b/crates/project_model/src/build_data.rs @@ -5,10 +5,11 @@ use std::{ io::BufReader, path::{Path, PathBuf}, process::{Command, Stdio}, + sync::Arc, }; use anyhow::Result; -use cargo_metadata::{BuildScript, Message, Package, PackageId}; +use cargo_metadata::{BuildScript, Message}; use itertools::Itertools; use paths::{AbsPath, AbsPathBuf}; use rustc_hash::FxHashMap; @@ -16,150 +17,195 @@ use stdx::JodChild; use crate::{cfg_flag::CfgFlag, CargoConfig}; -#[derive(Debug, Clone, Default)] -pub(crate) struct BuildDataMap { - data: FxHashMap, -} #[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct BuildData { +pub(crate) struct BuildData { /// List of config flags defined by this package's build script - pub cfgs: Vec, + pub(crate) cfgs: Vec, /// List of cargo-related environment variables with their value /// /// If the package has a build script which defines environment variables, /// they can also be found here. - pub envs: Vec<(String, String)>, + pub(crate) envs: Vec<(String, String)>, /// Directory where a build script might place its output - pub out_dir: Option, + pub(crate) out_dir: Option, /// Path to the proc-macro library file if this package exposes proc-macros - pub proc_macro_dylib_path: Option, + pub(crate) proc_macro_dylib_path: Option, } -impl BuildDataMap { - pub(crate) fn new( - cargo_toml: &AbsPath, - cargo_features: &CargoConfig, - packages: &Vec, - progress: &dyn Fn(String), - ) -> Result { - let mut cmd = Command::new(toolchain::cargo()); - cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"]) - .arg(cargo_toml.as_ref()); - - // --all-targets includes tests, benches and examples in addition to the - // default lib and bins. This is an independent concept from the --targets - // flag below. - cmd.arg("--all-targets"); - - if let Some(target) = &cargo_features.target { - cmd.args(&["--target", target]); - } +#[derive(Clone, Debug)] +pub(crate) struct BuildDataConfig { + cargo_toml: AbsPathBuf, + cargo_features: CargoConfig, + packages: Arc>, +} - if cargo_features.all_features { - cmd.arg("--all-features"); - } else { - if cargo_features.no_default_features { - // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` - // https://github.com/oli-obk/cargo_metadata/issues/79 - cmd.arg("--no-default-features"); - } - if !cargo_features.features.is_empty() { - cmd.arg("--features"); - cmd.arg(cargo_features.features.join(" ")); - } - } +impl PartialEq for BuildDataConfig { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.packages, &other.packages) + } +} - cmd.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null()); - - let mut child = cmd.spawn().map(JodChild)?; - let child_stdout = child.stdout.take().unwrap(); - let stdout = BufReader::new(child_stdout); - - let mut res = BuildDataMap::default(); - for message in cargo_metadata::Message::parse_stream(stdout) { - if let Ok(message) = message { - match message { - Message::BuildScriptExecuted(BuildScript { - package_id, - out_dir, - cfgs, - env, - .. - }) => { - let cfgs = { - let mut acc = Vec::new(); - for cfg in cfgs { - match cfg.parse::() { - Ok(it) => acc.push(it), - Err(err) => { - anyhow::bail!("invalid cfg from cargo-metadata: {}", err) - } - }; - } - acc - }; - let res = res.data.entry(package_id.clone()).or_default(); - // cargo_metadata crate returns default (empty) path for - // older cargos, which is not absolute, so work around that. - if out_dir != PathBuf::default() { - let out_dir = AbsPathBuf::assert(out_dir); - res.out_dir = Some(out_dir); - res.cfgs = cfgs; - } +impl Eq for BuildDataConfig {} - res.envs = env; - } - Message::CompilerArtifact(message) => { - progress(format!("metadata {}", message.target.name)); - - if message.target.kind.contains(&"proc-macro".to_string()) { - let package_id = message.package_id; - // Skip rmeta file - if let Some(filename) = - message.filenames.iter().find(|name| is_dylib(name)) - { - let filename = AbsPathBuf::assert(filename.clone()); - let res = res.data.entry(package_id.clone()).or_default(); - res.proc_macro_dylib_path = Some(filename); - } - } - } - Message::CompilerMessage(message) => { - progress(message.target.name.clone()); - } - Message::Unknown => (), - Message::BuildFinished(_) => {} - Message::TextLine(_) => {} - } - } +#[derive(Debug, Default)] +pub struct BuildDataCollector { + configs: FxHashMap, +} + +#[derive(Debug, Default, PartialEq, Eq)] +pub struct BuildDataResult { + data: FxHashMap, +} + +pub(crate) type BuildDataMap = FxHashMap; + +impl BuildDataCollector { + pub(crate) fn add_config(&mut self, workspace_root: &AbsPath, config: BuildDataConfig) { + self.configs.insert(workspace_root.to_path_buf().clone(), config); + } + + pub fn collect(&mut self, progress: &dyn Fn(String)) -> Result { + let mut res = BuildDataResult::default(); + for (path, config) in self.configs.iter() { + res.data.insert( + path.clone(), + collect_from_workspace( + &config.cargo_toml, + &config.cargo_features, + &config.packages, + progress, + )?, + ); } - res.inject_cargo_env(packages); Ok(res) } +} + +impl BuildDataResult { + pub(crate) fn get(&self, root: &AbsPath) -> Option<&BuildDataMap> { + self.data.get(&root.to_path_buf()) + } +} - pub(crate) fn with_cargo_env(packages: &Vec) -> Self { - let mut res = Self::default(); - res.inject_cargo_env(packages); - res +impl BuildDataConfig { + pub(crate) fn new( + cargo_toml: AbsPathBuf, + cargo_features: CargoConfig, + packages: Arc>, + ) -> Self { + Self { cargo_toml, cargo_features, packages } } +} - pub(crate) fn get(&self, id: &PackageId) -> Option<&BuildData> { - self.data.get(id) +fn collect_from_workspace( + cargo_toml: &AbsPath, + cargo_features: &CargoConfig, + packages: &Vec, + progress: &dyn Fn(String), +) -> Result { + let mut cmd = Command::new(toolchain::cargo()); + cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"]) + .arg(cargo_toml.as_ref()); + + // --all-targets includes tests, benches and examples in addition to the + // default lib and bins. This is an independent concept from the --targets + // flag below. + cmd.arg("--all-targets"); + + if let Some(target) = &cargo_features.target { + cmd.args(&["--target", target]); } - fn inject_cargo_env(&mut self, packages: &Vec) { - for meta_pkg in packages { - let resource = self.data.entry(meta_pkg.id.clone()).or_default(); - inject_cargo_env(meta_pkg, &mut resource.envs); + if cargo_features.all_features { + cmd.arg("--all-features"); + } else { + if cargo_features.no_default_features { + // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` + // https://github.com/oli-obk/cargo_metadata/issues/79 + cmd.arg("--no-default-features"); + } + if !cargo_features.features.is_empty() { + cmd.arg("--features"); + cmd.arg(cargo_features.features.join(" ")); + } + } + + cmd.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null()); - if let Some(out_dir) = &resource.out_dir { - // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() - if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { - resource.envs.push(("OUT_DIR".to_string(), out_dir)); + let mut child = cmd.spawn().map(JodChild)?; + let child_stdout = child.stdout.take().unwrap(); + let stdout = BufReader::new(child_stdout); + + let mut res = BuildDataMap::default(); + for message in cargo_metadata::Message::parse_stream(stdout) { + if let Ok(message) = message { + match message { + Message::BuildScriptExecuted(BuildScript { + package_id, + out_dir, + cfgs, + env, + .. + }) => { + let cfgs = { + let mut acc = Vec::new(); + for cfg in cfgs { + match cfg.parse::() { + Ok(it) => acc.push(it), + Err(err) => { + anyhow::bail!("invalid cfg from cargo-metadata: {}", err) + } + }; + } + acc + }; + let res = res.entry(package_id.repr.clone()).or_default(); + // cargo_metadata crate returns default (empty) path for + // older cargos, which is not absolute, so work around that. + if out_dir != PathBuf::default() { + let out_dir = AbsPathBuf::assert(out_dir); + res.out_dir = Some(out_dir); + res.cfgs = cfgs; + } + + res.envs = env; + } + Message::CompilerArtifact(message) => { + progress(format!("metadata {}", message.target.name)); + + if message.target.kind.contains(&"proc-macro".to_string()) { + let package_id = message.package_id; + // Skip rmeta file + if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) + { + let filename = AbsPathBuf::assert(filename.clone()); + let res = res.entry(package_id.repr.clone()).or_default(); + res.proc_macro_dylib_path = Some(filename); + } + } + } + Message::CompilerMessage(message) => { + progress(message.target.name.clone()); } + Message::Unknown => (), + Message::BuildFinished(_) => {} + Message::TextLine(_) => {} } } } + + for package in packages { + let build_data = res.entry(package.id.repr.clone()).or_default(); + inject_cargo_env(package, build_data); + if let Some(out_dir) = &build_data.out_dir { + // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() + if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { + build_data.envs.push(("OUT_DIR".to_string(), out_dir)); + } + } + } + + Ok(res) } // FIXME: File a better way to know if it is a dylib @@ -173,7 +219,9 @@ fn is_dylib(path: &Path) -> bool { /// Recreates the compile-time environment variables that Cargo sets. /// /// Should be synced with -fn inject_cargo_env(package: &cargo_metadata::Package, env: &mut Vec<(String, String)>) { +fn inject_cargo_env(package: &cargo_metadata::Package, build_data: &mut BuildData) { + let env = &mut build_data.envs; + // FIXME: Missing variables: // CARGO_PKG_HOMEPAGE, CARGO_CRATE_NAME, CARGO_BIN_NAME, CARGO_BIN_EXE_ -- cgit v1.2.3