aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_flycheck/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_flycheck/src/lib.rs')
-rw-r--r--crates/ra_flycheck/src/lib.rs123
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 @@
4mod conv; 4mod conv;
5 5
6use std::{ 6use 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};
23pub use crate::conv::url_from_path_with_drive_lowercasing; 23pub use crate::conv::url_from_path_with_drive_lowercasing;
24 24
25#[derive(Clone, Debug)] 25#[derive(Clone, Debug)]
26pub struct CheckConfig { 26pub 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
44impl Flycheck { 43impl 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
78struct FlycheckThread { 76struct 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
92impl FlycheckThread { 90impl 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)]
278pub struct CargoError(String);
279
280impl fmt::Display for CargoError {
281 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
282 write!(f, "Cargo failed: {}", self.0)
283 }
284}
285impl error::Error for CargoError {}
286
287fn run_cargo( 282fn 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)) 338fn cargo_binary() -> String {
339 env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
359} 340}