Add __isOSVersionAtLeast and __isPlatformVersionAtLeast symbols
Allows users to link to Objective-C code using `@available(...)`.
This commit is contained in:
parent
523d3999dc
commit
846d6a4466
12 changed files with 1127 additions and 2 deletions
|
|
@ -82,9 +82,13 @@ pub(super) fn mangle<'tcx>(
|
|||
}
|
||||
|
||||
pub fn mangle_internal_symbol<'tcx>(tcx: TyCtxt<'tcx>, item_name: &str) -> String {
|
||||
if item_name == "rust_eh_personality" {
|
||||
match item_name {
|
||||
// rust_eh_personality must not be renamed as LLVM hard-codes the name
|
||||
return "rust_eh_personality".to_owned();
|
||||
"rust_eh_personality" => return item_name.to_owned(),
|
||||
// Apple availability symbols need to not be mangled to be usable by
|
||||
// C/Objective-C code.
|
||||
"__isPlatformVersionAtLeast" | "__isOSVersionAtLeast" => return item_name.to_owned(),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let prefix = "_R";
|
||||
|
|
|
|||
|
|
@ -353,6 +353,7 @@
|
|||
#![feature(hasher_prefixfree_extras)]
|
||||
#![feature(hashmap_internals)]
|
||||
#![feature(hint_must_use)]
|
||||
#![feature(int_from_ascii)]
|
||||
#![feature(ip)]
|
||||
#![feature(lazy_get)]
|
||||
#![feature(maybe_uninit_slice)]
|
||||
|
|
@ -368,6 +369,7 @@
|
|||
#![feature(slice_internals)]
|
||||
#![feature(slice_ptr_get)]
|
||||
#![feature(slice_range)]
|
||||
#![feature(slice_split_once)]
|
||||
#![feature(std_internals)]
|
||||
#![feature(str_internals)]
|
||||
#![feature(sync_unsafe_cell)]
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ pub mod io;
|
|||
pub mod net;
|
||||
pub mod os_str;
|
||||
pub mod path;
|
||||
pub mod platform_version;
|
||||
pub mod process;
|
||||
pub mod random;
|
||||
pub mod stdio;
|
||||
|
|
|
|||
180
library/std/src/sys/platform_version/darwin/core_foundation.rs
Normal file
180
library/std/src/sys/platform_version/darwin/core_foundation.rs
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
//! Minimal utilities for interfacing with a dynamically loaded CoreFoundation.
|
||||
#![allow(non_snake_case, non_upper_case_globals)]
|
||||
use super::root_relative;
|
||||
use crate::ffi::{CStr, c_char, c_void};
|
||||
use crate::ptr::null_mut;
|
||||
use crate::sys::common::small_c_string::run_path_with_cstr;
|
||||
|
||||
// MacTypes.h
|
||||
pub(super) type Boolean = u8;
|
||||
// CoreFoundation/CFBase.h
|
||||
pub(super) type CFTypeID = usize;
|
||||
pub(super) type CFOptionFlags = usize;
|
||||
pub(super) type CFIndex = isize;
|
||||
pub(super) type CFTypeRef = *mut c_void;
|
||||
pub(super) type CFAllocatorRef = CFTypeRef;
|
||||
pub(super) const kCFAllocatorDefault: CFAllocatorRef = null_mut();
|
||||
// CoreFoundation/CFError.h
|
||||
pub(super) type CFErrorRef = CFTypeRef;
|
||||
// CoreFoundation/CFData.h
|
||||
pub(super) type CFDataRef = CFTypeRef;
|
||||
// CoreFoundation/CFPropertyList.h
|
||||
pub(super) const kCFPropertyListImmutable: CFOptionFlags = 0;
|
||||
pub(super) type CFPropertyListFormat = CFIndex;
|
||||
pub(super) type CFPropertyListRef = CFTypeRef;
|
||||
// CoreFoundation/CFString.h
|
||||
pub(super) type CFStringRef = CFTypeRef;
|
||||
pub(super) type CFStringEncoding = u32;
|
||||
pub(super) const kCFStringEncodingUTF8: CFStringEncoding = 0x08000100;
|
||||
// CoreFoundation/CFDictionary.h
|
||||
pub(super) type CFDictionaryRef = CFTypeRef;
|
||||
|
||||
/// An open handle to the dynamically loaded CoreFoundation framework.
|
||||
///
|
||||
/// This is `dlopen`ed, and later `dlclose`d. This is done to try to avoid
|
||||
/// "leaking" the CoreFoundation symbols to the rest of the user's binary if
|
||||
/// they decided to not link CoreFoundation themselves.
|
||||
///
|
||||
/// It is also faster to look up symbols directly via this handle than with
|
||||
/// `RTLD_DEFAULT`.
|
||||
pub(super) struct CFHandle(*mut c_void);
|
||||
|
||||
macro_rules! dlsym_fn {
|
||||
(
|
||||
unsafe fn $name:ident($($param:ident: $param_ty:ty),* $(,)?) $(-> $ret:ty)?;
|
||||
) => {
|
||||
pub(super) unsafe fn $name(&self, $($param: $param_ty),*) $(-> $ret)? {
|
||||
let ptr = unsafe {
|
||||
libc::dlsym(
|
||||
self.0,
|
||||
concat!(stringify!($name), '\0').as_bytes().as_ptr().cast(),
|
||||
)
|
||||
};
|
||||
if ptr.is_null() {
|
||||
let err = unsafe { CStr::from_ptr(libc::dlerror()) };
|
||||
panic!("could not find function {}: {err:?}", stringify!($name));
|
||||
}
|
||||
|
||||
// SAFETY: Just checked that the symbol isn't NULL, and macro invoker verifies that
|
||||
// the signature is correct.
|
||||
let fnptr = unsafe {
|
||||
crate::mem::transmute::<
|
||||
*mut c_void,
|
||||
unsafe extern "C" fn($($param_ty),*) $(-> $ret)?,
|
||||
>(ptr)
|
||||
};
|
||||
|
||||
// SAFETY: Upheld by caller.
|
||||
unsafe { fnptr($($param),*) }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl CFHandle {
|
||||
/// Link to the CoreFoundation dylib, and look up symbols from that.
|
||||
pub(super) fn new() -> Self {
|
||||
// We explicitly use non-versioned path here, to allow this to work on older iOS devices.
|
||||
let cf_path =
|
||||
root_relative("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation");
|
||||
|
||||
let handle = run_path_with_cstr(&cf_path, &|path| unsafe {
|
||||
Ok(libc::dlopen(path.as_ptr(), libc::RTLD_LAZY | libc::RTLD_LOCAL))
|
||||
})
|
||||
.expect("failed allocating string");
|
||||
|
||||
if handle.is_null() {
|
||||
let err = unsafe { CStr::from_ptr(libc::dlerror()) };
|
||||
panic!("could not open CoreFoundation.framework: {err:?}");
|
||||
}
|
||||
|
||||
Self(handle)
|
||||
}
|
||||
|
||||
pub(super) fn kCFAllocatorNull(&self) -> CFAllocatorRef {
|
||||
// Available: in all CF versions.
|
||||
let static_ptr = unsafe { libc::dlsym(self.0, c"kCFAllocatorNull".as_ptr()) };
|
||||
if static_ptr.is_null() {
|
||||
let err = unsafe { CStr::from_ptr(libc::dlerror()) };
|
||||
panic!("could not find kCFAllocatorNull: {err:?}");
|
||||
}
|
||||
unsafe { *static_ptr.cast() }
|
||||
}
|
||||
|
||||
// CoreFoundation/CFBase.h
|
||||
dlsym_fn!(
|
||||
// Available: in all CF versions.
|
||||
unsafe fn CFRelease(cf: CFTypeRef);
|
||||
);
|
||||
dlsym_fn!(
|
||||
// Available: in all CF versions.
|
||||
unsafe fn CFGetTypeID(cf: CFTypeRef) -> CFTypeID;
|
||||
);
|
||||
|
||||
// CoreFoundation/CFData.h
|
||||
dlsym_fn!(
|
||||
// Available: in all CF versions.
|
||||
unsafe fn CFDataCreateWithBytesNoCopy(
|
||||
allocator: CFAllocatorRef,
|
||||
bytes: *const u8,
|
||||
length: CFIndex,
|
||||
bytes_deallocator: CFAllocatorRef,
|
||||
) -> CFDataRef;
|
||||
);
|
||||
|
||||
// CoreFoundation/CFPropertyList.h
|
||||
dlsym_fn!(
|
||||
// Available: since macOS 10.6.
|
||||
unsafe fn CFPropertyListCreateWithData(
|
||||
allocator: CFAllocatorRef,
|
||||
data: CFDataRef,
|
||||
options: CFOptionFlags,
|
||||
format: *mut CFPropertyListFormat,
|
||||
error: *mut CFErrorRef,
|
||||
) -> CFPropertyListRef;
|
||||
);
|
||||
|
||||
// CoreFoundation/CFString.h
|
||||
dlsym_fn!(
|
||||
// Available: in all CF versions.
|
||||
unsafe fn CFStringGetTypeID() -> CFTypeID;
|
||||
);
|
||||
dlsym_fn!(
|
||||
// Available: in all CF versions.
|
||||
unsafe fn CFStringCreateWithCStringNoCopy(
|
||||
alloc: CFAllocatorRef,
|
||||
c_str: *const c_char,
|
||||
encoding: CFStringEncoding,
|
||||
contents_deallocator: CFAllocatorRef,
|
||||
) -> CFStringRef;
|
||||
);
|
||||
dlsym_fn!(
|
||||
// Available: in all CF versions.
|
||||
unsafe fn CFStringGetCString(
|
||||
the_string: CFStringRef,
|
||||
buffer: *mut c_char,
|
||||
buffer_size: CFIndex,
|
||||
encoding: CFStringEncoding,
|
||||
) -> Boolean;
|
||||
);
|
||||
|
||||
// CoreFoundation/CFDictionary.h
|
||||
dlsym_fn!(
|
||||
// Available: in all CF versions.
|
||||
unsafe fn CFDictionaryGetTypeID() -> CFTypeID;
|
||||
);
|
||||
dlsym_fn!(
|
||||
// Available: in all CF versions.
|
||||
unsafe fn CFDictionaryGetValue(
|
||||
the_dict: CFDictionaryRef,
|
||||
key: *const c_void,
|
||||
) -> *const c_void;
|
||||
);
|
||||
}
|
||||
|
||||
impl Drop for CFHandle {
|
||||
fn drop(&mut self) {
|
||||
// Ignore errors when closing. This is also what `libloading` does:
|
||||
// https://docs.rs/libloading/0.8.6/src/libloading/os/unix/mod.rs.html#374
|
||||
let _ = unsafe { libc::dlclose(self.0) };
|
||||
}
|
||||
}
|
||||
351
library/std/src/sys/platform_version/darwin/mod.rs
Normal file
351
library/std/src/sys/platform_version/darwin/mod.rs
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
use self::core_foundation::{
|
||||
CFDictionaryRef, CFHandle, CFIndex, CFStringRef, CFTypeRef, kCFAllocatorDefault,
|
||||
kCFPropertyListImmutable, kCFStringEncodingUTF8,
|
||||
};
|
||||
use crate::borrow::Cow;
|
||||
use crate::bstr::ByteStr;
|
||||
use crate::ffi::{CStr, c_char};
|
||||
use crate::num::{NonZero, ParseIntError};
|
||||
use crate::path::{Path, PathBuf};
|
||||
use crate::ptr::null_mut;
|
||||
use crate::sync::atomic::{AtomicU32, Ordering};
|
||||
use crate::{env, fs};
|
||||
|
||||
mod core_foundation;
|
||||
mod public_extern;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// The version of the operating system.
|
||||
///
|
||||
/// We use a packed u32 here to allow for fast comparisons and to match Mach-O's `LC_BUILD_VERSION`.
|
||||
type OSVersion = u32;
|
||||
|
||||
/// Combine parts of a version into an [`OSVersion`].
|
||||
///
|
||||
/// The size of the parts are inherently limited by Mach-O's `LC_BUILD_VERSION`.
|
||||
#[inline]
|
||||
const fn pack_os_version(major: u16, minor: u8, patch: u8) -> OSVersion {
|
||||
let (major, minor, patch) = (major as u32, minor as u32, patch as u32);
|
||||
(major << 16) | (minor << 8) | patch
|
||||
}
|
||||
|
||||
/// [`pack_os_version`], but takes `i32` and saturates.
|
||||
///
|
||||
/// Instead of using e.g. `major as u16`, which truncates.
|
||||
#[inline]
|
||||
fn pack_i32_os_version(major: i32, minor: i32, patch: i32) -> OSVersion {
|
||||
let major: u16 = major.try_into().unwrap_or(u16::MAX);
|
||||
let minor: u8 = minor.try_into().unwrap_or(u8::MAX);
|
||||
let patch: u8 = patch.try_into().unwrap_or(u8::MAX);
|
||||
pack_os_version(major, minor, patch)
|
||||
}
|
||||
|
||||
/// Get the current OS version, packed according to [`pack_os_version`].
|
||||
///
|
||||
/// # Semantics
|
||||
///
|
||||
/// The reported version on macOS might be 10.16 if the SDK version of the binary is less than 11.0.
|
||||
/// This is a workaround that Apple implemented to handle applications that assumed that macOS
|
||||
/// versions would always start with "10", see:
|
||||
/// <https://github.com/apple-oss-distributions/xnu/blob/xnu-11215.81.4/libsyscall/wrappers/system-version-compat.c>
|
||||
///
|
||||
/// It _is_ possible to get the real version regardless of the SDK version of the binary, this is
|
||||
/// what Zig does:
|
||||
/// <https://github.com/ziglang/zig/blob/0.13.0/lib/std/zig/system/darwin/macos.zig>
|
||||
///
|
||||
/// We choose to not do that, and instead follow Apple's behaviour here, and return 10.16 when
|
||||
/// compiled with an older SDK; the user should instead upgrade their tooling.
|
||||
///
|
||||
/// NOTE: `rustc` currently doesn't set the right SDK version when linking with ld64, so this will
|
||||
/// have the wrong behaviour with `-Clinker=ld` on x86_64. But that's a `rustc` bug:
|
||||
/// <https://github.com/rust-lang/rust/issues/129432>
|
||||
#[inline]
|
||||
fn current_version() -> OSVersion {
|
||||
// Cache the lookup for performance.
|
||||
//
|
||||
// 0.0.0 is never going to be a valid version ("vtool" reports "n/a" on 0 versions), so we use
|
||||
// that as our sentinel value.
|
||||
static CURRENT_VERSION: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
// We use relaxed atomics instead of e.g. a `Once`, it doesn't matter if multiple threads end up
|
||||
// racing to read or write the version, `lookup_version` should be idempotent and always return
|
||||
// the same value.
|
||||
//
|
||||
// `compiler-rt` uses `dispatch_once`, but that's overkill for the reasons above.
|
||||
let version = CURRENT_VERSION.load(Ordering::Relaxed);
|
||||
if version == 0 {
|
||||
let version = lookup_version().get();
|
||||
CURRENT_VERSION.store(version, Ordering::Relaxed);
|
||||
version
|
||||
} else {
|
||||
version
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up the os version.
|
||||
///
|
||||
/// # Aborts
|
||||
///
|
||||
/// Aborts if reading or parsing the version fails (or if the system was out of memory).
|
||||
///
|
||||
/// We deliberately choose to abort, as having this silently return an invalid OS version would be
|
||||
/// impossible for a user to debug.
|
||||
// The lookup is costly and should be on the cold path because of the cache in `current_version`.
|
||||
#[cold]
|
||||
// Micro-optimization: We use `extern "C"` to abort on panic, allowing `current_version` (inlined)
|
||||
// to be free of unwind handling. Aborting is required for `__isPlatformVersionAtLeast` anyhow.
|
||||
extern "C" fn lookup_version() -> NonZero<OSVersion> {
|
||||
// Try to read from `sysctl` first (faster), but if that fails, fall back to reading the
|
||||
// property list (this is roughly what `_availability_version_check` does internally).
|
||||
let version = version_from_sysctl().unwrap_or_else(version_from_plist);
|
||||
|
||||
// Use `NonZero` to try to make it clearer to the optimizer that this will never return 0.
|
||||
NonZero::new(version).expect("version cannot be 0.0.0")
|
||||
}
|
||||
|
||||
/// Read the version from `kern.osproductversion` or `kern.iossupportversion`.
|
||||
///
|
||||
/// This is faster than `version_from_plist`, since it doesn't need to invoke `dlsym`.
|
||||
fn version_from_sysctl() -> Option<OSVersion> {
|
||||
// This won't work in the simulator, as `kern.osproductversion` returns the host macOS version,
|
||||
// and `kern.iossupportversion` returns the host macOS' iOSSupportVersion (while you can run
|
||||
// simulators with many different iOS versions).
|
||||
if cfg!(target_abi = "sim") {
|
||||
// Fall back to `version_from_plist` on these targets.
|
||||
return None;
|
||||
}
|
||||
|
||||
let sysctl_version = |name: &CStr| {
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let mut size = buf.len();
|
||||
let ptr = buf.as_mut_ptr().cast();
|
||||
let ret = unsafe { libc::sysctlbyname(name.as_ptr(), ptr, &mut size, null_mut(), 0) };
|
||||
if ret != 0 {
|
||||
// This sysctl is not available.
|
||||
return None;
|
||||
}
|
||||
let buf = &buf[..(size - 1)];
|
||||
|
||||
if buf.is_empty() {
|
||||
// The buffer may be empty when using `kern.iossupportversion` on an actual iOS device,
|
||||
// or on visionOS when running under "Designed for iPad".
|
||||
//
|
||||
// In that case, fall back to `kern.osproductversion`.
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(parse_os_version(buf).unwrap_or_else(|err| {
|
||||
panic!("failed parsing version from sysctl ({}): {err}", ByteStr::new(buf))
|
||||
}))
|
||||
};
|
||||
|
||||
// When `target_os = "ios"`, we may be in many different states:
|
||||
// - Native iOS device.
|
||||
// - iOS Simulator.
|
||||
// - Mac Catalyst.
|
||||
// - Mac + "Designed for iPad".
|
||||
// - Native visionOS device + "Designed for iPad".
|
||||
// - visionOS simulator + "Designed for iPad".
|
||||
//
|
||||
// Of these, only native, Mac Catalyst and simulators can be differentiated at compile-time
|
||||
// (with `target_abi = ""`, `target_abi = "macabi"` and `target_abi = "sim"` respectively).
|
||||
//
|
||||
// That is, "Designed for iPad" will act as iOS at compile-time, but the `ProductVersion` will
|
||||
// still be the host macOS or visionOS version.
|
||||
//
|
||||
// Furthermore, we can't even reliably differentiate between these at runtime, since
|
||||
// `dyld_get_active_platform` isn't publicly available.
|
||||
//
|
||||
// Fortunately, we won't need to know any of that; we can simply attempt to get the
|
||||
// `iOSSupportVersion` (which may be set on native iOS too, but then it will be set to the host
|
||||
// iOS version), and if that fails, fall back to the `ProductVersion`.
|
||||
if cfg!(target_os = "ios") {
|
||||
// https://github.com/apple-oss-distributions/xnu/blob/xnu-11215.81.4/bsd/kern/kern_sysctl.c#L2077-L2100
|
||||
if let Some(ios_support_version) = sysctl_version(c"kern.iossupportversion") {
|
||||
return Some(ios_support_version);
|
||||
}
|
||||
|
||||
// On Mac Catalyst, if we failed looking up `iOSSupportVersion`, we don't want to
|
||||
// accidentally fall back to `ProductVersion`.
|
||||
if cfg!(target_abi = "macabi") {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// Introduced in macOS 10.13.4.
|
||||
// https://github.com/apple-oss-distributions/xnu/blob/xnu-11215.81.4/bsd/kern/kern_sysctl.c#L2015-L2051
|
||||
sysctl_version(c"kern.osproductversion")
|
||||
}
|
||||
|
||||
/// Look up the current OS version(s) from `/System/Library/CoreServices/SystemVersion.plist`.
|
||||
///
|
||||
/// More specifically, from the `ProductVersion` and `iOSSupportVersion` keys, and from
|
||||
/// `$IPHONE_SIMULATOR_ROOT/System/Library/CoreServices/SystemVersion.plist` on the simulator.
|
||||
///
|
||||
/// This file was introduced in macOS 10.3, which is well below the minimum supported version by
|
||||
/// `rustc`, which is (at the time of writing) macOS 10.12.
|
||||
///
|
||||
/// # Implementation
|
||||
///
|
||||
/// We do roughly the same thing in here as `compiler-rt`, and dynamically look up CoreFoundation
|
||||
/// utilities for parsing PLists (to avoid having to re-implement that in here, as pulling in a full
|
||||
/// PList parser into `std` seems costly).
|
||||
///
|
||||
/// If this is found to be undesirable, we _could_ possibly hack it by parsing the PList manually
|
||||
/// (it seems to use the plain-text "xml1" encoding/format in all versions), but that seems brittle.
|
||||
fn version_from_plist() -> OSVersion {
|
||||
// Read `SystemVersion.plist`. Always present on Apple platforms, reading it cannot fail.
|
||||
let path = root_relative("/System/Library/CoreServices/SystemVersion.plist");
|
||||
let plist_buffer = fs::read(&path).unwrap_or_else(|e| panic!("failed reading {path:?}: {e}"));
|
||||
let cf_handle = CFHandle::new();
|
||||
parse_version_from_plist(&cf_handle, &plist_buffer)
|
||||
}
|
||||
|
||||
/// Parse OS version from the given PList.
|
||||
///
|
||||
/// Split out from [`version_from_plist`] to allow for testing.
|
||||
fn parse_version_from_plist(cf_handle: &CFHandle, plist_buffer: &[u8]) -> OSVersion {
|
||||
let plist_data = unsafe {
|
||||
cf_handle.CFDataCreateWithBytesNoCopy(
|
||||
kCFAllocatorDefault,
|
||||
plist_buffer.as_ptr(),
|
||||
plist_buffer.len() as CFIndex,
|
||||
cf_handle.kCFAllocatorNull(),
|
||||
)
|
||||
};
|
||||
assert!(!plist_data.is_null(), "failed creating CFData");
|
||||
let _plist_data_release = Deferred(|| unsafe { cf_handle.CFRelease(plist_data) });
|
||||
|
||||
let plist = unsafe {
|
||||
cf_handle.CFPropertyListCreateWithData(
|
||||
kCFAllocatorDefault,
|
||||
plist_data,
|
||||
kCFPropertyListImmutable,
|
||||
null_mut(), // Don't care about the format of the PList.
|
||||
null_mut(), // Don't care about the error data.
|
||||
)
|
||||
};
|
||||
assert!(!plist.is_null(), "failed reading PList in SystemVersion.plist");
|
||||
let _plist_release = Deferred(|| unsafe { cf_handle.CFRelease(plist) });
|
||||
|
||||
assert_eq!(
|
||||
unsafe { cf_handle.CFGetTypeID(plist) },
|
||||
unsafe { cf_handle.CFDictionaryGetTypeID() },
|
||||
"SystemVersion.plist did not contain a dictionary at the top level"
|
||||
);
|
||||
let plist: CFDictionaryRef = plist.cast();
|
||||
|
||||
// Same logic as in `version_from_sysctl`.
|
||||
if cfg!(target_os = "ios") {
|
||||
if let Some(ios_support_version) =
|
||||
unsafe { string_version_key(cf_handle, plist, c"iOSSupportVersion") }
|
||||
{
|
||||
return ios_support_version;
|
||||
}
|
||||
|
||||
// Force Mac Catalyst to use iOSSupportVersion (do not fall back to ProductVersion).
|
||||
if cfg!(target_abi = "macabi") {
|
||||
panic!("expected iOSSupportVersion in SystemVersion.plist");
|
||||
}
|
||||
}
|
||||
|
||||
// On all other platforms, we can find the OS version by simply looking at `ProductVersion`.
|
||||
unsafe { string_version_key(cf_handle, plist, c"ProductVersion") }
|
||||
.expect("expected ProductVersion in SystemVersion.plist")
|
||||
}
|
||||
|
||||
/// Look up a string key in a CFDictionary, and convert it to an [`OSVersion`].
|
||||
unsafe fn string_version_key(
|
||||
cf_handle: &CFHandle,
|
||||
plist: CFDictionaryRef,
|
||||
lookup_key: &CStr,
|
||||
) -> Option<OSVersion> {
|
||||
let cf_lookup_key = unsafe {
|
||||
cf_handle.CFStringCreateWithCStringNoCopy(
|
||||
kCFAllocatorDefault,
|
||||
lookup_key.as_ptr(),
|
||||
kCFStringEncodingUTF8,
|
||||
cf_handle.kCFAllocatorNull(),
|
||||
)
|
||||
};
|
||||
assert!(!cf_lookup_key.is_null(), "failed creating CFString");
|
||||
let _lookup_key_release = Deferred(|| unsafe { cf_handle.CFRelease(cf_lookup_key) });
|
||||
|
||||
let value: CFTypeRef =
|
||||
unsafe { cf_handle.CFDictionaryGetValue(plist, cf_lookup_key) }.cast_mut();
|
||||
// `CFDictionaryGetValue` is a "getter", so we should not release,
|
||||
// the value is held alive internally by the CFDictionary, see:
|
||||
// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW12
|
||||
if value.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
unsafe { cf_handle.CFGetTypeID(value) },
|
||||
unsafe { cf_handle.CFStringGetTypeID() },
|
||||
"key in SystemVersion.plist must be a string"
|
||||
);
|
||||
let value: CFStringRef = value.cast();
|
||||
|
||||
let mut version_str = [0u8; 32];
|
||||
let ret = unsafe {
|
||||
cf_handle.CFStringGetCString(
|
||||
value,
|
||||
version_str.as_mut_ptr().cast::<c_char>(),
|
||||
version_str.len() as CFIndex,
|
||||
kCFStringEncodingUTF8,
|
||||
)
|
||||
};
|
||||
assert_ne!(ret, 0, "failed getting string from CFString");
|
||||
|
||||
let version_str =
|
||||
CStr::from_bytes_until_nul(&version_str).expect("failed converting CFString to CStr");
|
||||
|
||||
Some(parse_os_version(version_str.to_bytes()).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"failed parsing version from PList ({}): {err}",
|
||||
ByteStr::new(version_str.to_bytes())
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Parse an OS version from a bytestring like b"10.1" or b"14.3.7".
|
||||
fn parse_os_version(version: &[u8]) -> Result<OSVersion, ParseIntError> {
|
||||
if let Some((major, minor)) = version.split_once(|&b| b == b'.') {
|
||||
let major = u16::from_ascii(major)?;
|
||||
if let Some((minor, patch)) = minor.split_once(|&b| b == b'.') {
|
||||
let minor = u8::from_ascii(minor)?;
|
||||
let patch = u8::from_ascii(patch)?;
|
||||
Ok(pack_os_version(major, minor, patch))
|
||||
} else {
|
||||
let minor = u8::from_ascii(minor)?;
|
||||
Ok(pack_os_version(major, minor, 0))
|
||||
}
|
||||
} else {
|
||||
let major = u16::from_ascii(version)?;
|
||||
Ok(pack_os_version(major, 0, 0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a path relative to the root directory in which all files for the current env are located.
|
||||
fn root_relative(path: &str) -> Cow<'_, Path> {
|
||||
if cfg!(target_abi = "sim") {
|
||||
let mut root = PathBuf::from(env::var_os("IPHONE_SIMULATOR_ROOT").expect(
|
||||
"environment variable `IPHONE_SIMULATOR_ROOT` must be set when executing under simulator",
|
||||
));
|
||||
// Convert absolute path to relative path, to make the `.push` work as expected.
|
||||
root.push(Path::new(path).strip_prefix("/").unwrap());
|
||||
root.into()
|
||||
} else {
|
||||
Path::new(path).into()
|
||||
}
|
||||
}
|
||||
|
||||
struct Deferred<F: FnMut()>(F);
|
||||
|
||||
impl<F: FnMut()> Drop for Deferred<F> {
|
||||
fn drop(&mut self) {
|
||||
(self.0)();
|
||||
}
|
||||
}
|
||||
151
library/std/src/sys/platform_version/darwin/public_extern.rs
Normal file
151
library/std/src/sys/platform_version/darwin/public_extern.rs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
//! # Runtime version checking ABI for other compilers.
|
||||
//!
|
||||
//! The symbols in this file are useful for us to expose to allow linking code written in the
|
||||
//! following languages when using their version checking functionality:
|
||||
//! - Clang's `__builtin_available` macro.
|
||||
//! - Objective-C's `@available`.
|
||||
//! - Swift's `#available`,
|
||||
//!
|
||||
//! Without Rust exposing these symbols, the user would encounter a linker error when linking to
|
||||
//! C/Objective-C/Swift libraries using these features.
|
||||
//!
|
||||
//! The presence of these symbols is mostly considered a quality-of-implementation detail, and
|
||||
//! should not be relied upon to be available. The intended effect is that linking with code built
|
||||
//! with Clang's `__builtin_available` (or similar) will continue to work. For example, we may
|
||||
//! decide to remove `__isOSVersionAtLeast` if support for Clang 11 (Xcode 11) is dropped.
|
||||
//!
|
||||
//! ## Background
|
||||
//!
|
||||
//! The original discussion of this feature can be found at:
|
||||
//! - <https://lists.llvm.org/pipermail/cfe-dev/2016-July/049851.html>
|
||||
//! - <https://reviews.llvm.org/D27827>
|
||||
//! - <https://reviews.llvm.org/D30136>
|
||||
//!
|
||||
//! And the upstream implementation of these can be found in `compiler-rt`:
|
||||
//! <https://github.com/llvm/llvm-project/blob/llvmorg-20.1.0/compiler-rt/lib/builtins/os_version_check.c>
|
||||
//!
|
||||
//! Ideally, these symbols should probably have been a part of Apple's `libSystem.dylib`, both
|
||||
//! because their implementation is quite complex, using allocation, environment variables, file
|
||||
//! access and dynamic library loading (and emitting all of this into every binary).
|
||||
//!
|
||||
//! The reason why Apple chose to not do that originally is lost to the sands of time, but a good
|
||||
//! reason would be that implementing it as part of `compiler-rt` allowed them to back-deploy this
|
||||
//! to older OSes immediately.
|
||||
//!
|
||||
//! In Rust's case, while we may provide a feature similar to `@available` in the future, we will
|
||||
//! probably do so as a macro exposed by `std` (and not as a compiler builtin). So implementing this
|
||||
//! in `std` makes sense, since then we can implement it using `std` utilities, and we can avoid
|
||||
//! having `compiler-builtins` depend on `libSystem.dylib`.
|
||||
//!
|
||||
//! This does mean that users that attempt to link C/Objective-C/Swift code _and_ use `#![no_std]`
|
||||
//! in all their crates may get a linker error because these symbols are missing. Using `no_std` is
|
||||
//! quite uncommon on Apple systems though, so it's probably fine to not support this use-case.
|
||||
//!
|
||||
//! The workaround would be to link `libclang_rt.osx.a` or otherwise use Clang's `compiler-rt`.
|
||||
//!
|
||||
//! See also discussion in <https://github.com/rust-lang/compiler-builtins/pull/794>.
|
||||
//!
|
||||
//! ## Implementation details
|
||||
//!
|
||||
//! NOTE: Since macOS 10.15, `libSystem.dylib` _has_ actually provided the undocumented
|
||||
//! `_availability_version_check` via `libxpc` for doing the version lookup (zippered, which is why
|
||||
//! it requires a platform parameter to differentiate between macOS and Mac Catalyst), though its
|
||||
//! usage may be a bit dangerous, see:
|
||||
//! - <https://reviews.llvm.org/D150397>
|
||||
//! - <https://github.com/llvm/llvm-project/issues/64227>
|
||||
//!
|
||||
//! Besides, we'd need to implement the version lookup via PList to support older versions anyhow,
|
||||
//! so we might as well use that everywhere (since it can also be optimized more after inlining).
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use super::{current_version, pack_i32_os_version};
|
||||
|
||||
/// Whether the current platform's OS version is higher than or equal to the given version.
|
||||
///
|
||||
/// The first argument is the _base_ Mach-O platform (i.e. `PLATFORM_MACOS`, `PLATFORM_IOS`, etc.,
|
||||
/// but not `PLATFORM_IOSSIMULATOR` or `PLATFORM_MACCATALYST`) of the invoking binary.
|
||||
///
|
||||
/// Arguments are specified statically by Clang. Inlining with LTO should allow the versions to be
|
||||
/// combined into a single `u32`, which should make comparisons faster, and should make the
|
||||
/// `BASE_TARGET_PLATFORM` check a no-op.
|
||||
//
|
||||
// SAFETY: The signature is the same as what Clang expects, and we export weakly to allow linking
|
||||
// both this and `libclang_rt.*.a`, similar to how `compiler-builtins` does it:
|
||||
// https://github.com/rust-lang/compiler-builtins/blob/0.1.113/src/macros.rs#L494
|
||||
//
|
||||
// NOTE: This symbol has a workaround in the compiler's symbol mangling to avoid mangling it, while
|
||||
// still not exposing it from non-cdylib (like `#[no_mangle]` would).
|
||||
#[rustc_std_internal_symbol]
|
||||
// extern "C" is correct, Clang assumes the function cannot unwind:
|
||||
// https://github.com/llvm/llvm-project/blob/llvmorg-20.1.0/clang/lib/CodeGen/CGObjC.cpp#L3980
|
||||
//
|
||||
// If an error happens in this, we instead abort the process.
|
||||
pub(super) extern "C" fn __isPlatformVersionAtLeast(
|
||||
platform: i32,
|
||||
major: i32,
|
||||
minor: i32,
|
||||
subminor: i32,
|
||||
) -> i32 {
|
||||
let version = pack_i32_os_version(major, minor, subminor);
|
||||
|
||||
// Mac Catalyst is a technology that allows macOS to run in a different "mode" that closely
|
||||
// resembles iOS (and has iOS libraries like UIKit available).
|
||||
//
|
||||
// (Apple has added a "Designed for iPad" mode later on that allows running iOS apps
|
||||
// natively, but we don't need to think too much about those, since they link to
|
||||
// iOS-specific system binaries as well).
|
||||
//
|
||||
// To support Mac Catalyst, Apple added the concept of a "zippered" binary, which is a single
|
||||
// binary that can be run on both macOS and Mac Catalyst (has two `LC_BUILD_VERSION` Mach-O
|
||||
// commands, one set to `PLATFORM_MACOS` and one to `PLATFORM_MACCATALYST`).
|
||||
//
|
||||
// Most system libraries are zippered, which allows re-use across macOS and Mac Catalyst.
|
||||
// This includes the `libclang_rt.osx.a` shipped with Xcode! This means that `compiler-rt`
|
||||
// can't statically know whether it's compiled for macOS or Mac Catalyst, and thus this new
|
||||
// API (which replaces `__isOSVersionAtLeast`) is needed.
|
||||
//
|
||||
// In short:
|
||||
// normal binary calls normal compiler-rt --> `__isOSVersionAtLeast` was enough
|
||||
// normal binary calls zippered compiler-rt --> `__isPlatformVersionAtLeast` required
|
||||
// zippered binary calls zippered compiler-rt --> `__isPlatformOrVariantPlatformVersionAtLeast` called
|
||||
|
||||
// FIXME(madsmtm): `rustc` doesn't support zippered binaries yet, see rust-lang/rust#131216.
|
||||
// But once it does, we need the pre-compiled `std` shipped with rustup to be zippered, and thus
|
||||
// we also need to handle the `platform` difference here:
|
||||
//
|
||||
// if cfg!(target_os = "macos") && platform == 2 /* PLATFORM_IOS */ && cfg!(zippered) {
|
||||
// return (version.to_u32() <= current_ios_version()) as i32;
|
||||
// }
|
||||
//
|
||||
// `__isPlatformOrVariantPlatformVersionAtLeast` would also need to be implemented.
|
||||
|
||||
// The base Mach-O platform for the current target.
|
||||
const BASE_TARGET_PLATFORM: i32 = if cfg!(target_os = "macos") {
|
||||
1 // PLATFORM_MACOS
|
||||
} else if cfg!(target_os = "ios") {
|
||||
2 // PLATFORM_IOS
|
||||
} else if cfg!(target_os = "tvos") {
|
||||
3 // PLATFORM_TVOS
|
||||
} else if cfg!(target_os = "watchos") {
|
||||
4 // PLATFORM_WATCHOS
|
||||
} else if cfg!(target_os = "visionos") {
|
||||
11 // PLATFORM_VISIONOS
|
||||
} else {
|
||||
0 // PLATFORM_UNKNOWN
|
||||
};
|
||||
debug_assert_eq!(
|
||||
platform, BASE_TARGET_PLATFORM,
|
||||
"invalid platform provided to __isPlatformVersionAtLeast",
|
||||
);
|
||||
|
||||
(version <= current_version()) as i32
|
||||
}
|
||||
|
||||
/// Old entry point for availability. Used when compiling with older Clang versions.
|
||||
// SAFETY: Same as for `__isPlatformVersionAtLeast`.
|
||||
#[rustc_std_internal_symbol]
|
||||
pub(super) extern "C" fn __isOSVersionAtLeast(major: i32, minor: i32, subminor: i32) -> i32 {
|
||||
let version = pack_i32_os_version(major, minor, subminor);
|
||||
(version <= current_version()) as i32
|
||||
}
|
||||
379
library/std/src/sys/platform_version/darwin/tests.rs
Normal file
379
library/std/src/sys/platform_version/darwin/tests.rs
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
use super::public_extern::*;
|
||||
use super::*;
|
||||
use crate::process::Command;
|
||||
|
||||
#[test]
|
||||
fn test_general_available() {
|
||||
// Lowest version always available.
|
||||
assert_eq!(__isOSVersionAtLeast(0, 0, 0), 1);
|
||||
// This high version never available.
|
||||
assert_eq!(__isOSVersionAtLeast(9999, 99, 99), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_saturating() {
|
||||
// Higher version than supported by OSVersion -> make sure we saturate.
|
||||
assert_eq!(__isOSVersionAtLeast(0x10000, 0, 0), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(target_os = "macos"), ignore = "`sw_vers` is only available on host macOS")]
|
||||
fn compare_against_sw_vers() {
|
||||
let sw_vers = Command::new("sw_vers").arg("-productVersion").output().unwrap().stdout;
|
||||
let sw_vers = String::from_utf8(sw_vers).unwrap();
|
||||
let mut sw_vers = sw_vers.trim().split('.');
|
||||
|
||||
let major: i32 = sw_vers.next().unwrap().parse().unwrap();
|
||||
let minor: i32 = sw_vers.next().unwrap_or("0").parse().unwrap();
|
||||
let subminor: i32 = sw_vers.next().unwrap_or("0").parse().unwrap();
|
||||
assert_eq!(sw_vers.count(), 0);
|
||||
|
||||
// Current version is available
|
||||
assert_eq!(__isOSVersionAtLeast(major, minor, subminor), 1);
|
||||
|
||||
// One lower is available
|
||||
assert_eq!(__isOSVersionAtLeast(major, minor, subminor.saturating_sub(1)), 1);
|
||||
assert_eq!(__isOSVersionAtLeast(major, minor.saturating_sub(1), subminor), 1);
|
||||
assert_eq!(__isOSVersionAtLeast(major.saturating_sub(1), minor, subminor), 1);
|
||||
|
||||
// One higher isn't available
|
||||
assert_eq!(__isOSVersionAtLeast(major, minor, subminor + 1), 0);
|
||||
assert_eq!(__isOSVersionAtLeast(major, minor + 1, subminor), 0);
|
||||
assert_eq!(__isOSVersionAtLeast(major + 1, minor, subminor), 0);
|
||||
|
||||
// Test directly against the lookup
|
||||
assert_eq!(lookup_version().get(), pack_os_version(major as _, minor as _, subminor as _));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sysctl_same_as_in_plist() {
|
||||
if let Some(version) = version_from_sysctl() {
|
||||
assert_eq!(version, version_from_plist());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_idempotent() {
|
||||
let version = lookup_version();
|
||||
for _ in 0..10 {
|
||||
assert_eq!(version, lookup_version());
|
||||
}
|
||||
}
|
||||
|
||||
/// Test parsing a bunch of different PLists found in the wild, to ensure that
|
||||
/// if we decide to parse it without CoreFoundation in the future, that it
|
||||
/// would continue to work, even on older platforms.
|
||||
#[test]
|
||||
fn parse_plist() {
|
||||
#[track_caller]
|
||||
fn check(
|
||||
(major, minor, patch): (u16, u8, u8),
|
||||
ios_version: Option<(u16, u8, u8)>,
|
||||
plist: &str,
|
||||
) {
|
||||
let expected = if cfg!(target_os = "ios") {
|
||||
if let Some((ios_major, ios_minor, ios_patch)) = ios_version {
|
||||
pack_os_version(ios_major, ios_minor, ios_patch)
|
||||
} else if cfg!(target_abi = "macabi") {
|
||||
// Skip checking iOS version on Mac Catalyst.
|
||||
return;
|
||||
} else {
|
||||
// iOS version will be parsed from ProductVersion
|
||||
pack_os_version(major, minor, patch)
|
||||
}
|
||||
} else {
|
||||
pack_os_version(major, minor, patch)
|
||||
};
|
||||
let cf_handle = CFHandle::new();
|
||||
assert_eq!(expected, parse_version_from_plist(&cf_handle, plist.as_bytes()));
|
||||
}
|
||||
|
||||
// macOS 10.3.0
|
||||
let plist = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>ProductBuildVersion</key>
|
||||
<string>7B85</string>
|
||||
<key>ProductCopyright</key>
|
||||
<string>Apple Computer, Inc. 1983-2003</string>
|
||||
<key>ProductName</key>
|
||||
<string>Mac OS X</string>
|
||||
<key>ProductUserVisibleVersion</key>
|
||||
<string>10.3</string>
|
||||
<key>ProductVersion</key>
|
||||
<string>10.3</string>
|
||||
</dict>
|
||||
</plist>
|
||||
"#;
|
||||
check((10, 3, 0), None, plist);
|
||||
|
||||
// macOS 10.7.5
|
||||
let plist = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>ProductBuildVersion</key>
|
||||
<string>11G63</string>
|
||||
<key>ProductCopyright</key>
|
||||
<string>1983-2012 Apple Inc.</string>
|
||||
<key>ProductName</key>
|
||||
<string>Mac OS X</string>
|
||||
<key>ProductUserVisibleVersion</key>
|
||||
<string>10.7.5</string>
|
||||
<key>ProductVersion</key>
|
||||
<string>10.7.5</string>
|
||||
</dict>
|
||||
</plist>
|
||||
"#;
|
||||
check((10, 7, 5), None, plist);
|
||||
|
||||
// macOS 14.7.4
|
||||
let plist = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildID</key>
|
||||
<string>6A558D8A-E2EA-11EF-A1D3-6222CAA672A8</string>
|
||||
<key>ProductBuildVersion</key>
|
||||
<string>23H420</string>
|
||||
<key>ProductCopyright</key>
|
||||
<string>1983-2025 Apple Inc.</string>
|
||||
<key>ProductName</key>
|
||||
<string>macOS</string>
|
||||
<key>ProductUserVisibleVersion</key>
|
||||
<string>14.7.4</string>
|
||||
<key>ProductVersion</key>
|
||||
<string>14.7.4</string>
|
||||
<key>iOSSupportVersion</key>
|
||||
<string>17.7</string>
|
||||
</dict>
|
||||
</plist>
|
||||
"#;
|
||||
check((14, 7, 4), Some((17, 7, 0)), plist);
|
||||
|
||||
// SystemVersionCompat.plist on macOS 14.7.4
|
||||
let plist = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildID</key>
|
||||
<string>6A558D8A-E2EA-11EF-A1D3-6222CAA672A8</string>
|
||||
<key>ProductBuildVersion</key>
|
||||
<string>23H420</string>
|
||||
<key>ProductCopyright</key>
|
||||
<string>1983-2025 Apple Inc.</string>
|
||||
<key>ProductName</key>
|
||||
<string>Mac OS X</string>
|
||||
<key>ProductUserVisibleVersion</key>
|
||||
<string>10.16</string>
|
||||
<key>ProductVersion</key>
|
||||
<string>10.16</string>
|
||||
<key>iOSSupportVersion</key>
|
||||
<string>17.7</string>
|
||||
</dict>
|
||||
</plist>
|
||||
"#;
|
||||
check((10, 16, 0), Some((17, 7, 0)), plist);
|
||||
|
||||
// macOS 15.4 Beta 24E5238a
|
||||
let plist = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildID</key>
|
||||
<string>67A50F62-00DA-11F0-BDB6-F99BB8310D2A</string>
|
||||
<key>ProductBuildVersion</key>
|
||||
<string>24E5238a</string>
|
||||
<key>ProductCopyright</key>
|
||||
<string>1983-2025 Apple Inc.</string>
|
||||
<key>ProductName</key>
|
||||
<string>macOS</string>
|
||||
<key>ProductUserVisibleVersion</key>
|
||||
<string>15.4</string>
|
||||
<key>ProductVersion</key>
|
||||
<string>15.4</string>
|
||||
<key>iOSSupportVersion</key>
|
||||
<string>18.4</string>
|
||||
</dict>
|
||||
</plist>
|
||||
"#;
|
||||
check((15, 4, 0), Some((18, 4, 0)), plist);
|
||||
|
||||
// iOS Simulator 17.5
|
||||
let plist = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildID</key>
|
||||
<string>210B8A2C-09C3-11EF-9DB8-273A64AEFA1C</string>
|
||||
<key>ProductBuildVersion</key>
|
||||
<string>21F79</string>
|
||||
<key>ProductCopyright</key>
|
||||
<string>1983-2024 Apple Inc.</string>
|
||||
<key>ProductName</key>
|
||||
<string>iPhone OS</string>
|
||||
<key>ProductVersion</key>
|
||||
<string>17.5</string>
|
||||
</dict>
|
||||
</plist>
|
||||
"#;
|
||||
check((17, 5, 0), None, plist);
|
||||
|
||||
// visionOS Simulator 2.3
|
||||
let plist = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildID</key>
|
||||
<string>57CEFDE6-D079-11EF-837C-8B8C7961D0AC</string>
|
||||
<key>ProductBuildVersion</key>
|
||||
<string>22N895</string>
|
||||
<key>ProductCopyright</key>
|
||||
<string>1983-2025 Apple Inc.</string>
|
||||
<key>ProductName</key>
|
||||
<string>xrOS</string>
|
||||
<key>ProductVersion</key>
|
||||
<string>2.3</string>
|
||||
<key>SystemImageID</key>
|
||||
<string>D332C7F1-08DF-4DD9-8122-94EF39A1FB92</string>
|
||||
<key>iOSSupportVersion</key>
|
||||
<string>18.3</string>
|
||||
</dict>
|
||||
</plist>
|
||||
"#;
|
||||
check((2, 3, 0), Some((18, 3, 0)), plist);
|
||||
|
||||
// tvOS Simulator 18.2
|
||||
let plist = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildID</key>
|
||||
<string>617587B0-B059-11EF-BE70-4380EDE44645</string>
|
||||
<key>ProductBuildVersion</key>
|
||||
<string>22K154</string>
|
||||
<key>ProductCopyright</key>
|
||||
<string>1983-2024 Apple Inc.</string>
|
||||
<key>ProductName</key>
|
||||
<string>Apple TVOS</string>
|
||||
<key>ProductVersion</key>
|
||||
<string>18.2</string>
|
||||
<key>SystemImageID</key>
|
||||
<string>8BB5A425-33F0-4821-9F93-40E7ED92F4E0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
"#;
|
||||
check((18, 2, 0), None, plist);
|
||||
|
||||
// watchOS Simulator 11.2
|
||||
let plist = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildID</key>
|
||||
<string>BAAE2D54-B122-11EF-BF78-C6C6836B724A</string>
|
||||
<key>ProductBuildVersion</key>
|
||||
<string>22S99</string>
|
||||
<key>ProductCopyright</key>
|
||||
<string>1983-2024 Apple Inc.</string>
|
||||
<key>ProductName</key>
|
||||
<string>Watch OS</string>
|
||||
<key>ProductVersion</key>
|
||||
<string>11.2</string>
|
||||
<key>SystemImageID</key>
|
||||
<string>79F773E2-2041-43B4-98EE-FAE52402AE95</string>
|
||||
</dict>
|
||||
</plist>
|
||||
"#;
|
||||
check((11, 2, 0), None, plist);
|
||||
|
||||
// iOS 9.3.6
|
||||
let plist = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>ProductBuildVersion</key>
|
||||
<string>13G37</string>
|
||||
<key>ProductCopyright</key>
|
||||
<string>1983-2019 Apple Inc.</string>
|
||||
<key>ProductName</key>
|
||||
<string>iPhone OS</string>
|
||||
<key>ProductVersion</key>
|
||||
<string>9.3.6</string>
|
||||
</dict>
|
||||
</plist>
|
||||
"#;
|
||||
check((9, 3, 6), None, plist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "SystemVersion.plist did not contain a dictionary at the top level"]
|
||||
fn invalid_plist() {
|
||||
let cf_handle = CFHandle::new();
|
||||
let _ = parse_version_from_plist(&cf_handle, b"INVALID");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(
|
||||
target_abi = "macabi",
|
||||
should_panic = "expected iOSSupportVersion in SystemVersion.plist"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(target_abi = "macabi"),
|
||||
should_panic = "expected ProductVersion in SystemVersion.plist"
|
||||
)]
|
||||
fn empty_plist() {
|
||||
let plist = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
</dict>
|
||||
</plist>
|
||||
"#;
|
||||
let cf_handle = CFHandle::new();
|
||||
let _ = parse_version_from_plist(&cf_handle, plist.as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_version() {
|
||||
#[track_caller]
|
||||
fn check(major: u16, minor: u8, patch: u8, version: &str) {
|
||||
assert_eq!(
|
||||
pack_os_version(major, minor, patch),
|
||||
parse_os_version(version.as_bytes()).unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
check(0, 0, 0, "0");
|
||||
check(0, 0, 0, "0.0.0");
|
||||
check(1, 0, 0, "1");
|
||||
check(1, 2, 0, "1.2");
|
||||
check(1, 2, 3, "1.2.3");
|
||||
check(9999, 99, 99, "9999.99.99");
|
||||
|
||||
// Check leading zeroes
|
||||
check(10, 0, 0, "010");
|
||||
check(10, 20, 0, "010.020");
|
||||
check(10, 20, 30, "010.020.030");
|
||||
check(10000, 100, 100, "000010000.00100.00100");
|
||||
|
||||
// Too many parts
|
||||
assert!(parse_os_version(b"1.2.3.4").is_err());
|
||||
|
||||
// Empty
|
||||
assert!(parse_os_version(b"").is_err());
|
||||
|
||||
// Invalid digit
|
||||
assert!(parse_os_version(b"A.B").is_err());
|
||||
|
||||
// Missing digits
|
||||
assert!(parse_os_version(b".").is_err());
|
||||
assert!(parse_os_version(b".1").is_err());
|
||||
assert!(parse_os_version(b"1.").is_err());
|
||||
|
||||
// Too large
|
||||
assert!(parse_os_version(b"100000").is_err());
|
||||
assert!(parse_os_version(b"1.1000").is_err());
|
||||
assert!(parse_os_version(b"1.1.1000").is_err());
|
||||
}
|
||||
13
library/std/src/sys/platform_version/mod.rs
Normal file
13
library/std/src/sys/platform_version/mod.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
//! Runtime lookup of operating system / platform version.
|
||||
//!
|
||||
//! Related to [RFC 3750](https://github.com/rust-lang/rfcs/pull/3750), which
|
||||
//! does version detection at compile-time.
|
||||
//!
|
||||
//! See also the `os_info` crate.
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
mod darwin;
|
||||
|
||||
// In the future, we could expand this module with:
|
||||
// - `RtlGetVersion` on Windows.
|
||||
// - `__system_property_get` on Android.
|
||||
22
tests/run-make/apple-c-available-links/foo.c
Normal file
22
tests/run-make/apple-c-available-links/foo.c
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
int foo(void) {
|
||||
// Act as if using some API that's a lot newer than the deployment target.
|
||||
//
|
||||
// This forces Clang to insert a call to __isPlatformVersionAtLeast,
|
||||
// and linking will fail if that is not present.
|
||||
if (__builtin_available(
|
||||
macos 1000.0,
|
||||
ios 1000.0,
|
||||
tvos 1000.0,
|
||||
watchos 1000.0,
|
||||
// CI runs below Xcode 15, where `visionos` wasn't a valid key in
|
||||
// `__builtin_available`.
|
||||
#ifdef TARGET_OS_VISION
|
||||
visionos 1000.0,
|
||||
#endif
|
||||
*
|
||||
)) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
7
tests/run-make/apple-c-available-links/main.rs
Normal file
7
tests/run-make/apple-c-available-links/main.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
unsafe extern "C" {
|
||||
safe fn foo() -> core::ffi::c_int;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
assert_eq!(foo(), 0);
|
||||
}
|
||||
14
tests/run-make/apple-c-available-links/rmake.rs
Normal file
14
tests/run-make/apple-c-available-links/rmake.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
//! Test that using `__builtin_available` in C (`@available` in Objective-C)
|
||||
//! successfully links (because `std` provides the required symbols).
|
||||
|
||||
//@ only-apple __builtin_available is (mostly) specific to Apple platforms.
|
||||
|
||||
use run_make_support::{cc, rustc, target};
|
||||
|
||||
fn main() {
|
||||
// Invoke the C compiler to generate an object file.
|
||||
cc().arg("-c").input("foo.c").output("foo.o").run();
|
||||
|
||||
// Link the object file together with a Rust program.
|
||||
rustc().target(target()).input("main.rs").link_arg("foo.o").run();
|
||||
}
|
||||
|
|
@ -380,6 +380,7 @@ trigger_files = [
|
|||
[autolabel."O-apple"]
|
||||
trigger_files = [
|
||||
"library/std/src/os/darwin",
|
||||
"library/std/src/sys/platform_version/darwin",
|
||||
"library/std/src/sys/sync/thread_parking/darwin.rs",
|
||||
"compiler/rustc_target/src/spec/base/apple",
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue