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.abi) {
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.abi) {
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, sess.verbose_internals()) {
164 Ok((path, stderr)) => {
165 if !stderr.is_empty() {
167 sess.dcx().emit_warn(XcrunSdkPathWarning { sdk_name, stderr });
168 }
169 Some(path)
170 }
171 Err(err) => {
172 let mut diag = sess.dcx().create_err(err);
173
174 if let Some(developer_dir) = xcode_select_developer_dir() {
176 diag.arg("developer_dir", &developer_dir);
177 diag.note(fluent::codegen_ssa_xcrun_found_developer_dir);
178 if developer_dir.as_os_str().to_string_lossy().contains("CommandLineTools") {
179 if sdk_name != "MacOSX" {
180 diag.help(fluent::codegen_ssa_xcrun_command_line_tools_insufficient);
181 }
182 }
183 } else {
184 diag.help(fluent::codegen_ssa_xcrun_no_developer_dir);
185 }
186
187 diag.emit();
188 None
189 }
190 }
191}
192
193fn xcrun_show_sdk_path(
209 sdk_name: &'static str,
210 verbose: bool,
211) -> Result<(PathBuf, String), XcrunError> {
212 let mut cmd = Command::new("xcrun");
213 if verbose {
214 cmd.arg("--verbose");
215 }
216 cmd.arg("--sdk");
219 cmd.arg(&sdk_name.to_lowercase());
220 cmd.arg("--show-sdk-path");
221
222 let output = cmd.output().map_err(|error| XcrunError::FailedInvoking {
225 sdk_name,
226 command_formatted: format!("{cmd:?}"),
227 error,
228 })?;
229
230 let stderr = String::from_utf8_lossy_owned(output.stderr);
233 if !stderr.is_empty() {
234 debug!(stderr, "original xcrun stderr");
235 }
236
237 let stderr = stderr
240 .lines()
241 .filter(|line| {
242 !line.contains("Writing error result bundle")
243 && !line.contains("Requested but did not find extension point with identifier")
244 })
245 .join("\n");
246
247 if output.status.success() {
248 Ok((stdout_to_path(output.stdout), stderr))
249 } else {
250 let stdout = String::from_utf8_lossy_owned(output.stdout).trim().to_string();
253 Err(XcrunError::Unsuccessful {
254 sdk_name,
255 command_formatted: format!("{cmd:?}"),
256 stdout,
257 stderr,
258 })
259 }
260}
261
262fn xcode_select_developer_dir() -> Option<PathBuf> {
267 let mut cmd = Command::new("xcode-select");
268 cmd.arg("--print-path");
269 let output = cmd.output().ok()?;
270 if !output.status.success() {
271 return None;
272 }
273 Some(stdout_to_path(output.stdout))
274}
275
276fn stdout_to_path(mut stdout: Vec<u8>) -> PathBuf {
277 if let Some(b'\n') = stdout.last() {
279 let _ = stdout.pop().unwrap();
280 }
281 #[cfg(unix)]
282 let path = <OsString as std::os::unix::ffi::OsStringExt>::from_vec(stdout);
283 #[cfg(not(unix))] let path = OsString::from(String::from_utf8(stdout).unwrap());
285 PathBuf::from(path)
286}