aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_cargo_watch/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_cargo_watch/src/lib.rs')
-rw-r--r--crates/ra_cargo_watch/src/lib.rs175
1 files changed, 96 insertions, 79 deletions
diff --git a/crates/ra_cargo_watch/src/lib.rs b/crates/ra_cargo_watch/src/lib.rs
index 1a6926db3..71aa28f0a 100644
--- a/crates/ra_cargo_watch/src/lib.rs
+++ b/crates/ra_cargo_watch/src/lib.rs
@@ -9,8 +9,8 @@ use lsp_types::{
9}; 9};
10use std::{ 10use std::{
11 io::{BufRead, BufReader}, 11 io::{BufRead, BufReader},
12 path::PathBuf, 12 path::{Path, PathBuf},
13 process::{Command, Stdio}, 13 process::{Child, Command, Stdio},
14 thread::JoinHandle, 14 thread::JoinHandle,
15 time::Instant, 15 time::Instant,
16}; 16};
@@ -246,18 +246,71 @@ enum CheckEvent {
246 End, 246 End,
247} 247}
248 248
249pub fn run_cargo(
250 args: &[String],
251 current_dir: Option<&Path>,
252 mut on_message: impl FnMut(cargo_metadata::Message) -> bool,
253) -> Child {
254 let mut command = Command::new("cargo");
255 if let Some(current_dir) = current_dir {
256 command.current_dir(current_dir);
257 }
258
259 let mut child = command
260 .args(args)
261 .stdout(Stdio::piped())
262 .stderr(Stdio::null())
263 .stdin(Stdio::null())
264 .spawn()
265 .expect("couldn't launch cargo");
266
267 // We manually read a line at a time, instead of using serde's
268 // stream deserializers, because the deserializer cannot recover
269 // from an error, resulting in it getting stuck, because we try to
270 // be resillient against failures.
271 //
272 // Because cargo only outputs one JSON object per line, we can
273 // simply skip a line if it doesn't parse, which just ignores any
274 // erroneus output.
275 let stdout = BufReader::new(child.stdout.take().unwrap());
276 for line in stdout.lines() {
277 let line = match line {
278 Ok(line) => line,
279 Err(err) => {
280 log::error!("Couldn't read line from cargo: {}", err);
281 continue;
282 }
283 };
284
285 let message = serde_json::from_str::<cargo_metadata::Message>(&line);
286 let message = match message {
287 Ok(message) => message,
288 Err(err) => {
289 log::error!("Invalid json from cargo check, ignoring ({}): {:?} ", err, line);
290 continue;
291 }
292 };
293
294 if !on_message(message) {
295 break;
296 }
297 }
298
299 child
300}
301
249impl WatchThread { 302impl WatchThread {
250 fn dummy() -> WatchThread { 303 fn dummy() -> WatchThread {
251 WatchThread { handle: None, message_recv: never() } 304 WatchThread { handle: None, message_recv: never() }
252 } 305 }
253 306
254 fn new(options: &CheckOptions, workspace_root: &PathBuf) -> WatchThread { 307 fn new(options: &CheckOptions, workspace_root: &Path) -> WatchThread {
255 let mut args: Vec<String> = vec![ 308 let mut args: Vec<String> = vec![
256 options.command.clone(), 309 options.command.clone(),
257 "--workspace".to_string(), 310 "--workspace".to_string(),
258 "--message-format=json".to_string(), 311 "--message-format=json".to_string(),
259 "--manifest-path".to_string(), 312 "--manifest-path".to_string(),
260 format!("{}/Cargo.toml", workspace_root.to_string_lossy()), 313 format!("{}/Cargo.toml", workspace_root.display()),
261 ]; 314 ];
262 if options.all_targets { 315 if options.all_targets {
263 args.push("--all-targets".to_string()); 316 args.push("--all-targets".to_string());
@@ -265,83 +318,47 @@ impl WatchThread {
265 args.extend(options.args.iter().cloned()); 318 args.extend(options.args.iter().cloned());
266 319
267 let (message_send, message_recv) = unbounded(); 320 let (message_send, message_recv) = unbounded();
268 let enabled = options.enable; 321 let workspace_root = workspace_root.to_owned();
269 let handle = std::thread::spawn(move || { 322 let handle = if options.enable {
270 if !enabled { 323 Some(std::thread::spawn(move || {
271 return; 324 // If we trigger an error here, we will do so in the loop instead,
272 } 325 // which will break out of the loop, and continue the shutdown
273 326 let _ = message_send.send(CheckEvent::Begin);
274 let mut command = Command::new("cargo") 327
275 .args(&args) 328 let mut child = run_cargo(&args, Some(&workspace_root), |message| {
276 .stdout(Stdio::piped()) 329 // Skip certain kinds of messages to only spend time on what's useful
277 .stderr(Stdio::null()) 330 match &message {
278 .stdin(Stdio::null()) 331 Message::CompilerArtifact(artifact) if artifact.fresh => return true,
279 .spawn() 332 Message::BuildScriptExecuted(_) => return true,
280 .expect("couldn't launch cargo"); 333 Message::Unknown => return true,
281 334 _ => {}
282 // If we trigger an error here, we will do so in the loop instead,
283 // which will break out of the loop, and continue the shutdown
284 let _ = message_send.send(CheckEvent::Begin);
285
286 // We manually read a line at a time, instead of using serde's
287 // stream deserializers, because the deserializer cannot recover
288 // from an error, resulting in it getting stuck, because we try to
289 // be resillient against failures.
290 //
291 // Because cargo only outputs one JSON object per line, we can
292 // simply skip a line if it doesn't parse, which just ignores any
293 // erroneus output.
294 let stdout = BufReader::new(command.stdout.take().unwrap());
295 for line in stdout.lines() {
296 let line = match line {
297 Ok(line) => line,
298 Err(err) => {
299 log::error!("Couldn't read line from cargo: {}", err);
300 continue;
301 }
302 };
303
304 let message = serde_json::from_str::<cargo_metadata::Message>(&line);
305 let message = match message {
306 Ok(message) => message,
307 Err(err) => {
308 log::error!(
309 "Invalid json from cargo check, ignoring ({}): {:?} ",
310 err,
311 line
312 );
313 continue;
314 } 335 }
315 };
316
317 // Skip certain kinds of messages to only spend time on what's useful
318 match &message {
319 Message::CompilerArtifact(artifact) if artifact.fresh => continue,
320 Message::BuildScriptExecuted(_) => continue,
321 Message::Unknown => continue,
322 _ => {}
323 }
324 336
325 match message_send.send(CheckEvent::Msg(message)) { 337 match message_send.send(CheckEvent::Msg(message)) {
326 Ok(()) => {} 338 Ok(()) => {}
327 Err(_err) => { 339 Err(_err) => {
328 // The send channel was closed, so we want to shutdown 340 // The send channel was closed, so we want to shutdown
329 break; 341 return false;
330 } 342 }
331 } 343 };
332 } 344
333 345 true
334 // We can ignore any error here, as we are already in the progress 346 });
335 // of shutting down. 347
336 let _ = message_send.send(CheckEvent::End); 348 // We can ignore any error here, as we are already in the progress
337 349 // of shutting down.
338 // It is okay to ignore the result, as it only errors if the process is already dead 350 let _ = message_send.send(CheckEvent::End);
339 let _ = command.kill(); 351
340 352 // It is okay to ignore the result, as it only errors if the process is already dead
341 // Again, we don't care about the exit status so just ignore the result 353 let _ = child.kill();
342 let _ = command.wait(); 354
343 }); 355 // Again, we don't care about the exit status so just ignore the result
344 WatchThread { handle: Some(handle), message_recv } 356 let _ = child.wait();
357 }))
358 } else {
359 None
360 };
361 WatchThread { handle, message_recv }
345 } 362 }
346} 363}
347 364