diff options
Diffstat (limited to 'crates/ra_cargo_watch')
-rw-r--r-- | crates/ra_cargo_watch/src/lib.rs | 78 |
1 files changed, 43 insertions, 35 deletions
diff --git a/crates/ra_cargo_watch/src/lib.rs b/crates/ra_cargo_watch/src/lib.rs index deb763af9..35b0cb608 100644 --- a/crates/ra_cargo_watch/src/lib.rs +++ b/crates/ra_cargo_watch/src/lib.rs | |||
@@ -8,9 +8,10 @@ use lsp_types::{ | |||
8 | WorkDoneProgressEnd, WorkDoneProgressReport, | 8 | WorkDoneProgressEnd, WorkDoneProgressReport, |
9 | }; | 9 | }; |
10 | use std::{ | 10 | use std::{ |
11 | error, fmt, | ||
11 | io::{BufRead, BufReader}, | 12 | io::{BufRead, BufReader}, |
12 | path::{Path, PathBuf}, | 13 | path::{Path, PathBuf}, |
13 | process::{Child, Command, Stdio}, | 14 | process::{Command, Stdio}, |
14 | thread::JoinHandle, | 15 | thread::JoinHandle, |
15 | time::Instant, | 16 | time::Instant, |
16 | }; | 17 | }; |
@@ -70,10 +71,10 @@ impl std::ops::Drop for CheckWatcher { | |||
70 | fn drop(&mut self) { | 71 | fn drop(&mut self) { |
71 | if let Some(handle) = self.handle.take() { | 72 | if let Some(handle) = self.handle.take() { |
72 | // Take the sender out of the option | 73 | // Take the sender out of the option |
73 | let recv = self.cmd_send.take(); | 74 | let cmd_send = self.cmd_send.take(); |
74 | 75 | ||
75 | // Dropping the sender finishes the thread loop | 76 | // Dropping the sender finishes the thread loop |
76 | drop(recv); | 77 | drop(cmd_send); |
77 | 78 | ||
78 | // Join the thread, it should finish shortly. We don't really care | 79 | // Join the thread, it should finish shortly. We don't really care |
79 | // whether it panicked, so it is safe to ignore the result | 80 | // whether it panicked, so it is safe to ignore the result |
@@ -246,11 +247,21 @@ enum CheckEvent { | |||
246 | End, | 247 | End, |
247 | } | 248 | } |
248 | 249 | ||
250 | #[derive(Debug)] | ||
251 | pub struct CargoError(String); | ||
252 | |||
253 | impl fmt::Display for CargoError { | ||
254 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
255 | write!(f, "Cargo failed: {}", self.0) | ||
256 | } | ||
257 | } | ||
258 | impl error::Error for CargoError {} | ||
259 | |||
249 | pub fn run_cargo( | 260 | pub fn run_cargo( |
250 | args: &[String], | 261 | args: &[String], |
251 | current_dir: Option<&Path>, | 262 | current_dir: Option<&Path>, |
252 | on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool, | 263 | on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool, |
253 | ) -> Child { | 264 | ) -> Result<(), CargoError> { |
254 | let mut command = Command::new("cargo"); | 265 | let mut command = Command::new("cargo"); |
255 | if let Some(current_dir) = current_dir { | 266 | if let Some(current_dir) = current_dir { |
256 | command.current_dir(current_dir); | 267 | command.current_dir(current_dir); |
@@ -259,7 +270,7 @@ pub fn run_cargo( | |||
259 | let mut child = command | 270 | let mut child = command |
260 | .args(args) | 271 | .args(args) |
261 | .stdout(Stdio::piped()) | 272 | .stdout(Stdio::piped()) |
262 | .stderr(Stdio::piped()) | 273 | .stderr(Stdio::null()) |
263 | .stdin(Stdio::null()) | 274 | .stdin(Stdio::null()) |
264 | .spawn() | 275 | .spawn() |
265 | .expect("couldn't launch cargo"); | 276 | .expect("couldn't launch cargo"); |
@@ -273,6 +284,8 @@ pub fn run_cargo( | |||
273 | // simply skip a line if it doesn't parse, which just ignores any | 284 | // simply skip a line if it doesn't parse, which just ignores any |
274 | // erroneus output. | 285 | // erroneus output. |
275 | let stdout = BufReader::new(child.stdout.take().unwrap()); | 286 | let stdout = BufReader::new(child.stdout.take().unwrap()); |
287 | let mut read_at_least_one_message = false; | ||
288 | |||
276 | for line in stdout.lines() { | 289 | for line in stdout.lines() { |
277 | let line = match line { | 290 | let line = match line { |
278 | Ok(line) => line, | 291 | Ok(line) => line, |
@@ -291,12 +304,27 @@ pub fn run_cargo( | |||
291 | } | 304 | } |
292 | }; | 305 | }; |
293 | 306 | ||
307 | read_at_least_one_message = true; | ||
308 | |||
294 | if !on_message(message) { | 309 | if !on_message(message) { |
295 | break; | 310 | break; |
296 | } | 311 | } |
297 | } | 312 | } |
298 | 313 | ||
299 | child | 314 | // It is okay to ignore the result, as it only errors if the process is already dead |
315 | let _ = child.kill(); | ||
316 | |||
317 | let err_msg = match child.wait() { | ||
318 | Ok(exit_code) if !exit_code.success() && !read_at_least_one_message => { | ||
319 | // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment: | ||
320 | // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298 | ||
321 | format!("the command produced no valid metadata:\n cargo {}", args.join(" ")) | ||
322 | } | ||
323 | Err(err) => format!("io error: {:?}", err), | ||
324 | Ok(_) => return Ok(()), | ||
325 | }; | ||
326 | |||
327 | Err(CargoError(err_msg)) | ||
300 | } | 328 | } |
301 | 329 | ||
302 | impl WatchThread { | 330 | impl WatchThread { |
@@ -325,7 +353,7 @@ impl WatchThread { | |||
325 | // which will break out of the loop, and continue the shutdown | 353 | // which will break out of the loop, and continue the shutdown |
326 | let _ = message_send.send(CheckEvent::Begin); | 354 | let _ = message_send.send(CheckEvent::Begin); |
327 | 355 | ||
328 | let mut child = run_cargo(&args, Some(&workspace_root), &mut |message| { | 356 | let res = run_cargo(&args, Some(&workspace_root), &mut |message| { |
329 | // Skip certain kinds of messages to only spend time on what's useful | 357 | // Skip certain kinds of messages to only spend time on what's useful |
330 | match &message { | 358 | match &message { |
331 | Message::CompilerArtifact(artifact) if artifact.fresh => return true, | 359 | Message::CompilerArtifact(artifact) if artifact.fresh => return true, |
@@ -334,39 +362,19 @@ impl WatchThread { | |||
334 | _ => {} | 362 | _ => {} |
335 | } | 363 | } |
336 | 364 | ||
337 | match message_send.send(CheckEvent::Msg(message)) { | 365 | // if the send channel was closed, so we want to shutdown |
338 | Ok(()) => {} | 366 | message_send.send(CheckEvent::Msg(message)).is_ok() |
339 | Err(_err) => { | ||
340 | // The send channel was closed, so we want to shutdown | ||
341 | return false; | ||
342 | } | ||
343 | }; | ||
344 | |||
345 | true | ||
346 | }); | 367 | }); |
347 | 368 | ||
369 | if let Err(err) = res { | ||
370 | // FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>` | ||
371 | // to display user-caused misconfiguration errors instead of just logging them here | ||
372 | log::error!("Cargo watcher failed {:?}", err); | ||
373 | } | ||
374 | |||
348 | // We can ignore any error here, as we are already in the progress | 375 | // We can ignore any error here, as we are already in the progress |
349 | // of shutting down. | 376 | // of shutting down. |
350 | let _ = message_send.send(CheckEvent::End); | 377 | let _ = message_send.send(CheckEvent::End); |
351 | |||
352 | // It is okay to ignore the result, as it only errors if the process is already dead | ||
353 | let _ = child.kill(); | ||
354 | |||
355 | // Again, we are resilient to errors, so we don't try to panic here | ||
356 | match child.wait_with_output() { | ||
357 | Ok(output) => match output.status.code() { | ||
358 | Some(0) | None => {} | ||
359 | Some(exit_code) => { | ||
360 | let output = | ||
361 | std::str::from_utf8(&output.stderr).unwrap_or("<bad utf8 output>"); | ||
362 | |||
363 | if !output.contains("could not compile") { | ||
364 | log::error!("Cargo failed with exit code {} {}", exit_code, output); | ||
365 | } | ||
366 | } | ||
367 | }, | ||
368 | Err(err) => log::error!("Cargo io error: {:?}", err), | ||
369 | } | ||
370 | })) | 378 | })) |
371 | } else { | 379 | } else { |
372 | None | 380 | None |