1use crate::{Error, Platform, TargetFeatures};
12use serde::{Deserialize, Serialize};
13use std::{borrow::Cow, collections::BTreeSet};
14
15impl Platform {
16 #[inline]
20 pub fn to_summary(&self) -> PlatformSummary {
21 PlatformSummary::from_platform(self)
22 }
23}
24
25#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
31#[serde(rename_all = "kebab-case")]
32#[non_exhaustive]
33pub struct PlatformSummary {
34 pub triple: String,
36
37 #[serde(skip_serializing_if = "Option::is_none", default)]
39 pub custom_json: Option<String>,
40
41 pub target_features: TargetFeaturesSummary,
43
44 #[serde(skip_serializing_if = "BTreeSet::is_empty", default)]
46 pub flags: BTreeSet<String>,
47}
48
49impl PlatformSummary {
50 pub fn new(triple_str: impl Into<String>) -> Self {
58 Self {
59 triple: triple_str.into(),
60 custom_json: None,
61 target_features: TargetFeaturesSummary::Unknown,
62 flags: BTreeSet::new(),
63 }
64 }
65
66 pub fn with_custom_json(mut self, custom_json: impl Into<String>) -> Self {
72 self.custom_json = Some(custom_json.into());
73 self
74 }
75
76 pub fn with_target_features(mut self, target_features: TargetFeaturesSummary) -> Self {
78 self.target_features = target_features;
79 self
80 }
81
82 pub fn with_added_flags(mut self, flags: impl IntoIterator<Item = impl Into<String>>) -> Self {
84 self.flags.extend(flags.into_iter().map(|flag| flag.into()));
85 self
86 }
87
88 pub fn from_platform(platform: &Platform) -> Self {
90 Self {
91 triple: platform.triple_str().to_string(),
92 custom_json: platform.custom_json().map(|s| s.to_owned()),
93 target_features: TargetFeaturesSummary::new(platform.target_features()),
94 flags: platform.flags().map(|flag| flag.to_string()).collect(),
95 }
96 }
97
98 pub fn to_platform(&self) -> Result<Platform, Error> {
102 #[allow(unused_variables)] let mut platform = if let Some(json) = &self.custom_json {
104 #[cfg(not(feature = "custom"))]
105 return Err(Error::CustomPlatformCreate(
106 crate::errors::CustomTripleCreateError::Unavailable,
107 ));
108
109 #[cfg(feature = "custom")]
110 Platform::new_custom(
111 self.triple.to_owned(),
112 json,
113 self.target_features.to_target_features(),
114 )?
115 } else {
116 Platform::new(
117 self.triple.to_owned(),
118 self.target_features.to_target_features(),
119 )?
120 };
121
122 platform.add_flags(self.flags.iter().cloned());
123 Ok(platform)
124 }
125}
126
127#[derive(Clone, Debug, Eq, PartialEq)]
133#[non_exhaustive]
134#[derive(Default)]
135pub enum TargetFeaturesSummary {
136 #[default]
140 Unknown,
141 Features(BTreeSet<String>),
143 All,
145}
146
147impl TargetFeaturesSummary {
148 pub fn new(target_features: &TargetFeatures) -> Self {
150 match target_features {
151 TargetFeatures::Unknown => TargetFeaturesSummary::Unknown,
152 TargetFeatures::Features(features) => TargetFeaturesSummary::Features(
153 features.iter().map(|feature| feature.to_string()).collect(),
154 ),
155 TargetFeatures::All => TargetFeaturesSummary::All,
156 }
157 }
158
159 pub fn to_target_features(&self) -> TargetFeatures {
161 match self {
162 TargetFeaturesSummary::Unknown => TargetFeatures::Unknown,
163 TargetFeaturesSummary::All => TargetFeatures::All,
164 TargetFeaturesSummary::Features(features) => {
165 let features = features
166 .iter()
167 .map(|feature| Cow::Owned(feature.clone()))
168 .collect();
169 TargetFeatures::Features(features)
170 }
171 }
172 }
173}
174
175mod platform_impl {
176 use super::*;
177 use serde::Deserializer;
178
179 impl<'de> Deserialize<'de> for PlatformSummary {
180 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
181 where
182 D: Deserializer<'de>,
183 {
184 let d = PlatformSummaryDeserialize::deserialize(deserializer)?;
185 match d {
186 PlatformSummaryDeserialize::String(triple) => Ok(PlatformSummary {
187 triple,
188 custom_json: None,
189 target_features: TargetFeaturesSummary::default(),
190 flags: BTreeSet::default(),
191 }),
192 PlatformSummaryDeserialize::Full {
193 triple,
194 custom_json,
195 target_features,
196 flags,
197 } => Ok(PlatformSummary {
198 triple,
199 custom_json,
200 target_features,
201 flags,
202 }),
203 }
204 }
205 }
206
207 #[derive(Deserialize)]
208 #[serde(untagged)]
209 enum PlatformSummaryDeserialize {
210 String(String),
211 #[serde(rename_all = "kebab-case")]
212 Full {
213 triple: String,
214 #[serde(default)]
215 custom_json: Option<String>,
216 #[serde(default)]
218 target_features: TargetFeaturesSummary,
219 #[serde(skip_serializing_if = "BTreeSet::is_empty", default)]
221 flags: BTreeSet<String>,
222 },
223 }
224}
225
226mod target_features_impl {
227 use super::*;
228 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
229
230 impl Serialize for TargetFeaturesSummary {
231 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
232 where
233 S: Serializer,
234 {
235 match self {
236 TargetFeaturesSummary::Unknown => "unknown".serialize(serializer),
237 TargetFeaturesSummary::All => "all".serialize(serializer),
238 TargetFeaturesSummary::Features(features) => features.serialize(serializer),
239 }
240 }
241 }
242
243 impl<'de> Deserialize<'de> for TargetFeaturesSummary {
244 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
245 where
246 D: Deserializer<'de>,
247 {
248 let d = TargetFeaturesDeserialize::deserialize(deserializer)?;
249 match d {
250 TargetFeaturesDeserialize::String(target_features) => {
251 match target_features.as_str() {
252 "unknown" => Ok(TargetFeaturesSummary::Unknown),
253 "all" => Ok(TargetFeaturesSummary::All),
254 other => Err(D::Error::custom(format!(
255 "unknown string for target features: {}",
256 other,
257 ))),
258 }
259 }
260 TargetFeaturesDeserialize::List(target_features) => {
261 Ok(TargetFeaturesSummary::Features(target_features))
262 }
263 }
264 }
265 }
266
267 #[derive(Deserialize)]
268 #[serde(untagged)]
269 enum TargetFeaturesDeserialize {
270 String(String),
271 List(BTreeSet<String>),
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 #![allow(clippy::vec_init_then_push)]
278
279 use super::*;
280
281 #[test]
282 fn platform_deserialize_valid() {
283 #[derive(Debug, Deserialize, Serialize, Eq, PartialEq)]
285 struct Wrapper {
286 platform: PlatformSummary,
287 }
288
289 let mut valid = vec![];
290 valid.push((
291 r#"platform = "x86_64-unknown-linux-gnu""#,
292 PlatformSummary {
293 triple: "x86_64-unknown-linux-gnu".into(),
294 custom_json: None,
295 target_features: TargetFeaturesSummary::Unknown,
296 flags: BTreeSet::new(),
297 },
298 ));
299 valid.push((
300 r#"platform = { triple = "x86_64-unknown-linux-gnu" }"#,
301 PlatformSummary {
302 triple: "x86_64-unknown-linux-gnu".into(),
303 custom_json: None,
304 target_features: TargetFeaturesSummary::Unknown,
305 flags: BTreeSet::new(),
306 },
307 ));
308 valid.push((
309 r#"platform = { triple = "x86_64-unknown-linux-gnu", target-features = "unknown" }"#,
310 PlatformSummary {
311 triple: "x86_64-unknown-linux-gnu".into(),
312 custom_json: None,
313 target_features: TargetFeaturesSummary::Unknown,
314 flags: BTreeSet::new(),
315 },
316 ));
317 valid.push((
318 r#"platform = { triple = "x86_64-unknown-linux-gnu", target-features = "all" }"#,
319 PlatformSummary {
320 triple: "x86_64-unknown-linux-gnu".into(),
321 custom_json: None,
322 target_features: TargetFeaturesSummary::All,
323 flags: BTreeSet::new(),
324 },
325 ));
326 valid.push((
327 r#"platform = { triple = "x86_64-unknown-linux-gnu", target-features = [] }"#,
328 PlatformSummary {
329 triple: "x86_64-unknown-linux-gnu".into(),
330 custom_json: None,
331 target_features: TargetFeaturesSummary::Features(BTreeSet::new()),
332 flags: BTreeSet::new(),
333 },
334 ));
335
336 let custom_json = r#"{"arch":"x86_64","target-pointer-width":"64","llvm-target":"x86_64-unknown-haiku","data-layout":"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128","os":"haiku","abi":null,"env":null,"vendor":null,"families":[],"endian":"little","min-atomic-width":null,"max-atomic-width":64,"panic-strategy":"unwind"}"#;
337 let toml = format!(
338 r#"platform = {{ triple = "x86_64-unknown-haiku", custom-json = '{custom_json}' }}"#
339 );
340
341 valid.push((
342 &toml,
343 PlatformSummary {
344 triple: "x86_64-unknown-haiku".into(),
345 custom_json: Some(custom_json.to_owned()),
346 target_features: TargetFeaturesSummary::Unknown,
347 flags: BTreeSet::new(),
348 },
349 ));
350
351 let mut flags = BTreeSet::new();
352 flags.insert("cargo_web".to_owned());
353 valid.push((
354 r#"platform = { triple = "x86_64-unknown-linux-gnu", flags = ["cargo_web"] }"#,
355 PlatformSummary {
356 triple: "x86_64-unknown-linux-gnu".into(),
357 custom_json: None,
358 target_features: TargetFeaturesSummary::Unknown,
359 flags,
360 },
361 ));
362
363 for (input, expected) in valid {
364 let actual: Wrapper = toml::from_str(input)
365 .unwrap_or_else(|err| panic!("input {} is valid: {}", input, err));
366 assert_eq!(actual.platform, expected, "for input: {}", input);
367
368 let serialized = toml::to_string(&actual).expect("serialized correctly");
370 let actual_2: Wrapper = toml::from_str(&serialized)
371 .unwrap_or_else(|err| panic!("serialized input: {} is valid: {}", input, err));
372 assert_eq!(actual, actual_2, "for input: {}", input);
373
374 if actual.platform.custom_json.is_some() {
376 #[cfg(feature = "custom")]
377 {
378 let platform = actual
379 .platform
380 .to_platform()
381 .expect("custom platform parsed successfully");
382 assert!(platform.is_custom(), "this is a custom platform");
383 }
384
385 #[cfg(not(feature = "custom"))]
386 {
387 use crate::errors::CustomTripleCreateError;
388
389 let error = actual
390 .platform
391 .to_platform()
392 .expect_err("custom platforms are disabled");
393 assert!(matches!(
394 error,
395 Error::CustomPlatformCreate(CustomTripleCreateError::Unavailable)
396 ));
397 }
398 }
399 }
400 }
401}
402
403#[cfg(all(test, feature = "proptest1"))]
404mod proptests {
405 use super::*;
406 use proptest::prelude::*;
407 use std::collections::HashSet;
408
409 proptest! {
410 #[test]
411 fn summary_roundtrip(platform in Platform::strategy(any::<TargetFeatures>())) {
412 let summary = PlatformSummary::from_platform(&platform);
413 let serialized = toml::ser::to_string(&summary).expect("serialization succeeded");
414
415 let deserialized: PlatformSummary = toml::from_str(&serialized).expect("deserialization succeeded");
416 assert_eq!(summary, deserialized, "summary and deserialized should match");
417 let platform2 = deserialized.to_platform().expect("conversion to Platform succeeded");
418
419 assert_eq!(platform.triple_str(), platform2.triple_str(), "triples match");
420 assert_eq!(platform.target_features(), platform2.target_features(), "target features match");
421 assert_eq!(platform.flags().collect::<HashSet<_>>(), platform2.flags().collect::<HashSet<_>>(), "flags match");
422 }
423 }
424}