|
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