1use std::ffi::OsString;
2use std::path::PathBuf;
3use std::process::Command;
4
5use itertools::Itertools;
6use rustc_middle::middle::exported_symbols::SymbolExportKind;
7use rustc_session::Session;
8use rustc_target::spec::Target;
9pub(super) use rustc_target::spec::apple::OSVersion;
10use tracing::debug;
11
12use crate::errors::{XcrunError, XcrunSdkPathWarning};
13use crate::fluent_generated as fluent;
14
15#[cfg(test)]
16mod tests;
17
18pub(super) fn sdk_name(target: &Target) -> &'static str {
20 match (&*target.os, &*target.env) {
21 ("macos", "") => "MacOSX",
22 ("ios", "") => "iPhoneOS",
23 ("ios", "sim") => "iPhoneSimulator",
24 ("ios", "macabi") => "MacOSX",
26 ("tvos", "") => "AppleTVOS",
27 ("tvos", "sim") => "AppleTVSimulator",
28 ("visionos", "") => "XROS",
29 ("visionos", "sim") => "XRSimulator",
30 ("watchos", "") => "WatchOS",
31 ("watchos", "sim") => "WatchSimulator",
32 (os, abi) => unreachable!("invalid os '{os}' / abi '{abi}' combination for Apple target"),
33 }
34}
35
36pub(super) fn macho_platform(target: &Target) -> u32 {
37 match (&*target.os, &*target.env) {
38 ("macos", _) => object::macho::PLATFORM_MACOS,
39 ("ios", "macabi") => object::macho::PLATFORM_MACCATALYST,
40 ("ios", "sim") => object::macho::PLATFORM_IOSSIMULATOR,
41 ("ios", _) => object::macho::PLATFORM_IOS,
42 ("watchos", "sim") => object::macho::PLATFORM_WATCHOSSIMULATOR,
43 ("watchos", _) => object::macho::PLATFORM_WATCHOS,
44 ("tvos", "sim") => object::macho::PLATFORM_TVOSSIMULATOR,
45 ("tvos", _) => object::macho::PLATFORM_TVOS,
46 ("visionos", "sim") => object::macho::PLATFORM_XROSSIMULATOR,
47 ("visionos", _) => object::macho::PLATFORM_XROS,
48 _ => unreachable!("tried to get Mach-O platform for non-Apple target"),
49 }
50}
51
52pub(super) fn add_data_and_relocation(
82 file: &mut object::write::Object<'_>,
83 section: object::write::SectionId,
84 symbol: object::write::SymbolId,
85 target: &Target,
86 kind: SymbolExportKind,
87) -> object::write::Result<()> {
88 let authenticated_pointer =
89 kind == SymbolExportKind::Text && target.llvm_target.starts_with("arm64e");
90
91 let data: &[u8] = match target.pointer_width {
92 _ if authenticated_pointer => &[0, 0, 0, 0, 0, 0, 0, 0x80],
93 32 => &[0; 4],
94 64 => &[0; 8],
95 pointer_width => unimplemented!("unsupported Apple pointer width {pointer_width:?}"),
96 };
97
98 if target.arch == "x86_64" {
99 file.section_mut(section).append_data(&[], 16);
101 } else {
102 file.section_mut(section).append_data(&[], target.pointer_width as u64);
104 }
105
106 let offset = file.section_mut(section).append_data(data, data.len() as u64);
107
108 let flags = if authenticated_pointer {
109 object::write::RelocationFlags::MachO {
110 r_type: object::macho::ARM64_RELOC_AUTHENTICATED_POINTER,
111 r_pcrel: false,
112 r_length: 3,
113 }
114 } else if target.arch == "arm" {
115 object::write::RelocationFlags::MachO {
118 r_type: object::macho::ARM_RELOC_VANILLA,
119 r_pcrel: false,
120 r_length: 2,
121 }
122 } else {
123 object::write::RelocationFlags::Generic {
124 kind: object::RelocationKind::Absolute,
125 encoding: object::RelocationEncoding::Generic,
126 size: target.pointer_width as u8,
127 }
128 };
129
130 file.add_relocation(section, object::write::Relocation { offset, addend: 0, symbol, flags })?;
131
132 Ok(())
133}
134
135pub(super) fn add_version_to_llvm_target(
136 llvm_target: &str,
137 deployment_target: OSVersion,
138) -> String {
139 let mut components = llvm_target.split("-");
140 let arch = components.next().expect("apple target should have arch");
141 let vendor = components.next().expect("apple target should have vendor");
142 let os = components.next().expect("apple target should have os");
143 let environment = components.next();
144 assert_eq!(components.next(), None, "too many LLVM triple components");
145
146 assert!(
147 !os.contains(|c: char| c.is_ascii_digit()),
148 "LLVM target must not already be versioned"
149 );
150
151 let version = deployment_target.fmt_full();
152 if let Some(env) = environment {
153 format!("{arch}-{vendor}-{os}{version}-{env}")
155 } else {
156 format!("{arch}-{vendor}-{os}{version}")
157 }
158}
159
160pub(super) fn get_sdk_root(sess: &Session) -> Option<PathBuf> {
161 let sdk_name = sdk_name(&sess.target);
162
163 match xcrun_show_sdk_path(sdk_name, false) {
168 Ok((path, stderr)) => {
169 if !stderr.is_empty() {
171 sess.dcx().emit_warn(XcrunSdkPathWarning { sdk_name, stderr });
172 }
173 Some(path)
174 }
175 Err(err) => {
176 let mut diag = sess.dcx().create_warn(err);
188 diag.note(fluent::codegen_ssa_xcrun_about);
189
190 if let Some(developer_dir) = xcode_select_developer_dir() {
192 diag.arg("developer_dir", &developer_dir);
193 diag.note(fluent::codegen_ssa_xcrun_found_developer_dir);
194 if developer_dir.as_os_str().to_string_lossy().contains("CommandLineTools") {
195 if sdk_name != "MacOSX" {
196 diag.help(fluent::codegen_ssa_xcrun_command_line_tools_insufficient);
197 }
198 }
199 } else {
200 diag.help(fluent::codegen_ssa_xcrun_no_developer_dir);
201 }
202
203 diag.emit();
204 None
205 }
206 }
207}
208
209fn xcrun_show_sdk_path(
225 sdk_name: &'static str,
226 verbose: bool,
227) -> Result<(PathBuf, String), XcrunError> {
228 let mut cmd = Command::new("xcrun");
231 if verbose {
232 cmd.arg("--verbose");
233 }
234 cmd.arg("--sdk");
237 cmd.arg(&sdk_name.to_lowercase());
238 cmd.arg("--show-sdk-path");
239
240 let output = cmd.output().map_err(|error| XcrunError::FailedInvoking {
243 sdk_name,
244 command_formatted: format!("{cmd:?}"),
245 error,
246 })?;
247
248 let stderr = String::from_utf8_lossy_owned(output.stderr);
251 if !stderr.is_empty() {
252 debug!(stderr, "original xcrun stderr");
253 }
254
255 let stderr = stderr
258 .lines()
259 .filter(|line| {
260 !line.contains("Writing error result bundle")
261 && !line.contains("Requested but did not find extension point with identifier")
262 })
263 .join("\n");
264
265 if output.status.success() {
266 Ok((stdout_to_path(output.stdout), stderr))
267 } else {
268 let stdout = String::from_utf8_lossy_owned(output.stdout).trim().to_string();
271 Err(XcrunError::Unsuccessful {
272 sdk_name,
273 command_formatted: format!("{cmd:?}"),
274 stdout,
275 stderr,
276 })
277 }
278}
279
280fn xcode_select_developer_dir() -> Option<PathBuf> {
285 let mut cmd = Command::new("xcode-select");
286 cmd.arg("--print-path");
287 let output = cmd.output().ok()?;
288 if !output.status.success() {
289 return None;
290 }
291 Some(stdout_to_path(output.stdout))
292}
293
294fn stdout_to_path(mut stdout: Vec<u8>) -> PathBuf {
295 if let Some(b'\n') = stdout.last() {
297 let _ = stdout.pop().unwrap();
298 }
299 #[cfg(unix)]
300 let path = <OsString as std::os::unix::ffi::OsStringExt>::from_vec(stdout);
301 #[cfg(not(unix))] let path = OsString::from(String::from_utf8(stdout).expect("stdout must be UTF-8"));
303 PathBuf::from(path)
304}