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 #[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 #[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 #[inline]
173 pub fn as_str(&self) -> &str {
174 self.inner.as_str()
175 }
176
177 pub fn is_standard(&self) -> bool {
191 self.inner.is_standard()
192 }
193
194 #[inline]
206 pub fn is_builtin(&self) -> bool {
207 self.inner.is_builtin()
208 }
209
210 pub fn is_heuristic(&self) -> bool {
226 self.inner.is_heuristic()
227 }
228
229 pub fn is_custom(&self) -> bool {
235 self.inner.is_custom()
236 }
237
238 #[inline]
243 pub fn eval(&self, platform: &Platform) -> bool {
244 self.as_str() == platform.triple_str()
245 }
246
247 #[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#[derive(Clone, Debug)]
275enum TripleInner {
276 Builtin(&'static TargetInfo),
278
279 #[cfg(feature = "custom-cfg")]
281 Custom {
282 target_info: Box<cfg_expr::targets::TargetInfo>,
283 #[cfg(feature = "summaries")]
286 custom_source: CustomSource,
287 },
288
289 Lexicon {
291 triple_str: Cow<'static, str>,
292 lexicon_triple: target_lexicon::Triple,
293 },
294}
295
296#[cfg(all(feature = "custom-cfg", feature = "summaries"))]
299#[derive(Clone, Debug)]
300pub(crate) enum CustomSource {
301 #[cfg(feature = "custom")]
303 Json(String),
304 Cfg(String),
307}
308
309impl TripleInner {
310 fn new(triple_str: Cow<'static, str>) -> Result<Self, TripleParseError> {
311 if let Some(target_info) = get_builtin_target_by_triple(&triple_str) {
313 return Ok(TripleInner::Builtin(target_info));
314 }
315
316 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 if let Some(target_info) = get_builtin_target_by_triple(triple_str) {
336 return Ok(TripleInner::Builtin(target_info));
337 }
338
339 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#[derive(Eq, PartialEq, PartialOrd, Ord, Hash)]
444enum TripleInnerProjected<'a> {
445 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}