Skip to content

Commit 382967a

Browse files
committed
Auto merge of #5000 - Eh2406:i4347, r=alexcrichton
backtrack if can not activate This is a fix for #4347 Unfortunately this too regressed error messages for the case that you specified a dependency feature that does not exist. @alexcrichton advice on improving the message?
2 parents d363109 + 2cbd1dd commit 382967a

File tree

3 files changed

+189
-89
lines changed

3 files changed

+189
-89
lines changed

src/cargo/core/resolver/mod.rs

+155-80
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ fn activate(cx: &mut Context,
414414
parent: Option<&Summary>,
415415
candidate: Candidate,
416416
method: &Method)
417-
-> CargoResult<Option<(DepsFrame, Duration)>> {
417+
-> ActivateResult<Option<(DepsFrame, Duration)>> {
418418
if let Some(parent) = parent {
419419
cx.resolve_graph.push(GraphNode::Link(parent.package_id().clone(),
420420
candidate.summary.package_id().clone()));
@@ -443,7 +443,7 @@ fn activate(cx: &mut Context,
443443
};
444444

445445
let now = Instant::now();
446-
let deps = cx.build_deps(registry, &candidate, method)?;
446+
let deps = cx.build_deps(registry, parent, &candidate, method)?;
447447
let frame = DepsFrame {
448448
parent: candidate,
449449
remaining_siblings: RcVecIter::new(Rc::new(deps)),
@@ -536,14 +536,40 @@ impl Ord for DepsFrame {
536536
enum ConflictReason {
537537
Semver,
538538
Links(String),
539+
MissingFeatures(String),
540+
}
541+
542+
enum ActivateError {
543+
Error(::failure::Error),
544+
Conflict(PackageId, ConflictReason),
545+
}
546+
type ActivateResult<T> = Result<T, ActivateError>;
547+
548+
impl From<::failure::Error> for ActivateError {
549+
fn from(t: ::failure::Error) -> Self {
550+
ActivateError::Error(t)
551+
}
552+
}
553+
554+
impl From<(PackageId, ConflictReason)> for ActivateError {
555+
fn from(t: (PackageId, ConflictReason)) -> Self {
556+
ActivateError::Conflict(t.0, t.1)
557+
}
539558
}
540559

541560
impl ConflictReason {
542561
fn is_links(&self) -> bool {
543-
match *self {
544-
ConflictReason::Semver => false,
545-
ConflictReason::Links(_) => true,
562+
if let ConflictReason::Links(_) = *self {
563+
return true;
546564
}
565+
false
566+
}
567+
568+
fn is_missing_features(&self) -> bool {
569+
if let ConflictReason::MissingFeatures(_) = *self {
570+
return true;
571+
}
572+
false
547573
}
548574
}
549575

@@ -556,6 +582,7 @@ struct BacktrackFrame<'a> {
556582
parent: Summary,
557583
dep: Dependency,
558584
features: Rc<Vec<String>>,
585+
conflicting_activations: HashMap<PackageId, ConflictReason>,
559586
}
560587

561588
#[derive(Clone)]
@@ -652,9 +679,12 @@ fn activate_deps_loop<'a>(
652679
summary: summary.clone(),
653680
replace: None,
654681
};
655-
let res = activate(&mut cx, registry, None, candidate, method)?;
656-
if let Some((frame, _)) = res {
657-
remaining_deps.push(frame);
682+
let res = activate(&mut cx, registry, None, candidate, method);
683+
match res {
684+
Ok(Some((frame, _))) => remaining_deps.push(frame),
685+
Ok(None) => (),
686+
Err(ActivateError::Error(e)) => return Err(e),
687+
Err(ActivateError::Conflict(_, _)) => panic!("bad error from activate")
658688
}
659689
}
660690

@@ -710,74 +740,96 @@ fn activate_deps_loop<'a>(
710740

711741
trace!("{}[{}]>{} {} candidates", parent.name(), cur, dep.name(), candidates.len());
712742
trace!("{}[{}]>{} {} prev activations", parent.name(), cur, dep.name(), cx.prev_active(&dep).len());
713-
let mut remaining_candidates = RemainingCandidates::new(&candidates);
714-
let next = remaining_candidates.next(cx.prev_active(&dep), &cx.links);
715743

716-
// Alright, for each candidate that's gotten this far, it meets the
717-
// following requirements:
718-
//
719-
// 1. The version matches the dependency requirement listed for this
720-
// package
721-
// 2. There are no activated versions for this package which are
722-
// semver/links-compatible, or there's an activated version which is
723-
// precisely equal to `candidate`.
724-
//
725-
// This means that we're going to attempt to activate each candidate in
726-
// turn. We could possibly fail to activate each candidate, so we try
727-
// each one in turn.
728-
let (candidate, has_another) = next.or_else(|mut conflicting| {
729-
// This dependency has no valid candidate. Backtrack until we
730-
// find a dependency that does have a candidate to try, and try
731-
// to activate that one. This resets the `remaining_deps` to
732-
// their state at the found level of the `backtrack_stack`.
733-
trace!("{}[{}]>{} -- no candidates", parent.name(), cur, dep.name());
734-
find_candidate(
735-
&mut backtrack_stack,
736-
&mut cx,
737-
&mut remaining_deps,
738-
&mut parent,
739-
&mut cur,
740-
&mut dep,
741-
&mut features,
742-
&mut remaining_candidates,
743-
&mut conflicting,
744-
).ok_or_else(|| {
745-
activation_error(
746-
&cx,
747-
registry,
748-
&parent,
749-
&dep,
750-
&conflicting,
751-
&candidates,
752-
config,
753-
)
754-
})
755-
})?;
744+
let mut remaining_candidates = RemainingCandidates::new(&candidates);
745+
let mut successfully_activated = false;
746+
let mut conflicting_activations = HashMap::new();
747+
748+
while !successfully_activated {
749+
let next = remaining_candidates.next(cx.prev_active(&dep), &cx.links);
750+
751+
// Alright, for each candidate that's gotten this far, it meets the
752+
// following requirements:
753+
//
754+
// 1. The version matches the dependency requirement listed for this
755+
// package
756+
// 2. There are no activated versions for this package which are
757+
// semver/links-compatible, or there's an activated version which is
758+
// precisely equal to `candidate`.
759+
//
760+
// This means that we're going to attempt to activate each candidate in
761+
// turn. We could possibly fail to activate each candidate, so we try
762+
// each one in turn.
763+
let (candidate, has_another) = next.or_else(|conflicting| {
764+
conflicting_activations.extend(conflicting);
765+
// This dependency has no valid candidate. Backtrack until we
766+
// find a dependency that does have a candidate to try, and try
767+
// to activate that one. This resets the `remaining_deps` to
768+
// their state at the found level of the `backtrack_stack`.
769+
trace!("{}[{}]>{} -- no candidates", parent.name(), cur, dep.name());
770+
find_candidate(
771+
&mut backtrack_stack,
772+
&mut cx,
773+
&mut remaining_deps,
774+
&mut parent,
775+
&mut cur,
776+
&mut dep,
777+
&mut features,
778+
&mut remaining_candidates,
779+
&mut conflicting_activations,
780+
).ok_or_else(|| {
781+
// if we hit an activation error and we are out of other combinations
782+
// then just report that error
783+
activation_error(
784+
&cx,
785+
registry,
786+
&parent,
787+
&dep,
788+
&conflicting_activations,
789+
&candidates,
790+
config,
791+
)
792+
})
793+
})?;
756794

757-
// We have a candidate. Add an entry to the `backtrack_stack` so
758-
// we can try the next one if this one fails.
759-
if has_another {
760-
backtrack_stack.push(BacktrackFrame {
795+
// We have a candidate. Clone a `BacktrackFrame`
796+
// so we can add it to the `backtrack_stack` if activation succeeds.
797+
// We clone now in case `activate` changes `cx` and then fails.
798+
let backtrack = BacktrackFrame {
761799
cur,
762800
context_backup: Context::clone(&cx),
763801
deps_backup: <BinaryHeap<DepsFrame>>::clone(&remaining_deps),
764-
remaining_candidates,
802+
remaining_candidates: remaining_candidates.clone(),
765803
parent: Summary::clone(&parent),
766804
dep: Dependency::clone(&dep),
767805
features: Rc::clone(&features),
768-
});
769-
}
806+
conflicting_activations: conflicting_activations.clone(),
807+
};
770808

771-
let method = Method::Required {
772-
dev_deps: false,
773-
features: &features,
774-
uses_default_features: dep.uses_default_features(),
775-
};
776-
trace!("{}[{}]>{} trying {}", parent.name(), cur, dep.name(), candidate.summary.version());
777-
let res = activate(&mut cx, registry, Some(&parent), candidate, &method)?;
778-
if let Some((frame, dur)) = res {
779-
remaining_deps.push(frame);
780-
deps_time += dur;
809+
let method = Method::Required {
810+
dev_deps: false,
811+
features: &features,
812+
uses_default_features: dep.uses_default_features(),
813+
};
814+
trace!("{}[{}]>{} trying {}", parent.name(), cur, dep.name(), candidate.summary.version());
815+
let res = activate(&mut cx, registry, Some(&parent), candidate, &method);
816+
successfully_activated = res.is_ok();
817+
818+
match res {
819+
Ok(Some((frame, dur))) => {
820+
remaining_deps.push(frame);
821+
deps_time += dur;
822+
}
823+
Ok(None) => (),
824+
Err(ActivateError::Error(e)) => return Err(e),
825+
Err(ActivateError::Conflict(id, reason)) => { conflicting_activations.insert(id, reason); },
826+
}
827+
828+
// Add an entry to the `backtrack_stack` so
829+
// we can try the next one if this one fails.
830+
if has_another && successfully_activated {
831+
backtrack_stack.push(backtrack);
832+
}
781833
}
782834
}
783835

@@ -824,6 +876,7 @@ fn find_candidate<'a>(
824876
*dep = frame.dep;
825877
*features = frame.features;
826878
*remaining_candidates = frame.remaining_candidates;
879+
*conflicting_activations = frame.conflicting_activations;
827880
return Some((candidate, has_another));
828881
}
829882
}
@@ -865,9 +918,9 @@ fn activation_error(cx: &Context,
865918

866919
let mut conflicting_activations: Vec<_> = conflicting_activations.iter().collect();
867920
conflicting_activations.sort_unstable();
868-
let (links_errors, other_errors): (Vec<_>, Vec<_>) = conflicting_activations.drain(..).rev().partition(|&(_, r)| r.is_links());
921+
let (links_errors, mut other_errors): (Vec<_>, Vec<_>) = conflicting_activations.drain(..).rev().partition(|&(_, r)| r.is_links());
869922

870-
for &(p, r) in &links_errors {
923+
for &(p, r) in links_errors.iter() {
871924
if let ConflictReason::Links(ref link) = *r {
872925
msg.push_str("\n\nthe package `");
873926
msg.push_str(dep.name());
@@ -880,12 +933,29 @@ fn activation_error(cx: &Context,
880933
msg.push_str(&describe_path(p));
881934
}
882935

883-
if links_errors.is_empty() {
936+
let (features_errors, other_errors): (Vec<_>, Vec<_>) = other_errors.drain(..).partition(|&(_, r)| r.is_missing_features());
937+
938+
for &(p, r) in features_errors.iter() {
939+
if let ConflictReason::MissingFeatures(ref features) = *r {
940+
msg.push_str("\n\nthe package `");
941+
msg.push_str(p.name());
942+
msg.push_str("` depends on `");
943+
msg.push_str(dep.name());
944+
msg.push_str("`, with features: `");
945+
msg.push_str(features);
946+
msg.push_str("` but `");
947+
msg.push_str(dep.name());
948+
msg.push_str("` does not have these features.\n");
949+
}
950+
// p == parent so the full path is redundant.
951+
}
952+
953+
if !other_errors.is_empty() {
884954
msg.push_str("\n\nall possible versions conflict with \
885955
previously selected packages.");
886956
}
887957

888-
for &(p, _) in &other_errors {
958+
for &(p, _) in other_errors.iter() {
889959
msg.push_str("\n\n previously selected ");
890960
msg.push_str(&describe_path(p));
891961
}
@@ -1070,9 +1140,9 @@ impl<'r> Requirements<'r> {
10701140
}
10711141
}
10721142

1073-
// Takes requested features for a single package from the input Method and
1074-
// recurses to find all requested features, dependencies and requested
1075-
// dependency features in a Requirements object, returning it to the resolver.
1143+
/// Takes requested features for a single package from the input Method and
1144+
/// recurses to find all requested features, dependencies and requested
1145+
/// dependency features in a Requirements object, returning it to the resolver.
10761146
fn build_requirements<'a, 'b: 'a>(s: &'a Summary, method: &'b Method)
10771147
-> CargoResult<Requirements<'a>> {
10781148
let mut reqs = Requirements::new(s);
@@ -1147,12 +1217,13 @@ impl<'a> Context<'a> {
11471217

11481218
fn build_deps(&mut self,
11491219
registry: &mut Registry,
1220+
parent: Option<&Summary>,
11501221
candidate: &Summary,
1151-
method: &Method) -> CargoResult<Vec<DepInfo>> {
1222+
method: &Method) -> ActivateResult<Vec<DepInfo>> {
11521223
// First, figure out our set of dependencies based on the requested set
11531224
// of features. This also calculates what features we're going to enable
11541225
// for our own dependencies.
1155-
let deps = self.resolve_features(candidate, method)?;
1226+
let deps = self.resolve_features(parent,candidate, method)?;
11561227

11571228
// Next, transform all dependencies into a list of possible candidates
11581229
// which can satisfy that dependency.
@@ -1263,9 +1334,10 @@ impl<'a> Context<'a> {
12631334

12641335
/// Return all dependencies and the features we want from them.
12651336
fn resolve_features<'b>(&mut self,
1337+
parent: Option<&Summary>,
12661338
s: &'b Summary,
12671339
method: &'b Method)
1268-
-> CargoResult<Vec<(Dependency, Vec<String>)>> {
1340+
-> ActivateResult<Vec<(Dependency, Vec<String>)>> {
12691341
let dev_deps = match *method {
12701342
Method::Everything => true,
12711343
Method::Required { dev_deps, .. } => dev_deps,
@@ -1300,7 +1372,7 @@ impl<'a> Context<'a> {
13001372
base.extend(dep.features().iter().cloned());
13011373
for feature in base.iter() {
13021374
if feature.contains('/') {
1303-
bail!("feature names may not contain slashes: `{}`", feature);
1375+
return Err(format_err!("feature names may not contain slashes: `{}`", feature).into());
13041376
}
13051377
}
13061378
ret.push((dep.clone(), base));
@@ -1314,8 +1386,11 @@ impl<'a> Context<'a> {
13141386
.map(|s| &s[..])
13151387
.collect::<Vec<&str>>();
13161388
let features = unknown.join(", ");
1317-
bail!("Package `{}` does not have these features: `{}`",
1318-
s.package_id(), features)
1389+
return Err(match parent {
1390+
None => format_err!("Package `{}` does not have these features: `{}`",
1391+
s.package_id(), features).into(),
1392+
Some(p) => (p.package_id().clone(), ConflictReason::MissingFeatures(features)).into(),
1393+
});
13191394
}
13201395

13211396
// Record what list of features is active for this package.

tests/testsuite/features.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,14 @@ fn invalid4() {
109109

110110
assert_that(p.cargo("build"),
111111
execs().with_status(101).with_stderr("\
112-
[ERROR] Package `bar v0.0.1 ([..])` does not have these features: `bar`
113-
"));
112+
error: failed to select a version for `bar`.
113+
... required by package `foo v0.0.1 ([..])`
114+
versions that meet the requirements `*` are: 0.0.1
115+
116+
the package `foo` depends on `bar`, with features: `bar` but `bar` does not have these features.
117+
118+
119+
failed to select a version for `bar` which could resolve this conflict"));
114120

115121
p.change_file("Cargo.toml", r#"
116122
[project]
@@ -121,8 +127,7 @@ fn invalid4() {
121127

122128
assert_that(p.cargo("build").arg("--features").arg("test"),
123129
execs().with_status(101).with_stderr("\
124-
[ERROR] Package `foo v0.0.1 ([..])` does not have these features: `test`
125-
"));
130+
error: Package `foo v0.0.1 ([..])` does not have these features: `test`"));
126131
}
127132

128133
#[test]
@@ -1052,8 +1057,7 @@ fn dep_feature_in_cmd_line() {
10521057
// Trying to enable features of transitive dependencies is an error
10531058
assert_that(p.cargo("build").arg("--features").arg("bar/some-feat"),
10541059
execs().with_status(101).with_stderr("\
1055-
[ERROR] Package `foo v0.0.1 ([..])` does not have these features: `bar`
1056-
"));
1060+
error: Package `foo v0.0.1 ([..])` does not have these features: `bar`"));
10571061

10581062
// Hierarchical feature specification should still be disallowed
10591063
assert_that(p.cargo("build").arg("--features").arg("derived/bar/some-feat"),

0 commit comments

Comments
 (0)