1 // Copyright (C) 2023 The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 use std::collections::BTreeMap; 16 17 use anyhow::{anyhow, Result}; 18 19 use crate::{ 20 generate_android_bps, CrateCollection, Migratable, NameAndVersion, NameAndVersionMap, 21 NamedAndVersioned, 22 }; 23 24 #[derive(Debug)] 25 pub struct VersionPair<'a, T> { 26 pub source: &'a T, 27 pub dest: Option<&'a T>, 28 } 29 30 #[derive(Debug)] 31 pub struct CompatibleVersionPair<'a, T> { 32 pub source: &'a T, 33 pub dest: &'a T, 34 } 35 36 impl<'a, T> VersionPair<'a, T> { to_compatible(self) -> Option<CompatibleVersionPair<'a, T>>37 pub fn to_compatible(self) -> Option<CompatibleVersionPair<'a, T>> { 38 self.dest.map(|dest| CompatibleVersionPair { source: self.source, dest }) 39 } 40 } 41 42 pub struct VersionMatch<CollectionType: NameAndVersionMap> { 43 source: CollectionType, 44 dest: CollectionType, 45 compatibility: BTreeMap<NameAndVersion, Option<NameAndVersion>>, 46 } 47 48 impl<CollectionType: NameAndVersionMap> VersionMatch<CollectionType> { new(source: CollectionType, dest: CollectionType) -> Result<Self>49 pub fn new(source: CollectionType, dest: CollectionType) -> Result<Self> { 50 let mut vm = VersionMatch { source, dest, compatibility: BTreeMap::new() }; 51 52 for nv in vm.dest.map_field().keys() { 53 vm.compatibility.insert_or_error(nv.to_owned(), None)?; 54 } 55 56 for nv in vm.source.map_field().keys() { 57 let compatibility = if let Some(dest_nv) = vm.dest.get_version_upgradable_from(nv) { 58 vm.compatibility.map_field_mut().remove(dest_nv).ok_or(anyhow!( 59 "Destination crate version {} {} expected but not found", 60 dest_nv.name(), 61 dest_nv.version() 62 ))?; 63 Some(dest_nv.clone()) 64 } else { 65 None 66 }; 67 vm.compatibility.insert_or_error(nv.to_owned(), compatibility)?; 68 } 69 70 Ok(vm) 71 } is_superfluous(&self, dest: &dyn NamedAndVersioned) -> bool72 pub fn is_superfluous(&self, dest: &dyn NamedAndVersioned) -> bool { 73 self.dest.map_field().contains_key(dest) 74 && self.compatibility.get(dest).is_some_and(|compatibility| compatibility.is_none()) 75 } get_compatible_version( &self, source: &dyn NamedAndVersioned, ) -> Option<&NameAndVersion>76 pub fn get_compatible_version( 77 &self, 78 source: &dyn NamedAndVersioned, 79 ) -> Option<&NameAndVersion> { 80 self.compatibility.get(source).and_then(|compatibility| compatibility.as_ref()) 81 } get_compatible_item( &self, source: &dyn NamedAndVersioned, ) -> Option<&CollectionType::Value>82 pub fn get_compatible_item( 83 &self, 84 source: &dyn NamedAndVersioned, 85 ) -> Option<&CollectionType::Value> { 86 self.get_compatible_version(source).map(|nv| self.dest.map_field().get(nv).unwrap()) 87 } get_compatible_item_mut( &mut self, source: &dyn NamedAndVersioned, ) -> Option<&mut CollectionType::Value>88 pub fn get_compatible_item_mut( 89 &mut self, 90 source: &dyn NamedAndVersioned, 91 ) -> Option<&mut CollectionType::Value> { 92 let nv = self.get_compatible_version(source)?.clone(); 93 self.dest.map_field_mut().get_mut(&nv) 94 } 95 superfluous(&self) -> impl Iterator<Item = (&NameAndVersion, &CollectionType::Value)>96 pub fn superfluous(&self) -> impl Iterator<Item = (&NameAndVersion, &CollectionType::Value)> { 97 self.dest.map_field().iter().filter(|(nv, _val)| { 98 self.compatibility.get(*nv).is_some_and(|compatibility| compatibility.is_none()) 99 }) 100 } pairs<'a>(&'a self) -> impl Iterator<Item = VersionPair<'a, CollectionType::Value>>101 pub fn pairs<'a>(&'a self) -> impl Iterator<Item = VersionPair<'a, CollectionType::Value>> { 102 self.source 103 .map_field() 104 .iter() 105 .map(|(nv, source)| VersionPair { source, dest: self.get_compatible_item(nv) }) 106 } compatible_pairs<'a>( &'a self, ) -> impl Iterator<Item = CompatibleVersionPair<'a, CollectionType::Value>>107 pub fn compatible_pairs<'a>( 108 &'a self, 109 ) -> impl Iterator<Item = CompatibleVersionPair<'a, CollectionType::Value>> { 110 self.pairs().into_iter().filter_map( 111 |pair: VersionPair<'_, <CollectionType as NameAndVersionMap>::Value>| { 112 pair.to_compatible() 113 }, 114 ) 115 } print(&self)116 pub fn print(&self) { 117 for (nv, compatibility) in self.compatibility.iter() { 118 match compatibility { 119 Some(dest) => { 120 println!("{} old {} -> new {}", nv.name(), nv.version(), dest.version()) 121 } 122 None => { 123 if self.dest.contains_name(nv.name()) { 124 println!("{} {} -> NO MATCHING VERSION", nv.name(), nv.version()) 125 } else { 126 println!("{} {} -> NOT FOUND IN NEW", nv.name(), nv.version()) 127 } 128 } 129 } 130 } 131 for (nv, _) in self.superfluous() { 132 println!("{} {} -> NOT FOUND IN OLD", nv.name(), nv.version()); 133 } 134 } 135 } 136 137 impl<CollectionType: NameAndVersionMap> VersionMatch<CollectionType> 138 where 139 CollectionType::Value: Migratable, 140 { ineligible(&self) -> impl Iterator<Item = &CollectionType::Value>141 pub fn ineligible(&self) -> impl Iterator<Item = &CollectionType::Value> { 142 self.source.map_field().values().filter(|val| !val.is_migration_eligible()) 143 } eligible_but_not_migratable<'a>( &'a self, ) -> impl Iterator<Item = VersionPair<'a, CollectionType::Value>>144 pub fn eligible_but_not_migratable<'a>( 145 &'a self, 146 ) -> impl Iterator<Item = VersionPair<'a, CollectionType::Value>> { 147 self.pairs().filter(|pair| { 148 pair.source.is_migration_eligible() 149 && !pair.dest.is_some_and(|dest| dest.is_migratable()) 150 }) 151 } compatible_and_eligible<'a>( &'a self, ) -> impl Iterator<Item = CompatibleVersionPair<'a, CollectionType::Value>>152 pub fn compatible_and_eligible<'a>( 153 &'a self, 154 ) -> impl Iterator<Item = CompatibleVersionPair<'a, CollectionType::Value>> { 155 self.compatible_pairs().filter(|crate_pair| crate_pair.source.is_migration_eligible()) 156 } migratable<'a>( &'a self, ) -> impl Iterator<Item = CompatibleVersionPair<'a, CollectionType::Value>>157 pub fn migratable<'a>( 158 &'a self, 159 ) -> impl Iterator<Item = CompatibleVersionPair<'a, CollectionType::Value>> { 160 self.compatible_pairs() 161 .filter(|pair| pair.source.is_migration_eligible() && pair.dest.is_migratable()) 162 } 163 } 164 165 impl VersionMatch<CrateCollection> { copy_customizations(&self) -> Result<()>166 pub fn copy_customizations(&self) -> Result<()> { 167 for pair in self.compatible_and_eligible() { 168 pair.copy_customizations()?; 169 } 170 Ok(()) 171 } stage_crates(&mut self) -> Result<()>172 pub fn stage_crates(&mut self) -> Result<()> { 173 for pair in self.compatible_and_eligible() { 174 pair.dest.stage_crate()?; 175 } 176 Ok(()) 177 } apply_patches(&mut self) -> Result<()>178 pub fn apply_patches(&mut self) -> Result<()> { 179 let (s, d, c) = (&self.source, &mut self.dest, &self.compatibility); 180 for (source_key, source_crate) in s.map_field() { 181 if source_crate.is_migration_eligible() { 182 if let Some(dest_crate) = c.get(source_key).and_then(|compatibility| { 183 compatibility.as_ref().and_then(|dest_key| d.map_field_mut().get_mut(dest_key)) 184 }) { 185 dest_crate.apply_patches()? 186 } 187 } 188 } 189 Ok(()) 190 } generate_android_bps(&mut self) -> Result<()>191 pub fn generate_android_bps(&mut self) -> Result<()> { 192 let results = generate_android_bps(self.compatible_and_eligible().map(|pair| pair.dest))?; 193 for (nv, output) in results.into_iter() { 194 self.dest 195 .map_field_mut() 196 .get_mut(&nv) 197 .ok_or(anyhow!("Failed to get crate {} {}", nv.name(), nv.version()))? 198 .set_generate_android_bp_output(output); 199 } 200 Ok(()) 201 } 202 diff_android_bps(&mut self) -> Result<()>203 pub fn diff_android_bps(&mut self) -> Result<()> { 204 let mut results = BTreeMap::new(); 205 for pair in self.compatible_and_eligible() { 206 results.insert_or_error(NameAndVersion::from(pair.dest), pair.diff_android_bps()?)?; 207 } 208 for (nv, output) in results.into_iter() { 209 self.dest 210 .map_field_mut() 211 .get_mut(&nv) 212 .ok_or(anyhow!("Failed to get crate {} {}", nv.name(), nv.version()))? 213 .set_diff_output(output); 214 } 215 Ok(()) 216 } 217 } 218 219 #[cfg(test)] 220 mod tests { 221 use crate::try_name_version_map_from_iter; 222 223 use super::*; 224 use anyhow::Result; 225 use itertools::assert_equal; 226 use std::collections::BTreeMap; 227 228 #[test] test_version_map() -> Result<()>229 fn test_version_map() -> Result<()> { 230 let source = try_name_version_map_from_iter([ 231 ("equal", "2.3.4", "equal src".to_string()), 232 ("compatible", "1.2.3", "compatible src".to_string()), 233 ("incompatible", "1.1.1", "incompatible src".to_string()), 234 ("downgrade", "2.2.2", "downgrade src".to_string()), 235 ("missing", "1.0.0", "missing src".to_string()), 236 ])?; 237 let dest = try_name_version_map_from_iter([ 238 ("equal", "2.3.4", "equal dest".to_string()), 239 ("compatible", "1.2.4", "compatible dest".to_string()), 240 ("incompatible", "2.0.0", "incompatible dest".to_string()), 241 ("downgrade", "2.2.1", "downgrade dest".to_string()), 242 ("superfluous", "1.0.0", "superfluous dest".to_string()), 243 ])?; 244 245 let equal = NameAndVersion::try_from_str("equal", "2.3.4")?; 246 let compatible_old = NameAndVersion::try_from_str("compatible", "1.2.3")?; 247 let incompatible_old = NameAndVersion::try_from_str("incompatible", "1.1.1")?; 248 let downgrade_old = NameAndVersion::try_from_str("downgrade", "2.2.2")?; 249 let missing = NameAndVersion::try_from_str("missing", "1.0.0")?; 250 251 let compatible_new = NameAndVersion::try_from_str("compatible", "1.2.4")?; 252 let incompatible_new = NameAndVersion::try_from_str("incompatible", "2.0.0")?; 253 let downgrade_new = NameAndVersion::try_from_str("downgrade", "2.2.1")?; 254 let superfluous = NameAndVersion::try_from_str("superfluous", "1.0.0")?; 255 256 let mut version_match = VersionMatch::new(source, dest)?; 257 assert_eq!( 258 version_match.compatibility, 259 BTreeMap::from([ 260 (downgrade_new.clone(), None), 261 (downgrade_old.clone(), None), 262 (equal.clone(), Some(equal.clone())), 263 (compatible_old.clone(), Some(compatible_new.clone())), 264 (incompatible_old.clone(), None), 265 (incompatible_new.clone(), None), 266 (missing.clone(), None), 267 (superfluous.clone(), None), 268 ]) 269 ); 270 271 // assert!(version_match.has_compatible(&equal)); 272 assert_eq!(version_match.get_compatible_version(&equal), Some(&equal)); 273 assert_eq!(version_match.get_compatible_item(&equal), Some(&"equal dest".to_string())); 274 assert_eq!( 275 version_match.get_compatible_item_mut(&equal), 276 Some(&mut "equal dest".to_string()) 277 ); 278 assert!(!version_match.is_superfluous(&equal)); 279 280 // assert!(version_match.has_compatible(&compatible_old)); 281 assert_eq!(version_match.get_compatible_version(&compatible_old), Some(&compatible_new)); 282 assert_eq!( 283 version_match.get_compatible_item(&compatible_old), 284 Some(&"compatible dest".to_string()) 285 ); 286 assert_eq!( 287 version_match.get_compatible_item_mut(&compatible_old), 288 Some(&mut "compatible dest".to_string()) 289 ); 290 assert!(!version_match.is_superfluous(&compatible_old)); 291 assert!(!version_match.is_superfluous(&compatible_new)); 292 293 // assert!(!version_match.has_compatible(&incompatible_old)); 294 assert!(version_match.get_compatible_version(&incompatible_old).is_none()); 295 assert!(version_match.get_compatible_item(&incompatible_old).is_none()); 296 assert!(version_match.get_compatible_item_mut(&incompatible_old).is_none()); 297 assert!(!version_match.is_superfluous(&incompatible_old)); 298 assert!(version_match.is_superfluous(&incompatible_new)); 299 300 // assert!(!version_match.has_compatible(&downgrade_old)); 301 assert!(version_match.get_compatible_version(&downgrade_old).is_none()); 302 assert!(version_match.get_compatible_item(&downgrade_old).is_none()); 303 assert!(version_match.get_compatible_item_mut(&downgrade_old).is_none()); 304 assert!(!version_match.is_superfluous(&downgrade_old)); 305 assert!(version_match.is_superfluous(&downgrade_new)); 306 307 // assert!(!version_match.has_compatible(&missing)); 308 assert!(version_match.get_compatible_version(&missing).is_none()); 309 assert!(version_match.get_compatible_item(&missing).is_none()); 310 assert!(version_match.get_compatible_item_mut(&missing).is_none()); 311 assert!(!version_match.is_superfluous(&missing)); 312 313 // assert!(!version_match.has_compatible(&superfluous)); 314 assert!(version_match.get_compatible_version(&superfluous).is_none()); 315 assert!(version_match.get_compatible_item(&superfluous).is_none()); 316 assert!(version_match.get_compatible_item_mut(&superfluous).is_none()); 317 assert!(version_match.is_superfluous(&superfluous)); 318 319 assert_equal( 320 version_match.superfluous().map(|(nv, _dest)| nv), 321 [&downgrade_new, &incompatible_new, &superfluous], 322 ); 323 324 assert_equal( 325 version_match.pairs().map(|x| x.source), 326 ["compatible src", "downgrade src", "equal src", "incompatible src", "missing src"], 327 ); 328 assert_equal( 329 version_match.pairs().map(|x| x.dest), 330 [ 331 Some(&"compatible dest".to_string()), 332 None, 333 Some(&"equal dest".to_string()), 334 None, 335 None, 336 ], 337 ); 338 339 assert_equal( 340 version_match.compatible_pairs().map(|x| x.source), 341 ["compatible src", "equal src"], 342 ); 343 assert_equal( 344 version_match.compatible_pairs().map(|x| x.dest), 345 ["compatible dest", "equal dest"], 346 ); 347 348 Ok(()) 349 } 350 351 #[derive(Debug, PartialEq, Eq)] 352 struct FakeMigratable { 353 name: String, 354 source: bool, 355 eligible: bool, 356 migratable: bool, 357 } 358 359 impl FakeMigratable { source(name: &str, eligible: bool) -> FakeMigratable360 pub fn source(name: &str, eligible: bool) -> FakeMigratable { 361 FakeMigratable { name: name.to_string(), source: true, eligible, migratable: false } 362 } dest(migratable: bool) -> FakeMigratable363 pub fn dest(migratable: bool) -> FakeMigratable { 364 FakeMigratable { name: "".to_string(), source: false, eligible: false, migratable } 365 } 366 } 367 368 impl Migratable for FakeMigratable { is_migration_eligible(&self) -> bool369 fn is_migration_eligible(&self) -> bool { 370 if !self.source { 371 unreachable!("Checking if dest is migration-eligible"); 372 } 373 self.eligible 374 } 375 is_migratable(&self) -> bool376 fn is_migratable(&self) -> bool { 377 if self.source { 378 unreachable!("Checking if source is migratable"); 379 } 380 self.migratable 381 } 382 } 383 384 #[test] test_migratability() -> Result<()>385 fn test_migratability() -> Result<()> { 386 let source = try_name_version_map_from_iter([ 387 ("ineligible", "1.2.3", FakeMigratable::source("ineligible", false)), 388 ( 389 "eligible incompatible", 390 "1.2.3", 391 FakeMigratable::source("eligible incompatible", true), 392 ), 393 ("eligible compatible", "1.2.3", FakeMigratable::source("eligible compatible", true)), 394 ("migratable", "1.2.3", FakeMigratable::source("migratable", true)), 395 ( 396 "migratable incompatible", 397 "1.2.3", 398 FakeMigratable::source("migratable incompatible", true), 399 ), 400 ])?; 401 let dest = try_name_version_map_from_iter([ 402 ("ineligible", "1.2.3", FakeMigratable::dest(true)), 403 ("eligible incompatible", "2.0.0", FakeMigratable::dest(true)), 404 ("eligible compatible", "1.2.3", FakeMigratable::dest(false)), 405 ("migratable", "1.2.3", FakeMigratable::dest(true)), 406 ("migratable incompatible", "2.0.0", FakeMigratable::dest(true)), 407 ])?; 408 409 let version_match = VersionMatch::new(source, dest)?; 410 411 assert_equal(version_match.ineligible().map(|m| m.name.as_str()), ["ineligible"]); 412 assert_equal( 413 version_match.eligible_but_not_migratable().map(|pair| pair.source.name.as_str()), 414 ["eligible compatible", "eligible incompatible", "migratable incompatible"], 415 ); 416 assert_equal( 417 version_match.compatible_and_eligible().map(|pair| pair.source.name.as_str()), 418 ["eligible compatible", "migratable"], 419 ); 420 assert_equal( 421 version_match.migratable().map(|pair| pair.source.name.as_str()), 422 ["migratable"], 423 ); 424 425 Ok(()) 426 } 427 } 428