Skip to content

Commit f913894

Browse files
author
J Robert Ray
committed
Fix resolvo solver with migration-to-components
The solver tests were not passing if this feature is enabled, and this exposed a few problems in not handling ':all' requests correctly. When generating the "VersionSet" for dependencies, the PkgRequest needs to have the correct component populated. The logic for skipping or not skipping candidates when build from source needed to handle ':all' and it was restructured for clarity. When filtering candidates for a request of a component of a specific build, source builds needed to be filtered out. Signed-off-by: J Robert Ray <[email protected]>
1 parent 744649f commit f913894

File tree

5 files changed

+100
-21
lines changed

5 files changed

+100
-21
lines changed

crates/spk-schema/crates/foundation/src/ident_build/build.rs

+4
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ impl Build {
130130
}
131131
}
132132

133+
pub fn is_buildid(&self) -> bool {
134+
matches!(self, Build::BuildId(_))
135+
}
136+
133137
pub fn is_source(&self) -> bool {
134138
matches!(self, Build::Source)
135139
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use rstest::rstest;
88
use spk_schema::prelude::HasVersion;
99
use spk_schema::{opt_name, Package};
1010
use spk_solve_macros::{make_repo, request};
11+
use tap::TapFallible;
1112

1213
use super::Solver;
1314
use crate::cdcl_solver::AbstractSolverMut;
@@ -101,7 +102,7 @@ async fn package_with_dependency() {
101102

102103
let mut solver = Solver::new(vec![repo.into()], Cow::Borrowed(&[]));
103104
solver.add_request(request!("needs-dep/1.0.0"));
104-
let solution = solver.solve().await.unwrap();
105+
let solution = solver.solve().await.tap_err(|e| eprintln!("{e}")).unwrap();
105106
assert_eq!(solution.len(), 2);
106107
}
107108

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,16 @@ impl AbstractSolver for Solver {
322322

323323
#[async_trait::async_trait]
324324
impl AbstractSolverMut for Solver {
325-
fn add_request(&mut self, request: Request) {
325+
fn add_request(&mut self, mut request: Request) {
326+
if let Request::Pkg(request) = &mut request {
327+
if request.pkg.components.is_empty() {
328+
if request.pkg.is_source() {
329+
request.pkg.components.insert(Component::Source);
330+
} else {
331+
request.pkg.components.insert(Component::default_for_run());
332+
}
333+
}
334+
}
326335
self.requests.push(request);
327336
}
328337

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

+77-17
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ use crate::AbstractSolverMut;
6969
// be a relationship between every component of a package and a "base"
7070
// component, to prevent a solve containing a mix of components from different
7171
// versions of the same package.
72-
#[derive(Clone, Eq, Hash, PartialEq)]
72+
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
7373
pub(crate) struct PkgNameBufWithComponent {
7474
pub(crate) name: PkgNameBuf,
7575
pub(crate) component: SyntheticComponent,
@@ -128,7 +128,10 @@ impl ResolvoPackageName {
128128
None
129129
}
130130
ResolvoPackageName::PkgNameBufWithComponent(pkg_name) => {
131-
let mut located_builds = Vec::new();
131+
// Prevent duplicate solvables by using a set. Ties (same build
132+
// but "requires_build_from_source" differs) are resolved with
133+
// "requires_build_from_source" being true.
134+
let mut located_builds = HashSet::new();
132135

133136
let root_pkg_request = provider.global_pkg_requests.get(&pkg_name.name);
134137

@@ -201,23 +204,62 @@ impl ResolvoPackageName {
201204
// when a request for it is found it can act as a
202205
// surrogate that depends on all the individual
203206
// components.
204-
[Component::All],
207+
{
208+
if !build.is_source() {
209+
itertools::Either::Left([Component::All].into_iter())
210+
} else {
211+
// XXX: Unclear if this is the right
212+
// approach but without this special
213+
// case the Solution can incorrectly
214+
// end up with a src build marked as
215+
// requires_build_from_source for
216+
// requests that are asking for
217+
// :src.
218+
itertools::Either::Right([].into_iter())
219+
}
220+
},
205221
) {
206-
if component != *pkg_name_component
207-
&& (provider.binary_only || !component.is_source())
222+
let requires_build_from_source = build.is_source()
223+
&& (component != *pkg_name_component
224+
|| pkg_name_component.is_all());
225+
226+
if requires_build_from_source && provider.binary_only {
227+
// Deny anything that requires build
228+
// from source when binary_only is
229+
// enabled.
230+
continue;
231+
}
232+
233+
if (!requires_build_from_source || !build.is_source())
234+
&& component != *pkg_name_component
208235
{
236+
// Deny components that don't match
237+
// unless it is possible to build from
238+
// source.
209239
continue;
210240
}
211241

212-
located_builds.push(LocatedBuildIdentWithComponent {
242+
let new_entry = LocatedBuildIdentWithComponent {
213243
ident: located_build_ident.clone(),
214244
component: pkg_name.component.clone(),
215-
requires_build_from_source: component
216-
!= *pkg_name_component,
217-
});
245+
requires_build_from_source,
246+
};
247+
248+
if requires_build_from_source {
249+
// _replace_ any existing entry, which
250+
// might have
251+
// requires_build_from_source == false,
252+
// so it now is true.
253+
located_builds.replace(new_entry);
254+
} else {
255+
// _insert_ to not overwrite any
256+
// existing entry that might have
257+
// requires_build_from_source == true.
258+
located_builds.insert(new_entry);
259+
}
218260
}
219261
} else {
220-
located_builds.push(LocatedBuildIdentWithComponent {
262+
located_builds.insert(LocatedBuildIdentWithComponent {
221263
ident: located_build_ident,
222264
component: SyntheticComponent::Base,
223265
requires_build_from_source: false,
@@ -538,21 +580,21 @@ impl SpkProvider {
538580
fn pkg_request_to_known_dependencies(&self, pkg_request: &PkgRequest) -> KnownDependencies {
539581
let mut components = pkg_request.pkg.components.iter().peekable();
540582
let iter = if components.peek().is_some() {
541-
itertools::Either::Right(components.cloned().map(SyntheticComponent::Actual))
583+
itertools::Either::Right(components.cloned())
542584
} else {
543585
itertools::Either::Left(
544586
// A request with no components is assumed to be a request for
545-
// the run (or source) component.
587+
// the default_for_run (or source) component.
546588
if pkg_request
547589
.pkg
548590
.build
549591
.as_ref()
550592
.map(|build| build.is_source())
551593
.unwrap_or(false)
552594
{
553-
std::iter::once(SyntheticComponent::Actual(Component::Source))
595+
std::iter::once(Component::Source)
554596
} else {
555-
std::iter::once(SyntheticComponent::Actual(Component::Run))
597+
std::iter::once(Component::default_for_run())
556598
},
557599
)
558600
};
@@ -566,12 +608,14 @@ impl SpkProvider {
566608
.intern_package_name(ResolvoPackageName::PkgNameBufWithComponent(
567609
PkgNameBufWithComponent {
568610
name: pkg_request.pkg.name().to_owned(),
569-
component,
611+
component: SyntheticComponent::Actual(component.clone()),
570612
},
571613
));
614+
let mut pkg_request_with_component = pkg_request.clone();
615+
pkg_request_with_component.pkg.components = BTreeSet::from_iter([component]);
572616
let dep_vs = self.pool.intern_version_set(
573617
dep_name,
574-
RequestVS::SpkRequest(Request::Pkg(pkg_request.clone())),
618+
RequestVS::SpkRequest(Request::Pkg(pkg_request_with_component)),
575619
);
576620
match pkg_request.inclusion_policy {
577621
spk_schema::ident::InclusionPolicy::Always => {
@@ -761,7 +805,7 @@ impl SpkProvider {
761805

762806
// If neither build has a key, both packages failed to load?
763807
// Add debug assert to see if this ever happens.
764-
debug_assert!(false, "builds without keys");
808+
debug_assert!(false, "builds without keys {a:?} {b:?}");
765809

766810
a.1.ident.cmp(&b.1.ident)
767811
}
@@ -885,6 +929,22 @@ impl DependencyProvider for SpkProvider {
885929
// a candidate. Source builds that can't be built from
886930
// source are filtered out in `get_candidates`.
887931
if located_build_ident_with_component.requires_build_from_source {
932+
// However, building from source is not a suitable
933+
// candidate for a request for a specific component
934+
// of an existing build, such as when finding the
935+
// members of the :all component of a build.
936+
if pkg_request
937+
.pkg
938+
.build
939+
.as_ref()
940+
.is_some_and(|b| b.is_buildid())
941+
{
942+
if inverse {
943+
selected.push(*candidate);
944+
}
945+
continue;
946+
}
947+
888948
if !inverse {
889949
selected.push(*candidate);
890950
}

crates/spk-solve/src/solver_test.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,8 @@ async fn test_solver_package_with_no_recipe_and_impossible_initial_checks(
235235
let res = run_and_print_resolve_for_tests(&mut solver).await;
236236
if cfg!(feature = "migration-to-components") {
237237
match res {
238-
Err(Error::InitialRequestsContainImpossibleError(_)) => {
238+
Err(Error::InitialRequestsContainImpossibleError(_))
239+
| Err(Error::FailedToResolve(_)) => {
239240
// Success, when the 'migration-to-components' feature
240241
// is enabled because the initial checks for
241242
// impossible requests fail because the package does
@@ -332,7 +333,11 @@ async fn test_solver_package_with_no_recipe_from_cmd_line_and_impossible_initial
332333
// :build and a :run component to pass and it only has a :run
333334
// component
334335
assert!(
335-
matches!(res, Err(Error::InitialRequestsContainImpossibleError(_))),
336+
matches!(
337+
res,
338+
Err(Error::InitialRequestsContainImpossibleError(_))
339+
| Err(Error::FailedToResolve(_))
340+
),
336341
"'{res:?}' should be a Error::String('Initial requests contain 1 impossible request.')",
337342
);
338343
} else {

0 commit comments

Comments
 (0)