summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/flycheck/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/tools/rust-analyzer/crates/flycheck/src/lib.rs152
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;