1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
// Copyright (c) The cargo-guppy Contributors
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::{errors::TripleParseError, Platform};
use cfg_expr::{
    expr::TargetMatcher,
    target_lexicon,
    targets::{get_builtin_target_by_triple, TargetInfo},
    TargetPredicate,
};
use std::{borrow::Cow, cmp::Ordering, hash, str::FromStr};

/// A single, specific target, uniquely identified by a triple.
///
/// A `Triple` may be constructed through `new` or the `FromStr` implementation.
///
/// Every [`Platform`](crate::Platform) is backed by one of these.
///
/// # Standard and custom platforms
///
/// `target-spec` recognizes two kinds of platforms:
///
/// * **Standard platforms:** These platforms are only specified by their triple string, either
///   directly or via a [`Triple`]. For example, the platform `x86_64-unknown-linux-gnu` is a
///   standard platform since it is recognized by Rust.
///
///   All [builtin platforms](https://doc.rust-lang.org/nightly/rustc/platform-support.html) are
///   standard platforms.
///
///   By default, if a platform isn't builtin, target-spec attempts to heuristically determine the
///   characteristics of the platform based on the triple string. (Use the
///   [`new_strict`](Self::new_strict) constructor to disable this.)
///
/// * **Custom platforms:** These platforms are specified via both a triple string and a JSON file
///   in the format [defined by
///   Rust](https://docs.rust-embedded.org/embedonomicon/custom-target.html). Custom platforms are
///   used for targets not recognized by Rust.
///
/// # Examples
///
/// ```
/// use target_spec::Triple;
///
/// // Parse a simple target.
/// let target = Triple::new("x86_64-unknown-linux-gnu").unwrap();
/// // This is not a valid triple.
/// let err = Triple::new("cannot-be-known").unwrap_err();
/// ```
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct Triple {
    inner: TripleInner,
}

impl Triple {
    /// Creates a new `Triple` from a triple string.
    pub fn new(triple_str: impl Into<Cow<'static, str>>) -> Result<Self, TripleParseError> {
        let inner = TripleInner::new(triple_str.into())?;
        Ok(Self { inner })
    }

    /// Creates a new `Triple` from a triple string.
    ///
    /// This constructor only consults the builtin platform table, and does not attempt to
    /// heuristically determine the platform's characteristics based on the triple string.
    pub fn new_strict(triple_str: impl Into<Cow<'static, str>>) -> Result<Self, TripleParseError> {
        let inner = TripleInner::new_strict(triple_str.into())?;
        Ok(Self { inner })
    }

    /// Creates a new custom `Triple` from the given triple string and JSON specification.
    #[cfg(feature = "custom")]
    pub fn new_custom(
        triple_str: impl Into<Cow<'static, str>>,
        json: &str,
    ) -> Result<Self, crate::errors::CustomTripleCreateError> {
        use crate::custom::TargetDefinition;

        let triple_str = triple_str.into();
        let target_def: TargetDefinition = serde_json::from_str(json).map_err(|error| {
            crate::errors::CustomTripleCreateError::Deserialize {
                triple: triple_str.to_string(),
                error: error.into(),
            }
        })?;
        #[cfg(feature = "summaries")]
        let minified_json =
            serde_json::to_string(&target_def).expect("serialization is infallible");

        let target_info = Box::new(target_def.into_target_info(triple_str));
        Ok(Self {
            inner: TripleInner::Custom {
                target_info,
                #[cfg(feature = "summaries")]
                json: minified_json,
            },
        })
    }

    /// Returns the string corresponding to this triple.
    #[inline]
    pub fn as_str(&self) -> &str {
        self.inner.as_str()
    }

    /// Returns true if this is a triple corresponding to a standard platform.
    ///
    /// A standard platform can be either builtin, or heuristically determined.
    ///
    /// # Examples
    ///
    /// ```
    /// use target_spec::Triple;
    ///
    /// // x86_64-unknown-linux-gnu is Linux x86_64.
    /// let platform = Triple::new("x86_64-unknown-linux-gnu").unwrap();
    /// assert!(platform.is_standard());
    /// ```
    pub fn is_standard(&self) -> bool {
        self.inner.is_standard()
    }

    /// Returns true if this is a triple corresponding to a builtin platform.
    ///
    /// # Examples
    ///
    /// ```
    /// use target_spec::Triple;
    ///
    /// // x86_64-unknown-linux-gnu is Linux x86_64, which is a Rust tier 1 platform.
    /// let triple = Triple::new("x86_64-unknown-linux-gnu").unwrap();
    /// assert!(triple.is_builtin());
    /// ```
    #[inline]
    pub fn is_builtin(&self) -> bool {
        self.inner.is_builtin()
    }

    /// Returns true if this triple was heuristically determined.
    ///
    /// All heuristically determined platforms are standard, but most of the time, standard
    /// platforms are builtin.
    ///
    /// # Examples
    ///
    /// ```
    /// use target_spec::Triple;
    ///
    /// // armv5te-apple-darwin is not a real platform, but target-spec can heuristically
    /// // guess at its characteristics.
    /// let triple = Triple::new("armv5te-apple-darwin").unwrap();
    /// assert!(triple.is_heuristic());
    /// ```
    pub fn is_heuristic(&self) -> bool {
        self.inner.is_heuristic()
    }

    /// Returns true if this is a custom platform.
    ///
    /// This is always available, but if the `custom` feature isn't turned on this always returns
    /// false.
    pub fn is_custom(&self) -> bool {
        self.inner.is_custom()
    }

    /// Evaluates this triple against the given platform.
    ///
    /// This simply compares `self`'s string representation against the `Triple` the platform is
    /// based on, ignoring target features and flags.
    #[inline]
    pub fn eval(&self, platform: &Platform) -> bool {
        self.as_str() == platform.triple_str()
    }

    // Use cfg-expr's target matcher.
    #[inline]
    pub(crate) fn matches(&self, tp: &TargetPredicate) -> bool {
        self.inner.matches(tp)
    }

    #[cfg(feature = "summaries")]
    pub(crate) fn custom_json(&self) -> Option<&str> {
        self.inner.custom_json()
    }
}

impl FromStr for Triple {
    type Err = TripleParseError;

    fn from_str(triple_str: &str) -> Result<Self, Self::Err> {
        let inner = TripleInner::from_borrowed_str(triple_str)?;
        Ok(Self { inner })
    }
}

/// Inner representation of a triple.
#[derive(Clone, Debug)]
enum TripleInner {
    /// Prefer the builtin representation as it's more accurate.
    Builtin(&'static TargetInfo),

    /// A custom triple.
    #[cfg(feature = "custom")]
    Custom {
        target_info: Box<cfg_expr::targets::TargetInfo>,
        // The JSON is only needed if summaries are enabled.
        #[cfg(feature = "summaries")]
        json: String,
    },

    /// Fall back to the lexicon representation.
    Lexicon {
        triple_str: Cow<'static, str>,
        lexicon_triple: target_lexicon::Triple,
    },
}

impl TripleInner {
    fn new(triple_str: Cow<'static, str>) -> Result<Self, TripleParseError> {
        // First try getting the builtin.
        if let Some(target_info) = get_builtin_target_by_triple(&triple_str) {
            return Ok(TripleInner::Builtin(target_info));
        }

        // Next, try getting the lexicon representation.
        match triple_str.parse::<target_lexicon::Triple>() {
            Ok(lexicon_triple) => Ok(TripleInner::Lexicon {
                triple_str,
                lexicon_triple,
            }),
            Err(lexicon_err) => Err(TripleParseError::new(triple_str, lexicon_err)),
        }
    }

    fn new_strict(triple_str: Cow<'static, str>) -> Result<Self, TripleParseError> {
        if let Some(target_info) = get_builtin_target_by_triple(&triple_str) {
            return Ok(TripleInner::Builtin(target_info));
        }
        Err(TripleParseError::new_strict(triple_str))
    }

    fn from_borrowed_str(triple_str: &str) -> Result<Self, TripleParseError> {
        // First try getting the builtin.
        if let Some(target_info) = get_builtin_target_by_triple(triple_str) {
            return Ok(TripleInner::Builtin(target_info));
        }

        // Next, try getting the lexicon representation.
        match triple_str.parse::<target_lexicon::Triple>() {
            Ok(lexicon_triple) => Ok(TripleInner::Lexicon {
                triple_str: triple_str.to_owned().into(),
                lexicon_triple,
            }),
            Err(lexicon_err) => Err(TripleParseError::new(
                triple_str.to_owned().into(),
                lexicon_err,
            )),
        }
    }

    fn is_standard(&self) -> bool {
        match self {
            TripleInner::Builtin(_) | TripleInner::Lexicon { .. } => true,
            #[cfg(feature = "custom")]
            TripleInner::Custom { .. } => false,
        }
    }

    fn is_builtin(&self) -> bool {
        match self {
            TripleInner::Builtin(_) => true,
            TripleInner::Lexicon { .. } => false,
            #[cfg(feature = "custom")]
            TripleInner::Custom { .. } => false,
        }
    }

    fn is_heuristic(&self) -> bool {
        match self {
            TripleInner::Builtin(_) => false,
            TripleInner::Lexicon { .. } => true,
            #[cfg(feature = "custom")]
            TripleInner::Custom { .. } => false,
        }
    }

    fn is_custom(&self) -> bool {
        match self {
            TripleInner::Builtin(_) | TripleInner::Lexicon { .. } => false,
            #[cfg(feature = "custom")]
            TripleInner::Custom { .. } => true,
        }
    }

    fn as_str(&self) -> &str {
        match self {
            TripleInner::Builtin(target_info) => target_info.triple.as_str(),
            #[cfg(feature = "custom")]
            TripleInner::Custom { target_info, .. } => target_info.triple.as_str(),
            TripleInner::Lexicon { triple_str, .. } => triple_str,
        }
    }

    fn matches(&self, tp: &TargetPredicate) -> bool {
        match self {
            TripleInner::Builtin(target_info) => target_info.matches(tp),
            #[cfg(feature = "custom")]
            TripleInner::Custom { target_info, .. } => target_info.matches(tp),
            TripleInner::Lexicon { lexicon_triple, .. } => lexicon_triple.matches(tp),
        }
    }

    #[cfg(feature = "summaries")]
    pub(crate) fn custom_json(&self) -> Option<&str> {
        match self {
            TripleInner::Builtin(_) => None,
            #[cfg(feature = "custom")]
            TripleInner::Custom { json, .. } => Some(json),
            TripleInner::Lexicon { .. } => None,
        }
    }

    fn project(&self) -> TripleInnerProjected<'_> {
        match self {
            TripleInner::Builtin(target_info) => {
                TripleInnerProjected::Builtin(target_info.triple.as_str())
            }
            #[cfg(feature = "custom")]
            TripleInner::Custom { target_info, .. } => TripleInnerProjected::Custom(target_info),
            TripleInner::Lexicon { triple_str, .. } => TripleInnerProjected::Lexicon(triple_str),
        }
    }
}

/// This implementation is used for trait impls.
#[derive(Eq, PartialEq, PartialOrd, Ord, Hash)]
enum TripleInnerProjected<'a> {
    // Don't need anything else for builtin and lexicon since it's a pure function of the input.
    Builtin(&'a str),
    #[cfg(feature = "custom")]
    Custom(&'a TargetInfo),
    Lexicon(&'a str),
}

impl PartialEq for TripleInner {
    fn eq(&self, other: &Self) -> bool {
        self.project().eq(&other.project())
    }
}

impl Eq for TripleInner {}

impl PartialOrd for TripleInner {
    #[inline]
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for TripleInner {
    #[inline]
    fn cmp(&self, other: &Self) -> Ordering {
        self.project().cmp(&other.project())
    }
}

impl hash::Hash for TripleInner {
    fn hash<H: hash::Hasher>(&self, state: &mut H) {
        hash::Hash::hash(&self.project(), state);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use target_lexicon::*;

    #[test]
    fn test_parse() {
        let target =
            super::Triple::new("x86_64-pc-darwin").expect("this triple is known to target-lexicon");

        let expected_triple = target_lexicon::Triple {
            architecture: Architecture::X86_64,
            vendor: Vendor::Pc,
            operating_system: OperatingSystem::Darwin,
            environment: Environment::Unknown,
            binary_format: BinaryFormat::Macho,
        };

        let actual_triple = match target.inner {
            TripleInner::Lexicon { lexicon_triple, .. } => lexicon_triple,
            TripleInner::Builtin(_) => {
                panic!("should not have been able to parse x86_64-pc-darwin as a builtin");
            }
            #[cfg(feature = "custom")]
            TripleInner::Custom { .. } => {
                panic!("not a custom platform")
            }
        };
        assert_eq!(
            actual_triple, expected_triple,
            "lexicon triple matched correctly"
        );
    }
}