Skip to content

Commit 9ebd2d1

Browse files
authored
Update guide documentation for 0.3 (#14)
1 parent e257604 commit 9ebd2d1

18 files changed

+303
-441
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: 27 additions & 47 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
@@ -63,18 +51,16 @@ version but not both.
6351

6452
Another issue would be that the proxy and bucket scheme breaks strategies
6553
depending on ordering of versions. Since we have two proxy versions, one
66-
targetting the normal bucket, and one targetting the pre-release bucket, a
54+
targeting the normal bucket, and one targeting the pre-release bucket, a
6755
strategy aiming at the newest versions will lean towards normal or pre-release
6856
depending if the newest proxy version is the one for the normal or pre-release
6957
bucket. Mitigating this issue seems complicated, but hopefully, we are also
7058
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 can implement a custom `VersionSet` of
63+
multi-dimensional ranges:
7864

7965
```rust
8066
pub struct DoubleRange<V1: Version, V2: Version> {
@@ -83,30 +69,24 @@ pub struct DoubleRange<V1: Version, V2: Version> {
8369
}
8470
```
8571

86-
With multi-dimensional ranges we could match the semantics of version
87-
constraints in ways that do not introduce alterations of the core of the
88-
algorithm. For example, the constraint `2.0.0-alpha <= v < 2.0.0` could be
89-
matched to:
72+
With multi-dimensional ranges we can match the semantics of version constraints
73+
in ways that do not introduce alterations of the core of the algorithm. For
74+
example, the constraint `2.0.0-alpha <= v < 2.0.0` can be matched to:
9075

9176
```rust
9277
DoubleRange {
93-
normal_range: Range::none,
94-
prerelease_range: Range::between("2.0.0-alpha", "2.0.0"),
78+
normal_range: Ranges::empty(),
79+
prerelease_range: Ranges::between("2.0.0-alpha", "2.0.0"),
9580
}
9681
```
9782

98-
And the constraint `2.0.0-alpha <= v < 2.1.0` would have the same
99-
`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
101-
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
103-
regard to the mathematical properties of the sets manipulated.
104-
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!
83+
And the constraint `2.0.0-alpha <= v < 2.1.0` has the same `prerelease_range`
84+
but has `2.0.0 <= v < 2.1.0` for the normal range. Those constraints could also
85+
be interpreted differently since not all pre-release systems work the same. But
86+
the important property is that this enables a separation of the dimensions that
87+
do not behave consistently with regard to the mathematical properties of the
88+
sets manipulated.
11089

111-
[zulip]: https://rust-lang.zulipchat.com/#narrow/stream/260232-t-cargo.2FPubGrub
112-
[issues]: https://github.com/pubgrub-rs/pubgrub/issues
90+
This strategy is successfully used by
91+
[semver-pubgrub](https://github.com/pubgrub-rs/semver-pubgrub) to model rust
92+
dependencies.

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))| {

0 commit comments

Comments
 (0)