diff --git a/src/cargo/core/compiler/build_context/target_info.rs b/src/cargo/core/compiler/build_context/target_info.rs index 32f767ea718..7c569669857 100644 --- a/src/cargo/core/compiler/build_context/target_info.rs +++ b/src/cargo/core/compiler/build_context/target_info.rs @@ -606,22 +606,35 @@ fn env_args( kind: CompileKind, flags: Flags, ) -> CargoResult> { + let targeted_rustflags = config.cli_unstable().targeted_rustflags; let target_applies_to_host = config.target_applies_to_host()?; // Host artifacts should not generally pick up rustflags from anywhere except [host]. // // The one exception to this is if `target-applies-to-host = true`, which opts into a // particular (inconsistent) past Cargo behavior where host artifacts _do_ pick up rustflags - // set elsewhere when `--target` isn't passed. + // set elsewhere when `--target` isn't passed. Or, phrased differently, with + // `target-applies-to-host = false`, no `--target` behaves the same as `--target `, and + // host artifacts are always "special" (they don't pick up `target.*` or `RUSTFLAGS` for + // example). if kind.is_host() { if target_applies_to_host && requested_kinds == [CompileKind::Host] { // This is the past Cargo behavior where we fall back to the same logic as for other // artifacts without --target. + } else if let Some(v) = rustflags_from_env( + targeted_rustflags, + host_triple, + kind, + false, + target_applies_to_host, + flags, + ) { + // An environment variable that specifically applies to host artifacts was given. + // Use that! + return Ok(v); } else { - // In all other cases, host artifacts just get flags from [host], regardless of - // --target. Or, phrased differently, no `--target` behaves the same as `--target - // `, and host artifacts are always "special" (they don't pick up `RUSTFLAGS` for - // example). + // Otherwise, check [host], which is the only other place that can specify rustflags + // that will apply to host artifacts. return Ok(rustflags_from_host(config, flags, host_triple)?.unwrap_or_else(Vec::new)); } } @@ -630,7 +643,14 @@ fn env_args( // NOTE: It is impossible to have a [host] section and reach this logic with kind.is_host(), // since [host] implies `target-applies-to-host = false`, which always early-returns above. - if let Some(rustflags) = rustflags_from_env(flags) { + if let Some(rustflags) = rustflags_from_env( + targeted_rustflags, + host_triple, + kind, + true, + target_applies_to_host, + flags, + ) { Ok(rustflags) } else if let Some(rustflags) = rustflags_from_target(config, host_triple, target_cfg, kind, flags)? @@ -643,28 +663,115 @@ fn env_args( } } -fn rustflags_from_env(flags: Flags) -> Option> { - // First try CARGO_ENCODED_RUSTFLAGS from the environment. - // Prefer this over RUSTFLAGS since it's less prone to encoding errors. - if let Ok(a) = env::var(format!("CARGO_ENCODED_{}", flags.as_env())) { - if a.is_empty() { - return Some(Vec::new()); +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +enum Specificity { + Generic = 0, + Kind = 1, + Target = 2, +} + +fn get_var_variants( + targeted_rustflags: bool, + var_base: &str, + host_triple: &str, + kind: CompileKind, + allow_generic: bool, + target_applies_to_host: bool, +) -> Option<(Specificity, String)> { + if !targeted_rustflags { + return if allow_generic { + env::var(var_base).ok().map(|v| (Specificity::Generic, v)) + } else { + None + }; + } + + let compiling_for = match &kind { + CompileKind::Host => host_triple, + CompileKind::Target(target) => target.short_name(), + }; + let target_in_env: String = compiling_for + .chars() + .flat_map(|c| c.to_uppercase()) + .map(|c| if c == '-' { '_' } else { c }) + .collect(); + if !kind.is_host() || target_applies_to_host { + if let Ok(v) = env::var(format!("{}_{}", var_base, target_in_env)) { + return Some((Specificity::Target, v)); } - return Some(a.split('\x1f').map(str::to_string).collect()); } + let kind = if kind.is_host() { "HOST" } else { "TARGET" }; + if let Ok(v) = env::var(format!("{}_{}", kind, var_base)) { + return Some((Specificity::Kind, v)); + } + // NOTE: TARGET_RUSTFLAGS never applies to host artifacts, even when + // target-applies-to-host = true, and even if --target isn't passed. + if allow_generic { + env::var(var_base).ok().map(|v| (Specificity::Generic, v)) + } else { + None + } +} - // Then try RUSTFLAGS from the environment - if let Ok(a) = env::var(flags.as_env()) { +fn rustflags_from_env( + targeted_rustflags: bool, + host_triple: &str, + kind: CompileKind, + allow_generic: bool, + target_applies_to_host: bool, + flag: Flags, +) -> Option> { + let encoded = get_var_variants( + targeted_rustflags, + &format!("CARGO_ENCODED_{}", flag.as_env()), + host_triple, + kind, + allow_generic, + target_applies_to_host, + ) + .map(|(s, a)| { + if a.is_empty() { + (s, Vec::new()) + } else { + (s, a.split('\x1f').map(str::to_string).collect()) + } + }); + let spacesep = get_var_variants( + targeted_rustflags, + flag.as_env(), + host_triple, + kind, + allow_generic, + target_applies_to_host, + ) + .map(|(s, a)| { let args = a .split(' ') .map(str::trim) .filter(|s| !s.is_empty()) .map(str::to_string); - return Some(args.collect()); - } + (s, args.collect()) + }); - // No rustflags to be collected from the environment - None + match (encoded, spacesep) { + (Some((_, v)), None) | (None, Some((_, v))) => { + // If just one is set, use that. + Some(v) + } + (None, None) => { + // If neither is set, well, there's nothing to do here. + None + } + (Some((enc_s, enc_v)), Some((spc_s, spc_v))) => { + // If _both_ are set, prefer the more specific one. + // Ties go to CARGO_ENCODED_RUSTFLAGS since it's less prone to encoding errors. + if enc_s >= spc_s { + Some(enc_v) + } else { + Some(spc_v) + } + } + } } fn rustflags_from_target( diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index f4104e8c9b0..4bf42139f96 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -657,6 +657,7 @@ unstable_cli_options!( host_config: bool = ("Enable the [host] section in the .cargo/config.toml file"), http_registry: bool = ("Support HTTP-based crate registries"), target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"), + targeted_rustflags: bool = ("Make Cargo respect target-specific variants for RUSTFLAGS and CARGO_ENCODED_RUSTFLAGS"), rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"), separate_nightlies: bool = (HIDDEN), terminal_width: Option> = ("Provide a terminal width to rustc for error truncation"), @@ -861,6 +862,7 @@ impl CliUnstable { "jobserver-per-rustc" => self.jobserver_per_rustc = 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)?, + "targeted-rustflags" => self.targeted_rustflags = parse_empty(k, v)?, "features" => { // For now this is still allowed (there are still some // unstable options like "compare"). This should be removed at diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 5c151200f57..6c793d44066 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -1202,6 +1202,33 @@ For instance: cargo check -Z unstable-options -Z check-cfg-features ``` +### targeted-rustflags + +The `-Z targeted-rustflags` argument tells Cargo to also look for +target-specific variants of the `RUSTFLAGS` and +`CARGO_ENCODED_RUSTFLAGS` variables (and the same for `RUSTDOCFLAGS`). +The following variants are supported: + +1. `_` - for example, `RUSTFLAGS_X86_64_UNKNOWN_LINUX_GNU` +1. `_` - for example, `HOST_RUSTFLAGS` or `TARGET_RUSTFLAGS` + +`HOST_RUSTFLAGS` allows setting flags to be passed to rustc for +host-artifacts (like build scripts) when cross-compiling, or _all_ +artifacts when not cross-compiling. See also the `host-config` feature. +Note that Cargo considers _any_ `--target` as cross-compiling, even if +the specified target is equal to the host's target triple. +`TARGET_RUSTFLAGS` specifically applies to all artifacts being built for +the target triple specified with `--target`. + +This feature interacts with the `target-applies-to-host` setting. When +set to `false`, `_` will _not_ apply to artifacts +built for the host, like build scripts. + +More specific flags always take precedence over less specific ones, with +ties broken in favor of `CARGO_ENCODED_RUSTFLAGS`. So, for example, if +`RUSTFLAGS_$TARGET` and `TARGET_CARGO_ENCODED_RUSTFLAGS` are both +specified, `RUSTFLAGS_$TARGET` would be used. + ### check-cfg-well-known-names * RFC: [#3013](https://github.com/rust-lang/rfcs/pull/3013) diff --git a/tests/testsuite/rustflags.rs b/tests/testsuite/rustflags.rs index e16b8ea005e..4e3ca415f91 100644 --- a/tests/testsuite/rustflags.rs +++ b/tests/testsuite/rustflags.rs @@ -51,6 +51,176 @@ fn env_rustflags_normal_source() { .run(); } +#[cargo_test] +fn env_rustflags_variants() { + let p = project() + .file( + "src/lib.rs", + r#" + #[cfg(not(for_lib))] + compile_error!("did not use the expected rustflags"); + "#, + ) + .file( + "build.rs", + r#" + fn main() { assert!(cfg!(for_brs)) } + "#, + ) + .build(); + let host = rustc_host(); + + // First, check that the extended rustflags are feature-gated. + p.cargo("build") + .arg("--target") + .arg(&host) + .env( + &format!("RUSTFLAGS_{}", host.replace('-', "_").to_uppercase()), + "--cfg=for_brs --cfg=for_lib", + ) + .with_status(101) + .with_stderr_contains("[..]assertion failed: cfg!(for_brs)[..]") + .run(); + // Without --target, both lib.rs and build.rs should pick up RUSTFLAGS. + p.cargo("build") + .masquerade_as_nightly_cargo() + .arg("-Ztargeted-rustflags") + .env("RUSTFLAGS", "--cfg=for_lib --cfg=for_brs") + .run(); + // Same with CARGO_ENCODED_RUSTFLAGS. + // NOTE: We just assume that CARGO_ENCODED_RUSTFLAGS works like RUSTFLAGS after this. + p.cargo("build") + .masquerade_as_nightly_cargo() + .arg("-Ztargeted-rustflags") + .env("CARGO_ENCODED_RUSTFLAGS", "--cfg=for_lib\x1f--cfg=for_brs") + .run(); + // TARGET_RUSTFLAGS should _not_ affect the build script. + p.cargo("build") + .masquerade_as_nightly_cargo() + .arg("-Ztargeted-rustflags") + .env("TARGET_RUSTFLAGS", "--cfg=for_lib --cfg=for_brs") + .with_status(101) + .with_stderr_contains("[..]assertion failed: cfg!(for_brs)[..]") + .run(); + // With --target, lib.rs should pick up RUSTFLAGS, but build.rs should not. + p.cargo("build") + .masquerade_as_nightly_cargo() + .arg("-Ztargeted-rustflags") + .arg("--target") + .arg(&host) + .env("RUSTFLAGS", "--cfg=for_lib --cfg=for_brs") + .with_status(101) + .with_stderr_contains("[..]assertion failed: cfg!(for_brs)[..]") + .run(); + // build.rs picks up HOST_RUSTFLAGS (but lib does not). + p.cargo("build") + .masquerade_as_nightly_cargo() + .arg("-Ztargeted-rustflags") + .arg("--target") + .arg(&host) + .env("HOST_RUSTFLAGS", "--cfg=for_brs") + .env("RUSTFLAGS", "--cfg=for_lib") + .run(); + // lib.rs picks up TARGET_RUSTFLAGS too. + p.cargo("build") + .masquerade_as_nightly_cargo() + .arg("-Ztargeted-rustflags") + .arg("--target") + .arg(&host) + .env("HOST_RUSTFLAGS", "--cfg=for_brs") + .env("TARGET_RUSTFLAGS", "--cfg=for_lib") + .run(); + // Without `target-applies-to-host = false`, host picks up RUSTFLAGS_$HOST, and it takes + // precedence over all the other flags. + p.cargo("build") + .masquerade_as_nightly_cargo() + .arg("-Ztargeted-rustflags") + .arg("--target") + .arg(&host) + .env( + &format!("RUSTFLAGS_{}", host.replace('-', "_").to_uppercase()), + "--cfg=for_brs --cfg=for_lib", + ) + .env("HOST_RUSTFLAGS", "--cfg=host") + .env("TARGET_RUSTFLAGS", "--cfg=target") + .env("RUSTFLAGS", "--cfg=generic") + .run(); + // But with `target-applies-to-host = false`, host _only_ picks up `HOST_RUSTFLAGS`. + p.cargo("build") + .masquerade_as_nightly_cargo() + .arg("-Ztargeted-rustflags") + .arg("-Ztarget-applies-to-host") + .arg("-Zunstable-options") + .arg("--config") + .arg("target-applies-to-host=false") + .arg("--target") + .arg(&host) + .env( + &format!("RUSTFLAGS_{}", host.replace('-', "_").to_uppercase()), + "--cfg=for_lib", + ) + .env("HOST_RUSTFLAGS", "--cfg=for_brs") + .env("RUSTFLAGS", "--cfg=generic") + .run(); +} + +#[cargo_test] +fn env_rustflags_specificity() { + let p = project() + .file( + "src/lib.rs", + r#" + #[cfg(not(expected))] + compile_error!("did not use the expected rustflags"); + "#, + ) + .build(); + let host = rustc_host(); + + // CARGO_ENCODED_RUSTFLAGS should be preferred over RUSTFLAGS. + p.cargo("build") + .masquerade_as_nightly_cargo() + .arg("-Ztargeted-rustflags") + .env("CARGO_ENCODED_RUSTFLAGS", "--cfg=expected") + .env("RUSTFLAGS", "--cfg=rustflags") + .run(); + // RUSTFLAGS with target specified should be preferred over CARGO_ENCODED_RUSTFLAGS. + p.cargo("build") + .masquerade_as_nightly_cargo() + .arg("-Ztargeted-rustflags") + .env( + &format!("RUSTFLAGS_{}", host.replace('-', "_").to_uppercase()), + "--cfg=expected", + ) + .env("CARGO_ENCODED_RUSTFLAGS", "--cfg=encoded") + .run(); + // Same with TARGET_RUSTFLAGS over CARGO_ENCODED_RUSTFLAGS. + p.cargo("build") + .masquerade_as_nightly_cargo() + .arg("-Ztargeted-rustflags") + .arg("--target") + .arg(&host) + .env("TARGET_RUSTFLAGS", "--cfg=expected") + .env("CARGO_ENCODED_RUSTFLAGS", "--cfg=encoded") + .run(); + // But CARGO_ENCODED_RUSTFLAGS should win ties. + p.cargo("build") + .masquerade_as_nightly_cargo() + .arg("-Ztargeted-rustflags") + .env( + &format!( + "CARGO_ENCODED_RUSTFLAGS_{}", + host.replace('-', "_").to_uppercase() + ), + "--cfg=expected", + ) + .env( + &format!("RUSTFLAGS_{}", host.replace('-', "_").to_uppercase()), + "--cfg=rustflags", + ) + .run(); +} + #[cargo_test] fn env_rustflags_build_script() { // RUSTFLAGS should be passed to rustc for build scripts