diff --git a/src/cargo/core/compiler/compile_kind.rs b/src/cargo/core/compiler/compile_kind.rs index 605bb27f2ca..c3c921d4146 100644 --- a/src/cargo/core/compiler/compile_kind.rs +++ b/src/cargo/core/compiler/compile_kind.rs @@ -52,11 +52,8 @@ impl CompileKind { config: &Config, targets: &[String], ) -> CargoResult> { - if targets.len() > 1 && !config.cli_unstable().multitarget { - bail!("specifying multiple `--target` flags requires `-Zmultitarget`") - } - if !targets.is_empty() { - return Ok(targets + let dedup = |targets: &[String]| { + Ok(targets .iter() .map(|value| Ok(CompileKind::Target(CompileTarget::new(value)?))) // First collect into a set to deduplicate any `--target` passed @@ -64,21 +61,22 @@ impl CompileKind { .collect::>>()? // ... then generate a flat list for everything else to use. .into_iter() - .collect()); - } - let kind = match &config.build_config()?.target { - Some(val) => { - let value = if val.raw_value().ends_with(".json") { - let path = val.clone().resolve_path(config); - path.to_str().expect("must be utf-8 in toml").to_string() - } else { - val.raw_value().to_string() - }; - CompileKind::Target(CompileTarget::new(&value)?) + .collect()) + }; + + if !targets.is_empty() { + if targets.len() > 1 && !config.cli_unstable().multitarget { + bail!("specifying multiple `--target` flags requires `-Zmultitarget`") } - None => CompileKind::Host, + return dedup(targets); + } + + let kinds = match &config.build_config()?.target { + None => Ok(vec![CompileKind::Host]), + Some(build_target_config) => dedup(&build_target_config.values(config)?), }; - Ok(vec![kind]) + + kinds } /// Hash used for fingerprinting. diff --git a/src/cargo/util/config/mod.rs b/src/cargo/util/config/mod.rs index 12d7301f58e..15414ece9af 100644 --- a/src/cargo/util/config/mod.rs +++ b/src/cargo/util/config/mod.rs @@ -2169,7 +2169,7 @@ pub struct CargoBuildConfig { pub dep_info_basedir: Option, pub target_dir: Option, pub incremental: Option, - pub target: Option, + pub target: Option, pub jobs: Option, pub rustflags: Option, pub rustdocflags: Option, @@ -2180,6 +2180,61 @@ pub struct CargoBuildConfig { pub out_dir: Option, } +/// Configuration for `build.target`. +/// +/// Accepts in the following forms: +/// +/// ```toml +/// target = "a" +/// target = ["a"] +/// target = ["a", "b"] +/// ``` +#[derive(Debug, Deserialize)] +#[serde(transparent)] +pub struct BuildTargetConfig { + inner: Value, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +enum BuildTargetConfigInner { + One(String), + Many(Vec), +} + +impl BuildTargetConfig { + /// Gets values of `build.target` as a list of strings. + pub fn values(&self, config: &Config) -> CargoResult> { + let map = |s: &String| { + if s.ends_with(".json") { + // Path to a target specification file (in JSON). + // + self.inner + .definition + .root(config) + .join(s) + .to_str() + .expect("must be utf-8 in toml") + .to_string() + } else { + // A string. Probably a target triple. + s.to_string() + } + }; + let values = match &self.inner.val { + BuildTargetConfigInner::One(s) => vec![map(s)], + BuildTargetConfigInner::Many(v) => { + if !config.cli_unstable().multitarget { + bail!("specifying an array in `build.target` config value requires `-Zmultitarget`") + } else { + v.iter().map(map).collect() + } + } + }; + Ok(values) + } +} + #[derive(Deserialize, Default)] struct TermConfig { verbose: Option, diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 1c1db0ba713..3ad2bb94eef 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -235,6 +235,12 @@ or running tests for both targets: cargo test --target x86_64-unknown-linux-gnu --target i686-unknown-linux-gnu ``` +This can also be specified in `.cargo/config.toml` files. + +```toml +[build] +target = ["x86_64-unknown-linux-gnu", "i686-unknown-linux-gnu"] +``` #### New `dir-name` attribute diff --git a/tests/testsuite/multitarget.rs b/tests/testsuite/multitarget.rs index afa8ea3c9bc..d4aced728f5 100644 --- a/tests/testsuite/multitarget.rs +++ b/tests/testsuite/multitarget.rs @@ -10,7 +10,44 @@ fn double_target_rejected() { .build(); p.cargo("build --target a --target b") - .with_stderr("error: specifying multiple `--target` flags requires `-Zmultitarget`") + .with_stderr("[ERROR] specifying multiple `--target` flags requires `-Zmultitarget`") + .with_status(101) + .run(); +} + +#[cargo_test] +fn array_of_target_rejected_with_config() { + let p = project() + .file("Cargo.toml", &basic_manifest("foo", "1.0.0")) + .file("src/main.rs", "fn main() {}") + .file( + ".cargo/config.toml", + r#" + [build] + target = ["a", "b"] + "#, + ) + .build(); + + p.cargo("build") + .with_stderr( + "[ERROR] specifying an array in `build.target` config value requires `-Zmultitarget`", + ) + .with_status(101) + .run(); + + p.change_file( + ".cargo/config.toml", + r#" + [build] + target = ["a"] + "#, + ); + + p.cargo("build") + .with_stderr( + "[ERROR] specifying an array in `build.target` config value requires `-Zmultitarget`", + ) .with_status(101) .run(); } @@ -39,6 +76,35 @@ fn simple_build() { assert!(p.target_bin(t2, "foo").is_file()); } +#[cargo_test] +fn simple_build_with_config() { + if cross_compile::disabled() { + return; + } + let t1 = cross_compile::alternate(); + let t2 = rustc_host(); + let p = project() + .file("Cargo.toml", &basic_manifest("foo", "1.0.0")) + .file("src/main.rs", "fn main() {}") + .file( + ".cargo/config.toml", + &format!( + r#" + [unstable] + multitarget = true + [build] + target = ["{t1}", "{t2}"] + "# + ), + ) + .build(); + + p.cargo("build").masquerade_as_nightly_cargo().run(); + + assert!(p.target_bin(t1, "foo").is_file()); + assert!(p.target_bin(t2, "foo").is_file()); +} + #[cargo_test] fn simple_test() { if !cross_compile::can_run_on_host() { @@ -70,7 +136,7 @@ fn simple_run() { .build(); p.cargo("run -Z multitarget --target a --target b") - .with_stderr("error: only one `--target` argument is supported") + .with_stderr("[ERROR] only one `--target` argument is supported") .with_status(101) .masquerade_as_nightly_cargo() .run(); @@ -142,3 +208,88 @@ fn same_value_twice() { assert!(p.target_bin(t, "foo").is_file()); } + +#[cargo_test] +fn same_value_twice_with_config() { + if cross_compile::disabled() { + return; + } + let t = rustc_host(); + let p = project() + .file("Cargo.toml", &basic_manifest("foo", "1.0.0")) + .file("src/main.rs", "fn main() {}") + .file( + ".cargo/config.toml", + &format!( + r#" + [unstable] + multitarget = true + [build] + target = ["{t}", "{t}"] + "# + ), + ) + .build(); + + p.cargo("build").masquerade_as_nightly_cargo().run(); + + assert!(p.target_bin(t, "foo").is_file()); +} + +#[cargo_test] +fn works_with_config_in_both_string_or_list() { + if cross_compile::disabled() { + return; + } + let t = rustc_host(); + let p = project() + .file("Cargo.toml", &basic_manifest("foo", "1.0.0")) + .file("src/main.rs", "fn main() {}") + .file( + ".cargo/config.toml", + &format!( + r#" + [unstable] + multitarget = true + [build] + target = "{t}" + "# + ), + ) + .build(); + + p.cargo("build").masquerade_as_nightly_cargo().run(); + + assert!(p.target_bin(t, "foo").is_file()); + + p.cargo("clean").run(); + + p.change_file( + ".cargo/config.toml", + &format!( + r#" + [unstable] + multitarget = true + [build] + target = ["{t}"] + "# + ), + ); + + p.cargo("build").masquerade_as_nightly_cargo().run(); + + assert!(p.target_bin(t, "foo").is_file()); +} + +#[cargo_test] +fn works_with_env() { + let t = rustc_host(); + let p = project() + .file("Cargo.toml", &basic_manifest("foo", "1.0.0")) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build").env("CARGO_BUILD_TARGET", t).run(); + + assert!(p.target_bin(t, "foo").is_file()); +}