|
| 1 | +use std::env; |
| 2 | +use std::fmt::{Display, from_fn}; |
| 3 | +use std::num::ParseIntError; |
| 4 | + |
| 5 | +use rustc_session::Session; |
| 6 | +use rustc_target::spec::Target; |
| 7 | + |
| 8 | +use crate::errors::AppleDeploymentTarget; |
| 9 | + |
| 10 | +#[cfg(test)] |
| 11 | +mod tests; |
| 12 | + |
| 13 | +pub(super) fn macho_platform(target: &Target) -> u32 { |
| 14 | + match (&*target.os, &*target.abi) { |
| 15 | + ("macos", _) => object::macho::PLATFORM_MACOS, |
| 16 | + ("ios", "macabi") => object::macho::PLATFORM_MACCATALYST, |
| 17 | + ("ios", "sim") => object::macho::PLATFORM_IOSSIMULATOR, |
| 18 | + ("ios", _) => object::macho::PLATFORM_IOS, |
| 19 | + ("watchos", "sim") => object::macho::PLATFORM_WATCHOSSIMULATOR, |
| 20 | + ("watchos", _) => object::macho::PLATFORM_WATCHOS, |
| 21 | + ("tvos", "sim") => object::macho::PLATFORM_TVOSSIMULATOR, |
| 22 | + ("tvos", _) => object::macho::PLATFORM_TVOS, |
| 23 | + ("visionos", "sim") => object::macho::PLATFORM_XROSSIMULATOR, |
| 24 | + ("visionos", _) => object::macho::PLATFORM_XROS, |
| 25 | + _ => unreachable!("tried to get Mach-O platform for non-Apple target"), |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +/// Deployment target or SDK version. |
| 30 | +/// |
| 31 | +/// The size of the numbers in here are limited by Mach-O's `LC_BUILD_VERSION`. |
| 32 | +type OSVersion = (u16, u8, u8); |
| 33 | + |
| 34 | +/// Parse an OS version triple (SDK version or deployment target). |
| 35 | +fn parse_version(version: &str) -> Result<OSVersion, ParseIntError> { |
| 36 | + if let Some((major, minor)) = version.split_once('.') { |
| 37 | + let major = major.parse()?; |
| 38 | + if let Some((minor, patch)) = minor.split_once('.') { |
| 39 | + Ok((major, minor.parse()?, patch.parse()?)) |
| 40 | + } else { |
| 41 | + Ok((major, minor.parse()?, 0)) |
| 42 | + } |
| 43 | + } else { |
| 44 | + Ok((version.parse()?, 0, 0)) |
| 45 | + } |
| 46 | +} |
| 47 | + |
| 48 | +pub fn pretty_version(version: OSVersion) -> impl Display { |
| 49 | + let (major, minor, patch) = version; |
| 50 | + from_fn(move |f| { |
| 51 | + write!(f, "{major}.{minor}")?; |
| 52 | + if patch != 0 { |
| 53 | + write!(f, ".{patch}")?; |
| 54 | + } |
| 55 | + Ok(()) |
| 56 | + }) |
| 57 | +} |
| 58 | + |
| 59 | +/// Minimum operating system versions currently supported by `rustc`. |
| 60 | +fn os_minimum_deployment_target(os: &str) -> OSVersion { |
| 61 | + // When bumping a version in here, remember to update the platform-support docs too. |
| 62 | + // |
| 63 | + // NOTE: The defaults may change in future `rustc` versions, so if you are looking for the |
| 64 | + // default deployment target, prefer: |
| 65 | + // ``` |
| 66 | + // $ rustc --print deployment-target |
| 67 | + // ``` |
| 68 | + match os { |
| 69 | + "macos" => (10, 12, 0), |
| 70 | + "ios" => (10, 0, 0), |
| 71 | + "tvos" => (10, 0, 0), |
| 72 | + "watchos" => (5, 0, 0), |
| 73 | + "visionos" => (1, 0, 0), |
| 74 | + _ => unreachable!("tried to get deployment target for non-Apple platform"), |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +/// The deployment target for the given target. |
| 79 | +/// |
| 80 | +/// This is similar to `os_minimum_deployment_target`, except that on certain targets it makes sense |
| 81 | +/// to raise the minimum OS version. |
| 82 | +/// |
| 83 | +/// This matches what LLVM does, see in part: |
| 84 | +/// <https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/llvm/lib/TargetParser/Triple.cpp#L1900-L1932> |
| 85 | +fn minimum_deployment_target(target: &Target) -> OSVersion { |
| 86 | + match (&*target.os, &*target.arch, &*target.abi) { |
| 87 | + ("macos", "aarch64", _) => (11, 0, 0), |
| 88 | + ("ios", "aarch64", "macabi") => (14, 0, 0), |
| 89 | + ("ios", "aarch64", "sim") => (14, 0, 0), |
| 90 | + ("ios", _, _) if target.llvm_target.starts_with("arm64e") => (14, 0, 0), |
| 91 | + // Mac Catalyst defaults to 13.1 in Clang. |
| 92 | + ("ios", _, "macabi") => (13, 1, 0), |
| 93 | + ("tvos", "aarch64", "sim") => (14, 0, 0), |
| 94 | + ("watchos", "aarch64", "sim") => (7, 0, 0), |
| 95 | + (os, _, _) => os_minimum_deployment_target(os), |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +/// Name of the environment variable used to fetch the deployment target on the given OS. |
| 100 | +fn deployment_target_env_var(os: &str) -> &'static str { |
| 101 | + match os { |
| 102 | + "macos" => "MACOSX_DEPLOYMENT_TARGET", |
| 103 | + "ios" => "IPHONEOS_DEPLOYMENT_TARGET", |
| 104 | + "watchos" => "WATCHOS_DEPLOYMENT_TARGET", |
| 105 | + "tvos" => "TVOS_DEPLOYMENT_TARGET", |
| 106 | + "visionos" => "XROS_DEPLOYMENT_TARGET", |
| 107 | + _ => unreachable!("tried to get deployment target env var for non-Apple platform"), |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +/// Get the deployment target based on the standard environment variables, or fall back to the |
| 112 | +/// minimum version supported by `rustc`. |
| 113 | +pub fn deployment_target(sess: &Session) -> OSVersion { |
| 114 | + let min = minimum_deployment_target(&sess.target); |
| 115 | + let env_var = deployment_target_env_var(&sess.target.os); |
| 116 | + |
| 117 | + if let Ok(deployment_target) = env::var(env_var) { |
| 118 | + match parse_version(&deployment_target) { |
| 119 | + Ok(version) => { |
| 120 | + let os_min = os_minimum_deployment_target(&sess.target.os); |
| 121 | + // It is common that the deployment target is set a bit too low, for example on |
| 122 | + // macOS Aarch64 to also target older x86_64. So we only want to warn when variable |
| 123 | + // is lower than the minimum OS supported by rustc, not when the variable is lower |
| 124 | + // than the minimum for a specific target. |
| 125 | + if version < os_min { |
| 126 | + sess.dcx().emit_warn(AppleDeploymentTarget::TooLow { |
| 127 | + env_var, |
| 128 | + version: pretty_version(version).to_string(), |
| 129 | + os_min: pretty_version(os_min).to_string(), |
| 130 | + }); |
| 131 | + } |
| 132 | + |
| 133 | + // Raise the deployment target to the minimum supported. |
| 134 | + version.max(min) |
| 135 | + } |
| 136 | + Err(error) => { |
| 137 | + sess.dcx().emit_err(AppleDeploymentTarget::Invalid { env_var, error }); |
| 138 | + min |
| 139 | + } |
| 140 | + } |
| 141 | + } else { |
| 142 | + // If no deployment target variable is set, default to the minimum found above. |
| 143 | + min |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +pub(super) fn add_version_to_llvm_target( |
| 148 | + llvm_target: &str, |
| 149 | + deployment_target: OSVersion, |
| 150 | +) -> String { |
| 151 | + let mut components = llvm_target.split("-"); |
| 152 | + let arch = components.next().expect("apple target should have arch"); |
| 153 | + let vendor = components.next().expect("apple target should have vendor"); |
| 154 | + let os = components.next().expect("apple target should have os"); |
| 155 | + let environment = components.next(); |
| 156 | + assert_eq!(components.next(), None, "too many LLVM triple components"); |
| 157 | + |
| 158 | + let (major, minor, patch) = deployment_target; |
| 159 | + |
| 160 | + assert!( |
| 161 | + !os.contains(|c: char| c.is_ascii_digit()), |
| 162 | + "LLVM target must not already be versioned" |
| 163 | + ); |
| 164 | + |
| 165 | + if let Some(env) = environment { |
| 166 | + // Insert version into OS, before environment |
| 167 | + format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}-{env}") |
| 168 | + } else { |
| 169 | + format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}") |
| 170 | + } |
| 171 | +} |
0 commit comments