From 8b90d495917c604201c56fd2b6c280197b31c03f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Fri, 31 Jan 2025 05:40:16 +0100 Subject: [PATCH] Pass deployment target to cc linker with `-m*-version-min=` Clang supports many ways of passing the deployment target, and prefers that you use the `-target` flag for doing so. This works poorly work with configuration files and Zig CC though, so we use the older `-mmacosx-version-min=`, `-miphoneos-version-min=`, `-mtargetos=` etc. flags to pass the deployment target instead. --- compiler/rustc_codegen_ssa/src/back/apple.rs | 50 +++++++++++++- .../rustc_codegen_ssa/src/back/apple/tests.rs | 12 +++- compiler/rustc_codegen_ssa/src/back/link.rs | 66 ++++++++++--------- 3 files changed, 93 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/apple.rs b/compiler/rustc_codegen_ssa/src/back/apple.rs index d9c5c3e5af96d..0e8ccbcc4ea05 100644 --- a/compiler/rustc_codegen_ssa/src/back/apple.rs +++ b/compiler/rustc_codegen_ssa/src/back/apple.rs @@ -56,6 +56,11 @@ pub fn pretty_version(version: OSVersion) -> impl Display { }) } +fn full_version(version: OSVersion) -> impl Display { + let (major, minor, patch) = version; + from_fn(move |f| write!(f, "{major}.{minor}.{patch}")) +} + /// Minimum operating system versions currently supported by `rustc`. fn os_minimum_deployment_target(os: &str) -> OSVersion { // When bumping a version in here, remember to update the platform-support docs too. @@ -155,7 +160,7 @@ pub(super) fn add_version_to_llvm_target( let environment = components.next(); assert_eq!(components.next(), None, "too many LLVM triple components"); - let (major, minor, patch) = deployment_target; + let version = full_version(deployment_target); assert!( !os.contains(|c: char| c.is_ascii_digit()), @@ -164,8 +169,47 @@ pub(super) fn add_version_to_llvm_target( if let Some(env) = environment { // Insert version into OS, before environment - format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}-{env}") + format!("{arch}-{vendor}-{os}{version}-{env}") } else { - format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}") + format!("{arch}-{vendor}-{os}{version}") + } +} + +/// The flag needed to specify the OS and deployment target to a C compiler. +/// +/// There are many aliases for these, and `-mtargetos=` is preferred on Clang +/// nowadays, but for compatibility with older Clang and other tooling, we try +/// use the earliest supported name here. +pub(super) fn cc_os_version_min_flag(os: &str, abi: &str, deployment_target: OSVersion) -> String { + let version = full_version(deployment_target); + // NOTE: GCC does not support `-miphoneos-version-min=` etc. (because it + // does not support iOS in general), but we specify them there anyhow in + // case GCC adds support for these in the future, and to force a + // compilation error if GCC compiler is not configured for Darwin: + // https://gcc.gnu.org/onlinedocs/gcc/Darwin-Options.html + // + // See also: + // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-mmacos-version-min + // https://clang.llvm.org/docs/AttributeReference.html#availability + // https://gcc.gnu.org/onlinedocs/gcc/Darwin-Options.html#index-mmacosx-version-min + // + // Note: These are intentionally passed as a single argument, Clang + // doesn't seem to like it otherwise. + match (os, abi) { + ("macos", "") => format!("-mmacosx-version-min={version}"), + ("ios", "") => format!("-miphoneos-version-min={version}"), + ("ios", "sim") => format!("-mios-simulator-version-min={version}"), + // Mac Catalyst came after the introduction of `-mtargetos=`, so no + // other flag exists for that. + ("ios", "macabi") => format!("-mtargetos=ios{version}-macabi"), + ("tvos", "") => format!("-mappletvos-version-min={version}"), + ("tvos", "sim") => format!("-mappletvsimulator-version-min={version}"), + ("watchos", "") => format!("-mwatchos-version-min={version}"), + ("watchos", "sim") => format!("-mwatchsimulator-version-min={version}"), + // `-mxros-version-min=` does not exist, use `-mtargetos=`. + // https://github.com/llvm/llvm-project/issues/88271 + ("visionos", "") => format!("-mtargetos=xros{version}"), + ("visionos", "sim") => format!("-mtargetos=xros{version}-simulator"), + _ => unreachable!("tried to get cc version flag for non-Apple platform"), } } diff --git a/compiler/rustc_codegen_ssa/src/back/apple/tests.rs b/compiler/rustc_codegen_ssa/src/back/apple/tests.rs index 7ccda5a8190c7..a7a819b802770 100644 --- a/compiler/rustc_codegen_ssa/src/back/apple/tests.rs +++ b/compiler/rustc_codegen_ssa/src/back/apple/tests.rs @@ -1,4 +1,4 @@ -use super::{add_version_to_llvm_target, parse_version}; +use super::{add_version_to_llvm_target, cc_os_version_min_flag, parse_version}; #[test] fn test_add_version_to_llvm_target() { @@ -19,3 +19,13 @@ fn test_parse_version() { assert_eq!(parse_version("10.12.6"), Ok((10, 12, 6))); assert_eq!(parse_version("9999.99.99"), Ok((9999, 99, 99))); } + +#[test] +fn test_cc_os_version_min_flag() { + assert_eq!(cc_os_version_min_flag("macos", "", (10, 14, 1)), "-mmacosx-version-min=10.14.1"); + assert_eq!(cc_os_version_min_flag("ios", "macabi", (13, 1, 0)), "-mtargetos=ios13.1.0-macabi"); + assert_eq!( + cc_os_version_min_flag("visionos", "sim", (1, 0, 0)), + "-mtargetos=xros1.0.0-simulator" + ); +} diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 105a4cb81f0d1..7c53635ca7d34 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -3163,39 +3163,43 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo } else { // cc == Cc::Yes // - // We'd _like_ to use `-target` everywhere, since that can uniquely + // We'd _like_ to pass `--target` everywhere, since that can uniquely // communicate all the required details except for the SDK version - // (which is read by Clang itself from the SDKROOT), but that doesn't - // work on GCC, and since we don't know whether the `cc` compiler is - // Clang, GCC, or something else, we fall back to other options that - // also work on GCC when compiling for macOS. + // (which is read by Clang itself from the SDK root). But that has a + // few problems: + // - It doesn't work with GCC. + // - It doesn't work with Zig CC: + // . + // - It worked poorly with Clang when using config files before: + // + // - It worked poorly with certain versions of the `llvm` package in + // Homebrew that expected the older flags: + // // - // Targets other than macOS are ill-supported by GCC (it doesn't even - // support e.g. `-miphoneos-version-min`), so in those cases we can - // fairly safely use `-target`. See also the following, where it is - // made explicit that the recommendation by LLVM developers is to use - // `-target`: - if target_os == "macos" { - // `-arch` communicates the architecture. - // - // CC forwards the `-arch` to the linker, so we use the same value - // here intentionally. - cmd.cc_args(&["-arch", ld64_arch]); - - // The presence of `-mmacosx-version-min` makes CC default to - // macOS, and it sets the deployment target. - let (major, minor, patch) = apple::deployment_target(sess); - // Intentionally pass this as a single argument, Clang doesn't - // seem to like it otherwise. - cmd.cc_arg(&format!("-mmacosx-version-min={major}.{minor}.{patch}")); - - // macOS has no environment, so with these two, we've told CC the - // four desired parameters. - // - // We avoid `-m32`/`-m64`, as this is already encoded by `-arch`. - } else { - cmd.cc_args(&["-target", &versioned_llvm_target(sess)]); - } + // So instead, we fall back to to using a combination of `-arch` with + // `-mtargetos=` (and similar). + + // cc usually forwards the `-arch` to the linker, so we use the same + // value here intentionally. + // + // NOTE: We avoid `-m32`/`-m64` as this is already encoded by `-arch`. + cmd.cc_args(&["-arch", ld64_arch]); + + let version = apple::deployment_target(sess); + + // The presence of the `-mmacosx-version-min=`, `-mtargetos=` etc. + // flag both allows Clang to figure out the desired target OS, + // environment / ABI, and the deployment target. See the logic in: + // + // + // NOTE: These all (correctly) take precedence over the equivalent + // `*_DEPLOYMENT_TARGET` environment variables (which we read + // ourselves to figure out the desired deployment target), so the + // current state of the environment should not have an effect. + cmd.cc_arg(&apple::cc_os_version_min_flag(target_os, target_abi, version)); + + // With these two flags + the SDK root, we've told cc the five desired + // parameters. } }