about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/flycheck/src/lib.rs5
-rw-r--r--crates/project-model/src/build_scripts.rs4
-rw-r--r--crates/project-model/src/cargo_workspace.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs104
-rw-r--r--docs/user/generated_config.adoc10
-rw-r--r--editors/code/package.json15
6 files changed, 140 insertions, 0 deletions
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs
index 2de719af92c..0749d91eb32 100644
--- a/crates/flycheck/src/lib.rs
+++ b/crates/flycheck/src/lib.rs
@@ -50,6 +50,7 @@ pub enum FlycheckConfig {
         extra_args: Vec<String>,
         extra_env: FxHashMap<String, String>,
         ansi_color_output: bool,
+        target_dir: Option<PathBuf>,
     },
     CustomCommand {
         command: String,
@@ -308,6 +309,7 @@ impl FlycheckActor {
                 features,
                 extra_env,
                 ansi_color_output,
+                target_dir,
             } => {
                 let mut cmd = Command::new(toolchain::cargo());
                 cmd.arg(command);
@@ -340,6 +342,9 @@ impl FlycheckActor {
                         cmd.arg(features.join(" "));
                     }
                 }
+                if let Some(target_dir) = target_dir {
+                    cmd.arg("--target-dir").arg(target_dir);
+                }
                 cmd.envs(extra_env);
                 (cmd, extra_args)
             }
diff --git a/crates/project-model/src/build_scripts.rs b/crates/project-model/src/build_scripts.rs
index fb0f3ab7d17..68cd40c040b 100644
--- a/crates/project-model/src/build_scripts.rs
+++ b/crates/project-model/src/build_scripts.rs
@@ -73,6 +73,10 @@ impl WorkspaceBuildScripts {
                 cmd.args(["check", "--quiet", "--workspace", "--message-format=json"]);
                 cmd.args(&config.extra_args);
 
+                if let Some(target_dir) = &config.target_dir {
+                    cmd.arg("--target-dir").arg(target_dir);
+                }
+
                 // --all-targets includes tests, benches and examples in addition to the
                 // default lib and bins. This is an independent concept from the --target
                 // flag below.
diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs
index e47808a2cc9..ca3d6e0596c 100644
--- a/crates/project-model/src/cargo_workspace.rs
+++ b/crates/project-model/src/cargo_workspace.rs
@@ -96,6 +96,8 @@ pub struct CargoConfig {
     pub extra_env: FxHashMap<String, String>,
     pub invocation_strategy: InvocationStrategy,
     pub invocation_location: InvocationLocation,
+    /// Optional path to use instead of `target` when building
+    pub target_dir: Option<PathBuf>,
 }
 
 pub type Package = Idx<PackageData>;
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 8e780baa36d..c8df4255d96 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -480,6 +480,14 @@ 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 prevents rust-analyzer's `cargo check` from locking the `Cargo.lock`
+        /// at the expense of duplicating build artifacts.
+        ///
+        /// Set to `true` to use a subdirectory of the existing target directory or
+        /// set to a path relative to the workspace 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.
@@ -1263,6 +1271,7 @@ impl Config {
             run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
             extra_args: self.data.cargo_extraArgs.clone(),
             extra_env: self.data.cargo_extraEnv.clone(),
+            target_dir: self.target_dir_from_config(),
         }
     }
 
@@ -1335,10 +1344,21 @@ impl Config {
                 extra_args: self.check_extra_args(),
                 extra_env: self.check_extra_env(),
                 ansi_color_output: self.color_diagnostic_output(),
+                target_dir: self.target_dir_from_config(),
             },
         }
     }
 
+    fn target_dir_from_config(&self) -> Option<PathBuf> {
+        self.data.rust_analyzerTargetDir.as_ref().and_then(|target_dir| match target_dir {
+            TargetDirectory::UseSubdirectory(yes) if *yes => {
+                Some(PathBuf::from("target/rust-analyzer"))
+            }
+            TargetDirectory::UseSubdirectory(_) => None,
+            TargetDirectory::Directory(dir) => Some(dir.clone()),
+        })
+    }
+
     pub fn check_on_save(&self) -> bool {
         self.data.checkOnSave
     }
@@ -2037,6 +2057,14 @@ pub enum MemoryLayoutHoverRenderKindDef {
     Both,
 }
 
+#[derive(Deserialize, Debug, Clone, PartialEq)]
+#[serde(rename_all = "snake_case")]
+#[serde(untagged)]
+pub enum TargetDirectory {
+    UseSubdirectory(bool),
+    Directory(PathBuf),
+}
+
 macro_rules! _config_data {
     (struct $name:ident {
         $(
@@ -2465,6 +2493,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 +2666,67 @@ 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!(
+            matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir == None)
+        );
+    }
+
+    #[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!(
+            matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir == Some(PathBuf::from("target/rust-analyzer")))
+        );
+    }
+
+    #[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(PathBuf::from("other_folder")))
+        );
+        assert!(
+            matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir == Some(PathBuf::from("other_folder")))
+        );
+    }
 }
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index dec7a507574..7c76ae81bea 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -757,6 +757,16 @@ 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 prevents rust-analyzer's `cargo check` from locking the `Cargo.lock`
+at the expense of duplicating build artifacts.
+
+Set to `true` to use a subdirectory of the existing target directory or
+set to a path relative to the workspace 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..fc6597a0d40 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 prevents rust-analyzer's `cargo check` from locking the `Cargo.lock`\nat the expense of duplicating build artifacts.\n\nSet to `true` to use a subdirectory of the existing target directory or\nset to a path relative to the workspace 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,