use crate::{
Error, Platform,
errors::{RustcVersionVerboseParseError, TripleParseError},
};
use cfg_expr::{
TargetPredicate,
expr::TargetMatcher,
target_lexicon,
targets::{TargetInfo, get_builtin_target_by_triple},
};
use std::{borrow::Cow, cmp::Ordering, hash, str::FromStr};
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct Triple {
inner: TripleInner,
}
impl Triple {
pub fn new(triple_str: impl Into<Cow<'static, str>>) -> Result<Self, TripleParseError> {
let inner = TripleInner::new(triple_str.into())?;
Ok(Self { inner })
}
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 })
}
pub fn from_rustc_version_verbose(output: impl AsRef<[u8]>) -> Result<Self, Error> {
let output_slice = output.as_ref();
let output = std::str::from_utf8(output_slice).map_err(|_| {
let output_vec = output_slice.to_vec();
Error::RustcVersionVerboseParse(RustcVersionVerboseParseError::InvalidUtf8(
String::from_utf8(output_vec)
.expect_err("we just failed to convert to UTF-8 above"),
))
})?;
let triple_str = output
.lines()
.find_map(|line| line.strip_prefix("host: "))
.ok_or_else(|| {
Error::RustcVersionVerboseParse(RustcVersionVerboseParseError::MissingHostLine {
output: output.to_owned(),
})
})?;
Self::new(triple_str.to_owned()).map_err(Error::UnknownPlatformTriple)
}
#[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::DeserializeJson {
triple: triple_str.to_string(),
input: json.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")]
custom_source: CustomSource::Json(minified_json),
},
})
}
#[cfg(feature = "custom-cfg")]
pub fn new_custom_cfg(
triple_str: impl Into<Cow<'static, str>>,
cfg_text: &str,
) -> Result<Self, crate::errors::CustomTripleCreateError> {
let triple_str = triple_str.into();
let (target_info, _features) =
crate::custom_cfg::parse_cfg_output(triple_str.clone(), cfg_text)?;
Ok(Self {
inner: TripleInner::Custom {
target_info: Box::new(target_info),
#[cfg(feature = "summaries")]
custom_source: CustomSource::Cfg(cfg_text.to_string()),
},
})
}
#[inline]
pub fn as_str(&self) -> &str {
self.inner.as_str()
}
pub fn is_standard(&self) -> bool {
self.inner.is_standard()
}
#[inline]
pub fn is_builtin(&self) -> bool {
self.inner.is_builtin()
}
pub fn is_heuristic(&self) -> bool {
self.inner.is_heuristic()
}
pub fn is_custom(&self) -> bool {
self.inner.is_custom()
}
#[inline]
pub fn eval(&self, platform: &Platform) -> bool {
self.as_str() == platform.triple_str()
}
#[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()
}
#[cfg(feature = "summaries")]
pub(crate) fn custom_cfg_text(&self) -> Option<&str> {
self.inner.custom_cfg_text()
}
}
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 })
}
}
#[derive(Clone, Debug)]
enum TripleInner {
Builtin(&'static TargetInfo),
#[cfg(feature = "custom-cfg")]
Custom {
target_info: Box<cfg_expr::targets::TargetInfo>,
#[cfg(feature = "summaries")]
custom_source: CustomSource,
},
Lexicon {
triple_str: Cow<'static, str>,
lexicon_triple: target_lexicon::Triple,
},
}
#[cfg(all(feature = "custom-cfg", feature = "summaries"))]
#[derive(Clone, Debug)]
pub(crate) enum CustomSource {
#[cfg(feature = "custom")]
Json(String),
Cfg(String),
}
impl TripleInner {
fn new(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));
}
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> {
if let Some(target_info) = get_builtin_target_by_triple(triple_str) {
return Ok(TripleInner::Builtin(target_info));
}
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-cfg")]
TripleInner::Custom { .. } => false,
}
}
fn is_builtin(&self) -> bool {
match self {
TripleInner::Builtin(_) => true,
TripleInner::Lexicon { .. } => false,
#[cfg(feature = "custom-cfg")]
TripleInner::Custom { .. } => false,
}
}
fn is_heuristic(&self) -> bool {
match self {
TripleInner::Builtin(_) => false,
TripleInner::Lexicon { .. } => true,
#[cfg(feature = "custom-cfg")]
TripleInner::Custom { .. } => false,
}
}
fn is_custom(&self) -> bool {
match self {
TripleInner::Builtin(_) | TripleInner::Lexicon { .. } => false,
#[cfg(feature = "custom-cfg")]
TripleInner::Custom { .. } => true,
}
}
fn as_str(&self) -> &str {
match self {
TripleInner::Builtin(target_info) => target_info.triple.as_str(),
#[cfg(feature = "custom-cfg")]
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-cfg")]
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(_) | TripleInner::Lexicon { .. } => None,
#[cfg(feature = "custom-cfg")]
TripleInner::Custom { custom_source, .. } => match custom_source {
#[cfg(feature = "custom")]
CustomSource::Json(json) => Some(json),
CustomSource::Cfg(_) => None,
},
}
}
#[cfg(feature = "summaries")]
pub(crate) fn custom_cfg_text(&self) -> Option<&str> {
match self {
TripleInner::Builtin(_) | TripleInner::Lexicon { .. } => None,
#[cfg(feature = "custom-cfg")]
TripleInner::Custom { custom_source, .. } => match custom_source {
#[cfg(feature = "custom")]
CustomSource::Json(_) => None,
CustomSource::Cfg(cfg_text) => Some(cfg_text),
},
}
}
fn project(&self) -> TripleInnerProjected<'_> {
match self {
TripleInner::Builtin(target_info) => {
TripleInnerProjected::Builtin(target_info.triple.as_str())
}
#[cfg(feature = "custom-cfg")]
TripleInner::Custom { target_info, .. } => TripleInnerProjected::Custom(target_info),
TripleInner::Lexicon { triple_str, .. } => TripleInnerProjected::Lexicon(triple_str),
}
}
}
#[derive(Eq, PartialEq, PartialOrd, Ord, Hash)]
enum TripleInnerProjected<'a> {
Builtin(&'a str),
#[cfg(feature = "custom-cfg")]
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(None),
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-cfg")]
TripleInner::Custom { .. } => {
panic!("not a custom platform")
}
};
assert_eq!(
actual_triple, expected_triple,
"lexicon triple matched correctly"
);
}
#[test]
fn test_parse_rustc_version_verbose() {
let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
let output = std::process::Command::new(rustc)
.arg("-vV")
.output()
.expect("rustc -vV is successful");
if !output.status.success() {
panic!("rustc -vV failed: {output:?}");
}
let triple = super::Triple::from_rustc_version_verbose(output.stdout).unwrap();
assert!(triple.is_standard());
}
}