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.rs256
1 files changed, 148 insertions, 108 deletions
diff --git a/crates/project_model/src/build_data.rs b/crates/project_model/src/build_data.rs
index 0d4d39fef..7b88dca63 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,7 +12,8 @@ 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::{format_to, JodChild}; 15use serde::Deserialize;
16use stdx::format_to;
17 17
18use crate::{cfg_flag::CfgFlag, CargoConfig}; 18use crate::{cfg_flag::CfgFlag, CargoConfig};
19 19
@@ -58,12 +58,17 @@ impl PartialEq for BuildDataConfig {
58 58
59impl Eq for BuildDataConfig {} 59impl Eq for BuildDataConfig {}
60 60
61#[derive(Debug, Default)] 61#[derive(Debug)]
62pub struct BuildDataCollector { 62pub struct BuildDataCollector {
63 wrap_rustc: bool,
63 configs: FxHashMap<AbsPathBuf, BuildDataConfig>, 64 configs: FxHashMap<AbsPathBuf, BuildDataConfig>,
64} 65}
65 66
66impl BuildDataCollector { 67impl BuildDataCollector {
68 pub fn new(wrap_rustc: bool) -> Self {
69 Self { wrap_rustc, configs: FxHashMap::default() }
70 }
71
67 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) {
68 self.configs.insert(workspace_root.to_path_buf(), config); 73 self.configs.insert(workspace_root.to_path_buf(), config);
69 } 74 }
@@ -71,15 +76,14 @@ impl BuildDataCollector {
71 pub fn collect(&mut self, progress: &dyn Fn(String)) -> Result<BuildDataResult> { 76 pub fn collect(&mut self, progress: &dyn Fn(String)) -> Result<BuildDataResult> {
72 let mut res = BuildDataResult::default(); 77 let mut res = BuildDataResult::default();
73 for (path, config) in self.configs.iter() { 78 for (path, config) in self.configs.iter() {
74 res.per_workspace.insert( 79 let workspace_build_data = WorkspaceBuildData::collect(
75 path.clone(), 80 &config.cargo_toml,
76 collect_from_workspace( 81 &config.cargo_features,
77 &config.cargo_toml, 82 &config.packages,
78 &config.cargo_features, 83 self.wrap_rustc,
79 &config.packages, 84 progress,
80 progress, 85 )?;
81 )?, 86 res.per_workspace.insert(path.clone(), workspace_build_data);
82 );
83 } 87 }
84 Ok(res) 88 Ok(res)
85 } 89 }
@@ -120,119 +124,155 @@ impl BuildDataConfig {
120 } 124 }
121} 125}
122 126
123fn collect_from_workspace( 127impl WorkspaceBuildData {
124 cargo_toml: &AbsPath, 128 fn collect(
125 cargo_features: &CargoConfig, 129 cargo_toml: &AbsPath,
126 packages: &Vec<cargo_metadata::Package>, 130 cargo_features: &CargoConfig,
127 progress: &dyn Fn(String), 131 packages: &Vec<cargo_metadata::Package>,
128) -> Result<WorkspaceBuildData> { 132 wrap_rustc: bool,
129 let mut cmd = Command::new(toolchain::cargo()); 133 progress: &dyn Fn(String),
130 cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"]) 134 ) -> Result<WorkspaceBuildData> {
131 .arg(cargo_toml.as_ref()); 135 let mut cmd = Command::new(toolchain::cargo());
132 136
133 // --all-targets includes tests, benches and examples in addition to the 137 if wrap_rustc {
134 // 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
135 // flag below. 139 // that to compile only proc macros and build scripts during the initial
136 cmd.arg("--all-targets"); 140 // `cargo check`.
137 141 let myself = std::env::current_exe()?;
138 if let Some(target) = &cargo_features.target { 142 cmd.env("RUSTC_WRAPPER", myself);
139 cmd.args(&["--target", target]); 143 cmd.env("RA_RUSTC_WRAPPER", "1");
140 } 144 }
145
146 cmd.args(&["check", "--quiet", "--workspace", "--message-format=json", "--manifest-path"])
147 .arg(cargo_toml.as_ref());
141 148
142 if cargo_features.all_features { 149 // --all-targets includes tests, benches and examples in addition to the
143 cmd.arg("--all-features"); 150 // default lib and bins. This is an independent concept from the --targets
144 } else { 151 // flag below.
145 if cargo_features.no_default_features { 152 cmd.arg("--all-targets");
146 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` 153
147 // https://github.com/oli-obk/cargo_metadata/issues/79 154 if let Some(target) = &cargo_features.target {
148 cmd.arg("--no-default-features"); 155 cmd.args(&["--target", target]);
149 } 156 }
150 if !cargo_features.features.is_empty() { 157
151 cmd.arg("--features"); 158 if cargo_features.all_features {
152 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 }
153 } 170 }
154 }
155 171
156 cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null()); 172 cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
157 173
158 let mut child = cmd.spawn().map(JodChild)?; 174 let mut res = WorkspaceBuildData::default();
159 let child_stdout = child.stdout.take().unwrap(); 175
160 let stdout = BufReader::new(child_stdout); 176 let mut callback_err = None;
161 177 let output = stdx::process::streaming_output(
162 let mut res = WorkspaceBuildData::default(); 178 cmd,
163 for message in cargo_metadata::Message::parse_stream(stdout).flatten() { 179 &mut |line| {
164 match message { 180 if callback_err.is_some() {
165 Message::BuildScriptExecuted(BuildScript { 181 return;
166 package_id, out_dir, cfgs, env, ..
167 }) => {
168 let cfgs = {
169 let mut acc = Vec::new();
170 for cfg in cfgs {
171 match cfg.parse::<CfgFlag>() {
172 Ok(it) => acc.push(it),
173 Err(err) => {
174 anyhow::bail!("invalid cfg from cargo-metadata: {}", err)
175 }
176 };
177 }
178 acc
179 };
180 let package_build_data =
181 res.per_package.entry(package_id.repr.clone()).or_default();
182 // cargo_metadata crate returns default (empty) path for
183 // older cargos, which is not absolute, so work around that.
184 if !out_dir.as_str().is_empty() {
185 let out_dir = AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string()));
186 package_build_data.out_dir = Some(out_dir);
187 package_build_data.cfgs = cfgs;
188 } 182 }
189 183
190 package_build_data.envs = env; 184 // Copy-pasted from existing cargo_metadata. It seems like we
191 } 185 // should be using sered_stacker here?
192 Message::CompilerArtifact(message) => { 186 let mut deserializer = serde_json::Deserializer::from_str(&line);
193 progress(format!("metadata {}", message.target.name)); 187 deserializer.disable_recursion_limit();
194 188 let message = Message::deserialize(&mut deserializer)
195 if message.target.kind.contains(&"proc-macro".to_string()) { 189 .unwrap_or(Message::TextLine(line.to_string()));
196 let package_id = message.package_id; 190
197 // Skip rmeta file 191 match message {
198 if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) { 192 Message::BuildScriptExecuted(BuildScript {
199 let filename = AbsPathBuf::assert(PathBuf::from(&filename)); 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 };
212 }
213 acc
214 };
200 let package_build_data = 215 let package_build_data =
201 res.per_package.entry(package_id.repr.clone()).or_default(); 216 res.per_package.entry(package_id.repr.clone()).or_default();
202 package_build_data.proc_macro_dylib_path = Some(filename); 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;
227 }
228 Message::CompilerArtifact(message) => {
229 progress(format!("metadata {}", message.target.name));
230
231 if message.target.kind.contains(&"proc-macro".to_string()) {
232 let package_id = message.package_id;
233 // Skip rmeta file
234 if let Some(filename) =
235 message.filenames.iter().find(|name| is_dylib(name))
236 {
237 let filename = AbsPathBuf::assert(PathBuf::from(&filename));
238 let package_build_data =
239 res.per_package.entry(package_id.repr.clone()).or_default();
240 package_build_data.proc_macro_dylib_path = Some(filename);
241 }
242 }
243 }
244 Message::CompilerMessage(message) => {
245 progress(message.target.name.clone());
203 } 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));
204 } 262 }
205 } 263 }
206 Message::CompilerMessage(message) => {
207 progress(message.target.name.clone());
208 }
209 Message::BuildFinished(_) => {}
210 Message::TextLine(_) => {}
211 _ => {}
212 } 264 }
213 }
214 265
215 for package in packages { 266 if !output.status.success() {
216 let package_build_data = res.per_package.entry(package.id.repr.clone()).or_default(); 267 let mut stderr = String::from_utf8(output.stderr).unwrap_or_default();
217 inject_cargo_env(package, package_build_data); 268 if stderr.is_empty() {
218 if let Some(out_dir) = &package_build_data.out_dir { 269 stderr = "cargo check failed".to_string();
219 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
220 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
221 package_build_data.envs.push(("OUT_DIR".to_string(), out_dir));
222 } 270 }
271 res.error = Some(stderr)
223 } 272 }
224 }
225 273
226 let output = child.into_inner().wait_with_output()?; 274 Ok(res)
227 if !output.status.success() {
228 let mut stderr = String::from_utf8(output.stderr).unwrap_or_default();
229 if stderr.is_empty() {
230 stderr = "cargo check failed".to_string();
231 }
232 res.error = Some(stderr)
233 } 275 }
234
235 Ok(res)
236} 276}
237 277
238// 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