diff options
Diffstat (limited to 'crates/ra_cargo_watch')
-rw-r--r-- | crates/ra_cargo_watch/src/lib.rs | 175 |
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 | }; |
10 | use std::{ | 10 | use 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 | ||
249 | pub 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 | |||
249 | impl WatchThread { | 302 | impl 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 | ||