-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Save circular dependency errors til resolution finishes #63
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think your integration specs submodule needs a rebase, as it seems to have lost a test case
attempt_to_activate | ||
rescue CircularDependencyError => e | ||
create_conflict(e) | ||
unwind_for_conflict until possibility && state.is_a?(DependencyState) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it'd be cool if we could avoid repeating this logic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I wasn't sure of the best way to handle it. It seems like this logic might belong more inside unwind_for_conflict
than here, but that method is used in a couple other places and I'm not quite familiar enough with the intricacies of its use to know if that was the right call or not.
@@ -181,7 +187,10 @@ def unwind_for_conflict | |||
debug(depth) { "Unwinding for conflict: #{requirement} to #{state_index_for_unwind / 2}" } | |||
conflicts.tap do |c| | |||
sliced_states = states.slice!((state_index_for_unwind + 1)..-1) | |||
raise VersionConflict.new(c) unless state | |||
unless state | |||
error = c.values.map(&:root_error).detect {|e| ! e.nil? } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rubocop won't like this
@@ -241,7 +250,7 @@ def state_any?(state) | |||
|
|||
# @return [Conflict] a {Conflict} that reflects the failure to activate | |||
# the {#possibility} in conjunction with the current {#state} | |||
def create_conflict | |||
def create_conflict(root_error=nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spaces around =
Want to throw that other spec into the submodule? It looks like an easier one to debug on than the larger index |
The issue with the two failing specs appears to be that |
diff --git a/lib/molinillo/resolution.rb b/lib/molinillo/resolution.rb
index 4f4615f..4ef1d80 100644
--- a/lib/molinillo/resolution.rb
+++ b/lib/molinillo/resolution.rb
@@ -138,15 +138,18 @@ module Molinillo
begin
attempt_to_activate
rescue CircularDependencyError => e
- create_conflict(e)
- unwind_for_conflict until possibility && state.is_a?(DependencyState)
+ create_conflict_and_unwind(e)
end
else
- create_conflict if state.is_a? PossibilityState
- unwind_for_conflict until possibility && state.is_a?(DependencyState)
+ create_conflict_and_unwind
end
end
+ def create_conflict_and_unwind(root_error = nil)
+ create_conflict(root_error) if state.is_a? PossibilityState
+ unwind_for_conflict until possibility && state.is_a?(DependencyState)
+ end
+
# @return [Object] the current possibility that the resolution is trying
# to activate
def possibility
@@ -188,7 +191,7 @@ module Molinillo
conflicts.tap do |c|
sliced_states = states.slice!((state_index_for_unwind + 1)..-1)
unless state
- error = c.values.map(&:root_error).detect {|e| ! e.nil? }
+ error = c.values.map(&:root_error).find(&:itself)
raise error || VersionConflict.new(c)
end
activated.rewind_to(sliced_states.first || :initial_state) if sliced_states
@@ -216,6 +219,10 @@ module Molinillo
end
end
+ if current_requirement == existing_requirement && index == -1
+ index = states.length - 3
+ end
+
index
end
@@ -250,7 +257,7 @@ module Molinillo
# @return [Conflict] a {Conflict} that reflects the failure to activate
# the {#possibility} in conjunction with the current {#state}
- def create_conflict(root_error=nil)
+ def create_conflict(root_error = nil)
vertex = activated.vertex_named(name)
locked_requirement = locked_requirement_named(name)
diff --git a/spec/resolver_integration_specs b/spec/resolver_integration_specs
index a5315c3..4b05041 160000
--- a/spec/resolver_integration_specs
+++ b/spec/resolver_integration_specs
@@ -1 +1 @@
-Subproject commit a5315c384f91e51cb635cfcb2b6b0c271de76d65
+Subproject commit 4b050415cb747738c326aa32f5d089ea2bc1f1da |
Ok, pushed a new rebased submodule that's up to date and has that other spec. I agree, that one exposes the bug a little more simply without the circular deps muddying the waters (and that one fails on master too). I do have a branch where I've been experimenting with an alternative backjumping algorithm, but it's part of some more extensive changes. I'll take a look at that next and see if I can extract that one piece from the branch. |
Note that my diff fixes the issue for the circular case, just not the buckjumping case |
This might make failing resolutions take a tad longer, but it seems to fix the spec failures! diff --git a/lib/molinillo/resolution.rb b/lib/molinillo/resolution.rb
index 4f4615f..b0e73bc 100644
--- a/lib/molinillo/resolution.rb
+++ b/lib/molinillo/resolution.rb
@@ -216,7 +216,7 @@ module Molinillo
end
end
- index
+ index == -1 ? states.size - 2 : index
end
# @return [Object] the requirement that led to `requirement` being added |
I'm not positive, but I'm guessing that solution would still leave open cases where the backtracking jumps over a culprit to try an unsuccessful branch higher on the stack, then runs out of options and fails to resolve. FYI I'm working on an alternative backtracking choice on a branch, but haven't worked out all the kinks yet: https://github.com/iangreenleaf/Molinillo/tree/backjumping-bug |
Well it passed your test, so that's all we can ask 😝 |
This treats circular dependency errors as regular conflicts rather than aborting immediately. If no resolution is found, it will throw the relevant error at the end, thus still providing a useful error message in most cases.
This seems to work well in most cases, but unfortunately the spec uncovered an unrelated bug in the backjumping algorithm so it's not passing on all indexes yet.