]> git.proxmox.com Git - cargo.git/blob - src/cargo/core/resolver/resolve.rs
Move string interning to util
[cargo.git] / src / cargo / core / resolver / resolve.rs
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;
8 use std::cmp;
9 use std::collections::{HashMap, HashSet};
10 use std::fmt;
11
12 /// Represents a fully-resolved package dependency graph. Each node in the graph
13 /// is a package and edges represent dependencies between packages.
14 ///
15 /// Each instance of `Resolve` also understands the full set of features used
16 /// for each package.
17 pub struct Resolve {
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
28 /// simplify the API.
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.
39 metadata: Metadata,
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>,
52 }
53
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.
57 ///
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.
63 V1,
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.
68 V2,
69 }
70
71 impl Resolve {
72 pub fn new(
73 graph: Graph<PackageId, HashSet<Dependency>>,
74 replacements: HashMap<PackageId, PackageId>,
75 features: HashMap<PackageId, Vec<InternedString>>,
76 checksums: HashMap<PackageId, Option<String>>,
77 metadata: Metadata,
78 unused_patches: Vec<PackageId>,
79 version: ResolveVersion,
80 summaries: HashMap<PackageId, Summary>,
81 ) -> Resolve {
82 let reverse_replacements = replacements.iter().map(|(&p, &r)| (r, p)).collect();
83 let public_dependencies = graph
84 .iter()
85 .map(|p| {
86 let public_deps = graph
87 .edges(p)
88 .filter(|(_, deps)| {
89 deps.iter()
90 .any(|d| d.kind() == DepKind::Normal && d.is_public())
91 })
92 .map(|(dep_package, _)| *dep_package)
93 .collect::<HashSet<PackageId>>();
94
95 (*p, public_deps)
96 })
97 .collect();
98
99 Resolve {
100 graph,
101 replacements,
102 features,
103 checksums,
104 metadata,
105 unused_patches,
106 empty_features: Vec::new(),
107 reverse_replacements,
108 public_dependencies,
109 version,
110 summaries,
111 }
112 }
113
114 /// Resolves one of the paths from the given dependent package up to
115 /// the root.
116 pub fn path_to_top<'a>(&'a self, pkg: &'a PackageId) -> Vec<&'a PackageId> {
117 self.graph.path_to_top(pkg)
118 }
119
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())
124 };
125 }
126 }
127
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:
132 //
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
136 // were made
137 //
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) {
143 if mine == cksum {
144 continue;
145 }
146
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
151 // elsewhere.
152 if cksum.is_none() {
153 anyhow::bail!(
154 "\
155 checksum for `{}` was not previously calculated, but a checksum could now \
156 be calculated
157
158 this could be indicative of a few possible situations:
159
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
165 ",
166 id,
167 id.source_id()
168 )
169
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() {
175 anyhow::bail!(
176 "\
177 checksum for `{}` could not be calculated, but a checksum is listed in \
178 the existing lock file
179
180 this could be indicative of a few possible situations:
181
182 * the source `{}` supports checksums,
183 but was replaced with one that doesn't
184 * the lock file is corrupt
185
186 unable to verify that `{0}` is the same as when the lockfile was generated
187 ",
188 id,
189 id.source_id()
190 )
191
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.
194 // That's quite bad!
195 } else {
196 anyhow::bail!(
197 "\
198 checksum for `{}` changed between lock files
199
200 this could be indicative of a few possible errors:
201
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
205
206 unable to verify that `{0}` is the same as when the lockfile was generated
207 ",
208 id
209 );
210 }
211 }
212 }
213
214 // Be sure to just copy over any unknown metadata.
215 self.metadata = previous.metadata.clone();
216
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.
221 //
222 // The scenarios we could be in are:
223 //
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.
228 //
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
231 // three cases:
232 //
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.
238 //
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(
243 previous.version,
244 ResolveVersion::default_for_old_lockfiles(),
245 );
246
247 Ok(())
248 }
249
250 pub fn contains<Q: ?Sized>(&self, k: &Q) -> bool
251 where
252 PackageId: Borrow<Q>,
253 Q: Ord + Eq,
254 {
255 self.graph.contains(k)
256 }
257
258 pub fn sort(&self) -> Vec<PackageId> {
259 self.graph.sort()
260 }
261
262 pub fn iter<'a>(&'a self) -> impl Iterator<Item = PackageId> + 'a {
263 self.graph.iter().cloned()
264 }
265
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))
269 }
270
271 pub fn deps_not_replaced(
272 &self,
273 pkg: PackageId,
274 ) -> impl Iterator<Item = (PackageId, &HashSet<Dependency>)> {
275 self.graph.edges(&pkg).map(|(id, deps)| (*id, deps))
276 }
277
278 pub fn replacement(&self, pkg: PackageId) -> Option<PackageId> {
279 self.replacements.get(&pkg).cloned()
280 }
281
282 pub fn replacements(&self) -> &HashMap<PackageId, PackageId> {
283 &self.replacements
284 }
285
286 pub fn features(&self, pkg: PackageId) -> &[InternedString] {
287 self.features.get(&pkg).unwrap_or(&self.empty_features)
288 }
289
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()
294 }
295
296 pub fn is_public_dep(&self, pkg: PackageId, dep: PackageId) -> bool {
297 self.public_dependencies
298 .get(&pkg)
299 .map(|public_deps| public_deps.contains(&dep))
300 .unwrap_or_else(|| panic!("Unknown dependency {:?} for package {:?}", dep, pkg))
301 }
302
303 pub fn query(&self, spec: &str) -> CargoResult<PackageId> {
304 PackageIdSpec::query_str(spec, self.iter())
305 }
306
307 pub fn specs_to_ids(&self, specs: &[PackageIdSpec]) -> CargoResult<Vec<PackageId>> {
308 specs.iter().map(|s| s.query(self.iter())).collect()
309 }
310
311 pub fn unused_patches(&self) -> &[PackageId] {
312 &self.unused_patches
313 }
314
315 pub fn checksums(&self) -> &HashMap<PackageId, Option<String>> {
316 &self.checksums
317 }
318
319 pub fn metadata(&self) -> &Metadata {
320 &self.metadata
321 }
322
323 pub fn extern_crate_name(
324 &self,
325 from: PackageId,
326 to: PackageId,
327 to_target: &Target,
328 ) -> CargoResult<String> {
329 let empty_set: HashSet<Dependency> = HashSet::new();
330 let deps = if from == to {
331 &empty_set
332 } else {
333 self.dependencies_listed(from, to)
334 };
335
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())
341 });
342 let name = names.next().unwrap_or_else(|| crate_name.clone());
343 for n in names {
344 anyhow::ensure!(
345 n == name,
346 "the crate `{}` depends on crate `{}` multiple times with different names",
347 from,
348 to,
349 );
350 }
351 Ok(name)
352 }
353
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.
360 //
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) {
366 return deps;
367 }
368 }
369 match self.graph.edge(&from, &to) {
370 Some(ret) => ret,
371 None => panic!("no Dependency listed for `{}` => `{}`", from, to),
372 }
373 }
374
375 /// Returns the version of the encoding that's being used for this lock
376 /// file.
377 pub fn version(&self) -> &ResolveVersion {
378 &self.version
379 }
380
381 pub fn summary(&self, pkg_id: PackageId) -> &Summary {
382 &self.summaries[&pkg_id]
383 }
384 }
385
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)&&*
392 }
393 }
394 compare! {
395 // fields to compare
396 graph replacements reverse_replacements empty_features features
397 checksums metadata unused_patches public_dependencies summaries
398 |
399 // fields to ignore
400 version
401 }
402 }
403 }
404
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)?;
411 }
412 write!(fmt, "}}")
413 }
414 }
415
416 impl ResolveVersion {
417 /// The default way to encode new `Cargo.lock` files.
418 ///
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 {
424 ResolveVersion::V2
425 }
426
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.
430 ///
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
435 /// lock file.
436 pub fn default_for_old_lockfiles() -> ResolveVersion {
437 ResolveVersion::V1
438 }
439 }