1use std::ffi::OsString;
2use std::path::PathBuf;
3use std::process::Command;
45use itertools::Itertools;
6use rustc_errors::msg;
7use rustc_middle::middle::exported_symbols::SymbolExportKind;
8use rustc_session::Session;
9pub(super) use rustc_target::spec::apple::OSVersion;
10use rustc_target::spec::{Arch, Env, Os, Target};
11use tracing::debug;
1213use crate::errors::{XcrunError, XcrunSdkPathWarning};
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(rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("the SDK is needed by the linker to know where to find symbols in system libraries and for embedding the SDK version in the final object file"))msg!("the SDK is needed by the linker to know where to find symbols in system libraries and for embedding the SDK version in the final object file"));
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(rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("found active developer directory at \"{$developer_dir}\""))msg!("found active developer directory at \"{$developer_dir}\""));
194if developer_dir.as_os_str().to_string_lossy().contains("CommandLineTools") {
195if sdk_name != "MacOSX" {
196diag.help(rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("when compiling for iOS, tvOS, visionOS or watchOS, you need a full installation of Xcode"))msg!("when compiling for iOS, tvOS, visionOS or watchOS, you need a full installation of Xcode"));
197 }
198 }
199 } else {
200diag.help(rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("pass the path of an Xcode installation via the DEVELOPER_DIR environment variable, or an SDK with the SDKROOT environment variable"))msg!("pass the path of an Xcode installation via the DEVELOPER_DIR environment variable, or an SDK with the SDKROOT environment variable"));
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}