xref: /aosp_15_r20/external/bazelbuild-rules_rust/crate_universe/src/utils/starlark/select_list.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1 use std::collections::{BTreeMap, BTreeSet};
2 use std::fmt::Debug;
3 
4 use serde::ser::{SerializeMap, SerializeTupleStruct, Serializer};
5 use serde::Serialize;
6 use serde_starlark::{FunctionCall, MULTILINE};
7 
8 use crate::select::{Select, SelectableValue};
9 use crate::utils::starlark::serialize::MultilineArray;
10 use crate::utils::starlark::{
11     looks_like_bazel_configuration_label, NoMatchingPlatformTriples, WithOriginalConfigurations,
12 };
13 
14 #[derive(Debug, PartialEq, Eq)]
15 pub(crate) struct SelectList<T>
16 where
17     T: SelectableValue,
18 {
19     common: Vec<T>,
20     selects: BTreeMap<String, Vec<WithOriginalConfigurations<T>>>,
21     // Elements from the `Select` whose configuration did not get mapped to any
22     // new configuration. They could be ignored, but are preserved here to
23     // generate comments that help the user understand what happened.
24     unmapped: BTreeMap<String, Vec<T>>,
25 }
26 
27 impl<T> SelectList<T>
28 where
29     T: SelectableValue,
30 {
31     /// Re-keys the provided Select by the given configuration mapping.
32     /// This mapping maps from configurations in the input Select to sets of
33     /// configurations in the output SelectList.
new( select: Select<Vec<T>>, platforms: &BTreeMap<String, BTreeSet<String>>, ) -> Self34     pub(crate) fn new(
35         select: Select<Vec<T>>,
36         platforms: &BTreeMap<String, BTreeSet<String>>,
37     ) -> Self {
38         let (common, selects) = select.into_parts();
39 
40         // Map new configuration -> WithOriginalConfigurations(value, old configuration).
41         let mut remapped: BTreeMap<String, Vec<WithOriginalConfigurations<T>>> = BTreeMap::new();
42         // Map unknown configuration -> value.
43         let mut unmapped: BTreeMap<String, Vec<T>> = BTreeMap::new();
44 
45         for (original_configuration, values) in selects {
46             match platforms.get(&original_configuration) {
47                 Some(configurations) => {
48                     for configuration in configurations {
49                         for value in &values {
50                             remapped.entry(configuration.clone()).or_default().push(
51                                 WithOriginalConfigurations {
52                                     value: value.clone(),
53                                     original_configurations: BTreeSet::from([
54                                         original_configuration.clone(),
55                                     ]),
56                                 },
57                             );
58                         }
59                     }
60                 }
61                 None => {
62                     if looks_like_bazel_configuration_label(&original_configuration) {
63                         remapped
64                             .entry(original_configuration.clone())
65                             .or_default()
66                             .extend(values.into_iter().map(|value| WithOriginalConfigurations {
67                                 value,
68                                 original_configurations: BTreeSet::from([
69                                     original_configuration.clone(),
70                                 ]),
71                             }));
72                     } else {
73                         unmapped
74                             .entry(original_configuration.clone())
75                             .or_default()
76                             .extend(values.into_iter());
77                     }
78                 }
79             }
80         }
81 
82         Self {
83             common,
84             selects: remapped,
85             unmapped,
86         }
87     }
88 
89     /// Determine whether or not the select should be serialized
is_empty(&self) -> bool90     pub(crate) fn is_empty(&self) -> bool {
91         self.common.is_empty() && self.selects.is_empty() && self.unmapped.is_empty()
92     }
93 }
94 
95 impl<T> Serialize for SelectList<T>
96 where
97     T: SelectableValue,
98 {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,99     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
100     where
101         S: Serializer,
102     {
103         // Output looks like:
104         //
105         //     [
106         //         "common...",
107         //     ] + select({
108         //         "configuration": [
109         //             "value...",  # cfg(whatever)
110         //         ],
111         //         "//conditions:default": [],
112         //     })
113         //
114         // The common part and select are each omitted if they are empty (except
115         // if the entire thing is empty, in which case we serialize the common
116         // part to get an empty array).
117         //
118         // If there are unmapped entries, we include them like this:
119         //
120         //     [
121         //         "common...",
122         //     ] + selects.with_unmapped({
123         //         "configuration": [
124         //             "value...",  # cfg(whatever)
125         //         ],
126         //         "//conditions:default": [],
127         //         selects.NO_MATCHING_PLATFORM_TRIPLES: {
128         //             "cfg(obscure)": [
129         //                 "value...",
130         //             ],
131         //         },
132         //     })
133 
134         let mut plus = serializer.serialize_tuple_struct("+", MULTILINE)?;
135 
136         if !self.common.is_empty() || self.selects.is_empty() && self.unmapped.is_empty() {
137             plus.serialize_field(&MultilineArray(&self.common))?;
138         }
139 
140         if !self.selects.is_empty() || !self.unmapped.is_empty() {
141             struct SelectInner<'a, T>(&'a SelectList<T>)
142             where
143                 T: SelectableValue;
144 
145             impl<'a, T> Serialize for SelectInner<'a, T>
146             where
147                 T: SelectableValue,
148             {
149                 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
150                 where
151                     S: Serializer,
152                 {
153                     let mut map = serializer.serialize_map(Some(MULTILINE))?;
154                     for (cfg, values) in self.0.selects.iter() {
155                         map.serialize_entry(cfg, &MultilineArray(values))?;
156                     }
157                     map.serialize_entry("//conditions:default", &[] as &[T])?;
158                     if !self.0.unmapped.is_empty() {
159                         struct SelectUnmapped<'a, T>(&'a BTreeMap<String, Vec<T>>)
160                         where
161                             T: SelectableValue;
162 
163                         impl<'a, T> Serialize for SelectUnmapped<'a, T>
164                         where
165                             T: SelectableValue,
166                         {
167                             fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
168                             where
169                                 S: Serializer,
170                             {
171                                 let mut map = serializer.serialize_map(Some(MULTILINE))?;
172                                 for (cfg, values) in self.0.iter() {
173                                     map.serialize_entry(cfg, &MultilineArray(values))?;
174                                 }
175                                 map.end()
176                             }
177                         }
178 
179                         map.serialize_entry(
180                             &NoMatchingPlatformTriples,
181                             &SelectUnmapped(&self.0.unmapped),
182                         )?;
183                     }
184                     map.end()
185                 }
186             }
187 
188             let function = if self.unmapped.is_empty() {
189                 "select"
190             } else {
191                 "selects.with_unmapped"
192             };
193 
194             plus.serialize_field(&FunctionCall::new(function, [SelectInner(self)]))?;
195         }
196 
197         plus.end()
198     }
199 }
200 
201 #[cfg(test)]
202 mod test {
203     use super::*;
204 
205     use indoc::indoc;
206 
207     #[test]
empty_select_list()208     fn empty_select_list() {
209         let select_list: SelectList<String> =
210             SelectList::new(Default::default(), &Default::default());
211 
212         let expected_starlark = indoc! {r#"
213             []
214         "#};
215 
216         assert_eq!(
217             select_list.serialize(serde_starlark::Serializer).unwrap(),
218             expected_starlark,
219         );
220     }
221 
222     #[test]
no_platform_specific_empty_select_list()223     fn no_platform_specific_empty_select_list() {
224         let mut select: Select<Vec<String>> = Select::default();
225         select.insert("Hello".to_owned(), None);
226 
227         let select_list = SelectList::new(select, &Default::default());
228 
229         let expected_starlark = indoc! {r#"
230             [
231                 "Hello",
232             ]
233         "#};
234 
235         assert_eq!(
236             select_list.serialize(serde_starlark::Serializer).unwrap(),
237             expected_starlark,
238         );
239     }
240 
241     #[test]
only_platform_specific_empty_select_list()242     fn only_platform_specific_empty_select_list() {
243         let mut select: Select<Vec<String>> = Select::default();
244         select.insert("Hello".to_owned(), Some("platform".to_owned()));
245 
246         let platforms = BTreeMap::from([(
247             "platform".to_owned(),
248             BTreeSet::from(["platform".to_owned()]),
249         )]);
250 
251         let select_list = SelectList::new(select, &platforms);
252 
253         let expected_starlark = indoc! {r#"
254             select({
255                 "platform": [
256                     "Hello",  # platform
257                 ],
258                 "//conditions:default": [],
259             })
260         "#};
261 
262         assert_eq!(
263             select_list.serialize(serde_starlark::Serializer).unwrap(),
264             expected_starlark,
265         );
266     }
267 
268     #[test]
mixed_empty_select_list()269     fn mixed_empty_select_list() {
270         let mut select: Select<Vec<String>> = Select::default();
271         select.insert("Hello".to_owned(), Some("platform".to_owned()));
272         select.insert("Goodbye".to_owned(), None);
273 
274         let platforms = BTreeMap::from([(
275             "platform".to_owned(),
276             BTreeSet::from(["platform".to_owned()]),
277         )]);
278 
279         let select_list = SelectList::new(select, &platforms);
280 
281         let expected_starlark = indoc! {r#"
282             [
283                 "Goodbye",
284             ] + select({
285                 "platform": [
286                     "Hello",  # platform
287                 ],
288                 "//conditions:default": [],
289             })
290         "#};
291 
292         assert_eq!(
293             select_list.serialize(serde_starlark::Serializer).unwrap(),
294             expected_starlark,
295         );
296     }
297 
298     #[test]
remap_empty_select_list_configurations()299     fn remap_empty_select_list_configurations() {
300         let mut select: Select<Vec<String>> = Select::default();
301         select.insert("dep-a".to_owned(), Some("cfg(macos)".to_owned()));
302         select.insert("dep-b".to_owned(), Some("cfg(macos)".to_owned()));
303         select.insert("dep-d".to_owned(), Some("cfg(macos)".to_owned()));
304         select.insert("dep-a".to_owned(), Some("cfg(x86_64)".to_owned()));
305         select.insert("dep-c".to_owned(), Some("cfg(x86_64)".to_owned()));
306         select.insert("dep-e".to_owned(), Some("cfg(pdp11)".to_owned()));
307         select.insert("dep-d".to_owned(), None);
308         select.insert("dep-f".to_owned(), Some("@platforms//os:magic".to_owned()));
309         select.insert("dep-g".to_owned(), Some("//another:platform".to_owned()));
310 
311         let platforms = BTreeMap::from([
312             (
313                 "cfg(macos)".to_owned(),
314                 BTreeSet::from(["x86_64-macos".to_owned(), "aarch64-macos".to_owned()]),
315             ),
316             (
317                 "cfg(x86_64)".to_owned(),
318                 BTreeSet::from(["x86_64-linux".to_owned(), "x86_64-macos".to_owned()]),
319             ),
320         ]);
321 
322         let select_list = SelectList::new(select, &platforms);
323 
324         let expected = SelectList {
325             common: Vec::from(["dep-d".to_owned()]),
326             selects: BTreeMap::from([
327                 (
328                     "x86_64-macos".to_owned(),
329                     Vec::from([
330                         WithOriginalConfigurations {
331                             value: "dep-a".to_owned(),
332                             original_configurations: BTreeSet::from(["cfg(macos)".to_owned()]),
333                         },
334                         WithOriginalConfigurations {
335                             value: "dep-b".to_owned(),
336                             original_configurations: BTreeSet::from(["cfg(macos)".to_owned()]),
337                         },
338                         WithOriginalConfigurations {
339                             value: "dep-d".to_owned(),
340                             original_configurations: BTreeSet::from(["cfg(macos)".to_owned()]),
341                         },
342                         WithOriginalConfigurations {
343                             value: "dep-a".to_owned(),
344                             original_configurations: BTreeSet::from(["cfg(x86_64)".to_owned()]),
345                         },
346                         WithOriginalConfigurations {
347                             value: "dep-c".to_owned(),
348                             original_configurations: BTreeSet::from(["cfg(x86_64)".to_owned()]),
349                         },
350                     ]),
351                 ),
352                 (
353                     "aarch64-macos".to_owned(),
354                     Vec::from([
355                         WithOriginalConfigurations {
356                             value: "dep-a".to_owned(),
357                             original_configurations: BTreeSet::from(["cfg(macos)".to_owned()]),
358                         },
359                         WithOriginalConfigurations {
360                             value: "dep-b".to_owned(),
361                             original_configurations: BTreeSet::from(["cfg(macos)".to_owned()]),
362                         },
363                         WithOriginalConfigurations {
364                             value: "dep-d".to_owned(),
365                             original_configurations: BTreeSet::from(["cfg(macos)".to_owned()]),
366                         },
367                     ]),
368                 ),
369                 (
370                     "x86_64-linux".to_owned(),
371                     Vec::from([
372                         WithOriginalConfigurations {
373                             value: "dep-a".to_owned(),
374                             original_configurations: BTreeSet::from(["cfg(x86_64)".to_owned()]),
375                         },
376                         WithOriginalConfigurations {
377                             value: "dep-c".to_owned(),
378                             original_configurations: BTreeSet::from(["cfg(x86_64)".to_owned()]),
379                         },
380                     ]),
381                 ),
382                 (
383                     "@platforms//os:magic".to_owned(),
384                     Vec::from([WithOriginalConfigurations {
385                         value: "dep-f".to_owned(),
386                         original_configurations: BTreeSet::from(
387                             ["@platforms//os:magic".to_owned()],
388                         ),
389                     }]),
390                 ),
391                 (
392                     "//another:platform".to_owned(),
393                     Vec::from([WithOriginalConfigurations {
394                         value: "dep-g".to_owned(),
395                         original_configurations: BTreeSet::from(["//another:platform".to_owned()]),
396                     }]),
397                 ),
398             ]),
399             unmapped: BTreeMap::from([("cfg(pdp11)".to_owned(), Vec::from(["dep-e".to_owned()]))]),
400         };
401 
402         assert_eq!(select_list, expected);
403 
404         let expected_starlark = indoc! {r#"
405             [
406                 "dep-d",
407             ] + selects.with_unmapped({
408                 "//another:platform": [
409                     "dep-g",  # //another:platform
410                 ],
411                 "@platforms//os:magic": [
412                     "dep-f",  # @platforms//os:magic
413                 ],
414                 "aarch64-macos": [
415                     "dep-a",  # cfg(macos)
416                     "dep-b",  # cfg(macos)
417                     "dep-d",  # cfg(macos)
418                 ],
419                 "x86_64-linux": [
420                     "dep-a",  # cfg(x86_64)
421                     "dep-c",  # cfg(x86_64)
422                 ],
423                 "x86_64-macos": [
424                     "dep-a",  # cfg(macos)
425                     "dep-b",  # cfg(macos)
426                     "dep-d",  # cfg(macos)
427                     "dep-a",  # cfg(x86_64)
428                     "dep-c",  # cfg(x86_64)
429                 ],
430                 "//conditions:default": [],
431                 selects.NO_MATCHING_PLATFORM_TRIPLES: {
432                     "cfg(pdp11)": [
433                         "dep-e",
434                     ],
435                 },
436             })
437         "#};
438 
439         assert_eq!(
440             select_list.serialize(serde_starlark::Serializer).unwrap(),
441             expected_starlark,
442         );
443     }
444 }
445