use crate::{Error, Platform, TargetFeatures};
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, collections::BTreeSet};
impl Platform {
#[inline]
pub fn to_summary(&self) -> PlatformSummary {
PlatformSummary::new(self)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct PlatformSummary {
pub triple: String,
pub target_features: TargetFeaturesSummary,
#[serde(skip_serializing_if = "BTreeSet::is_empty", default)]
pub flags: BTreeSet<String>,
}
impl PlatformSummary {
pub fn new(platform: &Platform) -> Self {
Self {
triple: platform.triple_str().to_string(),
target_features: TargetFeaturesSummary::new(platform.target_features()),
flags: platform.flags().map(|flag| flag.to_string()).collect(),
}
}
pub fn to_platform(&self) -> Result<Platform, Error> {
let mut platform = Platform::new(
self.triple.to_owned(),
self.target_features.to_target_features(),
)?;
platform.add_flags(self.flags.iter().cloned());
Ok(platform)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum TargetFeaturesSummary {
Unknown,
Features(BTreeSet<String>),
All,
}
impl TargetFeaturesSummary {
pub fn new(target_features: &TargetFeatures) -> Self {
match target_features {
TargetFeatures::Unknown => TargetFeaturesSummary::Unknown,
TargetFeatures::Features(features) => TargetFeaturesSummary::Features(
features.iter().map(|feature| feature.to_string()).collect(),
),
TargetFeatures::All => TargetFeaturesSummary::All,
}
}
pub fn to_target_features(&self) -> TargetFeatures {
match self {
TargetFeaturesSummary::Unknown => TargetFeatures::Unknown,
TargetFeaturesSummary::All => TargetFeatures::All,
TargetFeaturesSummary::Features(features) => {
let features = features
.iter()
.map(|feature| Cow::Owned(feature.clone()))
.collect();
TargetFeatures::Features(features)
}
}
}
}
impl Default for TargetFeaturesSummary {
#[inline]
fn default() -> Self {
TargetFeaturesSummary::Unknown
}
}
mod platform_impl {
use super::*;
use serde::Deserializer;
impl<'de> Deserialize<'de> for PlatformSummary {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let d = PlatformSummaryDeserialize::deserialize(deserializer)?;
match d {
PlatformSummaryDeserialize::String(triple) => Ok(PlatformSummary {
triple,
target_features: TargetFeaturesSummary::default(),
flags: BTreeSet::default(),
}),
PlatformSummaryDeserialize::Full {
triple,
target_features,
flags,
} => Ok(PlatformSummary {
triple,
target_features,
flags,
}),
}
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum PlatformSummaryDeserialize {
String(String),
#[serde(rename_all = "kebab-case")]
Full {
triple: String,
#[serde(default)]
target_features: TargetFeaturesSummary,
#[serde(skip_serializing_if = "BTreeSet::is_empty", default)]
flags: BTreeSet<String>,
},
}
}
mod target_features_impl {
use super::*;
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
impl Serialize for TargetFeaturesSummary {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
TargetFeaturesSummary::Unknown => "unknown".serialize(serializer),
TargetFeaturesSummary::All => "all".serialize(serializer),
TargetFeaturesSummary::Features(features) => features.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for TargetFeaturesSummary {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let d = TargetFeaturesDeserialize::deserialize(deserializer)?;
match d {
TargetFeaturesDeserialize::String(target_features) => {
match target_features.as_str() {
"unknown" => Ok(TargetFeaturesSummary::Unknown),
"all" => Ok(TargetFeaturesSummary::All),
other => Err(D::Error::custom(format!(
"unknown string for target features: {}",
other,
))),
}
}
TargetFeaturesDeserialize::List(target_features) => {
Ok(TargetFeaturesSummary::Features(target_features))
}
}
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum TargetFeaturesDeserialize {
String(String),
List(BTreeSet<String>),
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::vec_init_then_push)]
use super::*;
#[test]
fn platform_deserialize_valid() {
#[derive(Debug, Deserialize, Serialize, Eq, PartialEq)]
struct Wrapper {
platform: PlatformSummary,
}
let mut valid = vec![];
valid.push((
r#"platform = "x86_64-unknown-linux-gnu""#,
PlatformSummary {
triple: "x86_64-unknown-linux-gnu".into(),
target_features: TargetFeaturesSummary::Unknown,
flags: BTreeSet::new(),
},
));
valid.push((
r#"platform = { triple = "x86_64-unknown-linux-gnu" }"#,
PlatformSummary {
triple: "x86_64-unknown-linux-gnu".into(),
target_features: TargetFeaturesSummary::Unknown,
flags: BTreeSet::new(),
},
));
valid.push((
r#"platform = { triple = "x86_64-unknown-linux-gnu", target-features = "unknown" }"#,
PlatformSummary {
triple: "x86_64-unknown-linux-gnu".into(),
target_features: TargetFeaturesSummary::Unknown,
flags: BTreeSet::new(),
},
));
valid.push((
r#"platform = { triple = "x86_64-unknown-linux-gnu", target-features = "all" }"#,
PlatformSummary {
triple: "x86_64-unknown-linux-gnu".into(),
target_features: TargetFeaturesSummary::All,
flags: BTreeSet::new(),
},
));
valid.push((
r#"platform = { triple = "x86_64-unknown-linux-gnu", target-features = [] }"#,
PlatformSummary {
triple: "x86_64-unknown-linux-gnu".into(),
target_features: TargetFeaturesSummary::Features(BTreeSet::new()),
flags: BTreeSet::new(),
},
));
let mut flags = BTreeSet::new();
flags.insert("cargo_web".to_owned());
valid.push((
r#"platform = { triple = "x86_64-unknown-linux-gnu", flags = ["cargo_web"] }"#,
PlatformSummary {
triple: "x86_64-unknown-linux-gnu".into(),
target_features: TargetFeaturesSummary::Unknown,
flags,
},
));
for (input, expected) in valid {
let actual: Wrapper = toml::from_str(input)
.unwrap_or_else(|err| panic!("input {} is valid: {}", input, err));
assert_eq!(actual.platform, expected, "for input: {}", input);
let serialized = toml::to_string(&actual).expect("serialized correctly");
let actual_2: Wrapper = toml::from_str(&serialized)
.unwrap_or_else(|err| panic!("serialized input: {} is valid: {}", input, err));
assert_eq!(actual, actual_2, "for input: {}", input);
}
}
}
#[cfg(all(test, feature = "proptest1"))]
mod proptests {
use super::*;
use proptest::prelude::*;
use std::collections::HashSet;
proptest! {
#[test]
fn summary_roundtrip(platform in Platform::strategy(any::<TargetFeatures>())) {
let summary = PlatformSummary::new(&platform);
let serialized = toml::ser::to_string(&summary).expect("serialization succeeded");
let deserialized: PlatformSummary = toml::from_str(&serialized).expect("deserialization succeeded");
assert_eq!(summary, deserialized, "summary and deserialized should match");
let platform2 = deserialized.to_platform().expect("conversion to Platform succeeded");
assert_eq!(platform.triple_str(), platform2.triple_str(), "triples match");
assert_eq!(platform.target_features(), platform2.target_features(), "target features match");
assert_eq!(platform.flags().collect::<HashSet<_>>(), platform2.flags().collect::<HashSet<_>>(), "flags match");
}
}
}