1use std::ffi::OsString;
2use std::path::PathBuf;
3use std::process::Command;
45use itertools::Itertools;
6use rustc_middle::middle::exported_symbols::SymbolExportKind;
7use rustc_session::Session;
8pub(super) use rustc_target::spec::apple::OSVersion;
9use rustc_target::spec::{Arch, Env, Os, Target};
10use tracing::debug;
1112use crate::errors::{XcrunError, XcrunSdkPathWarning};
13use crate::fluent_generatedas fluent;
1415#[cfg(test)]
16mod tests;
1718/// The canonical name of the desired SDK for a given target.
19pub(super) fn sdk_name(target: &Target) -> &'static str {
20match (&target.os, &target.env) {
21 (Os::MacOs, Env::Unspecified) => "MacOSX",
22 (Os::IOs, Env::Unspecified) => "iPhoneOS",
23 (Os::IOs, Env::Sim) => "iPhoneSimulator",
24// Mac Catalyst uses the macOS SDK
25(Os::IOs, Env::MacAbi) => "MacOSX",
26 (Os::TvOs, Env::Unspecified) => "AppleTVOS",
27 (Os::TvOs, Env::Sim) => "AppleTVSimulator",
28 (Os::VisionOs, Env::Unspecified) => "XROS",
29 (Os::VisionOs, Env::Sim) => "XRSimulator",
30 (Os::WatchOs, Env::Unspecified) => "WatchOS",
31 (Os::WatchOs, Env::Sim) => "WatchSimulator",
32 (os, abi) => {
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("invalid os \'{0}\' / abi \'{1}\' combination for Apple target",
os, abi)));
}unreachable!("invalid os '{os}' / abi '{abi}' combination for Apple target"),
33 }
34}
3536pub(super) fn macho_platform(target: &Target) -> u32 {
37match (&target.os, &target.env) {
38 (Os::MacOs, _) => object::macho::PLATFORM_MACOS,
39 (Os::IOs, Env::MacAbi) => object::macho::PLATFORM_MACCATALYST,
40 (Os::IOs, Env::Sim) => object::macho::PLATFORM_IOSSIMULATOR,
41 (Os::IOs, _) => object::macho::PLATFORM_IOS,
42 (Os::WatchOs, Env::Sim) => object::macho::PLATFORM_WATCHOSSIMULATOR,
43 (Os::WatchOs, _) => object::macho::PLATFORM_WATCHOS,
44 (Os::TvOs, Env::Sim) => object::macho::PLATFORM_TVOSSIMULATOR,
45 (Os::TvOs, _) => object::macho::PLATFORM_TVOS,
46 (Os::VisionOs, Env::Sim) => object::macho::PLATFORM_XROSSIMULATOR,
47 (Os::VisionOs, _) => object::macho::PLATFORM_XROS,
48 (os, env) => {
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("invalid os \'{0}\' / env \'{1}\' combination for Apple target",
os, env)));
}unreachable!("invalid os '{os}' / env '{env}' combination for Apple target"),
49 }
50}
5152/// Add relocation and section data needed for a symbol to be considered
53/// undefined by ld64.
54///
55/// The relocation must be valid, and hence must point to a valid piece of
56/// machine code, and hence this is unfortunately very architecture-specific.
57///
58///
59/// # New architectures
60///
61/// The values here are basically the same as emitted by the following program:
62///
63/// ```c
64/// // clang -c foo.c -target $CLANG_TARGET
65/// void foo(void);
66///
67/// extern int bar;
68///
69/// void* foobar[2] = {
70/// (void*)foo,
71/// (void*)&bar,
72/// // ...
73/// };
74/// ```
75///
76/// Can be inspected with:
77/// ```console
78/// objdump --macho --reloc foo.o
79/// objdump --macho --full-contents foo.o
80/// ```
81pub(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<()> {
88let authenticated_pointer =
89kind == SymbolExportKind::Text && target.llvm_target.starts_with("arm64e");
9091let data: &[u8] = match target.pointer_width {
92_ if authenticated_pointer => &[0, 0, 0, 0, 0, 0, 0, 0x80],
9332 => &[0; 4],
9464 => &[0; 8],
95 pointer_width => {
::core::panicking::panic_fmt(format_args!("not implemented: {0}",
format_args!("unsupported Apple pointer width {0:?}",
pointer_width)));
}unimplemented!("unsupported Apple pointer width {pointer_width:?}"),
96 };
9798if target.arch == Arch::X86_64 {
99// Force alignment for the entire section to be 16 on x86_64.
100file.section_mut(section).append_data(&[], 16);
101 } else {
102// Elsewhere, the section alignment is the same as the pointer width.
103file.section_mut(section).append_data(&[], target.pointer_width as u64);
104 }
105106let offset = file.section_mut(section).append_data(data, data.len() as u64);
107108let 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 == Arch::Arm {
115// FIXME(madsmtm): Remove once `object` supports 32-bit ARM relocations:
116 // https://github.com/gimli-rs/object/pull/757
117object::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 };
129130file.add_relocation(section, object::write::Relocation { offset, addend: 0, symbol, flags })?;
131132Ok(())
133}
134135pub(super) fn add_version_to_llvm_target(
136 llvm_target: &str,
137 deployment_target: OSVersion,
138) -> String {
139let mut components = llvm_target.split("-");
140let arch = components.next().expect("apple target should have arch");
141let vendor = components.next().expect("apple target should have vendor");
142let os = components.next().expect("apple target should have os");
143let environment = components.next();
144match (&components.next(), &None) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
let kind = ::core::panicking::AssertKind::Eq;
::core::panicking::assert_failed(kind, &*left_val, &*right_val,
::core::option::Option::Some(format_args!("too many LLVM triple components")));
}
}
};assert_eq!(components.next(), None, "too many LLVM triple components");
145146if !!os.contains(|c: char| c.is_ascii_digit()) {
{
::core::panicking::panic_fmt(format_args!("LLVM target must not already be versioned"));
}
};assert!(
147 !os.contains(|c: char| c.is_ascii_digit()),
148"LLVM target must not already be versioned"
149);
150151let version = deployment_target.fmt_full();
152if let Some(env) = environment {
153// Insert version into OS, before environment
154::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}-{1}-{2}{3}-{4}", arch, vendor,
os, version, env))
})format!("{arch}-{vendor}-{os}{version}-{env}")155 } else {
156::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}-{1}-{2}{3}", arch, vendor, os,
version))
})format!("{arch}-{vendor}-{os}{version}")157 }
158}
159160pub(super) fn get_sdk_root(sess: &Session) -> Option<PathBuf> {
161let sdk_name = sdk_name(&sess.target);
162163// Attempt to invoke `xcrun` to find the SDK.
164 //
165 // Note that when cross-compiling from e.g. Linux, the `xcrun` binary may sometimes be provided
166 // as a shim by a cross-compilation helper tool. It usually isn't, but we still try nonetheless.
167match xcrun_show_sdk_path(sdk_name, false) {
168Ok((path, stderr)) => {
169// Emit extra stderr, such as if `-verbose` was passed, or if `xcrun` emitted a warning.
170if !stderr.is_empty() {
171sess.dcx().emit_warn(XcrunSdkPathWarning { sdk_name, stderr });
172 }
173Some(path)
174 }
175Err(err) => {
176// Failure to find the SDK is not a hard error, since the user might have specified it
177 // in a manner unknown to us (moreso if cross-compiling):
178 // - A compiler driver like `zig cc` which links using an internally bundled SDK.
179 // - Extra linker arguments (`-Clink-arg=-syslibroot`).
180 // - A custom linker or custom compiler driver.
181 //
182 // Though we still warn, since such cases are uncommon, and it is very hard to debug if
183 // you do not know the details.
184 //
185 // FIXME(madsmtm): Make this a lint, to allow deny warnings to work.
186 // (Or fix <https://github.com/rust-lang/rust/issues/21204>).
187let mut diag = sess.dcx().create_warn(err);
188diag.note(fluent::codegen_ssa_xcrun_about);
189190// Recognize common error cases, and give more Rust-specific error messages for those.
191if let Some(developer_dir) = xcode_select_developer_dir() {
192diag.arg("developer_dir", &developer_dir);
193diag.note(fluent::codegen_ssa_xcrun_found_developer_dir);
194if developer_dir.as_os_str().to_string_lossy().contains("CommandLineTools") {
195if sdk_name != "MacOSX" {
196diag.help(fluent::codegen_ssa_xcrun_command_line_tools_insufficient);
197 }
198 }
199 } else {
200diag.help(fluent::codegen_ssa_xcrun_no_developer_dir);
201 }
202203diag.emit();
204None205 }
206 }
207}
208209/// Invoke `xcrun --sdk $sdk_name --show-sdk-path` to get the SDK path.
210///
211/// The exact logic that `xcrun` uses is unspecified (see `man xcrun` for a few details), and may
212/// change between macOS and Xcode versions, but it roughly boils down to finding the active
213/// developer directory, and then invoking `xcodebuild -sdk $sdk_name -version` to get the SDK
214/// details.
215///
216/// Finding the developer directory is roughly done by looking at, in order:
217/// - The `DEVELOPER_DIR` environment variable.
218/// - The `/var/db/xcode_select_link` symlink (set by `xcode-select --switch`).
219/// - `/Applications/Xcode.app` (hardcoded fallback path).
220/// - `/Library/Developer/CommandLineTools` (hardcoded fallback path).
221///
222/// Note that `xcrun` caches its result, but with a cold cache this whole operation can be quite
223/// slow, especially so the first time it's run after a reboot.
224fn xcrun_show_sdk_path(
225 sdk_name: &'static str,
226 verbose: bool,
227) -> Result<(PathBuf, String), XcrunError> {
228// Intentionally invoke the `xcrun` in PATH, since e.g. nixpkgs provide an `xcrun` shim, so we
229 // don't want to require `/usr/bin/xcrun`.
230let mut cmd = Command::new("xcrun");
231if verbose {
232cmd.arg("--verbose");
233 }
234// The `--sdk` parameter is the same as in xcodebuild, namely either an absolute path to an SDK,
235 // or the (lowercase) canonical name of an SDK.
236cmd.arg("--sdk");
237cmd.arg(&sdk_name.to_lowercase());
238cmd.arg("--show-sdk-path");
239240// We do not stream stdout/stderr lines directly to the user, since whether they are warnings or
241 // errors depends on the status code at the end.
242let output = cmd.output().map_err(|error| XcrunError::FailedInvoking {
243sdk_name,
244 command_formatted: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0:?}", cmd))
})format!("{cmd:?}"),
245error,
246 })?;
247248// It is fine to do lossy conversion here, non-UTF-8 paths are quite rare on macOS nowadays
249 // (only possible with the HFS+ file system), and we only use it for error messages.
250let stderr = String::from_utf8_lossy_owned(output.stderr);
251if !stderr.is_empty() {
252{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_codegen_ssa/src/back/apple.rs:252",
"rustc_codegen_ssa::back::apple", ::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_codegen_ssa/src/back/apple.rs"),
::tracing_core::__macro_support::Option::Some(252u32),
::tracing_core::__macro_support::Option::Some("rustc_codegen_ssa::back::apple"),
::tracing_core::field::FieldSet::new(&["message", "stderr"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("original xcrun stderr")
as &dyn Value)),
(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&stderr as
&dyn Value))])
});
} else { ; }
};debug!(stderr, "original xcrun stderr");
253 }
254255// Some versions of `xcodebuild` output beefy errors when invoked via `xcrun`,
256 // but these are usually red herrings.
257let stderr = stderr258 .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");
264265if output.status.success() {
266Ok((stdout_to_path(output.stdout), stderr))
267 } else {
268// Output both stdout and stderr, since shims of `xcrun` (such as the one provided by
269 // nixpkgs), do not always use stderr for errors.
270let stdout = String::from_utf8_lossy_owned(output.stdout).trim().to_string();
271Err(XcrunError::Unsuccessful {
272sdk_name,
273 command_formatted: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0:?}", cmd))
})format!("{cmd:?}"),
274stdout,
275stderr,
276 })
277 }
278}
279280/// Invoke `xcode-select --print-path`, and return the current developer directory.
281///
282/// NOTE: We don't do any error handling here, this is only used as a canary in diagnostics (`xcrun`
283/// will have already emitted the relevant error information).
284fn xcode_select_developer_dir() -> Option<PathBuf> {
285let mut cmd = Command::new("xcode-select");
286cmd.arg("--print-path");
287let output = cmd.output().ok()?;
288if !output.status.success() {
289return None;
290 }
291Some(stdout_to_path(output.stdout))
292}
293294fn stdout_to_path(mut stdout: Vec<u8>) -> PathBuf {
295// Remove trailing newline.
296if let Some(b'\n') = stdout.last() {
297let _ = stdout.pop().unwrap();
298 }
299#[cfg(unix)]
300let path = <OsStringas std::os::unix::ffi::OsStringExt>::from_vec(stdout);
301#[cfg(not(unix))] // Not so important, this is mostly used on macOS
302let path = OsString::from(String::from_utf8(stdout).expect("stdout must be UTF-8"));
303PathBuf::from(path)
304}