-
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? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # 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.