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