diff --git a/src/cargo/core/compiler/build_context/target_info.rs b/src/cargo/core/compiler/build_context/target_info.rs index 325d4168bc3..8aff294bb7a 100644 --- a/src/cargo/core/compiler/build_context/target_info.rs +++ b/src/cargo/core/compiler/build_context/target_info.rs @@ -682,10 +682,15 @@ impl<'cfg> RustcTargetData<'cfg> { ) -> CargoResult> { let config = ws.config(); let rustc = config.load_global_rustc(Some(ws))?; - let host_config = config.target_cfg_triple(&rustc.host)?; - let host_info = TargetInfo::new(config, requested_kinds, &rustc, CompileKind::Host)?; let mut target_config = HashMap::new(); let mut target_info = HashMap::new(); + let target_applies_to_host = config.target_applies_to_host()?; + let host_info = TargetInfo::new(config, requested_kinds, &rustc, CompileKind::Host)?; + let host_config = if target_applies_to_host { + config.target_cfg_triple(&rustc.host)? + } else { + config.host_cfg_triple(&rustc.host)? + }; // This is a hack. The unit_dependency graph builder "pretends" that // `CompileKind::Host` is `CompileKind::Target(host)` if the @@ -695,8 +700,8 @@ impl<'cfg> RustcTargetData<'cfg> { if requested_kinds.iter().any(CompileKind::is_host) { let ct = CompileTarget::new(&rustc.host)?; target_info.insert(ct, host_info.clone()); - target_config.insert(ct, host_config.clone()); - } + target_config.insert(ct, config.target_cfg_triple(&rustc.host)?); + }; let mut res = RustcTargetData { rustc, diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 0e459d4db83..b0e28c15f35 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -600,6 +600,8 @@ unstable_cli_options!( namespaced_features: bool = ("Allow features with `dep:` prefix"), no_index_update: bool = ("Do not update the registry index even if the cache is outdated"), panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"), + host_config: bool = ("Enable the [host] section in the .cargo/config.toml file"), + target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"), patch_in_config: bool = ("Allow `[patch]` sections in .cargo/config.toml files"), rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"), separate_nightlies: bool = (HIDDEN), @@ -787,6 +789,8 @@ impl CliUnstable { "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?, "jobserver-per-rustc" => self.jobserver_per_rustc = parse_empty(k, v)?, "configurable-env" => self.configurable_env = parse_empty(k, v)?, + "host-config" => self.host_config = parse_empty(k, v)?, + "target-applies-to-host" => self.target_applies_to_host = parse_empty(k, v)?, "patch-in-config" => self.patch_in_config = parse_empty(k, v)?, "features" => { // For now this is still allowed (there are still some diff --git a/src/cargo/util/config/mod.rs b/src/cargo/util/config/mod.rs index b9921dfee26..71bbf78b456 100644 --- a/src/cargo/util/config/mod.rs +++ b/src/cargo/util/config/mod.rs @@ -1486,6 +1486,16 @@ impl Config { .try_borrow_with(|| self.get::("doc.extern-map")) } + /// Returns true if the `[target]` table should be applied to host targets. + pub fn target_applies_to_host(&self) -> CargoResult { + target::get_target_applies_to_host(self) + } + + /// Returns the `[host]` table definition for the given target triple. + pub fn host_cfg_triple(&self, target: &str) -> CargoResult { + target::load_host_triple(self, target) + } + /// Returns the `[target]` table definition for the given target triple. pub fn target_cfg_triple(&self, target: &str) -> CargoResult { target::load_target_triple(self, target) diff --git a/src/cargo/util/config/target.rs b/src/cargo/util/config/target.rs index 304679854ff..61168b39c59 100644 --- a/src/cargo/util/config/target.rs +++ b/src/cargo/util/config/target.rs @@ -19,7 +19,7 @@ pub struct TargetCfgConfig { pub other: BTreeMap, } -/// Config definition of a `[target]` table. +/// Config definition of a `[target]` table or `[host]`. #[derive(Debug, Clone)] pub struct TargetConfig { /// Process to run as a wrapper for `cargo run`, `test`, and `bench` commands. @@ -64,18 +64,62 @@ pub(super) fn load_target_cfgs(config: &Config) -> CargoResult CargoResult { + if config.cli_unstable().target_applies_to_host { + if let Ok(target_applies_to_host) = config.get::("target-applies-to-host") { + Ok(target_applies_to_host) + } else { + Ok(!config.cli_unstable().host_config) + } + } else { + if config.cli_unstable().host_config { + anyhow::bail!( + "the -Zhost-config flag requires the -Ztarget-applies-to-host flag to be set" + ); + } else { + Ok(true) + } + } +} + +/// Loads a single `[host]` table for the given triple. +pub(super) fn load_host_triple(config: &Config, triple: &str) -> CargoResult { + if config.cli_unstable().host_config { + let host_triple_prefix = format!("host.{}", triple); + let host_triple_key = ConfigKey::from_str(&host_triple_prefix); + let host_prefix = match config.get_cv(&host_triple_key)? { + Some(_) => host_triple_prefix, + None => "host".to_string(), + }; + load_config_table(config, &host_prefix) + } else { + Ok(TargetConfig { + runner: None, + rustflags: None, + linker: None, + links_overrides: BTreeMap::new(), + }) + } +} + /// Loads a single `[target]` table for the given triple. pub(super) fn load_target_triple(config: &Config, triple: &str) -> CargoResult { + load_config_table(config, &format!("target.{}", triple)) +} + +/// Loads a single table for the given prefix. +fn load_config_table(config: &Config, prefix: &str) -> CargoResult { // This needs to get each field individually because it cannot fetch the // struct all at once due to `links_overrides`. Can't use `serde(flatten)` // because it causes serde to use `deserialize_map` which means the config // deserializer does not know which keys to deserialize, which means // environment variables would not work. - let runner: OptValue = config.get(&format!("target.{}.runner", triple))?; - let rustflags: OptValue = config.get(&format!("target.{}.rustflags", triple))?; - let linker: OptValue = config.get(&format!("target.{}.linker", triple))?; + let runner: OptValue = config.get(&format!("{}.runner", prefix))?; + let rustflags: OptValue = config.get(&format!("{}.rustflags", prefix))?; + let linker: OptValue = config.get(&format!("{}.linker", prefix))?; // Links do not support environment variables. - let target_key = ConfigKey::from_str(&format!("target.{}", triple)); + let target_key = ConfigKey::from_str(prefix); let links_overrides = match config.get_table(&target_key)? { Some(links) => parse_links_overrides(&target_key, links.val, config)?, None => BTreeMap::new(), diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index e4befbccd69..5ee841a4879 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -705,6 +705,61 @@ cargo +nightly -Zunstable-options -Zconfig-include --config somefile.toml build CLI paths are relative to the current working directory. +### target-applies-to-host +* Original Pull Request: [#9322](https://github.com/rust-lang/cargo/pull/9322) +* Tracking Issue: [#9453](https://github.com/rust-lang/cargo/issues/9453) + +The `target-applies-to-host` key in a config file can be used set the desired +behavior for passing target config flags to build scripts. + +It requires the `-Ztarget-applies-to-host` command-line option. + +The current default for `target-applies-to-host` is `true`, which will be +changed to `false` in the future, if `-Zhost-config` is used the new `false` +default will be set for `target-applies-to-host`. + +```toml +# config.toml +target-applies-to-host = false +``` + +```console +cargo +nightly -Ztarget-applies-to-host build --target x86_64-unknown-linux-gnu +``` + +### host-config +* Original Pull Request: [#9322](https://github.com/rust-lang/cargo/pull/9322) +* Tracking Issue: [#9452](https://github.com/rust-lang/cargo/issues/9452) + +The `host` key in a config file can be used pass flags to host build targets +such as build scripts that must run on the host system instead of the target +system when cross compiling. It supports both generic and host arch specific +tables. Matching host arch tables take precedence over generic host tables. + +It requires the `-Zhost-config` and `-Ztarget-applies-to-host` command-line +options to be set. + +```toml +# config.toml +[host] +linker = "/path/to/host/linker" +[host.x86_64-unknown-linux-gnu] +linker = "/path/to/host/arch/linker" +[target.x86_64-unknown-linux-gnu] +linker = "/path/to/target/linker" +``` + +The generic `host` table above will be entirely ignored when building on a +`x86_64-unknown-linux-gnu` host as the `host.x86_64-unknown-linux-gnu` table +takes precedence. + +Setting `-Zhost-config` changes the default for `target-applies-to-host` to +`false` from `true`. + +```console +cargo +nightly -Ztarget-applies-to-host -Zhost-config build --target x86_64-unknown-linux-gnu +``` + ### unit-graph * Tracking Issue: [#8002](https://github.com/rust-lang/cargo/issues/8002) diff --git a/tests/testsuite/build_script.rs b/tests/testsuite/build_script.rs index 0f99f73c2b9..405dce96595 100644 --- a/tests/testsuite/build_script.rs +++ b/tests/testsuite/build_script.rs @@ -164,6 +164,393 @@ fn custom_build_env_var_rustc_linker() { p.cargo("build --target").arg(&target).run(); } +#[cargo_test] +fn custom_build_env_var_rustc_linker_bad_host_target() { + let target = rustc_host(); + let p = project() + .file( + ".cargo/config", + &format!( + r#" + [target.{}] + linker = "/path/to/linker" + "#, + target + ), + ) + .file( + "build.rs", + r#" + use std::env; + + fn main() { + assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker")); + } + "#, + ) + .file("src/lib.rs", "") + .build(); + + // build.rs should fail since host == target when no target is set + p.cargo("build --verbose") + .with_status(101) + .with_stderr_contains( + "\ +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/linker [..]` +[ERROR] linker `[..]/path/to/linker` not found +" + ) + .run(); +} + +#[cargo_test] +fn custom_build_env_var_rustc_linker_host_target() { + let target = rustc_host(); + let p = project() + .file( + ".cargo/config", + &format!( + r#" + target-applies-to-host = false + [target.{}] + linker = "/path/to/linker" + "#, + target + ), + ) + .file( + "build.rs", + r#" + use std::env; + + fn main() { + assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker")); + } + "#, + ) + .file("src/lib.rs", "") + .build(); + + // no crate type set => linker never called => build succeeds if and + // only if build.rs succeeds, despite linker binary not existing. + if cargo_test_support::is_nightly() { + p.cargo("build -Z target-applies-to-host --target") + .arg(&target) + .masquerade_as_nightly_cargo() + .run(); + } +} + +#[cargo_test] +fn custom_build_env_var_rustc_linker_host_target_env() { + let target = rustc_host(); + let p = project() + .file( + ".cargo/config", + &format!( + r#" + [target.{}] + linker = "/path/to/linker" + "#, + target + ), + ) + .file( + "build.rs", + r#" + use std::env; + + fn main() { + assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker")); + } + "#, + ) + .file("src/lib.rs", "") + .build(); + + // no crate type set => linker never called => build succeeds if and + // only if build.rs succeeds, despite linker binary not existing. + if cargo_test_support::is_nightly() { + p.cargo("build -Z target-applies-to-host --target") + .env("CARGO_TARGET_APPLIES_TO_HOST", "false") + .arg(&target) + .masquerade_as_nightly_cargo() + .run(); + } +} + +#[cargo_test] +fn custom_build_invalid_host_config_feature_flag() { + let target = rustc_host(); + let p = project() + .file( + ".cargo/config", + &format!( + r#" + [target.{}] + linker = "/path/to/linker" + "#, + target + ), + ) + .file( + "build.rs", + r#" + use std::env; + + fn main() { + assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker")); + } + "#, + ) + .file("src/lib.rs", "") + .build(); + + // build.rs should fail due to -Zhost-config being set without -Ztarget-applies-to-host + if cargo_test_support::is_nightly() { + p.cargo("build -Z host-config --target") + .arg(&target) + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr_contains( + "\ +error: the -Zhost-config flag requires the -Ztarget-applies-to-host flag to be set +", + ) + .run(); + } +} + +#[cargo_test] +fn custom_build_env_var_rustc_linker_host_target_with_bad_host_config() { + let target = rustc_host(); + let p = project() + .file( + ".cargo/config", + &format!( + r#" + target-applies-to-host = true + [host] + linker = "/path/to/host/linker" + [target.{}] + linker = "/path/to/target/linker" + "#, + target + ), + ) + .file( + "build.rs", + r#" + use std::env; + + fn main() { + assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/target/linker")); + } + "#, + ) + .file("src/lib.rs", "") + .build(); + + // build.rs should fail due to bad target linker being set + if cargo_test_support::is_nightly() { + p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target") + .arg(&target) + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr_contains( + "\ +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/target/linker [..]` +[ERROR] linker `[..]/path/to/target/linker` not found +" + ) + .run(); + } +} + +#[cargo_test] +fn custom_build_env_var_rustc_linker_bad_host() { + let target = rustc_host(); + let p = project() + .file( + ".cargo/config", + &format!( + r#" + [host] + linker = "/path/to/host/linker" + [target.{}] + linker = "/path/to/target/linker" + "#, + target + ), + ) + .file( + "build.rs", + r#" + use std::env; + + fn main() { + assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/target/linker")); + } + "#, + ) + .file("src/lib.rs", "") + .build(); + + // build.rs should fail due to bad host linker being set + if cargo_test_support::is_nightly() { + p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target") + .arg(&target) + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr_contains( + "\ +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/host/linker [..]` +[ERROR] linker `[..]/path/to/host/linker` not found +" + ) + .run(); + } +} + +#[cargo_test] +fn custom_build_env_var_rustc_linker_bad_host_with_arch() { + let target = rustc_host(); + let p = project() + .file( + ".cargo/config", + &format!( + r#" + [host] + linker = "/path/to/host/linker" + [host.{}] + linker = "/path/to/host/arch/linker" + [target.{}] + linker = "/path/to/target/linker" + "#, + target, target + ), + ) + .file( + "build.rs", + r#" + use std::env; + + fn main() { + assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/target/linker")); + } + "#, + ) + .file("src/lib.rs", "") + .build(); + + // build.rs should fail due to bad host linker being set + if cargo_test_support::is_nightly() { + p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target") + .arg(&target) + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr_contains( + "\ +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/host/arch/linker [..]` +[ERROR] linker `[..]/path/to/host/arch/linker` not found +" + ) + .run(); + } +} + +#[cargo_test] +fn custom_build_env_var_rustc_linker_cross_arch_host() { + let target = rustc_host(); + let cross_target = cross_compile::alternate(); + let p = project() + .file( + ".cargo/config", + &format!( + r#" + [host.{}] + linker = "/path/to/host/arch/linker" + [target.{}] + linker = "/path/to/target/linker" + "#, + cross_target, target + ), + ) + .file( + "build.rs", + r#" + use std::env; + + fn main() { + assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/target/linker")); + } + "#, + ) + .file("src/lib.rs", "") + .build(); + + // build.rs should fail due to bad host linker being set + if cargo_test_support::is_nightly() { + p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target") + .arg(&target) + .masquerade_as_nightly_cargo() + .run(); + } +} + +#[cargo_test] +fn custom_build_env_var_rustc_linker_bad_cross_arch_host() { + let target = rustc_host(); + let cross_target = cross_compile::alternate(); + let p = project() + .file( + ".cargo/config", + &format!( + r#" + [host] + linker = "/path/to/host/linker" + [host.{}] + linker = "/path/to/host/arch/linker" + [target.{}] + linker = "/path/to/target/linker" + "#, + cross_target, target + ), + ) + .file( + "build.rs", + r#" + use std::env; + + fn main() { + assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/target/linker")); + } + "#, + ) + .file("src/lib.rs", "") + .build(); + + // build.rs should fail due to bad host linker being set + if cargo_test_support::is_nightly() { + p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target") + .arg(&target) + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr_contains( + "\ +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/host/linker [..]` +[ERROR] linker `[..]/path/to/host/linker` not found +" + ) + .run(); + } +} + #[cargo_test] fn custom_build_script_wrong_rustc_flags() { let p = project()