Skip to content

Commit 5b51331

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 23cdb50 commit 5b51331

28 files changed

Lines changed: 322 additions & 363 deletions

‎compiler/rustc_codegen_ssa/src/back/metadata.rs‎

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -372,27 +372,42 @@ pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static
372372
Some(file)
373373
}
374374

375-
/// Since Xcode 15 Apple's LD requires object files to contain information about what they were
376-
/// built for (LC_BUILD_VERSION): the platform (macOS/watchOS etc), minimum OS version, and SDK
377-
/// version. This returns a `MachOBuildVersion` for the target.
375+
/// Mach-O files contain information about:
376+
/// - The platform/OS they were built for (macOS/watchOS/Mac Catalyst/iOS simulator etc).
377+
/// - The minimum OS version / deployment target.
378+
/// - The version of the SDK they were targetting.
379+
///
380+
/// In the past, this was accomplished using the LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS,
381+
/// LC_VERSION_MIN_TVOS or LC_VERSION_MIN_WATCHOS load commands, which each contain information
382+
/// about the deployment target and SDK version, and implicitly, by their presence, which OS they
383+
/// target. Simulator targets were determined if the architecture was x86_64, but there was e.g. a
384+
/// LC_VERSION_MIN_IPHONEOS present.
385+
///
386+
/// This is of course brittle and limited, so modern tooling emit the LC_BUILD_VERSION load
387+
/// command (which contains all three pieces of information in one) when the deployment target is
388+
/// high enough, or the target is something that wouldn't be encodable with the old load commands
389+
/// (such as Mac Catalyst, or Aarch64 iOS simulator).
390+
///
391+
/// Since Xcode 15, Apple's LD apparently requires object files to use this load command, so this
392+
/// returns the `MachOBuildVersion` for the target to do so.
378393
fnmacho_object_build_version_for_target(target:&Target) -> object::write::MachOBuildVersion{
379394
/// The `object` crate demands "X.Y.Z encoded in nibbles as xxxx.yy.zz"
380395
/// e.g. minOS 14.0 = 0x000E0000, or SDK 16.2 = 0x00100200
381-
fnpack_version((major, minor):(u32,u32)) -> u32{
382-
(major << 16) | (minor << 8)
396+
fnpack_version((major, minor, patch):(u16,u8,u8)) -> u32{
397+
let(major, minor, patch) = (major asu32, minor asu32, patch asu32);
398+
(major << 16) | (minor << 8) | patch
383399
}
384400

385401
let platform =
386402
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 =
403+
let min_os = rustc_target::spec::current_apple_deployment_target(target);
404+
let(sdk_major, sdk_minor) =
390405
rustc_target::spec::current_apple_sdk_version(platform).expect("unknown Apple target OS");
391406

392407
letmut build_version = object::write::MachOBuildVersion::default();
393408
build_version.platform = platform;
394409
build_version.minos = pack_version(min_os);
395-
build_version.sdk = pack_version(sdk);
410+
build_version.sdk = pack_version((sdk_major, sdk_minor,0));
396411
build_version
397412
}
398413

‎compiler/rustc_driver_impl/src/lib.rs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -871,9 +871,9 @@ fn print_crate_info(
871871
use rustc_target::spec::current_apple_deployment_target;
872872

873873
if sess.target.is_like_osx {
874-
let(major, minor) = current_apple_deployment_target(&sess.target)
875-
.expect("unknown Apple target OS");
876-
println_info!("deployment_target={}", format!("{major}.{minor}"))
874+
let(major, minor, patch) = current_apple_deployment_target(&sess.target);
875+
let patch = if patch != 0{ format!(".{patch}")} else {String::new()};
876+
println_info!("deployment_target={major}.{minor}{patch}")
877877
} else {
878878
#[allow(rustc::diagnostic_outside_of_impl)]
879879
sess.dcx().fatal("only Apple targets currently support deployment version info")

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

Lines changed: 125 additions & 137 deletions
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
usecrate::spec::{
56
add_link_args, add_link_args_iter, cvs,Cc,DebuginfoKind,FramePointer,LinkArgs,
@@ -123,15 +124,8 @@ fn pre_link_args(os: &'static str, arch: Arch, abi: TargetAbi) -> LinkArgs {
123124
};
124125

125126
let min_version:StaticCow<str> = {
126-
let(major, minor) = match os {
127-
"ios" => ios_deployment_target(arch, abi.target_abi()),
128-
"tvos" => tvos_deployment_target(),
129-
"watchos" => watchos_deployment_target(),
130-
"visionos" => visionos_deployment_target(),
131-
"macos" => macos_deployment_target(arch),
132-
_ => unreachable!(),
133-
};
134-
format!("{major}.{minor}").into()
127+
let(major, minor, patch) = deployment_target(os, arch, abi);
128+
format!("{major}.{minor}.{patch}").into()
135129
};
136130
let sdk_version = min_version.clone();
137131

@@ -163,15 +157,22 @@ fn pre_link_args(os: &'static str, arch: Arch, abi: TargetAbi) -> LinkArgs {
163157
add_link_args_iter(
164158
&mut args,
165159
LinkerFlavor::Darwin(Cc::Yes,Lld::No),
166-
["-target".into(),mac_catalyst_llvm_target(arch).into()].into_iter(),
160+
["-target".into(),llvm_target(os,arch, abi)].into_iter(),
167161
);
168162
}
169163

170164
args
171165
}
172166

173-
pub(crate)fnopts(os:&'staticstr,arch:Arch,abi:TargetAbi) -> TargetOptions{
174-
TargetOptions{
167+
/// Get the base target options, LLVM target and `target_arch` from the three
168+
/// things that uniquely identify Rust's Apple targets: The OS, the
169+
/// architecture, and the ABI.
170+
pub(crate)fnbase(
171+
os:&'staticstr,
172+
arch:Arch,
173+
abi:TargetAbi,
174+
) -> (TargetOptions,StaticCow<str>,StaticCow<str>){
175+
let opts = TargetOptions{
175176
abi: abi.target_abi().into(),
176177
os: os.into(),
177178
cpu: arch.target_cpu(abi).into(),
@@ -221,10 +222,11 @@ pub(crate) fn opts(os: &'static str, arch: Arch, abi: TargetAbi) -> TargetOption
221222
link_env:Cow::Borrowed(&[(Cow::Borrowed("ZERO_AR_DATE"),Cow::Borrowed("1"))]),
222223

223224
..Default::default()
224-
}
225+
};
226+
(opts,llvm_target(os, arch, abi), arch.target_arch())
225227
}
226228

227-
pubfnsdk_version(platform:u32) -> Option<(u32,u32)>{
229+
pubfnsdk_version(platform:u32) -> Option<(u16,u8)>{
228230
// NOTE: These values are from an arbitrary point in time but shouldn't make it into the final
229231
// binary since the final link command will have the current SDK version passed to it.
230232
match platform {
@@ -258,58 +260,108 @@ pub fn platform(target: &Target) -> Option<u32> {
258260
})
259261
}
260262

261-
pubfndeployment_target(target:&Target) -> Option<(u32,u32)>{
262-
let(major, minor) = match&*target.os{
263-
"macos" => {
264-
// This does not need to be specific. It just needs to handle x86 vs M1.
265-
let arch = match target.arch.as_ref(){
266-
"x86" | "x86_64" => X86_64,
267-
"arm64e" => Arm64e,
268-
_ => Arm64,
269-
};
270-
macos_deployment_target(arch)
271-
}
272-
"ios" => {
273-
let arch = match target.arch.as_ref(){
274-
"arm64e" => Arm64e,
275-
_ => Arm64,
276-
};
277-
ios_deployment_target(arch,&target.abi)
278-
}
279-
"watchos" => watchos_deployment_target(),
280-
"tvos" => tvos_deployment_target(),
281-
"visionos" => visionos_deployment_target(),
282-
_ => returnNone,
263+
/// Hack for calling `deployment_target` outside of this module.
264+
pubfndeployment_target_for_target(target:&Target) -> (u16,u8,u8){
265+
let arch = if target.llvm_target.starts_with("arm64e"){
266+
Arch::Arm64e
267+
}elseif target.arch == "aarch64"{
268+
Arch::Arm64
269+
}else{
270+
// Dummy architecture, only used by `deployment_target` anyhow
271+
Arch::X86_64
283272
};
284-
285-
Some((major, minor))
273+
let abi = match&*target.abi{
274+
"macabi" => TargetAbi::MacCatalyst,
275+
"sim" => TargetAbi::Simulator,
276+
"" => TargetAbi::Normal,
277+
abi => unreachable!("invalid abi '{abi}' for Apple target"),
278+
};
279+
deployment_target(&target.os, arch, abi)
286280
}
287281

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

293-
Some((major, minor))
294-
}
302+
// On certain targets it makes sense to raise the minimum OS version.
303+
let min = match(os, arch, abi){
304+
// Use 11.0 on Aarch64 as that's the earliest version with M1 support.
305+
("macos",Arch::Arm64 | Arch::Arm64e, _) => (11,0,0),
306+
("ios",Arch::Arm64e, _) => (14,0,0),
307+
// Mac Catalyst defaults to 13.1 in Clang.
308+
("ios", _,TargetAbi::MacCatalyst) => (13,1,0),
309+
_ => os_min,
310+
};
295311

296-
fnmacos_default_deployment_target(arch:Arch) -> (u32,u32){
297-
match arch {
298-
Arm64 | Arm64e => (11,0),
299-
_ => (10,12),
300-
}
301-
}
312+
// The environment variable used to fetch the deployment target.
313+
let env_var = match os {
314+
"macos" => "MACOSX_DEPLOYMENT_TARGET",
315+
"ios" => "IPHONEOS_DEPLOYMENT_TARGET",
316+
"watchos" => "WATCHOS_DEPLOYMENT_TARGET",
317+
"tvos" => "TVOS_DEPLOYMENT_TARGET",
318+
"visionos" => "XROS_DEPLOYMENT_TARGET",
319+
_ => unreachable!("tried to get deployment target env var for non-Apple platform"),
320+
};
302321

303-
fnmacos_deployment_target(arch:Arch) -> (u32,u32){
304-
// If you are looking for the default deployment target, prefer `rustc --print deployment-target`.
305-
// Note: If bumping this version, remember to update it in the rustc/platform-support docs.
306-
from_set_deployment_target("MACOSX_DEPLOYMENT_TARGET")
307-
.unwrap_or_else(|| macos_default_deployment_target(arch))
322+
ifletOk(deployment_target) = env::var(env_var){
323+
matchparse_version(&deployment_target){
324+
// It is common that the deployment target is set too low, e.g. on
325+
// macOS Aarch64 to also target older x86_64, the user may set a
326+
// lower deployment target than supported.
327+
//
328+
// To avoid such issues, we silently raise the deployment target
329+
// here.
330+
// FIXME: We want to show a warning when `version < os_min`.
331+
Ok(version) => version.max(min),
332+
// FIXME: Report erroneous environment variable to user.
333+
Err(_) => min,
334+
}
335+
}else{
336+
min
337+
}
308338
}
309339

310-
pub(crate)fnmacos_llvm_target(arch:Arch) -> String{
311-
let(major, minor) = macos_deployment_target(arch);
312-
format!("{}-apple-macosx{}.{}.0", arch.target_name(), major, minor)
340+
/// Generate the target triple that we need to pass to LLVM and/or Clang.
341+
fnllvm_target(os:&str,arch:Arch,abi:TargetAbi) -> StaticCow<str>{
342+
// The target triple depends on the deployment target, and is required to
343+
// enable features such as cross-language LTO, and for picking the right
344+
// Mach-O commands.
345+
//
346+
// Certain optimizations also depend on the deployment target.
347+
let(major, minor, patch) = deployment_target(os, arch, abi);
348+
let arch = arch.target_name();
349+
// Convert to the "canonical" OS name used by LLVM:
350+
// https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/llvm/lib/TargetParser/Triple.cpp#L236-L282
351+
let os = match os {
352+
"macos" => "macosx",
353+
"ios" => "ios",
354+
"watchos" => "watchos",
355+
"tvos" => "tvos",
356+
"visionos" => "xros",
357+
_ => unreachable!("tried to get LLVM target OS for non-Apple platform"),
358+
};
359+
let environment = match abi {
360+
TargetAbi::Normal => "",
361+
TargetAbi::MacCatalyst => "-macabi",
362+
TargetAbi::Simulator => "-simulator",
363+
};
364+
format!("{arch}-apple-{os}{major}.{minor}.{patch}{environment}").into()
313365
}
314366

315367
fnlink_env_remove(os:&'staticstr) -> StaticCow<[StaticCow<str>]>{
@@ -349,83 +401,19 @@ fn link_env_remove(os: &'static str) -> StaticCow<[StaticCow<str>]> {
349401
}
350402
}
351403

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

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
usesuper::parse_version;
12
usecrate::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+
fntest_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+
}

0 commit comments

Comments
 (0)