Skip to content

Commit 74711de

Browse files
bors[bot]matklad
andauthored
Merge #8375
8375: feat: show errors from `cargo metadata` and initial `cargo check` in the status bar r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
2 parents 8e768a5 + de33702 commit 74711de

File tree

5 files changed

+110
-42
lines changed

5 files changed

+110
-42
lines changed

crates/paths/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Thin wrappers around `std::path`, distinguishing between absolute and
22
//! relative paths.
33
use std::{
4+
borrow::Borrow,
45
convert::{TryFrom, TryInto},
56
ops,
67
path::{Component, Path, PathBuf},
@@ -35,6 +36,12 @@ impl AsRef<AbsPath> for AbsPathBuf {
3536
}
3637
}
3738

39+
impl Borrow<AbsPath> for AbsPathBuf {
40+
fn borrow(&self) -> &AbsPath {
41+
self.as_path()
42+
}
43+
}
44+
3845
impl TryFrom<PathBuf> for AbsPathBuf {
3946
type Error = PathBuf;
4047
fn try_from(path_buf: PathBuf) -> Result<AbsPathBuf, PathBuf> {

crates/project_model/src/build_data.rs

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ use cargo_metadata::{BuildScript, Message};
1313
use itertools::Itertools;
1414
use paths::{AbsPath, AbsPathBuf};
1515
use rustc_hash::FxHashMap;
16-
use stdx::JodChild;
16+
use stdx::{format_to, JodChild};
1717

1818
use crate::{cfg_flag::CfgFlag, CargoConfig};
1919

2020
#[derive(Debug, Clone, Default, PartialEq, Eq)]
21-
pub(crate) struct BuildData {
21+
pub(crate) struct PackageBuildData {
2222
/// List of config flags defined by this package's build script
2323
pub(crate) cfgs: Vec<CfgFlag>,
2424
/// List of cargo-related environment variables with their value
@@ -32,6 +32,17 @@ pub(crate) struct BuildData {
3232
pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
3333
}
3434

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+
3546
#[derive(Clone, Debug)]
3647
pub(crate) struct BuildDataConfig {
3748
cargo_toml: AbsPathBuf,
@@ -52,13 +63,6 @@ pub struct BuildDataCollector {
5263
configs: FxHashMap<AbsPathBuf, BuildDataConfig>,
5364
}
5465

55-
#[derive(Debug, Default, PartialEq, Eq, Clone)]
56-
pub struct BuildDataResult {
57-
data: FxHashMap<AbsPathBuf, BuildDataMap>,
58-
}
59-
60-
pub(crate) type BuildDataMap = FxHashMap<String, BuildData>;
61-
6266
impl BuildDataCollector {
6367
pub(crate) fn add_config(&mut self, workspace_root: &AbsPath, config: BuildDataConfig) {
6468
self.configs.insert(workspace_root.to_path_buf(), config);
@@ -67,7 +71,7 @@ impl BuildDataCollector {
6771
pub fn collect(&mut self, progress: &dyn Fn(String)) -> Result<BuildDataResult> {
6872
let mut res = BuildDataResult::default();
6973
for (path, config) in self.configs.iter() {
70-
res.data.insert(
74+
res.per_workspace.insert(
7175
path.clone(),
7276
collect_from_workspace(
7377
&config.cargo_toml,
@@ -81,9 +85,28 @@ impl BuildDataCollector {
8185
}
8286
}
8387

88+
impl WorkspaceBuildData {
89+
pub(crate) fn get(&self, package_id: &str) -> Option<&PackageBuildData> {
90+
self.per_package.get(package_id)
91+
}
92+
}
93+
8494
impl BuildDataResult {
85-
pub(crate) fn get(&self, root: &AbsPath) -> Option<&BuildDataMap> {
86-
self.data.get(&root.to_path_buf())
95+
pub(crate) fn get(&self, workspace_root: &AbsPath) -> Option<&WorkspaceBuildData> {
96+
self.per_workspace.get(workspace_root)
97+
}
98+
pub fn error(&self) -> Option<String> {
99+
let mut buf = String::new();
100+
for (_workspace_root, build_data) in &self.per_workspace {
101+
if let Some(err) = &build_data.error {
102+
format_to!(buf, "cargo check failed:\n{}", err);
103+
}
104+
}
105+
if buf.is_empty() {
106+
return None;
107+
}
108+
109+
Some(buf)
87110
}
88111
}
89112

@@ -102,7 +125,7 @@ fn collect_from_workspace(
102125
cargo_features: &CargoConfig,
103126
packages: &Vec<cargo_metadata::Package>,
104127
progress: &dyn Fn(String),
105-
) -> Result<BuildDataMap> {
128+
) -> Result<WorkspaceBuildData> {
106129
let mut cmd = Command::new(toolchain::cargo());
107130
cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"])
108131
.arg(cargo_toml.as_ref());
@@ -130,13 +153,13 @@ fn collect_from_workspace(
130153
}
131154
}
132155

133-
cmd.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null());
156+
cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
134157

135158
let mut child = cmd.spawn().map(JodChild)?;
136159
let child_stdout = child.stdout.take().unwrap();
137160
let stdout = BufReader::new(child_stdout);
138161

139-
let mut res = BuildDataMap::default();
162+
let mut res = WorkspaceBuildData::default();
140163
for message in cargo_metadata::Message::parse_stream(stdout).flatten() {
141164
match message {
142165
Message::BuildScriptExecuted(BuildScript {
@@ -154,16 +177,17 @@ fn collect_from_workspace(
154177
}
155178
acc
156179
};
157-
let res = res.entry(package_id.repr.clone()).or_default();
180+
let package_build_data =
181+
res.per_package.entry(package_id.repr.clone()).or_default();
158182
// cargo_metadata crate returns default (empty) path for
159183
// older cargos, which is not absolute, so work around that.
160184
if !out_dir.as_str().is_empty() {
161185
let out_dir = AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string()));
162-
res.out_dir = Some(out_dir);
163-
res.cfgs = cfgs;
186+
package_build_data.out_dir = Some(out_dir);
187+
package_build_data.cfgs = cfgs;
164188
}
165189

166-
res.envs = env;
190+
package_build_data.envs = env;
167191
}
168192
Message::CompilerArtifact(message) => {
169193
progress(format!("metadata {}", message.target.name));
@@ -173,8 +197,9 @@ fn collect_from_workspace(
173197
// Skip rmeta file
174198
if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) {
175199
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);
200+
let package_build_data =
201+
res.per_package.entry(package_id.repr.clone()).or_default();
202+
package_build_data.proc_macro_dylib_path = Some(filename);
178203
}
179204
}
180205
}
@@ -188,16 +213,25 @@ fn collect_from_workspace(
188213
}
189214

190215
for package in packages {
191-
let build_data = res.entry(package.id.repr.clone()).or_default();
192-
inject_cargo_env(package, build_data);
193-
if let Some(out_dir) = &build_data.out_dir {
216+
let package_build_data = res.per_package.entry(package.id.repr.clone()).or_default();
217+
inject_cargo_env(package, package_build_data);
218+
if let Some(out_dir) = &package_build_data.out_dir {
194219
// NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
195220
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));
221+
package_build_data.envs.push(("OUT_DIR".to_string(), out_dir));
197222
}
198223
}
199224
}
200225

226+
let output = child.into_inner().wait_with_output()?;
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+
}
234+
201235
Ok(res)
202236
}
203237

@@ -212,7 +246,7 @@ fn is_dylib(path: &Utf8Path) -> bool {
212246
/// Recreates the compile-time environment variables that Cargo sets.
213247
///
214248
/// 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) {
249+
fn inject_cargo_env(package: &cargo_metadata::Package, build_data: &mut PackageBuildData) {
216250
let env = &mut build_data.envs;
217251

218252
// FIXME: Missing variables:

crates/project_model/src/workspace.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use proc_macro_api::ProcMacroClient;
1212
use rustc_hash::{FxHashMap, FxHashSet};
1313

1414
use crate::{
15-
build_data::{BuildData, BuildDataMap, BuildDataResult},
15+
build_data::{BuildDataResult, PackageBuildData, WorkspaceBuildData},
1616
cargo_workspace,
1717
cfg_flag::CfgFlag,
1818
rustc_cfg,
@@ -354,10 +354,10 @@ fn cargo_to_crate_graph(
354354
proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
355355
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
356356
cargo: &CargoWorkspace,
357-
build_data_map: Option<&BuildDataMap>,
357+
build_data_map: Option<&WorkspaceBuildData>,
358358
sysroot: &Sysroot,
359359
rustc: &Option<CargoWorkspace>,
360-
rustc_build_data_map: Option<&BuildDataMap>,
360+
rustc_build_data_map: Option<&WorkspaceBuildData>,
361361
) -> CrateGraph {
362362
let _p = profile::span("cargo_to_crate_graph");
363363
let mut crate_graph = CrateGraph::default();
@@ -464,7 +464,7 @@ fn handle_rustc_crates(
464464
rustc_workspace: &CargoWorkspace,
465465
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
466466
crate_graph: &mut CrateGraph,
467-
rustc_build_data_map: Option<&FxHashMap<String, BuildData>>,
467+
rustc_build_data_map: Option<&WorkspaceBuildData>,
468468
cfg_options: &CfgOptions,
469469
proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
470470
pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>,
@@ -555,7 +555,7 @@ fn handle_rustc_crates(
555555
fn add_target_crate_root(
556556
crate_graph: &mut CrateGraph,
557557
pkg: &cargo_workspace::PackageData,
558-
build_data: Option<&BuildData>,
558+
build_data: Option<&PackageBuildData>,
559559
cfg_options: &CfgOptions,
560560
proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
561561
file_id: FileId,

crates/rust-analyzer/src/reload.rs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -109,16 +109,22 @@ impl GlobalState {
109109
quiescent: self.is_quiescent(),
110110
message: None,
111111
};
112+
113+
if let Some(error) = self.build_data_error() {
114+
status.health = lsp_ext::Health::Warning;
115+
status.message = Some(error)
116+
}
112117
if !self.config.cargo_autoreload()
113118
&& self.is_quiescent()
114119
&& self.fetch_workspaces_queue.op_requested()
115120
{
116121
status.health = lsp_ext::Health::Warning;
117122
status.message = Some("Workspace reload required".to_string())
118123
}
119-
if let Some(error) = self.loading_error() {
124+
125+
if let Some(error) = self.fetch_workspace_error() {
120126
status.health = lsp_ext::Health::Error;
121-
status.message = Some(format!("Workspace reload failed: {}", error))
127+
status.message = Some(error)
122128
}
123129

124130
if self.last_reported_status.as_ref() != Some(&status) {
@@ -217,14 +223,19 @@ impl GlobalState {
217223
let _p = profile::span("GlobalState::switch_workspaces");
218224
log::info!("will switch workspaces");
219225

220-
if let Some(error_message) = self.loading_error() {
226+
if let Some(error_message) = self.fetch_workspace_error() {
221227
log::error!("failed to switch workspaces: {}", error_message);
222228
self.show_message(lsp_types::MessageType::Error, error_message);
223229
if !self.workspaces.is_empty() {
224230
return;
225231
}
226232
}
227233

234+
if let Some(error_message) = self.build_data_error() {
235+
log::error!("failed to switch build data: {}", error_message);
236+
self.show_message(lsp_types::MessageType::Error, error_message);
237+
}
238+
228239
let workspaces = self
229240
.fetch_workspaces_queue
230241
.last_op_result()
@@ -343,22 +354,30 @@ impl GlobalState {
343354
log::info!("did switch workspaces");
344355
}
345356

346-
fn loading_error(&self) -> Option<String> {
347-
let mut message = None;
357+
fn fetch_workspace_error(&self) -> Option<String> {
358+
let mut buf = String::new();
348359

349360
for ws in self.fetch_workspaces_queue.last_op_result() {
350361
if let Err(err) = ws {
351-
let message = message.get_or_insert_with(String::new);
352-
stdx::format_to!(message, "rust-analyzer failed to load workspace: {:#}\n", err);
362+
stdx::format_to!(buf, "rust-analyzer failed to load workspace: {:#}\n", err);
353363
}
354364
}
355365

356-
if let Some(Err(err)) = self.fetch_build_data_queue.last_op_result() {
357-
let message = message.get_or_insert_with(String::new);
358-
stdx::format_to!(message, "rust-analyzer failed to fetch build data: {:#}\n", err);
366+
if buf.is_empty() {
367+
return None;
359368
}
360369

361-
message
370+
Some(buf)
371+
}
372+
373+
fn build_data_error(&self) -> Option<String> {
374+
match self.fetch_build_data_queue.last_op_result() {
375+
Some(Err(err)) => {
376+
Some(format!("rust-analyzer failed to fetch build data: {:#}\n", err))
377+
}
378+
Some(Ok(data)) => data.error(),
379+
None => None,
380+
}
362381
}
363382

364383
fn reload_flycheck(&mut self) {

crates/stdx/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ where
178178
start..start + len
179179
}
180180

181+
#[repr(transparent)]
181182
pub struct JodChild(pub process::Child);
182183

183184
impl ops::Deref for JodChild {
@@ -200,6 +201,13 @@ impl Drop for JodChild {
200201
}
201202
}
202203

204+
impl JodChild {
205+
pub fn into_inner(self) -> process::Child {
206+
// SAFETY: repr transparent
207+
unsafe { std::mem::transmute::<JodChild, process::Child>(self) }
208+
}
209+
}
210+
203211
#[cfg(test)]
204212
mod tests {
205213
use super::*;

0 commit comments

Comments
 (0)