|
3 | 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
4 | 4 |
|
5 | 5 | //! PubGrub version solving algorithm.
|
| 6 | +//! |
| 7 | +//! Version solving consists in efficiently finding a set of packages and versions |
| 8 | +//! that satisfy all the constraints of a given project dependencies. |
| 9 | +//! In addition, when that is not possible, |
| 10 | +//! we should try to provide a very human-readable and clear |
| 11 | +//! explanation as to why that failed. |
| 12 | +//! |
| 13 | +//! # Package and Version traits |
| 14 | +//! |
| 15 | +//! All the code in this crate is manipulating packages and versions, |
| 16 | +//! and for this to work, we defined a `Package` and `Version` traits, |
| 17 | +//! that are used as bounds on most of the exposed types and functions. |
| 18 | +//! |
| 19 | +//! Package identifiers needs to implement our `Package` trait, |
| 20 | +//! which is automatic if the type already implements |
| 21 | +//! `Clone + Eq + Hash + Debug + Display`. |
| 22 | +//! So things like `String` will work out of the box. |
| 23 | +//! |
| 24 | +//! Our `Version` trait requires `Clone + Ord + Debug + Display` |
| 25 | +//! and also the definition of two methods, |
| 26 | +//! `lowest() -> Self` which returns the lowest version existing, |
| 27 | +//! and `bump(&self) -> Self` which returns the next smallest version |
| 28 | +//! strictly higher than the current one. |
| 29 | +//! For convenience, this library already provides two implementations of `Version`. |
| 30 | +//! The first one is `NumberVersion`, basically a newtype for `usize`. |
| 31 | +//! The second one is `SemanticVersion` that implements semantic versionning rules. |
| 32 | +//! |
| 33 | +//! # Basic example |
| 34 | +//! |
| 35 | +//! Let's imagine that we are building a user interface |
| 36 | +//! with a menu containing dropdowns with some icons, |
| 37 | +//! icons that we are also directly using in other parts of the interface. |
| 38 | +//! For this scenario our direct dependencies are `menu` and `icons`, |
| 39 | +//! but the complete set of dependencies looks like follows: |
| 40 | +//! |
| 41 | +//! - `root` depends on `menu` and `icons` |
| 42 | +//! - `menu` depends on `dropdown` |
| 43 | +//! - `dropdown` depends on `icons` |
| 44 | +//! - `icons` has no dependency |
| 45 | +//! |
| 46 | +//! We can model that scenario with this library as follows |
| 47 | +//! (don't worry about the `SimpleCache` name for now): |
| 48 | +//! ```ignore |
| 49 | +//! let mut solver = SimpleCache::<&str, NumberVersion>::new(); |
| 50 | +//! solver.add_dependencies( |
| 51 | +//! "root", 1, vec![("menu", Range::any()), ("icons", Range::any())], |
| 52 | +//! ); |
| 53 | +//! solver.add_dependencies("menu", 1, vec![("dropdown", Range::any())]); |
| 54 | +//! solver.add_dependencies("dropdown", 1, vec![("icons", Range::any())]); |
| 55 | +//! solver.add_dependencies("icons", 1, vec![]); |
| 56 | +//! |
| 57 | +//! // Run the solver. |
| 58 | +//! let _solution = solver.run("root", 1).unwrap(); |
| 59 | +//! ``` |
| 60 | +//! |
| 61 | +//! # Solver trait |
| 62 | +//! |
| 63 | +//! In our previous example we used the `SimpleCache` type |
| 64 | +//! to initialiaze the solver. |
| 65 | +//! `SimpleCache` is a basic implementation of the `Cache` |
| 66 | +//! trait, that we will explain in a moment and that automatically |
| 67 | +//! implements the `Solver` trait for convenience. |
| 68 | +//! |
| 69 | +//! But we might want to implement the `Solver` trait for our own type. |
| 70 | +//! Let's say that we will use `String` for packages, |
| 71 | +//! and `SemanticVersion` for versions. |
| 72 | +//! This may be done quite easily by implementing the two following functions. |
| 73 | +//! ```ignore |
| 74 | +//! impl Solver<String, SemanticVersion> for MySolver { |
| 75 | +//! fn list_available_versions( |
| 76 | +//! &mut self, |
| 77 | +//! package: &String |
| 78 | +//! ) -> Result<Vec<SemanticVersion>, Box<dyn Error>> { |
| 79 | +//! ... |
| 80 | +//! } |
| 81 | +//! |
| 82 | +//! fn get_dependencies( |
| 83 | +//! &mut self, |
| 84 | +//! package: &String, |
| 85 | +//! version: &SemanticVersion, |
| 86 | +//! ) -> Result<Option<Map<String, Range<SemanticVersion>>>, Box<dyn Error>> { |
| 87 | +//! ... |
| 88 | +//! } |
| 89 | +//! } |
| 90 | +//! ``` |
| 91 | +//! The first method `list_available_versions` should return all available |
| 92 | +//! versions of a package. |
| 93 | +//! The second method `get_dependencies` aims at retrieving the dependencies |
| 94 | +//! of a given package at a given version. |
| 95 | +//! Return `None` if dependencies are unknown. |
| 96 | +//! |
| 97 | +//! On a real scenario, these two methods may involve reading the file system |
| 98 | +//! or doing network request, so you may want to hold a cache in your `MySolver` type. |
| 99 | +//! You could use the `SimpleCache` type provided by the crate as guidance, |
| 100 | +//! but you are free to use whatever approach |
| 101 | +//! makes sense in your situation. |
| 102 | +//! |
| 103 | +//! # Solution and error reporting |
| 104 | +//! |
| 105 | +//! When everything goes well, the solver finds and returns the complete |
| 106 | +//! set of direct and indirect dependencies satisfying all the constraints. |
| 107 | +//! The packages and versions selected are returned in a `HashMap<P, V>`. |
| 108 | +//! But sometimes there is no solution because dependencies are incompatible. |
| 109 | +//! In such cases, `solver.run(...)` returns a |
| 110 | +//! `PubGrubError::NoSolution(derivation_tree)`, |
| 111 | +//! where the provided derivation tree is a custom binary tree |
| 112 | +//! containing the full chain of reasons why there is no solution. |
| 113 | +//! |
| 114 | +//! All the items in the tree are called incompatibilities |
| 115 | +//! and may be of two types, either "external" or "derived". |
| 116 | +//! Leafs of the tree are external incompatibilities, |
| 117 | +//! and nodes are derived. |
| 118 | +//! External incompatibilities have reasons that are independent |
| 119 | +//! of the way this solver is implemented such as |
| 120 | +//! - dependencies: "package_a" at version 1 depends on "package_b" at version 4 |
| 121 | +//! - missing dependencies: dependencies of "package_a" are unknown |
| 122 | +//! - absence of version: there is no version of "package_a" in the range [3.1.0 4.0.0[ |
| 123 | +//! |
| 124 | +//! Derived incompatibilities are obtained by the solver by deduction, |
| 125 | +//! such as if "a" depends on "b" and "b" depends on "c", "a" depends on "c". |
| 126 | +//! |
| 127 | +//! This crate defines a `Reporter` trait, with an associated `Output` type |
| 128 | +//! and a single method |
| 129 | +//! ```ignore |
| 130 | +//! report(derivation_tree: &DerivationTree<P, V>) -> Output |
| 131 | +//! ``` |
| 132 | +//! Implementing a `Reporter` may involve a lot of heuristics |
| 133 | +//! to make the output human-readable and natural. |
| 134 | +//! For convenience, we provide a default implementation |
| 135 | +//! `DefaultStringReporter`, that output the report as a String. |
| 136 | +//! You may use it as follows: |
| 137 | +//! ```ignore |
| 138 | +//! match solver.run(root_package, root_version) { |
| 139 | +//! Ok(solution) => println!("{:?}", solution), |
| 140 | +//! Err(PubGrubError::NoSolution(mut derivation_tree)) => { |
| 141 | +//! derivation_tree.collapse_noversion(); |
| 142 | +//! eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); |
| 143 | +//! } |
| 144 | +//! Err(err) => panic!("{:?}", err), |
| 145 | +//! }; |
| 146 | +//! ``` |
| 147 | +//! Notice that we also used `collapse_noversion()` above. |
| 148 | +//! This method simplifies the derivation tree to get rid |
| 149 | +//! of the `NoVersion` external incompatibilities in the derivation tree. |
| 150 | +//! So instead of seing things like this in the report: |
| 151 | +//! ```txt |
| 152 | +//! Because there is no version of foo in 1.0.1 <= v < 2.0.0 |
| 153 | +//! and foo 1.0.0 depends on bar 2.0.0 <= v < 3.0.0, |
| 154 | +//! foo 1.0.0 <= v < 2.0.0 depends on bar 2.0.0 <= v < 3.0.0. |
| 155 | +//! ``` |
| 156 | +//! you may have directly: |
| 157 | +//! ```txt |
| 158 | +//! foo 1.0.0 <= v < 2.0.0 depends on bar 2.0.0 <= v < 3.0.0. |
| 159 | +//! ``` |
| 160 | +//! Beware though that if you are using some kind of offline mode |
| 161 | +//! with a cache, you may want to know that some versions |
| 162 | +//! do not exist in your cache. |
6 | 163 |
|
7 | 164 | #![warn(missing_docs)]
|
8 | 165 |
|
|
0 commit comments