Skip to main content

apple_platforms/
platform.rs

1//! Apple platform definitions from LLVM's MachO.def.
2//!
3//! This module exposes two closely related types:
4//!
5//! * [`ApplePlatform`] — a `Copy` enum whose variants map 1-to-1 to LLVM's
6//!   `MachO::PlatformType` identifiers.  It is the primary API surface: parse
7//!   from a Rust target triple, look up an SDK name, or navigate the
8//!   simulator/device relationship.
9//!
10//! * [`Platform`] — a plain `Copy` struct of `&'static str` and `u32` fields
11//!   that carries the raw LLVM metadata for a single platform (the data that
12//!   lives in the `PLATFORM(…)` macro rows in `MachO.def`).  Obtain one via
13//!   [`ApplePlatform::metadata()`] or `Platform::from(platform)`.
14//!
15//! # Error handling
16//!
17//! All fallible constructors return [`ParsePlatformError`] on failure.  It
18//! implements both `Display` and `std::error::Error`, so it composes naturally
19//! with `anyhow`, `thiserror`, or plain `?` propagation.
20//!
21//! # Examples
22//!
23//! ```
24//! use apple_platforms::platform::ApplePlatform;
25//!
26//! // Parse from a Rust target triple
27//! let sim = ApplePlatform::from_rust_triple("aarch64-apple-ios-sim").unwrap();
28//! assert_eq!(sim, ApplePlatform::IOSSimulator);
29//! assert!(sim.is_simulator());
30//! assert!(!sim.is_device());
31//!
32//! // Navigate to the device platform
33//! let device = sim.device();
34//! assert_eq!(device, ApplePlatform::IOS);
35//!
36//! // Navigate back to the simulator
37//! assert_eq!(device.simulator(), Some(ApplePlatform::IOSSimulator));
38//!
39//! // SDK name for xcrun
40//! assert_eq!(sim.sdk(), Some("iphonesimulator"));
41//!
42//! // Iterate all platforms
43//! let count = ApplePlatform::iter().count();
44//! assert_eq!(count, 13); // Unknown + 12 real platforms
45//!
46//! // Sort by Mach-O ID (PartialOrd / Ord)
47//! let mut platforms = vec![ApplePlatform::XrOS, ApplePlatform::MacOS];
48//! platforms.sort();
49//! assert_eq!(platforms[0], ApplePlatform::MacOS);
50//! ```
51//!
52//! Source: <https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/BinaryFormat/MachO.def>
53
54use std::fmt;
55
56// ── Error type ───────────────────────────────────────────────────────────────
57
58/// Error returned when a platform name, ID, or target triple cannot be
59/// recognised as a known Apple platform.
60///
61/// Implements [`std::error::Error`] and [`Display`](std::fmt::Display), so it
62/// composes naturally with `?` and `anyhow`/`thiserror`.
63///
64/// ```
65/// use apple_platforms::platform::{ApplePlatform, ParsePlatformError};
66///
67/// let err: ParsePlatformError = "bogus".parse::<ApplePlatform>().unwrap_err();
68/// assert!(err.to_string().contains("bogus"));
69/// ```
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct ParsePlatformError(String);
72
73impl ParsePlatformError {
74    pub(crate) fn new(s: impl fmt::Display) -> Self {
75        Self(s.to_string())
76    }
77}
78
79impl fmt::Display for ParsePlatformError {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        write!(f, "unknown Apple platform: {:?}", self.0)
82    }
83}
84
85impl std::error::Error for ParsePlatformError {}
86
87// ── Platform metadata ────────────────────────────────────────────────────────
88
89/// Static metadata for a single Apple platform.
90///
91/// All string fields borrow from `'static` storage; there is no lifetime
92/// parameter.  Obtain instances via [`ApplePlatform::metadata()`] or the
93/// [`From`] / [`TryFrom`] conversions below.
94#[derive(Clone, Copy, PartialEq, Eq)]
95pub struct Platform {
96    /// Enum-style name (e.g. `"MACOS"`).
97    pub platform: &'static str,
98    /// Mach-O platform ID.
99    pub id: u32,
100    /// Lowercase name (e.g. `"macos"`).
101    pub name: &'static str,
102    /// Build name (e.g. `"macos"`).
103    pub build_name: &'static str,
104    /// Clang target OS component (e.g. `"macos"`, `"ios-simulator"`).
105    pub target: &'static str,
106    /// TAPI target (e.g. `"macos"`, `"maccatalyst"`).
107    pub tapi_target: &'static str,
108    /// Marketing name (e.g. `"macOS"`, `"iOS Simulator"`).
109    pub marketing: &'static str,
110    /// SDK name for `xcrun --sdk <name>` (e.g. `"macosx"`, `"iphoneos"`).
111    /// `None` for the `Unknown` platform.
112    pub sdk: Option<&'static str>,
113}
114
115impl fmt::Debug for Platform {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        f.debug_struct("Platform")
118            .field("platform", &self.platform)
119            .field("id", &self.id)
120            .field("name", &self.name)
121            .field("build_name", &self.build_name)
122            .field("target", &self.target)
123            .field("tapi_target", &self.tapi_target)
124            .field("marketing", &self.marketing)
125            .field("sdk", &self.sdk)
126            .finish()
127    }
128}
129
130impl fmt::Display for Platform {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        write!(f, "{} (id={}, target={})", self.marketing, self.id, self.target)
133    }
134}
135
136// ── ApplePlatform enum ───────────────────────────────────────────────────────
137
138/// Apple platform enumeration.
139///
140/// Matches LLVM's `MachO::PlatformType` identifiers.  Numeric discriminants
141/// are stable and correspond to Mach-O platform IDs, so `PartialOrd`/`Ord`
142/// order platforms by ID.
143#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
144pub enum ApplePlatform {
145    Unknown          = 0,
146    MacOS            = 1,
147    IOS              = 2,
148    TvOS             = 3,
149    WatchOS          = 4,
150    BridgeOS         = 5,
151    MacCatalyst      = 6,
152    IOSSimulator     = 7,
153    TvOSSimulator    = 8,
154    WatchOSSimulator = 9,
155    DriverKit        = 10,
156    XrOS             = 11,
157    XrOSSimulator    = 12,
158}
159
160impl Default for ApplePlatform {
161    fn default() -> Self {
162        Self::Unknown
163    }
164}
165
166impl fmt::Display for ApplePlatform {
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        write!(f, "{}", self.metadata().marketing)
169    }
170}
171
172impl ApplePlatform {
173    /// All platform variants in Mach-O ID order.
174    pub const ALL: [ApplePlatform; 13] = [
175        Self::Unknown,
176        Self::MacOS,
177        Self::IOS,
178        Self::TvOS,
179        Self::WatchOS,
180        Self::BridgeOS,
181        Self::MacCatalyst,
182        Self::IOSSimulator,
183        Self::TvOSSimulator,
184        Self::WatchOSSimulator,
185        Self::DriverKit,
186        Self::XrOS,
187        Self::XrOSSimulator,
188    ];
189
190    /// Iterate over all platform variants in Mach-O ID order.
191    pub fn iter() -> impl Iterator<Item = ApplePlatform> {
192        Self::ALL.into_iter()
193    }
194
195    /// Returns this platform's static [`Platform`] metadata.
196    #[inline]
197    pub fn metadata(self) -> Platform {
198        PLATFORMS[self as usize]
199    }
200
201    /// The Mach-O platform ID.
202    #[inline]
203    pub fn id(self) -> u32 {
204        self.metadata().id
205    }
206
207    // ── Construction ─────────────────────────────────────────────────────────
208
209    /// Create from a Mach-O platform ID.
210    ///
211    /// Returns `None` for unrecognised IDs instead of silently falling back to
212    /// `Unknown`.  Use [`TryFrom<u32>`] for the same behaviour as a trait impl.
213    pub fn from_id(id: u32) -> Option<Self> {
214        match id {
215            0  => Some(Self::Unknown),
216            1  => Some(Self::MacOS),
217            2  => Some(Self::IOS),
218            3  => Some(Self::TvOS),
219            4  => Some(Self::WatchOS),
220            5  => Some(Self::BridgeOS),
221            6  => Some(Self::MacCatalyst),
222            7  => Some(Self::IOSSimulator),
223            8  => Some(Self::TvOSSimulator),
224            9  => Some(Self::WatchOSSimulator),
225            10 => Some(Self::DriverKit),
226            11 => Some(Self::XrOS),
227            12 => Some(Self::XrOSSimulator),
228            _  => None,
229        }
230    }
231
232    /// Create from a Rust target triple (e.g. `"aarch64-apple-ios-sim"`).
233    ///
234    /// Returns an error if the triple is not a recognised Apple target.
235    ///
236    /// ```
237    /// use apple_platforms::platform::ApplePlatform;
238    /// assert_eq!(ApplePlatform::from_rust_triple("aarch64-apple-darwin").unwrap(), ApplePlatform::MacOS);
239    /// assert_eq!(ApplePlatform::from_rust_triple("aarch64-apple-ios-sim").unwrap(), ApplePlatform::IOSSimulator);
240    /// assert!(ApplePlatform::from_rust_triple("x86_64-unknown-linux-gnu").is_err());
241    /// ```
242    pub fn from_rust_triple(target: &str) -> Result<Self, ParsePlatformError> {
243        match target {
244            // macOS
245            "aarch64-apple-darwin" | "x86_64-apple-darwin"
246                => Ok(Self::MacOS),
247            // iOS device
248            "aarch64-apple-ios" | "armv7-apple-ios" | "armv7s-apple-ios"
249                => Ok(Self::IOS),
250            // iOS simulator  (x86_64-apple-ios is always sim)
251            "aarch64-apple-ios-sim" | "x86_64-apple-ios" | "i386-apple-ios"
252                => Ok(Self::IOSSimulator),
253            // Mac Catalyst (macOS SDK, macabi ABI)
254            "aarch64-apple-ios-macabi" | "x86_64-apple-ios-macabi"
255                => Ok(Self::MacCatalyst),
256            // tvOS
257            "aarch64-apple-tvos"
258                => Ok(Self::TvOS),
259            "aarch64-apple-tvos-sim" | "x86_64-apple-tvos"
260                => Ok(Self::TvOSSimulator),
261            // watchOS
262            "aarch64-apple-watchos" | "armv7k-apple-watchos" | "arm64_32-apple-watchos"
263                => Ok(Self::WatchOS),
264            "aarch64-apple-watchos-sim" | "x86_64-apple-watchos-sim"
265                => Ok(Self::WatchOSSimulator),
266            // visionOS (xrOS internal name)
267            "aarch64-apple-visionos"
268                => Ok(Self::XrOS),
269            "aarch64-apple-visionos-sim"
270                => Ok(Self::XrOSSimulator),
271            // DriverKit
272            "aarch64-apple-driverkit" | "x86_64-apple-driverkit"
273                => Ok(Self::DriverKit),
274            _   => Err(ParsePlatformError::new(target)),
275        }
276    }
277
278    // ── SDK / Clang helpers ───────────────────────────────────────────────────
279
280    /// The SDK name for `xcrun --sdk <name>`.
281    ///
282    /// Returns `None` only for the `Unknown` variant.
283    ///
284    /// Note: [`MacCatalyst`](Self::MacCatalyst) builds use the macOS SDK
285    /// (`"macosx"`) with the `macabi` ABI environment.
286    ///
287    /// ```
288    /// use apple_platforms::platform::ApplePlatform;
289    /// assert_eq!(ApplePlatform::MacOS.sdk(),          Some("macosx"));
290    /// assert_eq!(ApplePlatform::IOS.sdk(),            Some("iphoneos"));
291    /// assert_eq!(ApplePlatform::IOSSimulator.sdk(),   Some("iphonesimulator"));
292    /// assert_eq!(ApplePlatform::MacCatalyst.sdk(),    Some("macosx"));
293    /// assert_eq!(ApplePlatform::Unknown.sdk(),        None);
294    /// ```
295    #[inline]
296    pub fn sdk(self) -> Option<&'static str> {
297        self.metadata().sdk
298    }
299
300    // ── Simulator helpers ─────────────────────────────────────────────────────
301
302    /// Whether this is a simulator platform.
303    pub fn is_simulator(self) -> bool {
304        matches!(
305            self,
306            Self::IOSSimulator | Self::TvOSSimulator | Self::WatchOSSimulator | Self::XrOSSimulator
307        )
308    }
309
310    /// Whether this is a real-device platform (non-simulator, non-Unknown).
311    pub fn is_device(self) -> bool {
312        !self.is_simulator() && self != Self::Unknown
313    }
314
315    /// The corresponding device (non-simulator) platform.
316    ///
317    /// Returns `self` if already a device platform.
318    ///
319    /// ```
320    /// use apple_platforms::platform::ApplePlatform;
321    /// assert_eq!(ApplePlatform::IOSSimulator.device(), ApplePlatform::IOS);
322    /// assert_eq!(ApplePlatform::IOS.device(),          ApplePlatform::IOS);
323    /// ```
324    pub fn device(self) -> Self {
325        match self {
326            Self::IOSSimulator       => Self::IOS,
327            Self::TvOSSimulator      => Self::TvOS,
328            Self::WatchOSSimulator   => Self::WatchOS,
329            Self::XrOSSimulator      => Self::XrOS,
330            other                    => other,
331        }
332    }
333
334    /// The corresponding simulator platform, or `None` if this platform has
335    /// no simulator counterpart (e.g. macOS, DriverKit, BridgeOS).
336    ///
337    /// Returns `Some(self)` if already a simulator.
338    ///
339    /// ```
340    /// use apple_platforms::platform::ApplePlatform;
341    /// assert_eq!(ApplePlatform::IOS.simulator(),            Some(ApplePlatform::IOSSimulator));
342    /// assert_eq!(ApplePlatform::IOSSimulator.simulator(),   Some(ApplePlatform::IOSSimulator));
343    /// assert_eq!(ApplePlatform::MacOS.simulator(),          None);
344    /// assert_eq!(ApplePlatform::DriverKit.simulator(),      None);
345    /// ```
346    pub fn simulator(self) -> Option<Self> {
347        match self {
348            Self::IOS       => Some(Self::IOSSimulator),
349            Self::TvOS      => Some(Self::TvOSSimulator),
350            Self::WatchOS   => Some(Self::WatchOSSimulator),
351            Self::XrOS      => Some(Self::XrOSSimulator),
352            sim if sim.is_simulator() => Some(sim),
353            _ => None,
354        }
355    }
356}
357
358// ── FromStr ──────────────────────────────────────────────────────────────────
359
360impl std::str::FromStr for ApplePlatform {
361    type Err = ParsePlatformError;
362
363    fn from_str(s: &str) -> Result<Self, Self::Err> {
364        match s.to_lowercase().as_str() {
365            "macos" | "macosx" | "osx"                         => Ok(Self::MacOS),
366            "ios" | "iphoneos"                                  => Ok(Self::IOS),
367            "tvos" | "appletvos"                                => Ok(Self::TvOS),
368            "watchos"                                           => Ok(Self::WatchOS),
369            "bridgeos"                                          => Ok(Self::BridgeOS),
370            "maccatalyst" | "macabi"                            => Ok(Self::MacCatalyst),
371            "iossimulator" | "iphonesimulator"                  => Ok(Self::IOSSimulator),
372            "tvossimulator" | "appletvsimulator"                => Ok(Self::TvOSSimulator),
373            "watchossimulator" | "watchsimulator"               => Ok(Self::WatchOSSimulator),
374            "driverkit"                                         => Ok(Self::DriverKit),
375            "xros" | "visionos"                                 => Ok(Self::XrOS),
376            "xrossimulator" | "xrsimulator" | "visionossimulator" => Ok(Self::XrOSSimulator),
377            "unknown"                                           => Ok(Self::Unknown),
378            _ => Err(ParsePlatformError::new(s)),
379        }
380    }
381}
382
383// ── TryFrom ──────────────────────────────────────────────────────────────────
384
385impl TryFrom<u32> for ApplePlatform {
386    type Error = ParsePlatformError;
387    fn try_from(id: u32) -> Result<Self, Self::Error> {
388        Self::from_id(id).ok_or_else(|| ParsePlatformError::new(id))
389    }
390}
391
392impl TryFrom<usize> for ApplePlatform {
393    type Error = ParsePlatformError;
394    fn try_from(id: usize) -> Result<Self, Self::Error> {
395        u32::try_from(id)
396            .ok()
397            .and_then(Self::from_id)
398            .ok_or_else(|| ParsePlatformError::new(id))
399    }
400}
401
402// ── From / TryFrom for Platform ──────────────────────────────────────────────
403
404impl From<ApplePlatform> for Platform {
405    fn from(value: ApplePlatform) -> Self {
406        value.metadata()
407    }
408}
409
410/// Convert a [`Platform`] back to an [`ApplePlatform`] by its numeric ID.
411///
412/// Fails if `p.id` does not correspond to a known platform.
413impl TryFrom<Platform> for ApplePlatform {
414    type Error = ParsePlatformError;
415    fn try_from(p: Platform) -> Result<Self, Self::Error> {
416        Self::from_id(p.id).ok_or_else(|| ParsePlatformError::new(p.id))
417    }
418}
419
420// ── Platform data table ──────────────────────────────────────────────────────
421
422// PLATFORM(platform, id, name, build_name, target, tapi_target, marketing, sdk)
423const PLATFORMS: [Platform; 13] = [
424    Platform { platform: "UNKNOWN",          id: 0,  name: "unknown",          build_name: "unknown",          target: "unknown",           tapi_target: "unknown",          marketing: "unknown",           sdk: None                  },
425    Platform { platform: "MACOS",            id: 1,  name: "macos",            build_name: "macos",            target: "macos",             tapi_target: "macos",            marketing: "macOS",             sdk: Some("macosx")         },
426    Platform { platform: "IOS",              id: 2,  name: "ios",              build_name: "ios",              target: "ios",               tapi_target: "ios",              marketing: "iOS",               sdk: Some("iphoneos")       },
427    Platform { platform: "TVOS",             id: 3,  name: "tvos",             build_name: "tvos",             target: "tvos",              tapi_target: "tvos",             marketing: "tvOS",              sdk: Some("appletvos")      },
428    Platform { platform: "WATCHOS",          id: 4,  name: "watchos",          build_name: "watchos",          target: "watchos",           tapi_target: "watchos",          marketing: "watchOS",           sdk: Some("watchos")        },
429    Platform { platform: "BRIDGEOS",         id: 5,  name: "bridgeos",         build_name: "bridgeos",         target: "bridgeos",          tapi_target: "bridgeos",         marketing: "bridgeOS",          sdk: Some("bridgeos")       },
430    Platform { platform: "MACCATALYST",      id: 6,  name: "macCatalyst",      build_name: "macCatalyst",      target: "ios-macabi",        tapi_target: "maccatalyst",      marketing: "macCatalyst",       sdk: Some("macosx")         },
431    Platform { platform: "IOSSIMULATOR",     id: 7,  name: "iossimulator",     build_name: "iossimulator",     target: "ios-simulator",     tapi_target: "ios-simulator",    marketing: "iOS Simulator",     sdk: Some("iphonesimulator") },
432    Platform { platform: "TVOSSIMULATOR",    id: 8,  name: "tvossimulator",    build_name: "tvossimulator",    target: "tvos-simulator",    tapi_target: "tvos-simulator",   marketing: "tvOS Simulator",    sdk: Some("appletvsimulator") },
433    Platform { platform: "WATCHOSSIMULATOR", id: 9,  name: "watchossimulator", build_name: "watchossimulator", target: "watchos-simulator", tapi_target: "watchos-simulator", marketing: "watchOS Simulator", sdk: Some("watchsimulator") },
434    Platform { platform: "DRIVERKIT",        id: 10, name: "driverkit",        build_name: "driverkit",        target: "driverkit",         tapi_target: "driverkit",        marketing: "DriverKit",         sdk: Some("driverkit")      },
435    Platform { platform: "XROS",             id: 11, name: "xros",             build_name: "xros",             target: "xros",              tapi_target: "xros",             marketing: "visionOS",          sdk: Some("xros")           },
436    Platform { platform: "XROS_SIMULATOR",   id: 12, name: "xrsimulator",      build_name: "xrsimulator",      target: "xros-simulator",    tapi_target: "xros-simulator",   marketing: "visionOS Simulator", sdk: Some("xrsimulator")   },
437];
438
439// ── Tests ────────────────────────────────────────────────────────────────────
440
441#[cfg(test)]
442mod tests {
443    use super::*;
444
445    #[test]
446    fn all_platforms() {
447        let names: Vec<&str> = ApplePlatform::iter()
448            .map(|p| p.metadata().platform)
449            .collect();
450        assert_eq!(names, vec![
451            "UNKNOWN", "MACOS", "IOS", "TVOS", "WATCHOS", "BRIDGEOS",
452            "MACCATALYST", "IOSSIMULATOR", "TVOSSIMULATOR", "WATCHOSSIMULATOR",
453            "DRIVERKIT", "XROS", "XROS_SIMULATOR",
454        ]);
455    }
456
457    #[test]
458    fn platform_ids() {
459        assert_eq!(ApplePlatform::MacOS.id(), 1);
460        assert_eq!(ApplePlatform::IOS.id(), 2);
461        assert_eq!(ApplePlatform::XrOS.id(), 11);
462    }
463
464    #[test]
465    fn from_id_known() {
466        assert_eq!(ApplePlatform::from_id(1), Some(ApplePlatform::MacOS));
467        assert_eq!(ApplePlatform::from_id(11), Some(ApplePlatform::XrOS));
468        assert_eq!(ApplePlatform::from_id(0), Some(ApplePlatform::Unknown));
469    }
470
471    #[test]
472    fn from_id_unknown_is_none() {
473        assert_eq!(ApplePlatform::from_id(999), None);
474        assert_eq!(ApplePlatform::from_id(13), None);
475    }
476
477    #[test]
478    fn try_from_u32() {
479        assert_eq!(ApplePlatform::try_from(1u32).unwrap(), ApplePlatform::MacOS);
480        assert!(ApplePlatform::try_from(99u32).is_err());
481    }
482
483    #[test]
484    fn try_from_usize() {
485        assert_eq!(ApplePlatform::try_from(2usize).unwrap(), ApplePlatform::IOS);
486        assert!(ApplePlatform::try_from(999usize).is_err());
487    }
488
489    #[test]
490    fn from_rust_triple() {
491        assert_eq!(ApplePlatform::from_rust_triple("aarch64-apple-darwin").unwrap(), ApplePlatform::MacOS);
492        assert_eq!(ApplePlatform::from_rust_triple("x86_64-apple-darwin").unwrap(), ApplePlatform::MacOS);
493        assert_eq!(ApplePlatform::from_rust_triple("aarch64-apple-ios").unwrap(), ApplePlatform::IOS);
494        assert_eq!(ApplePlatform::from_rust_triple("aarch64-apple-ios-sim").unwrap(), ApplePlatform::IOSSimulator);
495        assert_eq!(ApplePlatform::from_rust_triple("x86_64-apple-ios").unwrap(), ApplePlatform::IOSSimulator);
496        assert_eq!(ApplePlatform::from_rust_triple("aarch64-apple-ios-macabi").unwrap(), ApplePlatform::MacCatalyst);
497        assert_eq!(ApplePlatform::from_rust_triple("aarch64-apple-tvos").unwrap(), ApplePlatform::TvOS);
498        assert_eq!(ApplePlatform::from_rust_triple("aarch64-apple-tvos-sim").unwrap(), ApplePlatform::TvOSSimulator);
499        assert_eq!(ApplePlatform::from_rust_triple("aarch64-apple-watchos").unwrap(), ApplePlatform::WatchOS);
500        assert_eq!(ApplePlatform::from_rust_triple("aarch64-apple-watchos-sim").unwrap(), ApplePlatform::WatchOSSimulator);
501        assert_eq!(ApplePlatform::from_rust_triple("aarch64-apple-visionos").unwrap(), ApplePlatform::XrOS);
502        assert_eq!(ApplePlatform::from_rust_triple("aarch64-apple-visionos-sim").unwrap(), ApplePlatform::XrOSSimulator);
503        assert_eq!(ApplePlatform::from_rust_triple("aarch64-apple-driverkit").unwrap(), ApplePlatform::DriverKit);
504        assert!(ApplePlatform::from_rust_triple("x86_64-unknown-linux-gnu").is_err());
505    }
506
507    #[test]
508    fn from_str() {
509        assert_eq!("macos".parse::<ApplePlatform>().unwrap(), ApplePlatform::MacOS);
510        assert_eq!("visionos".parse::<ApplePlatform>().unwrap(), ApplePlatform::XrOS);
511        assert_eq!("iphonesimulator".parse::<ApplePlatform>().unwrap(), ApplePlatform::IOSSimulator);
512        let err = "nope".parse::<ApplePlatform>().unwrap_err();
513        assert!(err.to_string().contains("nope"));
514    }
515
516    #[test]
517    fn sdk_names() {
518        assert_eq!(ApplePlatform::MacOS.sdk(),          Some("macosx"));
519        assert_eq!(ApplePlatform::IOS.sdk(),            Some("iphoneos"));
520        assert_eq!(ApplePlatform::IOSSimulator.sdk(),   Some("iphonesimulator"));
521        assert_eq!(ApplePlatform::TvOS.sdk(),           Some("appletvos"));
522        assert_eq!(ApplePlatform::TvOSSimulator.sdk(),  Some("appletvsimulator"));
523        assert_eq!(ApplePlatform::WatchOS.sdk(),        Some("watchos"));
524        assert_eq!(ApplePlatform::WatchOSSimulator.sdk(), Some("watchsimulator"));
525        assert_eq!(ApplePlatform::MacCatalyst.sdk(),    Some("macosx"));
526        assert_eq!(ApplePlatform::XrOS.sdk(),           Some("xros"));
527        assert_eq!(ApplePlatform::XrOSSimulator.sdk(),  Some("xrsimulator"));
528        assert_eq!(ApplePlatform::DriverKit.sdk(),      Some("driverkit"));
529        assert_eq!(ApplePlatform::Unknown.sdk(),        None);
530    }
531
532    #[test]
533    fn tapi_targets_match_llvm() {
534        assert_eq!(ApplePlatform::MacCatalyst.metadata().tapi_target, "maccatalyst");
535        assert_eq!(ApplePlatform::IOSSimulator.metadata().tapi_target, "ios-simulator");
536        assert_eq!(ApplePlatform::XrOSSimulator.metadata().tapi_target, "xros-simulator");
537    }
538
539    #[test]
540    fn simulator_helpers() {
541        assert!(ApplePlatform::IOSSimulator.is_simulator());
542        assert!(!ApplePlatform::IOS.is_simulator());
543        assert!(ApplePlatform::IOS.is_device());
544        assert!(!ApplePlatform::IOSSimulator.is_device());
545        assert!(!ApplePlatform::Unknown.is_device());
546    }
547
548    #[test]
549    fn device_method() {
550        assert_eq!(ApplePlatform::IOSSimulator.device(), ApplePlatform::IOS);
551        assert_eq!(ApplePlatform::XrOSSimulator.device(), ApplePlatform::XrOS);
552        assert_eq!(ApplePlatform::IOS.device(), ApplePlatform::IOS);
553        assert_eq!(ApplePlatform::MacOS.device(), ApplePlatform::MacOS);
554    }
555
556    #[test]
557    fn simulator_method() {
558        assert_eq!(ApplePlatform::IOS.simulator(),     Some(ApplePlatform::IOSSimulator));
559        assert_eq!(ApplePlatform::TvOS.simulator(),    Some(ApplePlatform::TvOSSimulator));
560        assert_eq!(ApplePlatform::WatchOS.simulator(), Some(ApplePlatform::WatchOSSimulator));
561        assert_eq!(ApplePlatform::XrOS.simulator(),    Some(ApplePlatform::XrOSSimulator));
562        // Already a simulator — returns self
563        assert_eq!(ApplePlatform::IOSSimulator.simulator(), Some(ApplePlatform::IOSSimulator));
564        // No simulator counterpart
565        assert_eq!(ApplePlatform::MacOS.simulator(),     None);
566        assert_eq!(ApplePlatform::DriverKit.simulator(), None);
567        assert_eq!(ApplePlatform::BridgeOS.simulator(),  None);
568    }
569
570    #[test]
571    fn ordering() {
572        assert!(ApplePlatform::MacOS < ApplePlatform::IOS);
573        assert!(ApplePlatform::IOS   < ApplePlatform::IOSSimulator);
574        let mut platforms = vec![ApplePlatform::XrOS, ApplePlatform::MacOS, ApplePlatform::IOS];
575        platforms.sort();
576        assert_eq!(platforms, vec![ApplePlatform::MacOS, ApplePlatform::IOS, ApplePlatform::XrOS]);
577    }
578
579    #[test]
580    fn display() {
581        assert_eq!(format!("{}", ApplePlatform::MacOS), "macOS");
582        assert_eq!(format!("{}", ApplePlatform::XrOS),  "visionOS");
583    }
584
585    #[test]
586    fn marketing_names() {
587        assert_eq!(ApplePlatform::XrOS.metadata().marketing,          "visionOS");
588        assert_eq!(ApplePlatform::XrOSSimulator.metadata().marketing,  "visionOS Simulator");
589    }
590
591    #[test]
592    fn from_into_platform() {
593        let p = Platform::from(ApplePlatform::MacOS);
594        assert_eq!(p.id, 1);
595        let back = ApplePlatform::try_from(p).unwrap();
596        assert_eq!(back, ApplePlatform::MacOS);
597    }
598}