rustc_codegen_ssa/back/
apple.rs

1use std::env;
2use std::fmt::{Display, from_fn};
3use std::num::ParseIntError;
4
5use rustc_session::Session;
6use rustc_target::spec::Target;
7
8use crate::errors::AppleDeploymentTarget;
9
10#[cfg(test)]
11mod tests;
12
13pub(super) fn macho_platform(target: &Target) -> u32 {
14    match (&*target.os, &*target.abi) {
15        ("macos", _) => object::macho::PLATFORM_MACOS,
16        ("ios", "macabi") => object::macho::PLATFORM_MACCATALYST,
17        ("ios", "sim") => object::macho::PLATFORM_IOSSIMULATOR,
18        ("ios", _) => object::macho::PLATFORM_IOS,
19        ("watchos", "sim") => object::macho::PLATFORM_WATCHOSSIMULATOR,
20        ("watchos", _) => object::macho::PLATFORM_WATCHOS,
21        ("tvos", "sim") => object::macho::PLATFORM_TVOSSIMULATOR,
22        ("tvos", _) => object::macho::PLATFORM_TVOS,
23        ("visionos", "sim") => object::macho::PLATFORM_XROSSIMULATOR,
24        ("visionos", _) => object::macho::PLATFORM_XROS,
25        _ => unreachable!("tried to get Mach-O platform for non-Apple target"),
26    }
27}
28
29/// Deployment target or SDK version.
30///
31/// The size of the numbers in here are limited by Mach-O's `LC_BUILD_VERSION`.
32type OSVersion = (u16, u8, u8);
33
34/// Parse an OS version triple (SDK version or deployment target).
35fn parse_version(version: &str) -> Result<OSVersion, ParseIntError> {
36    if let Some((major, minor)) = version.split_once('.') {
37        let major = major.parse()?;
38        if let Some((minor, patch)) = minor.split_once('.') {
39            Ok((major, minor.parse()?, patch.parse()?))
40        } else {
41            Ok((major, minor.parse()?, 0))
42        }
43    } else {
44        Ok((version.parse()?, 0, 0))
45    }
46}
47
48pub fn pretty_version(version: OSVersion) -> impl Display {
49    let (major, minor, patch) = version;
50    from_fn(move |f| {
51        write!(f, "{major}.{minor}")?;
52        if patch != 0 {
53            write!(f, ".{patch}")?;
54        }
55        Ok(())
56    })
57}
58
59/// Minimum operating system versions currently supported by `rustc`.
60fn os_minimum_deployment_target(os: &str) -> OSVersion {
61    // When bumping a version in here, remember to update the platform-support docs too.
62    //
63    // NOTE: The defaults may change in future `rustc` versions, so if you are looking for the
64    // default deployment target, prefer:
65    // ```
66    // $ rustc --print deployment-target
67    // ```
68    match os {
69        "macos" => (10, 12, 0),
70        "ios" => (10, 0, 0),
71        "tvos" => (10, 0, 0),
72        "watchos" => (5, 0, 0),
73        "visionos" => (1, 0, 0),
74        _ => unreachable!("tried to get deployment target for non-Apple platform"),
75    }
76}
77
78/// The deployment target for the given target.
79///
80/// This is similar to `os_minimum_deployment_target`, except that on certain targets it makes sense
81/// to raise the minimum OS version.
82///
83/// This matches what LLVM does, see in part:
84/// <https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/llvm/lib/TargetParser/Triple.cpp#L1900-L1932>
85fn minimum_deployment_target(target: &Target) -> OSVersion {
86    match (&*target.os, &*target.arch, &*target.abi) {
87        ("macos", "aarch64", _) => (11, 0, 0),
88        ("ios", "aarch64", "macabi") => (14, 0, 0),
89        ("ios", "aarch64", "sim") => (14, 0, 0),
90        ("ios", _, _) if target.llvm_target.starts_with("arm64e") => (14, 0, 0),
91        // Mac Catalyst defaults to 13.1 in Clang.
92        ("ios", _, "macabi") => (13, 1, 0),
93        ("tvos", "aarch64", "sim") => (14, 0, 0),
94        ("watchos", "aarch64", "sim") => (7, 0, 0),
95        (os, _, _) => os_minimum_deployment_target(os),
96    }
97}
98
99/// Name of the environment variable used to fetch the deployment target on the given OS.
100pub fn deployment_target_env_var(os: &str) -> &'static str {
101    match os {
102        "macos" => "MACOSX_DEPLOYMENT_TARGET",
103        "ios" => "IPHONEOS_DEPLOYMENT_TARGET",
104        "watchos" => "WATCHOS_DEPLOYMENT_TARGET",
105        "tvos" => "TVOS_DEPLOYMENT_TARGET",
106        "visionos" => "XROS_DEPLOYMENT_TARGET",
107        _ => unreachable!("tried to get deployment target env var for non-Apple platform"),
108    }
109}
110
111/// Get the deployment target based on the standard environment variables, or fall back to the
112/// minimum version supported by `rustc`.
113pub fn deployment_target(sess: &Session) -> OSVersion {
114    let min = minimum_deployment_target(&sess.target);
115    let env_var = deployment_target_env_var(&sess.target.os);
116
117    if let Ok(deployment_target) = env::var(env_var) {
118        match parse_version(&deployment_target) {
119            Ok(version) => {
120                let os_min = os_minimum_deployment_target(&sess.target.os);
121                // It is common that the deployment target is set a bit too low, for example on
122                // macOS Aarch64 to also target older x86_64. So we only want to warn when variable
123                // is lower than the minimum OS supported by rustc, not when the variable is lower
124                // than the minimum for a specific target.
125                if version < os_min {
126                    sess.dcx().emit_warn(AppleDeploymentTarget::TooLow {
127                        env_var,
128                        version: pretty_version(version).to_string(),
129                        os_min: pretty_version(os_min).to_string(),
130                    });
131                }
132
133                // Raise the deployment target to the minimum supported.
134                version.max(min)
135            }
136            Err(error) => {
137                sess.dcx().emit_err(AppleDeploymentTarget::Invalid { env_var, error });
138                min
139            }
140        }
141    } else {
142        // If no deployment target variable is set, default to the minimum found above.
143        min
144    }
145}
146
147pub(super) fn add_version_to_llvm_target(
148    llvm_target: &str,
149    deployment_target: OSVersion,
150) -> String {
151    let mut components = llvm_target.split("-");
152    let arch = components.next().expect("apple target should have arch");
153    let vendor = components.next().expect("apple target should have vendor");
154    let os = components.next().expect("apple target should have os");
155    let environment = components.next();
156    assert_eq!(components.next(), None, "too many LLVM triple components");
157
158    let (major, minor, patch) = deployment_target;
159
160    assert!(
161        !os.contains(|c: char| c.is_ascii_digit()),
162        "LLVM target must not already be versioned"
163    );
164
165    if let Some(env) = environment {
166        // Insert version into OS, before environment
167        format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}-{env}")
168    } else {
169        format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}")
170    }
171}