Skip to content

Commit

Permalink
Fix Apple deployment version floor when linking C++
Browse files Browse the repository at this point in the history
  • Loading branch information
BlackHoleFox committed Nov 14, 2023
1 parent 7e58690 commit 0f40f29
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 16 deletions.
86 changes: 70 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> {
let rustc = rustc?;
let output = Command::new(rustc)
Expand All @@ -3427,6 +3429,62 @@ 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::<u32>().expect("integer version"));

match os {
AppleOs::MacOs => {
let major = deployment_target.next().unwrap_or(0);
let minor = deployment_target.next().unwrap_or(0);

// If below 10.9, we round up.
if major == 10 && minor < 9 {
println!(
"cargo-warning: macOS deployment target ({}) too low, it will be increased",
deployment_target_ver
);
return String::from("10.9");
}
}
AppleOs::Ios => {
let major = deployment_target.next().unwrap_or(0);

if major < 7 {
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,
Expand All @@ -3436,31 +3494,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()),
}
Expand Down
39 changes: 39 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit 0f40f29

Please sign in to comment.