diff options
Diffstat (limited to 'crates/project_model')
-rw-r--r-- | crates/project_model/src/build_data.rs | 294 | ||||
-rw-r--r-- | crates/project_model/src/workspace.rs | 10 |
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 | ||
3 | use std::{ | 3 | use 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}; | |||
13 | use itertools::Itertools; | 12 | use itertools::Itertools; |
14 | use paths::{AbsPath, AbsPathBuf}; | 13 | use paths::{AbsPath, AbsPathBuf}; |
15 | use rustc_hash::FxHashMap; | 14 | use rustc_hash::FxHashMap; |
16 | use stdx::JodChild; | 15 | use serde::Deserialize; |
16 | use stdx::format_to; | ||
17 | 17 | ||
18 | use crate::{cfg_flag::CfgFlag, CargoConfig}; | 18 | use crate::{cfg_flag::CfgFlag, CargoConfig}; |
19 | 19 | ||
20 | #[derive(Debug, Clone, Default, PartialEq, Eq)] | 20 | #[derive(Debug, Clone, Default, PartialEq, Eq)] |
21 | pub(crate) struct BuildData { | 21 | pub(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)] | ||
36 | pub(crate) struct WorkspaceBuildData { | ||
37 | per_package: FxHashMap<String, PackageBuildData>, | ||
38 | error: Option<String>, | ||
39 | } | ||
40 | |||
41 | #[derive(Debug, Default, PartialEq, Eq, Clone)] | ||
42 | pub struct BuildDataResult { | ||
43 | per_workspace: FxHashMap<AbsPathBuf, WorkspaceBuildData>, | ||
44 | } | ||
45 | |||
35 | #[derive(Clone, Debug)] | 46 | #[derive(Clone, Debug)] |
36 | pub(crate) struct BuildDataConfig { | 47 | pub(crate) struct BuildDataConfig { |
37 | cargo_toml: AbsPathBuf, | 48 | cargo_toml: AbsPathBuf, |
@@ -47,19 +58,17 @@ impl PartialEq for BuildDataConfig { | |||
47 | 58 | ||
48 | impl Eq for BuildDataConfig {} | 59 | impl Eq for BuildDataConfig {} |
49 | 60 | ||
50 | #[derive(Debug, Default)] | 61 | #[derive(Debug)] |
51 | pub struct BuildDataCollector { | 62 | pub 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)] | ||
56 | pub struct BuildDataResult { | ||
57 | data: FxHashMap<AbsPathBuf, BuildDataMap>, | ||
58 | } | ||
59 | |||
60 | pub(crate) type BuildDataMap = FxHashMap<String, BuildData>; | ||
61 | |||
62 | impl BuildDataCollector { | 67 | impl 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 | ||
92 | impl WorkspaceBuildData { | ||
93 | pub(crate) fn get(&self, package_id: &str) -> Option<&PackageBuildData> { | ||
94 | self.per_package.get(package_id) | ||
95 | } | ||
96 | } | ||
97 | |||
84 | impl BuildDataResult { | 98 | impl 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 | ||
100 | fn collect_from_workspace( | 127 | impl 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> |
215 | fn inject_cargo_env(package: &cargo_metadata::Package, build_data: &mut BuildData) { | 289 | fn 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; | |||
12 | use rustc_hash::{FxHashMap, FxHashSet}; | 12 | use rustc_hash::{FxHashMap, FxHashSet}; |
13 | 13 | ||
14 | use crate::{ | 14 | use 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( | |||
555 | fn add_target_crate_root( | 555 | fn 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, |