diff options
Diffstat (limited to 'crates/ra_flycheck/src/lib.rs')
-rw-r--r-- | crates/ra_flycheck/src/lib.rs | 123 |
1 files changed, 52 insertions, 71 deletions
diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs index 75aece45f..13494a731 100644 --- a/crates/ra_flycheck/src/lib.rs +++ b/crates/ra_flycheck/src/lib.rs | |||
@@ -4,9 +4,9 @@ | |||
4 | mod conv; | 4 | mod conv; |
5 | 5 | ||
6 | use std::{ | 6 | use std::{ |
7 | error, fmt, | 7 | env, |
8 | io::{BufRead, BufReader}, | 8 | io::{self, BufRead, BufReader}, |
9 | path::{Path, PathBuf}, | 9 | path::PathBuf, |
10 | process::{Command, Stdio}, | 10 | process::{Command, Stdio}, |
11 | time::Instant, | 11 | time::Instant, |
12 | }; | 12 | }; |
@@ -23,10 +23,9 @@ use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic}; | |||
23 | pub use crate::conv::url_from_path_with_drive_lowercasing; | 23 | pub use crate::conv::url_from_path_with_drive_lowercasing; |
24 | 24 | ||
25 | #[derive(Clone, Debug)] | 25 | #[derive(Clone, Debug)] |
26 | pub struct CheckConfig { | 26 | pub enum FlycheckConfig { |
27 | pub args: Vec<String>, | 27 | CargoCommand { command: String, all_targets: bool, extra_args: Vec<String> }, |
28 | pub command: String, | 28 | CustomCommand { command: String, args: Vec<String> }, |
29 | pub all_targets: bool, | ||
30 | } | 29 | } |
31 | 30 | ||
32 | /// Flycheck wraps the shared state and communication machinery used for | 31 | /// Flycheck wraps the shared state and communication machinery used for |
@@ -42,12 +41,11 @@ pub struct Flycheck { | |||
42 | } | 41 | } |
43 | 42 | ||
44 | impl Flycheck { | 43 | impl Flycheck { |
45 | pub fn new(config: CheckConfig, workspace_root: PathBuf) -> Flycheck { | 44 | pub fn new(config: FlycheckConfig, workspace_root: PathBuf) -> Flycheck { |
46 | let (task_send, task_recv) = unbounded::<CheckTask>(); | 45 | let (task_send, task_recv) = unbounded::<CheckTask>(); |
47 | let (cmd_send, cmd_recv) = unbounded::<CheckCommand>(); | 46 | let (cmd_send, cmd_recv) = unbounded::<CheckCommand>(); |
48 | let handle = jod_thread::spawn(move || { | 47 | let handle = jod_thread::spawn(move || { |
49 | let mut check = FlycheckThread::new(config, workspace_root); | 48 | FlycheckThread::new(config, workspace_root).run(&task_send, &cmd_recv); |
50 | check.run(&task_send, &cmd_recv); | ||
51 | }); | 49 | }); |
52 | Flycheck { task_recv, cmd_send, handle } | 50 | Flycheck { task_recv, cmd_send, handle } |
53 | } | 51 | } |
@@ -76,7 +74,7 @@ pub enum CheckCommand { | |||
76 | } | 74 | } |
77 | 75 | ||
78 | struct FlycheckThread { | 76 | struct FlycheckThread { |
79 | options: CheckConfig, | 77 | config: FlycheckConfig, |
80 | workspace_root: PathBuf, | 78 | workspace_root: PathBuf, |
81 | last_update_req: Option<Instant>, | 79 | last_update_req: Option<Instant>, |
82 | // XXX: drop order is significant | 80 | // XXX: drop order is significant |
@@ -90,9 +88,9 @@ struct FlycheckThread { | |||
90 | } | 88 | } |
91 | 89 | ||
92 | impl FlycheckThread { | 90 | impl FlycheckThread { |
93 | fn new(options: CheckConfig, workspace_root: PathBuf) -> FlycheckThread { | 91 | fn new(config: FlycheckConfig, workspace_root: PathBuf) -> FlycheckThread { |
94 | FlycheckThread { | 92 | FlycheckThread { |
95 | options, | 93 | config, |
96 | workspace_root, | 94 | workspace_root, |
97 | last_update_req: None, | 95 | last_update_req: None, |
98 | message_recv: never(), | 96 | message_recv: never(), |
@@ -216,27 +214,34 @@ impl FlycheckThread { | |||
216 | self.message_recv = never(); | 214 | self.message_recv = never(); |
217 | self.check_process = None; | 215 | self.check_process = None; |
218 | 216 | ||
219 | let mut args: Vec<String> = vec![ | 217 | let mut cmd = match &self.config { |
220 | self.options.command.clone(), | 218 | FlycheckConfig::CargoCommand { command, all_targets, extra_args } => { |
221 | "--workspace".to_string(), | 219 | let mut cmd = Command::new(cargo_binary()); |
222 | "--message-format=json".to_string(), | 220 | cmd.arg(command); |
223 | "--manifest-path".to_string(), | 221 | cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]); |
224 | format!("{}/Cargo.toml", self.workspace_root.display()), | 222 | cmd.arg(self.workspace_root.join("Cargo.toml")); |
225 | ]; | 223 | if *all_targets { |
226 | if self.options.all_targets { | 224 | cmd.arg("--all-targets"); |
227 | args.push("--all-targets".to_string()); | 225 | } |
228 | } | 226 | cmd.args(extra_args); |
229 | args.extend(self.options.args.iter().cloned()); | 227 | cmd |
228 | } | ||
229 | FlycheckConfig::CustomCommand { command, args } => { | ||
230 | let mut cmd = Command::new(command); | ||
231 | cmd.args(args); | ||
232 | cmd | ||
233 | } | ||
234 | }; | ||
235 | cmd.current_dir(&self.workspace_root); | ||
230 | 236 | ||
231 | let (message_send, message_recv) = unbounded(); | 237 | let (message_send, message_recv) = unbounded(); |
232 | let workspace_root = self.workspace_root.to_owned(); | ||
233 | self.message_recv = message_recv; | 238 | self.message_recv = message_recv; |
234 | self.check_process = Some(jod_thread::spawn(move || { | 239 | self.check_process = Some(jod_thread::spawn(move || { |
235 | // If we trigger an error here, we will do so in the loop instead, | 240 | // If we trigger an error here, we will do so in the loop instead, |
236 | // which will break out of the loop, and continue the shutdown | 241 | // which will break out of the loop, and continue the shutdown |
237 | let _ = message_send.send(CheckEvent::Begin); | 242 | let _ = message_send.send(CheckEvent::Begin); |
238 | 243 | ||
239 | let res = run_cargo(&args, Some(&workspace_root), &mut |message| { | 244 | let res = run_cargo(cmd, &mut |message| { |
240 | // Skip certain kinds of messages to only spend time on what's useful | 245 | // Skip certain kinds of messages to only spend time on what's useful |
241 | match &message { | 246 | match &message { |
242 | Message::CompilerArtifact(artifact) if artifact.fresh => return true, | 247 | Message::CompilerArtifact(artifact) if artifact.fresh => return true, |
@@ -274,33 +279,12 @@ enum CheckEvent { | |||
274 | End, | 279 | End, |
275 | } | 280 | } |
276 | 281 | ||
277 | #[derive(Debug)] | ||
278 | pub struct CargoError(String); | ||
279 | |||
280 | impl fmt::Display for CargoError { | ||
281 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
282 | write!(f, "Cargo failed: {}", self.0) | ||
283 | } | ||
284 | } | ||
285 | impl error::Error for CargoError {} | ||
286 | |||
287 | fn run_cargo( | 282 | fn run_cargo( |
288 | args: &[String], | 283 | mut command: Command, |
289 | current_dir: Option<&Path>, | ||
290 | on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool, | 284 | on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool, |
291 | ) -> Result<(), CargoError> { | 285 | ) -> io::Result<()> { |
292 | let mut command = Command::new("cargo"); | 286 | let mut child = |
293 | if let Some(current_dir) = current_dir { | 287 | command.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null()).spawn()?; |
294 | command.current_dir(current_dir); | ||
295 | } | ||
296 | |||
297 | let mut child = command | ||
298 | .args(args) | ||
299 | .stdout(Stdio::piped()) | ||
300 | .stderr(Stdio::null()) | ||
301 | .stdin(Stdio::null()) | ||
302 | .spawn() | ||
303 | .expect("couldn't launch cargo"); | ||
304 | 288 | ||
305 | // We manually read a line at a time, instead of using serde's | 289 | // We manually read a line at a time, instead of using serde's |
306 | // stream deserializers, because the deserializer cannot recover | 290 | // stream deserializers, because the deserializer cannot recover |
@@ -314,13 +298,7 @@ fn run_cargo( | |||
314 | let mut read_at_least_one_message = false; | 298 | let mut read_at_least_one_message = false; |
315 | 299 | ||
316 | for line in stdout.lines() { | 300 | for line in stdout.lines() { |
317 | let line = match line { | 301 | let line = line?; |
318 | Ok(line) => line, | ||
319 | Err(err) => { | ||
320 | log::error!("Couldn't read line from cargo: {}", err); | ||
321 | continue; | ||
322 | } | ||
323 | }; | ||
324 | 302 | ||
325 | let message = serde_json::from_str::<cargo_metadata::Message>(&line); | 303 | let message = serde_json::from_str::<cargo_metadata::Message>(&line); |
326 | let message = match message { | 304 | let message = match message { |
@@ -341,19 +319,22 @@ fn run_cargo( | |||
341 | // It is okay to ignore the result, as it only errors if the process is already dead | 319 | // It is okay to ignore the result, as it only errors if the process is already dead |
342 | let _ = child.kill(); | 320 | let _ = child.kill(); |
343 | 321 | ||
344 | let err_msg = match child.wait() { | 322 | let exit_status = child.wait()?; |
345 | Ok(exit_code) if !exit_code.success() && !read_at_least_one_message => { | 323 | if !exit_status.success() && !read_at_least_one_message { |
346 | // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment: | 324 | // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment: |
347 | // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298 | 325 | // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298 |
326 | return Err(io::Error::new( | ||
327 | io::ErrorKind::Other, | ||
348 | format!( | 328 | format!( |
349 | "the command produced no valid metadata (exit code: {:?}): cargo {}", | 329 | "the command produced no valid metadata (exit code: {:?}): {:?}", |
350 | exit_code, | 330 | exit_status, command |
351 | args.join(" ") | 331 | ), |
352 | ) | 332 | )); |
353 | } | 333 | } |
354 | Err(err) => format!("io error: {:?}", err), | 334 | |
355 | Ok(_) => return Ok(()), | 335 | Ok(()) |
356 | }; | 336 | } |
357 | 337 | ||
358 | Err(CargoError(err_msg)) | 338 | fn cargo_binary() -> String { |
339 | env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()) | ||
359 | } | 340 | } |