diff options
Diffstat (limited to '')
-rw-r--r-- | src/tools/rust-analyzer/crates/flycheck/src/lib.rs | 152 |
1 files changed, 118 insertions, 34 deletions
diff --git a/src/tools/rust-analyzer/crates/flycheck/src/lib.rs b/src/tools/rust-analyzer/crates/flycheck/src/lib.rs index 4e8bc881a..73c3a48b4 100644 --- a/src/tools/rust-analyzer/crates/flycheck/src/lib.rs +++ b/src/tools/rust-analyzer/crates/flycheck/src/lib.rs @@ -12,6 +12,7 @@ use std::{ use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; use paths::AbsPathBuf; +use rustc_hash::FxHashMap; use serde::Deserialize; use stdx::{process::streaming_output, JodChild}; @@ -20,6 +21,20 @@ pub use cargo_metadata::diagnostic::{ DiagnosticSpanMacroExpansion, }; +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub enum InvocationStrategy { + Once, + #[default] + PerWorkspace, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub enum InvocationLocation { + Root(AbsPathBuf), + #[default] + Workspace, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum FlycheckConfig { CargoCommand { @@ -30,10 +45,14 @@ pub enum FlycheckConfig { all_features: bool, features: Vec<String>, extra_args: Vec<String>, + extra_env: FxHashMap<String, String>, }, CustomCommand { command: String, args: Vec<String>, + extra_env: FxHashMap<String, String>, + invocation_strategy: InvocationStrategy, + invocation_location: InvocationLocation, }, } @@ -41,7 +60,7 @@ 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 } => { + FlycheckConfig::CustomCommand { command, args, .. } => { write!(f, "{} {}", command, args.join(" ")) } } @@ -57,6 +76,7 @@ pub struct FlycheckHandle { // XXX: drop order is significant sender: Sender<Restart>, _thread: jod_thread::JoinHandle, + id: usize, } impl FlycheckHandle { @@ -72,18 +92,27 @@ impl FlycheckHandle { .name("Flycheck".to_owned()) .spawn(move || actor.run(receiver)) .expect("failed to spawn thread"); - FlycheckHandle { sender, _thread: thread } + FlycheckHandle { id, sender, _thread: thread } } /// Schedule a re-start of the cargo check worker. - pub fn update(&self) { - self.sender.send(Restart).unwrap(); + pub fn restart(&self) { + self.sender.send(Restart::Yes).unwrap(); + } + + /// Stop this cargo check worker. + pub fn cancel(&self) { + self.sender.send(Restart::No).unwrap(); + } + + pub fn id(&self) -> usize { + self.id } } pub enum Message { /// Request adding a diagnostic with fixes included to a file - AddDiagnostic { workspace_root: AbsPathBuf, diagnostic: Diagnostic }, + AddDiagnostic { id: usize, workspace_root: AbsPathBuf, diagnostic: Diagnostic }, /// Request check progress notification to client Progress { @@ -96,8 +125,9 @@ pub enum Message { impl fmt::Debug for Message { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Message::AddDiagnostic { workspace_root, diagnostic } => f + Message::AddDiagnostic { id, workspace_root, diagnostic } => f .debug_struct("AddDiagnostic") + .field("id", id) .field("workspace_root", workspace_root) .field("diagnostic_code", &diagnostic.code.as_ref().map(|it| &it.code)) .finish(), @@ -114,15 +144,23 @@ pub enum Progress { DidCheckCrate(String), DidFinish(io::Result<()>), DidCancel, + DidFailToRestart(String), } -struct Restart; +enum Restart { + Yes, + No, +} +/// A [`FlycheckActor`] is a single check instance of a workspace. struct FlycheckActor { + /// The workspace id of this flycheck instance. id: usize, sender: Box<dyn Fn(Message) + Send>, config: FlycheckConfig, - workspace_root: AbsPathBuf, + /// Either the workspace root of the workspace we are flychecking, + /// or the project root of the project. + root: AbsPathBuf, /// CargoHandle 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 @@ -143,25 +181,41 @@ impl FlycheckActor { config: FlycheckConfig, workspace_root: AbsPathBuf, ) -> FlycheckActor { - FlycheckActor { id, sender, config, workspace_root, cargo_handle: None } + tracing::info!(%id, ?workspace_root, "Spawning flycheck"); + FlycheckActor { id, sender, config, root: workspace_root, cargo_handle: None } } - fn progress(&self, progress: Progress) { + + fn report_progress(&self, progress: Progress) { self.send(Message::Progress { id: self.id, progress }); } + fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> { let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver); + if let Ok(msg) = inbox.try_recv() { + // give restarts a preference so check outputs don't block a restart or stop + return Some(Event::Restart(msg)); + } select! { recv(inbox) -> msg => msg.ok().map(Event::Restart), recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())), } } + fn run(mut self, inbox: Receiver<Restart>) { - while let Some(event) = self.next_event(&inbox) { + 'event: while let Some(event) = self.next_event(&inbox) { match event { - Event::Restart(Restart) => { + Event::Restart(Restart::No) => { + self.cancel_check_process(); + } + Event::Restart(Restart::Yes) => { // Cancel the previously spawned process self.cancel_check_process(); - while let Ok(Restart) = inbox.recv_timeout(Duration::from_millis(50)) {} + while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) { + // restart chained with a stop, so just cancel + if let Restart::No = restart { + continue 'event; + } + } let command = self.check_command(); tracing::debug!(?command, "will restart flycheck"); @@ -172,18 +226,19 @@ impl FlycheckActor { "did restart flycheck" ); self.cargo_handle = Some(cargo_handle); - self.progress(Progress::DidStart); + self.report_progress(Progress::DidStart); } Err(error) => { - tracing::error!( - command = ?self.check_command(), - %error, "failed to restart flycheck" - ); + self.report_progress(Progress::DidFailToRestart(format!( + "Failed to run the following command: {:?} error={}", + self.check_command(), + error + ))); } } } Event::CheckEvent(None) => { - tracing::debug!("flycheck finished"); + tracing::debug!(flycheck_id = self.id, "flycheck finished"); // Watcher finished let cargo_handle = self.cargo_handle.take().unwrap(); @@ -194,16 +249,17 @@ impl FlycheckActor { self.check_command() ); } - self.progress(Progress::DidFinish(res)); + self.report_progress(Progress::DidFinish(res)); } Event::CheckEvent(Some(message)) => match message { CargoMessage::CompilerArtifact(msg) => { - self.progress(Progress::DidCheckCrate(msg.target.name)); + self.report_progress(Progress::DidCheckCrate(msg.target.name)); } CargoMessage::Diagnostic(msg) => { self.send(Message::AddDiagnostic { - workspace_root: self.workspace_root.clone(), + id: self.id, + workspace_root: self.root.clone(), diagnostic: msg, }); } @@ -216,13 +272,17 @@ impl FlycheckActor { fn cancel_check_process(&mut self) { if let Some(cargo_handle) = self.cargo_handle.take() { + tracing::debug!( + command = ?self.check_command(), + "did cancel flycheck" + ); cargo_handle.cancel(); - self.progress(Progress::DidCancel); + self.report_progress(Progress::DidCancel); } } fn check_command(&self) -> Command { - let mut cmd = match &self.config { + let (mut cmd, args) = match &self.config { FlycheckConfig::CargoCommand { command, target_triple, @@ -231,12 +291,11 @@ impl FlycheckActor { all_features, extra_args, features, + extra_env, } => { let mut cmd = Command::new(toolchain::cargo()); cmd.arg(command); - cmd.current_dir(&self.workspace_root); - cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]) - .arg(self.workspace_root.join("Cargo.toml").as_os_str()); + cmd.args(&["--workspace", "--message-format=json"]); if let Some(target) = target_triple { cmd.args(&["--target", target.as_str()]); @@ -255,16 +314,41 @@ impl FlycheckActor { cmd.arg(features.join(" ")); } } - cmd.args(extra_args); - cmd + cmd.envs(extra_env); + (cmd, extra_args) } - FlycheckConfig::CustomCommand { command, args } => { + FlycheckConfig::CustomCommand { + command, + args, + extra_env, + invocation_strategy, + invocation_location, + } => { let mut cmd = Command::new(command); - cmd.args(args); - cmd + cmd.envs(extra_env); + + match invocation_location { + InvocationLocation::Workspace => { + match invocation_strategy { + InvocationStrategy::Once => { + cmd.current_dir(&self.root); + } + InvocationStrategy::PerWorkspace => { + // FIXME: cmd.current_dir(&affected_workspace); + cmd.current_dir(&self.root); + } + } + } + InvocationLocation::Root(root) => { + cmd.current_dir(root); + } + } + + (cmd, args) } }; - cmd.current_dir(&self.workspace_root); + + cmd.args(args); cmd } @@ -338,7 +422,7 @@ impl CargoActor { // // 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. + // erroneous output. let mut error = String::new(); let mut read_at_least_one_message = false; |