target_spec/
triple.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::{
5    Error, Platform,
6    errors::{RustcVersionVerboseParseError, TripleParseError},
7};
8use cfg_expr::{
9    TargetPredicate,
10    expr::TargetMatcher,
11    target_lexicon,
12    targets::{TargetInfo, get_builtin_target_by_triple},
13};
14use std::{borrow::Cow, cmp::Ordering, hash, str::FromStr};
15
16/// A single, specific target, uniquely identified by a triple.
17///
18/// A `Triple` may be constructed through `new` or the `FromStr` implementation.
19///
20/// Every [`Platform`](crate::Platform) is backed by one of these.
21///
22/// # Standard and custom platforms
23///
24/// `target-spec` recognizes two kinds of platforms:
25///
26/// * **Standard platforms:** These platforms are only specified by their triple string, either
27///   directly or via a [`Triple`]. For example, the platform `x86_64-unknown-linux-gnu` is a
28///   standard platform since it is recognized by Rust.
29///
30///   All [builtin platforms](https://doc.rust-lang.org/nightly/rustc/platform-support.html) are
31///   standard platforms.
32///
33///   By default, if a platform isn't builtin, target-spec attempts to heuristically determine the
34///   characteristics of the platform based on the triple string. (Use the
35///   [`new_strict`](Self::new_strict) constructor to disable this.)
36///
37/// * **Custom platforms:** These platforms are specified via both a triple string and a JSON file
38///   in the format [defined by
39///   Rust](https://docs.rust-embedded.org/embedonomicon/custom-target.html). Custom platforms are
40///   used for targets not recognized by Rust.
41///
42/// # Examples
43///
44/// ```
45/// use target_spec::Triple;
46///
47/// // Parse a simple target.
48/// let target = Triple::new("x86_64-unknown-linux-gnu").unwrap();
49/// // This is not a valid triple.
50/// let err = Triple::new("cannot-be-known").unwrap_err();
51/// ```
52#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
53pub struct Triple {
54    inner: TripleInner,
55}
56
57impl Triple {
58    /// Creates a new `Triple` from a triple string.
59    pub fn new(triple_str: impl Into<Cow<'static, str>>) -> Result<Self, TripleParseError> {
60        let inner = TripleInner::new(triple_str.into())?;
61        Ok(Self { inner })
62    }
63
64    /// Creates a new `Triple` from a triple string.
65    ///
66    /// This constructor only consults the builtin platform table, and does not attempt to
67    /// heuristically determine the platform's characteristics based on the triple string.
68    pub fn new_strict(triple_str: impl Into<Cow<'static, str>>) -> Result<Self, TripleParseError> {
69        let inner = TripleInner::new_strict(triple_str.into())?;
70        Ok(Self { inner })
71    }
72
73    /// Creates a new standard `Triple` from `rustc -vV` output.
74    ///
75    /// # Example
76    ///
77    /// ```
78    /// // This is the typical output of `rustc -vV`.
79    /// let output = b"rustc 1.84.1 (e71f9a9a9 2025-01-27)
80    /// binary: rustc
81    /// commit-hash: e71f9a9a98b0faf423844bf0ba7438f29dc27d58
82    /// commit-date: 2025-01-27
83    /// host: x86_64-unknown-linux-gnu
84    /// release: 1.84.1
85    /// LLVM version: 19.1.5";
86    ///
87    /// let triple = target_spec::Triple::from_rustc_version_verbose(output).unwrap();
88    /// assert_eq!(triple.as_str(), "x86_64-unknown-linux-gnu");
89    /// ```
90    pub fn from_rustc_version_verbose(output: impl AsRef<[u8]>) -> Result<Self, Error> {
91        let output_slice = output.as_ref();
92        let output = std::str::from_utf8(output_slice).map_err(|_| {
93            // Get a better error message in this case via
94            // std::string::FromUtf8Error.
95            let output_vec = output_slice.to_vec();
96            Error::RustcVersionVerboseParse(RustcVersionVerboseParseError::InvalidUtf8(
97                String::from_utf8(output_vec)
98                    .expect_err("we just failed to convert to UTF-8 above"),
99            ))
100        })?;
101
102        // Look for the line beginning with `host: ` and extract the triple.
103        // (This is a bit fragile, but it's what Cargo does.)
104        let triple_str = output
105            .lines()
106            .find_map(|line| line.strip_prefix("host: "))
107            .ok_or_else(|| {
108                Error::RustcVersionVerboseParse(RustcVersionVerboseParseError::MissingHostLine {
109                    output: output.to_owned(),
110                })
111            })?;
112
113        // Now look up the triple.
114        Self::new(triple_str.to_owned()).map_err(Error::UnknownPlatformTriple)
115    }
116
117    /// Creates a new custom `Triple` from the given triple string and JSON specification.
118    #[cfg(feature = "custom")]
119    pub fn new_custom(
120        triple_str: impl Into<Cow<'static, str>>,
121        json: &str,
122    ) -> Result<Self, crate::errors::CustomTripleCreateError> {
123        use crate::custom::TargetDefinition;
124
125        let triple_str = triple_str.into();
126        let target_def: TargetDefinition = serde_json::from_str(json).map_err(|error| {
127            crate::errors::CustomTripleCreateError::DeserializeJson {
128                triple: triple_str.to_string(),
129                input: json.to_string(),
130                error: error.into(),
131            }
132        })?;
133        #[cfg(feature = "summaries")]
134        let minified_json =
135            serde_json::to_string(&target_def).expect("serialization is infallible");
136
137        let target_info = Box::new(target_def.into_target_info(triple_str));
138        Ok(Self {
139            inner: TripleInner::Custom {
140                target_info,
141                #[cfg(feature = "summaries")]
142                json: minified_json,
143            },
144        })
145    }
146
147    /// Returns the string corresponding to this triple.
148    #[inline]
149    pub fn as_str(&self) -> &str {
150        self.inner.as_str()
151    }
152
153    /// Returns true if this is a triple corresponding to a standard platform.
154    ///
155    /// A standard platform can be either builtin, or heuristically determined.
156    ///
157    /// # Examples
158    ///
159    /// ```
160    /// use target_spec::Triple;
161    ///
162    /// // x86_64-unknown-linux-gnu is Linux x86_64.
163    /// let platform = Triple::new("x86_64-unknown-linux-gnu").unwrap();
164    /// assert!(platform.is_standard());
165    /// ```
166    pub fn is_standard(&self) -> bool {
167        self.inner.is_standard()
168    }
169
170    /// Returns true if this is a triple corresponding to a builtin platform.
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// use target_spec::Triple;
176    ///
177    /// // x86_64-unknown-linux-gnu is Linux x86_64, which is a Rust tier 1 platform.
178    /// let triple = Triple::new("x86_64-unknown-linux-gnu").unwrap();
179    /// assert!(triple.is_builtin());
180    /// ```
181    #[inline]
182    pub fn is_builtin(&self) -> bool {
183        self.inner.is_builtin()
184    }
185
186    /// Returns true if this triple was heuristically determined.
187    ///
188    /// All heuristically determined platforms are standard, but most of the time, standard
189    /// platforms are builtin.
190    ///
191    /// # Examples
192    ///
193    /// ```
194    /// use target_spec::Triple;
195    ///
196    /// // armv5te-apple-darwin is not a real platform, but target-spec can heuristically
197    /// // guess at its characteristics.
198    /// let triple = Triple::new("armv5te-apple-darwin").unwrap();
199    /// assert!(triple.is_heuristic());
200    /// ```
201    pub fn is_heuristic(&self) -> bool {
202        self.inner.is_heuristic()
203    }
204
205    /// Returns true if this is a custom platform.
206    ///
207    /// This is always available, but if the `custom` feature isn't turned on this always returns
208    /// false.
209    pub fn is_custom(&self) -> bool {
210        self.inner.is_custom()
211    }
212
213    /// Evaluates this triple against the given platform.
214    ///
215    /// This simply compares `self`'s string representation against the `Triple` the platform is
216    /// based on, ignoring target features and flags.
217    #[inline]
218    pub fn eval(&self, platform: &Platform) -> bool {
219        self.as_str() == platform.triple_str()
220    }
221
222    // Use cfg-expr's target matcher.
223    #[inline]
224    pub(crate) fn matches(&self, tp: &TargetPredicate) -> bool {
225        self.inner.matches(tp)
226    }
227
228    #[cfg(feature = "summaries")]
229    pub(crate) fn custom_json(&self) -> Option<&str> {
230        self.inner.custom_json()
231    }
232}
233
234impl FromStr for Triple {
235    type Err = TripleParseError;
236
237    fn from_str(triple_str: &str) -> Result<Self, Self::Err> {
238        let inner = TripleInner::from_borrowed_str(triple_str)?;
239        Ok(Self { inner })
240    }
241}
242
243/// Inner representation of a triple.
244#[derive(Clone, Debug)]
245enum TripleInner {
246    /// Prefer the builtin representation as it's more accurate.
247    Builtin(&'static TargetInfo),
248
249    /// A custom triple.
250    #[cfg(feature = "custom")]
251    Custom {
252        target_info: Box<cfg_expr::targets::TargetInfo>,
253        // The JSON is only needed if summaries are enabled.
254        #[cfg(feature = "summaries")]
255        json: String,
256    },
257
258    /// Fall back to the lexicon representation.
259    Lexicon {
260        triple_str: Cow<'static, str>,
261        lexicon_triple: target_lexicon::Triple,
262    },
263}
264
265impl TripleInner {
266    fn new(triple_str: Cow<'static, str>) -> Result<Self, TripleParseError> {
267        // First try getting the builtin.
268        if let Some(target_info) = get_builtin_target_by_triple(&triple_str) {
269            return Ok(TripleInner::Builtin(target_info));
270        }
271
272        // Next, try getting the lexicon representation.
273        match triple_str.parse::<target_lexicon::Triple>() {
274            Ok(lexicon_triple) => Ok(TripleInner::Lexicon {
275                triple_str,
276                lexicon_triple,
277            }),
278            Err(lexicon_err) => Err(TripleParseError::new(triple_str, lexicon_err)),
279        }
280    }
281
282    fn new_strict(triple_str: Cow<'static, str>) -> Result<Self, TripleParseError> {
283        if let Some(target_info) = get_builtin_target_by_triple(&triple_str) {
284            return Ok(TripleInner::Builtin(target_info));
285        }
286        Err(TripleParseError::new_strict(triple_str))
287    }
288
289    fn from_borrowed_str(triple_str: &str) -> Result<Self, TripleParseError> {
290        // First try getting the builtin.
291        if let Some(target_info) = get_builtin_target_by_triple(triple_str) {
292            return Ok(TripleInner::Builtin(target_info));
293        }
294
295        // Next, try getting the lexicon representation.
296        match triple_str.parse::<target_lexicon::Triple>() {
297            Ok(lexicon_triple) => Ok(TripleInner::Lexicon {
298                triple_str: triple_str.to_owned().into(),
299                lexicon_triple,
300            }),
301            Err(lexicon_err) => Err(TripleParseError::new(
302                triple_str.to_owned().into(),
303                lexicon_err,
304            )),
305        }
306    }
307
308    fn is_standard(&self) -> bool {
309        match self {
310            TripleInner::Builtin(_) | TripleInner::Lexicon { .. } => true,
311            #[cfg(feature = "custom")]
312            TripleInner::Custom { .. } => false,
313        }
314    }
315
316    fn is_builtin(&self) -> bool {
317        match self {
318            TripleInner::Builtin(_) => true,
319            TripleInner::Lexicon { .. } => false,
320            #[cfg(feature = "custom")]
321            TripleInner::Custom { .. } => false,
322        }
323    }
324
325    fn is_heuristic(&self) -> bool {
326        match self {
327            TripleInner::Builtin(_) => false,
328            TripleInner::Lexicon { .. } => true,
329            #[cfg(feature = "custom")]
330            TripleInner::Custom { .. } => false,
331        }
332    }
333
334    fn is_custom(&self) -> bool {
335        match self {
336            TripleInner::Builtin(_) | TripleInner::Lexicon { .. } => false,
337            #[cfg(feature = "custom")]
338            TripleInner::Custom { .. } => true,
339        }
340    }
341
342    fn as_str(&self) -> &str {
343        match self {
344            TripleInner::Builtin(target_info) => target_info.triple.as_str(),
345            #[cfg(feature = "custom")]
346            TripleInner::Custom { target_info, .. } => target_info.triple.as_str(),
347            TripleInner::Lexicon { triple_str, .. } => triple_str,
348        }
349    }
350
351    fn matches(&self, tp: &TargetPredicate) -> bool {
352        match self {
353            TripleInner::Builtin(target_info) => target_info.matches(tp),
354            #[cfg(feature = "custom")]
355            TripleInner::Custom { target_info, .. } => target_info.matches(tp),
356            TripleInner::Lexicon { lexicon_triple, .. } => lexicon_triple.matches(tp),
357        }
358    }
359
360    #[cfg(feature = "summaries")]
361    pub(crate) fn custom_json(&self) -> Option<&str> {
362        match self {
363            TripleInner::Builtin(_) => None,
364            #[cfg(feature = "custom")]
365            TripleInner::Custom { json, .. } => Some(json),
366            TripleInner::Lexicon { .. } => None,
367        }
368    }
369
370    fn project(&self) -> TripleInnerProjected<'_> {
371        match self {
372            TripleInner::Builtin(target_info) => {
373                TripleInnerProjected::Builtin(target_info.triple.as_str())
374            }
375            #[cfg(feature = "custom")]
376            TripleInner::Custom { target_info, .. } => TripleInnerProjected::Custom(target_info),
377            TripleInner::Lexicon { triple_str, .. } => TripleInnerProjected::Lexicon(triple_str),
378        }
379    }
380}
381
382/// This implementation is used for trait impls.
383#[derive(Eq, PartialEq, PartialOrd, Ord, Hash)]
384enum TripleInnerProjected<'a> {
385    // Don't need anything else for builtin and lexicon since it's a pure function of the input.
386    Builtin(&'a str),
387    #[cfg(feature = "custom")]
388    Custom(&'a TargetInfo),
389    Lexicon(&'a str),
390}
391
392impl PartialEq for TripleInner {
393    fn eq(&self, other: &Self) -> bool {
394        self.project().eq(&other.project())
395    }
396}
397
398impl Eq for TripleInner {}
399
400impl PartialOrd for TripleInner {
401    #[inline]
402    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
403        Some(self.cmp(other))
404    }
405}
406
407impl Ord for TripleInner {
408    #[inline]
409    fn cmp(&self, other: &Self) -> Ordering {
410        self.project().cmp(&other.project())
411    }
412}
413
414impl hash::Hash for TripleInner {
415    fn hash<H: hash::Hasher>(&self, state: &mut H) {
416        hash::Hash::hash(&self.project(), state);
417    }
418}
419
420#[cfg(test)]
421mod tests {
422    use super::*;
423    use target_lexicon::*;
424
425    #[test]
426    fn test_parse() {
427        let target =
428            super::Triple::new("x86_64-pc-darwin").expect("this triple is known to target-lexicon");
429
430        let expected_triple = target_lexicon::Triple {
431            architecture: Architecture::X86_64,
432            vendor: Vendor::Pc,
433            operating_system: OperatingSystem::Darwin(None),
434            environment: Environment::Unknown,
435            binary_format: BinaryFormat::Macho,
436        };
437
438        let actual_triple = match target.inner {
439            TripleInner::Lexicon { lexicon_triple, .. } => lexicon_triple,
440            TripleInner::Builtin(_) => {
441                panic!("should not have been able to parse x86_64-pc-darwin as a builtin");
442            }
443            #[cfg(feature = "custom")]
444            TripleInner::Custom { .. } => {
445                panic!("not a custom platform")
446            }
447        };
448        assert_eq!(
449            actual_triple, expected_triple,
450            "lexicon triple matched correctly"
451        );
452    }
453
454    #[test]
455    fn test_parse_rustc_version_verbose() {
456        let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
457        let output = std::process::Command::new(rustc)
458            .arg("-vV")
459            .output()
460            .expect("rustc -vV is successful");
461        if !output.status.success() {
462            panic!("rustc -vV failed: {:?}", output);
463        }
464
465        let triple = super::Triple::from_rustc_version_verbose(output.stdout).unwrap();
466        assert!(triple.is_standard());
467    }
468}