aboutsummaryrefslogtreecommitdiff
path: root/crates/project_model
diff options
context:
space:
mode:
Diffstat (limited to 'crates/project_model')
-rw-r--r--crates/project_model/src/build_data.rs294
-rw-r--r--crates/project_model/src/workspace.rs10
2 files changed, 189 insertions, 115 deletions
diff --git a/crates/project_model/src/build_data.rs b/crates/project_model/src/build_data.rs
index f7050be4e..faca336de 100644
--- a/crates/project_model/src/build_data.rs
+++ b/crates/project_model/src/build_data.rs
@@ -1,7 +1,6 @@
1//! Handles build script specific information 1//! Handles build script specific information
2 2
3use std::{ 3use std::{
4 io::BufReader,
5 path::PathBuf, 4 path::PathBuf,
6 process::{Command, Stdio}, 5 process::{Command, Stdio},
7 sync::Arc, 6 sync::Arc,
@@ -13,12 +12,13 @@ use cargo_metadata::{BuildScript, Message};
13use itertools::Itertools; 12use itertools::Itertools;
14use paths::{AbsPath, AbsPathBuf}; 13use paths::{AbsPath, AbsPathBuf};
15use rustc_hash::FxHashMap; 14use rustc_hash::FxHashMap;
16use stdx::JodChild; 15use serde::Deserialize;
16use stdx::format_to;
17 17
18use crate::{cfg_flag::CfgFlag, CargoConfig}; 18use crate::{cfg_flag::CfgFlag, CargoConfig};
19 19
20#[derive(Debug, Clone, Default, PartialEq, Eq)] 20#[derive(Debug, Clone, Default, PartialEq, Eq)]
21pub(crate) struct BuildData { 21pub(crate) struct PackageBuildData {
22 /// List of config flags defined by this package's build script 22 /// List of config flags defined by this package's build script
23 pub(crate) cfgs: Vec<CfgFlag>, 23 pub(crate) cfgs: Vec<CfgFlag>,
24 /// List of cargo-related environment variables with their value 24 /// List of cargo-related environment variables with their value
@@ -32,6 +32,17 @@ pub(crate) struct BuildData {
32 pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>, 32 pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
33} 33}
34 34
35#[derive(Debug, Default, PartialEq, Eq, Clone)]
36pub(crate) struct WorkspaceBuildData {
37 per_package: FxHashMap<String, PackageBuildData>,
38 error: Option<String>,
39}
40
41#[derive(Debug, Default, PartialEq, Eq, Clone)]
42pub struct BuildDataResult {
43 per_workspace: FxHashMap<AbsPathBuf, WorkspaceBuildData>,
44}
45
35#[derive(Clone, Debug)] 46#[derive(Clone, Debug)]
36pub(crate) struct BuildDataConfig { 47pub(crate) struct BuildDataConfig {
37 cargo_toml: AbsPathBuf, 48 cargo_toml: AbsPathBuf,
@@ -47,19 +58,17 @@ impl PartialEq for BuildDataConfig {
47 58
48impl Eq for BuildDataConfig {} 59impl Eq for BuildDataConfig {}
49 60
50#[derive(Debug, Default)] 61#[derive(Debug)]
51pub struct BuildDataCollector { 62pub struct BuildDataCollector {
63 wrap_rustc: bool,
52 configs: FxHashMap<AbsPathBuf, BuildDataConfig>, 64 configs: FxHashMap<AbsPathBuf, BuildDataConfig>,
53} 65}
54 66
55#[derive(Debug, Default, PartialEq, Eq)]
56pub struct BuildDataResult {
57 data: FxHashMap<AbsPathBuf, BuildDataMap>,
58}
59
60pub(crate) type BuildDataMap = FxHashMap<String, BuildData>;
61
62impl BuildDataCollector { 67impl BuildDataCollector {
68 pub fn new(wrap_rustc: bool) -> Self {
69 Self { wrap_rustc, configs: FxHashMap::default() }
70 }
71
63 pub(crate) fn add_config(&mut self, workspace_root: &AbsPath, config: BuildDataConfig) { 72 pub(crate) fn add_config(&mut self, workspace_root: &AbsPath, config: BuildDataConfig) {
64 self.configs.insert(workspace_root.to_path_buf(), config); 73 self.configs.insert(workspace_root.to_path_buf(), config);
65 } 74 }
@@ -67,23 +76,41 @@ impl BuildDataCollector {
67 pub fn collect(&mut self, progress: &dyn Fn(String)) -> Result<BuildDataResult> { 76 pub fn collect(&mut self, progress: &dyn Fn(String)) -> Result<BuildDataResult> {
68 let mut res = BuildDataResult::default(); 77 let mut res = BuildDataResult::default();
69 for (path, config) in self.configs.iter() { 78 for (path, config) in self.configs.iter() {
70 res.data.insert( 79 let workspace_build_data = WorkspaceBuildData::collect(
71 path.clone(), 80 &config.cargo_toml,
72 collect_from_workspace( 81 &config.cargo_features,
73 &config.cargo_toml, 82 &config.packages,
74 &config.cargo_features, 83 self.wrap_rustc,
75 &config.packages, 84 progress,
76 progress, 85 )?;
77 )?, 86 res.per_workspace.insert(path.clone(), workspace_build_data);
78 );
79 } 87 }
80 Ok(res) 88 Ok(res)
81 } 89 }
82} 90}
83 91
92impl WorkspaceBuildData {
93 pub(crate) fn get(&self, package_id: &str) -> Option<&PackageBuildData> {
94 self.per_package.get(package_id)
95 }
96}
97
84impl BuildDataResult { 98impl BuildDataResult {
85 pub(crate) fn get(&self, root: &AbsPath) -> Option<&BuildDataMap> { 99 pub(crate) fn get(&self, workspace_root: &AbsPath) -> Option<&WorkspaceBuildData> {
86 self.data.get(&root.to_path_buf()) 100 self.per_workspace.get(workspace_root)
101 }
102 pub fn error(&self) -> Option<String> {
103 let mut buf = String::new();
104 for (_workspace_root, build_data) in &self.per_workspace {
105 if let Some(err) = &build_data.error {
106 format_to!(buf, "cargo check failed:\n{}", err);
107 }
108 }
109 if buf.is_empty() {
110 return None;
111 }
112
113 Some(buf)
87 } 114 }
88} 115}
89 116
@@ -97,108 +124,155 @@ impl BuildDataConfig {
97 } 124 }
98} 125}
99 126
100fn collect_from_workspace( 127impl WorkspaceBuildData {
101 cargo_toml: &AbsPath, 128 fn collect(
102 cargo_features: &CargoConfig, 129 cargo_toml: &AbsPath,
103 packages: &Vec<cargo_metadata::Package>, 130 cargo_features: &CargoConfig,
104 progress: &dyn Fn(String), 131 packages: &Vec<cargo_metadata::Package>,
105) -> Result<BuildDataMap> { 132 wrap_rustc: bool,
106 let mut cmd = Command::new(toolchain::cargo()); 133 progress: &dyn Fn(String),
107 cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"]) 134 ) -> Result<WorkspaceBuildData> {
108 .arg(cargo_toml.as_ref()); 135 let mut cmd = Command::new(toolchain::cargo());
109 136
110 // --all-targets includes tests, benches and examples in addition to the 137 if wrap_rustc {
111 // default lib and bins. This is an independent concept from the --targets 138 // Setup RUSTC_WRAPPER to point to `rust-analyzer` binary itself. We use
112 // flag below. 139 // that to compile only proc macros and build scripts during the initial
113 cmd.arg("--all-targets"); 140 // `cargo check`.
114 141 let myself = std::env::current_exe()?;
115 if let Some(target) = &cargo_features.target { 142 cmd.env("RUSTC_WRAPPER", myself);
116 cmd.args(&["--target", target]); 143 cmd.env("RA_RUSTC_WRAPPER", "1");
117 } 144 }
118 145
119 if cargo_features.all_features { 146 cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"])
120 cmd.arg("--all-features"); 147 .arg(cargo_toml.as_ref());
121 } else { 148
122 if cargo_features.no_default_features { 149 // --all-targets includes tests, benches and examples in addition to the
123 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` 150 // default lib and bins. This is an independent concept from the --targets
124 // https://github.com/oli-obk/cargo_metadata/issues/79 151 // flag below.
125 cmd.arg("--no-default-features"); 152 cmd.arg("--all-targets");
153
154 if let Some(target) = &cargo_features.target {
155 cmd.args(&["--target", target]);
126 } 156 }
127 if !cargo_features.features.is_empty() { 157
128 cmd.arg("--features"); 158 if cargo_features.all_features {
129 cmd.arg(cargo_features.features.join(" ")); 159 cmd.arg("--all-features");
160 } else {
161 if cargo_features.no_default_features {
162 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
163 // https://github.com/oli-obk/cargo_metadata/issues/79
164 cmd.arg("--no-default-features");
165 }
166 if !cargo_features.features.is_empty() {
167 cmd.arg("--features");
168 cmd.arg(cargo_features.features.join(" "));
169 }
130 } 170 }
131 }
132 171
133 cmd.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null()); 172 cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
134 173
135 let mut child = cmd.spawn().map(JodChild)?; 174 let mut res = WorkspaceBuildData::default();
136 let child_stdout = child.stdout.take().unwrap(); 175
137 let stdout = BufReader::new(child_stdout); 176 let mut callback_err = None;
138 177 let output = stdx::process::streaming_output(
139 let mut res = BuildDataMap::default(); 178 cmd,
140 for message in cargo_metadata::Message::parse_stream(stdout).flatten() { 179 &mut |line| {
141 match message { 180 if callback_err.is_some() {
142 Message::BuildScriptExecuted(BuildScript { 181 return;
143 package_id, out_dir, cfgs, env, .. 182 }
144 }) => { 183
145 let cfgs = { 184 // Copy-pasted from existing cargo_metadata. It seems like we
146 let mut acc = Vec::new(); 185 // should be using sered_stacker here?
147 for cfg in cfgs { 186 let mut deserializer = serde_json::Deserializer::from_str(&line);
148 match cfg.parse::<CfgFlag>() { 187 deserializer.disable_recursion_limit();
149 Ok(it) => acc.push(it), 188 let message = Message::deserialize(&mut deserializer)
150 Err(err) => { 189 .unwrap_or(Message::TextLine(line.to_string()));
151 anyhow::bail!("invalid cfg from cargo-metadata: {}", err) 190
191 match message {
192 Message::BuildScriptExecuted(BuildScript {
193 package_id,
194 out_dir,
195 cfgs,
196 env,
197 ..
198 }) => {
199 let cfgs = {
200 let mut acc = Vec::new();
201 for cfg in cfgs {
202 match cfg.parse::<CfgFlag>() {
203 Ok(it) => acc.push(it),
204 Err(err) => {
205 callback_err = Some(anyhow::format_err!(
206 "invalid cfg from cargo-metadata: {}",
207 err
208 ));
209 return;
210 }
211 };
152 } 212 }
213 acc
153 }; 214 };
215 let package_build_data =
216 res.per_package.entry(package_id.repr.clone()).or_default();
217 // cargo_metadata crate returns default (empty) path for
218 // older cargos, which is not absolute, so work around that.
219 if !out_dir.as_str().is_empty() {
220 let out_dir =
221 AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string()));
222 package_build_data.out_dir = Some(out_dir);
223 package_build_data.cfgs = cfgs;
224 }
225
226 package_build_data.envs = env;
154 } 227 }
155 acc 228 Message::CompilerArtifact(message) => {
156 }; 229 progress(format!("metadata {}", message.target.name));
157 let res = res.entry(package_id.repr.clone()).or_default(); 230
158 // cargo_metadata crate returns default (empty) path for 231 if message.target.kind.contains(&"proc-macro".to_string()) {
159 // older cargos, which is not absolute, so work around that. 232 let package_id = message.package_id;
160 if !out_dir.as_str().is_empty() { 233 // Skip rmeta file
161 let out_dir = AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string())); 234 if let Some(filename) =
162 res.out_dir = Some(out_dir); 235 message.filenames.iter().find(|name| is_dylib(name))
163 res.cfgs = cfgs; 236 {
164 } 237 let filename = AbsPathBuf::assert(PathBuf::from(&filename));
165 238 let package_build_data =
166 res.envs = env; 239 res.per_package.entry(package_id.repr.clone()).or_default();
167 } 240 package_build_data.proc_macro_dylib_path = Some(filename);
168 Message::CompilerArtifact(message) => { 241 }
169 progress(format!("metadata {}", message.target.name)); 242 }
170
171 if message.target.kind.contains(&"proc-macro".to_string()) {
172 let package_id = message.package_id;
173 // Skip rmeta file
174 if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) {
175 let filename = AbsPathBuf::assert(PathBuf::from(&filename));
176 let res = res.entry(package_id.repr.clone()).or_default();
177 res.proc_macro_dylib_path = Some(filename);
178 } 243 }
244 Message::CompilerMessage(message) => {
245 progress(message.target.name.clone());
246 }
247 Message::BuildFinished(_) => {}
248 Message::TextLine(_) => {}
249 _ => {}
250 }
251 },
252 &mut |_| (),
253 )?;
254
255 for package in packages {
256 let package_build_data = res.per_package.entry(package.id.repr.clone()).or_default();
257 inject_cargo_env(package, package_build_data);
258 if let Some(out_dir) = &package_build_data.out_dir {
259 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
260 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
261 package_build_data.envs.push(("OUT_DIR".to_string(), out_dir));
179 } 262 }
180 } 263 }
181 Message::CompilerMessage(message) => {
182 progress(message.target.name.clone());
183 }
184 Message::BuildFinished(_) => {}
185 Message::TextLine(_) => {}
186 _ => {}
187 } 264 }
188 }
189 265
190 for package in packages { 266 if !output.status.success() {
191 let build_data = res.entry(package.id.repr.clone()).or_default(); 267 let mut stderr = String::from_utf8(output.stderr).unwrap_or_default();
192 inject_cargo_env(package, build_data); 268 if stderr.is_empty() {
193 if let Some(out_dir) = &build_data.out_dir { 269 stderr = "cargo check failed".to_string();
194 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
195 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
196 build_data.envs.push(("OUT_DIR".to_string(), out_dir));
197 } 270 }
271 res.error = Some(stderr)
198 } 272 }
199 }
200 273
201 Ok(res) 274 Ok(res)
275 }
202} 276}
203 277
204// FIXME: File a better way to know if it is a dylib 278// FIXME: File a better way to know if it is a dylib
@@ -212,7 +286,7 @@ fn is_dylib(path: &Utf8Path) -> bool {
212/// Recreates the compile-time environment variables that Cargo sets. 286/// Recreates the compile-time environment variables that Cargo sets.
213/// 287///
214/// Should be synced with <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates> 288/// Should be synced with <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates>
215fn inject_cargo_env(package: &cargo_metadata::Package, build_data: &mut BuildData) { 289fn inject_cargo_env(package: &cargo_metadata::Package, build_data: &mut PackageBuildData) {
216 let env = &mut build_data.envs; 290 let env = &mut build_data.envs;
217 291
218 // FIXME: Missing variables: 292 // FIXME: Missing variables:
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs
index 1b53fcc30..2fcd0f8fa 100644
--- a/crates/project_model/src/workspace.rs
+++ b/crates/project_model/src/workspace.rs
@@ -12,7 +12,7 @@ use proc_macro_api::ProcMacroClient;
12use rustc_hash::{FxHashMap, FxHashSet}; 12use rustc_hash::{FxHashMap, FxHashSet};
13 13
14use crate::{ 14use crate::{
15 build_data::{BuildData, BuildDataMap, BuildDataResult}, 15 build_data::{BuildDataResult, PackageBuildData, WorkspaceBuildData},
16 cargo_workspace, 16 cargo_workspace,
17 cfg_flag::CfgFlag, 17 cfg_flag::CfgFlag,
18 rustc_cfg, 18 rustc_cfg,
@@ -354,10 +354,10 @@ fn cargo_to_crate_graph(
354 proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>, 354 proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
355 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, 355 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
356 cargo: &CargoWorkspace, 356 cargo: &CargoWorkspace,
357 build_data_map: Option<&BuildDataMap>, 357 build_data_map: Option<&WorkspaceBuildData>,
358 sysroot: &Sysroot, 358 sysroot: &Sysroot,
359 rustc: &Option<CargoWorkspace>, 359 rustc: &Option<CargoWorkspace>,
360 rustc_build_data_map: Option<&BuildDataMap>, 360 rustc_build_data_map: Option<&WorkspaceBuildData>,
361) -> CrateGraph { 361) -> CrateGraph {
362 let _p = profile::span("cargo_to_crate_graph"); 362 let _p = profile::span("cargo_to_crate_graph");
363 let mut crate_graph = CrateGraph::default(); 363 let mut crate_graph = CrateGraph::default();
@@ -464,7 +464,7 @@ fn handle_rustc_crates(
464 rustc_workspace: &CargoWorkspace, 464 rustc_workspace: &CargoWorkspace,
465 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, 465 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
466 crate_graph: &mut CrateGraph, 466 crate_graph: &mut CrateGraph,
467 rustc_build_data_map: Option<&FxHashMap<String, BuildData>>, 467 rustc_build_data_map: Option<&WorkspaceBuildData>,
468 cfg_options: &CfgOptions, 468 cfg_options: &CfgOptions,
469 proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>, 469 proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
470 pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>, 470 pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>,
@@ -555,7 +555,7 @@ fn handle_rustc_crates(
555fn add_target_crate_root( 555fn add_target_crate_root(
556 crate_graph: &mut CrateGraph, 556 crate_graph: &mut CrateGraph,
557 pkg: &cargo_workspace::PackageData, 557 pkg: &cargo_workspace::PackageData,
558 build_data: Option<&BuildData>, 558 build_data: Option<&PackageBuildData>,
559 cfg_options: &CfgOptions, 559 cfg_options: &CfgOptions,
560 proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>, 560 proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
561 file_id: FileId, 561 file_id: FileId,