From 834846901d651e8b82296bc2f470dd8596a1b01a Mon Sep 17 00:00:00 2001 From: Eh2406 Date: Tue, 27 Feb 2018 10:08:19 -0500 Subject: [PATCH 1/5] add a test for cargo/issues/4347 --- tests/testsuite/resolve.rs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/tests/testsuite/resolve.rs b/tests/testsuite/resolve.rs index 55d683feefa..c43277f24f1 100644 --- a/tests/testsuite/resolve.rs +++ b/tests/testsuite/resolve.rs @@ -58,20 +58,20 @@ trait ToPkgId { fn to_pkgid(&self) -> PackageId; } -impl ToPkgId for &'static str { +impl<'a> ToPkgId for &'a str { fn to_pkgid(&self) -> PackageId { PackageId::new(*self, "1.0.0", ®istry_loc()).unwrap() } } -impl ToPkgId for (&'static str, &'static str) { +impl<'a> ToPkgId for (&'a str, &'a str) { fn to_pkgid(&self) -> PackageId { let (name, vers) = *self; PackageId::new(name, vers, ®istry_loc()).unwrap() } } -impl ToPkgId for (&'static str, String) { +impl<'a> ToPkgId for (&'a str, String) { fn to_pkgid(&self) -> PackageId { let (name, ref vers) = *self; PackageId::new(name, vers, ®istry_loc()).unwrap() @@ -316,6 +316,27 @@ fn resolving_backtrack() { ("baz", "1.0.0")]))); } +#[test] +fn resolving_backtrack_features() { + // test for cargo/issues/4347 + let mut bad = dep("bar"); + bad.set_features(vec!["bad".to_string()]); + + let reg = registry(vec![ + pkg!(("foo", "1.0.2") => [bad]), + pkg!(("foo", "1.0.1") => [dep("bar")]), + pkg!("bar"), + ]); + + let res = resolve(&pkg_id("root"), vec![ + dep_req("foo", "^1"), + ], ®).unwrap(); + + assert_that(&res, contains(names(&[("root", "1.0.0"), + ("foo", "1.0.1"), + ("bar", "1.0.0")]))); +} + #[test] fn resolving_allows_multiple_compatible_versions() { let reg = registry(vec![ From 3300728a75569fe905847c59dae21408c1e9ddc5 Mon Sep 17 00:00:00 2001 From: Eh2406 Date: Tue, 27 Feb 2018 11:08:11 -0500 Subject: [PATCH 2/5] WIP: make it recurs if activation fails --- src/cargo/core/resolver/mod.rs | 140 ++++++++++++++++++--------------- 1 file changed, 75 insertions(+), 65 deletions(-) diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index a57695eb1f9..bd1b69ab83f 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -710,74 +710,84 @@ fn activate_deps_loop<'a>( trace!("{}[{}]>{} {} candidates", parent.name(), cur, dep.name(), candidates.len()); trace!("{}[{}]>{} {} prev activations", parent.name(), cur, dep.name(), cx.prev_active(&dep).len()); - let mut remaining_candidates = RemainingCandidates::new(&candidates); - let next = remaining_candidates.next(cx.prev_active(&dep), &cx.links); - // Alright, for each candidate that's gotten this far, it meets the - // following requirements: - // - // 1. The version matches the dependency requirement listed for this - // package - // 2. There are no activated versions for this package which are - // semver/links-compatible, or there's an activated version which is - // precisely equal to `candidate`. - // - // This means that we're going to attempt to activate each candidate in - // turn. We could possibly fail to activate each candidate, so we try - // each one in turn. - let (candidate, has_another) = next.or_else(|mut conflicting| { - // This dependency has no valid candidate. Backtrack until we - // find a dependency that does have a candidate to try, and try - // to activate that one. This resets the `remaining_deps` to - // their state at the found level of the `backtrack_stack`. - trace!("{}[{}]>{} -- no candidates", parent.name(), cur, dep.name()); - find_candidate( - &mut backtrack_stack, - &mut cx, - &mut remaining_deps, - &mut parent, - &mut cur, - &mut dep, - &mut features, - &mut remaining_candidates, - &mut conflicting, - ).ok_or_else(|| { - activation_error( - &cx, - registry, - &parent, - &dep, - &conflicting, - &candidates, - config, - ) - }) - })?; + let mut remaining_candidates = RemainingCandidates::new(&candidates); + let mut successfully_activated = false; + + while !successfully_activated { + let next = remaining_candidates.next(cx.prev_active(&dep), &cx.links); + + // Alright, for each candidate that's gotten this far, it meets the + // following requirements: + // + // 1. The version matches the dependency requirement listed for this + // package + // 2. There are no activated versions for this package which are + // semver/links-compatible, or there's an activated version which is + // precisely equal to `candidate`. + // + // This means that we're going to attempt to activate each candidate in + // turn. We could possibly fail to activate each candidate, so we try + // each one in turn. + let (candidate, has_another) = next.or_else(|mut conflicting| { + // This dependency has no valid candidate. Backtrack until we + // find a dependency that does have a candidate to try, and try + // to activate that one. This resets the `remaining_deps` to + // their state at the found level of the `backtrack_stack`. + trace!("{}[{}]>{} -- no candidates", parent.name(), cur, dep.name()); + find_candidate( + &mut backtrack_stack, + &mut cx, + &mut remaining_deps, + &mut parent, + &mut cur, + &mut dep, + &mut features, + &mut remaining_candidates, + &mut conflicting, + ).ok_or_else(|| { + activation_error( + &cx, + registry, + &parent, + &dep, + &conflicting, + &candidates, + config, + ) + }) + })?; - // We have a candidate. Add an entry to the `backtrack_stack` so - // we can try the next one if this one fails. - if has_another { - backtrack_stack.push(BacktrackFrame { - cur, - context_backup: Context::clone(&cx), - deps_backup: >::clone(&remaining_deps), - remaining_candidates, - parent: Summary::clone(&parent), - dep: Dependency::clone(&dep), - features: Rc::clone(&features), - }); - } + // We have a candidate. Add an entry to the `backtrack_stack` so + // we can try the next one if this one fails. + if has_another { + // TODO: when to push this vs. activation error + backtrack_stack.push(BacktrackFrame { + cur, + context_backup: Context::clone(&cx), + deps_backup: >::clone(&remaining_deps), + remaining_candidates: remaining_candidates.clone(), + parent: Summary::clone(&parent), + dep: Dependency::clone(&dep), + features: Rc::clone(&features), + }); + } - let method = Method::Required { - dev_deps: false, - features: &features, - uses_default_features: dep.uses_default_features(), - }; - trace!("{}[{}]>{} trying {}", parent.name(), cur, dep.name(), candidate.summary.version()); - let res = activate(&mut cx, registry, Some(&parent), candidate, &method)?; - if let Some((frame, dur)) = res { - remaining_deps.push(frame); - deps_time += dur; + let method = Method::Required { + dev_deps: false, + features: &features, + uses_default_features: dep.uses_default_features(), + }; + trace!("{}[{}]>{} trying {}", parent.name(), cur, dep.name(), candidate.summary.version()); + let res = activate(&mut cx, registry, Some(&parent), candidate, &method); + successfully_activated = res.is_ok(); + // TODO: disable fast-backtracking + // TODO: save that disable status in backtrack frame + // TODO: integrate with error messages + if let Ok(Some((frame, dur))) = res { + remaining_deps.push(frame); + deps_time += dur; + } } } From 5d4402ce17e5d612c0f5cfe31f808065fe9e0ba4 Mon Sep 17 00:00:00 2001 From: Eh2406 Date: Tue, 27 Feb 2018 16:42:39 -0500 Subject: [PATCH 3/5] fix the todo's needs a test where we have an activation_error the then try activate something that dose not work and backtrack to where we had the activation_error then: - Hit fast backtracking that go past the crate with the missing features - Or give a bad error message that does not mention the activation_error. The test will pass, but there is code that is not yet justified by tests --- src/cargo/core/resolver/mod.rs | 105 +++++++++++++++++++++++++-------- tests/testsuite/features.rs | 19 ++++-- 2 files changed, 94 insertions(+), 30 deletions(-) diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index bd1b69ab83f..3859005b26a 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -414,7 +414,7 @@ fn activate(cx: &mut Context, parent: Option<&Summary>, candidate: Candidate, method: &Method) - -> CargoResult> { + -> ActivateResult> { if let Some(parent) = parent { cx.resolve_graph.push(GraphNode::Link(parent.package_id().clone(), candidate.summary.package_id().clone())); @@ -536,14 +536,40 @@ impl Ord for DepsFrame { enum ConflictReason { Semver, Links(String), + MissingFeatures(String), +} + +enum ActivateError { + Error(::failure::Error), + Conflict(PackageId, ConflictReason), +} +type ActivateResult = Result; + +impl From<::failure::Error> for ActivateError { + fn from(t: ::failure::Error) -> Self { + ActivateError::Error(t) + } +} + +impl From<(PackageId, ConflictReason)> for ActivateError { + fn from(t: (PackageId, ConflictReason)) -> Self { + ActivateError::Conflict(t.0, t.1) + } } impl ConflictReason { fn is_links(&self) -> bool { - match *self { - ConflictReason::Semver => false, - ConflictReason::Links(_) => true, + if let ConflictReason::Links(_) = *self { + return true; + } + false + } + + fn is_missing_features(&self) -> bool { + if let ConflictReason::MissingFeatures(_) = *self { + return true; } + false } } @@ -556,6 +582,7 @@ struct BacktrackFrame<'a> { parent: Summary, dep: Dependency, features: Rc>, + conflicting_activations: HashMap, } #[derive(Clone)] @@ -652,9 +679,17 @@ fn activate_deps_loop<'a>( summary: summary.clone(), replace: None, }; - let res = activate(&mut cx, registry, None, candidate, method)?; - if let Some((frame, _)) = res { - remaining_deps.push(frame); + let res = activate(&mut cx, registry, None, candidate, method); + match res { + Ok(Some((frame, _))) => remaining_deps.push(frame), + Ok(None) => (), + Err(ActivateError::Error(e)) => return Err(e), + Err(ActivateError::Conflict(id, reason)) => { + match reason { + ConflictReason::MissingFeatures(features) => bail!("Package `{}` does not have these features: `{}`", id, features), + _ => panic!("bad error from activate"), + } + } } } @@ -713,6 +748,7 @@ fn activate_deps_loop<'a>( let mut remaining_candidates = RemainingCandidates::new(&candidates); let mut successfully_activated = false; + let mut conflicting_activations = HashMap::new(); while !successfully_activated { let next = remaining_candidates.next(cx.prev_active(&dep), &cx.links); @@ -729,7 +765,8 @@ fn activate_deps_loop<'a>( // This means that we're going to attempt to activate each candidate in // turn. We could possibly fail to activate each candidate, so we try // each one in turn. - let (candidate, has_another) = next.or_else(|mut conflicting| { + let (candidate, has_another) = next.or_else(|conflicting| { + conflicting_activations.extend(conflicting); // This dependency has no valid candidate. Backtrack until we // find a dependency that does have a candidate to try, and try // to activate that one. This resets the `remaining_deps` to @@ -744,14 +781,16 @@ fn activate_deps_loop<'a>( &mut dep, &mut features, &mut remaining_candidates, - &mut conflicting, + &mut conflicting_activations, ).ok_or_else(|| { + // if we hit an activation error and we are out of other combinations + // then just report that error activation_error( &cx, registry, &parent, &dep, - &conflicting, + &conflicting_activations, &candidates, config, ) @@ -770,6 +809,7 @@ fn activate_deps_loop<'a>( parent: Summary::clone(&parent), dep: Dependency::clone(&dep), features: Rc::clone(&features), + conflicting_activations: conflicting_activations.clone(), }); } @@ -781,12 +821,14 @@ fn activate_deps_loop<'a>( trace!("{}[{}]>{} trying {}", parent.name(), cur, dep.name(), candidate.summary.version()); let res = activate(&mut cx, registry, Some(&parent), candidate, &method); successfully_activated = res.is_ok(); - // TODO: disable fast-backtracking - // TODO: save that disable status in backtrack frame - // TODO: integrate with error messages - if let Ok(Some((frame, dur))) = res { - remaining_deps.push(frame); - deps_time += dur; + match res { + Ok(Some((frame, dur))) => { + remaining_deps.push(frame); + deps_time += dur; + } + Ok(None) => (), + Err(ActivateError::Error(e)) => return Err(e), + Err(ActivateError::Conflict(id, reason)) => { conflicting_activations.insert(id, reason); }, } } } @@ -834,6 +876,7 @@ fn find_candidate<'a>( *dep = frame.dep; *features = frame.features; *remaining_candidates = frame.remaining_candidates; + *conflicting_activations = frame.conflicting_activations; return Some((candidate, has_another)); } } @@ -875,9 +918,9 @@ fn activation_error(cx: &Context, let mut conflicting_activations: Vec<_> = conflicting_activations.iter().collect(); conflicting_activations.sort_unstable(); - let (links_errors, other_errors): (Vec<_>, Vec<_>) = conflicting_activations.drain(..).rev().partition(|&(_, r)| r.is_links()); + let (links_errors, mut other_errors): (Vec<_>, Vec<_>) = conflicting_activations.drain(..).rev().partition(|&(_, r)| r.is_links()); - for &(p, r) in &links_errors { + for &(p, r) in links_errors.iter() { if let ConflictReason::Links(ref link) = *r { msg.push_str("\n\nthe package `"); msg.push_str(dep.name()); @@ -890,12 +933,27 @@ fn activation_error(cx: &Context, msg.push_str(&describe_path(p)); } + let (features_errors, other_errors): (Vec<_>, Vec<_>) = other_errors.drain(..).partition(|&(_, r)| r.is_missing_features()); + + for &(p, r) in features_errors.iter() { + if let ConflictReason::MissingFeatures(ref features) = *r { + msg.push_str("\n\nthe package `"); + msg.push_str(dep.name()); + msg.push_str("` depends on `"); + msg.push_str(p.name()); + msg.push_str("`, with features: `"); + msg.push_str(features); + msg.push_str("` but it does not have these features.\n"); + } + msg.push_str(&describe_path(p)); + } + if links_errors.is_empty() { msg.push_str("\n\nall possible versions conflict with \ previously selected packages."); } - for &(p, _) in &other_errors { + for &(p, _) in other_errors.iter() { msg.push_str("\n\n previously selected "); msg.push_str(&describe_path(p)); } @@ -1158,7 +1216,7 @@ impl<'a> Context<'a> { fn build_deps(&mut self, registry: &mut Registry, candidate: &Summary, - method: &Method) -> CargoResult> { + method: &Method) -> ActivateResult> { // First, figure out our set of dependencies based on the requested set // of features. This also calculates what features we're going to enable // for our own dependencies. @@ -1275,7 +1333,7 @@ impl<'a> Context<'a> { fn resolve_features<'b>(&mut self, s: &'b Summary, method: &'b Method) - -> CargoResult)>> { + -> ActivateResult)>> { let dev_deps = match *method { Method::Everything => true, Method::Required { dev_deps, .. } => dev_deps, @@ -1310,7 +1368,7 @@ impl<'a> Context<'a> { base.extend(dep.features().iter().cloned()); for feature in base.iter() { if feature.contains('/') { - bail!("feature names may not contain slashes: `{}`", feature); + return Err(format_err!("feature names may not contain slashes: `{}`", feature).into()); } } ret.push((dep.clone(), base)); @@ -1324,8 +1382,7 @@ impl<'a> Context<'a> { .map(|s| &s[..]) .collect::>(); let features = unknown.join(", "); - bail!("Package `{}` does not have these features: `{}`", - s.package_id(), features) + return Err((s.package_id().clone(), ConflictReason::MissingFeatures(features)))?; } // Record what list of features is active for this package. diff --git a/tests/testsuite/features.rs b/tests/testsuite/features.rs index 4654557237e..ca80513f7c8 100644 --- a/tests/testsuite/features.rs +++ b/tests/testsuite/features.rs @@ -109,8 +109,17 @@ fn invalid4() { assert_that(p.cargo("build"), execs().with_status(101).with_stderr("\ -[ERROR] Package `bar v0.0.1 ([..])` does not have these features: `bar` -")); +error: failed to select a version for `bar`. + ... required by package `foo v0.0.1 ([..])` +versions that meet the requirements `*` are: 0.0.1 + +the package `bar` depends on `bar`, with features: `bar` but it does not have these features. +package `bar v0.0.1 ([..])` + ... which is depended on by `foo v0.0.1 ([..])` + +all possible versions conflict with previously selected packages. + +failed to select a version for `bar` which could resolve this conflict")); p.change_file("Cargo.toml", r#" [project] @@ -121,8 +130,7 @@ fn invalid4() { assert_that(p.cargo("build").arg("--features").arg("test"), execs().with_status(101).with_stderr("\ -[ERROR] Package `foo v0.0.1 ([..])` does not have these features: `test` -")); +error: Package `foo v0.0.1 ([..])` does not have these features: `test`")); } #[test] @@ -1052,8 +1060,7 @@ fn dep_feature_in_cmd_line() { // Trying to enable features of transitive dependencies is an error assert_that(p.cargo("build").arg("--features").arg("bar/some-feat"), execs().with_status(101).with_stderr("\ -[ERROR] Package `foo v0.0.1 ([..])` does not have these features: `bar` -")); +error: Package `foo v0.0.1 ([..])` does not have these features: `bar`")); // Hierarchical feature specification should still be disallowed assert_that(p.cargo("build").arg("--features").arg("derived/bar/some-feat"), From 6218584db8e56f612c5bf2fb91a523806b805f42 Mon Sep 17 00:00:00 2001 From: Eh2406 Date: Thu, 1 Mar 2018 11:25:46 -0500 Subject: [PATCH 4/5] One more todo to fix --- src/cargo/core/resolver/mod.rs | 35 +++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index 3859005b26a..26c4d0a37db 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -797,21 +797,19 @@ fn activate_deps_loop<'a>( }) })?; - // We have a candidate. Add an entry to the `backtrack_stack` so - // we can try the next one if this one fails. - if has_another { - // TODO: when to push this vs. activation error - backtrack_stack.push(BacktrackFrame { - cur, - context_backup: Context::clone(&cx), - deps_backup: >::clone(&remaining_deps), - remaining_candidates: remaining_candidates.clone(), - parent: Summary::clone(&parent), - dep: Dependency::clone(&dep), - features: Rc::clone(&features), - conflicting_activations: conflicting_activations.clone(), - }); - } + // We have a candidate. Clone a `BacktrackFrame` + // so we can add it to the `backtrack_stack` if activation succeeds. + // We clone now in case `activate` changes `cx` and then fails. + let backtrack = BacktrackFrame { + cur, + context_backup: Context::clone(&cx), + deps_backup: >::clone(&remaining_deps), + remaining_candidates: remaining_candidates.clone(), + parent: Summary::clone(&parent), + dep: Dependency::clone(&dep), + features: Rc::clone(&features), + conflicting_activations: conflicting_activations.clone(), + }; let method = Method::Required { dev_deps: false, @@ -821,6 +819,7 @@ fn activate_deps_loop<'a>( trace!("{}[{}]>{} trying {}", parent.name(), cur, dep.name(), candidate.summary.version()); let res = activate(&mut cx, registry, Some(&parent), candidate, &method); successfully_activated = res.is_ok(); + match res { Ok(Some((frame, dur))) => { remaining_deps.push(frame); @@ -830,6 +829,12 @@ fn activate_deps_loop<'a>( Err(ActivateError::Error(e)) => return Err(e), Err(ActivateError::Conflict(id, reason)) => { conflicting_activations.insert(id, reason); }, } + + // Add an entry to the `backtrack_stack` so + // we can try the next one if this one fails. + if has_another && successfully_activated { + backtrack_stack.push(backtrack); + } } } From 2cbd1ddc93024abc752355a8b9efcf4f92aaccf7 Mon Sep 17 00:00:00 2001 From: Eh2406 Date: Thu, 1 Mar 2018 13:12:27 -0500 Subject: [PATCH 5/5] fix cause and the error messages --- src/cargo/core/resolver/mod.rs | 37 ++++++++++++++++++---------------- tests/testsuite/features.rs | 5 +---- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index 26c4d0a37db..8db8159cad9 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -443,7 +443,7 @@ fn activate(cx: &mut Context, }; let now = Instant::now(); - let deps = cx.build_deps(registry, &candidate, method)?; + let deps = cx.build_deps(registry, parent, &candidate, method)?; let frame = DepsFrame { parent: candidate, remaining_siblings: RcVecIter::new(Rc::new(deps)), @@ -684,12 +684,7 @@ fn activate_deps_loop<'a>( Ok(Some((frame, _))) => remaining_deps.push(frame), Ok(None) => (), Err(ActivateError::Error(e)) => return Err(e), - Err(ActivateError::Conflict(id, reason)) => { - match reason { - ConflictReason::MissingFeatures(features) => bail!("Package `{}` does not have these features: `{}`", id, features), - _ => panic!("bad error from activate"), - } - } + Err(ActivateError::Conflict(_, _)) => panic!("bad error from activate") } } @@ -943,17 +938,19 @@ fn activation_error(cx: &Context, for &(p, r) in features_errors.iter() { if let ConflictReason::MissingFeatures(ref features) = *r { msg.push_str("\n\nthe package `"); - msg.push_str(dep.name()); - msg.push_str("` depends on `"); msg.push_str(p.name()); + msg.push_str("` depends on `"); + msg.push_str(dep.name()); msg.push_str("`, with features: `"); msg.push_str(features); - msg.push_str("` but it does not have these features.\n"); + msg.push_str("` but `"); + msg.push_str(dep.name()); + msg.push_str("` does not have these features.\n"); } - msg.push_str(&describe_path(p)); + // p == parent so the full path is redundant. } - if links_errors.is_empty() { + if !other_errors.is_empty() { msg.push_str("\n\nall possible versions conflict with \ previously selected packages."); } @@ -1143,9 +1140,9 @@ impl<'r> Requirements<'r> { } } -// Takes requested features for a single package from the input Method and -// recurses to find all requested features, dependencies and requested -// dependency features in a Requirements object, returning it to the resolver. +/// Takes requested features for a single package from the input Method and +/// recurses to find all requested features, dependencies and requested +/// dependency features in a Requirements object, returning it to the resolver. fn build_requirements<'a, 'b: 'a>(s: &'a Summary, method: &'b Method) -> CargoResult> { let mut reqs = Requirements::new(s); @@ -1220,12 +1217,13 @@ impl<'a> Context<'a> { fn build_deps(&mut self, registry: &mut Registry, + parent: Option<&Summary>, candidate: &Summary, method: &Method) -> ActivateResult> { // First, figure out our set of dependencies based on the requested set // of features. This also calculates what features we're going to enable // for our own dependencies. - let deps = self.resolve_features(candidate, method)?; + let deps = self.resolve_features(parent,candidate, method)?; // Next, transform all dependencies into a list of possible candidates // which can satisfy that dependency. @@ -1336,6 +1334,7 @@ impl<'a> Context<'a> { /// Return all dependencies and the features we want from them. fn resolve_features<'b>(&mut self, + parent: Option<&Summary>, s: &'b Summary, method: &'b Method) -> ActivateResult)>> { @@ -1387,7 +1386,11 @@ impl<'a> Context<'a> { .map(|s| &s[..]) .collect::>(); let features = unknown.join(", "); - return Err((s.package_id().clone(), ConflictReason::MissingFeatures(features)))?; + return Err(match parent { + None => format_err!("Package `{}` does not have these features: `{}`", + s.package_id(), features).into(), + Some(p) => (p.package_id().clone(), ConflictReason::MissingFeatures(features)).into(), + }); } // Record what list of features is active for this package. diff --git a/tests/testsuite/features.rs b/tests/testsuite/features.rs index ca80513f7c8..2f2abe063a7 100644 --- a/tests/testsuite/features.rs +++ b/tests/testsuite/features.rs @@ -113,11 +113,8 @@ error: failed to select a version for `bar`. ... required by package `foo v0.0.1 ([..])` versions that meet the requirements `*` are: 0.0.1 -the package `bar` depends on `bar`, with features: `bar` but it does not have these features. -package `bar v0.0.1 ([..])` - ... which is depended on by `foo v0.0.1 ([..])` +the package `foo` depends on `bar`, with features: `bar` but `bar` does not have these features. -all possible versions conflict with previously selected packages. failed to select a version for `bar` which could resolve this conflict"));