1use 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#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
54pub struct Triple {
55 inner: TripleInner,
56}
57
58impl Triple {
59 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 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 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 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 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 Self::new(triple_str.to_owned()).map_err(Error::UnknownPlatformTriple)
116 }
117
118 #[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 #[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 #[inline]
168 pub fn as_str(&self) -> &str {
169 self.inner.as_str()
170 }
171
172 pub fn is_standard(&self) -> bool {
186 self.inner.is_standard()
187 }
188
189 #[inline]
201 pub fn is_builtin(&self) -> bool {
202 self.inner.is_builtin()
203 }
204
205 pub fn is_heuristic(&self) -> bool {
221 self.inner.is_heuristic()
222 }
223
224 pub fn is_custom(&self) -> bool {
233 self.inner.is_custom()
234 }
235
236 pub fn custom_source(&self) -> Option<CustomSource<'_>> {
240 self.inner.custom_source()
241 }
242
243 #[inline]
248 pub fn eval(&self, platform: &Platform) -> bool {
249 self.as_str() == platform.triple_str()
250 }
251
252 #[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#[derive(Clone, Debug)]
270enum TripleInner {
271 Builtin(&'static TargetInfo),
273
274 #[cfg(feature = "custom-cfg")]
276 Custom {
277 target_info: Box<cfg_expr::targets::TargetInfo>,
278 custom_source: OwnedCustomSource,
279 },
280
281 Lexicon {
283 triple_str: Cow<'static, str>,
284 lexicon_triple: target_lexicon::Triple,
285 },
286}
287
288#[cfg(feature = "custom-cfg")]
290#[derive(Clone, Debug)]
291enum OwnedCustomSource {
292 #[cfg(feature = "custom")]
294 Json(String),
295 Cfg(String),
298}
299
300impl TripleInner {
301 fn new(triple_str: Cow<'static, str>) -> Result<Self, TripleParseError> {
302 if let Some(target_info) = get_builtin_target_by_triple(&triple_str) {
304 return Ok(TripleInner::Builtin(target_info));
305 }
306
307 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 if let Some(target_info) = get_builtin_target_by_triple(triple_str) {
327 return Ok(TripleInner::Builtin(target_info));
328 }
329
330 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
425#[non_exhaustive]
426pub enum CustomSource<'a> {
427 Json(&'a str),
431
432 Cfg(&'a str),
434}
435
436#[derive(Eq, PartialEq, PartialOrd, Ord, Hash)]
438enum TripleInnerProjected<'a> {
439 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}