-
Notifications
You must be signed in to change notification settings - Fork 1.8k
flycheck: initial implementation of $saved_file
#15381
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,14 +5,15 @@ | |
#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)] | ||
|
||
use std::{ | ||
cell::OnceCell, | ||
fmt, io, | ||
process::{ChildStderr, ChildStdout, Command, Stdio}, | ||
time::Duration, | ||
}; | ||
|
||
use command_group::{CommandGroup, GroupChild}; | ||
use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; | ||
use paths::AbsPathBuf; | ||
use paths::{AbsPath, AbsPathBuf}; | ||
use rustc_hash::FxHashMap; | ||
use serde::Deserialize; | ||
use stdx::process::streaming_output; | ||
|
@@ -55,6 +56,7 @@ pub enum FlycheckConfig { | |
extra_env: FxHashMap<String, String>, | ||
invocation_strategy: InvocationStrategy, | ||
invocation_location: InvocationLocation, | ||
invoke_with_saved_file: bool, | ||
}, | ||
} | ||
|
||
|
@@ -69,6 +71,15 @@ impl fmt::Display for FlycheckConfig { | |
} | ||
} | ||
|
||
impl FlycheckConfig { | ||
pub fn invoke_with_saved_file(&self) -> bool { | ||
match self { | ||
FlycheckConfig::CustomCommand { invoke_with_saved_file, .. } => *invoke_with_saved_file, | ||
_ => false, | ||
} | ||
} | ||
} | ||
|
||
/// Flycheck wraps the shared state and communication machinery used for | ||
/// running `cargo check` (or other compatible command) and providing | ||
/// diagnostics based on the output. | ||
|
@@ -98,8 +109,8 @@ impl FlycheckHandle { | |
} | ||
|
||
/// Schedule a re-start of the cargo check worker. | ||
pub fn restart(&self) { | ||
self.sender.send(StateChange::Restart).unwrap(); | ||
pub fn restart(&self, saved_file: Option<AbsPathBuf>) { | ||
self.sender.send(StateChange::Restart { saved_file }).unwrap(); | ||
} | ||
|
||
/// Stop this cargo check worker. | ||
|
@@ -150,7 +161,7 @@ pub enum Progress { | |
} | ||
|
||
enum StateChange { | ||
Restart, | ||
Restart { saved_file: Option<AbsPathBuf> }, | ||
Cancel, | ||
} | ||
|
||
|
@@ -163,6 +174,7 @@ struct FlycheckActor { | |
/// Either the workspace root of the workspace we are flychecking, | ||
/// or the project root of the project. | ||
root: AbsPathBuf, | ||
state: OnceCell<FlycheckState>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the reason for having this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It solves a cancellation-related bug: a flycheck would be started, canceled before completion, and restarted. The restarted flycheck would then not interpolate the I think with some deeper changes to Flycheck, this can be replaced by a |
||
/// 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 | ||
|
@@ -171,6 +183,11 @@ struct FlycheckActor { | |
cargo_handle: Option<CargoHandle>, | ||
} | ||
|
||
#[derive(Debug)] | ||
struct FlycheckState { | ||
command: Command, | ||
} | ||
|
||
enum Event { | ||
RequestStateChange(StateChange), | ||
CheckEvent(Option<CargoMessage>), | ||
|
@@ -184,7 +201,14 @@ impl FlycheckActor { | |
workspace_root: AbsPathBuf, | ||
) -> FlycheckActor { | ||
tracing::info!(%id, ?workspace_root, "Spawning flycheck"); | ||
FlycheckActor { id, sender, config, root: workspace_root, cargo_handle: None } | ||
FlycheckActor { | ||
id, | ||
sender, | ||
config, | ||
root: workspace_root, | ||
state: OnceCell::new(), | ||
cargo_handle: None, | ||
} | ||
} | ||
|
||
fn report_progress(&self, progress: Progress) { | ||
|
@@ -210,7 +234,7 @@ impl FlycheckActor { | |
tracing::debug!(flycheck_id = self.id, "flycheck cancelled"); | ||
self.cancel_check_process(); | ||
} | ||
Event::RequestStateChange(StateChange::Restart) => { | ||
Event::RequestStateChange(StateChange::Restart { saved_file }) => { | ||
// Cancel the previously spawned process | ||
self.cancel_check_process(); | ||
while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) { | ||
|
@@ -220,22 +244,34 @@ impl FlycheckActor { | |
} | ||
} | ||
|
||
let command = self.check_command(); | ||
tracing::debug!(?command, "will restart flycheck"); | ||
match CargoHandle::spawn(command) { | ||
let command = self.make_check_command(saved_file.as_deref()); | ||
let state = FlycheckState { command }; | ||
match self.state.get_mut() { | ||
Some(old_state) => *old_state = state, | ||
None => { | ||
self.state.set(state).expect( | ||
"Unreachable code, as the state of the OnceCell was checked.", | ||
); | ||
} | ||
}; | ||
|
||
tracing::debug!(state = ?self.config, "restarting flycheck"); | ||
|
||
let command = self.state.get_mut().unwrap(); | ||
|
||
match CargoHandle::spawn(&mut command.command) { | ||
Ok(cargo_handle) => { | ||
tracing::debug!( | ||
command = ?self.check_command(), | ||
"did restart flycheck" | ||
command = ?self.state, | ||
"did restart flycheck" | ||
); | ||
self.cargo_handle = Some(cargo_handle); | ||
self.report_progress(Progress::DidStart); | ||
} | ||
Err(error) => { | ||
self.report_progress(Progress::DidFailToRestart(format!( | ||
"Failed to run the following command: {:?} error={}", | ||
self.check_command(), | ||
error | ||
&self.state, error | ||
))); | ||
} | ||
} | ||
|
@@ -249,7 +285,7 @@ impl FlycheckActor { | |
if res.is_err() { | ||
tracing::error!( | ||
"Flycheck failed to run the following command: {:?}", | ||
self.check_command() | ||
self.config | ||
); | ||
} | ||
self.report_progress(Progress::DidFinish(res)); | ||
|
@@ -285,16 +321,13 @@ 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" | ||
); | ||
tracing::debug!(command = ?self.config, "did cancel flycheck"); | ||
cargo_handle.cancel(); | ||
self.report_progress(Progress::DidCancel); | ||
} | ||
} | ||
|
||
fn check_command(&self) -> Command { | ||
fn make_check_command(&self, saved_file: Option<&AbsPath>) -> Command { | ||
let (mut cmd, args) = match &self.config { | ||
FlycheckConfig::CargoCommand { | ||
command, | ||
|
@@ -339,14 +372,15 @@ impl FlycheckActor { | |
} | ||
} | ||
cmd.envs(extra_env); | ||
(cmd, extra_args) | ||
(cmd, extra_args.clone()) | ||
} | ||
FlycheckConfig::CustomCommand { | ||
command, | ||
args, | ||
extra_env, | ||
invocation_strategy, | ||
invocation_location, | ||
invoke_with_saved_file, | ||
} => { | ||
let mut cmd = Command::new(command); | ||
cmd.envs(extra_env); | ||
|
@@ -368,11 +402,29 @@ impl FlycheckActor { | |
} | ||
} | ||
|
||
(cmd, args) | ||
if *invoke_with_saved_file { | ||
match (args.iter().position(|arg| arg == "$saved_file"), saved_file) { | ||
(Some(i), Some(saved_file)) => { | ||
let mut args = args.clone(); | ||
args[i] = saved_file.to_string(); | ||
(cmd, args) | ||
} | ||
_ => { | ||
tracing::error!( | ||
?saved_file, | ||
"the saved file is missing. This is likely a bug." | ||
); | ||
(cmd, args.clone()) | ||
} | ||
} | ||
} else { | ||
(cmd, args.clone()) | ||
} | ||
} | ||
}; | ||
|
||
cmd.args(args); | ||
|
||
cmd | ||
} | ||
|
||
|
@@ -400,7 +452,7 @@ struct CargoHandle { | |
} | ||
|
||
impl CargoHandle { | ||
fn spawn(mut command: Command) -> std::io::Result<CargoHandle> { | ||
fn spawn(command: &mut Command) -> std::io::Result<CargoHandle> { | ||
command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null()); | ||
let mut child = command.group_spawn().map(JodGroupChild)?; | ||
|
||
|
@@ -464,23 +516,28 @@ impl CargoActor { | |
// 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(); | ||
match JsonMessage::deserialize(&mut deserializer) { | ||
Ok(message) => { | ||
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(); | ||
} | ||
_ => (), | ||
}, | ||
JsonMessage::Rustc(message) => { | ||
self.sender.send(CargoMessage::Diagnostic(message)).unwrap(); | ||
} | ||
return true; | ||
} | ||
return true; | ||
Err(e) => tracing::error!(?e, "unable to deserialize message"), | ||
} | ||
|
||
error.push_str(line); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.