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