diff --git a/Cargo.lock b/Cargo.lock index 2ee2c52b32ade..4680eb137484e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3460,6 +3460,7 @@ dependencies = [ "rustc_serialize", "rustc_session", "rustc_span", + "rustc_target", ] [[package]] diff --git a/compiler/rustc_attr_parsing/Cargo.toml b/compiler/rustc_attr_parsing/Cargo.toml index 7ccedf40c3fa9..f3a70a7f38b3c 100644 --- a/compiler/rustc_attr_parsing/Cargo.toml +++ b/compiler/rustc_attr_parsing/Cargo.toml @@ -18,4 +18,5 @@ rustc_macros = { path = "../rustc_macros" } rustc_serialize = { path = "../rustc_serialize" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } +rustc_target = { path = "../rustc_target" } # tidy-alphabetical-end diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl index faa2865cb9130..9ed0fe21dfd9b 100644 --- a/compiler/rustc_attr_parsing/messages.ftl +++ b/compiler/rustc_attr_parsing/messages.ftl @@ -1,3 +1,9 @@ +attr_parsing_apple_version_invalid = + failed parsing version: {$error} + +attr_parsing_apple_version_unnecessarily_low = + version is set unnecessarily low, the minimum supported by Rust on this platform is {$os_min} + attr_parsing_cfg_predicate_identifier = `cfg` predicate key must be an identifier @@ -9,6 +15,12 @@ attr_parsing_deprecated_item_suggestion = attr_parsing_expected_one_cfg_pattern = expected 1 cfg-pattern +attr_parsing_expected_platform_and_version_literals = + expected two literals, a platform and a version + +attr_parsing_expected_platform_literal = + expected a platform literal + attr_parsing_expected_single_version_literal = expected single version literal @@ -104,6 +116,9 @@ attr_parsing_unknown_meta_item = unknown meta item '{$item}' .label = expected one of {$expected} +attr_parsing_unknown_platform_literal = + unknown platform literal, expected values are: {$possibilities} + attr_parsing_unknown_version_literal = unknown version literal format, assuming it refers to a future version diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index bb9aaaa2fea9e..b50e9957763ec 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -10,6 +10,7 @@ use rustc_session::lint::BuiltinLintDiag; use rustc_session::lint::builtin::UNEXPECTED_CFGS; use rustc_session::parse::feature_err; use rustc_span::{Span, Symbol, kw, sym}; +use rustc_target::spec::apple; use crate::util::UnsupportedLiteralReason; use crate::{fluent_generated, parse_version, session_diagnostics}; @@ -150,6 +151,122 @@ pub fn eval_condition( RustcVersion::CURRENT >= min_version } } + ast::MetaItemKind::List(mis) if cfg.name_or_empty() == sym::os_version_min => { + try_gate_cfg(sym::os_version_min, cfg.span, sess, features); + + let (platform, version) = match &mis[..] { + [platform, version] => (platform, version), + [..] => { + dcx.emit_err(session_diagnostics::ExpectedPlatformAndVersionLiterals { + span: cfg.span, + }); + return false; + } + }; + + let (platform_sym, platform_span) = match platform { + MetaItemInner::Lit(MetaItemLit { + kind: LitKind::Str(platform_sym, ..), + span: platform_span, + .. + }) => (platform_sym, platform_span), + MetaItemInner::Lit(MetaItemLit { span, .. }) + | MetaItemInner::MetaItem(MetaItem { span, .. }) => { + dcx.emit_err(session_diagnostics::ExpectedPlatformLiteral { span: *span }); + return false; + } + }; + + let (version_sym, version_span) = match version { + MetaItemInner::Lit(MetaItemLit { + kind: LitKind::Str(version_sym, ..), + span: version_span, + .. + }) => (version_sym, version_span), + MetaItemInner::Lit(MetaItemLit { span, .. }) + | MetaItemInner::MetaItem(MetaItem { span, .. }) => { + dcx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: *span }); + return false; + } + }; + + // Always parse version, regardless of current target platform. + let version = match *platform_sym { + // Apple platforms follow the same versioning schema. + sym::macos | sym::ios | sym::tvos | sym::watchos | sym::visionos => { + match version_sym.as_str().parse() { + Ok(version) => { + let os_min = apple::OSVersion::os_minimum_deployment_target( + &platform_sym.as_str(), + ); + + // It's unnecessary to specify `cfg_target_os(...)` for a platform + // version that is lower than the minimum targetted by `rustc` (instead, + // make the item always available). + // + // This is correct _now_, but once we bump versions next time, we should + // maybe make this a lint so that users can opt-in to supporting older + // `rustc` versions? Or perhaps only fire the warning when Cargo's + // `rust-version` field is above the version where the bump happened? Or + // perhaps keep the version we check against low for a sufficiently long + // time? + if version <= os_min { + sess.dcx().emit_warn( + session_diagnostics::AppleVersionUnnecessarilyLow { + span: *version_span, + os_min: os_min.fmt_pretty().to_string(), + }, + ); + // TODO(madsmtm): Add suggestion for replacing with `target_os = "..."` + } + + PlatformVersion::Apple { os: *platform_sym, version } + } + Err(error) => { + sess.dcx().emit_err(session_diagnostics::AppleVersionInvalid { + span: *version_span, + error, + }); + return false; + } + } + } + // FIXME(madsmtm): Handle further platforms as specified in the RFC. + sym::windows | sym::libc => { + #[allow(rustc::untranslatable_diagnostic)] // Temporary + dcx.span_err(*platform_span, "unimplemented platform"); + return false; + } + _ => { + // Unknown platform. This is intentionally a warning (and not an error) to be + // future-compatible with later additions. + let known_platforms = [ + sym::macos, + sym::ios, + sym::tvos, + sym::watchos, + sym::visionos, + // sym::windows, + // sym::libc, + ]; + dcx.emit_warn(session_diagnostics::UnknownPlatformLiteral { + span: *platform_span, + possibilities: known_platforms.into_iter().collect(), + }); + return false; + } + }; + + // Figure out actual cfg-status based on current platform. + match version { + PlatformVersion::Apple { os, version } if os.as_str() == sess.target.os => { + let deployment_target = sess.apple_deployment_target(); + version <= deployment_target + } + // If a `cfg`-value does not apply to a specific platform, assume + _ => false, + } + } ast::MetaItemKind::List(mis) => { for mi in mis.iter() { if mi.meta_item_or_bool().is_none() { @@ -251,3 +368,8 @@ pub fn eval_condition( } } } + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +enum PlatformVersion { + Apple { os: Symbol, version: apple::OSVersion }, +} diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 92bc2a8aeb05e..12f38d7b1c957 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -1,14 +1,32 @@ -use std::num::IntErrorKind; +use std::num::{IntErrorKind, ParseIntError}; use rustc_ast as ast; use rustc_errors::codes::*; -use rustc_errors::{Applicability, Diag, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level}; +use rustc_errors::{ + Applicability, Diag, DiagCtxtHandle, DiagSymbolList, Diagnostic, EmissionGuarantee, Level, +}; use rustc_macros::{Diagnostic, Subdiagnostic}; use rustc_span::{Span, Symbol}; use crate::attributes::util::UnsupportedLiteralReason; use crate::fluent_generated as fluent; +#[derive(Diagnostic)] +#[diag(attr_parsing_apple_version_invalid)] +pub(crate) struct AppleVersionInvalid { + #[primary_span] + pub span: Span, + pub error: ParseIntError, +} + +#[derive(Diagnostic)] +#[diag(attr_parsing_apple_version_unnecessarily_low)] +pub(crate) struct AppleVersionUnnecessarilyLow { + #[primary_span] + pub span: Span, + pub os_min: String, +} + #[derive(Diagnostic)] #[diag(attr_parsing_expected_one_cfg_pattern, code = E0536)] pub(crate) struct ExpectedOneCfgPattern { @@ -371,6 +389,20 @@ pub(crate) struct DeprecatedItemSuggestion { pub details: (), } +#[derive(Diagnostic)] +#[diag(attr_parsing_expected_platform_and_version_literals)] +pub(crate) struct ExpectedPlatformAndVersionLiterals { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(attr_parsing_expected_platform_literal)] +pub(crate) struct ExpectedPlatformLiteral { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag(attr_parsing_expected_single_version_literal)] pub(crate) struct ExpectedSingleVersionLiteral { @@ -417,6 +449,14 @@ pub(crate) struct SoftNoArgs { pub span: Span, } +#[derive(Diagnostic)] +#[diag(attr_parsing_unknown_platform_literal)] +pub(crate) struct UnknownPlatformLiteral { + #[primary_span] + pub span: Span, + pub possibilities: DiagSymbolList, +} + #[derive(Diagnostic)] #[diag(attr_parsing_unknown_version_literal)] pub(crate) struct UnknownVersionLiteral { diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl index 22e262546c3a7..fc3ed74f5e9fe 100644 --- a/compiler/rustc_codegen_ssa/messages.ftl +++ b/compiler/rustc_codegen_ssa/messages.ftl @@ -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_apple_sdk_error_sdk_path = failed to get {$sdk_name} SDK path: {$error} codegen_ssa_archive_build_failure = failed to build archive at `{$path}`: {$error} diff --git a/compiler/rustc_codegen_ssa/src/back/apple.rs b/compiler/rustc_codegen_ssa/src/back/apple.rs index d9c5c3e5af96d..b41ec30451953 100644 --- a/compiler/rustc_codegen_ssa/src/back/apple.rs +++ b/compiler/rustc_codegen_ssa/src/back/apple.rs @@ -1,11 +1,5 @@ -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; +pub(super) use rustc_target::spec::apple::OSVersion; #[cfg(test)] mod tests; @@ -26,124 +20,6 @@ pub(super) fn macho_platform(target: &Target) -> u32 { } } -/// 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, @@ -155,17 +31,16 @@ 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}") } } diff --git a/compiler/rustc_codegen_ssa/src/back/apple/tests.rs b/compiler/rustc_codegen_ssa/src/back/apple/tests.rs index 7ccda5a8190c7..06695c0d59360 100644 --- a/compiler/rustc_codegen_ssa/src/back/apple/tests.rs +++ b/compiler/rustc_codegen_ssa/src/back/apple/tests.rs @@ -1,21 +1,13 @@ -use super::{add_version_to_llvm_target, parse_version}; +use super::{OSVersion, add_version_to_llvm_target}; #[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))); -} diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 105a4cb81f0d1..aeed922d88274 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -53,7 +53,7 @@ use super::command::Command; use super::linker::{self, Linker}; use super::metadata::{MetadataPosition, create_wrapper_file}; use super::rpath::{self, RPathConfig}; -use super::{apple, versioned_llvm_target}; +use super::versioned_llvm_target; use crate::{ CodegenResults, CompiledModule, CrateInfo, NativeLib, common, errors, looks_like_rust_object_file, @@ -3114,8 +3114,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.: @@ -3184,10 +3183,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. diff --git a/compiler/rustc_codegen_ssa/src/back/metadata.rs b/compiler/rustc_codegen_ssa/src/back/metadata.rs index d70413b8a4741..a032d860e8e39 100644 --- a/compiler/rustc_codegen_ssa/src/back/metadata.rs +++ b/compiler/rustc_codegen_ssa/src/back/metadata.rs @@ -426,13 +426,13 @@ pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static 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; diff --git a/compiler/rustc_codegen_ssa/src/back/mod.rs b/compiler/rustc_codegen_ssa/src/back/mod.rs index 64b5d4569ecce..76971096a5820 100644 --- a/compiler/rustc_codegen_ssa/src/back/mod.rs +++ b/compiler/rustc_codegen_ssa/src/back/mod.rs @@ -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_osx { - 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, diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index 3ddbe4aeeec5d..ff8a08e2859bc 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -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; @@ -642,14 +641,6 @@ pub(crate) struct UnsupportedArch<'a> { pub os: &'a str, } -#[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)] pub(crate) enum AppleSdkRootError<'a> { #[diag(codegen_ssa_apple_sdk_error_sdk_path)] diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 428a45975f1ec..0bde08bf9110a 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -6,7 +6,6 @@ #![doc(rust_logo)] #![feature(assert_matches)] #![feature(box_patterns)] -#![feature(debug_closure_helpers)] #![feature(file_buffered)] #![feature(if_let_guard)] #![feature(let_chains)] diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 4c47ce93dd56f..8deeb3659da36 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -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::{ @@ -775,8 +774,8 @@ fn print_crate_info( if sess.target.is_like_osx { 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)] diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index eb5fac96af270..24591515d0338 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -33,6 +33,7 @@ const GATED_CFGS: &[GatedCfg] = &[ ), (sym::sanitize, sym::cfg_sanitize, Features::cfg_sanitize), (sym::version, sym::cfg_version, Features::cfg_version), + (sym::os_version_min, sym::cfg_os_version_min, Features::cfg_os_version_min), (sym::relocation_model, sym::cfg_relocation_model, Features::cfg_relocation_model), (sym::sanitizer_cfi_generalize_pointers, sym::cfg_sanitizer_cfi, Features::cfg_sanitizer_cfi), (sym::sanitizer_cfi_normalize_integers, sym::cfg_sanitizer_cfi, Features::cfg_sanitizer_cfi), diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 3a2e810dc6af7..41af2f11d2494 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -405,6 +405,8 @@ declare_features! ( (unstable, cfg_boolean_literals, "1.83.0", Some(131204)), /// Allows the use of `#[cfg(contract_checks)` to check if contract checks are enabled. (unstable, cfg_contract_checks, "CURRENT_RUSTC_VERSION", Some(128044)), + /// Allow conditional compilation depending on target platform version. + (unstable, cfg_os_version_min, "CURRENT_RUSTC_VERSION", Some(136866)), /// Allows the use of `#[cfg(overflow_checks)` to check if integer overflow behaviour. (unstable, cfg_overflow_checks, "1.71.0", Some(111466)), /// Provides the relocation model information as cfg entry diff --git a/compiler/rustc_session/messages.ftl b/compiler/rustc_session/messages.ftl index e5fba8cc5a2d2..99cfb2a88064d 100644 --- a/compiler/rustc_session/messages.ftl +++ b/compiler/rustc_session/messages.ftl @@ -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 diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs index 75c3b2c7a8592..6399531121ba9 100644 --- a/compiler/rustc_session/src/errors.rs +++ b/compiler/rustc_session/src/errors.rs @@ -1,4 +1,4 @@ -use std::num::NonZero; +use std::num::{NonZero, ParseIntError}; use rustc_ast::token; use rustc_ast::util::literal::LitError; @@ -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, diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index f795ad1ee17d7..7d75fcf90fbc8 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -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; @@ -877,6 +877,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_osx`. + 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 diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index c819d43323583..4d17b986a8905 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -577,6 +577,7 @@ symbols! { cfg_eval, cfg_fmt_debug, cfg_hide, + cfg_os_version_min, cfg_overflow_checks, cfg_panic, cfg_relocation_model, @@ -1139,6 +1140,7 @@ symbols! { intrinsics_unaligned_volatile_store, io_stderr, io_stdout, + ios, irrefutable_let_patterns, is, is_val_statically_known, @@ -1224,6 +1226,7 @@ symbols! { loop_break_value, lt, m68k_target_feature, + macos, macro_at_most_once_rep, macro_attributes_in_derive_output, macro_escape, @@ -1449,6 +1452,7 @@ symbols! { ord_cmp_method, os_str_to_os_string, os_string_as_os_str, + os_version_min, other, out, overflow_checks, @@ -2064,6 +2068,7 @@ symbols! { tuple, tuple_indexing, tuple_trait, + tvos, two_phase, ty, type_alias_enum_variants, @@ -2206,6 +2211,7 @@ symbols! { vfp2, vis, visible_private_types, + visionos, volatile, volatile_copy_memory, volatile_copy_nonoverlapping_memory, @@ -2222,6 +2228,7 @@ symbols! { wasm_abi, wasm_import_module, wasm_target_feature, + watchos, while_let, windows, windows_subsystem, diff --git a/compiler/rustc_target/src/lib.rs b/compiler/rustc_target/src/lib.rs index bde4af643fa6c..7249f148e62ee 100644 --- a/compiler/rustc_target/src/lib.rs +++ b/compiler/rustc_target/src/lib.rs @@ -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)] diff --git a/compiler/rustc_target/src/spec/base/apple/mod.rs b/compiler/rustc_target/src/spec/base/apple/mod.rs index 497994a5998cb..cdd3a0c30dec7 100644 --- a/compiler/rustc_target/src/spec/base/apple/mod.rs +++ b/compiler/rustc_target/src/spec/base/apple/mod.rs @@ -1,9 +1,12 @@ use std::borrow::Cow; use std::env; +use std::fmt::{Display, from_fn}; +use std::num::ParseIntError; +use std::str::FromStr; use crate::spec::{ Cc, DebuginfoKind, FloatAbi, FramePointer, LinkerFlavor, Lld, SplitDebuginfo, StackProbeType, - StaticCow, TargetOptions, cvs, + StaticCow, Target, TargetOptions, cvs, }; #[cfg(test)] @@ -217,3 +220,107 @@ fn link_env_remove(os: &'static str) -> StaticCow<[StaticCow<str>]> { cvs!["MACOSX_DEPLOYMENT_TARGET"] } } + +/// Deployment target or SDK version. +/// +/// The size of the numbers in here are limited by Mach-O's `LC_BUILD_VERSION`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct OSVersion { + pub major: u16, + pub minor: u8, + pub patch: u8, +} + +impl FromStr for OSVersion { + type Err = ParseIntError; + + /// Parse an OS version triple (SDK version or deployment target). + fn from_str(version: &str) -> Result<Self, ParseIntError> { + if let Some((major, minor)) = version.split_once('.') { + let major = major.parse()?; + if let Some((minor, patch)) = minor.split_once('.') { + Ok(Self { major, minor: minor.parse()?, patch: patch.parse()? }) + } else { + Ok(Self { major, minor: minor.parse()?, patch: 0 }) + } + } else { + Ok(Self { major: version.parse()?, minor: 0, patch: 0 }) + } + } +} + +impl OSVersion { + pub fn new(major: u16, minor: u8, patch: u8) -> Self { + Self { major, minor, patch } + } + + pub fn fmt_pretty(self) -> impl Display { + let Self { major, minor, patch } = self; + from_fn(move |f| { + write!(f, "{major}.{minor}")?; + if patch != 0 { + write!(f, ".{patch}")?; + } + Ok(()) + }) + } + + pub fn fmt_full(self) -> impl Display { + let Self { major, minor, patch } = self; + from_fn(move |f| write!(f, "{major}.{minor}.{patch}")) + } + + /// Minimum operating system versions currently supported by `rustc`. + pub fn os_minimum_deployment_target(os: &str) -> Self { + // 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 + // ``` + let (major, minor, patch) = 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"), + }; + Self { major, minor, patch } + } + + /// 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> + pub fn minimum_deployment_target(target: &Target) -> Self { + let (major, minor, patch) = 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, _, _) => return Self::os_minimum_deployment_target(os), + }; + Self { major, minor, patch } + } +} + +/// 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"), + } +} diff --git a/compiler/rustc_target/src/spec/base/apple/tests.rs b/compiler/rustc_target/src/spec/base/apple/tests.rs index 7a985ad4dc056..391f347010436 100644 --- a/compiler/rustc_target/src/spec/base/apple/tests.rs +++ b/compiler/rustc_target/src/spec/base/apple/tests.rs @@ -1,3 +1,4 @@ +use super::OSVersion; use crate::spec::targets::{ aarch64_apple_darwin, aarch64_apple_ios_sim, aarch64_apple_visionos_sim, aarch64_apple_watchos_sim, i686_apple_darwin, x86_64_apple_darwin, x86_64_apple_ios, @@ -42,3 +43,11 @@ fn macos_link_environment_unmodified() { ); } } + +#[test] +fn test_parse_version() { + assert_eq!("10".parse(), Ok(OSVersion::new(10, 0, 0))); + assert_eq!("10.12".parse(), Ok(OSVersion::new(10, 12, 0))); + assert_eq!("10.12.6".parse(), Ok(OSVersion::new(10, 12, 6))); + assert_eq!("9999.99.99".parse(), Ok(OSVersion::new(9999, 99, 99))); +} diff --git a/compiler/rustc_target/src/spec/base/mod.rs b/compiler/rustc_target/src/spec/base/mod.rs index 28d10dcf2ff3a..e8226d758c2a4 100644 --- a/compiler/rustc_target/src/spec/base/mod.rs +++ b/compiler/rustc_target/src/spec/base/mod.rs @@ -1,6 +1,6 @@ pub(crate) mod aix; pub(crate) mod android; -pub(crate) mod apple; +pub mod apple; pub(crate) mod avr_gnu; pub(crate) mod bpf; pub(crate) mod dragonfly; diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index df1862ec27eee..dfb231fa42438 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -67,6 +67,7 @@ pub mod abi { mod base; mod json; +pub use base::apple; pub use base::avr_gnu::ef_avr_arch; /// Linker is called through a C/C++ compiler. diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 954a4182fbd6c..6301e129886f6 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -281,6 +281,8 @@ #![feature(cfg_sanitizer_cfi)] #![feature(cfg_target_thread_local)] #![feature(cfi_encoding)] +#![feature(cfg_boolean_literals)] +#![cfg_attr(not(bootstrap), feature(cfg_os_version_min))] #![feature(concat_idents)] #![feature(decl_macro)] #![feature(deprecated_suggestion)] diff --git a/library/std/src/sys/pal/unix/fd.rs b/library/std/src/sys/pal/unix/fd.rs index 2fc33bdfefbf5..ca6a97e296626 100644 --- a/library/std/src/sys/pal/unix/fd.rs +++ b/library/std/src/sys/pal/unix/fd.rs @@ -252,7 +252,15 @@ impl FileDesc { #[cfg(all(target_os = "android", target_pointer_width = "32"))] pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> { - super::weak::weak!(fn preadv64(libc::c_int, *const libc::iovec, libc::c_int, off64_t) -> isize); + super::weak::maybe_weak!( + #[cfg_always_available_on(false)] + fn preadv64( + fd: libc::c_int, + iov: *const libc::iovec, + iovcnt: libc::c_int, + offset: off64_t, + ) -> isize; + ); match preadv64.get() { Some(preadv) => { @@ -281,7 +289,33 @@ impl FileDesc { // use "weak" linking. #[cfg(target_vendor = "apple")] pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> { - super::weak::weak!(fn preadv(libc::c_int, *const libc::iovec, libc::c_int, off64_t) -> isize); + #[cfg(not(bootstrap))] + super::weak::maybe_weak!( + #[cfg_always_available_on(any( + os_version_min("macos", "11.0"), + os_version_min("ios", "14.0"), + os_version_min("tvos", "14.0"), + os_version_min("watchos", "7.0"), + os_version_min("visionos", "1.0"), + ))] + fn preadv( + fd: libc::c_int, + iov: *const libc::iovec, + iovcnt: libc::c_int, + offset: off64_t, + ) -> isize; + ); + + #[cfg(bootstrap)] + super::weak::maybe_weak!( + #[cfg_always_available_on(false)] + fn preadv( + fd: libc::c_int, + iov: *const libc::iovec, + iovcnt: libc::c_int, + offset: off64_t, + ) -> isize; + ); match preadv.get() { Some(preadv) => { @@ -445,7 +479,15 @@ impl FileDesc { #[cfg(all(target_os = "android", target_pointer_width = "32"))] pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> { - super::weak::weak!(fn pwritev64(libc::c_int, *const libc::iovec, libc::c_int, off64_t) -> isize); + super::weak::maybe_weak!( + #[cfg_always_available_on(false)] + fn pwritev64( + fd: libc::c_int, + iov: *const libc::iovec, + iovcnt: libc::c_int, + offset: off64_t, + ) -> isize; + ); match pwritev64.get() { Some(pwritev) => { @@ -463,18 +505,35 @@ impl FileDesc { } } - // We support old MacOS, iOS, watchOS, tvOS and visionOS. `pwritev` was added in the following - // Apple OS versions: - // ios 14.0 - // tvos 14.0 - // macos 11.0 - // watchos 7.0 - // - // These versions may be newer than the minimum supported versions of OS's we support so we must - // use "weak" linking. #[cfg(target_vendor = "apple")] pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> { - super::weak::weak!(fn pwritev(libc::c_int, *const libc::iovec, libc::c_int, off64_t) -> isize); + #[cfg(not(bootstrap))] + super::weak::maybe_weak!( + #[cfg_always_available_on(any( + os_version_min("macos", "11.0"), + os_version_min("ios", "14.0"), + os_version_min("tvos", "14.0"), + os_version_min("watchos", "7.0"), + os_version_min("visionos", "1.0"), + ))] + fn pwritev( + fd: libc::c_int, + iov: *const libc::iovec, + iovcnt: libc::c_int, + offset: off64_t, + ) -> isize; + ); + + #[cfg(bootstrap)] + super::weak::maybe_weak!( + #[cfg_always_available_on(false)] + fn pwritev( + fd: libc::c_int, + iov: *const libc::iovec, + iovcnt: libc::c_int, + offset: off64_t, + ) -> isize; + ); match pwritev.get() { Some(pwritev) => { diff --git a/library/std/src/sys/pal/unix/fs.rs b/library/std/src/sys/pal/unix/fs.rs index 00cfa7a7fcfda..14191c0bff623 100644 --- a/library/std/src/sys/pal/unix/fs.rs +++ b/library/std/src/sys/pal/unix/fs.rs @@ -80,10 +80,10 @@ use crate::sync::Arc; use crate::sys::common::small_c_string::run_path_with_cstr; use crate::sys::fd::FileDesc; use crate::sys::time::SystemTime; +#[cfg(target_os = "android")] +use crate::sys::weak::maybe_weak; #[cfg(all(target_os = "linux", target_env = "gnu"))] use crate::sys::weak::syscall; -#[cfg(target_os = "android")] -use crate::sys::weak::weak; use crate::sys::{cvt, cvt_r}; pub use crate::sys_common::fs::exists; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; @@ -1508,9 +1508,13 @@ impl File { Ok(()) } else if #[cfg(target_os = "android")] { let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; - // futimens requires Android API level 19 cvt(unsafe { - weak!(fn futimens(c_int, *const libc::timespec) -> c_int); + maybe_weak!( + // FIXME: Once `os_version_min` supports Android, mark this as available on + // Android API level 19. + #[cfg_always_available_on(false)] + fn futimens(c_int, *const libc::timespec) -> c_int; + ); match futimens.get() { Some(futimens) => futimens(self.as_raw_fd(), times.as_ptr()), None => return Err(io::const_error!( @@ -1523,10 +1527,14 @@ impl File { } else { #[cfg(all(target_os = "linux", target_env = "gnu", target_pointer_width = "32", not(target_arch = "riscv32")))] { - use crate::sys::{time::__timespec64, weak::weak}; - - // Added in glibc 2.34 - weak!(fn __futimens64(libc::c_int, *const __timespec64) -> libc::c_int); + use crate::sys::{time::__timespec64, weak::maybe_weak}; + + maybe_weak!( + // FIXME: Once `os_version_min` supports glibc, mark this as availabe on + // glibc 2.34. + #[cfg_always_available_on(false)] + fn __futimens64(libc::c_int, *const __timespec64) -> libc::c_int; + ); if let Some(futimens64) = __futimens64.get() { let to_timespec = |time: Option<SystemTime>| time.map(|time| time.t.to_timespec64()) diff --git a/library/std/src/sys/pal/unix/process/process_unix.rs b/library/std/src/sys/pal/unix/process/process_unix.rs index 2bff192a5bd83..18c04a9150804 100644 --- a/library/std/src/sys/pal/unix/process/process_unix.rs +++ b/library/std/src/sys/pal/unix/process/process_unix.rs @@ -454,20 +454,24 @@ impl Command { cfg_if::cfg_if! { if #[cfg(target_os = "linux")] { - use crate::sys::weak::weak; + use crate::sys::weak::maybe_weak; - weak! { + maybe_weak!( + #[cfg_always_available_on(false)] fn pidfd_spawnp( - *mut libc::c_int, - *const libc::c_char, - *const libc::posix_spawn_file_actions_t, - *const libc::posix_spawnattr_t, - *const *mut libc::c_char, - *const *mut libc::c_char - ) -> libc::c_int - } + pidfd: *mut libc::c_int, + path: *const libc::c_char, + file_actions: *const libc::posix_spawn_file_actions_t, + attrp: *const libc::posix_spawnattr_t, + argv: *const *mut libc::c_char, + envp: *const *mut libc::c_char, + ) -> libc::c_int; + ); - weak! { fn pidfd_getpid(libc::c_int) -> libc::c_int } + maybe_weak!( + #[cfg_always_available_on(false)] + fn pidfd_getpid(fd: libc::c_int) -> libc::c_int; + ); static PIDFD_SUPPORTED: AtomicU8 = AtomicU8::new(0); const UNKNOWN: u8 = 0; @@ -575,38 +579,30 @@ impl Command { ) -> libc::c_int; /// Get the function pointer for adding a chdir action to a - /// `posix_spawn_file_actions_t`, if available, assuming a dynamic libc. + /// `posix_spawn_file_actions_t`, if available. /// /// Some platforms can set a new working directory for a spawned process in the /// `posix_spawn` path. This function looks up the function pointer for adding /// such an action to a `posix_spawn_file_actions_t` struct. - #[cfg(not(all(target_os = "linux", target_env = "musl")))] fn get_posix_spawn_addchdir() -> Option<PosixSpawnAddChdirFn> { - use crate::sys::weak::weak; - - weak! { + use crate::sys::weak::maybe_weak; + + maybe_weak!( + // Weak symbol lookup doesn't work with statically linked libcs, so in cases + // where static linking is possible we need to either check for the presence + // of the symbol at compile time or know about it upfront. + // + // Our minimum required musl supports this function, so we can just use it. + #[cfg_always_available_on(all(target_os = "linux", target_env = "musl"))] fn posix_spawn_file_actions_addchdir_np( - *mut libc::posix_spawn_file_actions_t, - *const libc::c_char - ) -> libc::c_int - } + file_actions: *mut libc::posix_spawn_file_actions_t, + path: *const libc::c_char, + ) -> libc::c_int; + ); posix_spawn_file_actions_addchdir_np.get() } - /// Get the function pointer for adding a chdir action to a - /// `posix_spawn_file_actions_t`, if available, on platforms where the function - /// is known to exist. - /// - /// Weak symbol lookup doesn't work with statically linked libcs, so in cases - /// where static linking is possible we need to either check for the presence - /// of the symbol at compile time or know about it upfront. - #[cfg(all(target_os = "linux", target_env = "musl"))] - fn get_posix_spawn_addchdir() -> Option<PosixSpawnAddChdirFn> { - // Our minimum required musl supports this function, so we can just use it. - Some(libc::posix_spawn_file_actions_addchdir_np) - } - let addchdir = match self.get_cwd() { Some(cwd) => { if cfg!(target_vendor = "apple") { diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs index 479021af040a7..129954de5063b 100644 --- a/library/std/src/sys/pal/unix/thread.rs +++ b/library/std/src/sys/pal/unix/thread.rs @@ -4,7 +4,7 @@ use crate::num::NonZero; #[cfg(all(target_os = "linux", target_env = "gnu"))] use crate::sys::weak::dlsym; #[cfg(any(target_os = "solaris", target_os = "illumos", target_os = "nto",))] -use crate::sys::weak::weak; +use crate::sys::weak::maybe_weak; use crate::sys::{os, stack_overflow}; use crate::time::Duration; use crate::{cmp, io, ptr}; @@ -186,11 +186,13 @@ impl Thread { #[cfg(any(target_os = "solaris", target_os = "illumos", target_os = "nto"))] pub fn set_name(name: &CStr) { - weak! { + maybe_weak!( + #[cfg_always_available_on(false)] fn pthread_setname_np( - libc::pthread_t, *const libc::c_char - ) -> libc::c_int - } + thread: libc::pthread_t, + name: *const libc::c_char, + ) -> libc::c_int; + ); if let Some(f) = pthread_setname_np.get() { #[cfg(target_os = "nto")] diff --git a/library/std/src/sys/pal/unix/time.rs b/library/std/src/sys/pal/unix/time.rs index e224980e95f31..f27542d640003 100644 --- a/library/std/src/sys/pal/unix/time.rs +++ b/library/std/src/sys/pal/unix/time.rs @@ -108,11 +108,18 @@ impl Timespec { not(target_arch = "riscv32") ))] { - use crate::sys::weak::weak; + use crate::sys::weak::maybe_weak; // __clock_gettime64 was added to 32-bit arches in glibc 2.34, // and it handles both vDSO calls and ENOSYS fallbacks itself. - weak!(fn __clock_gettime64(libc::clockid_t, *mut __timespec64) -> libc::c_int); + maybe_weak!( + // FIXME: Mark with `os_version_min` once that supports glibc. + #[cfg_always_available_on(false)] + fn __clock_gettime64( + clock_id: libc::clockid_t, + tp: *mut __timespec64, + ) -> libc::c_int; + ); if let Some(clock_gettime64) = __clock_gettime64.get() { let mut t = MaybeUninit::uninit(); diff --git a/library/std/src/sys/pal/unix/weak.rs b/library/std/src/sys/pal/unix/weak.rs index 5a37598f43827..fd9a62bd2d2d6 100644 --- a/library/std/src/sys/pal/unix/weak.rs +++ b/library/std/src/sys/pal/unix/weak.rs @@ -19,7 +19,7 @@ // There are a variety of `#[cfg]`s controlling which targets are involved in // each instance of `weak!` and `syscall!`. Rather than trying to unify all of // that, we'll just allow that some unix targets don't use this module at all. -#![allow(dead_code, unused_macros)] +#![allow(dead_code, unused_macros, unused_imports)] use crate::ffi::CStr; use crate::marker::PhantomData; @@ -28,11 +28,12 @@ use crate::{mem, ptr}; // We can use true weak linkage on ELF targets. #[cfg(all(unix, not(target_vendor = "apple")))] -pub(crate) macro weak { - (fn $name:ident($($t:ty),*) -> $ret:ty) => ( +pub(crate) macro weak_impl { + (fn $name:ident($($t:ty),*) -> $ret:ty $(, $sym:expr)?) => ( let ref $name: ExternWeak<unsafe extern "C" fn($($t),*) -> $ret> = { unsafe extern "C" { #[linkage = "extern_weak"] + $(#[link_name = $sym])? static $name: Option<unsafe extern "C" fn($($t),*) -> $ret>; } #[allow(unused_unsafe)] @@ -41,9 +42,44 @@ pub(crate) macro weak { ) } -// On non-ELF targets, use the dlsym approximation of weak linkage. +// On Apple targets, we use `dlsym` instead of real weak linkage, since that requires an Xcode SDK +// with the item available in `*.tbd` files for the linker, and we support compiling and linking +// the standard library with older Xcode versions. #[cfg(target_vendor = "apple")] -pub(crate) use self::dlsym as weak; +pub(crate) use dlsym as weak_impl; + +/// Try to use the symbol directly if always available, and fall back to weak linking if not. +pub(crate) macro maybe_weak { + { + #[cfg_always_available_on($($cfg:tt)*)] + $(#[link_name = $sym:expr])? + fn $name:ident($($param:ident : $t:ty),* $(,)?) -> $ret:ty; + } => { + // If the symbol is known to be available at compile-time, use it directly. + #[cfg($($cfg)*)] + let $name = { + extern "C" { + $(#[link_name = $sym])? + fn $name($($param : $t),*) -> $ret; + } + Known($name) + }; + + // Otherwise it needs to be weakly linked. + #[cfg(not($($cfg)*))] + weak_impl!(fn $name($($t),*) -> $ret $(, $sym)?); + } +} + +/// The function is statically known here. +pub(crate) struct Known<F>(F); + +impl<F: Copy> Known<F> { + #[inline] + pub(crate) fn get(&self) -> Option<F> { + Some(self.0) + } +} pub(crate) struct ExternWeak<F: Copy> { weak_ptr: Option<F>, @@ -145,7 +181,7 @@ unsafe fn fetch(name: &str) -> *mut libc::c_void { pub(crate) macro syscall { (fn $name:ident($($arg_name:ident: $t:ty),*) -> $ret:ty) => ( unsafe fn $name($($arg_name: $t),*) -> $ret { - weak! { fn $name($($t),*) -> $ret } + weak_impl! { fn $name($($t),*) -> $ret } if let Some(fun) = $name.get() { fun($($arg_name),*) @@ -161,7 +197,7 @@ pub(crate) macro syscall { pub(crate) macro syscall { (fn $name:ident($($arg_name:ident: $t:ty),*) -> $ret:ty) => ( unsafe fn $name($($arg_name:$t),*) -> $ret { - weak! { fn $name($($t),*) -> $ret } + weak_impl! { fn $name($($t),*) -> $ret } // Use a weak symbol from libc when possible, allowing `LD_PRELOAD` // interposition, but if it's not found just use a raw syscall. diff --git a/src/doc/unstable-book/src/language-features/cfg-os-version-min.md b/src/doc/unstable-book/src/language-features/cfg-os-version-min.md new file mode 100644 index 0000000000000..e2119814271e9 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/cfg-os-version-min.md @@ -0,0 +1,76 @@ +# `cfg_os_version_min` + +The tracking issue for this feature is: [#136866] + +[#136866]: https://github.com/rust-lang/rust/issues/136866 + +------------------------ + +The `cfg_os_version_min` feature makes it possible to conditionally compile +code depending on the target platform version(s). + +As `cfg(os_version_min("platform", "version"))` is platform-specific, it +internally contains relevant `cfg(target_os = "platform")`. So specifying +`cfg(os_version_min("macos", "1.0"))` is equivalent to +`cfg(target_os = "macos")`. + +Note that this only concerns the compile-time configured version; at runtime, +the version may be higher. + + +## Changing the version + +Each Rust target has a default set of targetted platform versions. Examples +include the operating system version (`windows`, `macos`, Linux `kernel`) and +system library version (`libc`). + +The mechanism for changing a targetted platform version is currently +target-specific (a more general mechanism may be created in the future): +- On macOS, you can select the minimum OS version using the + `MACOSX_DEPLOYMENT_TARGET` environment variable. Similarly for iOS, tvOS, + watchOS and visionOS, see the relevant [platform docs] for details. +- Others: Unknown. + +[platform docs]: https://doc.rust-lang.org/nightly/rustc/platform-support.html + + +## Examples + +Statically link the `preadv` symbol if available, or fall back to weak linking if not. + +```rust +#![feature(cfg_os_version_min)] +use libc; + +// Always available under these conditions. +#[cfg(any( + os_version_min("macos", "11.0"), + os_version_min("ios", "14.0"), + os_version_min("tvos", "14.0"), + os_version_min("watchos", "7.0"), + os_version_min("visionos", "1.0") +))] +let preadv = { + extern "C" { + fn preadv(libc::c_int, *const libc::iovec, libc::c_int, libc::off64_t) -> libc::ssize_t; + } + Some(preadv) +}; + +// Otherwise `preadv` needs to be weakly linked. +// We do that using a `weak!` macro, defined elsewhere. +#[cfg(not(any( + os_version_min("macos", "11.0"), + os_version_min("ios", "14.0"), + os_version_min("tvos", "14.0"), + os_version_min("watchos", "7.0"), + os_version_min("visionos", "1.0") +)))] +weak!(fn preadv(libc::c_int, *const libc::iovec, libc::c_int, libc::off64_t) -> libc::ssize_t); + +if let Some(preadv) = preadv { + preadv(...) // Use preadv, it's available +} else { + // ... fallback impl +} +``` diff --git a/tests/ui/cfg/cfg-os-version-min-deployment-target.rs b/tests/ui/cfg/cfg-os-version-min-deployment-target.rs new file mode 100644 index 0000000000000..3a80a2512a998 --- /dev/null +++ b/tests/ui/cfg/cfg-os-version-min-deployment-target.rs @@ -0,0 +1,21 @@ +//! Test the semantics of `cfg(os_version_min)` while setting deployment target. + +//@ only-macos +//@ revisions: no_env env_low env_mid env_high +//@[no_env] unset-rustc-env:MACOSX_DEPLOYMENT_TARGET +//@[env_low] rustc-env:MACOSX_DEPLOYMENT_TARGET=10.14 +//@[env_mid] rustc-env:MACOSX_DEPLOYMENT_TARGET=14.0 +//@[env_high] rustc-env:MACOSX_DEPLOYMENT_TARGET=17.0 +//@ run-pass + +#![feature(cfg_os_version_min)] + +fn main() { + assert_eq!(cfg!(os_version_min("macos", "14.0")), cfg!(any(env_mid, env_high))); + + // Aarch64 minimum is macOS 11.0, even if a lower env is requested. + assert_eq!( + cfg!(os_version_min("macos", "11.0")), + cfg!(any(env_mid, env_high, target_arch = "aarch64")) + ); +} diff --git a/tests/ui/cfg/cfg-os-version-min-edges.rs b/tests/ui/cfg/cfg-os-version-min-edges.rs new file mode 100644 index 0000000000000..35dfa85d22871 --- /dev/null +++ b/tests/ui/cfg/cfg-os-version-min-edges.rs @@ -0,0 +1,12 @@ +//! Test low and high version with `cfg(os_version_min)`. +//@ run-pass +#![feature(cfg_os_version_min)] + +fn main() { + // Always available on macOS + assert_eq!(cfg!(os_version_min("macos", "10.0")), cfg!(target_os = "macos")); + //~^ WARNING: version is set unnecessarily low + + // Never available + assert!(cfg!(not(os_version_min("macos", "9999.99.99")))); +} diff --git a/tests/ui/cfg/cfg-os-version-min-edges.stderr b/tests/ui/cfg/cfg-os-version-min-edges.stderr new file mode 100644 index 0000000000000..5e0b768cf25f8 --- /dev/null +++ b/tests/ui/cfg/cfg-os-version-min-edges.stderr @@ -0,0 +1,8 @@ +warning: version is set unnecessarily low, the minimum supported by Rust on this platform is 10.12 + --> $DIR/cfg-os-version-min-edges.rs:7:45 + | +LL | assert_eq!(cfg!(os_version_min("macos", "10.0")), cfg!(target_os = "macos")); + | ^^^^^^ + +warning: 1 warning emitted + diff --git a/tests/ui/cfg/cfg-os-version-min-invalid.rs b/tests/ui/cfg/cfg-os-version-min-invalid.rs new file mode 100644 index 0000000000000..c6da61e956e66 --- /dev/null +++ b/tests/ui/cfg/cfg-os-version-min-invalid.rs @@ -0,0 +1,57 @@ +//! Test invalid syntax for `cfg(os_version_min)`. +#![feature(cfg_os_version_min)] + +#[cfg(os_version_min = "macos")] //~ WARNING: unexpected `cfg` condition name: `os_version_min` +fn foo() {} + +#[cfg(os_version_min("macos"))] //~ ERROR: expected two literals, a platform and a version +fn foo() {} +#[cfg(os_version_min("1", "2", "3"))] //~ ERROR: expected two literals, a platform and a version +fn foo() {} + +#[cfg(os_version_min(macos, "1.0.0"))] //~ ERROR: expected a platform literal +fn foo() {} +#[cfg(os_version_min(42, "1.0.0"))] //~ ERROR: expected a platform literal +fn foo() {} + +#[cfg(os_version_min("macos", 42))] //~ ERROR: expected a version literal +fn foo() {} +#[cfg(os_version_min("macos", 10.10))] //~ ERROR: expected a version literal +fn foo() {} +#[cfg(os_version_min("macos", false))] //~ ERROR: expected a version literal +fn foo() {} + +#[cfg(os_version_min("11.0", "macos"))] //~ WARNING: unknown platform literal +fn foo() {} +#[cfg(os_version_min("linux", "5.3"))] //~ WARNING: unknown platform literal +fn foo() {} + +#[cfg(os_version_min("windows", "10.0.10240"))] //~ ERROR: unimplemented platform +fn foo() {} + +#[cfg(os_version_min("macos", "99999"))] //~ ERROR: failed parsing version +fn foo() {} +#[cfg(os_version_min("macos", "-1"))] //~ ERROR: failed parsing version +fn foo() {} +#[cfg(os_version_min("macos", "65536"))] //~ ERROR: failed parsing version +fn foo() {} +#[cfg(os_version_min("macos", "1.2.3.4"))] //~ ERROR: failed parsing version +fn foo() {} + +#[cfg(os_version_min("macos", "10.0"))] //~ WARNING: version is set unnecessarily low +fn bar1() {} +#[cfg(os_version_min("macos", "0"))] //~ WARNING: version is set unnecessarily low +fn bar2() {} + +#[cfg(os_version_min("macos", "10.12"))] //~ WARNING: version is set unnecessarily low +fn bar3() {} +#[cfg(os_version_min("ios", "10.0"))] //~ WARNING: version is set unnecessarily low +fn bar4() {} +#[cfg(os_version_min("tvos", "10.0"))] //~ WARNING: version is set unnecessarily low +fn bar5() {} +#[cfg(os_version_min("watchos", "5.0"))] //~ WARNING: version is set unnecessarily low +fn bar6() {} +#[cfg(os_version_min("visionos", "1.0"))] //~ WARNING: version is set unnecessarily low +fn bar7() {} + +fn main() {} diff --git a/tests/ui/cfg/cfg-os-version-min-invalid.stderr b/tests/ui/cfg/cfg-os-version-min-invalid.stderr new file mode 100644 index 0000000000000..7ef143993597e --- /dev/null +++ b/tests/ui/cfg/cfg-os-version-min-invalid.stderr @@ -0,0 +1,139 @@ +error: expected two literals, a platform and a version + --> $DIR/cfg-os-version-min-invalid.rs:7:7 + | +LL | #[cfg(os_version_min("macos"))] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected two literals, a platform and a version + --> $DIR/cfg-os-version-min-invalid.rs:9:7 + | +LL | #[cfg(os_version_min("1", "2", "3"))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected a platform literal + --> $DIR/cfg-os-version-min-invalid.rs:12:22 + | +LL | #[cfg(os_version_min(macos, "1.0.0"))] + | ^^^^^ + +error: expected a platform literal + --> $DIR/cfg-os-version-min-invalid.rs:14:22 + | +LL | #[cfg(os_version_min(42, "1.0.0"))] + | ^^ + +error: expected a version literal + --> $DIR/cfg-os-version-min-invalid.rs:17:31 + | +LL | #[cfg(os_version_min("macos", 42))] + | ^^ + +error: expected a version literal + --> $DIR/cfg-os-version-min-invalid.rs:19:31 + | +LL | #[cfg(os_version_min("macos", 10.10))] + | ^^^^^ + +error: expected a version literal + --> $DIR/cfg-os-version-min-invalid.rs:21:31 + | +LL | #[cfg(os_version_min("macos", false))] + | ^^^^^ + +warning: unknown platform literal, expected values are: `macos`, `ios`, `tvos`, `watchos`, and `visionos` + --> $DIR/cfg-os-version-min-invalid.rs:24:22 + | +LL | #[cfg(os_version_min("11.0", "macos"))] + | ^^^^^^ + +warning: unknown platform literal, expected values are: `macos`, `ios`, `tvos`, `watchos`, and `visionos` + --> $DIR/cfg-os-version-min-invalid.rs:26:22 + | +LL | #[cfg(os_version_min("linux", "5.3"))] + | ^^^^^^^ + +error: unimplemented platform + --> $DIR/cfg-os-version-min-invalid.rs:29:22 + | +LL | #[cfg(os_version_min("windows", "10.0.10240"))] + | ^^^^^^^^^ + +error: failed parsing version: number too large to fit in target type + --> $DIR/cfg-os-version-min-invalid.rs:32:31 + | +LL | #[cfg(os_version_min("macos", "99999"))] + | ^^^^^^^ + +error: failed parsing version: invalid digit found in string + --> $DIR/cfg-os-version-min-invalid.rs:34:31 + | +LL | #[cfg(os_version_min("macos", "-1"))] + | ^^^^ + +error: failed parsing version: number too large to fit in target type + --> $DIR/cfg-os-version-min-invalid.rs:36:31 + | +LL | #[cfg(os_version_min("macos", "65536"))] + | ^^^^^^^ + +error: failed parsing version: invalid digit found in string + --> $DIR/cfg-os-version-min-invalid.rs:38:31 + | +LL | #[cfg(os_version_min("macos", "1.2.3.4"))] + | ^^^^^^^^^ + +warning: version is set unnecessarily low, the minimum supported by Rust on this platform is 10.12 + --> $DIR/cfg-os-version-min-invalid.rs:41:31 + | +LL | #[cfg(os_version_min("macos", "10.0"))] + | ^^^^^^ + +warning: version is set unnecessarily low, the minimum supported by Rust on this platform is 10.12 + --> $DIR/cfg-os-version-min-invalid.rs:43:31 + | +LL | #[cfg(os_version_min("macos", "0"))] + | ^^^ + +warning: version is set unnecessarily low, the minimum supported by Rust on this platform is 10.12 + --> $DIR/cfg-os-version-min-invalid.rs:46:31 + | +LL | #[cfg(os_version_min("macos", "10.12"))] + | ^^^^^^^ + +warning: version is set unnecessarily low, the minimum supported by Rust on this platform is 10.0 + --> $DIR/cfg-os-version-min-invalid.rs:48:29 + | +LL | #[cfg(os_version_min("ios", "10.0"))] + | ^^^^^^ + +warning: version is set unnecessarily low, the minimum supported by Rust on this platform is 10.0 + --> $DIR/cfg-os-version-min-invalid.rs:50:30 + | +LL | #[cfg(os_version_min("tvos", "10.0"))] + | ^^^^^^ + +warning: version is set unnecessarily low, the minimum supported by Rust on this platform is 5.0 + --> $DIR/cfg-os-version-min-invalid.rs:52:33 + | +LL | #[cfg(os_version_min("watchos", "5.0"))] + | ^^^^^ + +warning: version is set unnecessarily low, the minimum supported by Rust on this platform is 1.0 + --> $DIR/cfg-os-version-min-invalid.rs:54:34 + | +LL | #[cfg(os_version_min("visionos", "1.0"))] + | ^^^^^ + +warning: unexpected `cfg` condition name: `os_version_min` + --> $DIR/cfg-os-version-min-invalid.rs:4:7 + | +LL | #[cfg(os_version_min = "macos")] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: expected names are: `FALSE` and `test` and 31 more + = help: to expect this configuration use `--check-cfg=cfg(os_version_min, values("macos"))` + = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration + = note: `#[warn(unexpected_cfgs)]` on by default + +error: aborting due to 12 previous errors; 10 warnings emitted + diff --git a/tests/ui/feature-gates/feature-gate-cfg-os-version-min.rs b/tests/ui/feature-gates/feature-gate-cfg-os-version-min.rs new file mode 100644 index 0000000000000..f14d6cb985601 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-cfg-os-version-min.rs @@ -0,0 +1,18 @@ +#![feature(cfg_boolean_literals)] + +#[cfg(os_version_min("macos", "11.0"))] +//~^ ERROR `cfg(os_version_min)` is experimental and subject to change +fn foo1() {} + +#[cfg(os_version_min("macos", 1.20))] //~ ERROR: expected a version literal +//~^ ERROR `cfg(os_version_min)` is experimental and subject to change +fn foo2() {} + +// No warning if cfg'd away. +#[cfg_attr(false, cfg(os_version_min("macos", false)))] +fn foo3() {} + +fn main() { + if cfg!(os_version_min("macos", "11.0")) {} + //~^ ERROR `cfg(os_version_min)` is experimental and subject to change +} diff --git a/tests/ui/feature-gates/feature-gate-cfg-os-version-min.stderr b/tests/ui/feature-gates/feature-gate-cfg-os-version-min.stderr new file mode 100644 index 0000000000000..9f2b738e2b4e2 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-cfg-os-version-min.stderr @@ -0,0 +1,36 @@ +error[E0658]: `cfg(os_version_min)` is experimental and subject to change + --> $DIR/feature-gate-cfg-os-version-min.rs:3:7 + | +LL | #[cfg(os_version_min("macos", "11.0"))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(cfg_os_version_min)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: `cfg(os_version_min)` is experimental and subject to change + --> $DIR/feature-gate-cfg-os-version-min.rs:7:7 + | +LL | #[cfg(os_version_min("macos", 1.20))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(cfg_os_version_min)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: expected a version literal + --> $DIR/feature-gate-cfg-os-version-min.rs:7:31 + | +LL | #[cfg(os_version_min("macos", 1.20))] + | ^^^^ + +error[E0658]: `cfg(os_version_min)` is experimental and subject to change + --> $DIR/feature-gate-cfg-os-version-min.rs:16:13 + | +LL | if cfg!(os_version_min("macos", "11.0")) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(cfg_os_version_min)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0658`.