Skip to content

Commit 03ee415

Browse files
committed
Rework build sorting in the new solver
Reuse the same build sorting logic as the original solver in order to get a deterministic result out of the solve. The solve can change drastically based on which build is selected first. Signed-off-by: J Robert Ray <[email protected]>
1 parent 19a2b60 commit 03ee415

File tree

3 files changed

+187
-38
lines changed

3 files changed

+187
-38
lines changed

crates/spk-solve/crates/package-iterator/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ mod error;
77
mod package_iterator;
88
mod promotion_patterns;
99

10+
pub use build_key::BuildKey;
1011
pub use error::{Error, Result};
1112
pub use package_iterator::{
1213
BuildIterator,
14+
BuildToSortedOptName,
1315
EmptyBuildIterator,
1416
PackageIterator,
1517
RepositoryPackageIterator,

crates/spk-solve/crates/package-iterator/src/package_iterator.rs

+8-16
Original file line numberDiff line numberDiff line change
@@ -472,13 +472,13 @@ pub struct BuildToSortedOptName {}
472472

473473
impl BuildToSortedOptName {
474474
pub fn sort_builds<'a>(
475-
builds: impl Iterator<Item = &'a (Arc<Spec>, PackageSource)>,
475+
builds: impl Iterator<Item = &'a Arc<Spec>>,
476476
) -> (Vec<OptNameBuf>, HashMap<BuildIdent, OptionMap>) {
477477
let mut number_non_src_builds: u64 = 0;
478478
let mut build_name_values: HashMap<BuildIdent, OptionMap> = HashMap::default();
479479
let mut changes: HashMap<OptNameBuf, ChangeCounter> = HashMap::new();
480480

481-
for (build, _) in builds {
481+
for build in builds {
482482
// Skip this if it's a '/src' build because '/src' builds
483483
// won't use the build option values in their key, they
484484
// don't need to be looked at. They have a type of key
@@ -591,20 +591,9 @@ impl SortedBuildIterator {
591591
Ok(sbi)
592592
}
593593

594-
pub async fn new_from_builds(
595-
builds: VecDeque<BuildWithRepos>,
596-
builds_with_impossible_requests: HashMap<BuildIdent, Compatibility>,
597-
) -> Result<Self> {
598-
let mut sbi = SortedBuildIterator { builds };
599-
600-
sbi.sort_by_build_option_values(builds_with_impossible_requests)
601-
.await;
602-
Ok(sbi)
603-
}
604-
605594
/// Helper for making BuildKey structures used in the sorting in
606595
/// sort_by_build_option_values() below
607-
fn make_option_values_build_key(
596+
pub fn make_option_values_build_key(
608597
spec: &Spec,
609598
ordered_names: &Vec<OptNameBuf>,
610599
build_name_values: &HashMap<BuildIdent, OptionMap>,
@@ -632,8 +621,11 @@ impl SortedBuildIterator {
632621
) {
633622
let start = Instant::now();
634623

635-
let (key_entry_names, build_name_values) =
636-
BuildToSortedOptName::sort_builds(self.builds.iter().flat_map(|hm| hm.values()));
624+
let (key_entry_names, build_name_values) = BuildToSortedOptName::sort_builds(
625+
self.builds
626+
.iter()
627+
.flat_map(|hm| hm.values().map(|(spec, _src)| spec)),
628+
);
637629

638630
// Sort the builds by their generated keys generated from the
639631
// ordered names and values worth including.

crates/spk-solve/src/cdcl_solver/spk_provider.rs

+177-22
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
use std::borrow::Cow;
66
use std::cell::RefCell;
7-
use std::collections::{BTreeSet, HashMap, HashSet, VecDeque};
7+
use std::collections::{BTreeSet, HashMap, HashSet};
8+
use std::ops::Not;
89
use std::str::FromStr;
910
use std::sync::Arc;
1011

12+
use itertools::Itertools;
1113
use resolvo::utils::Pool;
1214
use resolvo::{
1315
Candidates,
@@ -38,8 +40,18 @@ use spk_schema::name::{OptNameBuf, PkgNameBuf};
3840
use spk_schema::prelude::{HasVersion, Named};
3941
use spk_schema::version::Version;
4042
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};
4355
use spk_storage::RepositoryHandle;
4456
use tracing::{debug_span, Instrument};
4557

@@ -338,6 +350,53 @@ enum CanBuildFromSource {
338350
No(StringId),
339351
}
340352

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+
341400
pub(crate) struct SpkProvider {
342401
pub(crate) pool: Pool<RequestVS, ResolvoPackageName>,
343402
repos: Vec<Arc<RepositoryHandle>>,
@@ -565,6 +624,21 @@ impl SpkProvider {
565624
self.cancel_solving.borrow().is_some()
566625
}
567626

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+
568642
fn request_to_known_dependencies(&self, requirement: &Request) -> KnownDependencies {
569643
let mut known_deps = KnownDependencies::default();
570644
match requirement {
@@ -666,23 +740,38 @@ impl SpkProvider {
666740
/// solve as a candidate.
667741
///
668742
/// Generally this means a build with newer dependencies is ordered first.
669-
async fn sort_builds(
743+
fn sort_builds(
670744
&self,
671-
a: &LocatedBuildIdentWithComponent,
672-
b: &LocatedBuildIdentWithComponent,
745+
build_key_index: &HashMap<SolvableId, BuildKey>,
746+
a: (SolvableId, &LocatedBuildIdentWithComponent),
747+
b: (SolvableId, &LocatedBuildIdentWithComponent),
673748
) -> std::cmp::Ordering {
674749
// 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).
677752

678753
// 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()) {
680755
(true, false) => return std::cmp::Ordering::Greater,
681756
(false, true) => return std::cmp::Ordering::Less,
682757
_ => {}
683758
};
684759

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)
686775
}
687776

688777
pub fn var_requirements(&mut self, requests: &[Request]) -> Vec<VersionSetId> {
@@ -995,32 +1084,94 @@ impl DependencyProvider for SpkProvider {
9951084
}
9961085

9971086
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
9991095
.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);
10031098
match &solvable.record {
10041099
SpkSolvable::LocatedBuildIdentWithComponent(
10051100
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+
}
10071111
_ => None,
10081112
}
10091113
})
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+
)
10101121
.collect::<Vec<_>>();
10111122

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());
10131152

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+
}
10161167
}
10171168

10181169
// TODO: The ordering should take component names into account, so
10191170
// the run component or the build component is tried first in the
10201171
// 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);
10241175
match (&a.record, &b.record) {
10251176
(
10261177
SpkSolvable::LocatedBuildIdentWithComponent(a),
@@ -1052,7 +1203,11 @@ impl DependencyProvider for SpkProvider {
10521203
(_, Build::Source) => return std::cmp::Ordering::Less,
10531204
_ => {}
10541205
};
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+
)
10561211
}
10571212
ord => ord,
10581213
}

0 commit comments

Comments
 (0)