1use std::env;
2use std::ffi::OsString;
3use std::fmt::{Display, from_fn};
4use std::num::ParseIntError;
5use std::path::PathBuf;
6use std::process::Command;
7
8use itertools::Itertools;
9use rustc_middle::middle::exported_symbols::SymbolExportKind;
10use rustc_session::Session;
11use rustc_target::spec::Target;
12use tracing::debug;
13
14use crate::errors::{AppleDeploymentTarget, XcrunError, XcrunSdkPathWarning};
15use crate::fluent_generated as fluent;
16
17#[cfg(test)]
18mod tests;
19
20pub(super) fn sdk_name(target: &Target) -> &'static str {
22 match (&*target.os, &*target.abi) {
23 ("macos", "") => "MacOSX",
24 ("ios", "") => "iPhoneOS",
25 ("ios", "sim") => "iPhoneSimulator",
26 ("ios", "macabi") => "MacOSX",
28 ("tvos", "") => "AppleTVOS",
29 ("tvos", "sim") => "AppleTVSimulator",
30 ("visionos", "") => "XROS",
31 ("visionos", "sim") => "XRSimulator",
32 ("watchos", "") => "WatchOS",
33 ("watchos", "sim") => "WatchSimulator",
34 (os, abi) => unreachable!("invalid os '{os}' / abi '{abi}' combination for Apple target"),
35 }
36}
37
38pub(super) fn macho_platform(target: &Target) -> u32 {
39 match (&*target.os, &*target.abi) {
40 ("macos", _) => object::macho::PLATFORM_MACOS,
41 ("ios", "macabi") => object::macho::PLATFORM_MACCATALYST,
42 ("ios", "sim") => object::macho::PLATFORM_IOSSIMULATOR,
43 ("ios", _) => object::macho::PLATFORM_IOS,
44 ("watchos", "sim") => object::macho::PLATFORM_WATCHOSSIMULATOR,
45 ("watchos", _) => object::macho::PLATFORM_WATCHOS,
46 ("tvos", "sim") => object::macho::PLATFORM_TVOSSIMULATOR,
47 ("tvos", _) => object::macho::PLATFORM_TVOS,
48 ("visionos", "sim") => object::macho::PLATFORM_XROSSIMULATOR,
49 ("visionos", _) => object::macho::PLATFORM_XROS,
50 _ => unreachable!("tried to get Mach-O platform for non-Apple target"),
51 }
52}
53
54pub(super) fn add_data_and_relocation(
84 file: &mut object::write::Object<'_>,
85 section: object::write::SectionId,
86 symbol: object::write::SymbolId,
87 target: &Target,
88 kind: SymbolExportKind,
89) -> object::write::Result<()> {
90 let authenticated_pointer =
91 kind == SymbolExportKind::Text && target.llvm_target.starts_with("arm64e");
92
93 let data: &[u8] = match target.pointer_width {
94 _ if authenticated_pointer => &[0, 0, 0, 0, 0, 0, 0, 0x80],
95 32 => &[0; 4],
96 64 => &[0; 8],
97 pointer_width => unimplemented!("unsupported Apple pointer width {pointer_width:?}"),
98 };
99
100 if target.arch == "x86_64" {
101 file.section_mut(section).append_data(&[], 16);
103 } else {
104 file.section_mut(section).append_data(&[], target.pointer_width as u64);
106 }
107
108 let offset = file.section_mut(section).append_data(data, data.len() as u64);
109
110 let flags = if authenticated_pointer {
111 object::write::RelocationFlags::MachO {
112 r_type: object::macho::ARM64_RELOC_AUTHENTICATED_POINTER,
113 r_pcrel: false,
114 r_length: 3,
115 }
116 } else if target.arch == "arm" {
117 object::write::RelocationFlags::MachO {
120 r_type: object::macho::ARM_RELOC_VANILLA,
121 r_pcrel: false,
122 r_length: 2,
123 }
124 } else {
125 object::write::RelocationFlags::Generic {
126 kind: object::RelocationKind::Absolute,
127 encoding: object::RelocationEncoding::Generic,
128 size: target.pointer_width as u8,
129 }
130 };
131
132 file.add_relocation(section, object::write::Relocation { offset, addend: 0, symbol, flags })?;
133
134 Ok(())
135}
136
137type OSVersion = (u16, u8, u8);
141
142fn parse_version(version: &str) -> Result<OSVersion, ParseIntError> {
144 if let Some((major, minor)) = version.split_once('.') {
145 let major = major.parse()?;
146 if let Some((minor, patch)) = minor.split_once('.') {
147 Ok((major, minor.parse()?, patch.parse()?))
148 } else {
149 Ok((major, minor.parse()?, 0))
150 }
151 } else {
152 Ok((version.parse()?, 0, 0))
153 }
154}
155
156pub fn pretty_version(version: OSVersion) -> impl Display {
157 let (major, minor, patch) = version;
158 from_fn(move |f| {
159 write!(f, "{major}.{minor}")?;
160 if patch != 0 {
161 write!(f, ".{patch}")?;
162 }
163 Ok(())
164 })
165}
166
167fn os_minimum_deployment_target(os: &str) -> OSVersion {
169 match os {
177 "macos" => (10, 12, 0),
178 "ios" => (10, 0, 0),
179 "tvos" => (10, 0, 0),
180 "watchos" => (5, 0, 0),
181 "visionos" => (1, 0, 0),
182 _ => unreachable!("tried to get deployment target for non-Apple platform"),
183 }
184}
185
186fn minimum_deployment_target(target: &Target) -> OSVersion {
194 match (&*target.os, &*target.arch, &*target.abi) {
195 ("macos", "aarch64", _) => (11, 0, 0),
196 ("ios", "aarch64", "macabi") => (14, 0, 0),
197 ("ios", "aarch64", "sim") => (14, 0, 0),
198 ("ios", _, _) if target.llvm_target.starts_with("arm64e") => (14, 0, 0),
199 ("ios", _, "macabi") => (13, 1, 0),
201 ("tvos", "aarch64", "sim") => (14, 0, 0),
202 ("watchos", "aarch64", "sim") => (7, 0, 0),
203 (os, _, _) => os_minimum_deployment_target(os),
204 }
205}
206
207pub fn deployment_target_env_var(os: &str) -> &'static str {
209 match os {
210 "macos" => "MACOSX_DEPLOYMENT_TARGET",
211 "ios" => "IPHONEOS_DEPLOYMENT_TARGET",
212 "watchos" => "WATCHOS_DEPLOYMENT_TARGET",
213 "tvos" => "TVOS_DEPLOYMENT_TARGET",
214 "visionos" => "XROS_DEPLOYMENT_TARGET",
215 _ => unreachable!("tried to get deployment target env var for non-Apple platform"),
216 }
217}
218
219pub fn deployment_target(sess: &Session) -> OSVersion {
222 let min = minimum_deployment_target(&sess.target);
223 let env_var = deployment_target_env_var(&sess.target.os);
224
225 if let Ok(deployment_target) = env::var(env_var) {
226 match parse_version(&deployment_target) {
227 Ok(version) => {
228 let os_min = os_minimum_deployment_target(&sess.target.os);
229 if version < os_min {
234 sess.dcx().emit_warn(AppleDeploymentTarget::TooLow {
235 env_var,
236 version: pretty_version(version).to_string(),
237 os_min: pretty_version(os_min).to_string(),
238 });
239 }
240
241 version.max(min)
243 }
244 Err(error) => {
245 sess.dcx().emit_err(AppleDeploymentTarget::Invalid { env_var, error });
246 min
247 }
248 }
249 } else {
250 min
252 }
253}
254
255pub(super) fn add_version_to_llvm_target(
256 llvm_target: &str,
257 deployment_target: OSVersion,
258) -> String {
259 let mut components = llvm_target.split("-");
260 let arch = components.next().expect("apple target should have arch");
261 let vendor = components.next().expect("apple target should have vendor");
262 let os = components.next().expect("apple target should have os");
263 let environment = components.next();
264 assert_eq!(components.next(), None, "too many LLVM triple components");
265
266 let (major, minor, patch) = deployment_target;
267
268 assert!(
269 !os.contains(|c: char| c.is_ascii_digit()),
270 "LLVM target must not already be versioned"
271 );
272
273 if let Some(env) = environment {
274 format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}-{env}")
276 } else {
277 format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}")
278 }
279}
280
281pub(super) fn get_sdk_root(sess: &Session) -> Option<PathBuf> {
282 let sdk_name = sdk_name(&sess.target);
283
284 match xcrun_show_sdk_path(sdk_name, sess.verbose_internals()) {
285 Ok((path, stderr)) => {
286 if !stderr.is_empty() {
288 sess.dcx().emit_warn(XcrunSdkPathWarning { sdk_name, stderr });
289 }
290 Some(path)
291 }
292 Err(err) => {
293 let mut diag = sess.dcx().create_err(err);
294
295 if let Some(developer_dir) = xcode_select_developer_dir() {
297 diag.arg("developer_dir", &developer_dir);
298 diag.note(fluent::codegen_ssa_xcrun_found_developer_dir);
299 if developer_dir.as_os_str().to_string_lossy().contains("CommandLineTools") {
300 if sdk_name != "MacOSX" {
301 diag.help(fluent::codegen_ssa_xcrun_command_line_tools_insufficient);
302 }
303 }
304 } else {
305 diag.help(fluent::codegen_ssa_xcrun_no_developer_dir);
306 }
307
308 diag.emit();
309 None
310 }
311 }
312}
313
314fn xcrun_show_sdk_path(
330 sdk_name: &'static str,
331 verbose: bool,
332) -> Result<(PathBuf, String), XcrunError> {
333 let mut cmd = Command::new("xcrun");
334 if verbose {
335 cmd.arg("--verbose");
336 }
337 cmd.arg("--sdk");
340 cmd.arg(&sdk_name.to_lowercase());
341 cmd.arg("--show-sdk-path");
342
343 let output = cmd.output().map_err(|error| XcrunError::FailedInvoking {
346 sdk_name,
347 command_formatted: format!("{cmd:?}"),
348 error,
349 })?;
350
351 let stderr = String::from_utf8_lossy_owned(output.stderr);
354 if !stderr.is_empty() {
355 debug!(stderr, "original xcrun stderr");
356 }
357
358 let stderr = stderr
361 .lines()
362 .filter(|line| {
363 !line.contains("Writing error result bundle")
364 && !line.contains("Requested but did not find extension point with identifier")
365 })
366 .join("\n");
367
368 if output.status.success() {
369 Ok((stdout_to_path(output.stdout), stderr))
370 } else {
371 let stdout = String::from_utf8_lossy_owned(output.stdout).trim().to_string();
374 Err(XcrunError::Unsuccessful {
375 sdk_name,
376 command_formatted: format!("{cmd:?}"),
377 stdout,
378 stderr,
379 })
380 }
381}
382
383fn xcode_select_developer_dir() -> Option<PathBuf> {
388 let mut cmd = Command::new("xcode-select");
389 cmd.arg("--print-path");
390 let output = cmd.output().ok()?;
391 if !output.status.success() {
392 return None;
393 }
394 Some(stdout_to_path(output.stdout))
395}
396
397fn stdout_to_path(mut stdout: Vec<u8>) -> PathBuf {
398 if let Some(b'\n') = stdout.last() {
400 let _ = stdout.pop().unwrap();
401 }
402 #[cfg(unix)]
403 let path = <OsString as std::os::unix::ffi::OsStringExt>::from_vec(stdout);
404 #[cfg(not(unix))] let path = OsString::from(String::from_utf8(stdout).unwrap());
406 PathBuf::from(path)
407}