Skip to content

Commit ab71ba9

Browse files
committed
Auto merge of #14662 - epage:resolver, r=Eh2406,weihanglo
docs(resolver): Lay groundwork for documenting MSRV-aware logic ### What does this PR try to resolve? This is prep for document the MSRV-aware resolver (see #14639), in particular - This give more breathing room for adding this new heuristic to the resolver documentation - This provides the context for understanding the limitations In moving documentation, I asked the question "where would I look to find this if I had a question on it". I tried to balance this by not putting too much formal / technical documentation in more guide-level descriptions. In particular, while "Specifying Dependencies" is in the reference, its also written in somewhat of a guide-style. There is likely more work that can be done, including - Maybe making the "SemVer Compatibility" chapter the de facto reference for Cargo's version of semver that other sections reference for a more exhaustive description. - Splitting discussion of the Feature resolver out of the resolver and features documentation. In the current implementation, we have 3 resolve phases (1) lockfile, (2) adapt to the current compilation, (3) resolve features. The last two really serve the same role and I'd consider merging discussion of them. ### How should we test and review this PR? I tried to break up changes in smaller to digest chunks. This means some times a new section doesn't fully jive with another section until a follow up commit. I'd recommend reviewing by commit while having the full diff up on the side to see if a concern is still relevant. ### Additional information
2 parents a460018 + 0d072e1 commit ab71ba9

File tree

4 files changed

+208
-159
lines changed

4 files changed

+208
-159
lines changed

src/doc/src/SUMMARY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* [Specifying Dependencies](reference/specifying-dependencies.md)
2727
* [Overriding Dependencies](reference/overriding-dependencies.md)
2828
* [Source Replacement](reference/source-replacement.md)
29-
* [Dependency Resolution](reference/resolver.md)
29+
* [Dependency Resolution](reference/resolver.md)
3030
* [Features](reference/features.md)
3131
* [Features Examples](reference/features-examples.md)
3232
* [Profiles](reference/profiles.md)

src/doc/src/reference/manifest.md

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -92,28 +92,30 @@ a keyword. [crates.io] imposes even more restrictions, such as:
9292

9393
### The `version` field
9494

95-
Cargo bakes in the concept of [Semantic
96-
Versioning](https://semver.org/), so make sure you follow some basic rules:
97-
98-
* Before you reach 1.0.0, anything goes, but if you make breaking changes,
99-
increment the minor version. In Rust, breaking changes include adding fields to
100-
structs or variants to enums.
101-
* After 1.0.0, only make breaking changes when you increment the major version.
102-
Don’t break the build.
103-
* After 1.0.0, don’t add any new public API (no new `pub` anything) in patch-level
104-
versions. Always increment the minor version if you add any new `pub` structs,
105-
traits, fields, types, functions, methods or anything else.
106-
* Use version numbers with three numeric parts such as 1.0.0 rather than 1.0.
95+
The `version` field is formatted according to the [SemVer] specification:
10796

97+
Versions must have three numeric parts,
98+
the major version, the minor version, and the patch version.
99+
100+
A pre-release part can be added after a dash such as `1.0.0-alpha`.
101+
The pre-release part may be separated with periods to distinguish separate
102+
components. Numeric components will use numeric comparison while
103+
everything else will be compared lexicographically.
104+
For example, `1.0.0-alpha.11` is higher than `1.0.0-alpha.4`.
105+
106+
A metadata part can be added after a plus, such as `1.0.0+21AF26D3`.
107+
This is for informational purposes only and is generally ignored by Cargo.
108+
109+
Cargo bakes in the concept of [Semantic Versioning](https://semver.org/),
110+
so versions are considered considered [compatible](semver.md) if their left-most non-zero major/minor/patch component is the same.
108111
See the [Resolver] chapter for more information on how Cargo uses versions to
109-
resolve dependencies, and for guidelines on setting your own version. See the
110-
[SemVer compatibility] chapter for more details on exactly what constitutes a
111-
breaking change.
112+
resolve dependencies.
112113

113114
This field is optional and defaults to `0.0.0`. The field is required for publishing packages.
114115

115116
> **MSRV:** Before 1.75, this field was required
116117
118+
[SemVer]: https://semver.org
117119
[Resolver]: resolver.md
118120
[SemVer compatibility]: semver.md
119121

src/doc/src/reference/resolver.md

Lines changed: 140 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -5,99 +5,125 @@ use based on the version requirements specified in each package. This process
55
is called "dependency resolution" and is performed by the "resolver". The
66
result of the resolution is stored in the `Cargo.lock` file which "locks" the
77
dependencies to specific versions, and keeps them fixed over time.
8-
9-
The resolver attempts to unify common dependencies while considering possibly
10-
conflicting requirements. It turns out, however, that in many cases there is no
11-
single "best" dependency resolution, and so the resolver must use heuristics to
12-
choose a preferred solution. The sections below provide some details on how
13-
requirements are handled, and how to work with the resolver.
14-
15-
See the chapter [Specifying Dependencies] for more details about how
16-
dependency requirements are specified.
17-
188
The [`cargo tree`] command can be used to visualize the result of the
199
resolver.
2010

21-
[Specifying Dependencies]: specifying-dependencies.md
11+
[dependency specifications]: specifying-dependencies.md
12+
[dependency specification]: specifying-dependencies.md
2213
[`cargo tree`]: ../commands/cargo-tree.md
2314

24-
## SemVer compatibility
25-
26-
Cargo uses [SemVer] for specifying version numbers. This establishes a common
27-
convention for what is compatible between different versions of a package. See
28-
the [SemVer Compatibility] chapter for guidance on what is considered a
29-
"compatible" change. This notion of "compatibility" is important because Cargo
30-
assumes it should be safe to update a dependency within a compatibility range
31-
without breaking the build.
32-
33-
Versions are considered compatible if their left-most non-zero
34-
major/minor/patch component is the same. For example, `1.0.3` and `1.1.0` are
35-
considered compatible, and thus it should be safe to update from the older
36-
release to the newer one. However, an update from `1.1.0` to `2.0.0` would not
37-
be allowed to be made automatically. This convention also applies to versions
38-
with leading zeros. For example, `0.1.0` and `0.1.2` are compatible, but
39-
`0.1.0` and `0.2.0` are not. Similarly, `0.0.1` and `0.0.2` are not
40-
compatible.
41-
42-
As a quick refresher, the
43-
[*version requirement* syntax][Specifying Dependencies] Cargo uses for
44-
dependencies is:
45-
46-
Requirement | Example | Equivalence | Description
47-
------------|---------|-------------|-------------
48-
Caret | `1.2.3` or `^1.2.3` | <code>>=1.2.3,&nbsp;<2.0.0</code> | Any SemVer-compatible version of at least the given value.
49-
Tilde | `~1.2` | <code>>=1.2.0,&nbsp;<1.3.0</code> | Minimum version, with restricted compatibility range.
50-
Wildcard | `1.*` | <code>>=1.0.0,&nbsp;<2.0.0</code> | Any version in the `*` position.
51-
Equals | `=1.2.3` | <code>=1.2.3</code> | Exactly the specified version only.
52-
Comparison | `>1.1` | <code>>=1.2.0</code> | Naive numeric comparison of specified digits.
53-
Compound | <code>>=1.2,&nbsp;<1.5</code> | <code>>=1.2.0,&nbsp;<1.5.0</code> | Multiple requirements that must be simultaneously satisfied.
54-
55-
When multiple packages specify a dependency for a common package, the resolver
56-
attempts to ensure that they use the same version of that common package, as
57-
long as they are within a SemVer compatibility range. It also attempts to use
58-
the greatest version currently available within that compatibility range. For
59-
example, if there are two packages in the resolve graph with the following
60-
requirements:
15+
## Constraints and Heuristics
16+
17+
In many cases there is no single "best" dependency resolution.
18+
The resolver operates under various constraints and heuristics to find a generally applicable resolution.
19+
To understand how these interact, it is helpful to have a coarse understanding of how dependency resolution works.
20+
21+
This pseudo-code approximates what Cargo's resolver does:
22+
```rust
23+
pub fn resolve(workspace: &[Package], policy: Policy) -> Option<ResolveGraph> {
24+
let dep_queue = Queue::new(workspace);
25+
let resolved = ResolveGraph::new();
26+
resolve_next(pkq_queue, resolved, policy)
27+
}
28+
29+
fn resolve_next(dep_queue: Queue, resolved: ResolveGraph, policy: Policy) -> Option<ResolveGraph> {
30+
let Some(dep_spec) = policy.pick_next_dep(dep_queue) else {
31+
// Done
32+
return Some(resolved);
33+
};
34+
35+
if let Some(resolved) = policy.try_unify_version(dep_spec, resolved.clone()) {
36+
return Some(resolved);
37+
}
38+
39+
let dep_versions = dep_spec.lookup_versions()?;
40+
let mut dep_versions = policy.filter_versions(dep_spec, dep_versions);
41+
while let Some(dep_version) = policy.pick_next_version(&mut dep_versions) {
42+
if policy.needs_version_unification(dep_version, &resolved) {
43+
continue;
44+
}
45+
46+
let mut dep_queue = dep_queue.clone();
47+
dep_queue.enqueue(dep_version.dependencies);
48+
let mut resolved = resolved.clone();
49+
resolved.register(dep_version);
50+
if let Some(resolved) = resolve_next(dep_queue, resolved) {
51+
return Some(resolved);
52+
}
53+
}
54+
55+
// No valid solution found, backtrack and `pick_next_version`
56+
None
57+
}
58+
```
6159

60+
Key steps:
61+
- Walking dependencies (`pick_next_dep`):
62+
The order dependencies are walked can affect
63+
how related version requirements for the same dependency get resolved, see unifying versions,
64+
and how much the resolver backtracks, affecting resolver performance,
65+
- Unifying versions (`try_unify_version`, `needs_version_unification`):
66+
Cargo reuses versions where possible to reduce build times and allow types from common dependencies to be passed between APIs.
67+
If multiple versions would have been unified if it wasn't for conflicts in their [dependency specifications], Cargo will backtrack, erroring if no solution is found, rather than selecting multiple versions.
68+
A [dependency specification] or Cargo may decide that a version is undesirable,
69+
preferring to backtrack or error rather than use it.
70+
- Preferring versions (`pick_next_version`):
71+
Cargo may decide that it should prefer a specific version,
72+
falling back to the next version when backtracking.
73+
74+
### Version numbers
75+
76+
Cargo prefers the highest version currently available.
77+
78+
For example, if you had a package in the resolve graph with:
6279
```toml
63-
# Package A
6480
[dependencies]
65-
bitflags = "1.0"
81+
bitflags = "*"
82+
```
83+
If at the time the `Cargo.lock` file is generated, the greatest version of
84+
`bitflags` is `1.2.1`, then the package will use `1.2.1`.
6685

67-
# Package B
86+
### Version requirements
87+
88+
Package specify what versions they support, rejecting all others, through
89+
[version requirements].
90+
91+
For example, if you had a package in the resolve graph with:
92+
```toml
6893
[dependencies]
69-
bitflags = "1.1"
94+
bitflags = "1.0" # meaning `>=1.0.0,<2.0.0`
7095
```
71-
7296
If at the time the `Cargo.lock` file is generated, the greatest version of
73-
`bitflags` is `1.2.1`, then both packages will use `1.2.1` because it is the
97+
`bitflags` is `1.2.1`, then the package will use `1.2.1` because it is the
7498
greatest within the compatibility range. If `2.0.0` is published, it will
7599
still use `1.2.1` because `2.0.0` is considered incompatible.
76100

77-
If multiple packages have a common dependency with semver-incompatible
78-
versions, then Cargo will allow this, but will build two separate copies of
79-
the dependency. For example:
101+
[version requirements]: specifying-dependencies.md#version-requirement-syntax
102+
103+
### SemVer compatibility
104+
105+
Cargo assumes packages follow [SemVer] and will unify dependency versions if they are
106+
[SemVer] compatible according to the [Caret version requirements].
107+
If two compatible versions cannot be unified because of conflicting version requirements,
108+
Cargo will error.
80109

110+
See the [SemVer Compatibility] chapter for guidance on what is considered a
111+
"compatible" change.
112+
113+
Examples:
114+
115+
The following two packages will have their dependencies on `bitflags` unified because any version picked will be compatible with each other.
81116
```toml
82117
# Package A
83118
[dependencies]
84-
rand = "0.7"
119+
bitflags = "1.0" # meaning `>=1.0.0,<2.0.0`
85120

86121
# Package B
87122
[dependencies]
88-
rand = "0.6"
123+
bitflags = "1.1" # meaning `>=1.1.0,<2.0.0`
89124
```
90125

91-
The above will result in Package A using the greatest `0.7` release (`0.7.3`
92-
at the time of this writing) and Package B will use the greatest `0.6` release
93-
(`0.6.5` for example). This can lead to potential problems, see the
94-
[Version-incompatibility hazards] section for more details.
95-
96-
Multiple versions within the same compatibility range are not allowed and will
97-
result in a resolver error if it is constrained to two different versions
98-
within a compatibility range. For example, if there are two packages in the
99-
resolve graph with the following requirements:
100-
126+
The following packages will error because the version requirements conflict, selecting two distinct compatible versions.
101127
```toml
102128
# Package A
103129
[dependencies]
@@ -108,14 +134,39 @@ log = "=0.4.11"
108134
log = "=0.4.8"
109135
```
110136

111-
The above will fail because it is not allowed to have two separate copies of
112-
the `0.4` release of the `log` package.
137+
The following two packages will not have their dependencies on `rand` unified because only incompatible versions are available for each.
138+
Instead, two different versions (e.g. 0.6.5 and 0.7.3) will be resolved and built.
139+
This can lead to potential problems, see the [Version-incompatibility hazards] section for more details.
140+
```toml
141+
# Package A
142+
[dependencies]
143+
rand = "0.7" # meaning `>=0.7.0,<0.8.0`
144+
145+
# Package B
146+
[dependencies]
147+
rand = "0.6" # meaning `>=0.6.0,<0.7.0`
148+
```
149+
150+
Generally, the following two packages will not have their dependencies unified because incompatible versions are available that satisfy the version requirements:
151+
Instead, two different versions (e.g. 0.6.5 and 0.7.3) will be resolved and built.
152+
The application of other constraints or heuristics may cause these to be unified,
153+
picking one version (e.g. 0.6.5).
154+
```toml
155+
# Package A
156+
[dependencies]
157+
rand = ">=0.6,<0.8.0"
158+
159+
# Package B
160+
[dependencies]
161+
rand = "0.6" # meaning `>=0.6.0,<0.7.0`
162+
```
113163

114164
[SemVer]: https://semver.org/
115165
[SemVer Compatibility]: semver.md
166+
[Caret version requirements]: specifying-dependencies.md#default-requirements
116167
[Version-incompatibility hazards]: #version-incompatibility-hazards
117168

118-
### Version-incompatibility hazards
169+
#### Version-incompatibility hazards
119170

120171
When multiple versions of a crate appear in the resolve graph, this can cause
121172
problems when types from those crates are exposed by the crates using them.
@@ -150,54 +201,6 @@ ecosystem if you publish a SemVer-incompatible version of a popular library.
150201
[semver trick]: https://github.com/dtolnay/semver-trick
151202
[`downcast_ref`]: ../../std/any/trait.Any.html#method.downcast_ref
152203

153-
### Pre-releases
154-
155-
SemVer has the concept of "pre-releases" with a dash in the version, such as
156-
`1.0.0-alpha`, or `1.0.0-beta`. Cargo will avoid automatically using
157-
pre-releases unless explicitly asked. For example, if `1.0.0-alpha` of package
158-
`foo` is published, then a requirement of `foo = "1.0"` will *not* match, and
159-
will return an error. The pre-release must be specified, such as `foo =
160-
"1.0.0-alpha"`. Similarly [`cargo install`] will avoid pre-releases unless
161-
explicitly asked to install one.
162-
163-
Cargo allows "newer" pre-releases to be used automatically. For example, if
164-
`1.0.0-beta` is published, then a requirement `foo = "1.0.0-alpha"` will allow
165-
updating to the `beta` version. Note that this only works on the same release
166-
version, `foo = "1.0.0-alpha"` will not allow updating to `foo = "1.0.1-alpha"`
167-
or `foo = "1.0.1-beta"`.
168-
169-
Cargo will also upgrade automatically to semver-compatible released versions
170-
from prereleases. The requirement `foo = "1.0.0-alpha"` will allow updating to
171-
`foo = "1.0.0"` as well as `foo = "1.2.0"`.
172-
173-
Beware that pre-release versions can be unstable, and as such care should be
174-
taken when using them. Some projects may choose to publish breaking changes
175-
between pre-release versions. It is recommended to not use pre-release
176-
dependencies in a library if your library is not also a pre-release. Care
177-
should also be taken when updating your `Cargo.lock`, and be prepared if a
178-
pre-release update causes issues.
179-
180-
The pre-release tag may be separated with periods to distinguish separate
181-
components. Numeric components will use numeric comparison. For example,
182-
`1.0.0-alpha.4` will use numeric comparison for the `4` component. That means
183-
that if `1.0.0-alpha.11` is published, that will be chosen as the greatest
184-
release. Non-numeric components are compared lexicographically.
185-
186-
[`cargo install`]: ../commands/cargo-install.md
187-
188-
### Version metadata
189-
190-
SemVer has the concept of "version metadata" with a plus in the version, such
191-
as `1.0.0+21AF26D3`. This metadata is usually ignored, and should not be used
192-
in a version requirement. You should never publish multiple versions that
193-
differ only in the metadata tag.
194-
195-
## Other constraints
196-
197-
Version requirements aren't the only constraint that the resolver considers
198-
when selecting and unifying dependencies. The following sections cover some of
199-
the other constraints that can affect resolution.
200-
201204
### Features
202205

203206
For the purpose of generating `Cargo.lock`, the resolver builds the dependency
@@ -610,3 +613,20 @@ circumstances:
610613
change of your own library, for example if it exposes types from the
611614
dependency.
612615

616+
[`cargo install`]: ../commands/cargo-install.md
617+
618+
<script>
619+
(function() {
620+
var fragments = {
621+
"#version-metadata": "specifying-dependencies.html#version-metadata",
622+
"#pre-releases": "specifying-dependencies.html#pre-releases",
623+
"#other-constraints": "#constraints-and-heuristics",
624+
};
625+
var target = fragments[window.location.hash];
626+
if (target) {
627+
var url = window.location.toString();
628+
var base = url.substring(0, url.lastIndexOf('/'));
629+
window.location.replace(base + "/" + target);
630+
}
631+
})();
632+
</script>

0 commit comments

Comments
 (0)