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
This commit is contained in:
Victor Song 2023-09-29 03:49:37 -05:00
parent 7e9b25bff7
commit aeef7b644b
3 changed files with 190 additions and 34 deletions

View file

@ -480,6 +480,13 @@ config_data! {
/// tests or binaries. For example, it may be `--release`. /// tests or binaries. For example, it may be `--release`.
runnables_extraArgs: Vec<String> = "[]", 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 /// 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 /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
/// is installed. /// is installed.
@ -1192,6 +1199,7 @@ impl Config {
} }
pub fn cargo(&self) -> CargoConfig { 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| { let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| {
if rustc_src == "discover" { if rustc_src == "discover" {
RustLibSource::Discover RustLibSource::Discover
@ -1209,6 +1217,10 @@ impl Config {
let sysroot_src = let sysroot_src =
self.data.cargo_sysrootSrc.as_ref().map(|sysroot| self.root_path.join(sysroot)); 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 { CargoConfig {
features: match &self.data.cargo_features { features: match &self.data.cargo_features {
CargoFeaturesDef::All => CargoFeatures::All, CargoFeaturesDef::All => CargoFeatures::All,
@ -1261,7 +1273,7 @@ impl Config {
InvocationLocation::Workspace => project_model::InvocationLocation::Workspace, InvocationLocation::Workspace => project_model::InvocationLocation::Workspace,
}, },
run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(), 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(), extra_env: self.data.cargo_extraEnv.clone(),
} }
} }
@ -1281,10 +1293,14 @@ impl Config {
} }
pub fn flycheck(&self) -> FlycheckConfig { pub fn flycheck(&self) -> FlycheckConfig {
let target_directory = self.target_dir_from_config();
match &self.data.check_overrideCommand { match &self.data.check_overrideCommand {
Some(args) if !args.is_empty() => { Some(args) if !args.is_empty() => {
let mut args = args.clone(); let mut args = args.clone();
let command = args.remove(0); let command = args.remove(0);
add_target_dir_to_args(&mut args, target_directory);
FlycheckConfig::CustomCommand { FlycheckConfig::CustomCommand {
command, command,
args, args,
@ -1303,42 +1319,61 @@ impl Config {
}, },
} }
} }
Some(_) | None => FlycheckConfig::CargoCommand { Some(_) | None => {
command: self.data.check_command.clone(), let mut extra_args = self.check_extra_args();
target_triples: self add_target_dir_to_args(&mut extra_args, target_directory);
.data
.check_targets FlycheckConfig::CargoCommand {
.clone() command: self.data.check_command.clone(),
.and_then(|targets| match &targets.0[..] { target_triples: self
[] => None, .data
targets => Some(targets.into()), .check_targets
}) .clone()
.unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()), .and_then(|targets| match &targets.0[..] {
all_targets: self.data.check_allTargets, [] => None,
no_default_features: self targets => Some(targets.into()),
.data })
.check_noDefaultFeatures .unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()),
.unwrap_or(self.data.cargo_noDefaultFeatures), all_targets: self.data.check_allTargets,
all_features: matches!( no_default_features: self
self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features), .data
CargoFeaturesDef::All .check_noDefaultFeatures
), .unwrap_or(self.data.cargo_noDefaultFeatures),
features: match self all_features: matches!(
.data self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features),
.check_features CargoFeaturesDef::All
.clone() ),
.unwrap_or_else(|| self.data.cargo_features.clone()) features: match self
{ .data
CargoFeaturesDef::All => vec![], .check_features
CargoFeaturesDef::Selected(it) => it, .clone()
}, .unwrap_or_else(|| self.data.cargo_features.clone())
extra_args: self.check_extra_args(), {
extra_env: self.check_extra_env(), CargoFeaturesDef::All => vec![],
ansi_color_output: self.color_diagnostic_output(), 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 { pub fn check_on_save(&self) -> bool {
self.data.checkOnSave self.data.checkOnSave
} }
@ -1690,6 +1725,13 @@ impl Config {
self.is_visual_studio_code 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 // Deserialization definitions
macro_rules! create_bool_or_string_de { macro_rules! create_bool_or_string_de {
@ -2037,6 +2079,14 @@ pub enum MemoryLayoutHoverRenderKindDef {
Both, Both,
} }
#[derive(Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "snake_case")]
#[serde(untagged)]
pub enum TargetDirectory {
UseSubdirectory(bool),
Directory(String),
}
macro_rules! _config_data { macro_rules! _config_data {
(struct $name:ident { (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}"), _ => panic!("missing entry for {ty}: {default}"),
} }
@ -2625,4 +2688,73 @@ mod tests {
Some(AbsPathBuf::try_from(project_root().join("./server")).unwrap()) 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()])
);
}
} }

View file

@ -757,6 +757,15 @@ Command to be executed instead of 'cargo' for runnables.
Additional arguments to be passed to cargo for runnables such as Additional arguments to be passed to cargo for runnables such as
tests or binaries. For example, it may be `--release`. 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`):: [[rust-analyzer.rustc.source]]rust-analyzer.rustc.source (default: `null`)::
+ +
-- --

View file

@ -1488,6 +1488,21 @@
"type": "string" "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": { "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.", "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, "default": null,