From 186c5c47cbfde4ae9d81dc67450c958cb6aece2c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 12 Apr 2021 11:04:36 +0300 Subject: feat: avoid checking the whole project during initial loading --- crates/project_model/src/build_data.rs | 236 ++++++++++++++++++--------------- 1 file changed, 129 insertions(+), 107 deletions(-) (limited to 'crates/project_model') diff --git a/crates/project_model/src/build_data.rs b/crates/project_model/src/build_data.rs index 0d4d39fef..ab5cc8c49 100644 --- a/crates/project_model/src/build_data.rs +++ b/crates/project_model/src/build_data.rs @@ -58,12 +58,17 @@ impl PartialEq for BuildDataConfig { impl Eq for BuildDataConfig {} -#[derive(Debug, Default)] +#[derive(Debug)] pub struct BuildDataCollector { + wrap_rustc: bool, configs: FxHashMap, } impl BuildDataCollector { + pub fn new(wrap_rustc: bool) -> Self { + Self { wrap_rustc, configs: FxHashMap::default() } + } + pub(crate) fn add_config(&mut self, workspace_root: &AbsPath, config: BuildDataConfig) { self.configs.insert(workspace_root.to_path_buf(), config); } @@ -71,15 +76,14 @@ impl BuildDataCollector { pub fn collect(&mut self, progress: &dyn Fn(String)) -> Result { let mut res = BuildDataResult::default(); for (path, config) in self.configs.iter() { - res.per_workspace.insert( - path.clone(), - collect_from_workspace( - &config.cargo_toml, - &config.cargo_features, - &config.packages, - progress, - )?, - ); + let workspace_build_data = WorkspaceBuildData::collect( + &config.cargo_toml, + &config.cargo_features, + &config.packages, + self.wrap_rustc, + progress, + )?; + res.per_workspace.insert(path.clone(), workspace_build_data); } Ok(res) } @@ -120,119 +124,137 @@ impl BuildDataConfig { } } -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]); - } +impl WorkspaceBuildData { + fn collect( + cargo_toml: &AbsPath, + cargo_features: &CargoConfig, + packages: &Vec, + wrap_rustc: bool, + progress: &dyn Fn(String), + ) -> Result { + let mut cmd = Command::new(toolchain::cargo()); + + if wrap_rustc { + // Setup RUSTC_WRAPPER to point to `rust-analyzer` binary itself. We use + // that to compile only proc macros and build scripts during the initial + // `cargo check`. + let myself = std::env::current_exe()?; + cmd.env("RUSTC_WRAPPER", myself); + cmd.env("RA_RUSTC_WRAPPER", "1"); + } + + cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"]) + .arg(cargo_toml.as_ref()); - 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"); + // --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]); } - if !cargo_features.features.is_empty() { - cmd.arg("--features"); - cmd.arg(cargo_features.features.join(" ")); + + 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::piped()).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 = WorkspaceBuildData::default(); - for message in cargo_metadata::Message::parse_stream(stdout).flatten() { - 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) - } - }; + cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).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 = WorkspaceBuildData::default(); + for message in cargo_metadata::Message::parse_stream(stdout).flatten() { + 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 package_build_data = + res.per_package.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.as_str().is_empty() { + let out_dir = AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string())); + package_build_data.out_dir = Some(out_dir); + package_build_data.cfgs = cfgs; } - acc - }; - let package_build_data = - res.per_package.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.as_str().is_empty() { - let out_dir = AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string())); - package_build_data.out_dir = Some(out_dir); - package_build_data.cfgs = cfgs; - } - package_build_data.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(PathBuf::from(&filename)); - let package_build_data = - res.per_package.entry(package_id.repr.clone()).or_default(); - package_build_data.proc_macro_dylib_path = Some(filename); + package_build_data.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(PathBuf::from(&filename)); + let package_build_data = + res.per_package.entry(package_id.repr.clone()).or_default(); + package_build_data.proc_macro_dylib_path = Some(filename); + } } } + Message::CompilerMessage(message) => { + progress(message.target.name.clone()); + } + Message::BuildFinished(_) => {} + Message::TextLine(_) => {} + _ => {} } - Message::CompilerMessage(message) => { - progress(message.target.name.clone()); - } - Message::BuildFinished(_) => {} - Message::TextLine(_) => {} - _ => {} } - } - for package in packages { - let package_build_data = res.per_package.entry(package.id.repr.clone()).or_default(); - inject_cargo_env(package, package_build_data); - if let Some(out_dir) = &package_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()) { - package_build_data.envs.push(("OUT_DIR".to_string(), out_dir)); + for package in packages { + let package_build_data = res.per_package.entry(package.id.repr.clone()).or_default(); + inject_cargo_env(package, package_build_data); + if let Some(out_dir) = &package_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()) { + package_build_data.envs.push(("OUT_DIR".to_string(), out_dir)); + } } } - } - let output = child.into_inner().wait_with_output()?; - if !output.status.success() { - let mut stderr = String::from_utf8(output.stderr).unwrap_or_default(); - if stderr.is_empty() { - stderr = "cargo check failed".to_string(); + let output = child.into_inner().wait_with_output()?; + if !output.status.success() { + let mut stderr = String::from_utf8(output.stderr).unwrap_or_default(); + if stderr.is_empty() { + stderr = "cargo check failed".to_string(); + } + res.error = Some(stderr) } - res.error = Some(stderr) - } - Ok(res) + Ok(res) + } } // FIXME: File a better way to know if it is a dylib -- cgit v1.2.3