Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Apple version handling in the compiler #138947

Merged
merged 2 commits into from
Apr 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions compiler/rustc_codegen_ssa/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ codegen_ssa_add_native_library = failed to add native library {$library_path}: {

codegen_ssa_aix_strip_not_used = using host's `strip` binary to cross-compile to AIX which is not guaranteed to work

codegen_ssa_apple_deployment_target_invalid =
failed to parse deployment target specified in {$env_var}: {$error}

codegen_ssa_apple_deployment_target_too_low =
deployment target in {$env_var} was set to {$version}, but the minimum supported by `rustc` is {$os_min}

codegen_ssa_archive_build_failure = failed to build archive at `{$path}`: {$error}

codegen_ssa_atomic_compare_exchange = Atomic compare-exchange intrinsic missing failure memory ordering
Expand Down
131 changes: 5 additions & 126 deletions compiler/rustc_codegen_ssa/src/back/apple.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
use std::env;
use std::ffi::OsString;
use std::fmt::{Display, from_fn};
use std::num::ParseIntError;
use std::path::PathBuf;
use std::process::Command;

use itertools::Itertools;
use rustc_middle::middle::exported_symbols::SymbolExportKind;
use rustc_session::Session;
use rustc_target::spec::Target;
pub(super) use rustc_target::spec::apple::OSVersion;
use tracing::debug;

use crate::errors::{AppleDeploymentTarget, XcrunError, XcrunSdkPathWarning};
use crate::errors::{XcrunError, XcrunSdkPathWarning};
use crate::fluent_generated as fluent;

#[cfg(test)]
Expand Down Expand Up @@ -134,124 +132,6 @@ pub(super) fn add_data_and_relocation(
Ok(())
}

/// 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<OSVersion, ParseIntError> {
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:
/// <https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/llvm/lib/TargetParser/Triple.cpp#L1900-L1932>
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.
pub 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,
Expand All @@ -263,18 +143,17 @@ pub(super) fn add_version_to_llvm_target(
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"
);

let version = deployment_target.fmt_full();
if let Some(env) = environment {
// Insert version into OS, before environment
format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}-{env}")
format!("{arch}-{vendor}-{os}{version}-{env}")
} else {
format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}")
format!("{arch}-{vendor}-{os}{version}")
}
}

Expand Down
12 changes: 2 additions & 10 deletions compiler/rustc_codegen_ssa/src/back/apple/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,15 @@ use super::*;
#[test]
fn test_add_version_to_llvm_target() {
assert_eq!(
add_version_to_llvm_target("aarch64-apple-macosx", (10, 14, 1)),
add_version_to_llvm_target("aarch64-apple-macosx", OSVersion::new(10, 14, 1)),
"aarch64-apple-macosx10.14.1"
);
assert_eq!(
add_version_to_llvm_target("aarch64-apple-ios-simulator", (16, 1, 0)),
add_version_to_llvm_target("aarch64-apple-ios-simulator", OSVersion::new(16, 1, 0)),
"aarch64-apple-ios16.1.0-simulator"
);
}

#[test]
fn test_parse_version() {
assert_eq!(parse_version("10"), Ok((10, 0, 0)));
assert_eq!(parse_version("10.12"), Ok((10, 12, 0)));
assert_eq!(parse_version("10.12.6"), Ok((10, 12, 6)));
assert_eq!(parse_version("9999.99.99"), Ok((9999, 99, 99)));
}

#[test]
#[cfg_attr(not(target_os = "macos"), ignore = "xcode-select is only available on macOS")]
fn lookup_developer_dir() {
Expand Down
7 changes: 3 additions & 4 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3115,8 +3115,7 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo
_ => bug!("invalid OS/ABI combination for Apple target: {target_os}, {target_abi}"),
};

let (major, minor, patch) = apple::deployment_target(sess);
let min_version = format!("{major}.{minor}.{patch}");
let min_version = sess.apple_deployment_target().fmt_full().to_string();

// The SDK version is used at runtime when compiling with a newer SDK / version of Xcode:
// - By dyld to give extra warnings and errors, see e.g.:
Expand Down Expand Up @@ -3185,10 +3184,10 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo

// The presence of `-mmacosx-version-min` makes CC default to
// macOS, and it sets the deployment target.
let (major, minor, patch) = apple::deployment_target(sess);
let version = sess.apple_deployment_target().fmt_full();
// Intentionally pass this as a single argument, Clang doesn't
// seem to like it otherwise.
cmd.cc_arg(&format!("-mmacosx-version-min={major}.{minor}.{patch}"));
cmd.cc_arg(&format!("-mmacosx-version-min={version}"));

// macOS has no environment, so with these two, we've told CC the
// four desired parameters.
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_codegen_ssa/src/back/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,13 +388,13 @@ pub(super) fn elf_e_flags(architecture: Architecture, sess: &Session) -> u32 {
fn macho_object_build_version_for_target(sess: &Session) -> object::write::MachOBuildVersion {
/// The `object` crate demands "X.Y.Z encoded in nibbles as xxxx.yy.zz"
/// e.g. minOS 14.0 = 0x000E0000, or SDK 16.2 = 0x00100200
fn pack_version((major, minor, patch): (u16, u8, u8)) -> u32 {
fn pack_version(apple::OSVersion { major, minor, patch }: apple::OSVersion) -> u32 {
let (major, minor, patch) = (major as u32, minor as u32, patch as u32);
(major << 16) | (minor << 8) | patch
}

let platform = apple::macho_platform(&sess.target);
let min_os = apple::deployment_target(sess);
let min_os = sess.apple_deployment_target();

let mut build_version = object::write::MachOBuildVersion::default();
build_version.platform = platform;
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_ssa/src/back/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub mod write;
/// Certain optimizations also depend on the deployment target.
pub fn versioned_llvm_target(sess: &Session) -> Cow<'_, str> {
if sess.target.is_like_darwin {
apple::add_version_to_llvm_target(&sess.target.llvm_target, apple::deployment_target(sess))
apple::add_version_to_llvm_target(&sess.target.llvm_target, sess.apple_deployment_target())
.into()
} else {
// FIXME(madsmtm): Certain other targets also include a version,
Expand Down
9 changes: 0 additions & 9 deletions compiler/rustc_codegen_ssa/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use std::borrow::Cow;
use std::ffi::OsString;
use std::io::Error;
use std::num::ParseIntError;
use std::path::{Path, PathBuf};
use std::process::ExitStatus;

Expand Down Expand Up @@ -738,14 +737,6 @@ pub enum ExtractBundledLibsError<'a> {
ExtractSection { rlib: &'a Path, error: Box<dyn std::error::Error> },
}

#[derive(Diagnostic)]
pub(crate) enum AppleDeploymentTarget {
#[diag(codegen_ssa_apple_deployment_target_invalid)]
Invalid { env_var: &'static str, error: ParseIntError },
#[diag(codegen_ssa_apple_deployment_target_too_low)]
TooLow { env_var: &'static str, version: String, os_min: String },
}

#[derive(Diagnostic)]
#[diag(codegen_ssa_read_file)]
pub(crate) struct ReadFileError {
Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_codegen_ssa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
#![doc(rust_logo)]
#![feature(assert_matches)]
#![feature(box_patterns)]
#![feature(debug_closure_helpers)]
#![feature(file_buffered)]
#![feature(if_let_guard)]
#![feature(let_chains)]
Expand Down
5 changes: 2 additions & 3 deletions compiler/rustc_driver_impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ use std::time::{Instant, SystemTime};
use std::{env, str};

use rustc_ast as ast;
use rustc_codegen_ssa::back::apple;
use rustc_codegen_ssa::traits::CodegenBackend;
use rustc_codegen_ssa::{CodegenErrors, CodegenResults};
use rustc_data_structures::profiling::{
Expand Down Expand Up @@ -810,8 +809,8 @@ fn print_crate_info(
if sess.target.is_like_darwin {
println_info!(
"{}={}",
apple::deployment_target_env_var(&sess.target.os),
apple::pretty_version(apple::deployment_target(sess)),
rustc_target::spec::apple::deployment_target_env_var(&sess.target.os),
sess.apple_deployment_target().fmt_pretty(),
)
} else {
#[allow(rustc::diagnostic_outside_of_impl)]
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_session/messages.ftl
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
session_apple_deployment_target_invalid =
failed to parse deployment target specified in {$env_var}: {$error}

session_apple_deployment_target_too_low =
deployment target in {$env_var} was set to {$version}, but the minimum supported by `rustc` is {$os_min}

session_binary_float_literal_not_supported = binary float literal is not supported
session_branch_protection_requires_aarch64 = `-Zbranch-protection` is only supported on aarch64

Expand Down
10 changes: 9 additions & 1 deletion compiler/rustc_session/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::num::NonZero;
use std::num::{NonZero, ParseIntError};

use rustc_ast::token;
use rustc_ast::util::literal::LitError;
Expand All @@ -14,6 +14,14 @@ use rustc_target::spec::{SplitDebuginfo, StackProtector, TargetTuple};
use crate::config::CrateType;
use crate::parse::ParseSess;

#[derive(Diagnostic)]
pub(crate) enum AppleDeploymentTarget {
#[diag(session_apple_deployment_target_invalid)]
Invalid { env_var: &'static str, error: ParseIntError },
#[diag(session_apple_deployment_target_too_low)]
TooLow { env_var: &'static str, version: String, os_min: String },
}

pub(crate) struct FeatureGateError {
pub(crate) span: MultiSpan,
pub(crate) explain: DiagMessage,
Expand Down
41 changes: 40 additions & 1 deletion compiler/rustc_session/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use rustc_target::asm::InlineAsmArch;
use rustc_target::spec::{
CodeModel, DebuginfoKind, PanicStrategy, RelocModel, RelroLevel, SanitizerSet,
SmallDataThresholdSupport, SplitDebuginfo, StackProtector, SymbolVisibility, Target,
TargetTuple, TlsModel,
TargetTuple, TlsModel, apple,
};

use crate::code_stats::CodeStats;
Expand Down Expand Up @@ -891,6 +891,45 @@ impl Session {
FileNameDisplayPreference::Local
}
}

/// Get the deployment target on Apple platforms based on the standard environment variables,
/// or fall back to the minimum version supported by `rustc`.
///
/// This should be guarded behind `if sess.target.is_like_darwin`.
pub fn apple_deployment_target(&self) -> apple::OSVersion {
let min = apple::OSVersion::minimum_deployment_target(&self.target);
let env_var = apple::deployment_target_env_var(&self.target.os);

// FIXME(madsmtm): Track changes to this.
if let Ok(deployment_target) = env::var(env_var) {
match apple::OSVersion::from_str(&deployment_target) {
Ok(version) => {
let os_min = apple::OSVersion::os_minimum_deployment_target(&self.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 {
self.dcx().emit_warn(errors::AppleDeploymentTarget::TooLow {
env_var,
version: version.fmt_pretty().to_string(),
os_min: os_min.fmt_pretty().to_string(),
});
}

// Raise the deployment target to the minimum supported.
version.max(min)
}
Err(error) => {
self.dcx().emit_err(errors::AppleDeploymentTarget::Invalid { env_var, error });
min
}
}
} else {
// If no deployment target variable is set, default to the minimum found above.
min
}
}
}

// JUSTIFICATION: part of session construction
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_target/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
#![doc(rust_logo)]
#![feature(assert_matches)]
#![feature(debug_closure_helpers)]
#![feature(iter_intersperse)]
#![feature(let_chains)]
#![feature(rustc_attrs)]
Expand Down
Loading
Loading