|
| 1 | +use std::collections::HashMap; |
| 2 | +use std::fmt; |
| 3 | + |
| 4 | +use core::{Dependency, PackageId, Registry, Summary}; |
| 5 | +use failure::{Error, Fail}; |
| 6 | +use semver; |
| 7 | +use util::config::Config; |
| 8 | +use util::lev_distance::lev_distance; |
| 9 | + |
| 10 | +use super::context::Context; |
| 11 | +use super::types::{Candidate, ConflictReason}; |
| 12 | + |
| 13 | +/// Error during resolution providing a path of `PackageId`s. |
| 14 | +pub struct ResolveError { |
| 15 | + cause: Error, |
| 16 | + package_path: Vec<PackageId>, |
| 17 | +} |
| 18 | + |
| 19 | +impl ResolveError { |
| 20 | + pub fn new<E: Into<Error>>(cause: E, package_path: Vec<PackageId>) -> Self { |
| 21 | + Self { |
| 22 | + cause: cause.into(), |
| 23 | + package_path, |
| 24 | + } |
| 25 | + } |
| 26 | + |
| 27 | + /// Returns a path of packages from the package whose requirements could not be resolved up to |
| 28 | + /// the root. |
| 29 | + pub fn package_path(&self) -> &[PackageId] { |
| 30 | + &self.package_path |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +impl Fail for ResolveError { |
| 35 | + fn cause(&self) -> Option<&Fail> { |
| 36 | + self.cause.as_fail().cause() |
| 37 | + } |
| 38 | +} |
| 39 | + |
| 40 | +impl fmt::Debug for ResolveError { |
| 41 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 42 | + self.cause.fmt(f) |
| 43 | + } |
| 44 | +} |
| 45 | + |
| 46 | +impl fmt::Display for ResolveError { |
| 47 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 48 | + self.cause.fmt(f) |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +pub(super) fn activation_error( |
| 53 | + cx: &Context, |
| 54 | + registry: &mut Registry, |
| 55 | + parent: &Summary, |
| 56 | + dep: &Dependency, |
| 57 | + conflicting_activations: &HashMap<PackageId, ConflictReason>, |
| 58 | + candidates: &[Candidate], |
| 59 | + config: Option<&Config>, |
| 60 | +) -> ResolveError { |
| 61 | + let graph = cx.graph(); |
| 62 | + let to_resolve_err = |err| { |
| 63 | + ResolveError::new( |
| 64 | + err, |
| 65 | + graph |
| 66 | + .path_to_top(parent.package_id()) |
| 67 | + .into_iter() |
| 68 | + .cloned() |
| 69 | + .collect(), |
| 70 | + ) |
| 71 | + }; |
| 72 | + |
| 73 | + if !candidates.is_empty() { |
| 74 | + let mut msg = format!("failed to select a version for `{}`.", dep.package_name()); |
| 75 | + msg.push_str("\n ... required by "); |
| 76 | + msg.push_str(&describe_path(&graph.path_to_top(parent.package_id()))); |
| 77 | + |
| 78 | + msg.push_str("\nversions that meet the requirements `"); |
| 79 | + msg.push_str(&dep.version_req().to_string()); |
| 80 | + msg.push_str("` are: "); |
| 81 | + msg.push_str( |
| 82 | + &candidates |
| 83 | + .iter() |
| 84 | + .map(|v| v.summary.version()) |
| 85 | + .map(|v| v.to_string()) |
| 86 | + .collect::<Vec<_>>() |
| 87 | + .join(", "), |
| 88 | + ); |
| 89 | + |
| 90 | + let mut conflicting_activations: Vec<_> = conflicting_activations.iter().collect(); |
| 91 | + conflicting_activations.sort_unstable(); |
| 92 | + let (links_errors, mut other_errors): (Vec<_>, Vec<_>) = conflicting_activations |
| 93 | + .drain(..) |
| 94 | + .rev() |
| 95 | + .partition(|&(_, r)| r.is_links()); |
| 96 | + |
| 97 | + for &(p, r) in links_errors.iter() { |
| 98 | + if let ConflictReason::Links(ref link) = *r { |
| 99 | + msg.push_str("\n\nthe package `"); |
| 100 | + msg.push_str(&*dep.package_name()); |
| 101 | + msg.push_str("` links to the native library `"); |
| 102 | + msg.push_str(link); |
| 103 | + msg.push_str("`, but it conflicts with a previous package which links to `"); |
| 104 | + msg.push_str(link); |
| 105 | + msg.push_str("` as well:\n"); |
| 106 | + } |
| 107 | + msg.push_str(&describe_path(&graph.path_to_top(p))); |
| 108 | + } |
| 109 | + |
| 110 | + let (features_errors, other_errors): (Vec<_>, Vec<_>) = other_errors |
| 111 | + .drain(..) |
| 112 | + .partition(|&(_, r)| r.is_missing_features()); |
| 113 | + |
| 114 | + for &(p, r) in features_errors.iter() { |
| 115 | + if let ConflictReason::MissingFeatures(ref features) = *r { |
| 116 | + msg.push_str("\n\nthe package `"); |
| 117 | + msg.push_str(&*p.name()); |
| 118 | + msg.push_str("` depends on `"); |
| 119 | + msg.push_str(&*dep.package_name()); |
| 120 | + msg.push_str("`, with features: `"); |
| 121 | + msg.push_str(features); |
| 122 | + msg.push_str("` but `"); |
| 123 | + msg.push_str(&*dep.package_name()); |
| 124 | + msg.push_str("` does not have these features.\n"); |
| 125 | + } |
| 126 | + // p == parent so the full path is redundant. |
| 127 | + } |
| 128 | + |
| 129 | + if !other_errors.is_empty() { |
| 130 | + msg.push_str( |
| 131 | + "\n\nall possible versions conflict with \ |
| 132 | + previously selected packages.", |
| 133 | + ); |
| 134 | + } |
| 135 | + |
| 136 | + for &(p, _) in other_errors.iter() { |
| 137 | + msg.push_str("\n\n previously selected "); |
| 138 | + msg.push_str(&describe_path(&graph.path_to_top(p))); |
| 139 | + } |
| 140 | + |
| 141 | + msg.push_str("\n\nfailed to select a version for `"); |
| 142 | + msg.push_str(&*dep.package_name()); |
| 143 | + msg.push_str("` which could resolve this conflict"); |
| 144 | + |
| 145 | + return to_resolve_err(format_err!("{}", msg)); |
| 146 | + } |
| 147 | + |
| 148 | + // We didn't actually find any candidates, so we need to |
| 149 | + // give an error message that nothing was found. |
| 150 | + // |
| 151 | + // Maybe the user mistyped the ver_req? Like `dep="2"` when `dep="0.2"` |
| 152 | + // was meant. So we re-query the registry with `deb="*"` so we can |
| 153 | + // list a few versions that were actually found. |
| 154 | + let all_req = semver::VersionReq::parse("*").unwrap(); |
| 155 | + let mut new_dep = dep.clone(); |
| 156 | + new_dep.set_version_req(all_req); |
| 157 | + let mut candidates = match registry.query_vec(&new_dep, false) { |
| 158 | + Ok(candidates) => candidates, |
| 159 | + Err(e) => return to_resolve_err(e), |
| 160 | + }; |
| 161 | + candidates.sort_unstable_by(|a, b| b.version().cmp(a.version())); |
| 162 | + |
| 163 | + let mut msg = if !candidates.is_empty() { |
| 164 | + let versions = { |
| 165 | + let mut versions = candidates |
| 166 | + .iter() |
| 167 | + .take(3) |
| 168 | + .map(|cand| cand.version().to_string()) |
| 169 | + .collect::<Vec<_>>(); |
| 170 | + |
| 171 | + if candidates.len() > 3 { |
| 172 | + versions.push("...".into()); |
| 173 | + } |
| 174 | + |
| 175 | + versions.join(", ") |
| 176 | + }; |
| 177 | + |
| 178 | + let mut msg = format!( |
| 179 | + "failed to select a version for the requirement `{} = \"{}\"`\n \ |
| 180 | + candidate versions found which didn't match: {}\n \ |
| 181 | + location searched: {}\n", |
| 182 | + dep.package_name(), |
| 183 | + dep.version_req(), |
| 184 | + versions, |
| 185 | + registry.describe_source(dep.source_id()), |
| 186 | + ); |
| 187 | + msg.push_str("required by "); |
| 188 | + msg.push_str(&describe_path(&graph.path_to_top(parent.package_id()))); |
| 189 | + |
| 190 | + // If we have a path dependency with a locked version, then this may |
| 191 | + // indicate that we updated a sub-package and forgot to run `cargo |
| 192 | + // update`. In this case try to print a helpful error! |
| 193 | + if dep.source_id().is_path() && dep.version_req().to_string().starts_with('=') { |
| 194 | + msg.push_str( |
| 195 | + "\nconsider running `cargo update` to update \ |
| 196 | + a path dependency's locked version", |
| 197 | + ); |
| 198 | + } |
| 199 | + |
| 200 | + if registry.is_replaced(dep.source_id()) { |
| 201 | + msg.push_str("\nperhaps a crate was updated and forgotten to be re-vendored?"); |
| 202 | + } |
| 203 | + |
| 204 | + msg |
| 205 | + } else { |
| 206 | + // Maybe the user mistyped the name? Like `dep-thing` when `Dep_Thing` |
| 207 | + // was meant. So we try asking the registry for a `fuzzy` search for suggestions. |
| 208 | + let mut candidates = Vec::new(); |
| 209 | + if let Err(e) = registry.query(&new_dep, &mut |s| candidates.push(s.name()), true) { |
| 210 | + return to_resolve_err(e); |
| 211 | + }; |
| 212 | + candidates.sort_unstable(); |
| 213 | + candidates.dedup(); |
| 214 | + let mut candidates: Vec<_> = candidates |
| 215 | + .iter() |
| 216 | + .map(|n| (lev_distance(&*new_dep.package_name(), &*n), n)) |
| 217 | + .filter(|&(d, _)| d < 4) |
| 218 | + .collect(); |
| 219 | + candidates.sort_by_key(|o| o.0); |
| 220 | + let mut msg = format!( |
| 221 | + "no matching package named `{}` found\n\ |
| 222 | + location searched: {}\n", |
| 223 | + dep.package_name(), |
| 224 | + dep.source_id() |
| 225 | + ); |
| 226 | + if !candidates.is_empty() { |
| 227 | + let mut names = candidates |
| 228 | + .iter() |
| 229 | + .take(3) |
| 230 | + .map(|c| c.1.as_str()) |
| 231 | + .collect::<Vec<_>>(); |
| 232 | + |
| 233 | + if candidates.len() > 3 { |
| 234 | + names.push("..."); |
| 235 | + } |
| 236 | + |
| 237 | + msg.push_str("did you mean: "); |
| 238 | + msg.push_str(&names.join(", ")); |
| 239 | + msg.push_str("\n"); |
| 240 | + } |
| 241 | + msg.push_str("required by "); |
| 242 | + msg.push_str(&describe_path(&graph.path_to_top(parent.package_id()))); |
| 243 | + |
| 244 | + msg |
| 245 | + }; |
| 246 | + |
| 247 | + if let Some(config) = config { |
| 248 | + if config.cli_unstable().offline { |
| 249 | + msg.push_str( |
| 250 | + "\nAs a reminder, you're using offline mode (-Z offline) \ |
| 251 | + which can sometimes cause surprising resolution failures, \ |
| 252 | + if this error is too confusing you may with to retry \ |
| 253 | + without the offline flag.", |
| 254 | + ); |
| 255 | + } |
| 256 | + } |
| 257 | + |
| 258 | + to_resolve_err(format_err!("{}", msg)) |
| 259 | +} |
| 260 | + |
| 261 | +/// Returns String representation of dependency chain for a particular `pkgid`. |
| 262 | +pub(super) fn describe_path(path: &[&PackageId]) -> String { |
| 263 | + use std::fmt::Write; |
| 264 | + let mut dep_path_desc = format!("package `{}`", path[0]); |
| 265 | + for dep in path[1..].iter() { |
| 266 | + write!(dep_path_desc, "\n ... which is depended on by `{}`", dep).unwrap(); |
| 267 | + } |
| 268 | + dep_path_desc |
| 269 | +} |
0 commit comments