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
29type OSVersion = (u16, u8, u8);
33
34fn 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
59fn os_minimum_deployment_target(os: &str) -> OSVersion {
61 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
78fn 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 ("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
99pub 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
111pub 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 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 version.max(min)
135 }
136 Err(error) => {
137 sess.dcx().emit_err(AppleDeploymentTarget::Invalid { env_var, error });
138 min
139 }
140 }
141 } else {
142 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 format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}-{env}")
168 } else {
169 format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}")
170 }
171}