use std::{borrow::Cow, env}; use crate::spec::{cvs, Cc, DebuginfoKind, FramePointer, LinkArgs}; use crate::spec::{LinkerFlavor, Lld, SplitDebuginfo, StaticCow, Target, TargetOptions}; #[cfg(test)] #[path = "apple/tests.rs"] mod tests; use Arch::*; #[allow(non_camel_case_types)] #[derive(Copy, Clone)] pub enum Arch { Armv7k, Armv7s, Arm64, Arm64_32, I386, I686, X86_64, X86_64h, X86_64_sim, X86_64_macabi, Arm64_macabi, Arm64_sim, } impl Arch { pub fn target_name(self) -> &'static str { match self { Armv7k => "armv7k", Armv7s => "armv7s", Arm64 | Arm64_macabi | Arm64_sim => "arm64", Arm64_32 => "arm64_32", I386 => "i386", I686 => "i686", X86_64 | X86_64_sim | X86_64_macabi => "x86_64", X86_64h => "x86_64h", } } pub fn target_arch(self) -> Cow<'static, str> { Cow::Borrowed(match self { Armv7k | Armv7s => "arm", Arm64 | Arm64_32 | Arm64_macabi | Arm64_sim => "aarch64", I386 | I686 => "x86", X86_64 | X86_64_sim | X86_64_macabi | X86_64h => "x86_64", }) } fn target_abi(self) -> &'static str { match self { Armv7k | Armv7s | Arm64 | Arm64_32 | I386 | I686 | X86_64 | X86_64h => "", X86_64_macabi | Arm64_macabi => "macabi", // x86_64-apple-ios is a simulator target, even though it isn't // declared that way in the target like the other ones... Arm64_sim | X86_64_sim => "sim", } } fn target_cpu(self) -> &'static str { match self { Armv7k => "cortex-a8", Armv7s => "swift", // iOS 10 is only supported on iPhone 5 or higher. Arm64 => "apple-a7", Arm64_32 => "apple-s4", // Only macOS 10.12+ is supported, which means // all x86_64/x86 CPUs must be running at least penryn // https://github.com/llvm/llvm-project/blob/01f924d0e37a5deae51df0d77e10a15b63aa0c0f/clang/lib/Driver/ToolChains/Arch/X86.cpp#L79-L82 I386 | I686 => "penryn", X86_64 | X86_64_sim => "penryn", X86_64_macabi => "penryn", // Note: `core-avx2` is slightly more advanced than `x86_64h`, see // comments (and disabled features) in `x86_64h_apple_darwin` for // details. It is a higher baseline then `penryn` however. X86_64h => "core-avx2", Arm64_macabi => "apple-a12", Arm64_sim => "apple-a12", } } } fn pre_link_args(os: &'static str, arch: Arch, abi: &'static str) -> LinkArgs { let platform_name: StaticCow = match abi { "sim" => format!("{os}-simulator").into(), "macabi" => "mac-catalyst".into(), _ => os.into(), }; let platform_version: StaticCow = match os { "ios" => ios_lld_platform_version(), "tvos" => tvos_lld_platform_version(), "watchos" => watchos_lld_platform_version(), "macos" => macos_lld_platform_version(arch), _ => unreachable!(), } .into(); let arch = arch.target_name(); let mut args = TargetOptions::link_args( LinkerFlavor::Darwin(Cc::No, Lld::No), &["-arch", arch, "-platform_version"], ); super::add_link_args_iter( &mut args, LinkerFlavor::Darwin(Cc::No, Lld::No), [platform_name, platform_version.clone(), platform_version].into_iter(), ); if abi != "macabi" { super::add_link_args(&mut args, LinkerFlavor::Darwin(Cc::Yes, Lld::No), &["-arch", arch]); } args } pub fn opts(os: &'static str, arch: Arch) -> TargetOptions { let abi = arch.target_abi(); TargetOptions { abi: abi.into(), os: os.into(), cpu: arch.target_cpu().into(), link_env_remove: link_env_remove(arch, os), vendor: "apple".into(), linker_flavor: LinkerFlavor::Darwin(Cc::Yes, Lld::No), // macOS has -dead_strip, which doesn't rely on function_sections function_sections: false, dynamic_linking: true, pre_link_args: pre_link_args(os, arch, abi), families: cvs!["unix"], is_like_osx: true, // LLVM notes that macOS 10.11+ and iOS 9+ default // to v4, so we do the same. // https://github.com/llvm/llvm-project/blob/378778a0d10c2f8d5df8ceff81f95b6002984a4b/clang/lib/Driver/ToolChains/Darwin.cpp#L1203 default_dwarf_version: 4, frame_pointer: FramePointer::Always, has_rpath: true, dll_suffix: ".dylib".into(), archive_format: "darwin".into(), // Thread locals became available with iOS 8 and macOS 10.7, // and both are far below our minimum. has_thread_local: true, abi_return_struct_as_int: true, emit_debug_gdb_scripts: false, eh_frame_header: false, debuginfo_kind: DebuginfoKind::DwarfDsym, // The historical default for macOS targets is to run `dsymutil` which // generates a packed version of debuginfo split from the main file. split_debuginfo: SplitDebuginfo::Packed, supported_split_debuginfo: Cow::Borrowed(&[ SplitDebuginfo::Packed, SplitDebuginfo::Unpacked, SplitDebuginfo::Off, ]), // This environment variable is pretty magical but is intended for // producing deterministic builds. This was first discovered to be used // by the `ar` tool as a way to control whether or not mtime entries in // the archive headers were set to zero or not. It appears that // eventually the linker got updated to do the same thing and now reads // this environment variable too in recent versions. // // For some more info see the commentary on #47086 link_env: Cow::Borrowed(&[(Cow::Borrowed("ZERO_AR_DATE"), Cow::Borrowed("1"))]), ..Default::default() } } pub fn sdk_version(platform: u32) -> Option<(u32, u32)> { // NOTE: These values are from an arbitrary point in time but shouldn't make it into the final // binary since the final link command will have the current SDK version passed to it. match platform { object::macho::PLATFORM_MACOS => Some((13, 1)), object::macho::PLATFORM_IOS | object::macho::PLATFORM_IOSSIMULATOR | object::macho::PLATFORM_TVOS | object::macho::PLATFORM_TVOSSIMULATOR | object::macho::PLATFORM_MACCATALYST => Some((16, 2)), object::macho::PLATFORM_WATCHOS | object::macho::PLATFORM_WATCHOSSIMULATOR => Some((9, 1)), _ => None, } } pub fn platform(target: &Target) -> Option { Some(match (&*target.os, &*target.abi) { ("macos", _) => object::macho::PLATFORM_MACOS, ("ios", "macabi") => object::macho::PLATFORM_MACCATALYST, ("ios", "sim") => object::macho::PLATFORM_IOSSIMULATOR, ("ios", _) => object::macho::PLATFORM_IOS, ("watchos", "sim") => object::macho::PLATFORM_WATCHOSSIMULATOR, ("watchos", _) => object::macho::PLATFORM_WATCHOS, ("tvos", "sim") => object::macho::PLATFORM_TVOSSIMULATOR, ("tvos", _) => object::macho::PLATFORM_TVOS, _ => return None, }) } pub fn deployment_target(target: &Target) -> Option<(u32, u32)> { let (major, minor) = match &*target.os { "macos" => { // This does not need to be specific. It just needs to handle x86 vs M1. let arch = if target.arch == "x86" || target.arch == "x86_64" { X86_64 } else { Arm64 }; macos_deployment_target(arch) } "ios" => match &*target.abi { "macabi" => mac_catalyst_deployment_target(), _ => ios_deployment_target(), }, "watchos" => watchos_deployment_target(), "tvos" => tvos_deployment_target(), _ => return None, }; Some((major, minor)) } fn from_set_deployment_target(var_name: &str) -> Option<(u32, u32)> { let deployment_target = env::var(var_name).ok()?; let (unparsed_major, unparsed_minor) = deployment_target.split_once('.')?; let (major, minor) = (unparsed_major.parse().ok()?, unparsed_minor.parse().ok()?); Some((major, minor)) } fn macos_default_deployment_target(arch: Arch) -> (u32, u32) { match arch { // Note: Arm64_sim is not included since macOS has no simulator. Arm64 | Arm64_macabi => (11, 0), _ => (10, 12), } } fn macos_deployment_target(arch: Arch) -> (u32, u32) { // If you are looking for the default deployment target, prefer `rustc --print deployment-target`. from_set_deployment_target("MACOSX_DEPLOYMENT_TARGET") .unwrap_or_else(|| macos_default_deployment_target(arch)) } fn macos_lld_platform_version(arch: Arch) -> String { let (major, minor) = macos_deployment_target(arch); format!("{major}.{minor}") } pub fn macos_llvm_target(arch: Arch) -> String { let (major, minor) = macos_deployment_target(arch); format!("{}-apple-macosx{}.{}.0", arch.target_name(), major, minor) } fn link_env_remove(arch: Arch, os: &'static str) -> StaticCow<[StaticCow]> { // Apple platforms only officially support macOS as a host for any compilation. // // If building for macOS, we go ahead and remove any erroneous environment state // that's only applicable to cross-OS compilation. Always leave anything for the // host OS alone though. if os == "macos" { let mut env_remove = Vec::with_capacity(2); // Remove the `SDKROOT` environment variable if it's clearly set for the wrong platform, which // may occur when we're linking a custom build script while targeting iOS for example. if let Ok(sdkroot) = env::var("SDKROOT") { if sdkroot.contains("iPhoneOS.platform") || sdkroot.contains("iPhoneSimulator.platform") || sdkroot.contains("AppleTVOS.platform") || sdkroot.contains("AppleTVSimulator.platform") || sdkroot.contains("WatchOS.platform") || sdkroot.contains("WatchSimulator.platform") { env_remove.push("SDKROOT".into()) } } // Additionally, `IPHONEOS_DEPLOYMENT_TARGET` must not be set when using the Xcode linker at // "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld", // although this is apparently ignored when using the linker at "/usr/bin/ld". env_remove.push("IPHONEOS_DEPLOYMENT_TARGET".into()); env_remove.push("TVOS_DEPLOYMENT_TARGET".into()); env_remove.into() } else { // Otherwise if cross-compiling for a different OS/SDK, remove any part // of the linking environment that's wrong and reversed. match arch { Armv7k | Armv7s | Arm64 | Arm64_32 | I386 | I686 | X86_64 | X86_64_sim | X86_64h | Arm64_sim => { cvs!["MACOSX_DEPLOYMENT_TARGET"] } X86_64_macabi | Arm64_macabi => cvs!["IPHONEOS_DEPLOYMENT_TARGET"], } } } fn ios_deployment_target() -> (u32, u32) { // If you are looking for the default deployment target, prefer `rustc --print deployment-target`. from_set_deployment_target("IPHONEOS_DEPLOYMENT_TARGET").unwrap_or((10, 0)) } fn mac_catalyst_deployment_target() -> (u32, u32) { // If you are looking for the default deployment target, prefer `rustc --print deployment-target`. from_set_deployment_target("IPHONEOS_DEPLOYMENT_TARGET").unwrap_or((14, 0)) } pub fn ios_llvm_target(arch: Arch) -> String { // Modern iOS tooling extracts information about deployment target // from LC_BUILD_VERSION. This load command will only be emitted when // we build with a version specific `llvm_target`, with the version // set high enough. Luckily one LC_BUILD_VERSION is enough, for Xcode // to pick it up (since std and core are still built with the fallback // of version 7.0 and hence emit the old LC_IPHONE_MIN_VERSION). let (major, minor) = ios_deployment_target(); format!("{}-apple-ios{}.{}.0", arch.target_name(), major, minor) } fn ios_lld_platform_version() -> String { let (major, minor) = ios_deployment_target(); format!("{major}.{minor}") } pub fn ios_sim_llvm_target(arch: Arch) -> String { let (major, minor) = ios_deployment_target(); format!("{}-apple-ios{}.{}.0-simulator", arch.target_name(), major, minor) } fn tvos_deployment_target() -> (u32, u32) { // If you are looking for the default deployment target, prefer `rustc --print deployment-target`. from_set_deployment_target("TVOS_DEPLOYMENT_TARGET").unwrap_or((10, 0)) } fn tvos_lld_platform_version() -> String { let (major, minor) = tvos_deployment_target(); format!("{major}.{minor}") } pub fn tvos_llvm_target(arch: Arch) -> String { let (major, minor) = tvos_deployment_target(); format!("{}-apple-tvos{}.{}.0", arch.target_name(), major, minor) } pub fn tvos_sim_llvm_target(arch: Arch) -> String { let (major, minor) = tvos_deployment_target(); format!("{}-apple-tvos{}.{}.0-simulator", arch.target_name(), major, minor) } fn watchos_deployment_target() -> (u32, u32) { // If you are looking for the default deployment target, prefer `rustc --print deployment-target`. from_set_deployment_target("WATCHOS_DEPLOYMENT_TARGET").unwrap_or((5, 0)) } fn watchos_lld_platform_version() -> String { let (major, minor) = watchos_deployment_target(); format!("{major}.{minor}") } pub fn watchos_sim_llvm_target(arch: Arch) -> String { let (major, minor) = watchos_deployment_target(); format!("{}-apple-watchos{}.{}.0-simulator", arch.target_name(), major, minor) }