Skip to content

Commit aeef7b6

Browse files
committed
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 rust-lang#6007
1 parent 7e9b25b commit aeef7b6

File tree

3 files changed

+190
-34
lines changed

3 files changed

+190
-34
lines changed

crates/rust-analyzer/src/config.rs

+166-34
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,13 @@ config_data! {
480480
/// tests or binaries. For example, it may be `--release`.
481481
runnables_extraArgs: Vec<String> = "[]",
482482

483+
/// Optional path to a rust-analyzer specific target directory.
484+
/// This is useful to prevent rust-analyzer's `cargo check` from blocking builds.
485+
///
486+
/// Set to `true` to use a subdirectory of the existing target directory or
487+
/// set to a path to use that path.
488+
rust_analyzerTargetDir: Option<TargetDirectory> = "null",
489+
483490
/// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
484491
/// projects, or "discover" to try to automatically find it if the `rustc-dev` component
485492
/// is installed.
@@ -1192,6 +1199,7 @@ impl Config {
11921199
}
11931200

11941201
pub fn cargo(&self) -> CargoConfig {
1202+
let target_directory = self.target_dir_from_config();
11951203
let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| {
11961204
if rustc_src == "discover" {
11971205
RustLibSource::Discover
@@ -1209,6 +1217,10 @@ impl Config {
12091217
let sysroot_src =
12101218
self.data.cargo_sysrootSrc.as_ref().map(|sysroot| self.root_path.join(sysroot));
12111219

1220+
let mut extra_args = self.data.cargo_extraArgs.clone();
1221+
1222+
add_target_dir_to_args(&mut extra_args, target_directory);
1223+
12121224
CargoConfig {
12131225
features: match &self.data.cargo_features {
12141226
CargoFeaturesDef::All => CargoFeatures::All,
@@ -1261,7 +1273,7 @@ impl Config {
12611273
InvocationLocation::Workspace => project_model::InvocationLocation::Workspace,
12621274
},
12631275
run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
1264-
extra_args: self.data.cargo_extraArgs.clone(),
1276+
extra_args,
12651277
extra_env: self.data.cargo_extraEnv.clone(),
12661278
}
12671279
}
@@ -1281,10 +1293,14 @@ impl Config {
12811293
}
12821294

12831295
pub fn flycheck(&self) -> FlycheckConfig {
1296+
let target_directory = self.target_dir_from_config();
1297+
12841298
match &self.data.check_overrideCommand {
12851299
Some(args) if !args.is_empty() => {
12861300
let mut args = args.clone();
12871301
let command = args.remove(0);
1302+
add_target_dir_to_args(&mut args, target_directory);
1303+
12881304
FlycheckConfig::CustomCommand {
12891305
command,
12901306
args,
@@ -1303,42 +1319,61 @@ impl Config {
13031319
},
13041320
}
13051321
}
1306-
Some(_) | None => FlycheckConfig::CargoCommand {
1307-
command: self.data.check_command.clone(),
1308-
target_triples: self
1309-
.data
1310-
.check_targets
1311-
.clone()
1312-
.and_then(|targets| match &targets.0[..] {
1313-
[] => None,
1314-
targets => Some(targets.into()),
1315-
})
1316-
.unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()),
1317-
all_targets: self.data.check_allTargets,
1318-
no_default_features: self
1319-
.data
1320-
.check_noDefaultFeatures
1321-
.unwrap_or(self.data.cargo_noDefaultFeatures),
1322-
all_features: matches!(
1323-
self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features),
1324-
CargoFeaturesDef::All
1325-
),
1326-
features: match self
1327-
.data
1328-
.check_features
1329-
.clone()
1330-
.unwrap_or_else(|| self.data.cargo_features.clone())
1331-
{
1332-
CargoFeaturesDef::All => vec![],
1333-
CargoFeaturesDef::Selected(it) => it,
1334-
},
1335-
extra_args: self.check_extra_args(),
1336-
extra_env: self.check_extra_env(),
1337-
ansi_color_output: self.color_diagnostic_output(),
1338-
},
1322+
Some(_) | None => {
1323+
let mut extra_args = self.check_extra_args();
1324+
add_target_dir_to_args(&mut extra_args, target_directory);
1325+
1326+
FlycheckConfig::CargoCommand {
1327+
command: self.data.check_command.clone(),
1328+
target_triples: self
1329+
.data
1330+
.check_targets
1331+
.clone()
1332+
.and_then(|targets| match &targets.0[..] {
1333+
[] => None,
1334+
targets => Some(targets.into()),
1335+
})
1336+
.unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()),
1337+
all_targets: self.data.check_allTargets,
1338+
no_default_features: self
1339+
.data
1340+
.check_noDefaultFeatures
1341+
.unwrap_or(self.data.cargo_noDefaultFeatures),
1342+
all_features: matches!(
1343+
self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features),
1344+
CargoFeaturesDef::All
1345+
),
1346+
features: match self
1347+
.data
1348+
.check_features
1349+
.clone()
1350+
.unwrap_or_else(|| self.data.cargo_features.clone())
1351+
{
1352+
CargoFeaturesDef::All => vec![],
1353+
CargoFeaturesDef::Selected(it) => it,
1354+
},
1355+
extra_args,
1356+
extra_env: self.check_extra_env(),
1357+
ansi_color_output: self.color_diagnostic_output(),
1358+
}
1359+
}
13391360
}
13401361
}
13411362

1363+
fn target_dir_from_config(&self) -> Option<String> {
1364+
self.data
1365+
.rust_analyzerTargetDir
1366+
.as_ref()
1367+
.map(|target_dir| match target_dir {
1368+
TargetDirectory::UseSubdirectory(yes) if *yes => {
1369+
Some(String::from("target/rust-analyzer"))
1370+
}
1371+
TargetDirectory::UseSubdirectory(_) => None,
1372+
TargetDirectory::Directory(dir) => Some(dir.clone()),
1373+
})
1374+
.flatten()
1375+
}
1376+
13421377
pub fn check_on_save(&self) -> bool {
13431378
self.data.checkOnSave
13441379
}
@@ -1690,6 +1725,13 @@ impl Config {
16901725
self.is_visual_studio_code
16911726
}
16921727
}
1728+
1729+
fn add_target_dir_to_args(args: &mut Vec<String>, target_dir: Option<String>) {
1730+
if let Some(target_dir) = target_dir {
1731+
args.push(format!("--target-dir={}", target_dir));
1732+
}
1733+
}
1734+
16931735
// Deserialization definitions
16941736

16951737
macro_rules! create_bool_or_string_de {
@@ -2037,6 +2079,14 @@ pub enum MemoryLayoutHoverRenderKindDef {
20372079
Both,
20382080
}
20392081

2082+
#[derive(Deserialize, Debug, Clone, PartialEq)]
2083+
#[serde(rename_all = "snake_case")]
2084+
#[serde(untagged)]
2085+
pub enum TargetDirectory {
2086+
UseSubdirectory(bool),
2087+
Directory(String),
2088+
}
2089+
20402090
macro_rules! _config_data {
20412091
(struct $name:ident {
20422092
$(
@@ -2465,6 +2515,19 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
24652515
},
24662516
],
24672517
},
2518+
"Option<TargetDirectory>" => set! {
2519+
"anyOf": [
2520+
{
2521+
"type": "null"
2522+
},
2523+
{
2524+
"type": "boolean"
2525+
},
2526+
{
2527+
"type": "string"
2528+
},
2529+
],
2530+
},
24682531
_ => panic!("missing entry for {ty}: {default}"),
24692532
}
24702533

@@ -2625,4 +2688,73 @@ mod tests {
26252688
Some(AbsPathBuf::try_from(project_root().join("./server")).unwrap())
26262689
);
26272690
}
2691+
2692+
#[test]
2693+
fn cargo_target_dir_unset() {
2694+
let mut config = Config::new(
2695+
AbsPathBuf::try_from(project_root()).unwrap(),
2696+
Default::default(),
2697+
vec![],
2698+
false,
2699+
);
2700+
config
2701+
.update(serde_json::json!({
2702+
"rust": { "analyzerTargetDir": null }
2703+
}))
2704+
.unwrap();
2705+
assert_eq!(config.data.rust_analyzerTargetDir, None);
2706+
assert_eq!(config.cargo().extra_args.len(), 0);
2707+
assert!(
2708+
matches!(config.flycheck(), FlycheckConfig::CargoCommand { extra_args, .. } if extra_args.is_empty())
2709+
);
2710+
}
2711+
2712+
#[test]
2713+
fn cargo_target_dir_subdir() {
2714+
let mut config = Config::new(
2715+
AbsPathBuf::try_from(project_root()).unwrap(),
2716+
Default::default(),
2717+
vec![],
2718+
false,
2719+
);
2720+
config
2721+
.update(serde_json::json!({
2722+
"rust": { "analyzerTargetDir": true }
2723+
}))
2724+
.unwrap();
2725+
assert_eq!(
2726+
config.data.rust_analyzerTargetDir,
2727+
Some(TargetDirectory::UseSubdirectory(true))
2728+
);
2729+
assert_eq!(
2730+
config.cargo().extra_args,
2731+
vec!["--target-dir=target/rust-analyzer".to_string()]
2732+
);
2733+
assert!(
2734+
matches!(config.flycheck(), FlycheckConfig::CargoCommand { extra_args, .. } if extra_args == vec!["--target-dir=target/rust-analyzer".to_string()])
2735+
);
2736+
}
2737+
2738+
#[test]
2739+
fn cargo_target_dir_relative_dir() {
2740+
let mut config = Config::new(
2741+
AbsPathBuf::try_from(project_root()).unwrap(),
2742+
Default::default(),
2743+
vec![],
2744+
false,
2745+
);
2746+
config
2747+
.update(serde_json::json!({
2748+
"rust": { "analyzerTargetDir": "other_folder" }
2749+
}))
2750+
.unwrap();
2751+
assert_eq!(
2752+
config.data.rust_analyzerTargetDir,
2753+
Some(TargetDirectory::Directory("other_folder".to_string()))
2754+
);
2755+
assert_eq!(config.cargo().extra_args, vec!["--target-dir=other_folder".to_string()]);
2756+
assert!(
2757+
matches!(config.flycheck(), FlycheckConfig::CargoCommand { extra_args, .. } if extra_args == vec!["--target-dir=other_folder".to_string()])
2758+
);
2759+
}
26282760
}

docs/user/generated_config.adoc

+9
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,15 @@ Command to be executed instead of 'cargo' for runnables.
757757
Additional arguments to be passed to cargo for runnables such as
758758
tests or binaries. For example, it may be `--release`.
759759
--
760+
[[rust-analyzer.rust.analyzerTargetDir]]rust-analyzer.rust.analyzerTargetDir (default: `null`)::
761+
+
762+
--
763+
Optional path to a rust-analyzer specific target directory.
764+
This is useful to prevent rust-analyzer's `cargo check` from blocking builds.
765+
766+
Set to `true` to use a subdirectory of the existing target directory or
767+
set to a path to use that path.
768+
--
760769
[[rust-analyzer.rustc.source]]rust-analyzer.rustc.source (default: `null`)::
761770
+
762771
--

editors/code/package.json

+15
Original file line numberDiff line numberDiff line change
@@ -1488,6 +1488,21 @@
14881488
"type": "string"
14891489
}
14901490
},
1491+
"rust-analyzer.rust.analyzerTargetDir": {
1492+
"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.",
1493+
"default": null,
1494+
"anyOf": [
1495+
{
1496+
"type": "null"
1497+
},
1498+
{
1499+
"type": "boolean"
1500+
},
1501+
{
1502+
"type": "string"
1503+
}
1504+
]
1505+
},
14911506
"rust-analyzer.rustc.source": {
14921507
"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.",
14931508
"default": null,

0 commit comments

Comments
 (0)