Skip to main content

apple_platforms/
triple.rs

1//! Rust target triple → Clang triple / SDK name conversion.
2//!
3//! # Overview
4//!
5//! Apple's toolchain (Clang, `swiftc`, `xcrun`) uses a slightly different
6//! target-triple vocabulary than Rust's `rustc`.  The two free functions in
7//! this module bridge that gap:
8//!
9//! ```
10//! use apple_platforms::triple;
11//!
12//! // Rust → Clang
13//! assert_eq!(triple::to_clang("aarch64-apple-darwin"),      Some("arm64-apple-macosx"));
14//! assert_eq!(triple::to_clang("aarch64-apple-ios-sim"),     Some("arm64-apple-ios-simulator"));
15//! assert_eq!(triple::to_clang("x86_64-unknown-linux-gnu"),  None); // not an Apple target
16//!
17//! // Rust → SDK name (for `xcrun --sdk <name>`)
18//! assert_eq!(triple::to_sdk("aarch64-apple-darwin"),        Some("macosx"));
19//! assert_eq!(triple::to_sdk("aarch64-apple-ios-sim"),       Some("iphonesimulator"));
20//! assert_eq!(triple::to_sdk("x86_64-unknown-linux-gnu"),    None);
21//! ```
22//!
23//! Both functions return `None` for unknown triples instead of passing the
24//! input through unchanged, making unrecognised targets explicit.
25//!
26//! # Higher-level API
27//!
28//! If you need more than just strings, parse the triple into an
29//! [`ApplePlatform`](crate::platform::ApplePlatform) and use its methods:
30//!
31//! ```
32//! use apple_platforms::platform::ApplePlatform;
33//!
34//! let platform = ApplePlatform::from_rust_triple("aarch64-apple-ios-sim").unwrap();
35//! assert_eq!(platform, ApplePlatform::IOSSimulator);
36//! assert_eq!(platform.sdk(), Some("iphonesimulator"));
37//! assert_eq!(platform.device(), ApplePlatform::IOS);
38//! ```
39
40use std::{borrow::Cow, fmt, str::FromStr};
41
42// ── Architecture ──────────────────────────────────────────────────────────────
43
44/// CPU architecture extracted from a target triple.
45///
46/// Parsed from the first component of a Rust or Clang target triple string
47/// (`"aarch64"`, `"x86_64"`, `"armv7"`, etc.).
48///
49/// ```
50/// use apple_platforms::triple::Architecture;
51///
52/// assert_eq!("aarch64".parse::<Architecture>().unwrap(), Architecture::Arm64);
53/// assert_eq!("armv7k".parse::<Architecture>().unwrap(), Architecture::Arm);
54/// assert!("riscv64".parse::<Architecture>().is_err());
55/// ```
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
57pub enum Architecture {
58    /// 32-bit x86 (`x86`, `i386`, `i686`).
59    X86,
60    /// 64-bit x86 (`x86_64`).
61    X86_64,
62    /// 32-bit ARM (`arm`, `armv7`, `armv7s`, `armv7k`).
63    Arm,
64    /// 64-bit ARM / Apple Silicon (`aarch64`, `arm64`).
65    Arm64,
66    /// watchOS ILP32 64-bit-pointer ARM (`arm64_32`).
67    Arm64_32,
68}
69
70impl FromStr for Architecture {
71    type Err = UnknownArchitecture;
72
73    fn from_str(s: &str) -> Result<Self, Self::Err> {
74        match s.to_lowercase().as_str() {
75            "x86" | "i386" | "i686"          => Ok(Self::X86),
76            "x86_64"                          => Ok(Self::X86_64),
77            "arm" | "armv7" | "armv7s" | "armv7k" => Ok(Self::Arm),
78            "aarch64" | "arm64"               => Ok(Self::Arm64),
79            "arm64_32"                        => Ok(Self::Arm64_32),
80            _ => Err(UnknownArchitecture(s.to_string())),
81        }
82    }
83}
84
85impl fmt::Display for Architecture {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        let s: Cow<'static, str> = match self {
88            Self::X86      => Cow::Borrowed("x86"),
89            Self::X86_64   => Cow::Borrowed("x86_64"),
90            Self::Arm      => Cow::Borrowed("arm"),
91            Self::Arm64    => Cow::Borrowed("arm64"),
92            Self::Arm64_32 => Cow::Borrowed("arm64_32"),
93        };
94        write!(f, "{s}")
95    }
96}
97
98/// Error returned when parsing an architecture string fails.
99///
100/// The inner `String` is the unrecognised input.
101///
102/// ```
103/// use apple_platforms::triple::{Architecture, UnknownArchitecture};
104///
105/// let err: UnknownArchitecture = "riscv64".parse::<Architecture>().unwrap_err();
106/// assert!(err.to_string().contains("riscv64"));
107/// ```
108#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct UnknownArchitecture(pub String);
110
111impl fmt::Display for UnknownArchitecture {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        write!(f, "unsupported architecture: {:?}", self.0)
114    }
115}
116
117impl std::error::Error for UnknownArchitecture {}
118
119// ── to_clang ──────────────────────────────────────────────────────────────────
120
121/// Convert a Rust target triple to the equivalent Clang-compatible triple.
122///
123/// Returns `None` for triples that are not recognised Apple targets (e.g.
124/// Linux triples) rather than silently returning the input unchanged.
125///
126/// ```
127/// use apple_platforms::triple;
128///
129/// assert_eq!(triple::to_clang("aarch64-apple-darwin"),    Some("arm64-apple-macosx"));
130/// assert_eq!(triple::to_clang("aarch64-apple-visionos"),  Some("arm64-apple-xros"));
131/// assert_eq!(triple::to_clang("aarch64-apple-ios-sim"),   Some("arm64-apple-ios-simulator"));
132/// assert_eq!(triple::to_clang("x86_64-unknown-linux-gnu"), None);
133/// ```
134pub fn to_clang(target: &str) -> Option<&'static str> {
135    match target {
136        // macOS
137        "aarch64-apple-darwin"         => Some("arm64-apple-macosx"),
138        "x86_64-apple-darwin"          => Some("x86_64-apple-macosx"),
139
140        // iOS device
141        "aarch64-apple-ios"            => Some("arm64-apple-ios"),
142        "armv7-apple-ios"              => Some("armv7-apple-ios"),
143        "armv7s-apple-ios"             => Some("armv7s-apple-ios"),
144
145        // iOS simulator  (x86_64-apple-ios is historically the sim triple)
146        "aarch64-apple-ios-sim"        => Some("arm64-apple-ios-simulator"),
147        "x86_64-apple-ios"             => Some("x86_64-apple-ios-simulator"),
148
149        // Mac Catalyst
150        "aarch64-apple-ios-macabi"     => Some("arm64-apple-ios-macabi"),
151        "x86_64-apple-ios-macabi"      => Some("x86_64-apple-ios-macabi"),
152
153        // tvOS
154        "aarch64-apple-tvos"           => Some("arm64-apple-tvos"),
155        "aarch64-apple-tvos-sim"       => Some("arm64-apple-tvos-simulator"),
156        "x86_64-apple-tvos"            => Some("x86_64-apple-tvos-simulator"),
157
158        // watchOS
159        "aarch64-apple-watchos"        => Some("arm64-apple-watchos"),
160        "armv7k-apple-watchos"         => Some("armv7k-apple-watchos"),
161        "arm64_32-apple-watchos"       => Some("arm64_32-apple-watchos"),
162        "aarch64-apple-watchos-sim"    => Some("arm64-apple-watchos-simulator"),
163        "x86_64-apple-watchos-sim"     => Some("x86_64-apple-watchos-simulator"),
164
165        // visionOS (xrOS)
166        "aarch64-apple-visionos"       => Some("arm64-apple-xros"),
167        "aarch64-apple-visionos-sim"   => Some("arm64-apple-xros-simulator"),
168
169        // DriverKit
170        "aarch64-apple-driverkit"      => Some("arm64-apple-driverkit"),
171        "x86_64-apple-driverkit"       => Some("x86_64-apple-driverkit"),
172
173        _ => None,
174    }
175}
176
177// ── to_sdk ────────────────────────────────────────────────────────────────────
178
179/// Convert a Rust target triple to the Apple SDK name for `xcrun --sdk <name>`.
180///
181/// Returns `None` for unrecognised or non-Apple triples.
182///
183/// Note: Mac Catalyst targets use the macOS SDK (`"macosx"`) with the
184/// `macabi` ABI environment.
185///
186/// ```
187/// use apple_platforms::triple;
188///
189/// assert_eq!(triple::to_sdk("aarch64-apple-darwin"),    Some("macosx"));
190/// assert_eq!(triple::to_sdk("aarch64-apple-ios"),       Some("iphoneos"));
191/// assert_eq!(triple::to_sdk("aarch64-apple-ios-sim"),   Some("iphonesimulator"));
192/// assert_eq!(triple::to_sdk("x86_64-unknown-linux-gnu"), None);
193/// ```
194pub fn to_sdk(target: &str) -> Option<&'static str> {
195    // Delegate through ApplePlatform so the mapping lives in one place.
196    crate::platform::ApplePlatform::from_rust_triple(target)
197        .ok()
198        .and_then(|p| p.sdk())
199}
200
201// ── Tests ─────────────────────────────────────────────────────────────────────
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    // ── Architecture ─────────────────────────────────────────────────────────
208
209    #[test]
210    fn parse_architectures() {
211        assert_eq!("aarch64".parse::<Architecture>().unwrap(), Architecture::Arm64);
212        assert_eq!("x86_64".parse::<Architecture>().unwrap(), Architecture::X86_64);
213        assert_eq!("armv7".parse::<Architecture>().unwrap(), Architecture::Arm);
214        assert_eq!("arm64_32".parse::<Architecture>().unwrap(), Architecture::Arm64_32);
215        let err = "riscv64".parse::<Architecture>().unwrap_err();
216        assert!(err.to_string().contains("riscv64"));
217    }
218
219    #[test]
220    fn architecture_display() {
221        assert_eq!(format!("{}", Architecture::Arm64),   "arm64");
222        assert_eq!(format!("{}", Architecture::X86_64),  "x86_64");
223        assert_eq!(format!("{}", Architecture::Arm64_32), "arm64_32");
224    }
225
226    // ── to_clang ──────────────────────────────────────────────────────────────
227
228    #[test]
229    fn clang_macos() {
230        assert_eq!(to_clang("aarch64-apple-darwin"), Some("arm64-apple-macosx"));
231        assert_eq!(to_clang("x86_64-apple-darwin"),  Some("x86_64-apple-macosx"));
232    }
233
234    #[test]
235    fn clang_ios() {
236        assert_eq!(to_clang("aarch64-apple-ios"),     Some("arm64-apple-ios"));
237        assert_eq!(to_clang("aarch64-apple-ios-sim"), Some("arm64-apple-ios-simulator"));
238        assert_eq!(to_clang("x86_64-apple-ios"),      Some("x86_64-apple-ios-simulator"));
239    }
240
241    #[test]
242    fn clang_visionos() {
243        assert_eq!(to_clang("aarch64-apple-visionos"),     Some("arm64-apple-xros"));
244        assert_eq!(to_clang("aarch64-apple-visionos-sim"), Some("arm64-apple-xros-simulator"));
245    }
246
247    #[test]
248    fn clang_catalyst() {
249        assert_eq!(to_clang("aarch64-apple-ios-macabi"), Some("arm64-apple-ios-macabi"));
250    }
251
252    #[test]
253    fn clang_watchos() {
254        assert_eq!(to_clang("aarch64-apple-watchos"),     Some("arm64-apple-watchos"));
255        assert_eq!(to_clang("armv7k-apple-watchos"),      Some("armv7k-apple-watchos"));
256        assert_eq!(to_clang("arm64_32-apple-watchos"),    Some("arm64_32-apple-watchos"));
257        assert_eq!(to_clang("aarch64-apple-watchos-sim"), Some("arm64-apple-watchos-simulator"));
258    }
259
260    #[test]
261    fn clang_driverkit() {
262        assert_eq!(to_clang("aarch64-apple-driverkit"), Some("arm64-apple-driverkit"));
263        assert_eq!(to_clang("x86_64-apple-driverkit"),  Some("x86_64-apple-driverkit"));
264    }
265
266    #[test]
267    fn clang_unknown_is_none() {
268        assert_eq!(to_clang("x86_64-unknown-linux-gnu"), None);
269        assert_eq!(to_clang("aarch64-unknown-linux-musl"), None);
270        assert_eq!(to_clang(""), None);
271    }
272
273    // ── to_sdk ────────────────────────────────────────────────────────────────
274
275    #[test]
276    fn sdk_macos() {
277        assert_eq!(to_sdk("aarch64-apple-darwin"), Some("macosx"));
278        assert_eq!(to_sdk("x86_64-apple-darwin"),  Some("macosx"));
279    }
280
281    #[test]
282    fn sdk_ios() {
283        assert_eq!(to_sdk("aarch64-apple-ios"),     Some("iphoneos"));
284        assert_eq!(to_sdk("aarch64-apple-ios-sim"), Some("iphonesimulator"));
285        assert_eq!(to_sdk("x86_64-apple-ios"),      Some("iphonesimulator"));
286    }
287
288    #[test]
289    fn sdk_visionos() {
290        assert_eq!(to_sdk("aarch64-apple-visionos"),     Some("xros"));
291        assert_eq!(to_sdk("aarch64-apple-visionos-sim"), Some("xrsimulator"));
292    }
293
294    #[test]
295    fn sdk_tvos() {
296        assert_eq!(to_sdk("aarch64-apple-tvos"),     Some("appletvos"));
297        assert_eq!(to_sdk("aarch64-apple-tvos-sim"), Some("appletvsimulator"));
298    }
299
300    #[test]
301    fn sdk_watchos() {
302        assert_eq!(to_sdk("aarch64-apple-watchos"),     Some("watchos"));
303        assert_eq!(to_sdk("aarch64-apple-watchos-sim"), Some("watchsimulator"));
304    }
305
306    #[test]
307    fn sdk_catalyst() {
308        // Mac Catalyst uses the macOS SDK
309        assert_eq!(to_sdk("aarch64-apple-ios-macabi"), Some("macosx"));
310    }
311
312    #[test]
313    fn sdk_unknown_is_none() {
314        assert_eq!(to_sdk("x86_64-unknown-linux-gnu"), None);
315        assert_eq!(to_sdk(""), None);
316    }
317}