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