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