1 use super::encode
::Metadata
;
2 use crate::core
::dependency
::DepKind
;
3 use crate::core
::{Dependency, PackageId, PackageIdSpec, Summary, Target}
;
4 use crate::util
::errors
::CargoResult
;
5 use crate::util
::interning
::InternedString
;
6 use crate::util
::Graph
;
7 use std
::borrow
::Borrow
;
9 use std
::collections
::{HashMap, HashSet}
;
12 /// Represents a fully-resolved package dependency graph. Each node in the graph
13 /// is a package and edges represent dependencies between packages.
15 /// Each instance of `Resolve` also understands the full set of features used
18 /// A graph, whose vertices are packages and edges are dependency specifications
19 /// from `Cargo.toml`. We need a `HashSet` here because the same package
20 /// might be present in both `[dependencies]` and `[build-dependencies]`.
21 graph
: Graph
<PackageId
, HashSet
<Dependency
>>,
22 /// Replacements from the `[replace]` table.
23 replacements
: HashMap
<PackageId
, PackageId
>,
24 /// Inverted version of `replacements`.
25 reverse_replacements
: HashMap
<PackageId
, PackageId
>,
26 /// An empty `Vec` to avoid creating a new `Vec` for every package
27 /// that does not have any features, and to avoid using `Option` to
29 empty_features
: Vec
<InternedString
>,
30 /// Features enabled for a given package.
31 features
: HashMap
<PackageId
, Vec
<InternedString
>>,
32 /// Checksum for each package. A SHA256 hash of the `.crate` file used to
33 /// validate the correct crate file is used. This is `None` for sources
34 /// that do not use `.crate` files, like path or git dependencies.
35 checksums
: HashMap
<PackageId
, Option
<String
>>,
36 /// "Unknown" metadata. This is a collection of extra, unrecognized data
37 /// found in the `[metadata]` section of `Cargo.lock`, preserved for
38 /// forwards compatibility.
40 /// `[patch]` entries that did not match anything, preserved in
41 /// `Cargo.lock` as the `[[patch.unused]]` table array. Tracking unused
42 /// patches helps prevent Cargo from being forced to re-update the
43 /// registry every time it runs, and keeps the resolve in a locked state
44 /// so it doesn't re-resolve the unused entries.
45 unused_patches
: Vec
<PackageId
>,
46 /// A map from packages to a set of their public dependencies
47 public_dependencies
: HashMap
<PackageId
, HashSet
<PackageId
>>,
48 /// Version of the `Cargo.lock` format, see
49 /// `cargo::core::resolver::encode` for more.
50 version
: ResolveVersion
,
51 summaries
: HashMap
<PackageId
, Summary
>,
54 /// A version to indicate how a `Cargo.lock` should be serialized. Currently
55 /// V2 is the default when creating a new lockfile. If a V1 lockfile already
56 /// exists, it will stay as V1.
58 /// It's theorized that we can add more here over time to track larger changes
59 /// to the `Cargo.lock` format, but we've yet to see how that strategy pans out.
60 #[derive(PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)]
61 pub enum ResolveVersion
{
62 /// Historical baseline for when this abstraction was added.
64 /// A more compact format, more amenable to avoiding source-control merge
65 /// conflicts. The `dependencies` arrays are compressed and checksums are
66 /// listed inline. Introduced in 2019 in version 1.38. New lockfiles use
67 /// V2 by default starting in 1.41.
73 graph
: Graph
<PackageId
, HashSet
<Dependency
>>,
74 replacements
: HashMap
<PackageId
, PackageId
>,
75 features
: HashMap
<PackageId
, Vec
<InternedString
>>,
76 checksums
: HashMap
<PackageId
, Option
<String
>>,
78 unused_patches
: Vec
<PackageId
>,
79 version
: ResolveVersion
,
80 summaries
: HashMap
<PackageId
, Summary
>,
82 let reverse_replacements
= replacements
.iter().map(|(&p
, &r
)| (r
, p
)).collect();
83 let public_dependencies
= graph
86 let public_deps
= graph
90 .any(|d
| d
.kind() == DepKind
::Normal
&& d
.is_public())
92 .map(|(dep_package
, _
)| *dep_package
)
93 .collect
::<HashSet
<PackageId
>>();
106 empty_features
: Vec
::new(),
107 reverse_replacements
,
114 /// Resolves one of the paths from the given dependent package up to
116 pub fn path_to_top
<'a
>(&'a
self, pkg
: &'a PackageId
) -> Vec
<&'a PackageId
> {
117 self.graph
.path_to_top(pkg
)
120 pub fn register_used_patches(&mut self, patches
: &[Summary
]) {
121 for summary
in patches
{
122 if !self.graph
.contains(&summary
.package_id()) {
123 self.unused_patches
.push(summary
.package_id())
128 pub fn merge_from(&mut self, previous
: &Resolve
) -> CargoResult
<()> {
129 // Given a previous instance of resolve, it should be forbidden to ever
130 // have a checksums which *differ*. If the same package ID has differing
131 // checksums, then something has gone wrong such as:
133 // * Something got seriously corrupted
134 // * A "mirror" isn't actually a mirror as some changes were made
135 // * A replacement source wasn't actually a replacement, some changes
138 // In all of these cases, we want to report an error to indicate that
139 // something is awry. Normal execution (esp just using crates.io) should
140 // never run into this.
141 for (id
, cksum
) in previous
.checksums
.iter() {
142 if let Some(mine
) = self.checksums
.get(id
) {
147 // If the previous checksum wasn't calculated, the current
148 // checksum is `Some`. This may indicate that a source was
149 // erroneously replaced or was replaced with something that
150 // desires stronger checksum guarantees than can be afforded
155 checksum for `{}` was not previously calculated, but a checksum could now \
158 this could be indicative of a few possible situations:
160 * the source `{}` did not previously support checksums,
161 but was replaced with one that does
162 * newer Cargo implementations know how to checksum this source, but this
163 older implementation does not
164 * the lock file is corrupt
170 // If our checksum hasn't been calculated, then it could mean
171 // that future Cargo figured out how to checksum something or
172 // more realistically we were overridden with a source that does
173 // not have checksums.
174 } else if mine
.is_none() {
177 checksum for `{}` could not be calculated, but a checksum is listed in \
178 the existing lock file
180 this could be indicative of a few possible situations:
182 * the source `{}` supports checksums,
183 but was replaced with one that doesn't
184 * the lock file is corrupt
186 unable to verify that `{0}` is the same as when the lockfile was generated
192 // If the checksums aren't equal, and neither is None, then they
193 // must both be Some, in which case the checksum now differs.
198 checksum for `{}` changed between lock files
200 this could be indicative of a few possible errors:
202 * the lock file is corrupt
203 * a replacement source in use (e.g., a mirror) returned a different checksum
204 * the source itself may be corrupt in one way or another
206 unable to verify that `{0}` is the same as when the lockfile was generated
214 // Be sure to just copy over any unknown metadata.
215 self.metadata
= previous
.metadata
.clone();
217 // The goal of Cargo is largely to preserve the encoding of `Cargo.lock`
218 // that it finds on the filesystem. Sometimes `Cargo.lock` changes are
219 // in the works where they haven't been set as the default yet but will
220 // become the default soon.
222 // The scenarios we could be in are:
224 // * This is a brand new lock file with nothing previous. In that case
225 // this method isn't actually called at all, but instead
226 // `default_for_new_lockfiles` called below was encoded during the
227 // resolution step, so that's what we're gonna use.
229 // * We have an old lock file. In this case we want to switch the
230 // version to `default_for_old_lockfiles`. That puts us in one of
233 // * Our version is older than the default. This means that we're
234 // migrating someone forward, so we switch the encoding.
235 // * Our version is equal to the default, nothing to do!
236 // * Our version is *newer* than the default. This is where we
237 // critically keep the new version of encoding.
239 // This strategy should get new lockfiles into the pipeline more quickly
240 // while ensuring that any time an old cargo sees a future lock file it
241 // keeps the future lockfile encoding.
242 self.version
= cmp
::max(
244 ResolveVersion
::default_for_old_lockfiles(),
250 pub fn contains
<Q
: ?Sized
>(&self, k
: &Q
) -> bool
252 PackageId
: Borrow
<Q
>,
255 self.graph
.contains(k
)
258 pub fn sort(&self) -> Vec
<PackageId
> {
262 pub fn iter
<'a
>(&'a
self) -> impl Iterator
<Item
= PackageId
> + 'a
{
263 self.graph
.iter().cloned()
266 pub fn deps(&self, pkg
: PackageId
) -> impl Iterator
<Item
= (PackageId
, &HashSet
<Dependency
>)> {
267 self.deps_not_replaced(pkg
)
268 .map(move |(id
, deps
)| (self.replacement(id
).unwrap_or(id
), deps
))
271 pub fn deps_not_replaced(
274 ) -> impl Iterator
<Item
= (PackageId
, &HashSet
<Dependency
>)> {
275 self.graph
.edges(&pkg
).map(|(id
, deps
)| (*id
, deps
))
278 pub fn replacement(&self, pkg
: PackageId
) -> Option
<PackageId
> {
279 self.replacements
.get(&pkg
).cloned()
282 pub fn replacements(&self) -> &HashMap
<PackageId
, PackageId
> {
286 pub fn features(&self, pkg
: PackageId
) -> &[InternedString
] {
287 self.features
.get(&pkg
).unwrap_or(&self.empty_features
)
290 /// This is only here for legacy support, it will be removed when
291 /// switching to the new feature resolver.
292 pub fn features_clone(&self) -> HashMap
<PackageId
, Vec
<InternedString
>> {
293 self.features
.clone()
296 pub fn is_public_dep(&self, pkg
: PackageId
, dep
: PackageId
) -> bool
{
297 self.public_dependencies
299 .map(|public_deps
| public_deps
.contains(&dep
))
300 .unwrap_or_else(|| panic
!("Unknown dependency {:?} for package {:?}", dep
, pkg
))
303 pub fn query(&self, spec
: &str) -> CargoResult
<PackageId
> {
304 PackageIdSpec
::query_str(spec
, self.iter())
307 pub fn specs_to_ids(&self, specs
: &[PackageIdSpec
]) -> CargoResult
<Vec
<PackageId
>> {
308 specs
.iter().map(|s
| s
.query(self.iter())).collect()
311 pub fn unused_patches(&self) -> &[PackageId
] {
315 pub fn checksums(&self) -> &HashMap
<PackageId
, Option
<String
>> {
319 pub fn metadata(&self) -> &Metadata
{
323 pub fn extern_crate_name(
328 ) -> CargoResult
<String
> {
329 let empty_set
: HashSet
<Dependency
> = HashSet
::new();
330 let deps
= if from
== to
{
333 self.dependencies_listed(from
, to
)
336 let crate_name
= to_target
.crate_name();
337 let mut names
= deps
.iter().map(|d
| {
338 d
.explicit_name_in_toml()
339 .map(|s
| s
.as_str().replace("-", "_"))
340 .unwrap_or_else(|| crate_name
.clone())
342 let name
= names
.next().unwrap_or_else(|| crate_name
.clone());
346 "the crate `{}` depends on crate `{}` multiple times with different names",
354 fn dependencies_listed(&self, from
: PackageId
, to
: PackageId
) -> &HashSet
<Dependency
> {
355 // We've got a dependency on `from` to `to`, but this dependency edge
356 // may be affected by [replace]. If the `to` package is listed as the
357 // target of a replacement (aka the key of a reverse replacement map)
358 // then we try to find our dependency edge through that. If that fails
359 // then we go down below assuming it's not replaced.
361 // Note that we don't treat `from` as if it's been replaced because
362 // that's where the dependency originates from, and we only replace
363 // targets of dependencies not the originator.
364 if let Some(replace
) = self.reverse_replacements
.get(&to
) {
365 if let Some(deps
) = self.graph
.edge(&from
, replace
) {
369 match self.graph
.edge(&from
, &to
) {
371 None
=> panic
!("no Dependency listed for `{}` => `{}`", from
, to
),
375 /// Returns the version of the encoding that's being used for this lock
377 pub fn version(&self) -> &ResolveVersion
{
381 pub fn summary(&self, pkg_id
: PackageId
) -> &Summary
{
382 &self.summaries
[&pkg_id
]
386 impl PartialEq
for Resolve
{
387 fn eq(&self, other
: &Resolve
) -> bool
{
388 macro_rules
! compare
{
389 ($
($fields
:ident
)* | $
($ignored
:ident
)*) => {
390 let Resolve { $($fields,)* $($ignored: _,)* }
= self;
391 $
($fields
== &other
.$fields
)&&*
396 graph replacements reverse_replacements empty_features features
397 checksums metadata unused_patches public_dependencies summaries
405 impl fmt
::Debug
for Resolve
{
406 fn fmt(&self, fmt
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
407 writeln
!(fmt
, "graph: {:?}", self.graph
)?
;
408 writeln
!(fmt
, "\nfeatures: {{")?
;
409 for (pkg
, features
) in &self.features
{
410 writeln
!(fmt
, " {}: {:?}", pkg
, features
)?
;
416 impl ResolveVersion
{
417 /// The default way to encode new `Cargo.lock` files.
419 /// It's important that if a new version of `ResolveVersion` is added that
420 /// this is not updated until *at least* the support for the version is in
421 /// the stable release of Rust. It's ok for this to be newer than
422 /// `default_for_old_lockfiles` below.
423 pub fn default_for_new_lockfiles() -> ResolveVersion
{
427 /// The default way to encode old preexisting `Cargo.lock` files. This is
428 /// often trailing the new lockfiles one above to give older projects a
429 /// longer time to catch up.
431 /// It's important that this trails behind `default_for_new_lockfiles` for
432 /// quite some time. This gives projects a quite large window to update in
433 /// where we don't force updates, so if projects span many versions of Cargo
434 /// all those versions of Cargo will have support for a new version of the
436 pub fn default_for_old_lockfiles() -> ResolveVersion
{