Skip to main content

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