-
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
Re-enable all the indexes / specs #73
Conversation
ed8a9ca
to
2ab704c
Compare
lib/molinillo/resolution.rb
Outdated
@@ -185,6 +195,9 @@ def process_topmost_state | |||
create_conflict if state.is_a? PossibilityState | |||
unwind_for_conflict until possibility && state.is_a?(DependencyState) | |||
end | |||
rescue CircularDependencyError => root_error |
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 this new behavior will require a test
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.
For sure. CocoaPods/Resolver-Integration-Specs#14
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.
Doesn't pass on every index
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.
Does now. Still don't love the way I've implemented rescuing from CircularDependency errors, though. I'll have a think about it once I get properly handling previous conflicts working fast.
e51ccb0
to
2ab704c
Compare
1713d5d
to
4a72214
Compare
lib/molinillo/resolution.rb
Outdated
@@ -440,7 +467,7 @@ def parent_of(requirement) | |||
# @return [Object] the requirement that led to a version of a possibility | |||
# with the given name being activated. | |||
def requirement_for_existing_name(name) | |||
return nil unless activated.vertex_named(name).payload | |||
return nil unless activated.vertex_named(name) && activated.vertex_named(name).payload |
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.
let's store the vertex into a local to avoid the second lookup
lib/molinillo/resolution.rb
Outdated
@@ -391,6 +417,7 @@ def binding_requirements_for_conflict(conflict) | |||
# then the only two requirements we need to consider are the initial one | |||
# (where the dependency's version was first chosen) and the last | |||
if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities) | |||
return possible_binding_requirements if conflict.root_error |
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.
a comment on this would be 🚀
lib/molinillo/resolution.rb
Outdated
@@ -453,7 +480,7 @@ def find_state_for(requirement) | |||
|
|||
# @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.
document the param
df3c750
to
b2950e0
Compare
lib/molinillo/resolution.rb
Outdated
@@ -241,82 +259,78 @@ def initial_state | |||
# @return [void] | |||
def unwind_for_conflict | |||
details_for_unwind = build_details_for_unwind | |||
tmp = state.unused_unwind_options |
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.
the state.
is redundant
lib/molinillo/resolution.rb
Outdated
# for primary / parent elements of the tree? | ||
alternative = unused_unwind_options.select do |option_set| | ||
next false unless option_set.first > unwind_details.first | ||
(option_set.map(&:requirement).flatten & unwind_details.map(&:requirement).flatten).any? |
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.
Compatibility.flat_map
lib/molinillo/resolution.rb
Outdated
|
||
binding_requirements.reverse_each do |r| | ||
unwind_details << UnwindDetails.new(-1, requirement_tree_for(r), nil, binding_requirements) |
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 calculating the full requirement tree is rather slow... is it really necessary?
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.
With the approach I'm experimenting with it is. Might be able to speed things up by doing it lazily, but I'm not sure yet.
High level, the idea here is that all previous unwind options remain possible later, even if they're not selected at first. To make that feasible, we need to:
- Store them all (hence the removal of an early-exits from this method)
- Pick the relevant ones when rewinding from a future conflict
It's part (2) that needs the requirement trees - to know whether a previous, unused rewind would avoid the current conflict, we want to check whether there's any overlap between that previous conflict's requirement tree (the amalgamation of the requirement trees for all the requirements that caused it) and the current conflict's tree.
Part I'm hazy about is exactly how much filtering we can do on the past rewinds, but I'm more comfortable working back from the above, which I'm relatively certain is formally correct, than trying to get to correctness from an "efficient" solution.
🤞
lib/molinillo/resolution.rb
Outdated
end | ||
grandparent_r = parent_of(grandparent_r) | ||
end | ||
end | ||
|
||
unwind_details | ||
unwind_details.sort.reverse |
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.
might it be faster to do unwind_details.sort { |l ,r| r <=> l }
to avoid the intermediary array?
46b50a8
to
6135941
Compare
lib/molinillo/resolution.rb
Outdated
@@ -324,18 +327,23 @@ def build_details_for_unwind | |||
# resolve the given conflict conflict | |||
def unwind_details_for_requirements(binding_requirements) | |||
unwind_details = [] | |||
|
|||
full_requirement_tree = binding_requirements.map { |r| requirement_tree_for(r) }.flatten |
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.
Please either flat map or flatten a specific amount
3e81076
to
85052bd
Compare
4581619
to
4e385c9
Compare
525f8c3
to
811cef5
Compare
lib/molinillo/resolution.rb
Outdated
@@ -460,8 +461,12 @@ def filter_possibilities_for_primary_unwind(unwind_details) | |||
# @param [UnwindDetails] details of the conflict just unwound from | |||
# @return [void] | |||
def filter_possibilities_for_parent_unwind(unwind_details) | |||
unwinds = [unwind_details] + unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } |
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.
.select << unwind_details
to save an array allocation
lib/molinillo/resolution.rb
Outdated
end | ||
parent_r = grandparent_r | ||
grandparent_r = parent_of(parent_r) | ||
end | ||
end | ||
|
||
unwind_details | ||
unwind_details.sort |
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.
is it possible this array won't be used, and we could maybe sort!
it when first accessed?
lib/molinillo/resolution.rb
Outdated
next unless alternative > current_detail | ||
intersecting_requirements = | ||
alternative.requirement_trees.flatten(1) & | ||
last_detail_for_current_unwind.requirement_trees.flatten(1) |
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.
this can be pulled out into a local, but it might be worth memoizing this in the details?
9d67ace
to
c92a601
Compare
Crude benchmarking:
That should be well within any allowable margin of error. 🎉 |
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'm so indescribably excited for this to land. Thank you so so much @greysteil, you've managed to push Molinillo forward when I thought I'd hit a wall with it.
lib/molinillo/resolution.rb
Outdated
intersecting_requirements = | ||
last_detail_for_current_unwind.all_requirements & | ||
alternative.requirements_unwound_to_instead | ||
next unless intersecting_requirements.any? |
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.
next if empty?
# @param [Array] requirements an array of requirements | ||
# @return [Boolean] whether the possibility satisfies all of the | ||
# given requirements | ||
def possibility_satisfies_requirements?(possibility, requirements) |
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.
YASSSS
spec/resolver_spec.rb
Outdated
# These indexes don't do a great job sorting, and segiddins has been | ||
# unable to get the test passing with the bad sort without breaking | ||
# other specs | ||
# This index occassionally finds orders that are *incredibly* slow to resolver, |
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.
resolve
@@ -198,7 +198,7 @@ def versions_of(dependency_name) | |||
BundlerIndex, | |||
ReverseBundlerIndex, | |||
BundlerSingleAllNoPenaltyIndex, | |||
# RandomSortIndex, | |||
RandomSortIndex, |
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.
<3
Previously, we kept a hash of previous conflicts in `DependencyState#conflicts`, and considered unwinds from those previous considering when determining which state to unwind to. Unfortunately, there were a couple of problems with that approach: - A maximum of one conflict per dependency was stored. When a conflict was "resolved" and a new version of that dependency was activated, the previous conflict was deleted. Doing so deleted unwind options that may have been used in the future. - The `DependencyState#conflicts` method was also used when constructing error messages, so our ability to tweak it's behaviour was limited. - Full requirement trees for the conflicting requirements, and the associated unwind options, weren't stored, and could not be recreated at a later date (since the DependencyGraph would have changed since the time when the conflict was created) This PR introduces a new store on each `DependencyState` which contains details of all previously unused unwinds. Those old, previously unused unwinds are then considered when deciding whether to unwind from a new conflict. In theory, an old unwind may allow us to avoid the conflict we've just encountered, if it would affect the conflict's requirement trees in any way.
8e0314c
to
5a58e39
Compare
My pleasure @segiddins - it's an awesome project, and I've enjoyed every minute of working on it. (Well, maybe not all of the minutes banging my head against the keyboard...!) |
Gets Molinillo to the point where it can resolve anything that's thrown at it. Includes the following fixes and speedups (which will be pulled out into separate PRs when they're ready):
Handling failures where an unwind of a previous conflict is needed(Consider previous conflicts when determining the state to unwind to #72)Speeding up resolution by filtering grandparent+ states(Filter grandparents and up of requirements when unwinding #77)Handling circular dependencies(Only raise CircularDependency errors if they prevent resolution #78)TODO:
RandomSortIndex
specs 1000xARCHITECTURE.md
where appropriate (speedup is undoubtedly going to require some hard thinking about how we loop through conflicts)circular dependencies, multiple states for a single requirement, rewinding on previous conflicts)