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 #[serde(skip_serializing_if = "Option::is_none", default)]
43 pub custom_cfg: Option<String>,
44
45 pub target_features: TargetFeaturesSummary,
47
48 #[serde(skip_serializing_if = "BTreeSet::is_empty", default)]
50 pub flags: BTreeSet<String>,
51}
52
53impl PlatformSummary {
54 pub fn new(triple_str: impl Into<String>) -> Self {
63 Self {
64 triple: triple_str.into(),
65 custom_json: None,
66 custom_cfg: None,
67 target_features: TargetFeaturesSummary::Unknown,
68 flags: BTreeSet::new(),
69 }
70 }
71
72 pub fn with_custom_json(mut self, custom_json: impl Into<String>) -> Self {
83 self.custom_json = Some(custom_json.into());
84 self.custom_cfg = None;
85 self
86 }
87
88 pub fn with_custom_cfg(mut self, custom_cfg: impl Into<String>) -> Self {
94 self.custom_cfg = Some(custom_cfg.into());
95 self.custom_json = None;
96 self
97 }
98
99 pub fn with_target_features(mut self, target_features: TargetFeaturesSummary) -> Self {
101 self.target_features = target_features;
102 self
103 }
104
105 pub fn with_added_flags(mut self, flags: impl IntoIterator<Item = impl Into<String>>) -> Self {
107 self.flags.extend(flags.into_iter().map(|flag| flag.into()));
108 self
109 }
110
111 pub fn from_platform(platform: &Platform) -> Self {
113 Self {
114 triple: platform.triple_str().to_string(),
115 custom_json: platform.custom_json().map(|s| s.to_owned()),
116 custom_cfg: platform.custom_cfg_text().map(|s| s.to_owned()),
117 target_features: TargetFeaturesSummary::new(platform.target_features()),
118 flags: platform.flags().map(|flag| flag.to_string()).collect(),
119 }
120 }
121
122 pub fn to_platform(&self) -> Result<Platform, Error> {
126 if self.custom_json.is_some() && self.custom_cfg.is_some() {
127 return Err(Error::CustomPlatformCreate(
128 crate::errors::CustomTripleCreateError::ConflictingCustomPlatformSources {
129 triple: self.triple.clone(),
130 },
131 ));
132 }
133
134 #[allow(unused_variables)] let mut platform = if let Some(json) = &self.custom_json {
136 #[cfg(not(feature = "custom"))]
137 return Err(Error::CustomPlatformCreate(
138 crate::errors::CustomTripleCreateError::CustomJsonUnavailable,
139 ));
140
141 #[cfg(feature = "custom")]
142 Platform::new_custom(
143 self.triple.to_owned(),
144 json,
145 self.target_features.to_target_features(),
146 )?
147 } else if let Some(cfg_text) = &self.custom_cfg {
148 #[cfg(not(feature = "custom-cfg"))]
149 return Err(Error::CustomPlatformCreate(
150 crate::errors::CustomTripleCreateError::CustomCfgUnavailable,
151 ));
152
153 #[cfg(feature = "custom-cfg")]
154 Platform::new_custom_cfg(
155 self.triple.to_owned(),
156 cfg_text,
157 self.target_features.to_target_features(),
158 )?
159 } else {
160 Platform::new(
161 self.triple.to_owned(),
162 self.target_features.to_target_features(),
163 )?
164 };
165
166 platform.add_flags(self.flags.iter().cloned());
167 Ok(platform)
168 }
169}
170
171#[derive(Clone, Debug, Eq, PartialEq)]
177#[non_exhaustive]
178#[derive(Default)]
179pub enum TargetFeaturesSummary {
180 #[default]
184 Unknown,
185 Features(BTreeSet<String>),
187 All,
189}
190
191impl TargetFeaturesSummary {
192 pub fn new(target_features: &TargetFeatures) -> Self {
194 match target_features {
195 TargetFeatures::Unknown => TargetFeaturesSummary::Unknown,
196 TargetFeatures::Features(features) => TargetFeaturesSummary::Features(
197 features.iter().map(|feature| feature.to_string()).collect(),
198 ),
199 TargetFeatures::All => TargetFeaturesSummary::All,
200 }
201 }
202
203 pub fn to_target_features(&self) -> TargetFeatures {
205 match self {
206 TargetFeaturesSummary::Unknown => TargetFeatures::Unknown,
207 TargetFeaturesSummary::All => TargetFeatures::All,
208 TargetFeaturesSummary::Features(features) => {
209 let features = features
210 .iter()
211 .map(|feature| Cow::Owned(feature.clone()))
212 .collect();
213 TargetFeatures::Features(features)
214 }
215 }
216 }
217}
218
219mod platform_impl {
220 use super::*;
221 use serde::Deserializer;
222
223 impl<'de> Deserialize<'de> for PlatformSummary {
224 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
225 where
226 D: Deserializer<'de>,
227 {
228 let d = PlatformSummaryDeserialize::deserialize(deserializer)?;
229 match d {
230 PlatformSummaryDeserialize::String(triple) => Ok(PlatformSummary {
231 triple,
232 custom_json: None,
233 custom_cfg: None,
234 target_features: TargetFeaturesSummary::default(),
235 flags: BTreeSet::default(),
236 }),
237 PlatformSummaryDeserialize::Full {
238 triple,
239 custom_json,
240 custom_cfg,
241 target_features,
242 flags,
243 } => Ok(PlatformSummary {
244 triple,
245 custom_json,
246 custom_cfg,
247 target_features,
248 flags,
249 }),
250 }
251 }
252 }
253
254 #[derive(Deserialize)]
255 #[serde(untagged)]
256 enum PlatformSummaryDeserialize {
257 String(String),
258 #[serde(rename_all = "kebab-case")]
259 Full {
260 triple: String,
261 #[serde(default)]
262 custom_json: Option<String>,
263 #[serde(default)]
264 custom_cfg: Option<String>,
265 #[serde(default)]
267 target_features: TargetFeaturesSummary,
268 #[serde(skip_serializing_if = "BTreeSet::is_empty", default)]
270 flags: BTreeSet<String>,
271 },
272 }
273}
274
275mod target_features_impl {
276 use super::*;
277 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
278
279 impl Serialize for TargetFeaturesSummary {
280 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
281 where
282 S: Serializer,
283 {
284 match self {
285 TargetFeaturesSummary::Unknown => "unknown".serialize(serializer),
286 TargetFeaturesSummary::All => "all".serialize(serializer),
287 TargetFeaturesSummary::Features(features) => features.serialize(serializer),
288 }
289 }
290 }
291
292 impl<'de> Deserialize<'de> for TargetFeaturesSummary {
293 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
294 where
295 D: Deserializer<'de>,
296 {
297 let d = TargetFeaturesDeserialize::deserialize(deserializer)?;
298 match d {
299 TargetFeaturesDeserialize::String(target_features) => {
300 match target_features.as_str() {
301 "unknown" => Ok(TargetFeaturesSummary::Unknown),
302 "all" => Ok(TargetFeaturesSummary::All),
303 other => Err(D::Error::custom(format!(
304 "unknown string for target features: {other}",
305 ))),
306 }
307 }
308 TargetFeaturesDeserialize::List(target_features) => {
309 Ok(TargetFeaturesSummary::Features(target_features))
310 }
311 }
312 }
313 }
314
315 #[derive(Deserialize)]
316 #[serde(untagged)]
317 enum TargetFeaturesDeserialize {
318 String(String),
319 List(BTreeSet<String>),
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 #![allow(clippy::vec_init_then_push)]
326
327 use super::*;
328
329 #[test]
330 fn platform_deserialize_valid() {
331 #[derive(Debug, Deserialize, Serialize, Eq, PartialEq)]
333 struct Wrapper {
334 platform: PlatformSummary,
335 }
336
337 let mut valid = vec![];
338 valid.push((
339 r#"platform = "x86_64-unknown-linux-gnu""#,
340 PlatformSummary {
341 triple: "x86_64-unknown-linux-gnu".into(),
342 custom_json: None,
343 custom_cfg: None,
344 target_features: TargetFeaturesSummary::Unknown,
345 flags: BTreeSet::new(),
346 },
347 ));
348 valid.push((
349 r#"platform = { triple = "x86_64-unknown-linux-gnu" }"#,
350 PlatformSummary {
351 triple: "x86_64-unknown-linux-gnu".into(),
352 custom_json: None,
353 custom_cfg: None,
354 target_features: TargetFeaturesSummary::Unknown,
355 flags: BTreeSet::new(),
356 },
357 ));
358 valid.push((
359 r#"platform = { triple = "x86_64-unknown-linux-gnu", target-features = "unknown" }"#,
360 PlatformSummary {
361 triple: "x86_64-unknown-linux-gnu".into(),
362 custom_json: None,
363 custom_cfg: None,
364 target_features: TargetFeaturesSummary::Unknown,
365 flags: BTreeSet::new(),
366 },
367 ));
368 valid.push((
369 r#"platform = { triple = "x86_64-unknown-linux-gnu", target-features = "all" }"#,
370 PlatformSummary {
371 triple: "x86_64-unknown-linux-gnu".into(),
372 custom_json: None,
373 custom_cfg: None,
374 target_features: TargetFeaturesSummary::All,
375 flags: BTreeSet::new(),
376 },
377 ));
378 valid.push((
379 r#"platform = { triple = "x86_64-unknown-linux-gnu", target-features = [] }"#,
380 PlatformSummary {
381 triple: "x86_64-unknown-linux-gnu".into(),
382 custom_json: None,
383 custom_cfg: None,
384 target_features: TargetFeaturesSummary::Features(BTreeSet::new()),
385 flags: BTreeSet::new(),
386 },
387 ));
388
389 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"}"#;
390 let toml = format!(
391 r#"platform = {{ triple = "x86_64-unknown-haiku", custom-json = '{custom_json}' }}"#
392 );
393
394 valid.push((
395 &toml,
396 PlatformSummary {
397 triple: "x86_64-unknown-haiku".into(),
398 custom_json: Some(custom_json.to_owned()),
399 custom_cfg: None,
400 target_features: TargetFeaturesSummary::Unknown,
401 flags: BTreeSet::new(),
402 },
403 ));
404
405 let mut flags = BTreeSet::new();
406 flags.insert("cargo_web".to_owned());
407 valid.push((
408 r#"platform = { triple = "x86_64-unknown-linux-gnu", flags = ["cargo_web"] }"#,
409 PlatformSummary {
410 triple: "x86_64-unknown-linux-gnu".into(),
411 custom_json: None,
412 custom_cfg: None,
413 target_features: TargetFeaturesSummary::Unknown,
414 flags,
415 },
416 ));
417
418 let custom_cfg = indoc::indoc! {r#"
419 panic="unwind"
420 target_arch="x86_64"
421 target_endian="little"
422 target_env="gnu"
423 target_family="unix"
424 target_os="linux"
425 target_pointer_width="64"
426 target_vendor="unknown"
427 "#};
428 let toml_cfg = format!(
429 "[platform]\n\
430 triple = \"my-custom-linux\"\n\
431 custom-cfg = '''\n\
432 {custom_cfg}'''"
433 );
434 valid.push((
435 &toml_cfg,
436 PlatformSummary {
437 triple: "my-custom-linux".into(),
438 custom_json: None,
439 custom_cfg: Some(custom_cfg.to_owned()),
440 target_features: TargetFeaturesSummary::Unknown,
441 flags: BTreeSet::new(),
442 },
443 ));
444
445 for (input, expected) in valid {
446 let actual: Wrapper =
447 toml::from_str(input).unwrap_or_else(|err| panic!("input {input} is valid: {err}"));
448 assert_eq!(actual.platform, expected, "for input: {input}");
449
450 let serialized = toml::to_string(&actual).expect("serialized correctly");
452 let actual_2: Wrapper = toml::from_str(&serialized)
453 .unwrap_or_else(|err| panic!("serialized input: {input} is valid: {err}"));
454 assert_eq!(actual, actual_2, "for input: {input}");
455
456 if actual.platform.custom_json.is_some() {
458 #[cfg(feature = "custom")]
459 {
460 let platform = actual
461 .platform
462 .to_platform()
463 .expect("custom platform parsed successfully");
464 assert!(platform.is_custom(), "this is a custom platform");
465 }
466
467 #[cfg(not(feature = "custom"))]
468 {
469 use crate::errors::CustomTripleCreateError;
470
471 let error = actual
472 .platform
473 .to_platform()
474 .expect_err("custom platforms are disabled");
475 assert!(matches!(
476 error,
477 Error::CustomPlatformCreate(CustomTripleCreateError::CustomJsonUnavailable)
478 ));
479 }
480 }
481
482 if actual.platform.custom_cfg.is_some() {
484 #[cfg(feature = "custom-cfg")]
485 {
486 let platform = actual
487 .platform
488 .to_platform()
489 .expect("custom cfg platform parsed successfully");
490 assert!(platform.is_custom(), "this is a custom platform");
491 }
492
493 #[cfg(not(feature = "custom-cfg"))]
494 {
495 use crate::errors::CustomTripleCreateError;
496
497 let error = actual
498 .platform
499 .to_platform()
500 .expect_err("custom cfg platforms are disabled");
501 assert!(matches!(
502 error,
503 Error::CustomPlatformCreate(CustomTripleCreateError::CustomCfgUnavailable)
504 ));
505 }
506 }
507 }
508 }
509}
510
511#[cfg(all(test, feature = "proptest1"))]
512mod proptests {
513 use super::*;
514 use proptest::prelude::*;
515 use std::collections::HashSet;
516
517 proptest! {
518 #[test]
519 fn summary_roundtrip(platform in Platform::strategy(any::<TargetFeatures>())) {
520 let summary = PlatformSummary::from_platform(&platform);
521 let serialized = toml::ser::to_string(&summary).expect("serialization succeeded");
522
523 let deserialized: PlatformSummary = toml::from_str(&serialized).expect("deserialization succeeded");
524 assert_eq!(summary, deserialized, "summary and deserialized should match");
525 let platform2 = deserialized.to_platform().expect("conversion to Platform succeeded");
526
527 assert_eq!(platform.triple_str(), platform2.triple_str(), "triples match");
528 assert_eq!(platform.target_features(), platform2.target_features(), "target features match");
529 assert_eq!(platform.flags().collect::<HashSet<_>>(), platform2.flags().collect::<HashSet<_>>(), "flags match");
530 }
531 }
532}