|
4 | 4 |
|
5 | 5 | use std::borrow::Cow; |
6 | 6 | use std::cell::RefCell; |
7 | | -use std::collections::{BTreeSet, HashMap, HashSet, VecDeque}; |
| 7 | +use std::collections::{BTreeSet, HashMap, HashSet}; |
| 8 | +use std::ops::Not; |
8 | 9 | use std::str::FromStr; |
9 | 10 | use std::sync::Arc; |
10 | 11 |
|
| 12 | +use itertools::Itertools; |
11 | 13 | use resolvo::utils::Pool; |
12 | 14 | use resolvo::{ |
13 | 15 | Candidates, |
@@ -38,8 +40,18 @@ use spk_schema::name::{OptNameBuf, PkgNameBuf}; |
38 | 40 | use spk_schema::prelude::{HasVersion, Named}; |
39 | 41 | use spk_schema::version::Version; |
40 | 42 | use spk_schema::version_range::{parse_version_range, DoubleEqualsVersion, Ranged, VersionFilter}; |
41 | | -use spk_schema::{BuildIdent, Deprecate, Opt, OptionMap, Package, Recipe, Request, VersionIdent}; |
42 | | -use spk_solve_package_iterator::SortedBuildIterator; |
| 43 | +use spk_schema::{ |
| 44 | + BuildIdent, |
| 45 | + Deprecate, |
| 46 | + Opt, |
| 47 | + OptionMap, |
| 48 | + Package, |
| 49 | + Recipe, |
| 50 | + Request, |
| 51 | + Spec, |
| 52 | + VersionIdent, |
| 53 | +}; |
| 54 | +use spk_solve_package_iterator::{BuildKey, BuildToSortedOptName, SortedBuildIterator}; |
43 | 55 | use spk_storage::RepositoryHandle; |
44 | 56 | use tracing::{debug_span, Instrument}; |
45 | 57 |
|
@@ -338,6 +350,53 @@ enum CanBuildFromSource { |
338 | 350 | No(StringId), |
339 | 351 | } |
340 | 352 |
|
| 353 | +/// An iterator that yields slices of items that fall into the same partition. |
| 354 | +/// |
| 355 | +/// The partition is determined by the key function. |
| 356 | +/// The items must be already sorted in ascending order by the key function. |
| 357 | +struct PartitionIter<'a, I, F, K> |
| 358 | +where |
| 359 | + F: for<'i> Fn(&'i I) -> K, |
| 360 | + K: PartialOrd, |
| 361 | +{ |
| 362 | + slice: &'a [I], |
| 363 | + key_fn: F, |
| 364 | +} |
| 365 | + |
| 366 | +impl<'a, I, F, K> PartitionIter<'a, I, F, K> |
| 367 | +where |
| 368 | + F: for<'i> Fn(&'i I) -> K, |
| 369 | + K: PartialOrd, |
| 370 | +{ |
| 371 | + fn new(slice: &'a [I], key_fn: F) -> Self { |
| 372 | + Self { slice, key_fn } |
| 373 | + } |
| 374 | +} |
| 375 | + |
| 376 | +impl<'a, I, F, K> Iterator for PartitionIter<'a, I, F, K> |
| 377 | +where |
| 378 | + F: for<'i> Fn(&'i I) -> K, |
| 379 | + K: PartialOrd, |
| 380 | +{ |
| 381 | + type Item = &'a [I]; |
| 382 | + |
| 383 | + fn next(&mut self) -> Option<Self::Item> { |
| 384 | + let Some(element) = self.slice.first() else { |
| 385 | + return None; |
| 386 | + }; |
| 387 | + |
| 388 | + // Is a binary search overkill? |
| 389 | + let partition_key = (self.key_fn)(element); |
| 390 | + // No need to check the first element again. |
| 391 | + let p = |
| 392 | + 1 + self.slice[1..].partition_point(|element| (self.key_fn)(element) <= partition_key); |
| 393 | + |
| 394 | + let part = &self.slice[..p]; |
| 395 | + self.slice = &self.slice[p..]; |
| 396 | + Some(part) |
| 397 | + } |
| 398 | +} |
| 399 | + |
341 | 400 | pub(crate) struct SpkProvider { |
342 | 401 | pub(crate) pool: Pool<RequestVS, ResolvoPackageName>, |
343 | 402 | repos: Vec<Arc<RepositoryHandle>>, |
@@ -565,6 +624,21 @@ impl SpkProvider { |
565 | 624 | self.cancel_solving.borrow().is_some() |
566 | 625 | } |
567 | 626 |
|
| 627 | + /// Return an iterator that yields slices of builds that are from the same |
| 628 | + /// package version. |
| 629 | + /// |
| 630 | + /// The provided builds must already be sorted otherwise the behavior is |
| 631 | + /// undefined. |
| 632 | + fn find_version_runs<'a>( |
| 633 | + builds: &'a [(SolvableId, &'a LocatedBuildIdentWithComponent, Arc<Spec>)], |
| 634 | + ) -> impl Iterator<Item = &'a [(SolvableId, &'a LocatedBuildIdentWithComponent, Arc<Spec>)]> |
| 635 | + { |
| 636 | + PartitionIter::new(builds, |(_, ident, _)| { |
| 637 | + // partition by (name, version) ignoring repository |
| 638 | + (ident.ident.name(), ident.ident.version()) |
| 639 | + }) |
| 640 | + } |
| 641 | + |
568 | 642 | fn request_to_known_dependencies(&self, requirement: &Request) -> KnownDependencies { |
569 | 643 | let mut known_deps = KnownDependencies::default(); |
570 | 644 | match requirement { |
@@ -666,23 +740,38 @@ impl SpkProvider { |
666 | 740 | /// solve as a candidate. |
667 | 741 | /// |
668 | 742 | /// Generally this means a build with newer dependencies is ordered first. |
669 | | - async fn sort_builds( |
| 743 | + fn sort_builds( |
670 | 744 | &self, |
671 | | - a: &LocatedBuildIdentWithComponent, |
672 | | - b: &LocatedBuildIdentWithComponent, |
| 745 | + build_key_index: &HashMap<SolvableId, BuildKey>, |
| 746 | + a: (SolvableId, &LocatedBuildIdentWithComponent), |
| 747 | + b: (SolvableId, &LocatedBuildIdentWithComponent), |
673 | 748 | ) -> std::cmp::Ordering { |
674 | 749 | // This function should _not_ return `std::cmp::Ordering::Equal` unless |
675 | | - // `a` and `b` are the same build (in practice `a` and `b` will never be |
676 | | - // the same build). |
| 750 | + // `a` and `b` are the same build (in practice this function will never |
| 751 | + // be called when that is true). |
677 | 752 |
|
678 | 753 | // Embedded stubs are always ordered last. |
679 | | - match (a.ident.is_embedded(), b.ident.is_embedded()) { |
| 754 | + match (a.1.ident.is_embedded(), b.1.ident.is_embedded()) { |
680 | 755 | (true, false) => return std::cmp::Ordering::Greater, |
681 | 756 | (false, true) => return std::cmp::Ordering::Less, |
682 | 757 | _ => {} |
683 | 758 | }; |
684 | 759 |
|
685 | | - todo!() |
| 760 | + match (build_key_index.get(&a.0), build_key_index.get(&b.0)) { |
| 761 | + (Some(a_key), Some(b_key)) => { |
| 762 | + // BuildKey orders in reverse order from what is needed here. |
| 763 | + return b_key.cmp(a_key); |
| 764 | + } |
| 765 | + (Some(_), None) => return std::cmp::Ordering::Less, |
| 766 | + (None, Some(_)) => return std::cmp::Ordering::Greater, |
| 767 | + _ => {} |
| 768 | + }; |
| 769 | + |
| 770 | + // If neither build has a key, both packages failed to load? |
| 771 | + // Add debug assert to see if this ever happens. |
| 772 | + debug_assert!(false, "builds without keys"); |
| 773 | + |
| 774 | + a.1.ident.cmp(&b.1.ident) |
686 | 775 | } |
687 | 776 |
|
688 | 777 | pub fn var_requirements(&mut self, requests: &[Request]) -> Vec<VersionSetId> { |
@@ -995,32 +1084,94 @@ impl DependencyProvider for SpkProvider { |
995 | 1084 | } |
996 | 1085 |
|
997 | 1086 | async fn sort_candidates(&self, _solver: &SolverCache<Self>, solvables: &mut [SolvableId]) { |
998 | | - let located_builds = solvables |
| 1087 | + // Goal: Create a `BuildKey` for each build in `solvables`. |
| 1088 | + // The `BuildKey` factory needs as input the output from |
| 1089 | + // `BuildToSortedOptName::sort_builds`. |
| 1090 | + // `BuildToSortedOptName::sort_builds` needs to be fed builds from the |
| 1091 | + // same version. |
| 1092 | + // `solvables` can be builds from various versions so they need to be |
| 1093 | + // grouped by version. |
| 1094 | + let build_solvables = solvables |
999 | 1095 | .iter() |
1000 | | - .enumerate() |
1001 | | - .filter_map(|(index, solvable)| { |
1002 | | - let solvable = self.pool.resolve_solvable(*solvable); |
| 1096 | + .filter_map(|solvable_id| { |
| 1097 | + let solvable = self.pool.resolve_solvable(*solvable_id); |
1003 | 1098 | match &solvable.record { |
1004 | 1099 | SpkSolvable::LocatedBuildIdentWithComponent( |
1005 | 1100 | located_build_ident_with_component, |
1006 | | - ) => Some((located_build_ident_with_component, index)), |
| 1101 | + ) => |
| 1102 | + // sorting the source build (if any) is handled |
| 1103 | + // elsewhere; skip source builds. |
| 1104 | + { |
| 1105 | + located_build_ident_with_component |
| 1106 | + .ident |
| 1107 | + .is_source() |
| 1108 | + .not() |
| 1109 | + .then(|| (*solvable_id, located_build_ident_with_component)) |
| 1110 | + } |
1007 | 1111 | _ => None, |
1008 | 1112 | } |
1009 | 1113 | }) |
| 1114 | + .sorted_by( |
| 1115 | + |(_, LocatedBuildIdentWithComponent { ident: a, .. }), |
| 1116 | + (_, LocatedBuildIdentWithComponent { ident: b, .. })| { |
| 1117 | + // build_solvables will be ordered by (pkg, version, build). |
| 1118 | + a.target().cmp(b.target()) |
| 1119 | + }, |
| 1120 | + ) |
1010 | 1121 | .collect::<Vec<_>>(); |
1011 | 1122 |
|
1012 | | - let mut builds = VecDeque::new(); |
| 1123 | + // `BuildToSortedOptName::sort_builds` will need the package specs. |
| 1124 | + let mut build_solvables_and_specs = Vec::with_capacity(build_solvables.len()); |
| 1125 | + for build_solvable in build_solvables { |
| 1126 | + let (solvable_id, located_build_ident_with_component) = build_solvable; |
| 1127 | + let repo = self |
| 1128 | + .repos |
| 1129 | + .iter() |
| 1130 | + .find(|repo| { |
| 1131 | + repo.name() == located_build_ident_with_component.ident.repository_name() |
| 1132 | + }) |
| 1133 | + .expect("Expected solved package's repository to be in the list of repositories"); |
| 1134 | + let Ok(package) = repo |
| 1135 | + .read_package(located_build_ident_with_component.ident.target()) |
| 1136 | + .await |
| 1137 | + else { |
| 1138 | + // Any builds that can't load the spec will be sorted to the |
| 1139 | + // end. In most cases the package spec would already be loaded |
| 1140 | + // in cache at this point. |
| 1141 | + continue; |
| 1142 | + }; |
| 1143 | + build_solvables_and_specs.push(( |
| 1144 | + solvable_id, |
| 1145 | + located_build_ident_with_component, |
| 1146 | + package, |
| 1147 | + )); |
| 1148 | + } |
| 1149 | + |
| 1150 | + let mut build_key_index = HashMap::new(); |
| 1151 | + build_key_index.reserve(build_solvables_and_specs.len()); |
1013 | 1152 |
|
1014 | | - if let Ok(mut sbi) = SortedBuildIterator::new_from_builds(builds, HashMap::new()).await { |
1015 | | - todo!("sort solvables based on this order"); |
| 1153 | + // Find runs of the same package version. |
| 1154 | + for version_run in SpkProvider::find_version_runs(&build_solvables_and_specs) { |
| 1155 | + let (ordered_names, build_name_values) = |
| 1156 | + BuildToSortedOptName::sort_builds(version_run.iter().map(|(_, _, spec)| spec)); |
| 1157 | + |
| 1158 | + for (solvable_id, _, spec) in version_run { |
| 1159 | + let build_key = SortedBuildIterator::make_option_values_build_key( |
| 1160 | + spec, |
| 1161 | + &ordered_names, |
| 1162 | + &build_name_values, |
| 1163 | + false, |
| 1164 | + ); |
| 1165 | + build_key_index.insert(*solvable_id, build_key); |
| 1166 | + } |
1016 | 1167 | } |
1017 | 1168 |
|
1018 | 1169 | // TODO: The ordering should take component names into account, so |
1019 | 1170 | // the run component or the build component is tried first in the |
1020 | 1171 | // appropriate situations. |
1021 | | - solvables.sort_by(|a, b| { |
1022 | | - let a = self.pool.resolve_solvable(*a); |
1023 | | - let b = self.pool.resolve_solvable(*b); |
| 1172 | + solvables.sort_by(|solvable_id_a, solvable_id_b| { |
| 1173 | + let a = self.pool.resolve_solvable(*solvable_id_a); |
| 1174 | + let b = self.pool.resolve_solvable(*solvable_id_b); |
1024 | 1175 | match (&a.record, &b.record) { |
1025 | 1176 | ( |
1026 | 1177 | SpkSolvable::LocatedBuildIdentWithComponent(a), |
@@ -1052,7 +1203,11 @@ impl DependencyProvider for SpkProvider { |
1052 | 1203 | (_, Build::Source) => return std::cmp::Ordering::Less, |
1053 | 1204 | _ => {} |
1054 | 1205 | }; |
1055 | | - self.sort_builds(a, b).await |
| 1206 | + self.sort_builds( |
| 1207 | + &build_key_index, |
| 1208 | + (*solvable_id_a, a), |
| 1209 | + (*solvable_id_b, b), |
| 1210 | + ) |
1056 | 1211 | } |
1057 | 1212 | ord => ord, |
1058 | 1213 | } |
|
0 commit comments