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: {other}",
256 ))),
257 }
258 }
259 TargetFeaturesDeserialize::List(target_features) => {
260 Ok(TargetFeaturesSummary::Features(target_features))
261 }
262 }
263 }
264 }
265
266 #[derive(Deserialize)]
267 #[serde(untagged)]
268 enum TargetFeaturesDeserialize {
269 String(String),
270 List(BTreeSet<String>),
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 #![allow(clippy::vec_init_then_push)]
277
278 use super::*;
279
280 #[test]
281 fn platform_deserialize_valid() {
282 #[derive(Debug, Deserialize, Serialize, Eq, PartialEq)]
284 struct Wrapper {
285 platform: PlatformSummary,
286 }
287
288 let mut valid = vec![];
289 valid.push((
290 r#"platform = "x86_64-unknown-linux-gnu""#,
291 PlatformSummary {
292 triple: "x86_64-unknown-linux-gnu".into(),
293 custom_json: None,
294 target_features: TargetFeaturesSummary::Unknown,
295 flags: BTreeSet::new(),
296 },
297 ));
298 valid.push((
299 r#"platform = { triple = "x86_64-unknown-linux-gnu" }"#,
300 PlatformSummary {
301 triple: "x86_64-unknown-linux-gnu".into(),
302 custom_json: None,
303 target_features: TargetFeaturesSummary::Unknown,
304 flags: BTreeSet::new(),
305 },
306 ));
307 valid.push((
308 r#"platform = { triple = "x86_64-unknown-linux-gnu", target-features = "unknown" }"#,
309 PlatformSummary {
310 triple: "x86_64-unknown-linux-gnu".into(),
311 custom_json: None,
312 target_features: TargetFeaturesSummary::Unknown,
313 flags: BTreeSet::new(),
314 },
315 ));
316 valid.push((
317 r#"platform = { triple = "x86_64-unknown-linux-gnu", target-features = "all" }"#,
318 PlatformSummary {
319 triple: "x86_64-unknown-linux-gnu".into(),
320 custom_json: None,
321 target_features: TargetFeaturesSummary::All,
322 flags: BTreeSet::new(),
323 },
324 ));
325 valid.push((
326 r#"platform = { triple = "x86_64-unknown-linux-gnu", target-features = [] }"#,
327 PlatformSummary {
328 triple: "x86_64-unknown-linux-gnu".into(),
329 custom_json: None,
330 target_features: TargetFeaturesSummary::Features(BTreeSet::new()),
331 flags: BTreeSet::new(),
332 },
333 ));
334
335 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"}"#;
336 let toml = format!(
337 r#"platform = {{ triple = "x86_64-unknown-haiku", custom-json = '{custom_json}' }}"#
338 );
339
340 valid.push((
341 &toml,
342 PlatformSummary {
343 triple: "x86_64-unknown-haiku".into(),
344 custom_json: Some(custom_json.to_owned()),
345 target_features: TargetFeaturesSummary::Unknown,
346 flags: BTreeSet::new(),
347 },
348 ));
349
350 let mut flags = BTreeSet::new();
351 flags.insert("cargo_web".to_owned());
352 valid.push((
353 r#"platform = { triple = "x86_64-unknown-linux-gnu", flags = ["cargo_web"] }"#,
354 PlatformSummary {
355 triple: "x86_64-unknown-linux-gnu".into(),
356 custom_json: None,
357 target_features: TargetFeaturesSummary::Unknown,
358 flags,
359 },
360 ));
361
362 for (input, expected) in valid {
363 let actual: Wrapper =
364 toml::from_str(input).unwrap_or_else(|err| panic!("input {input} is valid: {err}"));
365 assert_eq!(actual.platform, expected, "for input: {input}");
366
367 let serialized = toml::to_string(&actual).expect("serialized correctly");
369 let actual_2: Wrapper = toml::from_str(&serialized)
370 .unwrap_or_else(|err| panic!("serialized input: {input} is valid: {err}"));
371 assert_eq!(actual, actual_2, "for input: {input}");
372
373 if actual.platform.custom_json.is_some() {
375 #[cfg(feature = "custom")]
376 {
377 let platform = actual
378 .platform
379 .to_platform()
380 .expect("custom platform parsed successfully");
381 assert!(platform.is_custom(), "this is a custom platform");
382 }
383
384 #[cfg(not(feature = "custom"))]
385 {
386 use crate::errors::CustomTripleCreateError;
387
388 let error = actual
389 .platform
390 .to_platform()
391 .expect_err("custom platforms are disabled");
392 assert!(matches!(
393 error,
394 Error::CustomPlatformCreate(CustomTripleCreateError::Unavailable)
395 ));
396 }
397 }
398 }
399 }
400}
401
402#[cfg(all(test, feature = "proptest1"))]
403mod proptests {
404 use super::*;
405 use proptest::prelude::*;
406 use std::collections::HashSet;
407
408 proptest! {
409 #[test]
410 fn summary_roundtrip(platform in Platform::strategy(any::<TargetFeatures>())) {
411 let summary = PlatformSummary::from_platform(&platform);
412 let serialized = toml::ser::to_string(&summary).expect("serialization succeeded");
413
414 let deserialized: PlatformSummary = toml::from_str(&serialized).expect("deserialization succeeded");
415 assert_eq!(summary, deserialized, "summary and deserialized should match");
416 let platform2 = deserialized.to_platform().expect("conversion to Platform succeeded");
417
418 assert_eq!(platform.triple_str(), platform2.triple_str(), "triples match");
419 assert_eq!(platform.target_features(), platform2.target_features(), "target features match");
420 assert_eq!(platform.flags().collect::<HashSet<_>>(), platform2.flags().collect::<HashSet<_>>(), "flags match");
421 }
422 }
423}