pubgrub/lib.rs
1// SPDX-License-Identifier: MPL-2.0
2
3//! PubGrub version solving algorithm.
4//!
5//! Version solving consists in efficiently finding a set of packages and versions
6//! that satisfy all the constraints of a given project dependencies.
7//! In addition, when that is not possible,
8//! we should try to provide a very human-readable and clear
9//! explanation as to why that failed.
10//!
11//! # Basic example
12//!
13//! Let's imagine that we are building a user interface
14//! with a menu containing dropdowns with some icons,
15//! icons that we are also directly using in other parts of the interface.
16//! For this scenario our direct dependencies are `menu` and `icons`,
17//! but the complete set of dependencies looks like follows:
18//!
19//! - `root` depends on `menu` and `icons`
20//! - `menu` depends on `dropdown`
21//! - `dropdown` depends on `icons`
22//! - `icons` has no dependency
23//!
24//! We can model that scenario with this library as follows
25//! ```
26//! # use pubgrub::{OfflineDependencyProvider, resolve, Ranges};
27//!
28//! type NumVS = Ranges<u32>;
29//!
30//! let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new();
31//!
32//! dependency_provider.add_dependencies(
33//! "root",
34//! 1u32,
35//! [("menu", Ranges::full()), ("icons", Ranges::full())],
36//! );
37//! dependency_provider.add_dependencies("menu", 1u32, [("dropdown", Ranges::full())]);
38//! dependency_provider.add_dependencies("dropdown", 1u32, [("icons", Ranges::full())]);
39//! dependency_provider.add_dependencies("icons", 1u32, []);
40//!
41//! // Run the algorithm.
42//! let solution = resolve(&dependency_provider, "root", 1u32).unwrap();
43//! ```
44//!
45//! # Package and Version flexibility
46//!
47//! The [OfflineDependencyProvider] used in that example is generic over the way package names,
48//! version requirements, and version numbers are represented.
49//!
50//! The first bound is the type of package names. It can be anything that implements our [Package] trait.
51//! The [Package] trait is automatic if the type already implements
52//! [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::Display).
53//! So things like [String] will work out of the box.
54//!
55//! The second bound is the type of package requirements. It can be anything that implements our [VersionSet] trait.
56//! This trait is used to figure out how version requirements are combined.
57//! If the normal [Ord]/[PartialEq] operations are all that is needed for requirements, our [Ranges] type will work.
58//!
59//! The chosen `VersionSet` in turn specifies what can be used for version numbers.
60//! This type needs to at least implement [Clone] + [Ord] + [Debug] + [Display](std::fmt::Display).
61//! For convenience, this library provides [SemanticVersion] that implements the basics of semantic versioning rules.
62//!
63//! # DependencyProvider trait
64//!
65//! In our previous example we used the
66//! [OfflineDependencyProvider],
67//! which is a basic implementation of the [DependencyProvider] trait.
68//!
69//! But we might want to implement the [DependencyProvider]
70//! trait for our own type.
71//! Let's say that we will use [String] for packages,
72//! and [SemanticVersion] for versions.
73//! This may be done quite easily by implementing the three following functions.
74//! ```
75//! # use pubgrub::{DependencyProvider, Dependencies, SemanticVersion, Ranges,
76//! # DependencyConstraints, Map, PackageResolutionStatistics};
77//! # use std::error::Error;
78//! # use std::borrow::Borrow;
79//! # use std::convert::Infallible;
80//! #
81//! # struct MyDependencyProvider;
82//! #
83//! type SemVS = Ranges<SemanticVersion>;
84//!
85//! impl DependencyProvider for MyDependencyProvider {
86//! fn choose_version(&self, package: &String, range: &SemVS) -> Result<Option<SemanticVersion>, Infallible> {
87//! unimplemented!()
88//! }
89//!
90//! type Priority = usize;
91//! fn prioritize(&self, package: &String, range: &SemVS, conflicts_counts: &PackageResolutionStatistics) -> Self::Priority {
92//! unimplemented!()
93//! }
94//!
95//! fn get_dependencies(
96//! &self,
97//! package: &String,
98//! version: &SemanticVersion,
99//! ) -> Result<Dependencies<String, SemVS, Self::M>, Infallible> {
100//! Ok(Dependencies::Available(DependencyConstraints::default()))
101//! }
102//!
103//! type Err = Infallible;
104//! type P = String;
105//! type V = SemanticVersion;
106//! type VS = SemVS;
107//! type M = String;
108//! }
109//! ```
110//!
111//! The first method
112//! [choose_version](DependencyProvider::choose_version)
113//! chooses a version compatible with the provided range for a package.
114//! The second method
115//! [prioritize](DependencyProvider::prioritize)
116//! in which order different packages should be chosen.
117//! Usually prioritizing packages
118//! with the fewest number of compatible versions speeds up resolution.
119//! But in general you are free to employ whatever strategy suits you best
120//! to pick a package and a version.
121//!
122//! The third method [get_dependencies](DependencyProvider::get_dependencies)
123//! aims at retrieving the dependencies of a given package at a given version.
124//!
125//! In a real scenario, these two methods may involve reading the file system
126//! or doing network request, so you may want to hold a cache in your
127//! [DependencyProvider] implementation.
128//! How exactly this could be achieved is shown in `CachingDependencyProvider`
129//! (see `examples/caching_dependency_provider.rs`).
130//! You could also use the [OfflineDependencyProvider]
131//! type defined by the crate as guidance,
132//! but you are free to use whatever approach makes sense in your situation.
133//!
134//! # Solution and error reporting
135//!
136//! When everything goes well, the algorithm finds and returns the complete
137//! set of direct and indirect dependencies satisfying all the constraints.
138//! The packages and versions selected are returned as
139//! [SelectedDependencies<P, V>](SelectedDependencies).
140//! But sometimes there is no solution because dependencies are incompatible.
141//! In such cases, [resolve(...)](resolve) returns a
142//! [PubGrubError::NoSolution(derivation_tree)](PubGrubError::NoSolution),
143//! where the provided derivation tree is a custom binary tree
144//! containing the full chain of reasons why there is no solution.
145//!
146//! All the items in the tree are called incompatibilities
147//! and may be of two types, either "external" or "derived".
148//! Leaves of the tree are external incompatibilities,
149//! and nodes are derived.
150//! External incompatibilities have reasons that are independent
151//! of the way this algorithm is implemented such as
152//! - dependencies: "package_a" at version 1 depends on "package_b" at version 4
153//! - missing dependencies: dependencies of "package_a" are unavailable
154//! - absence of version: there is no version of "package_a" in the range [3.1.0 4.0.0[
155//!
156//! Derived incompatibilities are obtained during the algorithm execution by deduction,
157//! such as if "a" depends on "b" and "b" depends on "c", "a" depends on "c".
158//!
159//! This crate defines a [Reporter] trait, with an associated
160//! [Output](Reporter::Output) type and a single method.
161//! ```
162//! # use pubgrub::{Package, VersionSet, DerivationTree};
163//! # use std::fmt::{Debug, Display};
164//! #
165//! pub trait Reporter<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> {
166//! type Output;
167//!
168//! fn report(derivation_tree: &DerivationTree<P, VS, M>) -> Self::Output;
169//! }
170//! ```
171//! Implementing a [Reporter] may involve a lot of heuristics
172//! to make the output human-readable and natural.
173//! For convenience, we provide a default implementation
174//! [DefaultStringReporter] that outputs the report as a [String].
175//! You may use it as follows:
176//! ```
177//! # use pubgrub::{resolve, OfflineDependencyProvider, DefaultStringReporter, Reporter, PubGrubError, Ranges};
178//! #
179//! # type NumVS = Ranges<u32>;
180//! #
181//! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new();
182//! # let root_package = "root";
183//! # let root_version = 1u32;
184//! #
185//! match resolve(&dependency_provider, root_package, root_version) {
186//! Ok(solution) => println!("{:?}", solution),
187//! Err(PubGrubError::NoSolution(mut derivation_tree)) => {
188//! derivation_tree.collapse_no_versions();
189//! eprintln!("{}", DefaultStringReporter::report(&derivation_tree));
190//! }
191//! Err(err) => panic!("{:?}", err),
192//! };
193//! ```
194//! Notice that we also used
195//! [collapse_no_versions()](DerivationTree::collapse_no_versions) above.
196//! This method simplifies the derivation tree to get rid of the
197//! [NoVersions](External::NoVersions)
198//! external incompatibilities in the derivation tree.
199//! So instead of seeing things like this in the report:
200//! ```txt
201//! Because there is no version of foo in 1.0.1 <= v < 2.0.0
202//! and foo 1.0.0 depends on bar 2.0.0 <= v < 3.0.0,
203//! foo 1.0.0 <= v < 2.0.0 depends on bar 2.0.0 <= v < 3.0.0.
204//! ```
205//! you may have directly:
206//! ```txt
207//! foo 1.0.0 <= v < 2.0.0 depends on bar 2.0.0 <= v < 3.0.0.
208//! ```
209//! Beware though that if you are using some kind of offline mode
210//! with a cache, you may want to know that some versions
211//! do not exist in your cache.
212
213#![warn(missing_docs)]
214
215mod error;
216mod package;
217mod provider;
218mod report;
219mod solver;
220mod term;
221mod type_aliases;
222mod version;
223mod version_set;
224
225pub use error::{NoSolutionError, PubGrubError};
226pub use package::Package;
227pub use provider::OfflineDependencyProvider;
228pub use report::{
229 DefaultStringReportFormatter, DefaultStringReporter, DerivationTree, Derived, External,
230 ReportFormatter, Reporter,
231};
232pub use solver::{resolve, Dependencies, DependencyProvider, PackageResolutionStatistics};
233pub use term::Term;
234pub use type_aliases::{DependencyConstraints, Map, SelectedDependencies, Set};
235pub use version::{SemanticVersion, VersionParseError};
236pub use version_ranges::Ranges;
237#[deprecated(note = "Use `Ranges` instead")]
238pub use version_ranges::Ranges as Range;
239pub use version_set::VersionSet;
240
241mod internal;