Make config queries source root dependent for flycheck

This commit is contained in:
Ali Bektas 2025-01-10 20:28:07 +01:00
parent 110ec27dbe
commit 09a84bd717
5 changed files with 154 additions and 125 deletions

View file

@ -1,20 +1,19 @@
//! Flycheck provides the functionality needed to run `cargo check` to provide
//! LSP diagnostics based on the output of the command.
use std::{fmt, io, process::Command, time::Duration};
use std::{fmt, io, time::Duration};
use cargo_metadata::PackageId;
use command::{FlycheckConfig, InvocationStrategy, Target};
use command::{check_command, FlycheckConfig, Target};
use crossbeam_channel::{select_biased, unbounded, Receiver, Sender};
use ide_db::FxHashSet;
use paths::{AbsPath, AbsPathBuf};
use paths::AbsPathBuf;
use serde::Deserialize as _;
use serde_derive::Deserialize;
pub(crate) use cargo_metadata::diagnostic::{
Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan,
};
use toolchain::Tool;
use triomphe::Arc;
use crate::command::{CommandHandle, ParseFromLine};
@ -37,13 +36,11 @@ impl FlycheckHandle {
pub(crate) fn spawn(
id: usize,
sender: Sender<FlycheckMessage>,
config: FlycheckConfig,
sysroot_root: Option<AbsPathBuf>,
workspace_root: AbsPathBuf,
manifest_path: Option<AbsPathBuf>,
) -> FlycheckHandle {
let actor =
FlycheckActor::new(id, sender, config, sysroot_root, workspace_root, manifest_path);
let actor = FlycheckActor::new(id, sender, sysroot_root, workspace_root, manifest_path);
let (sender, receiver) = unbounded::<StateChange>();
let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
.name("Flycheck".to_owned())
@ -53,14 +50,21 @@ impl FlycheckHandle {
}
/// Schedule a re-start of the cargo check worker to do a workspace wide check.
pub(crate) fn restart_workspace(&self, saved_file: Option<AbsPathBuf>) {
self.sender.send(StateChange::Restart { package: None, saved_file, target: None }).unwrap();
pub(crate) fn restart_workspace(&self, saved_file: Option<AbsPathBuf>, config: FlycheckConfig) {
self.sender
.send(StateChange::Restart { package: None, saved_file, target: None, config })
.unwrap();
}
/// Schedule a re-start of the cargo check worker to do a package wide check.
pub(crate) fn restart_for_package(&self, package: String, target: Option<Target>) {
pub(crate) fn restart_for_package(
&self,
package: String,
target: Option<Target>,
config: FlycheckConfig,
) {
self.sender
.send(StateChange::Restart { package: Some(package), saved_file: None, target })
.send(StateChange::Restart { package: Some(package), saved_file: None, target, config })
.unwrap();
}
@ -130,7 +134,12 @@ pub(crate) enum Progress {
}
enum StateChange {
Restart { package: Option<String>, saved_file: Option<AbsPathBuf>, target: Option<Target> },
Restart {
package: Option<String>,
saved_file: Option<AbsPathBuf>,
target: Option<Target>,
config: FlycheckConfig,
},
Cancel,
}
@ -140,7 +149,6 @@ struct FlycheckActor {
id: usize,
sender: Sender<FlycheckMessage>,
config: FlycheckConfig,
manifest_path: Option<AbsPathBuf>,
/// Either the workspace root of the workspace we are flychecking,
/// or the project root of the project.
@ -165,13 +173,10 @@ enum Event {
CheckEvent(Option<CargoCheckMessage>),
}
pub(crate) const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
impl FlycheckActor {
fn new(
id: usize,
sender: Sender<FlycheckMessage>,
config: FlycheckConfig,
sysroot_root: Option<AbsPathBuf>,
workspace_root: AbsPathBuf,
manifest_path: Option<AbsPathBuf>,
@ -180,7 +185,6 @@ impl FlycheckActor {
FlycheckActor {
id,
sender,
config,
sysroot_root,
root: Arc::new(workspace_root),
manifest_path,
@ -215,7 +219,12 @@ impl FlycheckActor {
tracing::debug!(flycheck_id = self.id, "flycheck cancelled");
self.cancel_check_process();
}
Event::RequestStateChange(StateChange::Restart { package, saved_file, target }) => {
Event::RequestStateChange(StateChange::Restart {
package,
saved_file,
target,
config,
}) => {
// Cancel the previously spawned process
self.cancel_check_process();
while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) {
@ -225,9 +234,15 @@ impl FlycheckActor {
}
}
let Some(command) =
self.check_command(package.as_deref(), saved_file.as_deref(), target)
else {
let Some(command) = check_command(
&self.root,
&self.sysroot_root,
&self.manifest_path,
config,
package.as_deref(),
saved_file.as_deref(),
target,
) else {
continue;
};
@ -360,95 +375,6 @@ impl FlycheckActor {
self.diagnostics_received = false;
}
/// Construct a `Command` object for checking the user's code. If the user
/// has specified a custom command with placeholders that we cannot fill,
/// return None.
fn check_command(
&self,
package: Option<&str>,
saved_file: Option<&AbsPath>,
target: Option<Target>,
) -> Option<Command> {
match &self.config {
FlycheckConfig::CargoCommand { command, options, ansi_color_output } => {
let mut cmd = toolchain::command(Tool::Cargo.path(), &*self.root);
if let Some(sysroot_root) = &self.sysroot_root {
cmd.env("RUSTUP_TOOLCHAIN", AsRef::<std::path::Path>::as_ref(sysroot_root));
}
cmd.arg(command);
match package {
Some(pkg) => cmd.arg("-p").arg(pkg),
None => cmd.arg("--workspace"),
};
if let Some(tgt) = target {
match tgt {
Target::Bin(tgt) => cmd.arg("--bin").arg(tgt),
Target::Example(tgt) => cmd.arg("--example").arg(tgt),
Target::Test(tgt) => cmd.arg("--test").arg(tgt),
Target::Benchmark(tgt) => cmd.arg("--bench").arg(tgt),
};
}
cmd.arg(if *ansi_color_output {
"--message-format=json-diagnostic-rendered-ansi"
} else {
"--message-format=json"
});
if let Some(manifest_path) = &self.manifest_path {
cmd.arg("--manifest-path");
cmd.arg(manifest_path);
if manifest_path.extension() == Some("rs") {
cmd.arg("-Zscript");
}
}
cmd.arg("--keep-going");
options.apply_on_command(&mut cmd);
cmd.args(&options.extra_args);
Some(cmd)
}
FlycheckConfig::CustomCommand { command, args, extra_env, invocation_strategy } => {
let root = match invocation_strategy {
InvocationStrategy::Once => &*self.root,
InvocationStrategy::PerWorkspace => {
// FIXME: &affected_workspace
&*self.root
}
};
let mut cmd = toolchain::command(command, root);
cmd.envs(extra_env);
// If the custom command has a $saved_file placeholder, and
// we're saving a file, replace the placeholder in the arguments.
if let Some(saved_file) = saved_file {
for arg in args {
if arg == SAVED_FILE_PLACEHOLDER {
cmd.arg(saved_file);
} else {
cmd.arg(arg);
}
}
} else {
for arg in args {
if arg == SAVED_FILE_PLACEHOLDER {
// The custom command has a $saved_file placeholder,
// but we had an IDE event that wasn't a file save. Do nothing.
return None;
}
cmd.arg(arg);
}
}
Some(cmd)
}
}
}
#[track_caller]
fn send(&self, check_task: FlycheckMessage) {
self.sender.send(check_task).unwrap();

View file

@ -2,6 +2,10 @@ use std::{fmt, process::Command};
use ide_db::FxHashMap;
use paths::Utf8PathBuf;
use toolchain::Tool;
use vfs::{AbsPath, AbsPathBuf};
pub(crate) const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) enum InvocationStrategy {
@ -82,3 +86,95 @@ impl fmt::Display for FlycheckConfig {
}
}
}
/// Construct a `Command` object for checking the user's code. If the user
/// has specified a custom command with placeholders that we cannot fill,
/// return None.
pub(super) fn check_command(
root: &AbsPathBuf,
sysroot_root: &Option<AbsPathBuf>,
manifest_path: &Option<AbsPathBuf>,
config: FlycheckConfig,
package: Option<&str>,
saved_file: Option<&AbsPath>,
target: Option<Target>,
) -> Option<Command> {
match config {
FlycheckConfig::CargoCommand { command, options, ansi_color_output } => {
let mut cmd = toolchain::command(Tool::Cargo.path(), &*root);
if let Some(sysroot_root) = &sysroot_root {
cmd.env("RUSTUP_TOOLCHAIN", AsRef::<std::path::Path>::as_ref(sysroot_root));
}
cmd.arg(command);
match package {
Some(pkg) => cmd.arg("-p").arg(pkg),
None => cmd.arg("--workspace"),
};
if let Some(tgt) = target {
match tgt {
Target::Bin(tgt) => cmd.arg("--bin").arg(tgt),
Target::Example(tgt) => cmd.arg("--example").arg(tgt),
Target::Test(tgt) => cmd.arg("--test").arg(tgt),
Target::Benchmark(tgt) => cmd.arg("--bench").arg(tgt),
};
}
cmd.arg(if ansi_color_output {
"--message-format=json-diagnostic-rendered-ansi"
} else {
"--message-format=json"
});
if let Some(manifest_path) = &manifest_path {
cmd.arg("--manifest-path");
cmd.arg(manifest_path);
if manifest_path.extension() == Some("rs") {
cmd.arg("-Zscript");
}
}
cmd.arg("--keep-going");
options.apply_on_command(&mut cmd);
cmd.args(&options.extra_args);
Some(cmd)
}
FlycheckConfig::CustomCommand { command, args, extra_env, invocation_strategy } => {
let root = match invocation_strategy {
InvocationStrategy::Once => &*root,
InvocationStrategy::PerWorkspace => {
// FIXME: &affected_workspace
&*root
}
};
let mut cmd = toolchain::command(command, root);
cmd.envs(extra_env);
// If the custom command has a $saved_file placeholder, and
// we're saving a file, replace the placeholder in the arguments.
if let Some(saved_file) = saved_file {
for arg in args {
if arg == SAVED_FILE_PLACEHOLDER {
cmd.arg(saved_file);
} else {
cmd.arg(arg);
}
}
} else {
for arg in args {
if arg == SAVED_FILE_PLACEHOLDER {
// The custom command has a $saved_file placeholder,
// but we had an IDE event that wasn't a file save. Do nothing.
return None;
}
cmd.arg(arg);
}
}
Some(cmd)
}
}
}

View file

@ -190,7 +190,9 @@ pub(crate) fn handle_did_save_text_document(
} else if state.config.check_on_save(None) && state.config.flycheck_workspace(None) {
// No specific flycheck was triggered, so let's trigger all of them.
for flycheck in state.flycheck.iter() {
flycheck.restart_workspace(None);
let sr = state.workspace_source_root_map.get(&flycheck.id());
let config = state.config.flycheck(sr.cloned());
flycheck.restart_workspace(None, config);
}
}
@ -330,7 +332,10 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
_ => false,
});
if let Some((idx, _)) = workspace {
world.flycheck[idx].restart_for_package(package, target);
let flycheck = &world.flycheck[idx];
let source_root = world.workspace_source_root_map.get(&flycheck.id());
let config = world.config.flycheck(source_root.cloned());
flycheck.restart_for_package(package, target, config);
}
}
}
@ -391,7 +396,9 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
for (id, _) in workspace_ids.clone() {
if id == flycheck.id() {
updated = true;
flycheck.restart_workspace(saved_file.clone());
let sr = world.workspace_source_root_map.get(&id);
let config = world.config.flycheck(sr.cloned());
flycheck.restart_workspace(saved_file.clone(), config);
continue 'flychecks;
}
}
@ -399,7 +406,9 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
// No specific flycheck was triggered, so let's trigger all of them.
if !updated {
for flycheck in world.flycheck.iter() {
flycheck.restart_workspace(saved_file.clone());
let sr = world.workspace_source_root_map.get(&flycheck.id());
let config = world.config.flycheck(sr.cloned());
flycheck.restart_workspace(saved_file.clone(), config);
}
}
Ok(())
@ -442,7 +451,9 @@ pub(crate) fn handle_run_flycheck(
// No specific flycheck was triggered, so let's trigger all of them.
if state.config.flycheck_workspace(None) {
for flycheck in state.flycheck.iter() {
flycheck.restart_workspace(None);
let sr = state.workspace_source_root_map.get(&flycheck.id());
let config = state.config.flycheck(sr.cloned());
flycheck.restart_workspace(None, config);
}
}
Ok(())

View file

@ -413,7 +413,11 @@ impl GlobalState {
&& !self.fetch_build_data_queue.op_requested()
{
// Project has loaded properly, kick off initial flycheck
self.flycheck.iter().for_each(|flycheck| flycheck.restart_workspace(None));
self.flycheck.iter().for_each(|flycheck| {
let sr = self.workspace_source_root_map.get(&flycheck.id());
let config = self.config.flycheck(sr.cloned());
flycheck.restart_workspace(None, config)
});
}
if self.config.prefill_caches() {
self.prime_caches_queue.request_op("became quiescent".to_owned(), ());

View file

@ -811,14 +811,7 @@ impl GlobalState {
self.flycheck = match invocation_strategy {
crate::flycheck::command::InvocationStrategy::Once => {
vec![FlycheckHandle::spawn(
0,
sender,
config,
None,
self.config.root_path().clone(),
None,
)]
vec![FlycheckHandle::spawn(0, sender, None, self.config.root_path().clone(), None)]
}
crate::flycheck::command::InvocationStrategy::PerWorkspace => {
self.workspaces
@ -852,7 +845,6 @@ impl GlobalState {
FlycheckHandle::spawn(
id,
sender.clone(),
config.clone(),
sysroot_root,
root.to_path_buf(),
manifest_path.map(|it| it.to_path_buf()),