1use cargo_util_schemas::core::PartialVersion;
2use cargo_util_schemas::manifest::RustVersion;
3
4use super::encode::Metadata;
5use crate::core::dependency::DepKind;
6use crate::core::{Dependency, PackageId, PackageIdSpec, PackageIdSpecQuery, Summary, Target};
7use crate::util::errors::CargoResult;
8use crate::util::interning::InternedString;
9use crate::util::Graph;
10use std::borrow::Borrow;
11use std::collections::{HashMap, HashSet};
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: Metadata,
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: Metadata,
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 deps_not_replaced(
333 &self,
334 pkg: PackageId,
335 ) -> impl Iterator<Item = (PackageId, &HashSet<Dependency>)> {
336 self.graph.edges(&pkg).map(|(id, deps)| (*id, deps))
337 }
338
339 pub fn transitive_deps_not_replaced(
342 &self,
343 pkg: PackageId,
344 ) -> impl Iterator<Item = (PackageId, &Dependency)> {
345 self.graph.edges(&pkg).filter_map(|(id, deps)| {
346 deps.iter()
347 .find(|d| d.is_transitive())
348 .map(|transitive_dep| (*id, transitive_dep))
349 })
350 }
351
352 pub fn replacement(&self, pkg: PackageId) -> Option<PackageId> {
353 self.replacements.get(&pkg).cloned()
354 }
355
356 pub fn replacements(&self) -> &HashMap<PackageId, PackageId> {
357 &self.replacements
358 }
359
360 pub fn features(&self, pkg: PackageId) -> &[InternedString] {
361 self.features.get(&pkg).map(|v| &**v).unwrap_or(&[])
362 }
363
364 pub fn features_clone(&self) -> HashMap<PackageId, Vec<InternedString>> {
367 self.features.clone()
368 }
369
370 pub fn is_public_dep(&self, pkg: PackageId, dep: PackageId) -> bool {
371 self.public_dependencies
372 .get(&pkg)
373 .map(|public_deps| public_deps.contains(&dep))
374 .unwrap_or_else(|| panic!("Unknown dependency {:?} for package {:?}", dep, pkg))
375 }
376
377 pub fn query(&self, spec: &str) -> CargoResult<PackageId> {
378 PackageIdSpec::query_str(spec, self.iter())
379 }
380
381 pub fn specs_to_ids(&self, specs: &[PackageIdSpec]) -> CargoResult<Vec<PackageId>> {
382 specs.iter().map(|s| s.query(self.iter())).collect()
383 }
384
385 pub fn unused_patches(&self) -> &[PackageId] {
386 &self.unused_patches
387 }
388
389 pub fn checksums(&self) -> &HashMap<PackageId, Option<String>> {
390 &self.checksums
391 }
392
393 pub fn metadata(&self) -> &Metadata {
394 &self.metadata
395 }
396
397 pub fn extern_crate_name_and_dep_name(
398 &self,
399 from: PackageId,
400 to: PackageId,
401 to_target: &Target,
402 ) -> CargoResult<(InternedString, Option<InternedString>)> {
403 let empty_set: HashSet<Dependency> = HashSet::new();
404 let deps = if from == to {
405 &empty_set
406 } else {
407 self.dependencies_listed(from, to)
408 };
409
410 let target_crate_name = || (to_target.crate_name(), None);
411 let mut name_pairs = deps.iter().map(|d| {
412 d.explicit_name_in_toml()
413 .map(|s| (s.as_str().replace("-", "_"), Some(s)))
414 .unwrap_or_else(target_crate_name)
415 });
416 let (extern_crate_name, dep_name) = name_pairs.next().unwrap_or_else(target_crate_name);
417 for (n, _) in name_pairs {
418 anyhow::ensure!(
419 n == extern_crate_name,
420 "the crate `{}` depends on crate `{}` multiple times with different names",
421 from,
422 to,
423 );
424 }
425 Ok((extern_crate_name.into(), dep_name))
426 }
427
428 fn dependencies_listed(&self, from: PackageId, to: PackageId) -> &HashSet<Dependency> {
429 if let Some(replace) = self.reverse_replacements.get(&to) {
439 if let Some(deps) = self.graph.edge(&from, replace) {
440 return deps;
441 }
442 }
443 match self.graph.edge(&from, &to) {
444 Some(ret) => ret,
445 None => panic!("no Dependency listed for `{}` => `{}`", from, to),
446 }
447 }
448
449 pub fn version(&self) -> ResolveVersion {
452 self.version
453 }
454
455 pub fn set_version(&mut self, version: ResolveVersion) {
456 self.version = version;
457 }
458
459 pub fn summary(&self, pkg_id: PackageId) -> &Summary {
460 &self.summaries[&pkg_id]
461 }
462}
463
464impl PartialEq for Resolve {
465 fn eq(&self, other: &Resolve) -> bool {
466 macro_rules! compare {
467 ($($fields:ident)* | $($ignored:ident)*) => {
468 let Resolve { $($fields,)* $($ignored: _,)* } = self;
469 $($fields == &other.$fields)&&*
470 }
471 }
472 compare! {
473 graph replacements reverse_replacements features
475 checksums metadata unused_patches public_dependencies summaries
476 |
477 version
479 }
480 }
481}
482
483impl fmt::Debug for Resolve {
484 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
485 writeln!(fmt, "graph: {:?}", self.graph)?;
486 writeln!(fmt, "\nfeatures: {{")?;
487 for (pkg, features) in &self.features {
488 writeln!(fmt, " {}: {:?}", pkg, features)?;
489 }
490 write!(fmt, "}}")
491 }
492}