1use std::fmt;
2use std::fmt::Write as _;
3use std::path::{Path, PathBuf};
4
5use crate::sources::IndexSummary;
6use crate::sources::path::RecursivePathSource;
7use crate::sources::source::QueryKind;
8use crate::util::edit_distance::{closest, edit_distance};
9use crate::util::errors::CargoResult;
10use crate::util::{GlobalContext, OptVersionReq, VersionExt};
11use crate::workspace::{Dependency, PackageId, Registry, Summary};
12use anyhow::Error;
13
14use super::VersionPreferences;
15use super::context::ResolverContext;
16use super::types::{ConflictMap, ConflictReason};
17
18pub struct ResolveError {
20 cause: Error,
21 package_path: Vec<PackageId>,
22}
23
24impl ResolveError {
25 pub fn new<E: Into<Error>>(cause: E, package_path: Vec<PackageId>) -> Self {
26 Self {
27 cause: cause.into(),
28 package_path,
29 }
30 }
31
32 pub fn package_path(&self) -> &[PackageId] {
35 &self.package_path
36 }
37}
38
39impl std::error::Error for ResolveError {
40 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
41 self.cause.source()
42 }
43}
44
45impl fmt::Debug for ResolveError {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 self.cause.fmt(f)
48 }
49}
50
51impl fmt::Display for ResolveError {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 self.cause.fmt(f)
54 }
55}
56
57pub type ActivateResult<T> = Result<T, ActivateError>;
58
59#[derive(Debug)]
60pub enum ActivateError {
61 Fatal(anyhow::Error),
62 Conflict(PackageId, ConflictReason),
63}
64
65impl From<::anyhow::Error> for ActivateError {
66 fn from(t: ::anyhow::Error) -> Self {
67 ActivateError::Fatal(t)
68 }
69}
70
71impl From<(PackageId, ConflictReason)> for ActivateError {
72 fn from(t: (PackageId, ConflictReason)) -> Self {
73 ActivateError::Conflict(t.0, t.1)
74 }
75}
76
77pub(super) fn activation_error(
78 resolver_ctx: &ResolverContext,
79 registry: &impl Registry,
80 version_prefs: &VersionPreferences,
81 parent: &Summary,
82 dep: &Dependency,
83 conflicting_activations: &ConflictMap,
84 candidates: &[Summary],
85 gctx: &GlobalContext,
86) -> ResolveError {
87 let to_resolve_err = |err| {
88 ResolveError::new(
89 err,
90 resolver_ctx
91 .parents
92 .path_to_bottom(&parent.package_id())
93 .into_iter()
94 .map(|(node, _)| node)
95 .cloned()
96 .collect(),
97 )
98 };
99
100 if !candidates.is_empty() {
101 let mut msg = format!("failed to select a version for `{}`.", dep.package_name());
102 msg.push_str("\n ... required by ");
103 msg.push_str(&describe_path_in_context(
104 resolver_ctx,
105 &parent.package_id(),
106 ));
107
108 msg.push_str("\nversions that meet the requirements `");
109 msg.push_str(&dep.version_req().to_string());
110 msg.push_str("` ");
111
112 if let Some(v) = dep.version_req().locked_version() {
113 msg.push_str("(locked to ");
114 msg.push_str(&v.to_string());
115 msg.push_str(") ");
116 }
117
118 msg.push_str("are: ");
119 msg.push_str(
120 &candidates
121 .iter()
122 .map(|v| v.version())
123 .map(|v| v.to_string())
124 .collect::<Vec<_>>()
125 .join(", "),
126 );
127
128 let mut conflicting_activations: Vec<_> = conflicting_activations.iter().collect();
129 conflicting_activations.sort_unstable();
130 conflicting_activations.reverse();
134 let mut has_semver = false;
136
137 for (p, r) in &conflicting_activations {
138 match r {
139 ConflictReason::Semver => {
140 has_semver = true;
141 }
142 ConflictReason::Links(link) => {
143 msg.push_str("\n\npackage `");
144 msg.push_str(&*dep.package_name());
145 msg.push_str("` links to the native library `");
146 msg.push_str(link);
147 msg.push_str("`, but it conflicts with a previous package which links to `");
148 msg.push_str(link);
149 msg.push_str("` as well:\n");
150 msg.push_str(&describe_path_in_context(resolver_ctx, p));
151 msg.push_str("\nnote: only one package in the dependency graph may specify the same links value to ensure that only one copy of a native library is linked in the final binary");
152 msg.push_str("\nfor more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links");
153 msg.push_str("\nhelp: try to adjust your dependencies so that only one package uses the `links = \"");
154 msg.push_str(link);
155 msg.push_str("\"` value");
156 }
157 ConflictReason::MissingFeature(feature) => {
158 msg.push_str("\n\npackage `");
159 msg.push_str(&*p.name());
160 msg.push_str("` depends on `");
161 msg.push_str(&*dep.package_name());
162 msg.push_str("` with feature `");
163 msg.push_str(feature);
164 msg.push_str("` but `");
165 msg.push_str(&*dep.package_name());
166 msg.push_str("` does not have that feature.\n");
167 let latest = candidates.last().expect("in the non-empty branch");
168 if let Some(closest) = closest(feature, latest.features().keys(), |k| k) {
169 msg.push_str("help: there is a feature `");
170 msg.push_str(closest);
171 msg.push_str("` with a similar name\n");
172 } else if !latest.features().is_empty() {
173 let mut features: Vec<_> =
174 latest.features().keys().map(|f| f.as_str()).collect();
175 features.sort();
176 msg.push_str("help: available features: ");
177 msg.push_str(&features.join(", "));
178 msg.push_str("\n");
179 }
180 }
182 ConflictReason::RequiredDependencyAsFeature(feature) => {
183 msg.push_str("\n\npackage `");
184 msg.push_str(&*p.name());
185 msg.push_str("` depends on `");
186 msg.push_str(&*dep.package_name());
187 msg.push_str("` with feature `");
188 msg.push_str(feature);
189 msg.push_str("` but `");
190 msg.push_str(&*dep.package_name());
191 msg.push_str("` does not have that feature.\n");
192 msg.push_str(
193 "note: a required dependency with that name exists, \
194 but only optional dependencies can be used as features.\n",
195 );
196 }
198 ConflictReason::NonImplicitDependencyAsFeature(feature) => {
199 msg.push_str("\n\npackage `");
200 msg.push_str(&*p.name());
201 msg.push_str("` depends on `");
202 msg.push_str(&*dep.package_name());
203 msg.push_str("` with feature `");
204 msg.push_str(feature);
205 msg.push_str("` but `");
206 msg.push_str(&*dep.package_name());
207 msg.push_str("` does not have that feature.\n");
208 msg.push_str(
209 "note: an optional dependency with that name exists, \
210 but that dependency uses the \"dep:\" \
211 syntax in the features table, so it does not have an \
212 implicit feature with that name.\n",
213 );
214 }
216 }
217 }
218
219 if has_semver {
220 msg.push_str("\n\nall possible versions conflict with previously selected packages");
222 for (p, r) in &conflicting_activations {
223 if let ConflictReason::Semver = r {
224 msg.push_str("\n\n previously selected ");
225 msg.push_str(&describe_path_in_context(resolver_ctx, p));
226 }
227 }
228 }
229
230 msg.push_str("\n\nfailed to select a version for `");
231 msg.push_str(&*dep.package_name());
232 msg.push_str("` which could resolve this conflict");
233
234 return to_resolve_err(anyhow::format_err!("{}", msg));
235 }
236
237 let mut msg = String::new();
240 let mut hints = String::new();
241 let mut has_too_new = false;
243 if let Some(version_candidates) = rejected_versions(registry, dep) {
244 let version_candidates = match version_candidates {
245 Ok(c) => c,
246 Err(e) => return to_resolve_err(e),
247 };
248
249 let locked_version = dep
250 .version_req()
251 .locked_version()
252 .map(|v| format!(" (locked to {})", v))
253 .unwrap_or_default();
254 let _ = writeln!(
255 &mut msg,
256 "failed to select a version for the requirement `{} = \"{}\"`{}",
257 dep.package_name(),
258 dep.version_req(),
259 locked_version
260 );
261 for candidate in version_candidates {
262 match candidate {
263 IndexSummary::Candidate(summary) => {
264 if let Some(violation) = version_prefs.too_new(&summary) {
265 has_too_new = true;
266 let note = violation.note();
267 let _ = writeln!(
268 &mut msg,
269 " version {} is too new ({note})",
270 summary.version(),
271 );
272 } else {
273 let _ =
276 writeln!(&mut msg, " version {} is unavailable", summary.version());
277 }
278 }
279 IndexSummary::Yanked(summary) => {
280 let _ = writeln!(&mut msg, " version {} is yanked", summary.version());
281 }
282 IndexSummary::Offline(summary) => {
283 let _ = writeln!(&mut msg, " version {} is not cached", summary.version());
284 }
285 IndexSummary::Unsupported(summary, schema_version) => {
286 if let Some(rust_version) = summary.rust_version() {
287 let _ = writeln!(
290 &mut msg,
291 " version {} requires cargo {}",
292 summary.version(),
293 rust_version
294 );
295 } else {
296 let _ = writeln!(
297 &mut msg,
298 " version {} requires a Cargo version that supports index version {}",
299 summary.version(),
300 schema_version
301 );
302 }
303 }
304 IndexSummary::Invalid(summary) => {
305 let _ = writeln!(
306 &mut msg,
307 " version {}'s index entry is invalid",
308 summary.version()
309 );
310 }
311 }
312 }
313 } else if let Some(candidates) = alt_versions(registry, dep) {
314 let candidates = match candidates {
315 Ok(c) => c,
316 Err(e) => return to_resolve_err(e),
317 };
318 let versions = {
319 let mut versions = candidates
320 .iter()
321 .take(3)
322 .map(|cand| cand.version().to_string())
323 .collect::<Vec<_>>();
324
325 if candidates.len() > 3 {
326 versions.push("...".into());
327 }
328
329 versions.join(", ")
330 };
331
332 let locked_version = dep
333 .version_req()
334 .locked_version()
335 .map(|v| format!(" (locked to {})", v))
336 .unwrap_or_default();
337
338 let _ = writeln!(
339 &mut msg,
340 "failed to select a version for the requirement `{} = \"{}\"`{}",
341 dep.package_name(),
342 dep.version_req(),
343 locked_version,
344 );
345 let _ = writeln!(
346 &mut msg,
347 "candidate versions found which didn't match: {versions}",
348 );
349
350 if let Some(pre) = candidates.iter().find(|c| c.version().is_prerelease()) {
352 let _ = write!(
353 &mut hints,
354 "\nhelp: if you are looking for the prerelease package it needs to be specified explicitly"
355 );
356 let _ = write!(
357 &mut hints,
358 "\n {} = {{ version = \"{}\" }}",
359 pre.name(),
360 pre.version()
361 );
362 }
363
364 if dep.source_id().is_path() && dep.version_req().is_locked() {
368 let _ = write!(
369 &mut hints,
370 "\nhelp: to update a path dependency's locked version, run `cargo update`",
371 );
372 }
373
374 if registry.is_replaced(dep.source_id()) {
375 let _ = write!(
376 &mut hints,
377 "\nnote: perhaps a crate was updated and forgotten to be re-vendored?"
378 );
379 }
380 } else if let Some(packages) = alt_paths(dep, gctx) {
381 let path = dep.source_id().url().to_file_path().unwrap();
382 let _ = writeln!(
383 &mut msg,
384 "no matching package named `{}` found",
385 dep.package_name()
386 );
387
388 let mut exact_match: Option<PathBuf> = None;
389 let mut found_dir: Option<String> = None;
390 let mut names_found: Vec<(String, PathBuf)> = vec![];
391
392 for pkg in &packages {
393 let manifest_dir = pkg.manifest_path().parent().unwrap();
394 let p_name = pkg.name().as_str();
395 if p_name == dep.package_name().as_str() {
396 exact_match = Some(manifest_dir.to_path_buf());
397 break;
398 } else if manifest_dir == path {
399 found_dir = Some(p_name.to_string());
400 } else {
401 names_found.push((p_name.to_string(), manifest_dir.to_path_buf()));
402 }
403 }
404
405 let mut add_hint = |name: &str, p: &Path| {
406 let _ = writeln!(&mut hints);
407 let _ = write!(
408 &mut hints,
409 "help: package `{}` exists at `{}`",
410 name,
411 p.display()
412 );
413 };
414
415 if let Some(dir) = exact_match {
416 add_hint(dep.package_name().as_str(), &dir);
417 } else if let Some(dir_pkg) = found_dir {
418 add_hint(&dir_pkg, &path);
419 } else {
420 names_found.sort_by(|a, b| a.0.cmp(&b.0));
421 for (name, p) in names_found.iter() {
422 add_hint(name, p);
423 }
424 }
425 } else if let Some(name_candidates) = alt_names(registry, dep) {
426 let name_candidates = match name_candidates {
427 Ok(c) => c,
428 Err(e) => return to_resolve_err(e),
429 };
430 let _ = writeln!(
431 &mut msg,
432 "no matching package named `{}` found",
433 dep.package_name()
434 );
435
436 let mut names = name_candidates
437 .iter()
438 .take(3)
439 .map(|c| c.1.name().as_str())
440 .collect::<Vec<_>>();
441 if name_candidates.len() > 3 {
442 names.push("...");
443 }
444 let suggestions =
445 names
446 .iter()
447 .enumerate()
448 .fold(String::default(), |acc, (i, el)| match i {
449 0 => acc + el,
450 i if names.len() - 1 == i && name_candidates.len() <= 3 => acc + " or " + el,
451 _ => acc + ", " + el,
452 });
453 let _ = writeln!(
454 &mut hints,
455 "\nhelp: packages with similar names: {suggestions}"
456 );
457 } else {
458 let _ = writeln!(
459 &mut msg,
460 "no matching package named `{}` found",
461 dep.package_name()
462 );
463 }
464
465 let mut location_searched_msg = registry.describe_source(dep.source_id());
466 if location_searched_msg.is_empty() {
467 location_searched_msg = format!("{}", dep.source_id());
468 }
469 let _ = writeln!(&mut msg, "location searched: {}", location_searched_msg);
470 let _ = write!(
471 &mut msg,
472 "required by {}",
473 describe_path_in_context(resolver_ctx, &parent.package_id()),
474 );
475
476 if has_too_new {
477 let downgrade_to =
478 alt_versions(registry, dep)
479 .and_then(|r| r.ok())
480 .and_then(|candidates| {
481 candidates
482 .into_iter()
483 .find(|s| version_prefs.too_new(s).is_none())
484 });
485 if let Some(summary) = downgrade_to {
486 let _ = write!(
487 &mut hints,
488 "\nhelp: to preserve the min-publish-age, \
489 downgrade the requirement to \"{}\"",
490 summary.version(),
491 );
492 }
493 let _ = write!(
494 &mut hints,
495 "\nhelp: to use too-new packages anyways, \
496 re-resolve with `CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE=allow`",
497 );
498 }
499
500 if let Some(offline_flag) = gctx.offline_flag() {
501 let _ = write!(
502 &mut hints,
503 "\nnote: offline mode (via `{offline_flag}`) \
504 can sometimes cause surprising resolution failures\
505 \nhelp: if this error is too confusing you may wish to retry \
506 without `{offline_flag}`",
507 );
508 }
509
510 to_resolve_err(anyhow::format_err!("{msg}{hints}"))
511}
512
513fn alt_versions(registry: &impl Registry, dep: &Dependency) -> Option<CargoResult<Vec<Summary>>> {
517 let mut wild_dep = dep.clone();
518 wild_dep.set_version_req(OptVersionReq::Any);
519
520 let candidates = match crate::util::block_on(registry.query_vec(&wild_dep, QueryKind::Exact)) {
521 Ok(candidates) => candidates,
522 Err(e) => return Some(Err(e)),
523 };
524 let mut candidates: Vec<_> = candidates
525 .into_iter()
526 .filter_map(|s| match s {
527 IndexSummary::Candidate(s) => Some(s),
528 _ => None,
529 })
530 .collect();
531 candidates.sort_unstable_by(|a, b| b.version().cmp(a.version()));
532 if candidates.is_empty() {
533 None
534 } else {
535 Some(Ok(candidates))
536 }
537}
538
539fn rejected_versions(
541 registry: &impl Registry,
542 dep: &Dependency,
543) -> Option<CargoResult<Vec<IndexSummary>>> {
544 let mut version_candidates =
545 match crate::util::block_on(registry.query_vec(&dep, QueryKind::RejectedVersions)) {
546 Ok(candidates) => candidates,
547 Err(e) => return Some(Err(e)),
548 };
549 version_candidates.sort_unstable_by_key(|a| a.package_id().version().clone());
550 if version_candidates.is_empty() {
551 None
552 } else {
553 Some(Ok(version_candidates))
554 }
555}
556
557fn alt_names(
560 registry: &impl Registry,
561 dep: &Dependency,
562) -> Option<CargoResult<Vec<(usize, Summary)>>> {
563 let mut wild_dep = dep.clone();
564 wild_dep.set_version_req(OptVersionReq::Any);
565
566 let name_candidates =
567 match crate::util::block_on(registry.query_vec(&wild_dep, QueryKind::AlternativeNames)) {
568 Ok(candidates) => candidates,
569 Err(e) => return Some(Err(e)),
570 };
571 let mut name_candidates: Vec<_> = name_candidates
572 .into_iter()
573 .map(|s| match s {
574 IndexSummary::Candidate(sum)
575 | IndexSummary::Yanked(sum)
576 | IndexSummary::Offline(sum)
577 | IndexSummary::Unsupported(sum, _)
578 | IndexSummary::Invalid(sum) => sum,
579 })
580 .collect();
581 name_candidates.sort_unstable_by_key(|a| a.name());
582 name_candidates.dedup_by(|a, b| a.name() == b.name());
583 let mut name_candidates: Vec<_> = name_candidates
584 .into_iter()
585 .filter_map(|n| Some((edit_distance(&*wild_dep.package_name(), &*n.name(), 3)?, n)))
586 .collect();
587 name_candidates.sort_by_key(|o| o.0);
588
589 if name_candidates.is_empty() {
590 None
591 } else {
592 Some(Ok(name_candidates))
593 }
594}
595
596fn alt_paths(dep: &Dependency, gctx: &GlobalContext) -> Option<Vec<crate::workspace::Package>> {
600 if !dep.source_id().is_path() {
601 return None;
602 }
603 let path = dep.source_id().url().to_file_path().ok()?;
604 if !path.is_dir() {
605 return None;
606 }
607 let source_id = dep.source_id();
608 let source = RecursivePathSource::new(&path, source_id, gctx);
609 let packages = source.read_packages().ok()?;
610 if packages.is_empty() {
611 None
612 } else {
613 Some(packages)
614 }
615}
616
617pub(super) fn describe_path_in_context(cx: &ResolverContext, id: &PackageId) -> String {
620 let iter = cx
621 .parents
622 .path_to_bottom(id)
623 .into_iter()
624 .map(|(p, d)| (p, d.and_then(|d| d.iter().next())));
625 describe_path(iter)
626}
627
628pub(crate) fn describe_path<'a>(
638 mut path: impl Iterator<Item = (&'a PackageId, Option<&'a Dependency>)>,
639) -> String {
640 use std::fmt::Write;
641
642 if let Some(p) = path.next() {
643 let mut dep_path_desc = format!("package `{}`", p.0);
644 for (pkg, dep) in path {
645 let dep = dep.unwrap();
646 let source_kind = if dep.source_id().is_path() {
647 "path "
648 } else if dep.source_id().is_git() {
649 "git "
650 } else {
651 ""
652 };
653 let requirement = if source_kind.is_empty() {
654 format!("{} = \"{}\"", dep.name_in_toml(), dep.version_req())
655 } else {
656 dep.name_in_toml().to_string()
657 };
658 let locked_version = dep
659 .version_req()
660 .locked_version()
661 .map(|v| format!("(locked to {}) ", v))
662 .unwrap_or_default();
663
664 write!(
665 dep_path_desc,
666 "\n ... which satisfies {}dependency `{}` {}of package `{}`",
667 source_kind, requirement, locked_version, pkg
668 )
669 .unwrap();
670 }
671
672 return dep_path_desc;
673 }
674
675 String::new()
676}