diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 8e780baa36..3cd6fa49b2 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 = "[]", + /// 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 = "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 { + 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, target_dir: Option) { + 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" => 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 dec7a50757..f7ae6afe38 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 554b05c46c..e1402cb0f5 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,