Skip to content

Commit 87e5509

Browse files
committed
Update guide documentation for 0.3
Update the guide to be in sync with the changes made for 0.3. I've rewritten the tutorial chapter, starting using the offline dependency provider to implementing your own dependency provider, focussed on getting a user with no prior solver experience from getting started quickly to eventually writing a full dependency provider with all custom types. Some parts are completely removed (such as the continuous versions) as the new traits and structs make them unnecessary. Others i've only touched up to not be not wrong, though they could use a rewrite with the input from uv and cargo. I want to unblock the 0.3 release without starting any new projects. I'm tempted to remove the top-level chapter pages and instead move their (remaining) content into the first page of each chapter. We should move the book into the main pubgrub repository to prevent it from getting out of sync in the future. The only non-trivial item is the cloudflare secrets.
1 parent 072b9ab commit 87e5509

18 files changed

+295
-428
lines changed

src/SUMMARY.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@
33
- [Version solving](./version_solving.md)
44
- [Using the pubgrub crate](./pubgrub_crate/intro.md)
55
- [Basic example with OfflineDependencyProvider](./pubgrub_crate/offline_dep_provider.md)
6-
- [Writing your own dependency provider](./pubgrub_crate/custom_dep_provider.md)
7-
- [Caching dependencies in a DependencyProvider](./pubgrub_crate/caching.md)
6+
- [Implementing a dependency provider](./pubgrub_crate/dep_provider.md)
7+
- [Caching dependencies](./pubgrub_crate/caching.md)
88
- [Strategical decision making in a DependencyProvider](./pubgrub_crate/strategy.md)
99
- [Solution and error reporting](./pubgrub_crate/solution.md)
1010
- [Writing your own error reporting logic](./pubgrub_crate/custom_report.md)
1111
- [Advanced usage and limitations](./limitations/intro.md)
1212
- [Optional dependencies](./limitations/optional_deps.md)
1313
- [Allowing multiple versions of a package](./limitations/multiple_versions.md)
1414
- [Public and Private packages](./limitations/public_private.md)
15-
- [Versions in a continuous space](./limitations/continuous_versions.md)
1615
- [Pre-release versions](./limitations/prerelease_versions.md)
1716
- [Internals of the PubGrub algorithm](./internals/intro.md)
1817
- [Overview of the algorithm](./internals/overview.md)

src/internals/partial_solution.md

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ have already been taken (including that one if it is a decision). If we
1212
represent all assignments as a chronological vec, they would look like follows:
1313

1414
```txt
15-
[ (0, root_derivation),
15+
[
16+
(0, root_derivation),
1617
(1, root_decision),
1718
(1, derivation_1a),
1819
(1, derivation_1b),
@@ -26,14 +27,3 @@ represent all assignments as a chronological vec, they would look like follows:
2627
The partial solution must also enable efficient evaluation of incompatibilities
2728
in the unit propagation loop. For this, we need to have efficient access to all
2829
assignments referring to the packages present in an incompatibility.
29-
30-
To enable both efficient backtracking and efficient access to specific package
31-
assignments, the current implementation holds a dual representation of the the
32-
partial solution. One is called `history` and keeps dated (with decision levels)
33-
assignments in an ordered growing vec. The other is called `memory` and
34-
organizes assignments in a hashmap where they are regrouped by packages which
35-
are the hashmap keys. It would be interresting to see how the partial solution
36-
is stored in other implementations of PubGrub such as the one in [dart
37-
pub][pub].
38-
39-
[pub]: https://github.com/dart-lang/pub

src/limitations/continuous_versions.md

Lines changed: 0 additions & 95 deletions
This file was deleted.

src/limitations/intro.md

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,23 @@ a dependency system with the following constraints:
55

66
1. Packages are uniquely identified.
77
2. Versions are in a discrete set, with a total order.
8-
3. The successor of a given version is always uniquely defined.
9-
4. Dependencies of a package version are fixed.
10-
5. Exactly one version must be selected per package depended on.
8+
3. Dependencies of a package version are fixed.
9+
4. Exactly one version must be selected per package depended on.
1110

1211
The fact that packages are uniquely identified (1) is perhaps the only
1312
constraint that makes sense for all common dependency systems. But for the rest
1413
of the constraints, they are all inadequate for some common real-world
1514
dependency systems. For example, it's possible to have dependency systems where
1615
order is not required for versions (2). In such systems, dependencies must be
1716
specified with exact sets of compatible versions, and bounded ranges make no
18-
sense. Being able to uniquely define the successor of any version (3) is also a
19-
constraint that is not a natural fit if versions have a system of pre-releases.
20-
Indeed, what is the successor of `2.0.0-alpha`? We can't tell if that is `2.0.0`
21-
or `2.0.0-beta` or `2.0.0-whatever`. Having fixed dependencies (4) is also not
22-
followed in programming languages allowing optional dependencies. In Rust
23-
packages, optional dependencies are called "features" for example. Finally,
24-
restricting solutions to only one version per package (5) is also too
25-
constraining for dependency systems allowing breaking changes. In cases where
26-
packages A and B both depend on different ranges of package C, we sometimes want
27-
to be able to have a solution where two versions of C are present, and let the
28-
compiler decide if their usages of C in the code are compatible.
17+
sense. Having fixed dependencies (3) is also not followed in programming
18+
languages allowing optional dependencies. In Rust packages, optional
19+
dependencies are called "features" for example. Finally, restricting solutions
20+
to only one version per package (4) is also too constraining for dependency
21+
systems allowing breaking changes. In cases where packages A and B both depend
22+
on different ranges of package C, we sometimes want to be able to have a
23+
solution where two versions of C are present, and let the compiler decide if
24+
their usages of C in the code are compatible.
2925

3026
In the following subsections, we try to show how we can circumvent those
3127
limitations with clever usage of dependency providers.

src/limitations/multiple_versions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ fn get_dependencies(
250250
}
251251
})
252252
.collect();
253-
Ok(Dependencies::Known(pkg_deps))
253+
Ok(Dependencies::Available(pkg_deps))
254254
}
255255
Package::Proxy { source, target } => {
256256
// If this is a proxy package, it depends on a single bucket package, the target,
@@ -266,7 +266,7 @@ fn get_dependencies(
266266
}),
267267
bucket_range.intersection(target_range),
268268
);
269-
Ok(Dependencies::Known(bucket_dep))
269+
Ok(Dependencies::Available(bucket_dep))
270270
}
271271
}
272272
}

src/limitations/optional_deps.md

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,13 @@ We define an `Index`, storing all dependencies (`Deps`) of every package version
5656
in a double map, first indexed by package, then by version.
5757

5858
```rust
59-
// Use NumberVersion, which are simple u32 for the versions.
60-
use pubgrub::version::NumberVersion as Version;
6159
/// Each package is identified by its name.
6260
pub type PackageName = String;
6361

6462
/// Global registry of known packages.
6563
pub struct Index {
6664
/// Specify dependencies of each package version.
67-
pub packages: Map<PackageName, BTreeMap<Version, Deps>>,
65+
pub packages: Map<PackageName, BTreeMap<u32, Deps>>,
6866
}
6967
```
7068

@@ -127,22 +125,21 @@ pub enum Package {
127125
}
128126
```
129127

130-
Let's implement the first function required by a dependency provider,
131-
`choose_package_version`. For that we defined the `base_pkg()` method on a
132-
`Package` that returns the string of the base package. And we defined the
133-
`available_versions()` method on an `Index` to list existing versions of a given
134-
package. Then we simply called the `choose_package_with_fewest_versions` helper
135-
function provided by pubgrub.
128+
We'll ignore `prioritize` for this example.
129+
130+
Let's implement the second function required by a dependency provider,
131+
`choose_version`. For that we defined the `base_pkg()` method on a `Package`
132+
that returns the string of the base package, and the `available_versions()`
133+
method on an `Index` to list existing versions of a given package in descending
134+
order.
136135

137136
```rust
138-
fn choose_package_version<T: Borrow<Package>, U: Borrow<Range<Version>>>(
137+
fn choose_version(
139138
&self,
140-
potential_packages: impl Iterator<Item = (T, U)>,
141-
) -> Result<(T, Option<Version>), Box<dyn std::error::Error>> {
142-
Ok(pubgrub::solver::choose_package_with_fewest_versions(
143-
|p| self.available_versions(p.base_pkg()).cloned(),
144-
potential_packages,
145-
))
139+
package: &Self::P,
140+
range: &Self::VS,
141+
) -> Result<Option<Self::V>, Self::Err> {
142+
Ok(self.available_versions(p.base_pkg()).find(|version| range.contains(version)).cloned())
146143
}
147144
```
148145

@@ -165,7 +162,7 @@ fn get_dependencies(
165162

166163
match package {
167164
// If we asked for a base package, we simply return the mandatory dependencies.
168-
Package::Base(_) => Ok(Dependencies::Known(from_deps(&deps.mandatory))),
165+
Package::Base(_) => Ok(Dependencies::Available(from_deps(&deps.mandatory))),
169166
// Otherwise, we concatenate the feature deps with a dependency to the base package.
170167
Package::Feature { base, feature } => {
171168
let feature_deps = deps.optional.get(feature).unwrap();
@@ -174,7 +171,7 @@ fn get_dependencies(
174171
Package::Base(base.to_string()),
175172
Range::exact(version.clone()),
176173
);
177-
Ok(Dependencies::Known(all_deps))
174+
Ok(Dependencies::Available(all_deps))
178175
},
179176
}
180177
}

src/limitations/prerelease_versions.md

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,19 @@
33
Pre-releasing is a very common pattern in the world of versioning. It is however
44
one of the worst to take into account in a dependency system, and I highly
55
recommend that if you can avoid introducing pre-releases in your package
6-
manager, you should. In the context of pubgrub, pre-releases break two
7-
fondamental properties of the solver.
6+
manager, you should.
87

9-
1. Pre-releases act similar to continuous spaces.
10-
2. Pre-releases break the mathematical properties of subsets in a space with
11-
total order.
8+
In the context of pubgrub, pre-releases break the fundamental properties of the
9+
solver that there is or isn't a version between two versions "x" and "x+1", that
10+
there cannot be a version "(x+1).alpha.1" depending on whether an input version
11+
had a pre-release specifier.
1212

13-
(1) Indeed, it is hard to answer what version comes after "1-alpha0". Is it
14-
"1-alpha1", "1-beta0", "2"? In practice, we could say that the version that
15-
comes after "1-alpha0" is "1-alpha0?" where the "?" character is chosen to be
16-
the lowest character in the lexicographic order, but we clearly are on a stretch
17-
here and it certainly isn't natural.
18-
19-
(2) Pre-releases are often semantically linked to version constraints written by
13+
Pre-releases are often semantically linked to version constraints written by
2014
humans, interpreted differently depending on context. For example, "2.0.0-beta"
21-
is meant to exist previous to version "2.0.0". Yet, it is not supposed to be
22-
contained in the set described by `1.0.0 <= v < 2.0.0`, and only within sets
23-
where one of the bounds contains a pre-release marker such as
24-
`2.0.0-alpha <= v < 2.0.0`. This poses a problem to the dependency solver
15+
is meant to exist previous to version "2.0.0". Yet, in many versioning schemes
16+
it is not supposed to be contained in the set described by `1.0.0 <= v < 2.0.0`,
17+
and only within sets where one of the bounds contains a pre-release marker such
18+
as `2.0.0-alpha <= v < 2.0.0`. This poses a problem to the dependency solver
2519
because of backtracking. Indeed, the PubGrub algorithm relies on knowledge
2620
accumulated all along the propagation of the solver front. And this knowledge is
2721
composed of facts, that are thus never removed even when backtracking happens.
@@ -33,12 +27,6 @@ return nothing even without checking if a pre-release exists in that range. And
3327
this is one of the fundamental mechanisms of the algorithm, so we should not try
3428
to alter it.
3529

36-
Point (2) is probably the reason why some pubgrub implementations have issues
37-
dealing with pre-releases when backtracking, as can be seen in [an issue of the
38-
dart implementation][dart-prerelease-issue].
39-
40-
[dart-prerelease-issue]: https://github.com/dart-lang/pub/pull/3038
41-
4230
## Playing again with packages?
4331

4432
In the light of the "bucket" and "proxies" scheme we introduced in the section
@@ -71,10 +59,8 @@ exploring alternative API changes that could enable pre-releases.
7159

7260
## Multi-dimensional ranges
7361

74-
We are currently exploring new APIs where `Range` is transformed into a trait,
75-
instead of a predefined struct with a single sequence of non-intersecting
76-
intervals. For now, the new trait is called `RangeSet` and could be implemented
77-
on structs with multiple dimensions for ranges.
62+
Building on top of the `Ranges` API, we could implement a custom `VersionSet` of
63+
multi-dimensional ranges:
7864

7965
```rust
8066
pub struct DoubleRange<V1: Version, V2: Version> {
@@ -90,23 +76,23 @@ matched to:
9076

9177
```rust
9278
DoubleRange {
93-
normal_range: Range::none,
94-
prerelease_range: Range::between("2.0.0-alpha", "2.0.0"),
79+
normal_range: Ranges::empty(),
80+
prerelease_range: Ranges::between("2.0.0-alpha", "2.0.0"),
9581
}
9682
```
9783

9884
And the constraint `2.0.0-alpha <= v < 2.1.0` would have the same
9985
`prerelease_range` but would have `2.0.0 <= v < 2.1.0` for the normal range.
100-
Those constraints could also be intrepreted differently since not all
86+
Those constraints could also be interpreted differently since not all
10187
pre-release systems work the same. But the important property is that this
102-
enable a separation of the dimensions that do not behave consistently with
88+
enables a separation of the dimensions that do not behave consistently with
10389
regard to the mathematical properties of the sets manipulated.
10490

105-
All this is under ongoing experimentations, to try reaching a sweet spot
106-
API-wise and performance-wise. If you are eager to experiment with all the
107-
extensions and limitations mentionned in this section of the guide for your
108-
dependency provider, don't hesitate to reach out to us in our [zulip
109-
stream][zulip] or in [GitHub issues][issues] to let us know how it went!
91+
All this needs more experimentation, to try reaching a sweet spot API-wise and
92+
performance-wise. If you are eager to experiment with all the extensions and
93+
limitations mentioned in this section of the guide for your dependency provider,
94+
don't hesitate to reach out to us in our [zulip stream][zulip] or in [GitHub
95+
issues][issues] to let us know how it went!
11096

11197
[zulip]: https://rust-lang.zulipchat.com/#narrow/stream/260232-t-cargo.2FPubGrub
11298
[issues]: https://github.com/pubgrub-rs/pubgrub/issues

src/limitations/public_private.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ fn get_dependencies(&self, package: &Package, version: &SemVer)
210210
-> Result<Dependencies<Package, SemVer>, ...> {
211211
match &package.seeds {
212212
// A Constraint variant does not have any dependency
213-
PkgSeeds::Constraint(_) => Ok(Dependencies::Known(Map::default())),
213+
PkgSeeds::Constraint(_) => Ok(Dependencies::Available(Map::default())),
214214
// A Markers variant has dependencies to:
215215
// - one Constraint variant per seed marker
216216
// - one Markers variant per original dependency
@@ -219,7 +219,7 @@ fn get_dependencies(&self, package: &Package, version: &SemVer)
219219
let seed_constraints = ...;
220220
// Figure out if there are private dependencies.
221221
let has_private = ...;
222-
Ok(Dependencies::Known(
222+
Ok(Dependencies::Available(
223223
// Chain the seed constraints with actual dependencies.
224224
seed_constraints
225225
.chain(index_deps.iter().map(|(p, (privacy, r))| {

src/pubgrub_crate/caching.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ pub struct CachingDependencyProvider<P: Package, V: Version> {
4141
}
4242

4343
impl<P: Package, V: Version> DependencyProvider<P, V> for CachingDependencyProvider<P, V> {
44-
fn choose_package_version<...>(...) -> ... { ... }
44+
fn choose_version(&self, package: &DP::P, ranges: &DP::VS) -> Result<Option<DP::V>, DP::Err> {
45+
...
46+
}
47+
4548
fn get_dependencies(
4649
&self,
4750
package: &P,

0 commit comments

Comments
 (0)