Skip to content

Commit 7b2ab86

Browse files
committed
Apple: Refactor deployment target version parsing
- Merge minimum OS version list into one function (makes it easier to see the logic in it). - Parse patch deployment target versions. - Consistently specify deployment target in LLVM target (previously omitted on `aarch64-apple-watchos`).
1 parent 5b0e086 commit 7b2ab86

28 files changed

+311
-359
lines changed

compiler/rustc_codegen_ssa/src/back/metadata.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -378,21 +378,21 @@ pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static
378378
fn macho_object_build_version_for_target(target: &Target) -> object::write::MachOBuildVersion {
379379
/// The `object` crate demands "X.Y.Z encoded in nibbles as xxxx.yy.zz"
380380
/// e.g. minOS 14.0 = 0x000E0000, or SDK 16.2 = 0x00100200
381-
fn pack_version((major, minor): (u32, u32)) -> u32 {
382-
(major << 16) | (minor << 8)
381+
fn pack_version((major, minor, patch): (u16, u8, u8)) -> u32 {
382+
let (major, minor, patch) = (major as u32, minor as u32, patch as u32);
383+
(major << 16) | (minor << 8) | patch
383384
}
384385

385386
let platform =
386387
rustc_target::spec::current_apple_platform(target).expect("unknown Apple target OS");
387-
let min_os = rustc_target::spec::current_apple_deployment_target(target)
388-
.expect("unknown Apple target OS");
389-
let sdk =
388+
let min_os = rustc_target::spec::current_apple_deployment_target(target);
389+
let (sdk_major, sdk_minor) =
390390
rustc_target::spec::current_apple_sdk_version(platform).expect("unknown Apple target OS");
391391

392392
let mut build_version = object::write::MachOBuildVersion::default();
393393
build_version.platform = platform;
394394
build_version.minos = pack_version(min_os);
395-
build_version.sdk = pack_version(sdk);
395+
build_version.sdk = pack_version((sdk_major, sdk_minor, 0));
396396
build_version
397397
}
398398

compiler/rustc_driver_impl/src/lib.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -862,9 +862,9 @@ fn print_crate_info(
862862
use rustc_target::spec::current_apple_deployment_target;
863863

864864
if sess.target.is_like_osx {
865-
let (major, minor) = current_apple_deployment_target(&sess.target)
866-
.expect("unknown Apple target OS");
867-
println_info!("deployment_target={}", format!("{major}.{minor}"))
865+
let (major, minor, patch) = current_apple_deployment_target(&sess.target);
866+
let patch = if patch != 0 { format!(".{patch}") } else { String::new() };
867+
println_info!("deployment_target={major}.{minor}{patch}")
868868
} else {
869869
#[allow(rustc::diagnostic_outside_of_impl)]
870870
sess.dcx().fatal("only Apple targets currently support deployment version info")

compiler/rustc_target/src/spec/base/apple/mod.rs

+132-137
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::borrow::Cow;
22
use std::env;
3+
use std::num::ParseIntError;
34

45
use crate::spec::{
56
add_link_args, add_link_args_iter, cvs, Cc, DebuginfoKind, FramePointer, LinkArgs,
@@ -104,15 +105,8 @@ fn pre_link_args(os: &'static str, arch: Arch, abi: TargetAbi) -> LinkArgs {
104105
};
105106

106107
let min_version: StaticCow<str> = {
107-
let (major, minor) = match os {
108-
"ios" => ios_deployment_target(arch, abi.target_abi()),
109-
"tvos" => tvos_deployment_target(),
110-
"watchos" => watchos_deployment_target(),
111-
"visionos" => visionos_deployment_target(),
112-
"macos" => macos_deployment_target(arch),
113-
_ => unreachable!(),
114-
};
115-
format!("{major}.{minor}").into()
108+
let (major, minor, patch) = deployment_target(os, arch, abi);
109+
format!("{major}.{minor}.{patch}").into()
116110
};
117111
let sdk_version = min_version.clone();
118112

@@ -154,15 +148,22 @@ fn pre_link_args(os: &'static str, arch: Arch, abi: TargetAbi) -> LinkArgs {
154148
add_link_args_iter(
155149
&mut args,
156150
LinkerFlavor::Darwin(Cc::Yes, Lld::No),
157-
["-target".into(), mac_catalyst_llvm_target(arch).into()].into_iter(),
151+
["-target".into(), llvm_target(os, arch, abi)].into_iter(),
158152
);
159153
}
160154

161155
args
162156
}
163157

164-
pub fn opts(os: &'static str, arch: Arch, abi: TargetAbi) -> TargetOptions {
165-
TargetOptions {
158+
/// Get the base target options, LLVM target and `target_arch` from the three
159+
/// things that uniquely identify Rust's Apple targets: The OS, the
160+
/// architecture, and the ABI.
161+
pub fn base(
162+
os: &'static str,
163+
arch: Arch,
164+
abi: TargetAbi,
165+
) -> (TargetOptions, StaticCow<str>, StaticCow<str>) {
166+
let opts = TargetOptions {
166167
abi: abi.target_abi().into(),
167168
os: os.into(),
168169
cpu: arch.target_cpu(abi).into(),
@@ -212,10 +213,11 @@ pub fn opts(os: &'static str, arch: Arch, abi: TargetAbi) -> TargetOptions {
212213
link_env: Cow::Borrowed(&[(Cow::Borrowed("ZERO_AR_DATE"), Cow::Borrowed("1"))]),
213214

214215
..Default::default()
215-
}
216+
};
217+
(opts, llvm_target(os, arch, abi), arch.target_arch())
216218
}
217219

218-
pub fn sdk_version(platform: u32) -> Option<(u32, u32)> {
220+
pub fn sdk_version(platform: u32) -> Option<(u16, u8)> {
219221
// NOTE: These values are from an arbitrary point in time but shouldn't make it into the final
220222
// binary since the final link command will have the current SDK version passed to it.
221223
match platform {
@@ -249,58 +251,115 @@ pub fn platform(target: &Target) -> Option<u32> {
249251
})
250252
}
251253

252-
pub fn deployment_target(target: &Target) -> Option<(u32, u32)> {
253-
let (major, minor) = match &*target.os {
254-
"macos" => {
255-
// This does not need to be specific. It just needs to handle x86 vs M1.
256-
let arch = match target.arch.as_ref() {
257-
"x86" | "x86_64" => X86_64,
258-
"arm64e" => Arm64e,
259-
_ => Arm64,
260-
};
261-
macos_deployment_target(arch)
262-
}
263-
"ios" => {
264-
let arch = match target.arch.as_ref() {
265-
"arm64e" => Arm64e,
266-
_ => Arm64,
267-
};
268-
ios_deployment_target(arch, &target.abi)
269-
}
270-
"watchos" => watchos_deployment_target(),
271-
"tvos" => tvos_deployment_target(),
272-
"visionos" => visionos_deployment_target(),
273-
_ => return None,
254+
/// Hack for calling `deployment_target` outside of this module.
255+
pub fn deployment_target_for_target(target: &Target) -> (u16, u8, u8) {
256+
let arch = if target.llvm_target.starts_with("arm64e") {
257+
Arch::Arm64e
258+
} else if target.arch == "aarch64" {
259+
Arch::Arm64
260+
} else {
261+
// Dummy architecture, only used by `deployment_target` anyhow
262+
Arch::X86_64
274263
};
275-
276-
Some((major, minor))
264+
let abi = match &*target.abi {
265+
"macabi" => TargetAbi::MacCatalyst,
266+
"sim" => TargetAbi::Simulator,
267+
"" => TargetAbi::Normal,
268+
abi => unreachable!("invalid abi '{abi}' for Apple target"),
269+
};
270+
deployment_target(&target.os, arch, abi)
277271
}
278272

279-
fn from_set_deployment_target(var_name: &str) -> Option<(u32, u32)> {
280-
let deployment_target = env::var(var_name).ok()?;
281-
let (unparsed_major, unparsed_minor) = deployment_target.split_once('.')?;
282-
let (major, minor) = (unparsed_major.parse().ok()?, unparsed_minor.parse().ok()?);
273+
/// Get the deployment target based on the standard environment variables, or
274+
/// fall back to a sane default.
275+
fn deployment_target(os: &str, arch: Arch, abi: TargetAbi) -> (u16, u8, u8) {
276+
// When bumping a version in here, remember to update the platform-support
277+
// docs too.
278+
//
279+
// NOTE: If you are looking for the default deployment target, prefer
280+
// `rustc --print deployment-target`, as the default here may change in
281+
// future `rustc` versions.
282+
283+
// Minimum operating system versions currently supported by `rustc`.
284+
let os_min = match os {
285+
"macos" => (10, 12, 0),
286+
"ios" => (10, 0, 0),
287+
"tvos" => (10, 0, 0),
288+
"watchos" => (5, 0, 0),
289+
"visionos" => (1, 0, 0),
290+
_ => unreachable!("tried to get deployment target for non-Apple platform"),
291+
};
283292

284-
Some((major, minor))
285-
}
293+
// On certain targets it makes sense to raise the minimum OS version.
294+
let min = match (os, arch, abi) {
295+
// Use 11.0 on Aarch64 as that's the earliest version with M1 support.
296+
("macos", Arch::Arm64 | Arch::Arm64e, _) => (11, 0, 0),
297+
("ios", Arch::Arm64e, _) => (14, 0, 0),
298+
// Mac Catalyst defaults to 13.1 in Clang.
299+
("ios", _, TargetAbi::MacCatalyst) => (13, 1, 0),
300+
_ => os_min,
301+
};
286302

287-
fn macos_default_deployment_target(arch: Arch) -> (u32, u32) {
288-
match arch {
289-
Arm64 | Arm64e => (11, 0),
290-
_ => (10, 12),
291-
}
292-
}
303+
// The environment variable used to fetch the deployment target.
304+
let env_var = match os {
305+
"macos" => "MACOSX_DEPLOYMENT_TARGET",
306+
"ios" => "IPHONEOS_DEPLOYMENT_TARGET",
307+
"watchos" => "WATCHOS_DEPLOYMENT_TARGET",
308+
"tvos" => "TVOS_DEPLOYMENT_TARGET",
309+
"visionos" => "XROS_DEPLOYMENT_TARGET",
310+
_ => unreachable!("tried to get deployment target env var for non-Apple platform"),
311+
};
293312

294-
fn macos_deployment_target(arch: Arch) -> (u32, u32) {
295-
// If you are looking for the default deployment target, prefer `rustc --print deployment-target`.
296-
// Note: If bumping this version, remember to update it in the rustc/platform-support docs.
297-
from_set_deployment_target("MACOSX_DEPLOYMENT_TARGET")
298-
.unwrap_or_else(|| macos_default_deployment_target(arch))
313+
if let Ok(deployment_target) = env::var(env_var) {
314+
match parse_version(&deployment_target) {
315+
// It is common that the deployment target is set too low, e.g. on
316+
// macOS Aarch64 to also target older x86_64, the user may set a
317+
// lower deployment target than supported.
318+
//
319+
// To avoid such issues, we silently raise the deployment target
320+
// here.
321+
// FIXME: We want to show a warning when `version < os_min`.
322+
Ok(version) => version.max(min),
323+
// FIXME: Report erroneous environment variable to user.
324+
Err(_) => min,
325+
}
326+
} else {
327+
min
328+
}
299329
}
300330

301-
pub fn macos_llvm_target(arch: Arch) -> String {
302-
let (major, minor) = macos_deployment_target(arch);
303-
format!("{}-apple-macosx{}.{}.0", arch.target_name(), major, minor)
331+
/// Generate the target triple that we need to pass to LLVM and/or Clang.
332+
fn llvm_target(os: &str, arch: Arch, abi: TargetAbi) -> StaticCow<str> {
333+
// The target triple depends on the deployment target, and is required to
334+
// enable features such as cross-language LTO, and for picking the right
335+
// Mach-O commands.
336+
//
337+
// Certain optimizations also depend on the deployment target.
338+
//
339+
// Modern iOS tooling extracts information about deployment target
340+
// from LC_BUILD_VERSION. This load command will only be emitted when
341+
// we build with a version specific `llvm_target`, with the version
342+
// set high enough. Luckily one LC_BUILD_VERSION is enough, for Xcode
343+
// to pick it up (since std and core are still built with the fallback
344+
// of version 7.0 and hence emit the old LC_IPHONE_MIN_VERSION).
345+
let (major, minor, patch) = deployment_target(os, arch, abi);
346+
let arch = arch.target_name();
347+
// Convert to the "canonical" OS name used by LLVM:
348+
// https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/llvm/lib/TargetParser/Triple.cpp#L236-L282
349+
let os = match os {
350+
"macos" => "macosx",
351+
"ios" => "ios",
352+
"watchos" => "watchos",
353+
"tvos" => "tvos",
354+
"visionos" => "xros",
355+
_ => unreachable!("tried to get LLVM target OS for non-Apple platform"),
356+
};
357+
let environment = match abi {
358+
TargetAbi::Normal => "",
359+
TargetAbi::MacCatalyst => "-macabi",
360+
TargetAbi::Simulator => "-simulator",
361+
};
362+
format!("{arch}-apple-{os}{major}.{minor}.{patch}{environment}").into()
304363
}
305364

306365
fn link_env_remove(os: &'static str) -> StaticCow<[StaticCow<str>]> {
@@ -340,83 +399,19 @@ fn link_env_remove(os: &'static str) -> StaticCow<[StaticCow<str>]> {
340399
}
341400
}
342401

343-
fn ios_deployment_target(arch: Arch, abi: &str) -> (u32, u32) {
344-
// If you are looking for the default deployment target, prefer `rustc --print deployment-target`.
345-
// Note: If bumping this version, remember to update it in the rustc/platform-support docs.
346-
let (major, minor) = match (arch, abi) {
347-
(Arm64e, _) => (14, 0),
348-
// Mac Catalyst defaults to 13.1 in Clang.
349-
(_, "macabi") => (13, 1),
350-
_ => (10, 0),
351-
};
352-
from_set_deployment_target("IPHONEOS_DEPLOYMENT_TARGET").unwrap_or((major, minor))
353-
}
354-
355-
pub fn ios_llvm_target(arch: Arch) -> String {
356-
// Modern iOS tooling extracts information about deployment target
357-
// from LC_BUILD_VERSION. This load command will only be emitted when
358-
// we build with a version specific `llvm_target`, with the version
359-
// set high enough. Luckily one LC_BUILD_VERSION is enough, for Xcode
360-
// to pick it up (since std and core are still built with the fallback
361-
// of version 7.0 and hence emit the old LC_IPHONE_MIN_VERSION).
362-
let (major, minor) = ios_deployment_target(arch, "");
363-
format!("{}-apple-ios{}.{}.0", arch.target_name(), major, minor)
364-
}
365-
366-
pub fn mac_catalyst_llvm_target(arch: Arch) -> String {
367-
let (major, minor) = ios_deployment_target(arch, "macabi");
368-
format!("{}-apple-ios{}.{}.0-macabi", arch.target_name(), major, minor)
369-
}
370-
371-
pub fn ios_sim_llvm_target(arch: Arch) -> String {
372-
let (major, minor) = ios_deployment_target(arch, "sim");
373-
format!("{}-apple-ios{}.{}.0-simulator", arch.target_name(), major, minor)
374-
}
375-
376-
fn tvos_deployment_target() -> (u32, u32) {
377-
// If you are looking for the default deployment target, prefer `rustc --print deployment-target`.
378-
// Note: If bumping this version, remember to update it in the rustc platform-support docs.
379-
from_set_deployment_target("TVOS_DEPLOYMENT_TARGET").unwrap_or((10, 0))
380-
}
381-
382-
pub fn tvos_llvm_target(arch: Arch) -> String {
383-
let (major, minor) = tvos_deployment_target();
384-
format!("{}-apple-tvos{}.{}.0", arch.target_name(), major, minor)
385-
}
386-
387-
pub fn tvos_sim_llvm_target(arch: Arch) -> String {
388-
let (major, minor) = tvos_deployment_target();
389-
format!("{}-apple-tvos{}.{}.0-simulator", arch.target_name(), major, minor)
390-
}
391-
392-
fn watchos_deployment_target() -> (u32, u32) {
393-
// If you are looking for the default deployment target, prefer `rustc --print deployment-target`.
394-
// Note: If bumping this version, remember to update it in the rustc platform-support docs.
395-
from_set_deployment_target("WATCHOS_DEPLOYMENT_TARGET").unwrap_or((5, 0))
396-
}
397-
398-
pub fn watchos_llvm_target(arch: Arch) -> String {
399-
let (major, minor) = watchos_deployment_target();
400-
format!("{}-apple-watchos{}.{}.0", arch.target_name(), major, minor)
401-
}
402-
403-
pub fn watchos_sim_llvm_target(arch: Arch) -> String {
404-
let (major, minor) = watchos_deployment_target();
405-
format!("{}-apple-watchos{}.{}.0-simulator", arch.target_name(), major, minor)
406-
}
407-
408-
fn visionos_deployment_target() -> (u32, u32) {
409-
// If you are looking for the default deployment target, prefer `rustc --print deployment-target`.
410-
// Note: If bumping this version, remember to update it in the rustc platform-support docs.
411-
from_set_deployment_target("XROS_DEPLOYMENT_TARGET").unwrap_or((1, 0))
412-
}
413-
414-
pub fn visionos_llvm_target(arch: Arch) -> String {
415-
let (major, minor) = visionos_deployment_target();
416-
format!("{}-apple-visionos{}.{}.0", arch.target_name(), major, minor)
417-
}
418-
419-
pub fn visionos_sim_llvm_target(arch: Arch) -> String {
420-
let (major, minor) = visionos_deployment_target();
421-
format!("{}-apple-visionos{}.{}.0-simulator", arch.target_name(), major, minor)
402+
/// Parse an OS version triple (SDK version or deployment target).
403+
///
404+
/// The size of the returned numbers here are limited by Mach-O's
405+
/// `LC_BUILD_VERSION`.
406+
fn parse_version(version: &str) -> Result<(u16, u8, u8), ParseIntError> {
407+
if let Some((major, minor)) = version.split_once('.') {
408+
let major = major.parse()?;
409+
if let Some((minor, patch)) = minor.split_once('.') {
410+
Ok((major, minor.parse()?, patch.parse()?))
411+
} else {
412+
Ok((major, minor.parse()?, 0))
413+
}
414+
} else {
415+
Ok((version.parse()?, 0, 0))
416+
}
422417
}

compiler/rustc_target/src/spec/base/apple/tests.rs

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use super::parse_version;
12
use crate::spec::targets::{
23
aarch64_apple_darwin, aarch64_apple_ios_sim, aarch64_apple_visionos_sim,
34
aarch64_apple_watchos_sim, i686_apple_darwin, x86_64_apple_darwin, x86_64_apple_ios,
@@ -42,3 +43,11 @@ fn macos_link_environment_unmodified() {
4243
);
4344
}
4445
}
46+
47+
#[test]
48+
fn test_parse_version() {
49+
assert_eq!(parse_version("10"), Ok((10, 0, 0)));
50+
assert_eq!(parse_version("10.12"), Ok((10, 12, 0)));
51+
assert_eq!(parse_version("10.12.6"), Ok((10, 12, 6)));
52+
assert_eq!(parse_version("9999.99.99"), Ok((9999, 99, 99)));
53+
}

compiler/rustc_target/src/spec/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ pub mod crt_objects;
6060

6161
mod base;
6262
pub use base::apple::{
63-
deployment_target as current_apple_deployment_target, platform as current_apple_platform,
64-
sdk_version as current_apple_sdk_version,
63+
deployment_target_for_target as current_apple_deployment_target,
64+
platform as current_apple_platform, sdk_version as current_apple_sdk_version,
6565
};
6666
pub use base::avr_gnu::ef_avr_arch;
6767

0 commit comments

Comments
 (0)