`.
+- The types `Dependencies` and `DependencyConstraints` were introduced to clarify intent.
+- New function `choose_package_with_fewest_versions` to help implement
+ the `choose_package_version` method of a `DependencyProvider`.
+- Implement `FromStr` for `SemanticVersion`.
+- Add the `VersionParseError` type for parsing of semantic versions.
+
+#### Changed
+
+- The `Solver` trait was replaced by a `DependencyProvider` trait
+ which now must implement a `choose_package_version` method
+ instead of `list_available_versions`.
+ So it now has the ability to choose a package in addition to a version.
+ The `DependencyProvider` also has a new optional method `should_cancel`
+ that may be used to stop the solver if needed.
+- The `choose_package_version` and `get_dependencies` methods of the
+ `DependencyProvider` trait now take an immutable reference to `self`.
+ Interior mutability can be used by implementor if mutability is needed.
+- The `Solver.run` method was thus replaced by a free function `solver::resolve`
+ taking a dependency provider as first argument.
+- The `OfflineSolver` is thus replaced by an `OfflineDependencyProvider`.
+- `SemanticVersion` now takes `u32` instead of `usize` for its 3 parts.
+- `NumberVersion` now uses `u32` instead of `usize`.
+
+#### Removed
+
+- `ErrorRetrievingVersions` variant of `error::PubGrubError`.
+
+### Changes in the internal parts of the API
+
+#### Added
+
+- `benches/large_case.rs` enables benchmarking of serialized registries of packages.
+- `examples/caching_dependency_provider.rs` an example dependency provider caching dependencies.
+- `PackageTerm
= (P, Term)` new type alias for readability.
+- `Memory.term_intersection_for_package(&mut self, package: &P) -> Option<&Term>`
+- New types were introduces for conflict resolution in `internal::partial_solution`
+ to clarify the intent and return values of some functions.
+ Those types are `DatedAssignment` and `SatisfierAndPreviousHistory`.
+- `PartialSolution.term_intersection_for_package` calling the same function
+ from its `memory`.
+- New property tests for ranges: `negate_contains_opposite`, `intesection_contains_both`
+ and `union_contains_either`.
+- A large synthetic test case was added in `test-examples/`.
+- A new test example `double_choices` was added
+ for the detection of a bug (fixed) in the implementation.
+- Property testing of big synthetic datasets was added in `tests/proptest.rs`.
+- Comparison of PubGrub solver and a SAT solver
+ was added with `tests/sat_dependency_provider.rs`.
+- Other regression and unit tests were added to `tests/tests.rs`.
+
+#### Changed
+
+- CI workflow was improved (`./github/workflows/`), including a check for [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) and [Clippy ](https://github.com/rust-lang/rust-clippy) for source code linting.
+- Using SPDX license identifiers instead of MPL-2.0 classic file headers.
+- `State.incompatibilities` is now wrapped inside a `Rc`.
+- `DecisionLevel(u32)` is used in place of `usize` for partial solution decision levels.
+- `State.conflict_resolution` now also returns the almost satisfied package
+ to avoid an unnecessary call to `self.partial_solution.relation(...)` after conflict resolution.
+- `Kind::NoVersion` renamed to `Kind::NoVersions` and all other usage of `noversion`
+ has been changed to `no_versions`.
+- Variants of the `incompatibility::Relation` enum have changed.
+- Incompatibility now uses a deterministic hasher to store packages in its hash map.
+- `incompatibility.relation(...)` now takes a function as argument to avoid computations
+ of unnecessary terms intersections.
+- `Memory` now uses a deterministic hasher instead of the default one.
+- `memory::PackageAssignments` is now an enum instead of a struct.
+- Derivations in a `PackageAssignments` keep a precomputed intersection of derivation terms.
+- `potential_packages` method now returns a `Range`
+ instead of a `Term` for the versions constraint of each package.
+- `PartialSolution.relation` now takes `&mut self` instead of `&self`
+ to be able to store computation of terms intersection.
+- `Term.accept_version` was renamed `Term.contains`.
+- The `satisfied_by` and `contradicted_by` methods of a `Term`
+ now directly takes a reference to the intersection of other terms.
+ Same for `relation_with`.
+
+#### Removed
+
+- `term` field of an `Assignment::Derivation` variant.
+- `Memory.all_terms` method was removed.
+- `Memory.remove_decision` method was removed in favor of a check before using `Memory.add_decision`.
+- `PartialSolution` methods `pick_package` and `pick_version` have been removed
+ since control was given back to the dependency provider to choose a package version.
+- `PartialSolution` methods `remove_last_decision` and `satisfies_any_of` were removed
+ in favor of a preventive check before calling `add_decision`.
+- `Term.is_negative`.
+
+#### Fixed
+
+- Prior cause computation (`incompatibility::prior_cause`) now uses the intersection of package terms
+ instead of their union, which was an implementation error.
+
+## [0.1.0] - 2020-10-01
+
+### Added
+
+- `README.md` as the home page of this repository.
+- `LICENSE`, code is provided under the MPL 2.0 license.
+- `Cargo.toml` configuration of this Rust project.
+- `src/` containing all the source code for this first implementation of PubGrub in Rust.
+- `tests/` containing test end-to-end examples.
+- `examples/` other examples, not in the form of tests.
+- `.gitignore` configured for a Rust project.
+- `.github/workflows/` CI to automatically build, test and document on push and pull requests.
+
+[0.2.0]: https://github.com/pubgrub-rs/pubgrub/releases/tag/v0.2.0
+[0.1.0]: https://github.com/pubgrub-rs/pubgrub/releases/tag/v0.1.0
+[diff-unreleased]: https://github.com/pubgrub-rs/pubgrub/compare/release...dev
+[diff-0.2.0]: https://github.com/mpizenberg/elm-pointer-events/compare/v0.1.0...v0.2.0
diff --git a/Cargo.toml b/Cargo.toml
index d936f89a..37a4e88b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,9 @@
+# SPDX-License-Identifier: MPL-2.0
+
[package]
name = "pubgrub"
-version = "0.1.0"
-authors = ["Matthieu Pizenberg ", "Alex Tokarev "]
+version = "0.2.0"
+authors = ["Matthieu Pizenberg ", "Alex Tokarev ", "Jacob Finkelman "]
edition = "2018"
description = "PubGrub version solving algorithm"
readme = "README.md"
@@ -9,12 +11,22 @@ repository = "https://github.com/mpizenberg/pubgrub-rs"
license = "MPL-2.0"
keywords = ["dependency", "pubgrub", "semver", "solver", "version"]
categories = ["algorithms"]
-include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples/**"]
+include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples/**", "benches/**"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = "1.0"
+rustc-hash = "1.1.0"
+serde = { version = "1.0", features = ["derive"], optional = true }
[dev-dependencies]
proptest = "0.10.1"
+ron="0.6"
+varisat="0.2.2"
+criterion = "0.3"
+
+[[bench]]
+name = "large_case"
+harness = false
+required-features = ["serde"]
diff --git a/README.md b/README.md
index 8481f3de..2bddc3f4 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,17 @@
-# PubGrub version solving algorithm.
+# PubGrub version solving algorithm
-[package documentation](https://mpizenberg.github.io/pubgrub-rs/pubgrub/)
+![license](https://img.shields.io/crates/l/pubgrub.svg)
+[![crates.io](https://img.shields.io/crates/v/pubgrub.svg?logo=rust)][crates]
+[![docs.rs](https://img.shields.io/badge/docs.rs-pubgrub-yellow)][docs]
+[![guide](https://img.shields.io/badge/guide-pubgrub-pink?logo=read-the-docs)][guide]
-It consists in efficiently finding a set of packages and versions
+Version solving consists in efficiently finding a set of packages and versions
that satisfy all the constraints of a given project dependencies.
In addition, when that is not possible,
PubGrub tries to provide a very human-readable and clear
explanation as to why that failed.
-Below is an example of an explanation present in
-the introductory blog post about PubGrub.
+The [introductory blog post about PubGrub][medium-pubgrub] presents
+one such example of failure explanation:
```txt
Because dropdown >=2.0.0 depends on icons >=2.0.0 and
@@ -25,32 +28,31 @@ So, because root depends on both menu >=1.0.0 and intl >=5.0.0,
version solving failed.
```
-The algorithm is generic and works for any type of dependency system
+This pubgrub crate provides a Rust implementation of PubGrub.
+It is generic and works for any type of dependency system
as long as packages (P) and versions (V) implement
-the `Package` and `Version` traits.
-`Package` is strictly equivalent and automatically generated
-for any type that implement `Clone + Eq + Hash + Debug + Display`.
-`Version` simply states that versions are ordered,
-that there should be
-a minimal `lowest` version (like 0.0.0 in semantic versions),
-and that for any version, it is possible to compute
-what the next version closest to this one is (`bump`).
-For semantic versions, `bump` corresponds to an increment of the patch number.
+the provided `Package` and `Version` traits.
-## API
+## Using the pubgrub crate
-```rust
-solution = solver.run(package, version)?;
-```
+A [guide][guide] with both high-level explanations and
+in-depth algorithm details is available online.
+The [API documentation is available on docs.rs][docs].
+A version of the [API docs for the unreleased functionality][docs-dev] from `dev` branch is also
+accessible for convenience.
+
+
+## Contributing
+
+Discussion and development happens here on GitHub and on our
+[Zulip stream](https://rust-lang.zulipchat.com/#narrow/stream/260232-t-cargo.2FPubGrub).
+Please join in!
-Where `solver` provides the list of available packages and versions,
-as well as the dependencies of every available package
-by implementing the `Solver` trait.
-The call to `run` for a given package at a given version
-will compute the set of packages and versions needed
-to satisfy the dependencies of that package and version pair.
-If there is no solution, the reason will be provided as clear as possible.
+Remember to always be considerate of others,
+who may have different native languages, cultures and experiences.
+We want everyone to feel welcomed,
+let us know with a private message on Zulip if you don't feel that way.
## PubGrub
@@ -64,12 +66,18 @@ An introductory blog post was
[published on Medium][medium-pubgrub] by its author.
The detailed explanation of the algorithm is
-[provided on GitHub][github-pubgrub].
+[provided on GitHub][github-pubgrub],
+and complemented by the ["Internals" section of our guide][guide-internals].
The foundation of the algorithm is based on ASP (Answer Set Programming),
and a book called
"[Answer Set Solving in Practice][potassco-book]"
by Martin Gebser, Roland Kaminski, Benjamin Kaufmann and Torsten Schaub.
+[crates]: https://crates.io/crates/pubgrub
+[guide]: https://pubgrub-rs-guide.netlify.app/
+[guide-internals]: https://pubgrub-rs-guide.netlify.app/internals/intro.html
+[docs]: https://docs.rs/pubgrub
+[docs-dev]: https://pubgrub-rs.github.io/pubgrub/pubgrub/
[medium-pubgrub]: https://medium.com/@nex3/pubgrub-2fb6470504f
[github-pubgrub]: https://github.com/dart-lang/pub/blob/master/doc/solver.md
[potassco-book]: https://potassco.org/book/
diff --git a/benches/large_case.rs b/benches/large_case.rs
new file mode 100644
index 00000000..ad9f486d
--- /dev/null
+++ b/benches/large_case.rs
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: MPL-2.0
+use std::time::Duration;
+
+extern crate criterion;
+use self::criterion::*;
+
+use pubgrub::solver::{resolve, OfflineDependencyProvider};
+use pubgrub::version::NumberVersion;
+
+fn bench_nested(c: &mut Criterion) {
+ let mut group = c.benchmark_group("large_cases");
+ group.measurement_time(Duration::from_secs(20));
+
+ for case in std::fs::read_dir("test-examples").unwrap() {
+ let case = case.unwrap().path();
+
+ group.bench_function(
+ format!("{}", case.file_name().unwrap().to_string_lossy()),
+ |b| {
+ let s = std::fs::read_to_string(&case).unwrap();
+ let dependency_provider: OfflineDependencyProvider =
+ ron::de::from_str(&s).unwrap();
+
+ b.iter(|| {
+ for &n in dependency_provider.versions(&0).unwrap() {
+ let _ = resolve(&dependency_provider, 0, n);
+ }
+ });
+ },
+ );
+ }
+
+ group.finish();
+}
+
+criterion_group!(benches, bench_nested);
+criterion_main!(benches);
diff --git a/examples/branching_error_reporting.rs b/examples/branching_error_reporting.rs
index 2747ba0d..c1daa1bf 100644
--- a/examples/branching_error_reporting.rs
+++ b/examples/branching_error_reporting.rs
@@ -1,21 +1,23 @@
+// SPDX-License-Identifier: MPL-2.0
+
use pubgrub::error::PubGrubError;
use pubgrub::range::Range;
use pubgrub::report::{DefaultStringReporter, Reporter};
-use pubgrub::solver::{OfflineSolver, Solver};
+use pubgrub::solver::{resolve, OfflineDependencyProvider};
use pubgrub::version::SemanticVersion;
// https://github.com/dart-lang/pub/blob/master/doc/solver.md#branching-error-reporting
fn main() {
- let mut solver = OfflineSolver::<&str, SemanticVersion>::new();
+ let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new();
#[rustfmt::skip]
// root 1.0.0 depends on foo ^1.0.0
- solver.add_dependencies(
+ dependency_provider.add_dependencies(
"root", (1, 0, 0),
vec![("foo", Range::between((1, 0, 0), (2, 0, 0)))],
);
#[rustfmt::skip]
// foo 1.0.0 depends on a ^1.0.0 and b ^1.0.0
- solver.add_dependencies(
+ dependency_provider.add_dependencies(
"foo", (1, 0, 0),
vec![
("a", Range::between((1, 0, 0), (2, 0, 0))),
@@ -24,7 +26,7 @@ fn main() {
);
#[rustfmt::skip]
// foo 1.1.0 depends on x ^1.0.0 and y ^1.0.0
- solver.add_dependencies(
+ dependency_provider.add_dependencies(
"foo", (1, 1, 0),
vec![
("x", Range::between((1, 0, 0), (2, 0, 0))),
@@ -33,28 +35,28 @@ fn main() {
);
#[rustfmt::skip]
// a 1.0.0 depends on b ^2.0.0
- solver.add_dependencies(
+ dependency_provider.add_dependencies(
"a", (1, 0, 0),
vec![("b", Range::between((2, 0, 0), (3, 0, 0)))],
);
// b 1.0.0 and 2.0.0 have no dependencies.
- solver.add_dependencies("b", (1, 0, 0), vec![]);
- solver.add_dependencies("b", (2, 0, 0), vec![]);
+ dependency_provider.add_dependencies("b", (1, 0, 0), vec![]);
+ dependency_provider.add_dependencies("b", (2, 0, 0), vec![]);
#[rustfmt::skip]
// x 1.0.0 depends on y ^2.0.0.
- solver.add_dependencies(
+ dependency_provider.add_dependencies(
"x", (1, 0, 0),
vec![("y", Range::between((2, 0, 0), (3, 0, 0)))],
);
// y 1.0.0 and 2.0.0 have no dependencies.
- solver.add_dependencies("y", (1, 0, 0), vec![]);
- solver.add_dependencies("y", (2, 0, 0), vec![]);
+ dependency_provider.add_dependencies("y", (1, 0, 0), vec![]);
+ dependency_provider.add_dependencies("y", (2, 0, 0), vec![]);
- // Run the solver.
- match solver.run("root", (1, 0, 0)) {
+ // Run the algorithm.
+ match resolve(&dependency_provider, "root", (1, 0, 0)) {
Ok(sol) => println!("{:?}", sol),
Err(PubGrubError::NoSolution(mut derivation_tree)) => {
- derivation_tree.collapse_noversion();
+ derivation_tree.collapse_no_versions();
eprintln!("{}", DefaultStringReporter::report(&derivation_tree));
std::process::exit(1);
}
diff --git a/examples/caching_dependency_provider.rs b/examples/caching_dependency_provider.rs
new file mode 100644
index 00000000..bac730ea
--- /dev/null
+++ b/examples/caching_dependency_provider.rs
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: MPL-2.0
+
+use std::cell::RefCell;
+use std::error::Error;
+
+use pubgrub::package::Package;
+use pubgrub::range::Range;
+use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider};
+use pubgrub::version::{NumberVersion, Version};
+
+// An example implementing caching dependency provider that will
+// store queried dependencies in memory and check them before querying more from remote.
+struct CachingDependencyProvider> {
+ remote_dependencies: DP,
+ cached_dependencies: RefCell>,
+}
+
+impl> CachingDependencyProvider
),
- /// Error arising when the implementer of `Solver`
- /// returned an error in the method `list_available_versions`.
- #[error("Retrieving available versions of package {package} failed)")]
- ErrorRetrievingVersions {
- /// Package for which we want the list of versions.
+ /// Error arising when the implementer of
+ /// [DependencyProvider](crate::solver::DependencyProvider)
+ /// returned an error in the method
+ /// [get_dependencies](crate::solver::DependencyProvider::get_dependencies).
+ #[error("Retrieving dependencies of {package} {version} failed")]
+ ErrorRetrievingDependencies {
+ /// Package whose dependencies we want.
package: P,
- /// Error raised by the implementer of `Solver`.
+ /// Version of the package for which we want the dependencies.
+ version: V,
+ /// Error raised by the implementer of
+ /// [DependencyProvider](crate::solver::DependencyProvider).
source: Box,
},
- /// Error arising when the implementer of `Solver`
- /// returned an error in the method `get_dependencies`.
- #[error("Retrieving dependencies of {package} {version} failed)")]
- ErrorRetrievingDependencies {
+ /// Error arising when the implementer of
+ /// [DependencyProvider](crate::solver::DependencyProvider)
+ /// returned a dependency on an empty range.
+ /// This technically means that the package can not be selected,
+ /// but is clearly some kind of mistake.
+ #[error("Package {dependent} required by {package} {version} depends on the empty set")]
+ DependencyOnTheEmptySet {
+ /// Package whose dependencies we want.
+ package: P,
+ /// Version of the package for which we want the dependencies.
+ version: V,
+ /// The dependent package that requires us to pick from the empty set.
+ dependent: P,
+ },
+
+ /// Error arising when the implementer of
+ /// [DependencyProvider](crate::solver::DependencyProvider)
+ /// returned a dependency on the requested package.
+ /// This technically means that the package directly depends on itself,
+ /// and is clearly some kind of mistake.
+ #[error("{package} {version} depends on itself")]
+ SelfDependency {
/// Package whose dependencies we want.
package: P,
/// Version of the package for which we want the dependencies.
version: V,
- /// Error raised by the implementer of `Solver`.
- source: Box,
},
+ /// Error arising when the implementer of
+ /// [DependencyProvider](crate::solver::DependencyProvider)
+ /// returned an error in the method
+ /// [choose_package_version](crate::solver::DependencyProvider::choose_package_version).
+ #[error("Decision making failed")]
+ ErrorChoosingPackageVersion(Box),
+
+ /// Error arising when the implementer of [DependencyProvider](crate::solver::DependencyProvider)
+ /// returned an error in the method [should_cancel](crate::solver::DependencyProvider::should_cancel).
+ #[error("We should cancel")]
+ ErrorInShouldCancel(Box),
+
/// Something unexpected happened.
#[error("{0}")]
Failure(String),
diff --git a/src/internal/assignment.rs b/src/internal/assignment.rs
index 801e1354..ac8f1270 100644
--- a/src/internal/assignment.rs
+++ b/src/internal/assignment.rs
@@ -1,6 +1,4 @@
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+// SPDX-License-Identifier: MPL-2.0
//! Assignments are the building blocks of a PubGrub partial solution.
//! (partial solution = the current state of the solution we are building in the algorithm).
@@ -26,8 +24,6 @@ pub enum Assignment {
Derivation {
/// The package corresponding to the derivation.
package: P,
- /// Term of the derivation.
- term: Term,
/// Incompatibility cause of the derivation.
cause: Incompatibility
,
},
@@ -42,13 +38,13 @@ impl Assignment
{
}
}
- /// Retrieve the current assignment as a `Term`.
+ /// Retrieve the current assignment as a [Term].
/// If this is decision, it returns a positive term with that exact version.
/// Otherwise, if this is a derivation, just returns its term.
pub fn as_term(&self) -> Term {
match &self {
Self::Decision { version, .. } => Term::exact(version.clone()),
- Self::Derivation { term, .. } => term.clone(),
+ Self::Derivation { package, cause } => cause.get(&package).unwrap().negate(),
}
}
}
diff --git a/src/internal/core.rs b/src/internal/core.rs
index 4239f34d..e67f3792 100644
--- a/src/internal/core.rs
+++ b/src/internal/core.rs
@@ -1,16 +1,14 @@
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+// SPDX-License-Identifier: MPL-2.0
//! Core model and functions
//! to write a functional PubGrub algorithm.
-use std::collections::HashSet as Set;
+use std::{collections::HashSet as Set, rc::Rc};
use crate::error::PubGrubError;
use crate::internal::assignment::Assignment::{Decision, Derivation};
use crate::internal::incompatibility::{Incompatibility, Relation};
-use crate::internal::partial_solution::PartialSolution;
+use crate::internal::partial_solution::{DecisionLevel, PartialSolution};
use crate::package::Package;
use crate::report::DerivationTree;
use crate::version::Version;
@@ -22,7 +20,7 @@ pub struct State {
root_version: V,
/// TODO: remove pub.
- pub incompatibilities: Vec>,
+ pub incompatibilities: Rc>>,
/// Partial solution.
/// TODO: remove pub.
@@ -30,8 +28,8 @@ pub struct State {
/// The store is the reference storage for all incompatibilities.
/// The id field in one incompatibility refers
- /// to the position in the `incompatibility_store` vec,
- /// NOT the position in the `incompatibilities` vec.
+ /// to the position in the [incompatibility_store](State::incompatibility_store) vec,
+ /// NOT the position in the [incompatibilities](State::incompatibilities) vec.
/// TODO: remove pub.
pub incompatibility_store: Vec>,
}
@@ -44,7 +42,7 @@ impl State
>(&mut self, gen_incompat: F) {
let incompat = gen_incompat(self.incompatibility_store.len());
self.incompatibility_store.push(incompat.clone());
- incompat.merge_into(&mut self.incompatibilities);
+ incompat.merge_into(Rc::make_mut(&mut self.incompatibilities));
}
/// Check if an incompatibility is terminal.
@@ -63,15 +61,14 @@ impl State
{
}
/// Unit propagation is the core mechanism of the solving algorithm.
- /// CF https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation
+ /// CF
pub fn unit_propagation(&mut self, package: P) -> Result<(), PubGrubError
> {
let mut current_package = package.clone();
let mut changed = vec![package];
loop {
// Iterate over incompatibilities in reverse order
// to evaluate first the newest incompatibilities.
- let mut loop_incompatibilities = self.incompatibilities.clone();
- while let Some(incompat) = loop_incompatibilities.pop() {
+ for incompat in Rc::clone(&self.incompatibilities).iter().rev() {
// We only care about that incompatibility if it contains the current package.
if incompat.get(¤t_package) == None {
continue;
@@ -80,26 +77,17 @@ impl State
{
// If the partial solution satisfies the incompatibility
// we must perform conflict resolution.
Relation::Satisfied => {
- let root_cause = self.conflict_resolution(&incompat)?;
- // root_cause is guaranteed to be almost satisfied by the partial solution
- // according to PubGrub documentation.
- match self.partial_solution.relation(&root_cause) {
- Relation::AlmostSatisfied(package_almost, term) => {
- changed = vec![package_almost.clone()];
- // Add (not term) to the partial solution with incompat as cause.
- self.partial_solution.add_derivation(package_almost, term.negate(), root_cause);
- }
- _ => return Err(PubGrubError::Failure("This should never happen, root_cause is guaranteed to be almost satisfied by the partial solution".into())),
- }
+ let (package_almost, root_cause) = self.conflict_resolution(&incompat)?;
+ changed = vec![package_almost.clone()];
+ // Add to the partial solution with incompat as cause.
+ self.partial_solution
+ .add_derivation(package_almost, root_cause);
}
- Relation::AlmostSatisfied(package_almost, term) => {
+ Relation::AlmostSatisfied(package_almost) => {
changed.push(package_almost.clone());
// Add (not term) to the partial solution with incompat as cause.
- self.partial_solution.add_derivation(
- package_almost,
- term.negate(),
- incompat,
- );
+ self.partial_solution
+ .add_derivation(package_almost, incompat.clone());
}
_ => {}
}
@@ -114,11 +102,11 @@ impl State
{
}
/// Return the root cause and the backtracked model.
- /// CF https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation
+ /// CF
fn conflict_resolution(
&mut self,
incompatibility: &Incompatibility
,
- ) -> Result, PubGrubError
> {
+ ) -> Result<(P, Incompatibility
), PubGrubError
> {
let mut current_incompat = incompatibility.clone();
let mut current_incompat_changed = false;
loop {
@@ -131,29 +119,30 @@ impl State
,
incompat_changed: bool,
- decision_level: usize,
+ decision_level: DecisionLevel,
) {
self.partial_solution.backtrack(decision_level);
if incompat_changed {
- incompat.merge_into(&mut self.incompatibilities);
+ incompat.merge_into(Rc::make_mut(&mut self.incompatibilities));
}
}
diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs
index 2313b3fc..219c6ba7 100644
--- a/src/internal/incompatibility.rs
+++ b/src/internal/incompatibility.rs
@@ -1,18 +1,17 @@
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+// SPDX-License-Identifier: MPL-2.0
//! An incompatibility is a set of terms for different packages
//! that should never be satisfied all together.
-use std::collections::HashMap as Map;
use std::collections::HashSet as Set;
use std::fmt;
use crate::package::Package;
use crate::range::Range;
use crate::report::{DefaultStringReporter, DerivationTree, Derived, External};
+use crate::solver::DependencyConstraints;
use crate::term::{self, Term};
+use crate::type_aliases::Map;
use crate::version::Version;
/// An incompatibility is a set of terms for different packages
@@ -40,13 +39,21 @@ pub struct Incompatibility {
#[derive(Debug, Clone)]
enum Kind {
+ /// Initial incompatibility aiming at picking the root package for the first decision.
NotRoot(P, V),
- NoVersion(P, Range),
+ /// There are no versions in the given range for this package.
+ NoVersions(P, Range),
+ /// Dependencies of the package are unavailable for versions in that range.
UnavailableDependencies(P, Range),
+ /// Incompatibility coming from the dependencies of a given package.
FromDependencyOf(P, Range, P, Range),
+ /// Derived from two causes. Stores cause ids.
DerivedFrom(usize, usize),
}
+/// A type alias for a pair of [Package] and a corresponding [Term].
+pub type PackageTerm
= (P, Term);
+
/// A Relation describes how a set of terms can be compared to an incompatibility.
/// Typically, the set of terms comes from the partial solution.
#[derive(Eq, PartialEq)]
@@ -56,10 +63,10 @@ pub enum Relation {
Satisfied,
/// We say that S contradicts I
/// if S contradicts at least one term in I.
- Contradicted(P, Term),
+ Contradicted(PackageTerm
),
/// If S satisfies all but one of I's terms and is inconclusive for the remaining term,
/// we say S "almost satisfies" I and we call the remaining term the "unsatisfied term".
- AlmostSatisfied(P, Term),
+ AlmostSatisfied(P),
/// Otherwise, we say that their relation is inconclusive.
Inconclusive,
}
@@ -67,7 +74,7 @@ pub enum Relation {
impl Incompatibility
{
/// Build an incompatibility from a given dependency.
fn from_dependency(id: usize, package: P, version: V, dep: (&P, &Range)) -> Self {
- let mut package_terms = Map::with_capacity(2);
+ let mut package_terms = Map::with_capacity_and_hasher(2, Default::default());
let range1 = Range::exact(version);
package_terms.insert(package.clone(), Term::Positive(range1.clone()));
let (p2, range2) = dep;
@@ -138,22 +145,9 @@ impl Incompatibility
{
}
}
- /// Perform the union of two incompatibilities.
- /// Terms that are always satisfied are removed from the union.
- fn union(id: usize, i1: &Map
>, i2: &Map
>, kind: Kind
) -> Self {
- let package_terms = Self::merge(i1, i2, |t1, t2| {
- let term_union = t1.union(t2);
- if term_union == Term::any() {
- None
- } else {
- Some(term_union)
- }
- });
- Self {
- id,
- package_terms,
- kind,
- }
+ /// Perform the intersection of terms in two incompatibilities.
+ fn intersection(i1: &Map
{
/// If the result is None, remove that key from the merged map,
/// otherwise add the content of the Some(_).
fn merge Option>(
- hashmap_1: &Map
,
- hashmap_2: &Map
,
+ map_1: &Map
,
+ map_2: &Map
,
f: F,
) -> Map
{
- let mut merged_map = hashmap_1.clone();
- merged_map.reserve(hashmap_2.len());
+ let mut merged_map = map_1.clone();
+ merged_map.reserve(map_2.len());
let mut to_delete = Vec::new();
- for (key, val_2) in hashmap_2.iter() {
+ for (key, val_2) in map_2.iter() {
match merged_map.get_mut(key) {
None => {
merged_map.insert(key.clone(), val_2.clone());
@@ -202,7 +196,7 @@ impl Incompatibility
{
/// (provided that no other version of foo exists between 1.0.0 and 2.0.0).
/// We could collapse them into { foo (1.0.0 ∪ 1.1.0), not bar ^1.0.0 }
/// without having to check the existence of other versions though.
- /// And it would even keep the same `Kind`: `FromDependencyOf foo`.
+ /// And it would even keep the same [Kind]: [FromDependencyOf](Kind::FromDependencyOf) foo.
///
/// Here we do the simple stupid thing of just growing the Vec.
/// TODO: improve this.
@@ -213,31 +207,42 @@ impl Incompatibility
{
incompatibilities.push(self);
}
- /// A prior cause is computed as the union of the terms in two incompatibilities.
- /// Terms that are always satisfied are removed from the union.
- pub fn prior_cause(id: usize, i1: &Self, i2: &Self) -> Self {
- let kind = Kind::DerivedFrom(i1.id, i2.id);
- Self::union(id, &i1.package_terms, &i2.package_terms, kind)
+ /// Prior cause of two incompatibilities using the rule of resolution.
+ pub fn prior_cause(id: usize, incompat: &Self, satisfier_cause: &Self, package: &P) -> Self {
+ let kind = Kind::DerivedFrom(incompat.id, satisfier_cause.id);
+ let mut incompat1 = incompat.package_terms.clone();
+ let mut incompat2 = satisfier_cause.package_terms.clone();
+ let t1 = incompat1.remove(package).unwrap();
+ let t2 = incompat2.remove(package).unwrap();
+ let mut package_terms = Self::intersection(&incompat1, &incompat2);
+ let term = t1.union(&t2);
+ if term != Term::any() {
+ package_terms.insert(package.clone(), term);
+ }
+ Self {
+ id,
+ package_terms,
+ kind,
+ }
}
/// CF definition of Relation enum.
- pub fn relation>>(
- &self,
- terms_set: &mut Map
{
let mut relation = Relation::Satisfied;
for (package, incompat_term) in self.package_terms.iter() {
- let terms_in_set = terms_set.get_mut(package).into_iter().flatten();
- match incompat_term.relation_with(terms_in_set) {
- term::Relation::Satisfied => {}
- term::Relation::Contradicted => {
- relation = Relation::Contradicted(package.clone(), incompat_term.clone());
- break;
+ match terms(package).map(|term| incompat_term.relation_with(&term)) {
+ Some(term::Relation::Satisfied) => {}
+ Some(term::Relation::Contradicted) => {
+ return Relation::Contradicted((package.clone(), incompat_term.clone()));
}
- term::Relation::Inconclusive => {
+ None | Some(term::Relation::Inconclusive) => {
+ // If a package is not present, the intersection is the same as [Term::any].
+ // According to the rules of satisfactions, the relation would be inconclusive.
+ // It could also be satisfied if the incompatibility term was also [Term::any],
+ // but we systematically remove those from incompatibilities
+ // so we're safe on that front.
if relation == Relation::Satisfied {
- relation =
- Relation::AlmostSatisfied(package.clone(), incompat_term.clone());
+ relation = Relation::AlmostSatisfied(package.clone());
} else {
relation = Relation::Inconclusive;
}
@@ -256,7 +261,7 @@ impl Incompatibility
{
Kind::NotRoot(package, version) => {
DerivationTree::External(External::NotRoot(package.clone(), version.clone()))
}
- Kind::NoVersion(package, range) => {
- DerivationTree::External(External::NoVersion(package.clone(), range.clone()))
+ Kind::NoVersions(package, range) => {
+ DerivationTree::External(External::NoVersions(package.clone(), range.clone()))
}
Kind::UnavailableDependencies(package, range) => DerivationTree::External(
External::UnavailableDependencies(package.clone(), range.clone()),
@@ -357,19 +362,21 @@ pub mod tests {
/// { p1: t1, p3: t3 }
#[test]
fn rule_of_resolution(t1 in term_strat(), t2 in term_strat(), t3 in term_strat()) {
- let mut i1 = Map::new();
+ let mut i1 = Map::default();
i1.insert("p1", t1.clone());
i1.insert("p2", t2.negate());
+ let i1 = Incompatibility { id: 0, package_terms: i1, kind: Kind::DerivedFrom(0,0) };
- let mut i2 = Map::new();
+ let mut i2 = Map::default();
i2.insert("p2", t2.clone());
i2.insert("p3", t3.clone());
+ let i2 = Incompatibility { id: 0, package_terms: i2, kind: Kind::DerivedFrom(0,0) };
- let mut i3 = Map::new();
+ let mut i3 = Map::default();
i3.insert("p1", t1);
i3.insert("p3", t3);
- let i_resolution = Incompatibility::union(0, &i1, &i2, Kind::DerivedFrom(0, 0));
+ let i_resolution = Incompatibility::prior_cause(0, &i1, &i2, &"p2");
assert_eq!(i_resolution.package_terms, i3);
}
diff --git a/src/internal/memory.rs b/src/internal/memory.rs
index 940ba5f6..090e51e9 100644
--- a/src/internal/memory.rs
+++ b/src/internal/memory.rs
@@ -1,15 +1,13 @@
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+// SPDX-License-Identifier: MPL-2.0
//! A Memory acts like a structured partial solution
-//! where terms are regrouped by package in a hashmap.
-
-use std::collections::HashMap as Map;
+//! where terms are regrouped by package in a [Map](crate::type_aliases::Map).
use crate::internal::assignment::Assignment::{self, Decision, Derivation};
use crate::package::Package;
+use crate::range::Range;
use crate::term::Term;
+use crate::type_aliases::{Map, SelectedDependencies};
use crate::version::Version;
/// A memory is the set of all assignments in the partial solution,
@@ -24,61 +22,79 @@ pub struct Memory {
/// A package memory contains the potential decision and derivations
/// that have already been made for a given package.
#[derive(Clone)]
-struct PackageAssignments {
- decision: Option<(V, Term)>,
- derivations: Vec>,
+enum PackageAssignments {
+ Decision((V, Term)),
+ Derivations {
+ intersected: Term,
+ not_intersected_yet: Vec>,
+ },
}
impl Memory