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        #[cfg(feature = "summaries")]
135        let minified_json =
136            serde_json::to_string(&target_def).expect("serialization is infallible");
137
138        let target_info = Box::new(target_def.into_target_info(triple_str));
139        Ok(Self {
140            inner: TripleInner::Custom {
141                target_info,
142                #[cfg(feature = "summaries")]
143                custom_source: CustomSource::Json(minified_json),
144            },
145        })
146    }
147
148    /// Creates a new custom `Triple` from the given triple
149    /// string and `rustc --print=cfg` output.
150    ///
151    /// Target feature lines in the cfg output are parsed but
152    /// not stored in the `Triple` (they are the caller's
153    /// responsibility, just as with [`new_custom`](Self::new_custom)).
154    #[cfg(feature = "custom-cfg")]
155    pub fn new_custom_cfg(
156        triple_str: impl Into<Cow<'static, str>>,
157        cfg_text: &str,
158    ) -> Result<Self, crate::errors::CustomTripleCreateError> {
159        let triple_str = triple_str.into();
160        let (target_info, _features) =
161            crate::custom_cfg::parse_cfg_output(triple_str.clone(), cfg_text)?;
162        Ok(Self {
163            inner: TripleInner::Custom {
164                target_info: Box::new(target_info),
165                #[cfg(feature = "summaries")]
166                custom_source: CustomSource::Cfg(cfg_text.to_string()),
167            },
168        })
169    }
170
171    /// Returns the string corresponding to this triple.
172    #[inline]
173    pub fn as_str(&self) -> &str {
174        self.inner.as_str()
175    }
176
177    /// Returns true if this is a triple corresponding to a standard platform.
178    ///
179    /// A standard platform can be either builtin, or heuristically determined.
180    ///
181    /// # Examples
182    ///
183    /// ```
184    /// use target_spec::Triple;
185    ///
186    /// // x86_64-unknown-linux-gnu is Linux x86_64.
187    /// let platform = Triple::new("x86_64-unknown-linux-gnu").unwrap();
188    /// assert!(platform.is_standard());
189    /// ```
190    pub fn is_standard(&self) -> bool {
191        self.inner.is_standard()
192    }
193
194    /// Returns true if this is a triple corresponding to a builtin platform.
195    ///
196    /// # Examples
197    ///
198    /// ```
199    /// use target_spec::Triple;
200    ///
201    /// // x86_64-unknown-linux-gnu is Linux x86_64, which is a Rust tier 1 platform.
202    /// let triple = Triple::new("x86_64-unknown-linux-gnu").unwrap();
203    /// assert!(triple.is_builtin());
204    /// ```
205    #[inline]
206    pub fn is_builtin(&self) -> bool {
207        self.inner.is_builtin()
208    }
209
210    /// Returns true if this triple was heuristically determined.
211    ///
212    /// All heuristically determined platforms are standard, but most of the time, standard
213    /// platforms are builtin.
214    ///
215    /// # Examples
216    ///
217    /// ```
218    /// use target_spec::Triple;
219    ///
220    /// // armv5te-apple-darwin is not a real platform, but target-spec can heuristically
221    /// // guess at its characteristics.
222    /// let triple = Triple::new("armv5te-apple-darwin").unwrap();
223    /// assert!(triple.is_heuristic());
224    /// ```
225    pub fn is_heuristic(&self) -> bool {
226        self.inner.is_heuristic()
227    }
228
229    /// Returns true if this is a custom platform.
230    ///
231    /// This is always available, but if neither the `custom` nor
232    /// `custom-cfg` feature is turned on, this always returns
233    /// false.
234    pub fn is_custom(&self) -> bool {
235        self.inner.is_custom()
236    }
237
238    /// Evaluates this triple against the given platform.
239    ///
240    /// This simply compares `self`'s string representation against the `Triple` the platform is
241    /// based on, ignoring target features and flags.
242    #[inline]
243    pub fn eval(&self, platform: &Platform) -> bool {
244        self.as_str() == platform.triple_str()
245    }
246
247    // Use cfg-expr's target matcher.
248    #[inline]
249    pub(crate) fn matches(&self, tp: &TargetPredicate) -> bool {
250        self.inner.matches(tp)
251    }
252
253    #[cfg(feature = "summaries")]
254    pub(crate) fn custom_json(&self) -> Option<&str> {
255        self.inner.custom_json()
256    }
257
258    #[cfg(feature = "summaries")]
259    pub(crate) fn custom_cfg_text(&self) -> Option<&str> {
260        self.inner.custom_cfg_text()
261    }
262}
263
264impl FromStr for Triple {
265    type Err = TripleParseError;
266
267    fn from_str(triple_str: &str) -> Result<Self, Self::Err> {
268        let inner = TripleInner::from_borrowed_str(triple_str)?;
269        Ok(Self { inner })
270    }
271}
272
273/// Inner representation of a triple.
274#[derive(Clone, Debug)]
275enum TripleInner {
276    /// Prefer the builtin representation as it's more accurate.
277    Builtin(&'static TargetInfo),
278
279    /// A custom triple.
280    #[cfg(feature = "custom-cfg")]
281    Custom {
282        target_info: Box<cfg_expr::targets::TargetInfo>,
283        // The source representation is only needed if summaries
284        // are enabled.
285        #[cfg(feature = "summaries")]
286        custom_source: CustomSource,
287    },
288
289    /// Fall back to the lexicon representation.
290    Lexicon {
291        triple_str: Cow<'static, str>,
292        lexicon_triple: target_lexicon::Triple,
293    },
294}
295
296/// The source representation of a custom platform, used for
297/// round-tripping through summaries.
298#[cfg(all(feature = "custom-cfg", feature = "summaries"))]
299#[derive(Clone, Debug)]
300pub(crate) enum CustomSource {
301    /// Created from target JSON (via `new_custom`).
302    #[cfg(feature = "custom")]
303    Json(String),
304    /// Created from `rustc --print=cfg` output (via
305    /// `new_custom_cfg`).
306    Cfg(String),
307}
308
309impl TripleInner {
310    fn new(triple_str: Cow<'static, str>) -> Result<Self, TripleParseError> {
311        // First try getting the builtin.
312        if let Some(target_info) = get_builtin_target_by_triple(&triple_str) {
313            return Ok(TripleInner::Builtin(target_info));
314        }
315
316        // Next, try getting the lexicon representation.
317        match triple_str.parse::<target_lexicon::Triple>() {
318            Ok(lexicon_triple) => Ok(TripleInner::Lexicon {
319                triple_str,
320                lexicon_triple,
321            }),
322            Err(lexicon_err) => Err(TripleParseError::new(triple_str, lexicon_err)),
323        }
324    }
325
326    fn new_strict(triple_str: Cow<'static, str>) -> Result<Self, TripleParseError> {
327        if let Some(target_info) = get_builtin_target_by_triple(&triple_str) {
328            return Ok(TripleInner::Builtin(target_info));
329        }
330        Err(TripleParseError::new_strict(triple_str))
331    }
332
333    fn from_borrowed_str(triple_str: &str) -> Result<Self, TripleParseError> {
334        // First try getting the builtin.
335        if let Some(target_info) = get_builtin_target_by_triple(triple_str) {
336            return Ok(TripleInner::Builtin(target_info));
337        }
338
339        // Next, try getting the lexicon representation.
340        match triple_str.parse::<target_lexicon::Triple>() {
341            Ok(lexicon_triple) => Ok(TripleInner::Lexicon {
342                triple_str: triple_str.to_owned().into(),
343                lexicon_triple,
344            }),
345            Err(lexicon_err) => Err(TripleParseError::new(
346                triple_str.to_owned().into(),
347                lexicon_err,
348            )),
349        }
350    }
351
352    fn is_standard(&self) -> bool {
353        match self {
354            TripleInner::Builtin(_) | TripleInner::Lexicon { .. } => true,
355            #[cfg(feature = "custom-cfg")]
356            TripleInner::Custom { .. } => false,
357        }
358    }
359
360    fn is_builtin(&self) -> bool {
361        match self {
362            TripleInner::Builtin(_) => true,
363            TripleInner::Lexicon { .. } => false,
364            #[cfg(feature = "custom-cfg")]
365            TripleInner::Custom { .. } => false,
366        }
367    }
368
369    fn is_heuristic(&self) -> bool {
370        match self {
371            TripleInner::Builtin(_) => false,
372            TripleInner::Lexicon { .. } => true,
373            #[cfg(feature = "custom-cfg")]
374            TripleInner::Custom { .. } => false,
375        }
376    }
377
378    fn is_custom(&self) -> bool {
379        match self {
380            TripleInner::Builtin(_) | TripleInner::Lexicon { .. } => false,
381            #[cfg(feature = "custom-cfg")]
382            TripleInner::Custom { .. } => true,
383        }
384    }
385
386    fn as_str(&self) -> &str {
387        match self {
388            TripleInner::Builtin(target_info) => target_info.triple.as_str(),
389            #[cfg(feature = "custom-cfg")]
390            TripleInner::Custom { target_info, .. } => target_info.triple.as_str(),
391            TripleInner::Lexicon { triple_str, .. } => triple_str,
392        }
393    }
394
395    fn matches(&self, tp: &TargetPredicate) -> bool {
396        match self {
397            TripleInner::Builtin(target_info) => target_info.matches(tp),
398            #[cfg(feature = "custom-cfg")]
399            TripleInner::Custom { target_info, .. } => target_info.matches(tp),
400            TripleInner::Lexicon { lexicon_triple, .. } => lexicon_triple.matches(tp),
401        }
402    }
403
404    #[cfg(feature = "summaries")]
405    pub(crate) fn custom_json(&self) -> Option<&str> {
406        match self {
407            TripleInner::Builtin(_) | TripleInner::Lexicon { .. } => None,
408            #[cfg(feature = "custom-cfg")]
409            TripleInner::Custom { custom_source, .. } => match custom_source {
410                #[cfg(feature = "custom")]
411                CustomSource::Json(json) => Some(json),
412                CustomSource::Cfg(_) => None,
413            },
414        }
415    }
416
417    #[cfg(feature = "summaries")]
418    pub(crate) fn custom_cfg_text(&self) -> Option<&str> {
419        match self {
420            TripleInner::Builtin(_) | TripleInner::Lexicon { .. } => None,
421            #[cfg(feature = "custom-cfg")]
422            TripleInner::Custom { custom_source, .. } => match custom_source {
423                #[cfg(feature = "custom")]
424                CustomSource::Json(_) => None,
425                CustomSource::Cfg(cfg_text) => Some(cfg_text),
426            },
427        }
428    }
429
430    fn project(&self) -> TripleInnerProjected<'_> {
431        match self {
432            TripleInner::Builtin(target_info) => {
433                TripleInnerProjected::Builtin(target_info.triple.as_str())
434            }
435            #[cfg(feature = "custom-cfg")]
436            TripleInner::Custom { target_info, .. } => TripleInnerProjected::Custom(target_info),
437            TripleInner::Lexicon { triple_str, .. } => TripleInnerProjected::Lexicon(triple_str),
438        }
439    }
440}
441
442/// This implementation is used for trait impls.
443#[derive(Eq, PartialEq, PartialOrd, Ord, Hash)]
444enum TripleInnerProjected<'a> {
445    // Don't need anything else for builtin and lexicon since it's a pure function of the input.
446    Builtin(&'a str),
447    #[cfg(feature = "custom-cfg")]
448    Custom(&'a TargetInfo),
449    Lexicon(&'a str),
450}
451
452impl PartialEq for TripleInner {
453    fn eq(&self, other: &Self) -> bool {
454        self.project().eq(&other.project())
455    }
456}
457
458impl Eq for TripleInner {}
459
460impl PartialOrd for TripleInner {
461    #[inline]
462    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
463        Some(self.cmp(other))
464    }
465}
466
467impl Ord for TripleInner {
468    #[inline]
469    fn cmp(&self, other: &Self) -> Ordering {
470        self.project().cmp(&other.project())
471    }
472}
473
474impl hash::Hash for TripleInner {
475    fn hash<H: hash::Hasher>(&self, state: &mut H) {
476        hash::Hash::hash(&self.project(), state);
477    }
478}
479
480#[cfg(test)]
481mod tests {
482    use super::*;
483    use target_lexicon::*;
484
485    #[test]
486    fn test_parse() {
487        let target =
488            super::Triple::new("x86_64-pc-darwin").expect("this triple is known to target-lexicon");
489
490        let expected_triple = target_lexicon::Triple {
491            architecture: Architecture::X86_64,
492            vendor: Vendor::Pc,
493            operating_system: OperatingSystem::Darwin(None),
494            environment: Environment::Unknown,
495            binary_format: BinaryFormat::Macho,
496        };
497
498        let actual_triple = match target.inner {
499            TripleInner::Lexicon { lexicon_triple, .. } => lexicon_triple,
500            TripleInner::Builtin(_) => {
501                panic!("should not have been able to parse x86_64-pc-darwin as a builtin");
502            }
503            #[cfg(feature = "custom-cfg")]
504            TripleInner::Custom { .. } => {
505                panic!("not a custom platform")
506            }
507        };
508        assert_eq!(
509            actual_triple, expected_triple,
510            "lexicon triple matched correctly"
511        );
512    }
513
514    #[test]
515    fn test_parse_rustc_version_verbose() {
516        let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
517        let output = std::process::Command::new(rustc)
518            .arg("-vV")
519            .output()
520            .expect("rustc -vV is successful");
521        if !output.status.success() {
522            panic!("rustc -vV failed: {output:?}");
523        }
524
525        let triple = super::Triple::from_rustc_version_verbose(output.stdout).unwrap();
526        assert!(triple.is_standard());
527    }
528}