From 0ec5d4f55c6a5ac3fadcd48ae17b70379aba17fa Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 25 Jun 2020 09:13:46 +0200 Subject: Rename ra_flycheck -> flycheck --- crates/ra_flycheck/src/lib.rs | 342 ------------------------------------------ 1 file changed, 342 deletions(-) delete mode 100644 crates/ra_flycheck/src/lib.rs (limited to 'crates/ra_flycheck/src') diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs deleted file mode 100644 index af75adbe2..000000000 --- a/crates/ra_flycheck/src/lib.rs +++ /dev/null @@ -1,342 +0,0 @@ -//! cargo_check provides the functionality needed to run `cargo check` or -//! another compatible command (f.x. clippy) in a background thread and provide -//! LSP diagnostics based on the output of the command. - -use std::{ - fmt, - io::{self, BufReader}, - path::PathBuf, - process::{Command, Stdio}, - time::Instant, -}; - -use cargo_metadata::Message; -use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; - -pub use cargo_metadata::diagnostic::{ - Applicability, Diagnostic, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion, -}; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum FlycheckConfig { - CargoCommand { - command: String, - all_targets: bool, - all_features: bool, - features: Vec, - extra_args: Vec, - }, - CustomCommand { - command: String, - args: Vec, - }, -} - -impl fmt::Display for FlycheckConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {}", command), - FlycheckConfig::CustomCommand { command, args } => { - write!(f, "{} {}", command, args.join(" ")) - } - } - } -} - -/// Flycheck wraps the shared state and communication machinery used for -/// running `cargo check` (or other compatible command) and providing -/// diagnostics based on the output. -/// The spawned thread is shut down when this struct is dropped. -#[derive(Debug)] -pub struct FlycheckHandle { - // XXX: drop order is significant - cmd_send: Sender, - handle: jod_thread::JoinHandle<()>, -} - -impl FlycheckHandle { - pub fn spawn( - sender: Box, - config: FlycheckConfig, - workspace_root: PathBuf, - ) -> FlycheckHandle { - let (cmd_send, cmd_recv) = unbounded::(); - let handle = jod_thread::spawn(move || { - FlycheckActor::new(sender, config, workspace_root).run(&cmd_recv); - }); - FlycheckHandle { cmd_send, handle } - } - - /// Schedule a re-start of the cargo check worker. - pub fn update(&self) { - self.cmd_send.send(CheckCommand::Update).unwrap(); - } -} - -#[derive(Debug)] -pub enum CheckTask { - /// Request a clearing of all cached diagnostics from the check watcher - ClearDiagnostics, - - /// Request adding a diagnostic with fixes included to a file - AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic }, - - /// Request check progress notification to client - Status(Status), -} - -#[derive(Debug)] -pub enum Status { - Being, - Progress(String), - End, -} - -pub enum CheckCommand { - /// Request re-start of check thread - Update, -} - -struct FlycheckActor { - sender: Box, - config: FlycheckConfig, - workspace_root: PathBuf, - last_update_req: Option, - // XXX: drop order is significant - message_recv: Receiver, - /// WatchThread exists to wrap around the communication needed to be able to - /// run `cargo check` without blocking. Currently the Rust standard library - /// 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. - check_process: Option>, -} - -impl FlycheckActor { - fn new( - sender: Box, - config: FlycheckConfig, - workspace_root: PathBuf, - ) -> FlycheckActor { - FlycheckActor { - sender, - config, - workspace_root, - last_update_req: None, - message_recv: never(), - check_process: None, - } - } - - fn run(&mut self, cmd_recv: &Receiver) { - // If we rerun the thread, we need to discard the previous check results first - self.clean_previous_results(); - - loop { - select! { - recv(&cmd_recv) -> cmd => match cmd { - Ok(cmd) => self.handle_command(cmd), - Err(RecvError) => { - // Command channel has closed, so shut down - break; - }, - }, - recv(self.message_recv) -> msg => match msg { - Ok(msg) => self.handle_message(msg), - Err(RecvError) => { - // Watcher finished, replace it with a never channel to - // avoid busy-waiting. - self.message_recv = never(); - self.check_process = None; - }, - } - }; - - if self.should_recheck() { - self.last_update_req = None; - self.send(CheckTask::ClearDiagnostics); - self.restart_check_process(); - } - } - } - - fn clean_previous_results(&self) { - self.send(CheckTask::ClearDiagnostics); - self.send(CheckTask::Status(Status::End)); - } - - fn should_recheck(&mut self) -> bool { - if let Some(_last_update_req) = &self.last_update_req { - // We currently only request an update on save, as we need up to - // date source on disk for cargo check to do it's magic, so we - // don't really need to debounce the requests at this point. - return true; - } - false - } - - fn handle_command(&mut self, cmd: CheckCommand) { - match cmd { - CheckCommand::Update => self.last_update_req = Some(Instant::now()), - } - } - - fn handle_message(&self, msg: CheckEvent) { - match msg { - CheckEvent::Begin => { - self.send(CheckTask::Status(Status::Being)); - } - - CheckEvent::End => { - self.send(CheckTask::Status(Status::End)); - } - - CheckEvent::Msg(Message::CompilerArtifact(msg)) => { - self.send(CheckTask::Status(Status::Progress(msg.target.name))); - } - - CheckEvent::Msg(Message::CompilerMessage(msg)) => { - self.send(CheckTask::AddDiagnostic { - workspace_root: self.workspace_root.clone(), - diagnostic: msg.message, - }); - } - - CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {} - CheckEvent::Msg(Message::BuildFinished(_)) => {} - CheckEvent::Msg(Message::TextLine(_)) => {} - CheckEvent::Msg(Message::Unknown) => {} - } - } - - fn restart_check_process(&mut self) { - // First, clear and cancel the old thread - self.message_recv = never(); - self.check_process = None; - - let mut cmd = match &self.config { - FlycheckConfig::CargoCommand { - command, - all_targets, - all_features, - extra_args, - features, - } => { - let mut cmd = Command::new(ra_toolchain::cargo()); - cmd.arg(command); - cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]) - .arg(self.workspace_root.join("Cargo.toml")); - if *all_targets { - cmd.arg("--all-targets"); - } - if *all_features { - cmd.arg("--all-features"); - } else if !features.is_empty() { - cmd.arg("--features"); - cmd.arg(features.join(" ")); - } - cmd.args(extra_args); - cmd - } - FlycheckConfig::CustomCommand { command, args } => { - let mut cmd = Command::new(command); - cmd.args(args); - cmd - } - }; - cmd.current_dir(&self.workspace_root); - - let (message_send, message_recv) = unbounded(); - self.message_recv = message_recv; - self.check_process = Some(jod_thread::spawn(move || { - // If we trigger an error here, we will do so in the loop instead, - // which will break out of the loop, and continue the shutdown - let _ = message_send.send(CheckEvent::Begin); - - let res = run_cargo(cmd, &mut |message| { - // Skip certain kinds of messages to only spend time on what's useful - match &message { - Message::CompilerArtifact(artifact) if artifact.fresh => return true, - Message::BuildScriptExecuted(_) => return true, - Message::Unknown => return true, - _ => {} - } - - // if the send channel was closed, we want to shutdown - message_send.send(CheckEvent::Msg(message)).is_ok() - }); - - if let Err(err) = res { - // FIXME: make the `message_send` to be `Sender>` - // to display user-caused misconfiguration errors instead of just logging them here - log::error!("Cargo watcher failed {:?}", err); - } - - // We can ignore any error here, as we are already in the progress - // of shutting down. - let _ = message_send.send(CheckEvent::End); - })) - } - - fn send(&self, check_task: CheckTask) { - (self.sender)(check_task) - } -} - -enum CheckEvent { - Begin, - Msg(cargo_metadata::Message), - End, -} - -fn run_cargo( - mut command: Command, - on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool, -) -> io::Result<()> { - let mut child = - command.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null()).spawn()?; - - // 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 resillient 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 - // erroneus output. - let stdout = BufReader::new(child.stdout.take().unwrap()); - let mut read_at_least_one_message = false; - for message in cargo_metadata::Message::parse_stream(stdout) { - let message = match message { - Ok(message) => message, - Err(err) => { - log::error!("Invalid json from cargo check, ignoring ({})", err); - continue; - } - }; - - read_at_least_one_message = true; - - if !on_message(message) { - break; - } - } - - // It is okay to ignore the result, as it only errors if the process is already dead - let _ = child.kill(); - - let exit_status = child.wait()?; - if !exit_status.success() && !read_at_least_one_message { - // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment: - // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298 - return Err(io::Error::new( - io::ErrorKind::Other, - format!( - "the command produced no valid metadata (exit code: {:?}): {:?}", - exit_status, command - ), - )); - } - - Ok(()) -} -- cgit v1.2.3