From 188e2a0bd7f03f108f4115b3203a5475366abb34 Mon Sep 17 00:00:00 2001 From: BlackHoleFox Date: Mon, 13 Nov 2023 23:20:03 -0600 Subject: [PATCH] Fix Apple deployment version floor when linking C++ --- src/lib.rs | 98 ++++++++++++++++++++++++++++++++++++++++++--------- tests/test.rs | 39 ++++++++++++++++++++ 2 files changed, 121 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d8effdbaf..4e6aaa159 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3407,6 +3407,8 @@ impl Build { target: &str, arch_str: Option<&str>, ) -> String { + const OLD_IOS_MINIMUM_VERSION: &str = "7.0"; + fn rustc_provided_target(rustc: Option<&str>, target: &str) -> Option { let rustc = rustc?; let output = Command::new(rustc) @@ -3427,6 +3429,74 @@ impl Build { } } + let deployment_from_env = |name: &str| { + // note this isn't hit in production codepaths, its mostly just for tests which don't + // set the real env + if let Some((_, v)) = self.env.iter().find(|(k, _)| &**k == OsStr::new(name)) { + Some(v.to_str().unwrap().to_string()) + } else { + env::var(name).ok() + } + }; + + // Determines if the acquired deployment target is too low to support modern C++ on some Apple platform. + // + // A long time ago they used libstdc++, but since macOS 10.9 and iOS 7 libc++ has been the library the SDKs provide to link against. + // If a `cc`` config wants to use C++, we round up to these versions as the baseline. + let maybe_cpp_version_baseline = |deployment_target_ver: String| -> String { + if !self.cpp { + return deployment_target_ver; + } + + let mut deployment_target = deployment_target_ver + .split('.') + .map(|v| v.parse::().expect("integer version")); + + match os { + AppleOs::MacOs => { + // Targeting higher then 10.x (11.0+)? + let major_supported = deployment_target + .next() + .map(|major| major > 10) + .unwrap_or_default(); + + // Targeting higher then 10.9? + let minor_supported = deployment_target + .next() + .map(|minor| minor >= 9) + .unwrap_or_default(); + + // If below 10.9, we round up. + if !major_supported && !minor_supported { + println!( + "cargo-warning: macOS deployment target ({}) too low, it will be increased", + deployment_target_ver + ); + return String::from("10.9"); + } + } + AppleOs::Ios => { + let major_supported = deployment_target + .next() + .map(|major| major >= 7) + .unwrap_or_default(); + + if !major_supported { + println!( + "cargo-warning: iOS deployment target ({}) too low, it will be increased", + deployment_target_ver + ); + return String::from(OLD_IOS_MINIMUM_VERSION); + } + } + // watchOS, tvOS, and others are all new enough that libc++ is their baseline. + _ => {} + } + + // If the deployment target met or exceeded the C++ baseline + deployment_target_ver + }; + let rustc = self.getenv("RUSTC"); let rustc = rustc.as_deref(); // note the hardcoded minimums here are subject to change in a future compiler release, @@ -3436,31 +3506,27 @@ impl Build { // the ordering of env -> rustc -> old defaults is intentional for performance when using // an explicit target match os { - AppleOs::MacOs => env::var("MACOSX_DEPLOYMENT_TARGET") - .ok() + AppleOs::MacOs => deployment_from_env("MACOSX_DEPLOYMENT_TARGET") .or_else(|| rustc_provided_target(rustc, target)) + .map(maybe_cpp_version_baseline) .unwrap_or_else(|| { if arch_str == Some("aarch64") { - "11.0" + "11.0".into() } else { - if self.cpp { - "10.9" - } else { - "10.7" - } + maybe_cpp_version_baseline("10.7".into()) } - .into() }), - AppleOs::Ios => env::var("IPHONEOS_DEPLOYMENT_TARGET") - .ok() + + AppleOs::Ios => deployment_from_env("IPHONEOS_DEPLOYMENT_TARGET") .or_else(|| rustc_provided_target(rustc, target)) - .unwrap_or_else(|| "7.0".into()), - AppleOs::WatchOs => env::var("WATCHOS_DEPLOYMENT_TARGET") - .ok() + .map(maybe_cpp_version_baseline) + .unwrap_or_else(|| OLD_IOS_MINIMUM_VERSION.into()), + + AppleOs::WatchOs => deployment_from_env("WATCHOS_DEPLOYMENT_TARGET") .or_else(|| rustc_provided_target(rustc, target)) .unwrap_or_else(|| "5.0".into()), - AppleOs::TvOs => env::var("TVOS_DEPLOYMENT_TARGET") - .ok() + + AppleOs::TvOs => deployment_from_env("TVOS_DEPLOYMENT_TARGET") .or_else(|| rustc_provided_target(rustc, target)) .unwrap_or_else(|| "9.0".into()), } diff --git a/tests/test.rs b/tests/test.rs index fc07e6253..220d2f2f7 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -508,6 +508,45 @@ fn gnu_apple_darwin() { } } +#[cfg(target_os = "macos")] +#[test] +fn macos_cpp_minimums() { + let versions = &[ + // Too low + ("10.7", "10.9"), + // Minimum + ("10.9", "10.9"), + // Higher + ("11.0", "11.0"), + ]; + + let target = "x86_64-apple-darwin"; + for (deployment_target, expected) in versions { + let test = Test::gnu(); + test.gcc() + .target(target) + .host(target) + .cpp(true) + .__set_env("MACOSX_DEPLOYMENT_TARGET", deployment_target) + .file("foo.c") + .compile("foo"); + + test.cmd(0) + .must_have(format!("-mmacosx-version-min={}", expected)); + } + + let test = Test::gnu(); + test.gcc() + .target(target) + .host(target) + .__set_env("MACOSX_DEPLOYMENT_TARGET", "10.7") + .file("foo.c") + .compile("foo"); + + // No C++ leaves it untouched + test.cmd(0).must_have("-mmacosx-version-min=10.7"); +} + #[cfg(target_os = "macos")] #[test] fn clang_apple_tvos() {