From 65e8b977d3a201a326df0013e33b85824dd1a980 Mon Sep 17 00:00:00 2001 From: "Alexis (Poliorcetics) Bourget" Date: Sun, 25 Sep 2022 01:22:27 +0200 Subject: [PATCH] feat: Support passing multiple targets to cargo (for Rust 1.64.0+) --- crates/flycheck/src/lib.rs | 6 +-- crates/project-model/src/build_scripts.rs | 6 +-- crates/project-model/src/cargo_workspace.rs | 54 +++++++++++++++------ crates/project-model/src/project_json.rs | 1 + crates/project-model/src/rustc_cfg.rs | 10 ++-- crates/project-model/src/workspace.rs | 16 +++--- crates/rust-analyzer/src/config.rs | 24 +++++---- crates/rust-analyzer/src/reload.rs | 2 +- docs/user/generated_config.adoc | 12 +++-- editors/code/package.json | 24 ++++----- 10 files changed, 91 insertions(+), 64 deletions(-) diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index fdc03f4053a2..c086cabf7dc8 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs @@ -25,7 +25,7 @@ pub use cargo_metadata::diagnostic::{ pub enum FlycheckConfig { CargoCommand { command: String, - target_triple: Option, + target_triples: Vec, all_targets: bool, no_default_features: bool, all_features: bool, @@ -253,7 +253,7 @@ impl FlycheckActor { let mut cmd = match &self.config { FlycheckConfig::CargoCommand { command, - target_triple, + target_triples, no_default_features, all_targets, all_features, @@ -267,7 +267,7 @@ impl FlycheckActor { cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]) .arg(self.workspace_root.join("Cargo.toml").as_os_str()); - if let Some(target) = target_triple { + for target in target_triples { cmd.args(&["--target", target.as_str()]); } if *all_targets { diff --git a/crates/project-model/src/build_scripts.rs b/crates/project-model/src/build_scripts.rs index 32db42f1db75..7efccce6d3b6 100644 --- a/crates/project-model/src/build_scripts.rs +++ b/crates/project-model/src/build_scripts.rs @@ -52,12 +52,12 @@ impl WorkspaceBuildScripts { cmd.args(&["check", "--quiet", "--workspace", "--message-format=json"]); // --all-targets includes tests, benches and examples in addition to the - // default lib and bins. This is an independent concept from the --targets + // default lib and bins. This is an independent concept from the --target // flag below. cmd.arg("--all-targets"); - if let Some(target) = &config.target { - cmd.args(&["--target", target]); + for target in &config.targets { + cmd.args(&["--target", target.as_str()]); } match &config.features { diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs index bd2bbadea239..79aaa148454a 100644 --- a/crates/project-model/src/cargo_workspace.rs +++ b/crates/project-model/src/cargo_workspace.rs @@ -92,8 +92,8 @@ impl Default for CargoFeatures { pub struct CargoConfig { /// List of features to activate. pub features: CargoFeatures, - /// rustc target - pub target: Option, + /// rustc targets + pub targets: Vec, /// Don't load sysroot crates (`std`, `core` & friends). Might be useful /// when debugging isolated issues. pub no_sysroot: bool, @@ -269,11 +269,7 @@ impl CargoWorkspace { config: &CargoConfig, progress: &dyn Fn(String), ) -> Result { - let target = config - .target - .clone() - .or_else(|| cargo_config_build_target(cargo_toml, &config.extra_env)) - .or_else(|| rustc_discover_host_triple(cargo_toml, &config.extra_env)); + let targets = find_list_of_build_targets(config, cargo_toml); let mut meta = MetadataCommand::new(); meta.cargo_path(toolchain::cargo()); @@ -295,8 +291,12 @@ impl CargoWorkspace { } meta.current_dir(current_dir.as_os_str()); - if let Some(target) = target { - meta.other_options(vec![String::from("--filter-platform"), target]); + if !targets.is_empty() { + let other_options: Vec<_> = targets + .into_iter() + .flat_map(|target| ["--filter-platform".to_string(), target]) + .collect(); + meta.other_options(other_options); } // FIXME: Fetching metadata is a slow process, as it might require @@ -466,6 +466,19 @@ impl CargoWorkspace { } } +fn find_list_of_build_targets(config: &CargoConfig, cargo_toml: &ManifestPath) -> Vec { + if !config.targets.is_empty() { + return config.targets.clone(); + } + + let build_targets = cargo_config_build_target(cargo_toml, &config.extra_env); + if !build_targets.is_empty() { + return build_targets; + } + + rustc_discover_host_triple(cargo_toml, &config.extra_env).into_iter().collect() +} + fn rustc_discover_host_triple( cargo_toml: &ManifestPath, extra_env: &FxHashMap, @@ -496,7 +509,7 @@ fn rustc_discover_host_triple( fn cargo_config_build_target( cargo_toml: &ManifestPath, extra_env: &FxHashMap, -) -> Option { +) -> Vec { let mut cargo_config = Command::new(toolchain::cargo()); cargo_config.envs(extra_env); cargo_config @@ -504,12 +517,21 @@ fn cargo_config_build_target( .args(&["-Z", "unstable-options", "config", "get", "build.target"]) .env("RUSTC_BOOTSTRAP", "1"); // if successful we receive `build.target = "target-triple"` + // or `build.target = ["", ..]` tracing::debug!("Discovering cargo config target by {:?}", cargo_config); - match utf8_stdout(cargo_config) { - Ok(stdout) => stdout - .strip_prefix("build.target = \"") - .and_then(|stdout| stdout.strip_suffix('"')) - .map(ToOwned::to_owned), - Err(_) => None, + utf8_stdout(cargo_config).map(parse_output_cargo_config_build_target).unwrap_or_default() +} + +fn parse_output_cargo_config_build_target(stdout: String) -> Vec { + let trimmed = stdout.trim_start_matches("build.target = ").trim_matches('"'); + + if !trimmed.starts_with('[') { + return [trimmed.to_string()].to_vec(); + } + + let res = serde_json::from_str(trimmed); + if let Err(e) = &res { + tracing::warn!("Failed to parse `build.target` as an array of target: {}`", e); } + res.unwrap_or_default() } diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs index 5133a14d532b..24d203ccb06c 100644 --- a/crates/project-model/src/project_json.rs +++ b/crates/project-model/src/project_json.rs @@ -35,6 +35,7 @@ pub struct Crate { pub(crate) version: Option, pub(crate) deps: Vec, pub(crate) cfg: Vec, + // XXX: support multiple targets ? (Rust 1.64.0+) pub(crate) target: Option, pub(crate) env: FxHashMap, pub(crate) proc_macro_dylib_path: Option, diff --git a/crates/project-model/src/rustc_cfg.rs b/crates/project-model/src/rustc_cfg.rs index 323136183663..7595eabb959b 100644 --- a/crates/project-model/src/rustc_cfg.rs +++ b/crates/project-model/src/rustc_cfg.rs @@ -9,7 +9,7 @@ use crate::{cfg_flag::CfgFlag, utf8_stdout, ManifestPath}; pub(crate) fn get( cargo_toml: Option<&ManifestPath>, - target: Option<&str>, + targets: &[String], extra_env: &FxHashMap, ) -> Vec { let _p = profile::span("rustc_cfg::get"); @@ -23,7 +23,7 @@ pub(crate) fn get( } } - match get_rust_cfgs(cargo_toml, target, extra_env) { + match get_rust_cfgs(cargo_toml, targets, extra_env) { Ok(rustc_cfgs) => { tracing::debug!( "rustc cfgs found: {:?}", @@ -42,7 +42,7 @@ pub(crate) fn get( fn get_rust_cfgs( cargo_toml: Option<&ManifestPath>, - target: Option<&str>, + targets: &[String], extra_env: &FxHashMap, ) -> Result { if let Some(cargo_toml) = cargo_toml { @@ -52,7 +52,7 @@ fn get_rust_cfgs( .current_dir(cargo_toml.parent()) .args(&["-Z", "unstable-options", "rustc", "--print", "cfg"]) .env("RUSTC_BOOTSTRAP", "1"); - if let Some(target) = target { + for target in targets { cargo_config.args(&["--target", target]); } match utf8_stdout(cargo_config) { @@ -64,7 +64,7 @@ fn get_rust_cfgs( let mut cmd = Command::new(toolchain::rustc()); cmd.envs(extra_env); cmd.args(&["--print", "cfg", "-O"]); - if let Some(target) = target { + for target in targets { cmd.args(&["--target", target]); } utf8_stdout(cmd) diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index 2c0af99940f9..75cbc9ddd553 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -156,11 +156,7 @@ impl ProjectWorkspace { })?; let project_location = project_json.parent().to_path_buf(); let project_json = ProjectJson::new(&project_location, data); - ProjectWorkspace::load_inline( - project_json, - config.target.as_deref(), - &config.extra_env, - )? + ProjectWorkspace::load_inline(project_json, &config.targets, &config.extra_env)? } ProjectManifest::CargoToml(cargo_toml) => { let cargo_version = utf8_stdout({ @@ -226,7 +222,7 @@ impl ProjectWorkspace { }; let rustc_cfg = - rustc_cfg::get(Some(&cargo_toml), config.target.as_deref(), &config.extra_env); + rustc_cfg::get(Some(&cargo_toml), &config.targets, &config.extra_env); let cfg_overrides = config.cfg_overrides(); ProjectWorkspace::Cargo { @@ -246,7 +242,7 @@ impl ProjectWorkspace { pub fn load_inline( project_json: ProjectJson, - target: Option<&str>, + targets: &[String], extra_env: &FxHashMap, ) -> Result { let sysroot = match (project_json.sysroot.clone(), project_json.sysroot_src.clone()) { @@ -269,7 +265,7 @@ impl ProjectWorkspace { (None, None) => None, }; - let rustc_cfg = rustc_cfg::get(None, target, extra_env); + let rustc_cfg = rustc_cfg::get(None, targets, extra_env); Ok(ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg }) } @@ -281,7 +277,7 @@ impl ProjectWorkspace { .ok_or_else(|| format_err!("No detached files to load"))?, &Default::default(), )?; - let rustc_cfg = rustc_cfg::get(None, None, &Default::default()); + let rustc_cfg = rustc_cfg::get(None, &[], &Default::default()); Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg }) } @@ -499,7 +495,7 @@ fn project_json_to_crate_graph( let target_cfgs = match krate.target.as_deref() { Some(target) => cfg_cache .entry(target) - .or_insert_with(|| rustc_cfg::get(None, Some(target), extra_env)), + .or_insert_with(|| rustc_cfg::get(None, &[target.into()], extra_env)), None => &rustc_cfg, }; diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index ab600441e757..4acfd5ad1e3b 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -96,8 +96,10 @@ config_data! { cargo_noDefaultFeatures: bool = "false", /// Internal config for debugging, disables loading of sysroot crates. cargo_noSysroot: bool = "false", - /// Compilation target override (target triple). - cargo_target: Option = "null", + /// Compilation targets override (target triples). + /// Setting this with multiple elements is like passing multiple + /// `--target` flags to `cargo`, only available on Rust 1.64.0+. + cargo_target: Vec = "[]", /// Unsets `#[cfg(test)]` for the specified crates. cargo_unsetTest: Vec = "[\"core\"]", @@ -139,9 +141,9 @@ config_data! { /// ``` /// . checkOnSave_overrideCommand: Option> = "null", - /// Check for a specific target. Defaults to - /// `#rust-analyzer.cargo.target#`. - checkOnSave_target: Option = "null", + /// Check for specific targets. Defaults to + /// `#rust-analyzer.cargo.targets#`. + checkOnSave_target: Vec = "[]", /// Toggles the additional completions that automatically add imports when completed. /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. @@ -1037,7 +1039,7 @@ impl Config { no_default_features: self.data.cargo_noDefaultFeatures, }, }, - target: self.data.cargo_target.clone(), + targets: self.data.cargo_target.clone(), no_sysroot: self.data.cargo_noSysroot, rustc_source, unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()), @@ -1077,11 +1079,15 @@ impl Config { } Some(_) | None => FlycheckConfig::CargoCommand { command: self.data.checkOnSave_command.clone(), - target_triple: self + target_triples: self .data .checkOnSave_target - .clone() - .or_else(|| self.data.cargo_target.clone()), + .is_empty() + // If empty, fallback to `cargo_target` + .then_some(&self.data.cargo_target) + // Else use specified data + .unwrap_or(&self.data.checkOnSave_target) + .clone(), all_targets: self.data.checkOnSave_allTargets, no_default_features: self .data diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index e7f7972e9abb..1969245bf3ea 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -142,7 +142,7 @@ impl GlobalState { LinkedProject::InlineJsonProject(it) => { project_model::ProjectWorkspace::load_inline( it.clone(), - cargo_config.target.as_deref(), + &cargo_config.targets, &cargo_config.extra_env, ) } diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index a34f4d5093e3..d0505de3196b 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -69,10 +69,12 @@ Whether to pass `--no-default-features` to cargo. -- Internal config for debugging, disables loading of sysroot crates. -- -[[rust-analyzer.cargo.target]]rust-analyzer.cargo.target (default: `null`):: +[[rust-analyzer.cargo.target]]rust-analyzer.cargo.target (default: `[]`):: + -- -Compilation target override (target triple). +Compilation targets override (target triples). +Setting this with multiple elements is like passing multiple +`--target` flags to `cargo`, only available on Rust 1.64.0+. -- [[rust-analyzer.cargo.unsetTest]]rust-analyzer.cargo.unsetTest (default: `["core"]`):: + @@ -141,11 +143,11 @@ cargo check --workspace --message-format=json --all-targets ``` . -- -[[rust-analyzer.checkOnSave.target]]rust-analyzer.checkOnSave.target (default: `null`):: +[[rust-analyzer.checkOnSave.target]]rust-analyzer.checkOnSave.target (default: `[]`):: + -- -Check for a specific target. Defaults to -`#rust-analyzer.cargo.target#`. +Check for specific targets. Defaults to +`#rust-analyzer.cargo.targets#`. -- [[rust-analyzer.completion.autoimport.enable]]rust-analyzer.completion.autoimport.enable (default: `true`):: + diff --git a/editors/code/package.json b/editors/code/package.json index f8eec9f62e52..192c815a3eeb 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -474,12 +474,12 @@ "type": "boolean" }, "rust-analyzer.cargo.target": { - "markdownDescription": "Compilation target override (target triple).", - "default": null, - "type": [ - "null", - "string" - ] + "markdownDescription": "Compilation targets override (target triples).\nSetting this with multiple elements is like passing multiple\n`--target` flags to `cargo`, only available on Rust 1.64.0+.", + "default": [], + "type": "array", + "items": { + "type": "string" + } }, "rust-analyzer.cargo.unsetTest": { "markdownDescription": "Unsets `#[cfg(test)]` for the specified crates.", @@ -563,12 +563,12 @@ } }, "rust-analyzer.checkOnSave.target": { - "markdownDescription": "Check for a specific target. Defaults to\n`#rust-analyzer.cargo.target#`.", - "default": null, - "type": [ - "null", - "string" - ] + "markdownDescription": "Check for specific targets. Defaults to\n`#rust-analyzer.cargo.targets#`.", + "default": [], + "type": "array", + "items": { + "type": "string" + } }, "rust-analyzer.completion.autoimport.enable": { "markdownDescription": "Toggles the additional completions that automatically add imports when completed.\nNote that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.",