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