molinillo 0.4.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/molinillo/delegates/resolution_state.rb +50 -0
- data/lib/molinillo/delegates/specification_provider.rb +80 -0
- data/lib/molinillo/dependency_graph.rb +56 -144
- data/lib/molinillo/dependency_graph/action.rb +35 -0
- data/lib/molinillo/dependency_graph/add_edge_no_circular.rb +58 -0
- data/lib/molinillo/dependency_graph/add_vertex.rb +61 -0
- data/lib/molinillo/dependency_graph/detach_vertex_named.rb +53 -0
- data/lib/molinillo/dependency_graph/log.rb +114 -0
- data/lib/molinillo/dependency_graph/set_payload.rb +45 -0
- data/lib/molinillo/dependency_graph/tag.rb +35 -0
- data/lib/molinillo/dependency_graph/vertex.rb +123 -0
- data/lib/molinillo/gem_metadata.rb +1 -1
- data/lib/molinillo/resolution.rb +46 -51
- data/lib/molinillo/state.rb +4 -2
- data/spec/dependency_graph/log_spec.rb +29 -0
- data/spec/dependency_graph_spec.rb +1 -0
- data/spec/errors_spec.rb +1 -0
- data/spec/resolver_spec.rb +1 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/spec_helper/index.rb +1 -0
- data/spec/spec_helper/specification.rb +1 -0
- data/spec/spec_helper/ui.rb +6 -1
- data/spec/state_spec.rb +1 -0
- metadata +15 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b84ed4e59212748f00d03e6af1c8efa14a50312
|
4
|
+
data.tar.gz: c23774f7ef7ceb3641ae3f34357fbc4894c9afb5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 748d0fcec60150be7b9b33b0585ee1bd52e08ba19bf1ac7d2be6889ad2b4669a29d15a5d4de21e22823f9900f8e2f1b364b1ac40406cff35d4261a8a7c783103
|
7
|
+
data.tar.gz: 1a09bbe11d5b9c1d27e26e5f7b0dfc4848f28a87729d999fea11e784b6ba788a98146108572c6c8b340774360815bc0e3c44ae2f478c9373a85a85be1bbec8c8
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Molinillo
|
3
|
+
# @!visibility private
|
4
|
+
module Delegates
|
5
|
+
# Delegates all {Molinillo::ResolutionState} methods to a `#state` property.
|
6
|
+
module ResolutionState
|
7
|
+
# (see Molinillo::ResolutionState#name)
|
8
|
+
def name
|
9
|
+
current_state = state || Molinillo::ResolutionState.empty
|
10
|
+
current_state.name
|
11
|
+
end
|
12
|
+
|
13
|
+
# (see Molinillo::ResolutionState#requirements)
|
14
|
+
def requirements
|
15
|
+
current_state = state || Molinillo::ResolutionState.empty
|
16
|
+
current_state.requirements
|
17
|
+
end
|
18
|
+
|
19
|
+
# (see Molinillo::ResolutionState#activated)
|
20
|
+
def activated
|
21
|
+
current_state = state || Molinillo::ResolutionState.empty
|
22
|
+
current_state.activated
|
23
|
+
end
|
24
|
+
|
25
|
+
# (see Molinillo::ResolutionState#requirement)
|
26
|
+
def requirement
|
27
|
+
current_state = state || Molinillo::ResolutionState.empty
|
28
|
+
current_state.requirement
|
29
|
+
end
|
30
|
+
|
31
|
+
# (see Molinillo::ResolutionState#possibilities)
|
32
|
+
def possibilities
|
33
|
+
current_state = state || Molinillo::ResolutionState.empty
|
34
|
+
current_state.possibilities
|
35
|
+
end
|
36
|
+
|
37
|
+
# (see Molinillo::ResolutionState#depth)
|
38
|
+
def depth
|
39
|
+
current_state = state || Molinillo::ResolutionState.empty
|
40
|
+
current_state.depth
|
41
|
+
end
|
42
|
+
|
43
|
+
# (see Molinillo::ResolutionState#conflicts)
|
44
|
+
def conflicts
|
45
|
+
current_state = state || Molinillo::ResolutionState.empty
|
46
|
+
current_state.conflicts
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Molinillo
|
3
|
+
module Delegates
|
4
|
+
# Delegates all {Molinillo::SpecificationProvider} methods to a
|
5
|
+
# `#specification_provider` property.
|
6
|
+
module SpecificationProvider
|
7
|
+
# (see Molinillo::SpecificationProvider#search_for)
|
8
|
+
def search_for(dependency)
|
9
|
+
with_no_such_dependency_error_handling do
|
10
|
+
specification_provider.search_for(dependency)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# (see Molinillo::SpecificationProvider#dependencies_for)
|
15
|
+
def dependencies_for(specification)
|
16
|
+
with_no_such_dependency_error_handling do
|
17
|
+
specification_provider.dependencies_for(specification)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# (see Molinillo::SpecificationProvider#requirement_satisfied_by?)
|
22
|
+
def requirement_satisfied_by?(requirement, activated, spec)
|
23
|
+
with_no_such_dependency_error_handling do
|
24
|
+
specification_provider.requirement_satisfied_by?(requirement, activated, spec)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# (see Molinillo::SpecificationProvider#name_for)
|
29
|
+
def name_for(dependency)
|
30
|
+
with_no_such_dependency_error_handling do
|
31
|
+
specification_provider.name_for(dependency)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# (see Molinillo::SpecificationProvider#name_for_explicit_dependency_source)
|
36
|
+
def name_for_explicit_dependency_source
|
37
|
+
with_no_such_dependency_error_handling do
|
38
|
+
specification_provider.name_for_explicit_dependency_source
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# (see Molinillo::SpecificationProvider#name_for_locking_dependency_source)
|
43
|
+
def name_for_locking_dependency_source
|
44
|
+
with_no_such_dependency_error_handling do
|
45
|
+
specification_provider.name_for_locking_dependency_source
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# (see Molinillo::SpecificationProvider#sort_dependencies)
|
50
|
+
def sort_dependencies(dependencies, activated, conflicts)
|
51
|
+
with_no_such_dependency_error_handling do
|
52
|
+
specification_provider.sort_dependencies(dependencies, activated, conflicts)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# (see Molinillo::SpecificationProvider#allow_missing?)
|
57
|
+
def allow_missing?(dependency)
|
58
|
+
with_no_such_dependency_error_handling do
|
59
|
+
specification_provider.allow_missing?(dependency)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Ensures any raised {NoSuchDependencyError} has its
|
66
|
+
# {NoSuchDependencyError#required_by} set.
|
67
|
+
# @yield
|
68
|
+
def with_no_such_dependency_error_handling
|
69
|
+
yield
|
70
|
+
rescue NoSuchDependencyError => error
|
71
|
+
if state
|
72
|
+
vertex = activated.vertex_named(name_for(error.dependency))
|
73
|
+
error.required_by += vertex.incoming_edges.map { |e| e.origin.name }
|
74
|
+
error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty?
|
75
|
+
end
|
76
|
+
raise
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -2,6 +2,9 @@
|
|
2
2
|
require 'set'
|
3
3
|
require 'tsort'
|
4
4
|
|
5
|
+
require 'molinillo/dependency_graph/log'
|
6
|
+
require 'molinillo/dependency_graph/vertex'
|
7
|
+
|
5
8
|
module Molinillo
|
6
9
|
# A directed acyclic graph that is tuned to hold named dependencies
|
7
10
|
class DependencyGraph
|
@@ -10,15 +13,16 @@ module Molinillo
|
|
10
13
|
# Enumerates through the vertices of the graph.
|
11
14
|
# @return [Array<Vertex>] The graph's vertices.
|
12
15
|
def each
|
16
|
+
return vertices.values.each unless block_given?
|
13
17
|
vertices.values.each { |v| yield v }
|
14
18
|
end
|
15
19
|
|
16
20
|
include TSort
|
17
21
|
|
18
|
-
#
|
22
|
+
# @!visibility private
|
19
23
|
alias tsort_each_node each
|
20
24
|
|
21
|
-
#
|
25
|
+
# @!visibility private
|
22
26
|
def tsort_each_child(vertex, &block)
|
23
27
|
vertex.successors.each(&block)
|
24
28
|
end
|
@@ -44,9 +48,27 @@ module Molinillo
|
|
44
48
|
# by {Vertex#name}
|
45
49
|
attr_reader :vertices
|
46
50
|
|
51
|
+
# @return [Log] the op log for this graph
|
52
|
+
attr_reader :log
|
53
|
+
|
47
54
|
# Initializes an empty dependency graph
|
48
55
|
def initialize
|
49
56
|
@vertices = {}
|
57
|
+
@log = Log.new
|
58
|
+
end
|
59
|
+
|
60
|
+
# Tags the current state of the dependency as the given tag
|
61
|
+
# @param [Object] tag an opaque tag for the current state of the graph
|
62
|
+
# @return [Void]
|
63
|
+
def tag(tag)
|
64
|
+
log.tag(self, tag)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Rewinds the graph to the state tagged as `tag`
|
68
|
+
# @param [Object] tag the tag to rewind to
|
69
|
+
# @return [Void]
|
70
|
+
def rewind_to(tag)
|
71
|
+
log.rewind_to(self, tag)
|
50
72
|
end
|
51
73
|
|
52
74
|
# Initializes a copy of a {DependencyGraph}, ensuring that all {#vertices}
|
@@ -55,6 +77,7 @@ module Molinillo
|
|
55
77
|
def initialize_copy(other)
|
56
78
|
super
|
57
79
|
@vertices = {}
|
80
|
+
@log = other.log.dup
|
58
81
|
traverse = lambda do |new_v, old_v|
|
59
82
|
return if new_v.outgoing_edges.size == old_v.outgoing_edges.size
|
60
83
|
old_v.outgoing_edges.each do |edge|
|
@@ -75,6 +98,22 @@ module Molinillo
|
|
75
98
|
"#{self.class}:#{vertices.values.inspect}"
|
76
99
|
end
|
77
100
|
|
101
|
+
# @return [String] Returns a dot format representation of the graph
|
102
|
+
def to_dot
|
103
|
+
dot_vertices = []
|
104
|
+
dot_edges = []
|
105
|
+
vertices.each do |n, v|
|
106
|
+
dot_vertices << " #{n} [label=\"{#{n}|#{v.payload}}\"]"
|
107
|
+
v.outgoing_edges.each do |e|
|
108
|
+
dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=\"#{e.requirement}\"]"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
dot_vertices.sort!
|
112
|
+
dot_edges.sort!
|
113
|
+
dot = dot_vertices.unshift('digraph G {').push('') + dot_edges.push('}')
|
114
|
+
dot.join("\n")
|
115
|
+
end
|
116
|
+
|
78
117
|
# @return [Boolean] whether the two dependency graphs are equal, determined
|
79
118
|
# by a recursive traversal of each {#root_vertices} and its
|
80
119
|
# {Vertex#successors}
|
@@ -93,12 +132,9 @@ module Molinillo
|
|
93
132
|
# @param [Object] requirement the requirement that is requiring the child
|
94
133
|
# @return [void]
|
95
134
|
def add_child_vertex(name, payload, parent_names, requirement)
|
96
|
-
|
135
|
+
root = !parent_names.delete(nil) { true }
|
136
|
+
vertex = add_vertex(name, payload, root)
|
97
137
|
parent_names.each do |parent_name|
|
98
|
-
unless parent_name
|
99
|
-
vertex.root = true
|
100
|
-
next
|
101
|
-
end
|
102
138
|
parent_node = vertex_named(parent_name)
|
103
139
|
add_edge(parent_node, vertex, requirement)
|
104
140
|
end
|
@@ -110,10 +146,7 @@ module Molinillo
|
|
110
146
|
# @param [Object] payload
|
111
147
|
# @return [Vertex] the vertex that was added to `self`
|
112
148
|
def add_vertex(name, payload, root = false)
|
113
|
-
|
114
|
-
vertex.payload ||= payload
|
115
|
-
vertex.root ||= root
|
116
|
-
vertex
|
149
|
+
log.add_vertex(self, name, payload, root)
|
117
150
|
end
|
118
151
|
|
119
152
|
# Detaches the {#vertex_named} `name` {Vertex} from the graph, recursively
|
@@ -121,16 +154,7 @@ module Molinillo
|
|
121
154
|
# @param [String] name
|
122
155
|
# @return [void]
|
123
156
|
def detach_vertex_named(name)
|
124
|
-
|
125
|
-
vertex.outgoing_edges.each do |e|
|
126
|
-
v = e.destination
|
127
|
-
v.incoming_edges.delete(e)
|
128
|
-
detach_vertex_named(v.name) unless v.root? || v.predecessors.any?
|
129
|
-
end
|
130
|
-
vertex.incoming_edges.each do |e|
|
131
|
-
v = e.origin
|
132
|
-
v.outgoing_edges.delete(e)
|
133
|
-
end
|
157
|
+
log.detach_vertex_named(self, name)
|
134
158
|
end
|
135
159
|
|
136
160
|
# @param [String] name
|
@@ -158,134 +182,22 @@ module Molinillo
|
|
158
182
|
add_edge_no_circular(origin, destination, requirement)
|
159
183
|
end
|
160
184
|
|
185
|
+
# Sets the payload of the vertex with the given name
|
186
|
+
# @param [String] name the name of the vertex
|
187
|
+
# @param [Object] payload the payload
|
188
|
+
# @return [Void]
|
189
|
+
def set_payload(name, payload)
|
190
|
+
log.set_payload(self, name, payload)
|
191
|
+
end
|
192
|
+
|
161
193
|
private
|
162
194
|
|
163
195
|
# Adds a new {Edge} to the dependency graph without checking for
|
164
196
|
# circularity.
|
197
|
+
# @param (see #add_edge)
|
198
|
+
# @return (see #add_edge)
|
165
199
|
def add_edge_no_circular(origin, destination, requirement)
|
166
|
-
|
167
|
-
origin.outgoing_edges << edge
|
168
|
-
destination.incoming_edges << edge
|
169
|
-
edge
|
170
|
-
end
|
171
|
-
|
172
|
-
# A vertex in a {DependencyGraph} that encapsulates a {#name} and a
|
173
|
-
# {#payload}
|
174
|
-
class Vertex
|
175
|
-
# @return [String] the name of the vertex
|
176
|
-
attr_accessor :name
|
177
|
-
|
178
|
-
# @return [Object] the payload the vertex holds
|
179
|
-
attr_accessor :payload
|
180
|
-
|
181
|
-
# @return [Arrary<Object>] the explicit requirements that required
|
182
|
-
# this vertex
|
183
|
-
attr_reader :explicit_requirements
|
184
|
-
|
185
|
-
# @return [Boolean] whether the vertex is considered a root vertex
|
186
|
-
attr_accessor :root
|
187
|
-
alias root? root
|
188
|
-
|
189
|
-
# Initializes a vertex with the given name and payload.
|
190
|
-
# @param [String] name see {#name}
|
191
|
-
# @param [Object] payload see {#payload}
|
192
|
-
def initialize(name, payload)
|
193
|
-
@name = name
|
194
|
-
@payload = payload
|
195
|
-
@explicit_requirements = []
|
196
|
-
@outgoing_edges = []
|
197
|
-
@incoming_edges = []
|
198
|
-
end
|
199
|
-
|
200
|
-
# @return [Array<Object>] all of the requirements that required
|
201
|
-
# this vertex
|
202
|
-
def requirements
|
203
|
-
incoming_edges.map(&:requirement) + explicit_requirements
|
204
|
-
end
|
205
|
-
|
206
|
-
# @return [Array<Edge>] the edges of {#graph} that have `self` as their
|
207
|
-
# {Edge#origin}
|
208
|
-
attr_accessor :outgoing_edges
|
209
|
-
|
210
|
-
# @return [Array<Edge>] the edges of {#graph} that have `self` as their
|
211
|
-
# {Edge#destination}
|
212
|
-
attr_accessor :incoming_edges
|
213
|
-
|
214
|
-
# @return [Array<Vertex>] the vertices of {#graph} that have an edge with
|
215
|
-
# `self` as their {Edge#destination}
|
216
|
-
def predecessors
|
217
|
-
incoming_edges.map(&:origin)
|
218
|
-
end
|
219
|
-
|
220
|
-
# @return [Array<Vertex>] the vertices of {#graph} where `self` is a
|
221
|
-
# {#descendent?}
|
222
|
-
def recursive_predecessors
|
223
|
-
vertices = predecessors
|
224
|
-
vertices += vertices.map(&:recursive_predecessors).flatten(1)
|
225
|
-
vertices.uniq!
|
226
|
-
vertices
|
227
|
-
end
|
228
|
-
|
229
|
-
# @return [Array<Vertex>] the vertices of {#graph} that have an edge with
|
230
|
-
# `self` as their {Edge#origin}
|
231
|
-
def successors
|
232
|
-
outgoing_edges.map(&:destination)
|
233
|
-
end
|
234
|
-
|
235
|
-
# @return [Array<Vertex>] the vertices of {#graph} where `self` is an
|
236
|
-
# {#ancestor?}
|
237
|
-
def recursive_successors
|
238
|
-
vertices = successors
|
239
|
-
vertices += vertices.map(&:recursive_successors).flatten(1)
|
240
|
-
vertices.uniq!
|
241
|
-
vertices
|
242
|
-
end
|
243
|
-
|
244
|
-
# @return [String] a string suitable for debugging
|
245
|
-
def inspect
|
246
|
-
"#{self.class}:#{name}(#{payload.inspect})"
|
247
|
-
end
|
248
|
-
|
249
|
-
# @return [Boolean] whether the two vertices are equal, determined
|
250
|
-
# by a recursive traversal of each {Vertex#successors}
|
251
|
-
def ==(other)
|
252
|
-
shallow_eql?(other) &&
|
253
|
-
successors.to_set == other.successors.to_set
|
254
|
-
end
|
255
|
-
|
256
|
-
# @param [Vertex] other the other vertex to compare to
|
257
|
-
# @return [Boolean] whether the two vertices are equal, determined
|
258
|
-
# solely by {#name} and {#payload} equality
|
259
|
-
def shallow_eql?(other)
|
260
|
-
other &&
|
261
|
-
name == other.name &&
|
262
|
-
payload == other.payload
|
263
|
-
end
|
264
|
-
|
265
|
-
alias eql? ==
|
266
|
-
|
267
|
-
# @return [Fixnum] a hash for the vertex based upon its {#name}
|
268
|
-
def hash
|
269
|
-
name.hash
|
270
|
-
end
|
271
|
-
|
272
|
-
# Is there a path from `self` to `other` following edges in the
|
273
|
-
# dependency graph?
|
274
|
-
# @return true iff there is a path following edges within this {#graph}
|
275
|
-
def path_to?(other)
|
276
|
-
equal?(other) || successors.any? { |v| v.path_to?(other) }
|
277
|
-
end
|
278
|
-
|
279
|
-
alias descendent? path_to?
|
280
|
-
|
281
|
-
# Is there a path from `other` to `self` following edges in the
|
282
|
-
# dependency graph?
|
283
|
-
# @return true iff there is a path following edges within this {#graph}
|
284
|
-
def ancestor?(other)
|
285
|
-
other.path_to?(self)
|
286
|
-
end
|
287
|
-
|
288
|
-
alias is_reachable_from? ancestor?
|
200
|
+
log.add_edge_no_circular(self, origin.name, destination.name, requirement)
|
289
201
|
end
|
290
202
|
end
|
291
203
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Molinillo
|
3
|
+
class DependencyGraph
|
4
|
+
# An action that modifies a {DependencyGraph} that is reversible.
|
5
|
+
# @abstract
|
6
|
+
class Action
|
7
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
8
|
+
|
9
|
+
# @return [Symbol] The name of the action.
|
10
|
+
def self.name
|
11
|
+
raise 'Abstract'
|
12
|
+
end
|
13
|
+
|
14
|
+
# Performs the action on the given graph.
|
15
|
+
# @param [DependencyGraph] graph the graph to perform the action on.
|
16
|
+
# @return [Void]
|
17
|
+
def up(graph)
|
18
|
+
raise 'Abstract'
|
19
|
+
end
|
20
|
+
|
21
|
+
# Reverses the action on the given graph.
|
22
|
+
# @param [DependencyGraph] graph the graph to reverse the action on.
|
23
|
+
# @return [Void]
|
24
|
+
def down(graph)
|
25
|
+
raise 'Abstract'
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Action,Nil] The previous action
|
29
|
+
attr_accessor :previous
|
30
|
+
|
31
|
+
# @return [Action,Nil] The next action
|
32
|
+
attr_accessor :next
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'molinillo/dependency_graph/action'
|
3
|
+
module Molinillo
|
4
|
+
class DependencyGraph
|
5
|
+
# @!visibility private
|
6
|
+
# (see DependencyGraph#add_edge_no_circular)
|
7
|
+
class AddEdgeNoCircular < Action
|
8
|
+
# @!group Action
|
9
|
+
|
10
|
+
# (see Action.name)
|
11
|
+
def self.name
|
12
|
+
:add_vertex
|
13
|
+
end
|
14
|
+
|
15
|
+
# (see Action#up)
|
16
|
+
def up(graph)
|
17
|
+
edge = make_edge(graph)
|
18
|
+
edge.origin.outgoing_edges << edge
|
19
|
+
edge.destination.incoming_edges << edge
|
20
|
+
edge
|
21
|
+
end
|
22
|
+
|
23
|
+
# (see Action#down)
|
24
|
+
def down(graph)
|
25
|
+
edge = make_edge(graph)
|
26
|
+
edge.origin.outgoing_edges.delete(edge)
|
27
|
+
edge.destination.incoming_edges.delete(edge)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @!group AddEdgeNoCircular
|
31
|
+
|
32
|
+
# @return [String] the name of the origin of the edge
|
33
|
+
attr_reader :origin
|
34
|
+
|
35
|
+
# @return [String] the name of the destination of the edge
|
36
|
+
attr_reader :destination
|
37
|
+
|
38
|
+
# @return [Object] the requirement that the edge represents
|
39
|
+
attr_reader :requirement
|
40
|
+
|
41
|
+
# @param [DependencyGraph] graph the graph to find vertices from
|
42
|
+
# @return [Edge] The edge this action adds
|
43
|
+
def make_edge(graph)
|
44
|
+
Edge.new(graph.vertex_named(origin), graph.vertex_named(destination), requirement)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Initialize an action to add an edge to a dependency graph
|
48
|
+
# @param [String] origin the name of the origin of the edge
|
49
|
+
# @param [String] destination the name of the destination of the edge
|
50
|
+
# @param [Object] requirement the requirement that the edge represents
|
51
|
+
def initialize(origin, destination, requirement)
|
52
|
+
@origin = origin
|
53
|
+
@destination = destination
|
54
|
+
@requirement = requirement
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'molinillo/dependency_graph/action'
|
3
|
+
module Molinillo
|
4
|
+
class DependencyGraph
|
5
|
+
# @!visibility private
|
6
|
+
# (see DependencyGraph#add_vertex)
|
7
|
+
class AddVertex < Action # :nodoc:
|
8
|
+
# @!group Action
|
9
|
+
|
10
|
+
# (see Action.name)
|
11
|
+
def self.name
|
12
|
+
:add_vertex
|
13
|
+
end
|
14
|
+
|
15
|
+
# (see Action#up)
|
16
|
+
def up(graph)
|
17
|
+
if existing = graph.vertices[name]
|
18
|
+
@existing_payload = existing.payload
|
19
|
+
@existing_root = existing.root
|
20
|
+
end
|
21
|
+
vertex = existing || Vertex.new(name, payload)
|
22
|
+
graph.vertices[vertex.name] = vertex
|
23
|
+
vertex.payload ||= payload
|
24
|
+
vertex.root ||= root
|
25
|
+
vertex
|
26
|
+
end
|
27
|
+
|
28
|
+
# (see Action#down)
|
29
|
+
def down(graph)
|
30
|
+
if defined?(@existing_payload)
|
31
|
+
vertex = graph.vertices[name]
|
32
|
+
vertex.payload = @existing_payload
|
33
|
+
vertex.root = @existing_root
|
34
|
+
else
|
35
|
+
graph.vertices.delete(name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @!group AddVertex
|
40
|
+
|
41
|
+
# @return [String] the name of the vertex
|
42
|
+
attr_reader :name
|
43
|
+
|
44
|
+
# @return [Object] the payload for the vertex
|
45
|
+
attr_reader :payload
|
46
|
+
|
47
|
+
# @return [Boolean] whether the vertex is root or not
|
48
|
+
attr_reader :root
|
49
|
+
|
50
|
+
# Initialize an action to add a vertex to a dependency graph
|
51
|
+
# @param [String] name the name of the vertex
|
52
|
+
# @param [Object] payload the payload for the vertex
|
53
|
+
# @param [Boolean] root whether the vertex is root or not
|
54
|
+
def initialize(name, payload, root)
|
55
|
+
@name = name
|
56
|
+
@payload = payload
|
57
|
+
@root = root
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'molinillo/dependency_graph/action'
|
3
|
+
module Molinillo
|
4
|
+
class DependencyGraph
|
5
|
+
# @!visibility private
|
6
|
+
# @see DependencyGraph#detach_vertex_named
|
7
|
+
class DetachVertexNamed < Action
|
8
|
+
# @!group Action
|
9
|
+
|
10
|
+
# (see Action#name)
|
11
|
+
def self.name
|
12
|
+
:add_vertex
|
13
|
+
end
|
14
|
+
|
15
|
+
# (see Action#up)
|
16
|
+
def up(graph)
|
17
|
+
return unless @vertex = graph.vertices.delete(name)
|
18
|
+
@vertex.outgoing_edges.each do |e|
|
19
|
+
v = e.destination
|
20
|
+
v.incoming_edges.delete(e)
|
21
|
+
graph.detach_vertex_named(v.name) unless v.root? || v.predecessors.any?
|
22
|
+
end
|
23
|
+
@vertex.incoming_edges.each do |e|
|
24
|
+
v = e.origin
|
25
|
+
v.outgoing_edges.delete(e)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# (see Action#down)
|
30
|
+
def down(graph)
|
31
|
+
return unless @vertex
|
32
|
+
graph.vertices[@vertex.name] = @vertex
|
33
|
+
@vertex.outgoing_edges.each do |e|
|
34
|
+
e.destination.incoming_edges << e
|
35
|
+
end
|
36
|
+
@vertex.incoming_edges.each do |e|
|
37
|
+
e.origin.outgoing_edges << e
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @!group DetachVertexNamed
|
42
|
+
|
43
|
+
# @return [String] the name of the vertex to detach
|
44
|
+
attr_reader :name
|
45
|
+
|
46
|
+
# Initialize an action to detach a vertex from a dependency graph
|
47
|
+
# @param [String] name the name of the vertex to detach
|
48
|
+
def initialize(name)
|
49
|
+
@name = name
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'molinillo/dependency_graph/add_edge_no_circular'
|
3
|
+
require 'molinillo/dependency_graph/add_vertex'
|
4
|
+
require 'molinillo/dependency_graph/detach_vertex_named'
|
5
|
+
require 'molinillo/dependency_graph/set_payload'
|
6
|
+
require 'molinillo/dependency_graph/tag'
|
7
|
+
|
8
|
+
module Molinillo
|
9
|
+
class DependencyGraph
|
10
|
+
# A log for dependency graph actions
|
11
|
+
class Log
|
12
|
+
# Initializes an empty log
|
13
|
+
def initialize
|
14
|
+
@current_action = @first_action = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# @!macro [new] action
|
18
|
+
# {include:DependencyGraph#$0}
|
19
|
+
# @param [Graph] graph the graph to perform the action on
|
20
|
+
# @param (see DependencyGraph#$0)
|
21
|
+
# @return (see DependencyGraph#$0)
|
22
|
+
|
23
|
+
# @macro action
|
24
|
+
def tag(graph, tag)
|
25
|
+
push_action(graph, Tag.new(tag))
|
26
|
+
end
|
27
|
+
|
28
|
+
# @macro action
|
29
|
+
def add_vertex(graph, name, payload, root)
|
30
|
+
push_action(graph, AddVertex.new(name, payload, root))
|
31
|
+
end
|
32
|
+
|
33
|
+
# @macro action
|
34
|
+
def detach_vertex_named(graph, name)
|
35
|
+
push_action(graph, DetachVertexNamed.new(name))
|
36
|
+
end
|
37
|
+
|
38
|
+
# @macro action
|
39
|
+
def add_edge_no_circular(graph, origin, destination, requirement)
|
40
|
+
push_action(graph, AddEdgeNoCircular.new(origin, destination, requirement))
|
41
|
+
end
|
42
|
+
|
43
|
+
# @macro action
|
44
|
+
def set_payload(graph, name, payload)
|
45
|
+
push_action(graph, SetPayload.new(name, payload))
|
46
|
+
end
|
47
|
+
|
48
|
+
# Pops the most recent action from the log and undoes the action
|
49
|
+
# @param [DependencyGraph] graph
|
50
|
+
# @return [Action] the action that was popped off the log
|
51
|
+
def pop!(graph)
|
52
|
+
return unless action = @current_action
|
53
|
+
unless @current_action = action.previous
|
54
|
+
@first_action = nil
|
55
|
+
end
|
56
|
+
action.down(graph)
|
57
|
+
action
|
58
|
+
end
|
59
|
+
|
60
|
+
extend Enumerable
|
61
|
+
|
62
|
+
# @!visibility private
|
63
|
+
# Enumerates each action in the log
|
64
|
+
# @yield [Action]
|
65
|
+
def each
|
66
|
+
return enum_for unless block_given?
|
67
|
+
action = @first_action
|
68
|
+
loop do
|
69
|
+
break unless action
|
70
|
+
yield action
|
71
|
+
action = action.next
|
72
|
+
end
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
# @!visibility private
|
77
|
+
# Enumerates each action in the log in reverse order
|
78
|
+
# @yield [Action]
|
79
|
+
def reverse_each
|
80
|
+
return enum_for(:reverse_each) unless block_given?
|
81
|
+
action = @current_action
|
82
|
+
loop do
|
83
|
+
break unless action
|
84
|
+
yield action
|
85
|
+
action = action.previous
|
86
|
+
end
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
# @macro action
|
91
|
+
def rewind_to(graph, tag)
|
92
|
+
loop do
|
93
|
+
action = pop!(graph)
|
94
|
+
raise "No tag #{tag.inspect} found" unless action
|
95
|
+
break if action.class.name == :tag && action.tag == tag
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# Adds the given action to the log, running the action
|
102
|
+
# @param [DependencyGraph] graph
|
103
|
+
# @param [Action] action
|
104
|
+
# @return The value returned by `action.up`
|
105
|
+
def push_action(graph, action)
|
106
|
+
action.previous = @current_action
|
107
|
+
@current_action.next = action if @current_action
|
108
|
+
@current_action = action
|
109
|
+
@first_action ||= action
|
110
|
+
action.up(graph)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'molinillo/dependency_graph/action'
|
3
|
+
module Molinillo
|
4
|
+
class DependencyGraph
|
5
|
+
# @!visibility private
|
6
|
+
# @see DependencyGraph#set_payload
|
7
|
+
class SetPayload < Action # :nodoc:
|
8
|
+
# @!group Action
|
9
|
+
|
10
|
+
# (see Action.name)
|
11
|
+
def self.name
|
12
|
+
:set_payload
|
13
|
+
end
|
14
|
+
|
15
|
+
# (see Action#up)
|
16
|
+
def up(graph)
|
17
|
+
vertex = graph.vertex_named(name)
|
18
|
+
@old_payload = vertex.payload
|
19
|
+
vertex.payload = payload
|
20
|
+
end
|
21
|
+
|
22
|
+
# (see Action#down)
|
23
|
+
def down(graph)
|
24
|
+
graph.vertex_named(name).payload = @old_payload
|
25
|
+
end
|
26
|
+
|
27
|
+
# @!group SetPayload
|
28
|
+
|
29
|
+
# @return [String] the name of the vertex
|
30
|
+
attr_reader :name
|
31
|
+
|
32
|
+
# @return [Object] the payload for the vertex
|
33
|
+
attr_reader :payload
|
34
|
+
|
35
|
+
# Initialize an action to add set the payload for a vertex in a dependency
|
36
|
+
# graph
|
37
|
+
# @param [String] name the name of the vertex
|
38
|
+
# @param [Object] payload the payload for the vertex
|
39
|
+
def initialize(name, payload)
|
40
|
+
@name = name
|
41
|
+
@payload = payload
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'molinillo/dependency_graph/action'
|
3
|
+
module Molinillo
|
4
|
+
class DependencyGraph
|
5
|
+
# @!visibility private
|
6
|
+
# @see DependencyGraph#tag
|
7
|
+
class Tag < Action
|
8
|
+
# @!group Action
|
9
|
+
|
10
|
+
# (see Action.name)
|
11
|
+
def self.name
|
12
|
+
:tag
|
13
|
+
end
|
14
|
+
|
15
|
+
# (see Action#up)
|
16
|
+
def up(_graph)
|
17
|
+
end
|
18
|
+
|
19
|
+
# (see Action#down)
|
20
|
+
def down(_graph)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @!group Tag
|
24
|
+
|
25
|
+
# @return [Object] An opaque tag
|
26
|
+
attr_reader :tag
|
27
|
+
|
28
|
+
# Initialize an action to tag a state of a dependency graph
|
29
|
+
# @param [Object] tag an opaque tag
|
30
|
+
def initialize(tag)
|
31
|
+
@tag = tag
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Molinillo
|
3
|
+
class DependencyGraph
|
4
|
+
# A vertex in a {DependencyGraph} that encapsulates a {#name} and a
|
5
|
+
# {#payload}
|
6
|
+
class Vertex
|
7
|
+
# @return [String] the name of the vertex
|
8
|
+
attr_accessor :name
|
9
|
+
|
10
|
+
# @return [Object] the payload the vertex holds
|
11
|
+
attr_accessor :payload
|
12
|
+
|
13
|
+
# @return [Arrary<Object>] the explicit requirements that required
|
14
|
+
# this vertex
|
15
|
+
attr_reader :explicit_requirements
|
16
|
+
|
17
|
+
# @return [Boolean] whether the vertex is considered a root vertex
|
18
|
+
attr_accessor :root
|
19
|
+
alias root? root
|
20
|
+
|
21
|
+
# Initializes a vertex with the given name and payload.
|
22
|
+
# @param [String] name see {#name}
|
23
|
+
# @param [Object] payload see {#payload}
|
24
|
+
def initialize(name, payload)
|
25
|
+
@name = name.frozen? ? name : name.dup.freeze
|
26
|
+
@payload = payload
|
27
|
+
@explicit_requirements = []
|
28
|
+
@outgoing_edges = []
|
29
|
+
@incoming_edges = []
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Array<Object>] all of the requirements that required
|
33
|
+
# this vertex
|
34
|
+
def requirements
|
35
|
+
incoming_edges.map(&:requirement) + explicit_requirements
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Array<Edge>] the edges of {#graph} that have `self` as their
|
39
|
+
# {Edge#origin}
|
40
|
+
attr_accessor :outgoing_edges
|
41
|
+
|
42
|
+
# @return [Array<Edge>] the edges of {#graph} that have `self` as their
|
43
|
+
# {Edge#destination}
|
44
|
+
attr_accessor :incoming_edges
|
45
|
+
|
46
|
+
# @return [Array<Vertex>] the vertices of {#graph} that have an edge with
|
47
|
+
# `self` as their {Edge#destination}
|
48
|
+
def predecessors
|
49
|
+
incoming_edges.map(&:origin)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Array<Vertex>] the vertices of {#graph} where `self` is a
|
53
|
+
# {#descendent?}
|
54
|
+
def recursive_predecessors
|
55
|
+
vertices = predecessors
|
56
|
+
vertices += vertices.map(&:recursive_predecessors).flatten(1)
|
57
|
+
vertices.uniq!
|
58
|
+
vertices
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Array<Vertex>] the vertices of {#graph} that have an edge with
|
62
|
+
# `self` as their {Edge#origin}
|
63
|
+
def successors
|
64
|
+
outgoing_edges.map(&:destination)
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Array<Vertex>] the vertices of {#graph} where `self` is an
|
68
|
+
# {#ancestor?}
|
69
|
+
def recursive_successors
|
70
|
+
vertices = successors
|
71
|
+
vertices += vertices.map(&:recursive_successors).flatten(1)
|
72
|
+
vertices.uniq!
|
73
|
+
vertices
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [String] a string suitable for debugging
|
77
|
+
def inspect
|
78
|
+
"#{self.class}:#{name}(#{payload.inspect})"
|
79
|
+
end
|
80
|
+
|
81
|
+
# @return [Boolean] whether the two vertices are equal, determined
|
82
|
+
# by a recursive traversal of each {Vertex#successors}
|
83
|
+
def ==(other)
|
84
|
+
shallow_eql?(other) &&
|
85
|
+
successors.to_set == other.successors.to_set
|
86
|
+
end
|
87
|
+
|
88
|
+
# @param [Vertex] other the other vertex to compare to
|
89
|
+
# @return [Boolean] whether the two vertices are equal, determined
|
90
|
+
# solely by {#name} and {#payload} equality
|
91
|
+
def shallow_eql?(other)
|
92
|
+
other &&
|
93
|
+
name == other.name &&
|
94
|
+
payload == other.payload
|
95
|
+
end
|
96
|
+
|
97
|
+
alias eql? ==
|
98
|
+
|
99
|
+
# @return [Fixnum] a hash for the vertex based upon its {#name}
|
100
|
+
def hash
|
101
|
+
name.hash
|
102
|
+
end
|
103
|
+
|
104
|
+
# Is there a path from `self` to `other` following edges in the
|
105
|
+
# dependency graph?
|
106
|
+
# @return true iff there is a path following edges within this {#graph}
|
107
|
+
def path_to?(other)
|
108
|
+
equal?(other) || successors.any? { |v| v.path_to?(other) }
|
109
|
+
end
|
110
|
+
|
111
|
+
alias descendent? path_to?
|
112
|
+
|
113
|
+
# Is there a path from `other` to `self` following edges in the
|
114
|
+
# dependency graph?
|
115
|
+
# @return true iff there is a path following edges within this {#graph}
|
116
|
+
def ancestor?(other)
|
117
|
+
other.path_to?(self)
|
118
|
+
end
|
119
|
+
|
120
|
+
alias is_reachable_from? ancestor?
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/molinillo/resolution.rb
CHANGED
@@ -52,6 +52,7 @@ module Molinillo
|
|
52
52
|
@base = base
|
53
53
|
@states = []
|
54
54
|
@iteration_counter = 0
|
55
|
+
@parent_of = {}
|
55
56
|
end
|
56
57
|
|
57
58
|
# Resolves the {#original_requested} dependencies into a full dependency
|
@@ -67,7 +68,12 @@ module Molinillo
|
|
67
68
|
indicate_progress
|
68
69
|
if state.respond_to?(:pop_possibility_state) # DependencyState
|
69
70
|
debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" }
|
70
|
-
state.pop_possibility_state.tap
|
71
|
+
state.pop_possibility_state.tap do |s|
|
72
|
+
if s
|
73
|
+
states.push(s)
|
74
|
+
activated.tag(s)
|
75
|
+
end
|
76
|
+
end
|
71
77
|
end
|
72
78
|
process_topmost_state
|
73
79
|
end
|
@@ -118,27 +124,11 @@ module Molinillo
|
|
118
124
|
require 'molinillo/state'
|
119
125
|
require 'molinillo/modules/specification_provider'
|
120
126
|
|
121
|
-
|
122
|
-
|
123
|
-
current_state = state || ResolutionState.empty
|
124
|
-
current_state.send(member, *args, &block)
|
125
|
-
end
|
126
|
-
end
|
127
|
+
require 'molinillo/delegates/resolution_state'
|
128
|
+
require 'molinillo/delegates/specification_provider'
|
127
129
|
|
128
|
-
|
129
|
-
|
130
|
-
begin
|
131
|
-
specification_provider.send(instance_method, *args, &block)
|
132
|
-
rescue NoSuchDependencyError => error
|
133
|
-
if state
|
134
|
-
vertex = activated.vertex_named(name_for(error.dependency))
|
135
|
-
error.required_by += vertex.incoming_edges.map { |e| e.origin.name }
|
136
|
-
error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty?
|
137
|
-
end
|
138
|
-
raise
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
130
|
+
include Molinillo::Delegates::ResolutionState
|
131
|
+
include Molinillo::Delegates::SpecificationProvider
|
142
132
|
|
143
133
|
# Processes the topmost available {RequirementState} on the stack
|
144
134
|
# @return [void]
|
@@ -169,6 +159,7 @@ module Molinillo
|
|
169
159
|
def initial_state
|
170
160
|
graph = DependencyGraph.new.tap do |dg|
|
171
161
|
original_requested.each { |r| dg.add_vertex(name_for(r), nil, true).tap { |v| v.explicit_requirements << r } }
|
162
|
+
dg.tag(:initial_state)
|
172
163
|
end
|
173
164
|
|
174
165
|
requirements = sort_dependencies(original_requested, graph, {})
|
@@ -189,8 +180,9 @@ module Molinillo
|
|
189
180
|
def unwind_for_conflict
|
190
181
|
debug(depth) { "Unwinding for conflict: #{requirement}" }
|
191
182
|
conflicts.tap do |c|
|
192
|
-
states.slice!((state_index_for_unwind + 1)..-1)
|
183
|
+
sliced_states = states.slice!((state_index_for_unwind + 1)..-1)
|
193
184
|
raise VersionConflict.new(c) unless state
|
185
|
+
activated.rewind_to(sliced_states.first || :initial_state) if sliced_states
|
194
186
|
state.conflicts = c
|
195
187
|
end
|
196
188
|
end
|
@@ -217,20 +209,14 @@ module Molinillo
|
|
217
209
|
# @return [Object] the requirement that led to `requirement` being added
|
218
210
|
# to the list of requirements.
|
219
211
|
def parent_of(requirement)
|
220
|
-
|
221
|
-
seen = false
|
222
|
-
state = states.reverse_each.find do |s|
|
223
|
-
seen ||= s.requirement == requirement || s.requirements.include?(requirement)
|
224
|
-
seen && s.requirement != requirement && !s.requirements.include?(requirement)
|
225
|
-
end
|
226
|
-
state && state.requirement
|
212
|
+
@parent_of[requirement]
|
227
213
|
end
|
228
214
|
|
229
215
|
# @return [Object] the requirement that led to a version of a possibility
|
230
216
|
# with the given name being activated.
|
231
217
|
def requirement_for_existing_name(name)
|
232
218
|
return nil unless activated.vertex_named(name).payload
|
233
|
-
states.
|
219
|
+
states.find { |s| s.name == name }.requirement
|
234
220
|
end
|
235
221
|
|
236
222
|
# @return [ResolutionState] the state whose `requirement` is the given
|
@@ -250,19 +236,25 @@ module Molinillo
|
|
250
236
|
# the {#possibility} in conjunction with the current {#state}
|
251
237
|
def create_conflict
|
252
238
|
vertex = activated.vertex_named(name)
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
239
|
+
locked_requirement = locked_requirement_named(name)
|
240
|
+
|
241
|
+
requirements = {}
|
242
|
+
unless vertex.explicit_requirements.empty?
|
243
|
+
requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements
|
244
|
+
end
|
245
|
+
requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement
|
257
246
|
vertex.incoming_edges.each { |edge| (requirements[edge.origin.payload] ||= []).unshift(edge.requirement) }
|
247
|
+
|
248
|
+
activated_by_name = {}
|
249
|
+
activated.each { |v| activated_by_name[v.name] = v.payload if v.payload }
|
258
250
|
conflicts[name] = Conflict.new(
|
259
251
|
requirement,
|
260
|
-
|
252
|
+
requirements,
|
261
253
|
vertex.payload,
|
262
254
|
possibility,
|
263
|
-
|
255
|
+
locked_requirement,
|
264
256
|
requirement_trees,
|
265
|
-
|
257
|
+
activated_by_name
|
266
258
|
)
|
267
259
|
end
|
268
260
|
|
@@ -341,15 +333,16 @@ module Molinillo
|
|
341
333
|
# spec with the given name
|
342
334
|
# @return [Boolean] Whether the possibility was swapped into {#activated}
|
343
335
|
def attempt_to_swap_possibility
|
344
|
-
|
345
|
-
vertex =
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
336
|
+
activated.tag(:swap)
|
337
|
+
vertex = activated.vertex_named(name)
|
338
|
+
activated.set_payload(name, possibility)
|
339
|
+
if !vertex.requirements.
|
340
|
+
all? { |r| requirement_satisfied_by?(r, activated, possibility) } ||
|
341
|
+
!new_spec_satisfied?
|
342
|
+
activated.rewind_to(:swap)
|
343
|
+
return
|
344
|
+
end
|
345
|
+
fixup_swapped_children(vertex)
|
353
346
|
activate_spec
|
354
347
|
end
|
355
348
|
|
@@ -412,8 +405,7 @@ module Molinillo
|
|
412
405
|
def activate_spec
|
413
406
|
conflicts.delete(name)
|
414
407
|
debug(depth) { 'Activated ' + name + ' at ' + possibility.to_s }
|
415
|
-
|
416
|
-
vertex.payload = possibility
|
408
|
+
activated.set_payload(name, possibility)
|
417
409
|
require_nested_dependencies_for(possibility)
|
418
410
|
end
|
419
411
|
|
@@ -424,7 +416,10 @@ module Molinillo
|
|
424
416
|
def require_nested_dependencies_for(activated_spec)
|
425
417
|
nested_dependencies = dependencies_for(activated_spec)
|
426
418
|
debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" }
|
427
|
-
nested_dependencies.each
|
419
|
+
nested_dependencies.each do |d|
|
420
|
+
activated.add_child_vertex(name_for(d), nil, [name_for(activated_spec)], d)
|
421
|
+
@parent_of[d] = requirement
|
422
|
+
end
|
428
423
|
|
429
424
|
push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?)
|
430
425
|
end
|
@@ -436,7 +431,7 @@ module Molinillo
|
|
436
431
|
def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated)
|
437
432
|
new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort
|
438
433
|
new_requirement = new_requirements.shift
|
439
|
-
new_name = new_requirement ? name_for(new_requirement) : ''
|
434
|
+
new_name = new_requirement ? name_for(new_requirement) : ''.freeze
|
440
435
|
possibilities = new_requirement ? search_for(new_requirement) : []
|
441
436
|
handle_missing_or_push_dependency_state DependencyState.new(
|
442
437
|
new_name, new_requirements, new_activated,
|
@@ -457,7 +452,7 @@ module Molinillo
|
|
457
452
|
state.activated.detach_vertex_named(state.name)
|
458
453
|
push_state_for_requirements(state.requirements.dup, false, state.activated)
|
459
454
|
else
|
460
|
-
states.push state
|
455
|
+
states.push(state).tap { activated.tag(state) }
|
461
456
|
end
|
462
457
|
end
|
463
458
|
end
|
data/lib/molinillo/state.rb
CHANGED
@@ -36,12 +36,14 @@ module Molinillo
|
|
36
36
|
PossibilityState.new(
|
37
37
|
name,
|
38
38
|
requirements.dup,
|
39
|
-
activated
|
39
|
+
activated,
|
40
40
|
requirement,
|
41
41
|
[possibilities.pop],
|
42
42
|
depth + 1,
|
43
43
|
conflicts.dup
|
44
|
-
)
|
44
|
+
).tap do |state|
|
45
|
+
state.activated.tag(state)
|
46
|
+
end
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Molinillo::DependencyGraph::Log do
|
5
|
+
shared_examples_for 'replay' do |steps|
|
6
|
+
it 'replays the log' do
|
7
|
+
copy = Molinillo::DependencyGraph.new
|
8
|
+
graph = Molinillo::DependencyGraph.new.tap { |g| steps.each { |s| s.call(g) } }
|
9
|
+
graph.log.each { |a| a.up(copy) }
|
10
|
+
expect(copy).to eq(graph)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'can undo to an empty graph' do
|
14
|
+
graph = Molinillo::DependencyGraph.new
|
15
|
+
graph.tag(self)
|
16
|
+
steps.each { |s| s.call(graph) }
|
17
|
+
graph.log.rewind_to(graph, self)
|
18
|
+
expect(graph).to eq(Molinillo::DependencyGraph.new)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it_behaves_like 'replay', []
|
23
|
+
it_behaves_like 'replay', [
|
24
|
+
proc { |g| g.add_child_vertex('Foo', 1, [nil], 4) },
|
25
|
+
proc { |g| g.add_child_vertex('Bar', 2, ['Foo', nil], 3) },
|
26
|
+
proc { |g| g.add_child_vertex('Baz', 3, %w(Foo Bar), 2) },
|
27
|
+
proc { |g| g.add_child_vertex('Foo', 4, [], 1) },
|
28
|
+
]
|
29
|
+
end
|
data/spec/errors_spec.rb
CHANGED
data/spec/resolver_spec.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
data/spec/spec_helper/index.rb
CHANGED
data/spec/spec_helper/ui.rb
CHANGED
data/spec/state_spec.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: molinillo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel E. Giddins
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-06-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -48,7 +48,17 @@ files:
|
|
48
48
|
- LICENSE
|
49
49
|
- README.md
|
50
50
|
- lib/molinillo.rb
|
51
|
+
- lib/molinillo/delegates/resolution_state.rb
|
52
|
+
- lib/molinillo/delegates/specification_provider.rb
|
51
53
|
- lib/molinillo/dependency_graph.rb
|
54
|
+
- lib/molinillo/dependency_graph/action.rb
|
55
|
+
- lib/molinillo/dependency_graph/add_edge_no_circular.rb
|
56
|
+
- lib/molinillo/dependency_graph/add_vertex.rb
|
57
|
+
- lib/molinillo/dependency_graph/detach_vertex_named.rb
|
58
|
+
- lib/molinillo/dependency_graph/log.rb
|
59
|
+
- lib/molinillo/dependency_graph/set_payload.rb
|
60
|
+
- lib/molinillo/dependency_graph/tag.rb
|
61
|
+
- lib/molinillo/dependency_graph/vertex.rb
|
52
62
|
- lib/molinillo/errors.rb
|
53
63
|
- lib/molinillo/gem_metadata.rb
|
54
64
|
- lib/molinillo/modules/specification_provider.rb
|
@@ -56,6 +66,7 @@ files:
|
|
56
66
|
- lib/molinillo/resolution.rb
|
57
67
|
- lib/molinillo/resolver.rb
|
58
68
|
- lib/molinillo/state.rb
|
69
|
+
- spec/dependency_graph/log_spec.rb
|
59
70
|
- spec/dependency_graph_spec.rb
|
60
71
|
- spec/errors_spec.rb
|
61
72
|
- spec/resolver_integration_specs/index_from_rubygems.rb
|
@@ -85,11 +96,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
85
96
|
version: '0'
|
86
97
|
requirements: []
|
87
98
|
rubyforge_project:
|
88
|
-
rubygems_version: 2.6.
|
99
|
+
rubygems_version: 2.6.4
|
89
100
|
signing_key:
|
90
101
|
specification_version: 4
|
91
102
|
summary: Provides support for dependency resolution
|
92
103
|
test_files:
|
104
|
+
- spec/dependency_graph/log_spec.rb
|
93
105
|
- spec/dependency_graph_spec.rb
|
94
106
|
- spec/errors_spec.rb
|
95
107
|
- spec/resolver_integration_specs/index_from_rubygems.rb
|