about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVictor Song <vms2@rice.edu>2023-09-29 03:49:37 -0500
committerVictor Song <vms2@rice.edu>2023-10-09 02:15:05 -0500
commitaeef7b644b135c80c2fc7a28954d97194df7fabc (patch)
tree79d2733bd93d1b37e92d6cb26d5a16f1dd848757
parent7e9b25bff75a24bd85521ca54fa9d01edd35ad1d (diff)
downloadrust-aeef7b644b135c80c2fc7a28954d97194df7fabc.tar.gz
rust-aeef7b644b135c80c2fc7a28954d97194df7fabc.zip
Add config option to use `rust-analyzer` specific target dir
Adds a Rust Analyzer configuration option to set a custom
target directory for builds. This is a workaround for Rust Analyzer
blocking debug builds while running `cargo check`. This change
should close #6007
-rw-r--r--crates/rust-analyzer/src/config.rs200
-rw-r--r--docs/user/generated_config.adoc9
-rw-r--r--editors/code/package.json15
3 files changed, 190 insertions, 34 deletions
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 8e780baa36d..3cd6fa49b20 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -480,6 +480,13 @@ config_data! {
         /// tests or binaries. For example, it may be `--release`.
         runnables_extraArgs: Vec<String>   = "[]",
 
+        /// Optional path to a rust-analyzer specific target directory.
+        /// This is useful to prevent rust-analyzer's `cargo check` from blocking builds.
+        ///
+        /// Set to `true` to use a subdirectory of the existing target directory or
+        /// set to a path to use that path.
+        rust_analyzerTargetDir: Option<TargetDirectory> = "null",
+
         /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
         /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
         /// is installed.
@@ -1192,6 +1199,7 @@ impl Config {
     }
 
     pub fn cargo(&self) -> CargoConfig {
+        let target_directory = self.target_dir_from_config();
         let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| {
             if rustc_src == "discover" {
                 RustLibSource::Discover
@@ -1209,6 +1217,10 @@ impl Config {
         let sysroot_src =
             self.data.cargo_sysrootSrc.as_ref().map(|sysroot| self.root_path.join(sysroot));
 
+        let mut extra_args = self.data.cargo_extraArgs.clone();
+
+        add_target_dir_to_args(&mut extra_args, target_directory);
+
         CargoConfig {
             features: match &self.data.cargo_features {
                 CargoFeaturesDef::All => CargoFeatures::All,
@@ -1261,7 +1273,7 @@ impl Config {
                 InvocationLocation::Workspace => project_model::InvocationLocation::Workspace,
             },
             run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
-            extra_args: self.data.cargo_extraArgs.clone(),
+            extra_args,
             extra_env: self.data.cargo_extraEnv.clone(),
         }
     }
@@ -1281,10 +1293,14 @@ impl Config {
     }
 
     pub fn flycheck(&self) -> FlycheckConfig {
+        let target_directory = self.target_dir_from_config();
+
         match &self.data.check_overrideCommand {
             Some(args) if !args.is_empty() => {
                 let mut args = args.clone();
                 let command = args.remove(0);
+                add_target_dir_to_args(&mut args, target_directory);
+
                 FlycheckConfig::CustomCommand {
                     command,
                     args,
@@ -1303,42 +1319,61 @@ impl Config {
                     },
                 }
             }
-            Some(_) | None => FlycheckConfig::CargoCommand {
-                command: self.data.check_command.clone(),
-                target_triples: self
-                    .data
-                    .check_targets
-                    .clone()
-                    .and_then(|targets| match &targets.0[..] {
-                        [] => None,
-                        targets => Some(targets.into()),
-                    })
-                    .unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()),
-                all_targets: self.data.check_allTargets,
-                no_default_features: self
-                    .data
-                    .check_noDefaultFeatures
-                    .unwrap_or(self.data.cargo_noDefaultFeatures),
-                all_features: matches!(
-                    self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features),
-                    CargoFeaturesDef::All
-                ),
-                features: match self
-                    .data
-                    .check_features
-                    .clone()
-                    .unwrap_or_else(|| self.data.cargo_features.clone())
-                {
-                    CargoFeaturesDef::All => vec![],
-                    CargoFeaturesDef::Selected(it) => it,
-                },
-                extra_args: self.check_extra_args(),
-                extra_env: self.check_extra_env(),
-                ansi_color_output: self.color_diagnostic_output(),
-            },
+            Some(_) | None => {
+                let mut extra_args = self.check_extra_args();
+                add_target_dir_to_args(&mut extra_args, target_directory);
+
+                FlycheckConfig::CargoCommand {
+                    command: self.data.check_command.clone(),
+                    target_triples: self
+                        .data
+                        .check_targets
+                        .clone()
+                        .and_then(|targets| match &targets.0[..] {
+                            [] => None,
+                            targets => Some(targets.into()),
+                        })
+                        .unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()),
+                    all_targets: self.data.check_allTargets,
+                    no_default_features: self
+                        .data
+                        .check_noDefaultFeatures
+                        .unwrap_or(self.data.cargo_noDefaultFeatures),
+                    all_features: matches!(
+                        self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features),
+                        CargoFeaturesDef::All
+                    ),
+                    features: match self
+                        .data
+                        .check_features
+                        .clone()
+                        .unwrap_or_else(|| self.data.cargo_features.clone())
+                    {
+                        CargoFeaturesDef::All => vec![],
+                        CargoFeaturesDef::Selected(it) => it,
+                    },
+                    extra_args,
+                    extra_env: self.check_extra_env(),
+                    ansi_color_output: self.color_diagnostic_output(),
+                }
+            }
         }
     }
 
+    fn target_dir_from_config(&self) -> Option<String> {
+        self.data
+            .rust_analyzerTargetDir
+            .as_ref()
+            .map(|target_dir| match target_dir {
+                TargetDirectory::UseSubdirectory(yes) if *yes => {
+                    Some(String::from("target/rust-analyzer"))
+                }
+                TargetDirectory::UseSubdirectory(_) => None,
+                TargetDirectory::Directory(dir) => Some(dir.clone()),
+            })
+            .flatten()
+    }
+
     pub fn check_on_save(&self) -> bool {
         self.data.checkOnSave
     }
@@ -1690,6 +1725,13 @@ impl Config {
         self.is_visual_studio_code
     }
 }
+
+fn add_target_dir_to_args(args: &mut Vec<String>, target_dir: Option<String>) {
+    if let Some(target_dir) = target_dir {
+        args.push(format!("--target-dir={}", target_dir));
+    }
+}
+
 // Deserialization definitions
 
 macro_rules! create_bool_or_string_de {
@@ -2037,6 +2079,14 @@ pub enum MemoryLayoutHoverRenderKindDef {
     Both,
 }
 
+#[derive(Deserialize, Debug, Clone, PartialEq)]
+#[serde(rename_all = "snake_case")]
+#[serde(untagged)]
+pub enum TargetDirectory {
+    UseSubdirectory(bool),
+    Directory(String),
+}
+
 macro_rules! _config_data {
     (struct $name:ident {
         $(
@@ -2465,6 +2515,19 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
                 },
             ],
         },
+        "Option<TargetDirectory>" => set! {
+            "anyOf": [
+                {
+                    "type": "null"
+                },
+                {
+                    "type": "boolean"
+                },
+                {
+                    "type": "string"
+                },
+            ],
+        },
         _ => panic!("missing entry for {ty}: {default}"),
     }
 
@@ -2625,4 +2688,73 @@ mod tests {
             Some(AbsPathBuf::try_from(project_root().join("./server")).unwrap())
         );
     }
+
+    #[test]
+    fn cargo_target_dir_unset() {
+        let mut config = Config::new(
+            AbsPathBuf::try_from(project_root()).unwrap(),
+            Default::default(),
+            vec![],
+            false,
+        );
+        config
+            .update(serde_json::json!({
+                "rust": { "analyzerTargetDir": null }
+            }))
+            .unwrap();
+        assert_eq!(config.data.rust_analyzerTargetDir, None);
+        assert_eq!(config.cargo().extra_args.len(), 0);
+        assert!(
+            matches!(config.flycheck(), FlycheckConfig::CargoCommand { extra_args, .. } if extra_args.is_empty())
+        );
+    }
+
+    #[test]
+    fn cargo_target_dir_subdir() {
+        let mut config = Config::new(
+            AbsPathBuf::try_from(project_root()).unwrap(),
+            Default::default(),
+            vec![],
+            false,
+        );
+        config
+            .update(serde_json::json!({
+                "rust": { "analyzerTargetDir": true }
+            }))
+            .unwrap();
+        assert_eq!(
+            config.data.rust_analyzerTargetDir,
+            Some(TargetDirectory::UseSubdirectory(true))
+        );
+        assert_eq!(
+            config.cargo().extra_args,
+            vec!["--target-dir=target/rust-analyzer".to_string()]
+        );
+        assert!(
+            matches!(config.flycheck(), FlycheckConfig::CargoCommand { extra_args, .. } if extra_args == vec!["--target-dir=target/rust-analyzer".to_string()])
+        );
+    }
+
+    #[test]
+    fn cargo_target_dir_relative_dir() {
+        let mut config = Config::new(
+            AbsPathBuf::try_from(project_root()).unwrap(),
+            Default::default(),
+            vec![],
+            false,
+        );
+        config
+            .update(serde_json::json!({
+                "rust": { "analyzerTargetDir": "other_folder" }
+            }))
+            .unwrap();
+        assert_eq!(
+            config.data.rust_analyzerTargetDir,
+            Some(TargetDirectory::Directory("other_folder".to_string()))
+        );
+        assert_eq!(config.cargo().extra_args, vec!["--target-dir=other_folder".to_string()]);
+        assert!(
+            matches!(config.flycheck(), FlycheckConfig::CargoCommand { extra_args, .. } if extra_args == vec!["--target-dir=other_folder".to_string()])
+        );
+    }
 }
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index dec7a507574..f7ae6afe386 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -757,6 +757,15 @@ Command to be executed instead of 'cargo' for runnables.
 Additional arguments to be passed to cargo for runnables such as
 tests or binaries. For example, it may be `--release`.
 --
+[[rust-analyzer.rust.analyzerTargetDir]]rust-analyzer.rust.analyzerTargetDir (default: `null`)::
++
+--
+Optional path to a rust-analyzer specific target directory.
+This is useful to prevent rust-analyzer's `cargo check` from blocking builds.
+
+Set to `true` to use a subdirectory of the existing target directory or
+set to a path to use that path.
+--
 [[rust-analyzer.rustc.source]]rust-analyzer.rustc.source (default: `null`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index 554b05c46c7..e1402cb0f5c 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -1488,6 +1488,21 @@
                         "type": "string"
                     }
                 },
+                "rust-analyzer.rust.analyzerTargetDir": {
+                    "markdownDescription": "Optional path to a rust-analyzer specific target directory.\nThis is useful to prevent rust-analyzer's `cargo check` from blocking builds.\n\nSet to `true` to use a subdirectory of the existing target directory or\nset to a path to use that path.",
+                    "default": null,
+                    "anyOf": [
+                        {
+                            "type": "null"
+                        },
+                        {
+                            "type": "boolean"
+                        },
+                        {
+                            "type": "string"
+                        }
+                    ]
+                },
                 "rust-analyzer.rustc.source": {
                     "markdownDescription": "Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private\nprojects, or \"discover\" to try to automatically find it if the `rustc-dev` component\nis installed.\n\nAny project which uses rust-analyzer with the rustcPrivate\ncrates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.\n\nThis option does not take effect until rust-analyzer is restarted.",
                     "default": null,