Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/flycheck/src/lib.rs')
| -rw-r--r-- | crates/flycheck/src/lib.rs | 201 |
1 files changed, 42 insertions, 159 deletions
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index 8bcdca5bb8..f8efb52022 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs @@ -2,22 +2,18 @@ //! another compatible command (f.x. clippy) in a background thread and provide //! LSP diagnostics based on the output of the command. +// FIXME: This crate now handles running `cargo test` needed in the test explorer in +// addition to `cargo check`. Either split it into 3 crates (one for test, one for check +// and one common utilities) or change its name and docs to reflect the current state. + #![warn(rust_2018_idioms, unused_lifetimes)] -use std::{ - ffi::OsString, - fmt, io, - path::PathBuf, - process::{ChildStderr, ChildStdout, Command, Stdio}, - time::Duration, -}; +use std::{fmt, io, path::PathBuf, process::Command, time::Duration}; -use command_group::{CommandGroup, GroupChild}; use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; use paths::{AbsPath, AbsPathBuf}; use rustc_hash::FxHashMap; use serde::Deserialize; -use stdx::process::streaming_output; pub use cargo_metadata::diagnostic::{ Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan, @@ -25,6 +21,12 @@ pub use cargo_metadata::diagnostic::{ }; use toolchain::Tool; +mod command; +mod test_runner; + +use command::{CommandHandle, ParseFromLine}; +pub use test_runner::{CargoTestHandle, CargoTestMessage, TestState}; + #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub enum InvocationStrategy { Once, @@ -181,12 +183,12 @@ struct FlycheckActor { /// doesn't provide a way to read sub-process output without blocking, so we /// have to wrap sub-processes output handling in a thread and pass messages /// back over a channel. - command_handle: Option<CommandHandle>, + command_handle: Option<CommandHandle<CargoCheckMessage>>, } enum Event { RequestStateChange(StateChange), - CheckEvent(Option<CargoMessage>), + CheckEvent(Option<CargoCheckMessage>), } const SAVED_FILE_PLACEHOLDER: &str = "$saved_file"; @@ -282,7 +284,7 @@ impl FlycheckActor { self.report_progress(Progress::DidFinish(res)); } Event::CheckEvent(Some(message)) => match message { - CargoMessage::CompilerArtifact(msg) => { + CargoCheckMessage::CompilerArtifact(msg) => { tracing::trace!( flycheck_id = self.id, artifact = msg.target.name, @@ -291,7 +293,7 @@ impl FlycheckActor { self.report_progress(Progress::DidCheckCrate(msg.target.name)); } - CargoMessage::Diagnostic(msg) => { + CargoCheckMessage::Diagnostic(msg) => { tracing::trace!( flycheck_id = self.id, message = msg.message, @@ -448,161 +450,42 @@ impl FlycheckActor { } } -struct JodGroupChild(GroupChild); - -impl Drop for JodGroupChild { - fn drop(&mut self) { - _ = self.0.kill(); - _ = self.0.wait(); - } -} - -/// A handle to a cargo process used for fly-checking. -struct CommandHandle { - /// The handle to the actual cargo process. As we cannot cancel directly from with - /// a read syscall dropping and therefore terminating the process is our best option. - child: JodGroupChild, - thread: stdx::thread::JoinHandle<io::Result<(bool, String)>>, - receiver: Receiver<CargoMessage>, - program: OsString, - arguments: Vec<OsString>, - current_dir: Option<PathBuf>, -} - -impl fmt::Debug for CommandHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("CommandHandle") - .field("program", &self.program) - .field("arguments", &self.arguments) - .field("current_dir", &self.current_dir) - .finish() - } +#[allow(clippy::large_enum_variant)] +enum CargoCheckMessage { + CompilerArtifact(cargo_metadata::Artifact), + Diagnostic(Diagnostic), } -impl CommandHandle { - fn spawn(mut command: Command) -> std::io::Result<CommandHandle> { - command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null()); - let mut child = command.group_spawn().map(JodGroupChild)?; - - let program = command.get_program().into(); - let arguments = command.get_args().map(|arg| arg.into()).collect::<Vec<OsString>>(); - let current_dir = command.get_current_dir().map(|arg| arg.to_path_buf()); - - let stdout = child.0.inner().stdout.take().unwrap(); - let stderr = child.0.inner().stderr.take().unwrap(); - - let (sender, receiver) = unbounded(); - let actor = CargoActor::new(sender, stdout, stderr); - let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker) - .name("CommandHandle".to_owned()) - .spawn(move || actor.run()) - .expect("failed to spawn thread"); - Ok(CommandHandle { program, arguments, current_dir, child, thread, receiver }) - } - - fn cancel(mut self) { - let _ = self.child.0.kill(); - let _ = self.child.0.wait(); - } - - fn join(mut self) -> io::Result<()> { - let _ = self.child.0.kill(); - let exit_status = self.child.0.wait()?; - let (read_at_least_one_message, error) = self.thread.join()?; - if read_at_least_one_message || exit_status.success() { - Ok(()) - } else { - Err(io::Error::new(io::ErrorKind::Other, format!( - "Cargo watcher failed, the command produced no valid metadata (exit code: {exit_status:?}):\n{error}" - ))) +impl ParseFromLine for CargoCheckMessage { + fn from_line(line: &str, error: &mut String) -> Option<Self> { + let mut deserializer = serde_json::Deserializer::from_str(line); + deserializer.disable_recursion_limit(); + if let Ok(message) = JsonMessage::deserialize(&mut deserializer) { + return match message { + // Skip certain kinds of messages to only spend time on what's useful + JsonMessage::Cargo(message) => match message { + cargo_metadata::Message::CompilerArtifact(artifact) if !artifact.fresh => { + Some(CargoCheckMessage::CompilerArtifact(artifact)) + } + cargo_metadata::Message::CompilerMessage(msg) => { + Some(CargoCheckMessage::Diagnostic(msg.message)) + } + _ => None, + }, + JsonMessage::Rustc(message) => Some(CargoCheckMessage::Diagnostic(message)), + }; } - } -} -struct CargoActor { - sender: Sender<CargoMessage>, - stdout: ChildStdout, - stderr: ChildStderr, -} - -impl CargoActor { - fn new(sender: Sender<CargoMessage>, stdout: ChildStdout, stderr: ChildStderr) -> CargoActor { - CargoActor { sender, stdout, stderr } + error.push_str(line); + error.push('\n'); + None } - fn run(self) -> io::Result<(bool, String)> { - // We manually read a line at a time, instead of using serde's - // stream deserializers, because the deserializer cannot recover - // from an error, resulting in it getting stuck, because we try to - // be resilient against failures. - // - // Because cargo only outputs one JSON object per line, we can - // simply skip a line if it doesn't parse, which just ignores any - // erroneous output. - - let mut stdout_errors = String::new(); - let mut stderr_errors = String::new(); - let mut read_at_least_one_stdout_message = false; - let mut read_at_least_one_stderr_message = false; - let process_line = |line: &str, error: &mut String| { - // Try to deserialize a message from Cargo or Rustc. - let mut deserializer = serde_json::Deserializer::from_str(line); - deserializer.disable_recursion_limit(); - if let Ok(message) = JsonMessage::deserialize(&mut deserializer) { - match message { - // Skip certain kinds of messages to only spend time on what's useful - JsonMessage::Cargo(message) => match message { - cargo_metadata::Message::CompilerArtifact(artifact) if !artifact.fresh => { - self.sender.send(CargoMessage::CompilerArtifact(artifact)).unwrap(); - } - cargo_metadata::Message::CompilerMessage(msg) => { - self.sender.send(CargoMessage::Diagnostic(msg.message)).unwrap(); - } - _ => (), - }, - JsonMessage::Rustc(message) => { - self.sender.send(CargoMessage::Diagnostic(message)).unwrap(); - } - } - return true; - } - - error.push_str(line); - error.push('\n'); - false - }; - let output = streaming_output( - self.stdout, - self.stderr, - &mut |line| { - if process_line(line, &mut stdout_errors) { - read_at_least_one_stdout_message = true; - } - }, - &mut |line| { - if process_line(line, &mut stderr_errors) { - read_at_least_one_stderr_message = true; - } - }, - ); - - let read_at_least_one_message = - read_at_least_one_stdout_message || read_at_least_one_stderr_message; - let mut error = stdout_errors; - error.push_str(&stderr_errors); - match output { - Ok(_) => Ok((read_at_least_one_message, error)), - Err(e) => Err(io::Error::new(e.kind(), format!("{e:?}: {error}"))), - } + fn from_eof() -> Option<Self> { + None } } -#[allow(clippy::large_enum_variant)] -enum CargoMessage { - CompilerArtifact(cargo_metadata::Artifact), - Diagnostic(Diagnostic), -} - #[derive(Deserialize)] #[serde(untagged)] enum JsonMessage { |