blob: 40bc59ca145e5f0d72eb467dcc46da0b192c552f [file] [log] [blame]
use std::{borrow::Cow, env};
use crate::spec::{cvs, Cc, DebuginfoKind, FramePointer, LinkArgs};
use crate::spec::{LinkerFlavor, Lld, SplitDebuginfo, StaticCow, TargetOptions};
fn pre_link_args(os: &'static str, arch: &'static str, abi: &'static str) -> LinkArgs {
let platform_name: StaticCow<str> = match abi {
"sim" => format!("{}-simulator", os).into(),
"macabi" => "mac-catalyst".into(),
_ => os.into(),
};
let platform_version: StaticCow<str> = match os.as_ref() {
"ios" => ios_lld_platform_version(),
"tvos" => tvos_lld_platform_version(),
"watchos" => watchos_lld_platform_version(),
"macos" => macos_lld_platform_version(arch),
_ => unreachable!(),
}
.into();
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: &'static str, abi: &'static str) -> TargetOptions {
// ELF TLS is only available in macOS 10.7+. If you try to compile for 10.6
// either the linker will complain if it is used or the binary will end up
// segfaulting at runtime when run on 10.6. Rust by default supports macOS
// 10.7+, but there is a standard environment variable,
// MACOSX_DEPLOYMENT_TARGET, which is used to signal targeting older
// versions of macOS. For example compiling on 10.10 with
// MACOSX_DEPLOYMENT_TARGET set to 10.6 will cause the linker to generate
// warnings about the usage of ELF TLS.
//
// Here we detect what version is being requested, defaulting to 10.7. ELF
// TLS is flagged as enabled if it looks to be supported. The architecture
// only matters for default deployment target which is 11.0 for ARM64 and
// 10.7 for everything else.
let has_thread_local = macos_deployment_target("x86_64") >= (10, 7);
TargetOptions {
os: os.into(),
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,
default_dwarf_version: 2,
frame_pointer: FramePointer::Always,
has_rpath: true,
dll_suffix: ".dylib".into(),
archive_format: "darwin".into(),
has_thread_local,
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()
}
}
fn deployment_target(var_name: &str) -> Option<(u32, u32)> {
let deployment_target = env::var(var_name).ok();
deployment_target
.as_ref()
.and_then(|s| s.split_once('.'))
.and_then(|(a, b)| a.parse::<u32>().and_then(|a| b.parse::<u32>().map(|b| (a, b))).ok())
}
fn macos_default_deployment_target(arch: &str) -> (u32, u32) {
if arch == "arm64" { (11, 0) } else { (10, 7) }
}
fn macos_deployment_target(arch: &str) -> (u32, u32) {
deployment_target("MACOSX_DEPLOYMENT_TARGET")
.unwrap_or_else(|| macos_default_deployment_target(arch))
}
fn macos_lld_platform_version(arch: &str) -> String {
let (major, minor) = macos_deployment_target(arch);
format!("{}.{}", major, minor)
}
pub fn macos_llvm_target(arch: &str) -> String {
let (major, minor) = macos_deployment_target(arch);
format!("{}-apple-macosx{}.{}.0", arch, major, minor)
}
pub fn macos_link_env_remove() -> Vec<StaticCow<str>> {
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") {
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
}
fn ios_deployment_target() -> (u32, u32) {
deployment_target("IPHONEOS_DEPLOYMENT_TARGET").unwrap_or((7, 0))
}
pub fn ios_llvm_target(arch: &str) -> 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, major, minor)
}
fn ios_lld_platform_version() -> String {
let (major, minor) = ios_deployment_target();
format!("{}.{}", major, minor)
}
pub fn ios_sim_llvm_target(arch: &str) -> String {
let (major, minor) = ios_deployment_target();
format!("{}-apple-ios{}.{}.0-simulator", arch, major, minor)
}
fn tvos_deployment_target() -> (u32, u32) {
deployment_target("TVOS_DEPLOYMENT_TARGET").unwrap_or((7, 0))
}
fn tvos_lld_platform_version() -> String {
let (major, minor) = tvos_deployment_target();
format!("{}.{}", major, minor)
}
fn watchos_deployment_target() -> (u32, u32) {
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: &str) -> String {
let (major, minor) = watchos_deployment_target();
format!("{}-apple-watchos{}.{}.0-simulator", arch, major, minor)
}