use std::env; use std::fmt::{Display, from_fn}; use std::num::ParseIntError; use rustc_session::Session; use rustc_target::spec::Target; use crate::errors::AppleDeploymentTarget; #[cfg(test)] mod tests; pub(super) fn macho_platform(target: &Target) -> u32 { 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, ("visionos", "sim") => object::macho::PLATFORM_XROSSIMULATOR, ("visionos", _) => object::macho::PLATFORM_XROS, _ => unreachable!("tried to get Mach-O platform for non-Apple target"), } } /// Deployment target or SDK version. /// /// The size of the numbers in here are limited by Mach-O's `LC_BUILD_VERSION`. type OSVersion = (u16, u8, u8); /// Parse an OS version triple (SDK version or deployment target). fn parse_version(version: &str) -> Result { if let Some((major, minor)) = version.split_once('.') { let major = major.parse()?; if let Some((minor, patch)) = minor.split_once('.') { Ok((major, minor.parse()?, patch.parse()?)) } else { Ok((major, minor.parse()?, 0)) } } else { Ok((version.parse()?, 0, 0)) } } pub fn pretty_version(version: OSVersion) -> impl Display { let (major, minor, patch) = version; from_fn(move |f| { write!(f, "{major}.{minor}")?; if patch != 0 { write!(f, ".{patch}")?; } Ok(()) }) } /// Minimum operating system versions currently supported by `rustc`. fn os_minimum_deployment_target(os: &str) -> OSVersion { // When bumping a version in here, remember to update the platform-support docs too. // // NOTE: The defaults may change in future `rustc` versions, so if you are looking for the // default deployment target, prefer: // ``` // $ rustc --print deployment-target // ``` match os { "macos" => (10, 12, 0), "ios" => (10, 0, 0), "tvos" => (10, 0, 0), "watchos" => (5, 0, 0), "visionos" => (1, 0, 0), _ => unreachable!("tried to get deployment target for non-Apple platform"), } } /// The deployment target for the given target. /// /// This is similar to `os_minimum_deployment_target`, except that on certain targets it makes sense /// to raise the minimum OS version. /// /// This matches what LLVM does, see in part: /// fn minimum_deployment_target(target: &Target) -> OSVersion { match (&*target.os, &*target.arch, &*target.abi) { ("macos", "aarch64", _) => (11, 0, 0), ("ios", "aarch64", "macabi") => (14, 0, 0), ("ios", "aarch64", "sim") => (14, 0, 0), ("ios", _, _) if target.llvm_target.starts_with("arm64e") => (14, 0, 0), // Mac Catalyst defaults to 13.1 in Clang. ("ios", _, "macabi") => (13, 1, 0), ("tvos", "aarch64", "sim") => (14, 0, 0), ("watchos", "aarch64", "sim") => (7, 0, 0), (os, _, _) => os_minimum_deployment_target(os), } } /// Name of the environment variable used to fetch the deployment target on the given OS. fn deployment_target_env_var(os: &str) -> &'static str { match os { "macos" => "MACOSX_DEPLOYMENT_TARGET", "ios" => "IPHONEOS_DEPLOYMENT_TARGET", "watchos" => "WATCHOS_DEPLOYMENT_TARGET", "tvos" => "TVOS_DEPLOYMENT_TARGET", "visionos" => "XROS_DEPLOYMENT_TARGET", _ => unreachable!("tried to get deployment target env var for non-Apple platform"), } } /// Get the deployment target based on the standard environment variables, or fall back to the /// minimum version supported by `rustc`. pub fn deployment_target(sess: &Session) -> OSVersion { let min = minimum_deployment_target(&sess.target); let env_var = deployment_target_env_var(&sess.target.os); if let Ok(deployment_target) = env::var(env_var) { match parse_version(&deployment_target) { Ok(version) => { let os_min = os_minimum_deployment_target(&sess.target.os); // It is common that the deployment target is set a bit too low, for example on // macOS Aarch64 to also target older x86_64. So we only want to warn when variable // is lower than the minimum OS supported by rustc, not when the variable is lower // than the minimum for a specific target. if version < os_min { sess.dcx().emit_warn(AppleDeploymentTarget::TooLow { env_var, version: pretty_version(version).to_string(), os_min: pretty_version(os_min).to_string(), }); } // Raise the deployment target to the minimum supported. version.max(min) } Err(error) => { sess.dcx().emit_err(AppleDeploymentTarget::Invalid { env_var, error }); min } } } else { // If no deployment target variable is set, default to the minimum found above. min } } pub(super) fn add_version_to_llvm_target( llvm_target: &str, deployment_target: OSVersion, ) -> String { let mut components = llvm_target.split("-"); let arch = components.next().expect("apple target should have arch"); let vendor = components.next().expect("apple target should have vendor"); let os = components.next().expect("apple target should have os"); let environment = components.next(); assert_eq!(components.next(), None, "too many LLVM triple components"); let (major, minor, patch) = deployment_target; assert!( !os.contains(|c: char| c.is_ascii_digit()), "LLVM target must not already be versioned" ); if let Some(env) = environment { // Insert version into OS, before environment format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}-{env}") } else { format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}") } }