xref: /aosp_15_r20/external/bazelbuild-rules_rust/crate_universe/src/utils/starlark/select_scalar.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1 use std::collections::{BTreeMap, BTreeSet};
2 use std::fmt::Debug;
3 
4 use serde::ser::{SerializeMap, Serializer};
5 use serde::Serialize;
6 use serde_starlark::{FunctionCall, MULTILINE};
7 
8 use crate::select::{Select, SelectableScalar};
9 use crate::utils::starlark::{
10     looks_like_bazel_configuration_label, NoMatchingPlatformTriples, WithOriginalConfigurations,
11 };
12 
13 #[derive(Debug, PartialEq, Eq)]
14 pub(crate) struct SelectScalar<T>
15 where
16     T: SelectableScalar,
17 {
18     common: Option<T>,
19     selects: BTreeMap<String, WithOriginalConfigurations<T>>,
20     // Elements from the `Select` whose configuration did not get mapped to any
21     // new configuration. They could be ignored, but are preserved here to
22     // generate comments that help the user understand what happened.
23     unmapped: BTreeMap<String, T>,
24 }
25 
26 impl<T> SelectScalar<T>
27 where
28     T: SelectableScalar,
29 {
30     /// Re-keys the provided Select by the given configuration mapping.
31     /// This mapping maps from configurations in the input Select to sets of
32     /// configurations in the output SelectScalar.
new(select: Select<T>, platforms: &BTreeMap<String, BTreeSet<String>>) -> Self33     pub(crate) fn new(select: Select<T>, platforms: &BTreeMap<String, BTreeSet<String>>) -> Self {
34         let (common, selects) = select.into_parts();
35 
36         // Map new configuration -> WithOriginalConfigurations(value, old configurations).
37         let mut remapped: BTreeMap<String, WithOriginalConfigurations<T>> = BTreeMap::new();
38         // Map unknown configuration -> value.
39         let mut unmapped: BTreeMap<String, T> = BTreeMap::new();
40 
41         for (original_configuration, value) in selects {
42             match platforms.get(&original_configuration) {
43                 Some(configurations) => {
44                     for configuration in configurations {
45                         remapped
46                             .entry(configuration.clone())
47                             .or_insert_with(|| WithOriginalConfigurations {
48                                 value: value.clone(),
49                                 original_configurations: BTreeSet::new(),
50                             })
51                             .original_configurations
52                             .insert(original_configuration.clone());
53                     }
54                 }
55                 None => {
56                     if looks_like_bazel_configuration_label(&original_configuration) {
57                         remapped
58                             .entry(original_configuration.clone())
59                             .or_insert_with(|| WithOriginalConfigurations {
60                                 value: value.clone(),
61                                 original_configurations: BTreeSet::new(),
62                             })
63                             .original_configurations
64                             .insert(original_configuration.clone());
65                     } else {
66                         unmapped.insert(original_configuration.clone(), value);
67                     }
68                 }
69             }
70         }
71 
72         Self {
73             common,
74             selects: remapped,
75             unmapped,
76         }
77     }
78 
79     /// Determine whether or not the select should be serialized
is_empty(&self) -> bool80     pub(crate) fn is_empty(&self) -> bool {
81         self.common.is_none() && self.selects.is_empty() && self.unmapped.is_empty()
82     }
83 }
84 
85 impl<T> Serialize for SelectScalar<T>
86 where
87     T: SelectableScalar,
88 {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,89     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
90     where
91         S: Serializer,
92     {
93         // If there are no platform-specific entries, we output just an ordinary
94         // value.
95         //
96         // If there are platform-specific ones, we use the following.
97         //
98         //     select({
99         //         "configuration": "plat-value",  # cfg(whatever),
100         //         "//conditions:default": "common-value",
101         //     })
102         //
103         // If there are unmapped entries, we include them like this:
104         //
105         //     selects.with_unmapped({
106         //         "configuration": "plat-value",  # cfg(whatever),
107         //         "//conditions:default": "common-value",
108         //         selects.NO_MATCHING_PLATFORM_TRIPLES: {
109         //             "cfg(obscure)": [
110         //                 "unmapped-value",
111         //             ],
112         //         },
113         //     })
114 
115         if self.common.is_some() && self.selects.is_empty() && self.unmapped.is_empty() {
116             return self.common.as_ref().unwrap().serialize(serializer);
117         }
118 
119         struct SelectInner<'a, T>(&'a SelectScalar<T>)
120         where
121             T: SelectableScalar;
122 
123         impl<'a, T> Serialize for SelectInner<'a, T>
124         where
125             T: SelectableScalar,
126         {
127             fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
128             where
129                 S: Serializer,
130             {
131                 let mut map = serializer.serialize_map(Some(MULTILINE))?;
132                 for (configuration, value) in self.0.selects.iter() {
133                     map.serialize_entry(configuration, value)?;
134                 }
135                 if let Some(common) = self.0.common.as_ref() {
136                     map.serialize_entry("//conditions:default", common)?;
137                 }
138                 if !self.0.unmapped.is_empty() {
139                     struct SelectUnmapped<'a, T>(&'a BTreeMap<String, T>)
140                     where
141                         T: SelectableScalar;
142 
143                     impl<'a, T> Serialize for SelectUnmapped<'a, T>
144                     where
145                         T: SelectableScalar,
146                     {
147                         fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
148                         where
149                             S: Serializer,
150                         {
151                             let mut map = serializer.serialize_map(Some(MULTILINE))?;
152                             for (cfg, value) in self.0.iter() {
153                                 map.serialize_entry(cfg, value)?;
154                             }
155                             map.end()
156                         }
157                     }
158 
159                     map.serialize_entry(
160                         &NoMatchingPlatformTriples,
161                         &SelectUnmapped(&self.0.unmapped),
162                     )?;
163                 }
164                 map.end()
165             }
166         }
167 
168         let function = if self.unmapped.is_empty() {
169             "select"
170         } else {
171             "selects.with_unmapped"
172         };
173 
174         FunctionCall::new(function, [SelectInner(self)]).serialize(serializer)
175     }
176 }
177 
178 #[cfg(test)]
179 mod test {
180     use super::*;
181 
182     use indoc::indoc;
183 
184     #[test]
empty_select_value()185     fn empty_select_value() {
186         let select_value: SelectScalar<String> =
187             SelectScalar::new(Default::default(), &Default::default());
188 
189         let expected_starlark = indoc! {r#"
190             select({})
191         "#};
192 
193         assert_eq!(
194             select_value.serialize(serde_starlark::Serializer).unwrap(),
195             expected_starlark,
196         );
197     }
198 
199     #[test]
no_platform_specific_select_value()200     fn no_platform_specific_select_value() {
201         let mut select: Select<String> = Select::default();
202         select.insert("Hello".to_owned(), None);
203 
204         let select_value = SelectScalar::new(select, &Default::default());
205 
206         let expected_starlark = indoc! {r#"
207             "Hello"
208         "#};
209 
210         assert_eq!(
211             select_value.serialize(serde_starlark::Serializer).unwrap(),
212             expected_starlark,
213         );
214     }
215 
216     #[test]
only_platform_specific_select_value()217     fn only_platform_specific_select_value() {
218         let mut select: Select<String> = Select::default();
219         select.insert("Hello".to_owned(), Some("platform".to_owned()));
220 
221         let platforms = BTreeMap::from([(
222             "platform".to_owned(),
223             BTreeSet::from(["platform".to_owned()]),
224         )]);
225 
226         let select_value = SelectScalar::new(select, &platforms);
227 
228         let expected_starlark = indoc! {r#"
229             select({
230                 "platform": "Hello",  # platform
231             })
232         "#};
233 
234         assert_eq!(
235             select_value.serialize(serde_starlark::Serializer).unwrap(),
236             expected_starlark,
237         );
238     }
239 
240     #[test]
mixed_select_value()241     fn mixed_select_value() {
242         let mut select: Select<String> = Select::default();
243         select.insert("Hello".to_owned(), Some("platform".to_owned()));
244         select.insert("Goodbye".to_owned(), None);
245 
246         let platforms = BTreeMap::from([(
247             "platform".to_owned(),
248             BTreeSet::from(["platform".to_owned()]),
249         )]);
250 
251         let select_value = SelectScalar::new(select, &platforms);
252 
253         let expected_starlark = indoc! {r#"
254             select({
255                 "platform": "Hello",  # platform
256                 "//conditions:default": "Goodbye",
257             })
258         "#};
259 
260         assert_eq!(
261             select_value.serialize(serde_starlark::Serializer).unwrap(),
262             expected_starlark,
263         );
264     }
265 
266     #[test]
remap_select_value_configurations()267     fn remap_select_value_configurations() {
268         let mut select: Select<String> = Select::default();
269         select.insert("a".to_owned(), Some("cfg(macos)".to_owned()));
270         select.insert("a".to_owned(), Some("cfg(x86_64)".to_owned()));
271         select.insert("e".to_owned(), Some("cfg(pdp11)".to_owned()));
272         select.insert("f".to_owned(), Some("@platforms//os:magic".to_owned()));
273         select.insert("g".to_owned(), Some("//another:platform".to_owned()));
274 
275         let platforms = BTreeMap::from([
276             (
277                 "cfg(macos)".to_owned(),
278                 BTreeSet::from(["x86_64-macos".to_owned(), "aarch64-macos".to_owned()]),
279             ),
280             (
281                 "cfg(x86_64)".to_owned(),
282                 BTreeSet::from(["x86_64-linux".to_owned(), "x86_64-macos".to_owned()]),
283             ),
284         ]);
285 
286         let select_value = SelectScalar::new(select, &platforms);
287 
288         let expected = SelectScalar {
289             common: None,
290             selects: BTreeMap::from([
291                 (
292                     "x86_64-macos".to_owned(),
293                     WithOriginalConfigurations {
294                         value: "a".to_owned(),
295                         original_configurations: BTreeSet::from([
296                             "cfg(macos)".to_owned(),
297                             "cfg(x86_64)".to_owned(),
298                         ]),
299                     },
300                 ),
301                 (
302                     "aarch64-macos".to_owned(),
303                     WithOriginalConfigurations {
304                         value: "a".to_owned(),
305                         original_configurations: BTreeSet::from(["cfg(macos)".to_owned()]),
306                     },
307                 ),
308                 (
309                     "x86_64-linux".to_owned(),
310                     WithOriginalConfigurations {
311                         value: "a".to_owned(),
312                         original_configurations: BTreeSet::from(["cfg(x86_64)".to_owned()]),
313                     },
314                 ),
315                 (
316                     "@platforms//os:magic".to_owned(),
317                     WithOriginalConfigurations {
318                         value: "f".to_owned(),
319                         original_configurations: BTreeSet::from(
320                             ["@platforms//os:magic".to_owned()],
321                         ),
322                     },
323                 ),
324                 (
325                     "//another:platform".to_owned(),
326                     WithOriginalConfigurations {
327                         value: "g".to_owned(),
328                         original_configurations: BTreeSet::from(["//another:platform".to_owned()]),
329                     },
330                 ),
331             ]),
332             unmapped: BTreeMap::from([("cfg(pdp11)".to_owned(), "e".to_owned())]),
333         };
334 
335         assert_eq!(select_value, expected);
336 
337         let expected_starlark = indoc! {r#"
338             selects.with_unmapped({
339                 "//another:platform": "g",  # //another:platform
340                 "@platforms//os:magic": "f",  # @platforms//os:magic
341                 "aarch64-macos": "a",  # cfg(macos)
342                 "x86_64-linux": "a",  # cfg(x86_64)
343                 "x86_64-macos": "a",  # cfg(macos), cfg(x86_64)
344                 selects.NO_MATCHING_PLATFORM_TRIPLES: {
345                     "cfg(pdp11)": "e",
346                 },
347             })
348         "#};
349 
350         assert_eq!(
351             select_value.serialize(serde_starlark::Serializer).unwrap(),
352             expected_starlark,
353         );
354     }
355 }
356