Expand description
Resolution of the entire dependency graph for a crate.
This module implements the core logic in taking the world of crates and constraints and creating a resolved graph with locked versions for all crates and their dependencies. This is separate from the registry module which is more worried about discovering crates from various sources, this module just uses the Registry trait as a source to learn about crates from.
Actually solving a constraint graph is an NP-hard problem. This algorithm is basically a nice heuristic to make sure we get roughly the best answer most of the time. The constraints that we’re working with are:
- Each crate can have any number of dependencies. Each dependency can declare a version range that it is compatible with.
- Crates can be activated with multiple version (e.g., show up in the dependency graph twice) so long as each pairwise instance have semver-incompatible versions.
The algorithm employed here is fairly simple, we simply do a DFS, activating the “newest crate” (highest version) first and then going to the next option. The heuristics we employ are:
- Never try to activate a crate version which is incompatible. This means we only try crates which will actually satisfy a dependency and we won’t ever try to activate a crate that’s semver compatible with something else activated (as we’re only allowed to have one) nor try to activate a crate that has the same links attribute as something else activated.
- Always try to activate the highest version crate first. The default
dependency in Cargo (e.g., when you write
foo = "0.1.2"
) is semver-compatible, so selecting the highest version possible will allow us to hopefully satisfy as many dependencies at once.
Beyond that, what’s implemented below is just a naive backtracking version which should in theory try all possible combinations of dependencies and versions to see if one works. The first resolution that works causes everything to bail out immediately and return success, and only if nothing works do we actually return an error up the stack.
Resolution is currently performed twice
- With all features enabled (this is what gets saved to
Cargo.lock
) - With only the specific features the user selected on the command-line. Ideally this run will get removed in the future when transitioning to the new feature resolver.
A new feature-specific resolver was added in 2020 which adds more sophisticated feature
resolution. It is located in the features
module. The original dependency resolver still
performs feature unification, as it can help reduce the dependencies it has to consider during
resolution (rather than assuming every optional dependency of every package is enabled).
Checking if a feature is enabled must go through the new feature resolver.
Performance
Note that this is a relatively performance-critical portion of Cargo. The data that we’re processing is proportional to the size of the dependency graph, which can often be quite large (e.g., take a look at Servo). To make matters worse the DFS algorithm we’re implemented is inherently quite inefficient. When we add the requirement of backtracking on top it means that we’re implementing something that probably shouldn’t be allocating all over the place.
Re-exports
pub use self::features::CliFeatures;
pub use self::features::ForceAllTargets;
pub use self::features::HasDevUnits;
Modules
- context 🔒
- There are 2 sources of facts for the resolver:
- encode 🔒Definition of how to encode a
Resolve
into a TOMLCargo.lock
file - errors 🔒
- Resolves conditional compilation for
features
section in the manifest. - resolve 🔒
- types 🔒
- This module implements support for preferring some versions of a package over other versions.
Structs
- The
Cargo.lock
structure. - A helper “iterator” used to extract candidates within a current
Context
of a dependency graph. - Represents a fully-resolved package dependency graph. Each node in the graph is a package and edges represent dependencies between packages.
- Error during resolution providing a path of
PackageId
s. - Options for how the resolve should work.
- A collection of preferences for particular package versions.
Enums
- Resolver behavior, used to opt-in to new behavior that is backwards-incompatible via the
resolver
field in the manifest. - A version to indicate how a
Cargo.lock
should be serialized.
Functions
- activate 🔒Attempts to activate the summary
candidate
in the contextcx
. - Recursively activates the dependencies for
summaries
, in depth-first order, backtracking across possible candidates for each dependency as necessary. - Checks that packages are unique when written to lock file.
- Looks through the states in
backtrack_stack
for dependencies with remaining candidates. For each one, also checks if rolling back could change the outcome of the failed resolution that caused backtracking in the first place. Namely, if we’ve backtracked past the parent of the failed dep, or any of the packages flagged as giving us trouble inconflicting_activations
. - Attempts to find a new conflict that allows a
find_candidate
better then the input one. It will add the new conflict to the cache if one is found. - Builds the list of all packages required to build the first argument.
- Returns Some of the largest item in the iterator. Returns None if any of the items are None or the iterator is empty.