-
Notifications
You must be signed in to change notification settings - Fork 29
feat: support multiversion packages #239
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2379,3 +2379,143 @@ fn shared_requires_gate_survives_backtrack() { | |
| } | ||
| } | ||
| } | ||
|
|
||
| mod allow_multiple_versions { | ||
| use super::*; | ||
|
|
||
| /// Non-overlapping version requirements are unsolvable without `allow_multiple`. | ||
| #[test] | ||
| fn test_non_overlapping_requirements_unsat_without_allow_multiple() { | ||
| let mut provider = BundleBoxProvider::new(); | ||
| provider.add_package("kernel", 1.into(), &[], &[]); | ||
| provider.add_package("kernel", 2.into(), &[], &[]); | ||
| provider.add_package("kernel", 3.into(), &[], &[]); | ||
| provider.add_package("kmod-a", 1.into(), &["kernel 1..2"], &[]); | ||
| provider.add_package("kmod-b", 1.into(), &["kernel 3..4"], &[]); | ||
|
|
||
| let result = solve_unsat(provider, &["kmod-a", "kmod-b"]); | ||
| assert_snapshot!(result); | ||
| } | ||
|
|
||
| /// When `allow_multiple` is set, non-overlapping version requirements | ||
| /// from different dependents are solved by installing multiple versions. | ||
| #[test] | ||
| fn test_non_overlapping_requirements() { | ||
| let mut provider = BundleBoxProvider::new(); | ||
| provider.add_package("kernel", 1.into(), &[], &[]); | ||
| provider.add_package("kernel", 2.into(), &[], &[]); | ||
| provider.add_package("kernel", 3.into(), &[], &[]); | ||
| provider.set_allow_multiple("kernel"); | ||
| provider.add_package("kmod-a", 1.into(), &["kernel 1..2"], &[]); | ||
| provider.add_package("kmod-b", 1.into(), &["kernel 3..4"], &[]); | ||
|
|
||
| let requirements = provider.requirements(&["kmod-a", "kmod-b"]); | ||
| let mut solver = Solver::new(provider); | ||
| let problem = Problem::new().requirements(requirements); | ||
| let solved = solver.solve(problem).unwrap(); | ||
| let result = transaction_to_string(solver.provider(), &solved); | ||
| assert_snapshot!(result, @r" | ||
| kernel=1 | ||
| kernel=3 | ||
| kmod-a=1 | ||
| kmod-b=1 | ||
| "); | ||
| } | ||
|
|
||
| /// When dependents' version requirements overlap, each requirement independently | ||
| /// selects its best (highest) candidate. Here kmod-a picks kernel=2 (highest in 1..3) | ||
| /// and kmod-b picks kernel=3 (highest in 2..4), so both are installed even though | ||
| /// kernel=2 alone would satisfy both requirements. | ||
| /// | ||
| /// This is actually NOT ideal - installing only kernel=2 would be optimal - but resolving | ||
| /// overlapping ranges to a single common version would require additional optimization in | ||
| /// the solver (e.g. soft forbid clauses or a post-solve minimization pass). | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Notable. I'm not particularly fussed about this at the moment but it is probably worth doing eventually.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless you want me to do it now.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @baszalmstra Let me know your thoughts.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @baszalmstra Can I at least get feedback on whether I need to deal with this now or if it can be deferred until later? I'd like to get this PR out of the way so that I can rebase the other work
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah sorry, this slipped of my radar. I think we can deal with the optimal solution later. |
||
| #[test] | ||
| fn test_overlapping_requirements() { | ||
| let mut provider = BundleBoxProvider::new(); | ||
| provider.add_package("kernel", 1.into(), &[], &[]); | ||
| provider.add_package("kernel", 2.into(), &[], &[]); | ||
| provider.add_package("kernel", 3.into(), &[], &[]); | ||
| provider.set_allow_multiple("kernel"); | ||
| provider.add_package("kmod-a", 1.into(), &["kernel 1..3"], &[]); | ||
| provider.add_package("kmod-b", 1.into(), &["kernel 2..4"], &[]); | ||
|
|
||
| let requirements = provider.requirements(&["kmod-a", "kmod-b"]); | ||
| let mut solver = Solver::new(provider); | ||
| let problem = Problem::new().requirements(requirements); | ||
| let solved = solver.solve(problem).unwrap(); | ||
| let result = transaction_to_string(solver.provider(), &solved); | ||
| assert_snapshot!(result, @r" | ||
| kernel=2 | ||
| kernel=3 | ||
| kmod-a=1 | ||
| kmod-b=1 | ||
| "); | ||
| } | ||
|
|
||
| /// `allow_multiple` works correctly when the multiversion package is required by a | ||
| /// transitive dependency discovered in a later solver pass (i.e. a different | ||
| /// Encoder invocation than the one that first processed the package's candidates). | ||
| /// Uses two `allow_multiple` packages with version-pinned dependencies between them: | ||
| /// each kmod version is built against a specific kernel, so installing multiple kmod | ||
| /// versions transitively pulls in multiple kernels. | ||
| /// | ||
| /// The explanation may sound a bit tormented, but it exists to catch a regression | ||
| /// encountered with the original implementation. | ||
| #[test] | ||
| fn test_allow_multiple_transitive() { | ||
| let mut provider = BundleBoxProvider::new(); | ||
| provider.add_package("kernel", 1.into(), &[], &[]); | ||
| provider.add_package("kernel", 2.into(), &[], &[]); | ||
| provider.add_package("kernel", 3.into(), &[], &[]); | ||
| provider.set_allow_multiple("kernel"); | ||
| provider.add_package("kmod", 1.into(), &["kernel 1..2"], &[]); | ||
| provider.add_package("kmod", 2.into(), &["kernel 2..3"], &[]); | ||
| provider.add_package("kmod", 3.into(), &["kernel 3..4"], &[]); | ||
| provider.set_allow_multiple("kmod"); | ||
| provider.add_package("app-old", 1.into(), &["kmod 1..2"], &[]); | ||
| provider.add_package("app-new", 1.into(), &["kmod 3..4"], &[]); | ||
|
|
||
| let requirements = provider.requirements(&["app-old", "app-new"]); | ||
| let mut solver = Solver::new(provider); | ||
| let problem = Problem::new().requirements(requirements); | ||
| let solved = solver.solve(problem).unwrap(); | ||
| let result = transaction_to_string(solver.provider(), &solved); | ||
| assert_snapshot!(result, @r" | ||
| app-new=1 | ||
| app-old=1 | ||
| kernel=1 | ||
| kernel=3 | ||
| kmod=1 | ||
| kmod=3 | ||
| "); | ||
| } | ||
|
|
||
| /// Soft requirements model the "already installed" scenario: the solver tries to keep | ||
| /// each requested version, but when one is excluded the solve still succeeds with the | ||
| /// multiple remaining versions. | ||
| #[test] | ||
| fn test_soft_requirements_with_allow_multiple() { | ||
| let mut provider = BundleBoxProvider::new(); | ||
| provider.add_package("kernel", 1.into(), &[], &[]); | ||
| provider.add_package("kernel", 2.into(), &[], &[]); | ||
| provider.add_package("kernel", 3.into(), &[], &[]); | ||
| provider.set_allow_multiple("kernel"); | ||
| provider.exclude("kernel", 1, "retracted due to security issue"); | ||
|
|
||
| let k1 = provider.solvable_id("kernel", 1u32); | ||
| let k2 = provider.solvable_id("kernel", 2u32); | ||
| let k3 = provider.solvable_id("kernel", 3u32); | ||
| let requirements = provider.requirements(&["kernel"]); | ||
| let mut solver = Solver::new(provider); | ||
| let problem = Problem::new() | ||
| .requirements(requirements) | ||
| .soft_requirements(vec![k1, k2, k3]); | ||
| let solved = solver.solve(problem).unwrap(); | ||
| let result = transaction_to_string(solver.provider(), &solved); | ||
| assert_snapshot!(result, @r" | ||
| kernel=2 | ||
| kernel=3 | ||
| "); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| --- | ||
| source: tests/solver/main.rs | ||
| expression: result | ||
| --- | ||
| The following packages are incompatible | ||
| ├─ kmod-a * can be installed with any of the following options: | ||
| │ └─ kmod-a 1 would require | ||
| │ └─ kernel >=1, <2, which can be installed with any of the following options: | ||
| │ └─ kernel 1 | ||
| └─ kmod-b * cannot be installed because there are no viable options: | ||
| └─ kmod-b 1 would require | ||
| └─ kernel >=3, <4, which cannot be installed because there are no viable options: | ||
| └─ kernel 3, which conflicts with the versions reported above. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It likely doesn't matter much but you could probably give the branch predictor a hint here that this path is unlikely to be taken.