1use cargo_util_schemas::core::PartialVersion;
2use cargo_util_schemas::manifest::RustVersion;
3
4use crate::core::dependency::DepKind;
5use crate::core::{Dependency, PackageId, PackageIdSpec, PackageIdSpecQuery, Summary, Target};
6use crate::util::Graph;
7use crate::util::data_structures::{HashMap, HashSet};
8use crate::util::errors::CargoResult;
9use crate::util::interning::InternedString;
10use cargo_util_schemas::lockfile::TomlLockfileMetadata;
11use std::borrow::Borrow;
12use std::fmt;
13
14pub struct Resolve {
20 graph: Graph<PackageId, HashSet<Dependency>>,
24 replacements: HashMap<PackageId, PackageId>,
26 reverse_replacements: HashMap<PackageId, PackageId>,
28 features: HashMap<PackageId, Vec<InternedString>>,
30 checksums: HashMap<PackageId, Option<String>>,
34 metadata: TomlLockfileMetadata,
38 unused_patches: Vec<PackageId>,
44 public_dependencies: HashMap<PackageId, HashSet<PackageId>>,
46 version: ResolveVersion,
49 summaries: HashMap<PackageId, Summary>,
50}
51
52#[derive(PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)]
71pub enum ResolveVersion {
72 V1,
74 V2,
81 V3,
89 V4,
96 V5,
100}
101
102impl ResolveVersion {
103 fn default() -> ResolveVersion {
111 ResolveVersion::V4
112 }
113
114 pub fn max_stable() -> ResolveVersion {
120 ResolveVersion::V4
121 }
122
123 pub fn with_rust_version(rust_version: Option<&RustVersion>) -> Self {
125 let Some(rust_version) = rust_version else {
126 return ResolveVersion::default();
127 };
128
129 let rust = |major, minor| -> RustVersion {
130 PartialVersion {
131 major,
132 minor: Some(minor),
133 patch: None,
134 pre: None,
135 build: None,
136 }
137 .try_into()
138 .unwrap()
139 };
140
141 if rust_version >= &rust(1, 83) {
142 ResolveVersion::V4
143 } else if rust_version >= &rust(1, 53) {
144 ResolveVersion::V3
145 } else if rust_version >= &rust(1, 41) {
146 ResolveVersion::V2
147 } else {
148 ResolveVersion::V1
149 }
150 }
151}
152
153impl Resolve {
154 pub fn new(
155 graph: Graph<PackageId, HashSet<Dependency>>,
156 replacements: HashMap<PackageId, PackageId>,
157 features: HashMap<PackageId, Vec<InternedString>>,
158 checksums: HashMap<PackageId, Option<String>>,
159 metadata: TomlLockfileMetadata,
160 unused_patches: Vec<PackageId>,
161 version: ResolveVersion,
162 summaries: HashMap<PackageId, Summary>,
163 ) -> Resolve {
164 let reverse_replacements = replacements.iter().map(|(&p, &r)| (r, p)).collect();
165 let public_dependencies = graph
166 .iter()
167 .map(|p| {
168 let public_deps = graph
169 .edges(p)
170 .filter(|(_, deps)| {
171 deps.iter()
172 .any(|d| d.kind() == DepKind::Normal && d.is_public())
173 })
174 .map(|(dep_package, _)| *dep_package)
175 .collect::<HashSet<PackageId>>();
176
177 (*p, public_deps)
178 })
179 .collect();
180
181 Resolve {
182 graph,
183 replacements,
184 features,
185 checksums,
186 metadata,
187 unused_patches,
188 reverse_replacements,
189 public_dependencies,
190 version,
191 summaries,
192 }
193 }
194
195 pub fn path_to_top<'a>(
198 &'a self,
199 pkg: &'a PackageId,
200 ) -> Vec<(&'a PackageId, Option<&'a HashSet<Dependency>>)> {
201 self.graph.path_to_top(pkg)
202 }
203
204 pub fn register_used_patches<'a>(&mut self, patches: impl Iterator<Item = &'a Summary>) {
205 for summary in patches {
206 if !self.graph.contains(&summary.package_id()) {
207 self.unused_patches.push(summary.package_id())
208 };
209 }
210 }
211
212 pub fn merge_from(&mut self, previous: &Resolve) -> CargoResult<()> {
213 for (id, cksum) in previous.checksums.iter() {
226 if let Some(mine) = self.checksums.get(id) {
227 if mine == cksum {
228 continue;
229 }
230
231 if cksum.is_none() {
237 anyhow::bail!(
238 "\
239checksum for `{}` was not previously calculated, but a checksum could now \
240be calculated
241
242this could be indicative of a few possible situations:
243
244 * the source `{}` did not previously support checksums,
245 but was replaced with one that does
246 * newer Cargo implementations know how to checksum this source, but this
247 older implementation does not
248 * the lock file is corrupt
249",
250 id,
251 id.source_id()
252 )
253
254 } else if mine.is_none() {
259 anyhow::bail!(
260 "\
261checksum for `{}` could not be calculated, but a checksum is listed in \
262the existing lock file
263
264this could be indicative of a few possible situations:
265
266 * the source `{}` supports checksums,
267 but was replaced with one that doesn't
268 * the lock file is corrupt
269
270unable to verify that `{0}` is the same as when the lockfile was generated
271",
272 id,
273 id.source_id()
274 )
275
276 } else {
280 anyhow::bail!(
281 "\
282checksum for `{}` changed between lock files
283
284this could be indicative of a few possible errors:
285
286 * the lock file is corrupt
287 * a replacement source in use (e.g., a mirror) returned a different checksum
288 * the source itself may be corrupt in one way or another
289
290unable to verify that `{0}` is the same as when the lockfile was generated
291",
292 id
293 );
294 }
295 }
296 }
297
298 self.metadata = previous.metadata.clone();
300
301 self.version = previous.version;
303
304 Ok(())
305 }
306
307 pub fn contains<Q: ?Sized>(&self, k: &Q) -> bool
308 where
309 PackageId: Borrow<Q>,
310 Q: Ord + Eq,
311 {
312 self.graph.contains(k)
313 }
314
315 pub fn sort(&self) -> Vec<PackageId> {
316 self.graph.sort()
317 }
318
319 pub fn iter(&self) -> impl Iterator<Item = PackageId> + '_ {
320 self.graph.iter().cloned()
321 }
322
323 pub fn len(&self) -> usize {
324 self.graph.len()
325 }
326
327 pub fn deps(&self, pkg: PackageId) -> impl Iterator<Item = (PackageId, &HashSet<Dependency>)> {
328 self.deps_not_replaced(pkg)
329 .map(move |(id, deps)| (self.replacement(id).unwrap_or(id), deps))
330 }
331
332 pub fn bindeps(&self, pkg: PackageId) -> impl Iterator<Item = (PackageId, &Dependency)> {
333 self.deps(pkg).flat_map(|(dep_id, deps)| {
334 deps.iter()
335 .filter(|dep| dep.artifact().is_some())
336 .map(move |dep| (dep_id, dep))
337 })
338 }
339
340 pub fn deps_not_replaced(
341 &self,
342 pkg: PackageId,
343 ) -> impl Iterator<Item = (PackageId, &HashSet<Dependency>)> {
344 self.graph.edges(&pkg).map(|(id, deps)| (*id, deps))
345 }
346
347 pub fn transitive_deps_not_replaced(
350 &self,
351 pkg: PackageId,
352 ) -> impl Iterator<Item = (PackageId, &Dependency)> {
353 self.graph.edges(&pkg).filter_map(|(id, deps)| {
354 deps.iter()
355 .find(|d| d.is_transitive())
356 .map(|transitive_dep| (*id, transitive_dep))
357 })
358 }
359
360 pub fn replacement(&self, pkg: PackageId) -> Option<PackageId> {
361 self.replacements.get(&pkg).cloned()
362 }
363
364 pub fn replacements(&self) -> &HashMap<PackageId, PackageId> {
365 &self.replacements
366 }
367
368 pub fn features(&self, pkg: PackageId) -> &[InternedString] {
369 self.features.get(&pkg).map(|v| &**v).unwrap_or(&[])
370 }
371
372 pub fn features_clone(&self) -> HashMap<PackageId, Vec<InternedString>> {
375 self.features.clone()
376 }
377
378 pub fn is_public_dep(&self, pkg: PackageId, dep: PackageId) -> bool {
379 self.public_dependencies
380 .get(&pkg)
381 .map(|public_deps| public_deps.contains(&dep))
382 .unwrap_or_else(|| panic!("Unknown dependency {:?} for package {:?}", dep, pkg))
383 }
384
385 pub fn query(&self, spec: &str) -> CargoResult<PackageId> {
386 PackageIdSpec::query_str(spec, self.iter())
387 }
388
389 pub fn specs_to_ids(&self, specs: &[PackageIdSpec]) -> CargoResult<Vec<PackageId>> {
390 specs.iter().map(|s| s.query(self.iter())).collect()
391 }
392
393 pub fn unused_patches(&self) -> &[PackageId] {
394 &self.unused_patches
395 }
396
397 pub fn checksums(&self) -> &HashMap<PackageId, Option<String>> {
398 &self.checksums
399 }
400
401 pub fn set_checksum(&mut self, pkg_id: PackageId, checksum: String) {
402 self.checksums.insert(pkg_id, Some(checksum));
403 }
404
405 pub fn metadata(&self) -> &TomlLockfileMetadata {
406 &self.metadata
407 }
408
409 pub fn extern_crate_name_and_dep_name(
410 &self,
411 from: PackageId,
412 to: PackageId,
413 to_target: &Target,
414 ) -> CargoResult<(InternedString, Option<InternedString>)> {
415 let empty_set: HashSet<Dependency> = HashSet::default();
416 let deps = if from == to {
417 &empty_set
418 } else {
419 self.dependencies_listed(from, to)
420 };
421
422 let target_crate_name = || (to_target.crate_name(), None);
423 let mut name_pairs = deps.iter().map(|d| {
424 d.explicit_name_in_toml()
425 .map(|s| (s.as_str().replace("-", "_"), Some(s)))
426 .unwrap_or_else(target_crate_name)
427 });
428 let (extern_crate_name, dep_name) = name_pairs.next().unwrap_or_else(target_crate_name);
429 for (n, _) in name_pairs {
430 anyhow::ensure!(
431 n == extern_crate_name,
432 "the crate `{}` depends on crate `{}` multiple times with different names",
433 from,
434 to,
435 );
436 }
437 Ok((extern_crate_name.into(), dep_name))
438 }
439
440 fn dependencies_listed(&self, from: PackageId, to: PackageId) -> &HashSet<Dependency> {
441 if let Some(replace) = self.reverse_replacements.get(&to) {
451 if let Some(deps) = self.graph.edge(&from, replace) {
452 return deps;
453 }
454 }
455 match self.graph.edge(&from, &to) {
456 Some(ret) => ret,
457 None => panic!("no Dependency listed for `{}` => `{}`", from, to),
458 }
459 }
460
461 pub fn version(&self) -> ResolveVersion {
464 self.version
465 }
466
467 pub fn set_version(&mut self, version: ResolveVersion) {
468 self.version = version;
469 }
470
471 pub fn summary(&self, pkg_id: PackageId) -> &Summary {
472 &self.summaries[&pkg_id]
473 }
474}
475
476impl PartialEq for Resolve {
477 fn eq(&self, other: &Resolve) -> bool {
478 macro_rules! compare {
479 ($($fields:ident)* | $($ignored:ident)*) => {
480 let Resolve { $($fields,)* $($ignored: _,)* } = self;
481 $($fields == &other.$fields)&&*
482 }
483 }
484 compare! {
485 graph replacements reverse_replacements features
487 checksums metadata unused_patches public_dependencies summaries
488 |
489 version
491 }
492 }
493}
494
495impl fmt::Debug for Resolve {
496 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
497 writeln!(fmt, "graph: {:?}", self.graph)?;
498 writeln!(fmt, "\nfeatures: {{")?;
499 for (pkg, features) in &self.features {
500 writeln!(fmt, " {}: {:?}", pkg, features)?;
501 }
502 write!(fmt, "}}")
503 }
504}