about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLukas Wirth <lukastw97@gmail.com>2022-09-15 13:28:09 +0200
committerLukas Wirth <lukastw97@gmail.com>2022-10-19 23:21:34 +0200
commit4a287d25258aac0873863584e6f3d075dfb69033 (patch)
tree8ece78617b9430ce507370c9a38f230b869507b5
parent7e2c41dbd68014acbf91c2657a975c5a4b486da9 (diff)
downloadrust-4a287d25258aac0873863584e6f3d075dfb69033.tar.gz
rust-4a287d25258aac0873863584e6f3d075dfb69033.zip
Implement invocation strategy config for checkOnSave
Note that due to how cargo works, none of the modes currently work for r-a
-rw-r--r--crates/flycheck/src/lib.rs53
-rw-r--r--crates/rust-analyzer/src/config.rs17
-rw-r--r--crates/rust-analyzer/src/reload.rs65
-rw-r--r--docs/user/generated_config.adoc11
-rw-r--r--editors/code/package.json15
5 files changed, 127 insertions, 34 deletions
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs
index e8c63d410aa..c3976e6b7a8 100644
--- a/crates/flycheck/src/lib.rs
+++ b/crates/flycheck/src/lib.rs
@@ -6,6 +6,7 @@
 
 use std::{
     fmt, io,
+    path::Path,
     process::{ChildStderr, ChildStdout, Command, Stdio},
     time::Duration,
 };
@@ -21,6 +22,14 @@ pub use cargo_metadata::diagnostic::{
     DiagnosticSpanMacroExpansion,
 };
 
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+pub enum InvocationStrategy {
+    OnceInRoot,
+    PerWorkspaceWithManifestPath,
+    #[default]
+    PerWorkspace,
+}
+
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum FlycheckConfig {
     CargoCommand {
@@ -32,11 +41,13 @@ pub enum FlycheckConfig {
         features: Vec<String>,
         extra_args: Vec<String>,
         extra_env: FxHashMap<String, String>,
+        invocation_strategy: InvocationStrategy,
     },
     CustomCommand {
         command: String,
         args: Vec<String>,
         extra_env: FxHashMap<String, String>,
+        invocation_strategy: InvocationStrategy,
     },
 }
 
@@ -136,7 +147,9 @@ enum Restart {
     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,
@@ -164,9 +177,11 @@ impl FlycheckActor {
         tracing::info!(%id, ?workspace_root, "Spawning flycheck");
         FlycheckActor { id, sender, config, 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() {
@@ -178,6 +193,7 @@ impl FlycheckActor {
             recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())),
         }
     }
+
     fn run(mut self, inbox: Receiver<Restart>) {
         'event: while let Some(event) = self.next_event(&inbox) {
             match event {
@@ -194,7 +210,20 @@ impl FlycheckActor {
                         }
                     }
 
-                    let command = self.check_command();
+                    let mut command = self.check_command();
+                    let invocation_strategy = self.invocation_strategy();
+                    match invocation_strategy {
+                        InvocationStrategy::OnceInRoot => (),
+                        InvocationStrategy::PerWorkspaceWithManifestPath => {
+                            command.arg("--manifest-path");
+                            command.arg(<_ as AsRef<Path>>::as_ref(
+                                &self.workspace_root.join("Cargo.toml"),
+                            ));
+                        }
+                        InvocationStrategy::PerWorkspace => {
+                            command.current_dir(&self.workspace_root);
+                        }
+                    }
                     tracing::debug!(?command, "will restart flycheck");
                     match CargoHandle::spawn(command) {
                         Ok(cargo_handle) => {
@@ -203,10 +232,10 @@ impl FlycheckActor {
                                 "did  restart flycheck"
                             );
                             self.cargo_handle = Some(cargo_handle);
-                            self.progress(Progress::DidStart);
+                            self.report_progress(Progress::DidStart);
                         }
                         Err(error) => {
-                            self.progress(Progress::DidFailToRestart(format!(
+                            self.report_progress(Progress::DidFailToRestart(format!(
                                 "Failed to run the following command: {:?} error={}",
                                 self.check_command(),
                                 error
@@ -226,11 +255,11 @@ 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) => {
@@ -254,7 +283,14 @@ impl FlycheckActor {
                 "did  cancel flycheck"
             );
             cargo_handle.cancel();
-            self.progress(Progress::DidCancel);
+            self.report_progress(Progress::DidCancel);
+        }
+    }
+
+    fn invocation_strategy(&self) -> InvocationStrategy {
+        match self.config {
+            FlycheckConfig::CargoCommand { invocation_strategy, .. }
+            | FlycheckConfig::CustomCommand { invocation_strategy, .. } => invocation_strategy,
         }
     }
 
@@ -269,6 +305,7 @@ impl FlycheckActor {
                 extra_args,
                 features,
                 extra_env,
+                invocation_strategy: _,
             } => {
                 let mut cmd = Command::new(toolchain::cargo());
                 cmd.arg(command);
@@ -297,7 +334,7 @@ impl FlycheckActor {
                 cmd.envs(extra_env);
                 cmd
             }
-            FlycheckConfig::CustomCommand { command, args, extra_env } => {
+            FlycheckConfig::CustomCommand { command, args, extra_env, invocation_strategy: _ } => {
                 let mut cmd = Command::new(command);
                 cmd.args(args);
                 cmd.envs(extra_env);
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 79f6ded4899..a61e38706e5 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -130,6 +130,14 @@ config_data! {
         ///
         /// Set to `"all"` to pass `--all-features` to Cargo.
         checkOnSave_features: Option<CargoFeaturesDef>      = "null",
+        /// Specifies the invocation strategy to use when running the checkOnSave command.
+        /// If `per_workspace_with_manifest_path` is set, the command will be executed for each
+        /// workspace, `--manifest-path {workspace-dir}` will be passed to the invoked command and
+        /// the command will be executed from the project root.
+        /// If `per_workspace` is set, the command will be executed for each workspace and the
+        /// command will be executed from the corresponding workspace root.
+        /// If `once_in_root` is set, the command will be executed once in the project root.
+        checkOnSave_invocationStrategy: InvocationStrategy = "\"per_workspace\"",
         /// Whether to pass `--no-default-features` to Cargo. Defaults to
         /// `#rust-analyzer.cargo.noDefaultFeatures#`.
         checkOnSave_noDefaultFeatures: Option<bool>      = "null",
@@ -1094,6 +1102,13 @@ impl Config {
         if !self.data.checkOnSave_enable {
             return None;
         }
+        let invocation_strategy = match self.data.cargo_buildScripts_invocationStrategy {
+            InvocationStrategy::OnceInRoot => flycheck::InvocationStrategy::OnceInRoot,
+            InvocationStrategy::PerWorkspaceWithManifestPath => {
+                flycheck::InvocationStrategy::PerWorkspaceWithManifestPath
+            }
+            InvocationStrategy::PerWorkspace => flycheck::InvocationStrategy::PerWorkspace,
+        };
         let flycheck_config = match &self.data.checkOnSave_overrideCommand {
             Some(args) if !args.is_empty() => {
                 let mut args = args.clone();
@@ -1102,6 +1117,7 @@ impl Config {
                     command,
                     args,
                     extra_env: self.check_on_save_extra_env(),
+                    invocation_strategy,
                 }
             }
             Some(_) | None => FlycheckConfig::CargoCommand {
@@ -1131,6 +1147,7 @@ impl Config {
                 },
                 extra_args: self.data.checkOnSave_extraArgs.clone(),
                 extra_env: self.check_on_save_extra_env(),
+                invocation_strategy,
             },
         };
         Some(flycheck_config)
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index bd5741f6159..5382790f6e7 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -473,32 +473,45 @@ impl GlobalState {
         };
 
         let sender = self.flycheck_sender.clone();
-        self.flycheck = self
-            .workspaces
-            .iter()
-            .enumerate()
-            .filter_map(|(id, w)| match w {
-                ProjectWorkspace::Cargo { cargo, .. } => Some((id, cargo.workspace_root())),
-                ProjectWorkspace::Json { project, .. } => {
-                    // Enable flychecks for json projects if a custom flycheck command was supplied
-                    // in the workspace configuration.
-                    match config {
-                        FlycheckConfig::CustomCommand { .. } => Some((id, project.path())),
-                        _ => None,
-                    }
-                }
-                ProjectWorkspace::DetachedFiles { .. } => None,
-            })
-            .map(|(id, root)| {
-                let sender = sender.clone();
-                FlycheckHandle::spawn(
-                    id,
-                    Box::new(move |msg| sender.send(msg).unwrap()),
-                    config.clone(),
-                    root.to_path_buf(),
-                )
-            })
-            .collect();
+        let (FlycheckConfig::CargoCommand { invocation_strategy, .. }
+        | FlycheckConfig::CustomCommand { invocation_strategy, .. }) = config;
+
+        self.flycheck = match invocation_strategy {
+            flycheck::InvocationStrategy::OnceInRoot => vec![FlycheckHandle::spawn(
+                0,
+                Box::new(move |msg| sender.send(msg).unwrap()),
+                config.clone(),
+                self.config.root_path().clone(),
+            )],
+            flycheck::InvocationStrategy::PerWorkspaceWithManifestPath
+            | flycheck::InvocationStrategy::PerWorkspace => {
+                self.workspaces
+                    .iter()
+                    .enumerate()
+                    .filter_map(|(id, w)| match w {
+                        ProjectWorkspace::Cargo { cargo, .. } => Some((id, cargo.workspace_root())),
+                        ProjectWorkspace::Json { project, .. } => {
+                            // Enable flychecks for json projects if a custom flycheck command was supplied
+                            // in the workspace configuration.
+                            match config {
+                                FlycheckConfig::CustomCommand { .. } => Some((id, project.path())),
+                                _ => None,
+                            }
+                        }
+                        ProjectWorkspace::DetachedFiles { .. } => None,
+                    })
+                    .map(|(id, root)| {
+                        let sender = sender.clone();
+                        FlycheckHandle::spawn(
+                            id,
+                            Box::new(move |msg| sender.send(msg).unwrap()),
+                            config.clone(),
+                            root.to_path_buf(),
+                        )
+                    })
+                    .collect()
+            }
+        };
     }
 }
 
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index a5307b63151..3ced42ef72e 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -129,6 +129,17 @@ List of features to activate. Defaults to
 
 Set to `"all"` to pass `--all-features` to Cargo.
 --
+[[rust-analyzer.checkOnSave.invocationStrategy]]rust-analyzer.checkOnSave.invocationStrategy (default: `"per_workspace"`)::
++
+--
+Specifies the invocation strategy to use when running the checkOnSave command.
+If `per_workspace_with_manifest_path` is set, the command will be executed for each
+workspace, `--manifest-path {workspace-dir}` will be passed to the invoked command and
+the command will be executed from the project root.
+If `per_workspace` is set, the command will be executed for each workspace and the
+command will be executed from the corresponding workspace root.
+If `once_in_root` is set, the command will be executed once in the project root.
+--
 [[rust-analyzer.checkOnSave.noDefaultFeatures]]rust-analyzer.checkOnSave.noDefaultFeatures (default: `null`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index b1b565106a3..3af32685fda 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -561,6 +561,21 @@
                         }
                     ]
                 },
+                "rust-analyzer.checkOnSave.invocationStrategy": {
+                    "markdownDescription": "Specifies the invocation strategy to use when running the checkOnSave command.\nIf `per_workspace_with_manifest_path` is set, the command will be executed for each\nworkspace, `--manifest-path {workspace-dir}` will be passed to the invoked command and\nthe command will be executed from the project root.\nIf `per_workspace` is set, the command will be executed for each workspace and the\ncommand will be executed from the corresponding workspace root.\nIf `once_in_root` is set, the command will be executed once in the project root.",
+                    "default": "per_workspace",
+                    "type": "string",
+                    "enum": [
+                        "per_workspace",
+                        "per_workspace_with_manifest_path",
+                        "once_in_root"
+                    ],
+                    "enumDescriptions": [
+                        "The command will be executed for each workspace, `--manifest-path {workspace-dir}` will be passed to the invoked command and the command will be executed from the project root.",
+                        "The command will be executed for each workspace and the command will be executed from the corresponding workspace root.",
+                        "The command will be executed once in the project root."
+                    ]
+                },
                 "rust-analyzer.checkOnSave.noDefaultFeatures": {
                     "markdownDescription": "Whether to pass `--no-default-features` to Cargo. Defaults to\n`#rust-analyzer.cargo.noDefaultFeatures#`.",
                     "default": null,