Skip to content

Support -Zmultitarget in cargo config #10473

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 27 additions & 11 deletions src/cargo/core/compiler/compile_kind.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::core::Target;
use crate::util::config::BuildTargetConfigValue;
use crate::util::errors::CargoResult;
use crate::util::interning::InternedString;
use crate::util::{Config, StableHasher};
Expand Down Expand Up @@ -66,19 +67,34 @@ impl CompileKind {
.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)?)

let kinds = match &config.build_config()?.target {
None => vec![CompileKind::Host],
Some(build_target_config) => {
let values = build_target_config.values(config);
if values.len() > 1 && !config.cli_unstable().multitarget {
bail!("specifying multiple `--target` flags requires `-Zmultitarget`")
}
values
.into_iter()
.map(|k| {
use BuildTargetConfigValue::*;
let value = match &k {
Path(p) => p.to_str().expect("must be utf-8 in toml"),
Simple(s) => s,
};
CompileTarget::new(value).map(CompileKind::Target)
})
// First collect into a set to deduplicate any `--target` passed
// more than once...
.collect::<CargoResult<BTreeSet<_>>>()?
// ... then generate a flat list for everything else to use.
.into_iter()
.collect()
}
None => CompileKind::Host,
};
Ok(vec![kind])

Ok(kinds)
}

/// Hash used for fingerprinting.
Expand Down
53 changes: 52 additions & 1 deletion src/cargo/util/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2169,7 +2169,7 @@ pub struct CargoBuildConfig {
pub dep_info_basedir: Option<ConfigRelativePath>,
pub target_dir: Option<ConfigRelativePath>,
pub incremental: Option<bool>,
pub target: Option<ConfigRelativePath>,
pub target: Option<BuildTargetConfig>,
pub jobs: Option<u32>,
pub rustflags: Option<StringList>,
pub rustdocflags: Option<StringList>,
Expand All @@ -2180,6 +2180,57 @@ pub struct CargoBuildConfig {
pub out_dir: Option<ConfigRelativePath>,
}

/// 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<BuildTargetConfigInner>,
}

#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum BuildTargetConfigInner {
One(String),
Many(Vec<String>),
}

impl BuildTargetConfig {
/// Gets values of `build.target` as a list of [`BuildTargetConfigValue`].
pub fn values(&self, config: &Config) -> Vec<BuildTargetConfigValue<'_>> {
let def_root = self.inner.definition.root(config);
fn map<'a>(s: &'a str, root: &Path) -> BuildTargetConfigValue<'a> {
if s.ends_with(".json") {
// To absolute path.
BuildTargetConfigValue::Path(root.join(s))
} else {
BuildTargetConfigValue::Simple(s)
}
}
match &self.inner.val {
BuildTargetConfigInner::One(s) => vec![map(s, def_root)],
BuildTargetConfigInner::Many(v) => v.iter().map(|s| map(s, def_root)).collect(),
}
}
}

/// Represents a value of `build.target`.
#[derive(Debug)]
pub enum BuildTargetConfigValue<'a> {
/// Path to a target specification file (in JSON).
/// <https://doc.rust-lang.org/rustc/targets/custom.html>
Path(PathBuf),
/// A string. Probably a target triple.
Simple(&'a str),
}

#[derive(Deserialize, Default)]
struct TermConfig {
verbose: Option<bool>,
Expand Down
6 changes: 6 additions & 0 deletions src/doc/src/reference/unstable.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
141 changes: 139 additions & 2 deletions tests/testsuite/multitarget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,27 @@ 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 double_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 multiple `--target` flags requires `-Zmultitarget`")
.with_status(101)
.run();
}
Expand Down Expand Up @@ -39,6 +59,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() {
Expand Down Expand Up @@ -70,7 +119,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();
Expand Down Expand Up @@ -142,3 +191,91 @@ 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)
.masquerade_as_nightly_cargo()
.run();

assert!(p.target_bin(t, "foo").is_file());
}