solve 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/solve.rb +7 -2
- data/lib/solve/constraint.rb +1 -3
- data/lib/solve/demand.rb +7 -0
- data/lib/solve/gem_version.rb +1 -1
- data/lib/solve/graph.rb +29 -5
- data/lib/solve/solver.rb +48 -17
- data/lib/solve/solver/constraint_row.rb +13 -5
- data/lib/solve/solver/constraint_table.rb +11 -5
- data/lib/solve/solver/serializer.rb +104 -0
- data/lib/solve/solver/{variable.rb → variable_row.rb} +8 -4
- data/lib/solve/solver/variable_table.rb +18 -11
- data/solve.gemspec +5 -2
- data/spec/acceptance/solutions_spec.rb +68 -5
- data/spec/unit/solve/constraint_spec.rb +25 -0
- data/spec/unit/solve/demand_spec.rb +24 -0
- data/spec/unit/solve/graph_spec.rb +38 -0
- data/spec/unit/solve/solver/serializer_spec.rb +26 -0
- metadata +126 -29
data/lib/solve.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'json'
|
1
3
|
require 'solve/errors'
|
2
4
|
|
3
5
|
# @author Jamie Winsor <jamie@vialstudios.com>
|
@@ -15,14 +17,17 @@ module Solve
|
|
15
17
|
# requirements (demands) which must be met. Return me the best solution of
|
16
18
|
# artifacts and verisons that I should use.
|
17
19
|
#
|
20
|
+
# If a ui object is passed in, the resolution will be traced
|
21
|
+
#
|
18
22
|
# @param [Solve::Graph] graph
|
19
23
|
# @param [Array<Solve::Demand>, Array<String, String>] demands
|
24
|
+
# @param [#say, nil] ui
|
20
25
|
#
|
21
26
|
# @raise [NoSolutionError]
|
22
27
|
#
|
23
28
|
# @return [Hash]
|
24
|
-
def it!(graph, demands)
|
25
|
-
Solver.new(graph, demands).resolve
|
29
|
+
def it!(graph, demands, ui=nil)
|
30
|
+
Solver.new(graph, demands, ui).resolve
|
26
31
|
end
|
27
32
|
end
|
28
33
|
end
|
data/lib/solve/constraint.rb
CHANGED
@@ -145,9 +145,7 @@ module Solve
|
|
145
145
|
def ==(other)
|
146
146
|
other.is_a?(self.class) &&
|
147
147
|
self.operator == other.operator &&
|
148
|
-
self.
|
149
|
-
self.minor == other.minor &&
|
150
|
-
self.patch == other.patch
|
148
|
+
self.version == other.version
|
151
149
|
end
|
152
150
|
alias_method :eql?, :==
|
153
151
|
|
data/lib/solve/demand.rb
CHANGED
data/lib/solve/gem_version.rb
CHANGED
data/lib/solve/graph.rb
CHANGED
@@ -134,11 +134,35 @@ module Solve
|
|
134
134
|
!get_artifact(name, version).nil?
|
135
135
|
end
|
136
136
|
|
137
|
-
|
137
|
+
# @param [Object] other
|
138
|
+
#
|
139
|
+
# @return [Boolean]
|
140
|
+
def ==(other)
|
141
|
+
return false unless other.is_a?(self.class)
|
138
142
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
+
self_artifacts = self.artifacts
|
144
|
+
other_artifacts = other.artifacts
|
145
|
+
|
146
|
+
self_dependencies = self_artifacts.inject([]) do |list, artifact|
|
147
|
+
list << artifact.dependencies
|
148
|
+
end.flatten
|
149
|
+
|
150
|
+
other_dependencies = other_artifacts.inject([]) do |list, artifact|
|
151
|
+
list << artifact.dependencies
|
152
|
+
end.flatten
|
153
|
+
|
154
|
+
self_artifacts.size == other_artifacts.size &&
|
155
|
+
self_dependencies.size == other_dependencies.size &&
|
156
|
+
self_artifacts.all? { |artifact| other_artifacts.include?(artifact) } &&
|
157
|
+
self_dependencies.all? { |dependency| other_dependencies.include?(dependency) }
|
158
|
+
end
|
159
|
+
alias_method :eql?, :==
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
# @return [Array<Solve::Artifact>]
|
164
|
+
def artifact_collection
|
165
|
+
@artifacts.collect { |name, artifact| artifact }
|
166
|
+
end
|
143
167
|
end
|
144
168
|
end
|
data/lib/solve/solver.rb
CHANGED
@@ -2,9 +2,10 @@ module Solve
|
|
2
2
|
# @author Jamie Winsor <jamie@vialstudios.com>
|
3
3
|
class Solver
|
4
4
|
autoload :VariableTable, 'solve/solver/variable_table'
|
5
|
-
autoload :
|
5
|
+
autoload :VariableRow, 'solve/solver/variable_row'
|
6
6
|
autoload :ConstraintTable, 'solve/solver/constraint_table'
|
7
7
|
autoload :ConstraintRow, 'solve/solver/constraint_row'
|
8
|
+
autoload :Serializer, 'solve/solver/serializer'
|
8
9
|
|
9
10
|
class << self
|
10
11
|
# Create a key to identify a demand on a Solver.
|
@@ -61,6 +62,8 @@ module Solve
|
|
61
62
|
#
|
62
63
|
# @return [Solve::Graph]
|
63
64
|
attr_reader :graph
|
65
|
+
attr_reader :demands
|
66
|
+
attr_reader :ui
|
64
67
|
|
65
68
|
attr_reader :domain
|
66
69
|
attr_reader :variable_table
|
@@ -69,10 +72,13 @@ module Solve
|
|
69
72
|
|
70
73
|
# @param [Solve::Graph] graph
|
71
74
|
# @param [Array<String>, Array<Array<String, String>>] demands
|
72
|
-
|
75
|
+
# @param [#say] ui
|
76
|
+
def initialize(graph, demands = Array.new, ui=nil)
|
73
77
|
@graph = graph
|
74
|
-
@domain = Hash.new
|
75
78
|
@demands = Hash.new
|
79
|
+
@ui = ui.respond_to?(:say) ? ui : nil
|
80
|
+
|
81
|
+
@domain = Hash.new
|
76
82
|
@possible_values = Hash.new
|
77
83
|
@constraint_table = ConstraintTable.new
|
78
84
|
@variable_table = VariableTable.new
|
@@ -84,16 +90,20 @@ module Solve
|
|
84
90
|
|
85
91
|
# @return [Hash]
|
86
92
|
def resolve
|
93
|
+
trace("Attempting to find a solution")
|
87
94
|
seed_demand_dependencies
|
88
95
|
|
89
96
|
while unbound_variable = variable_table.first_unbound
|
90
97
|
possible_values_for_unbound = possible_values_for(unbound_variable)
|
98
|
+
trace("Searching for a value for #{unbound_variable.artifact}")
|
99
|
+
trace("Possible values are #{possible_values_for_unbound}")
|
91
100
|
|
92
101
|
while possible_value = possible_values_for_unbound.shift
|
93
|
-
possible_artifact = graph.get_artifact(unbound_variable.
|
102
|
+
possible_artifact = graph.get_artifact(unbound_variable.artifact, possible_value.version)
|
94
103
|
possible_dependencies = possible_artifact.dependencies
|
95
104
|
all_ok = possible_dependencies.all? { |dependency| can_add_new_constraint?(dependency) }
|
96
105
|
if all_ok
|
106
|
+
trace("Attempting to use #{possible_artifact}")
|
97
107
|
add_dependencies(possible_dependencies, possible_artifact)
|
98
108
|
unbound_variable.bind(possible_value)
|
99
109
|
break
|
@@ -101,15 +111,21 @@ module Solve
|
|
101
111
|
end
|
102
112
|
|
103
113
|
unless unbound_variable.bound?
|
114
|
+
trace("Could not find an acceptable value for #{unbound_variable.artifact}")
|
104
115
|
backtrack(unbound_variable)
|
105
116
|
end
|
106
117
|
end
|
107
118
|
|
108
|
-
{}.tap do |solution|
|
119
|
+
solution = {}.tap do |solution|
|
109
120
|
variable_table.rows.each do |variable|
|
110
|
-
solution[variable.
|
121
|
+
solution[variable.artifact] = variable.value.version.to_s
|
111
122
|
end
|
112
123
|
end
|
124
|
+
|
125
|
+
trace("Found Solution")
|
126
|
+
trace(solution)
|
127
|
+
|
128
|
+
solution
|
113
129
|
end
|
114
130
|
|
115
131
|
# @overload demands(name, constraint)
|
@@ -192,28 +208,29 @@ module Solve
|
|
192
208
|
end
|
193
209
|
|
194
210
|
def can_add_new_constraint?(dependency)
|
195
|
-
current_binding = variable_table.
|
211
|
+
current_binding = variable_table.find_artifact(dependency.name)
|
196
212
|
#haven't seen it before, haven't bound it yet or the binding is ok
|
197
213
|
current_binding.nil? || current_binding.value.nil? || dependency.constraint.satisfies?(current_binding.value.version)
|
198
214
|
end
|
199
215
|
|
200
216
|
def possible_values_for(variable)
|
201
|
-
possible_values_for_variable = possible_values[variable.
|
217
|
+
possible_values_for_variable = possible_values[variable.artifact]
|
202
218
|
if possible_values_for_variable.nil?
|
203
|
-
constraints_for_variable = constraint_table.
|
204
|
-
all_values_for_variable = domain[variable.
|
219
|
+
constraints_for_variable = constraint_table.constraints_on_artifact(variable.artifact)
|
220
|
+
all_values_for_variable = domain[variable.artifact]
|
205
221
|
possible_values_for_variable = constraints_for_variable.inject(all_values_for_variable) do |remaining_values, constraint|
|
206
222
|
remaining_values.reject { |value| !constraint.satisfies?(value.version) }
|
207
223
|
end
|
208
|
-
possible_values[variable.
|
224
|
+
possible_values[variable.artifact] = possible_values_for_variable
|
209
225
|
end
|
210
226
|
possible_values_for_variable
|
211
227
|
end
|
212
228
|
|
213
229
|
def add_dependencies(dependencies, source)
|
214
230
|
dependencies.each do |dependency|
|
231
|
+
trace("Adding constraint #{dependency.name} #{dependency.constraint} from #{source}")
|
215
232
|
variable_table.add(dependency.name, source)
|
216
|
-
constraint_table.add(dependency
|
233
|
+
constraint_table.add(dependency, source)
|
217
234
|
dependency_domain = graph.versions(dependency.name, dependency.constraint)
|
218
235
|
domain[dependency.name] = [(domain[dependency.name] || []), dependency_domain]
|
219
236
|
.flatten
|
@@ -223,24 +240,38 @@ module Solve
|
|
223
240
|
end
|
224
241
|
|
225
242
|
def reset_possible_values_for(variable)
|
226
|
-
possible_values[variable.
|
243
|
+
possible_values[variable.artifact] = nil
|
227
244
|
possible_values_for(variable)
|
228
245
|
end
|
229
246
|
|
230
247
|
def backtrack(unbound_variable)
|
231
|
-
previous_variable = variable_table.before(unbound_variable.
|
248
|
+
previous_variable = variable_table.before(unbound_variable.artifact)
|
232
249
|
|
233
250
|
if previous_variable.nil?
|
251
|
+
trace("Cannot backtrack any further")
|
234
252
|
raise Errors::NoSolutionError
|
235
253
|
end
|
236
254
|
|
255
|
+
trace("Unbinding #{previous_variable.artifact}")
|
256
|
+
|
237
257
|
source = previous_variable.value
|
238
|
-
variable_table.remove_all_with_only_this_source!(source)
|
239
|
-
|
258
|
+
removed_variables = variable_table.remove_all_with_only_this_source!(source)
|
259
|
+
removed_variables.each do |removed_variable|
|
260
|
+
possible_values[removed_variable.artifact] = nil
|
261
|
+
trace("Removed variable #{removed_variable.artifact}")
|
262
|
+
end
|
263
|
+
removed_constraints = constraint_table.remove_constraints_from_source!(source)
|
264
|
+
removed_constraints.each do |removed_constraint|
|
265
|
+
trace("Removed constraint #{removed_constraint.name} #{removed_constraint.constraint}")
|
266
|
+
end
|
240
267
|
previous_variable.unbind
|
241
|
-
variable_table.all_after(previous_variable.
|
268
|
+
variable_table.all_after(previous_variable.artifact).each do |variable|
|
242
269
|
new_possibles = reset_possible_values_for(variable)
|
243
270
|
end
|
244
271
|
end
|
272
|
+
|
273
|
+
def trace(message)
|
274
|
+
ui.say(message) unless ui.nil?
|
275
|
+
end
|
245
276
|
end
|
246
277
|
end
|
@@ -3,15 +3,23 @@ module Solve
|
|
3
3
|
# @author Andrew Garson <andrew.garson@gmail.com>
|
4
4
|
# @author Jamie Winsor <jamie@vialstudios.com>
|
5
5
|
class ConstraintRow
|
6
|
-
|
7
|
-
|
6
|
+
extend Forwardable
|
7
|
+
|
8
8
|
attr_reader :source
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def_delegator :dependency, :name
|
11
|
+
def_delegator :dependency, :constraint
|
12
|
+
|
13
|
+
# @param [Solve::Dependency] dependency
|
14
|
+
# @param [String, Symbol] source
|
15
|
+
def initialize(dependency, source)
|
16
|
+
@dependency = dependency
|
13
17
|
@source = source
|
14
18
|
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :dependency
|
15
23
|
end
|
16
24
|
end
|
17
25
|
end
|
@@ -9,18 +9,24 @@ module Solve
|
|
9
9
|
@rows = Array.new
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
# @param [Solve::Dependency] dependency
|
13
|
+
# @param [String, Symbol] source
|
14
|
+
#
|
15
|
+
# @return [Array<ConstraintRow>]
|
16
|
+
def add(dependency, source)
|
17
|
+
@rows << ConstraintRow.new(dependency, source)
|
14
18
|
end
|
15
19
|
|
16
|
-
def
|
20
|
+
def constraints_on_artifact(name)
|
17
21
|
@rows.select do |row|
|
18
|
-
row.
|
22
|
+
row.name == name
|
19
23
|
end.map { |row| row.constraint }
|
20
24
|
end
|
21
25
|
|
22
26
|
def remove_constraints_from_source!(source)
|
23
|
-
@rows.
|
27
|
+
from_source, others = @rows.partition { |row| row.source == source }
|
28
|
+
@rows = others
|
29
|
+
from_source
|
24
30
|
end
|
25
31
|
end
|
26
32
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Solve
|
2
|
+
class Solver
|
3
|
+
class Serializer
|
4
|
+
# @param [Solve::Solver] solver
|
5
|
+
#
|
6
|
+
# @return [String]
|
7
|
+
def serialize(solver)
|
8
|
+
graph = solver.graph
|
9
|
+
demands = solver.demands
|
10
|
+
|
11
|
+
graph_hash = format_graph(graph)
|
12
|
+
demands_hash = format_demands(demands)
|
13
|
+
|
14
|
+
problem = graph_hash.merge(demands_hash)
|
15
|
+
problem.to_json
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [Hash, #to_s] solver a json string or a hash representing a solver
|
19
|
+
#
|
20
|
+
# @return [Solve::Solver]
|
21
|
+
def deserialize(solver)
|
22
|
+
unless solver.is_a?(Hash)
|
23
|
+
solver = JSON.parse(solver.to_s)
|
24
|
+
end
|
25
|
+
|
26
|
+
graph_spec = solver["graph"]
|
27
|
+
demands_spec = solver["demands"]
|
28
|
+
|
29
|
+
graph = load_graph(graph_spec)
|
30
|
+
demands = load_demands(demands_spec)
|
31
|
+
|
32
|
+
Solve::Solver.new(graph, demands)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def format_graph(graph)
|
38
|
+
artifacts = graph.artifacts.inject([]) do |list, artifact|
|
39
|
+
list << format_artifact(artifact)
|
40
|
+
end
|
41
|
+
{ "graph" => artifacts }
|
42
|
+
end
|
43
|
+
|
44
|
+
def format_artifact(artifact)
|
45
|
+
dependencies = artifact.dependencies.inject([]) do |list, dependency|
|
46
|
+
list << format_dependency(dependency)
|
47
|
+
end
|
48
|
+
|
49
|
+
{
|
50
|
+
"name" => artifact.name,
|
51
|
+
"version" => artifact.version.to_s,
|
52
|
+
"dependencies" => dependencies
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def format_dependency(dependency)
|
57
|
+
{
|
58
|
+
"name" => dependency.name,
|
59
|
+
"constraint" => dependency.constraint.to_s
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def format_demands(demands)
|
64
|
+
demands_list = demands.inject([]) do |list, demand|
|
65
|
+
list << format_demand(demand)
|
66
|
+
end
|
67
|
+
{ "demands" => demands_list }
|
68
|
+
end
|
69
|
+
|
70
|
+
def format_demand(demand)
|
71
|
+
{
|
72
|
+
"name" => demand.name,
|
73
|
+
"constraint" => demand.constraint.to_s
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def load_graph(artifacts_list)
|
78
|
+
graph = Solve::Graph.new
|
79
|
+
artifacts_list.each do |artifact_spec|
|
80
|
+
load_artifact(graph, artifact_spec)
|
81
|
+
end
|
82
|
+
graph
|
83
|
+
end
|
84
|
+
|
85
|
+
def load_artifact(graph, artifact_spec)
|
86
|
+
artifact = graph.artifacts(artifact_spec["name"], artifact_spec["version"])
|
87
|
+
artifact_spec["dependencies"].each do |dependency_spec|
|
88
|
+
load_dependency(artifact, dependency_spec)
|
89
|
+
end
|
90
|
+
artifact
|
91
|
+
end
|
92
|
+
|
93
|
+
def load_dependency(artifact, dependency_spec)
|
94
|
+
artifact.depends(dependency_spec["name"], dependency_spec["constraint"])
|
95
|
+
end
|
96
|
+
|
97
|
+
def load_demands(demand_specs)
|
98
|
+
demand_specs.inject([]) do |list, demand_spec|
|
99
|
+
list << [demand_spec["name"], demand_spec["constraint"]]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -2,17 +2,20 @@ module Solve
|
|
2
2
|
class Solver
|
3
3
|
# @author Andrew Garson <andrew.garson@gmail.com>
|
4
4
|
# @author Jamie Winsor <jamie@vialstudios.com>
|
5
|
-
class
|
6
|
-
attr_reader :
|
5
|
+
class VariableRow
|
6
|
+
attr_reader :artifact
|
7
7
|
attr_reader :value
|
8
8
|
attr_reader :sources
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
# @param [String] artifact
|
11
|
+
# @param [String, Symbol] source
|
12
|
+
def initialize(artifact, source)
|
13
|
+
@artifact = artifact
|
12
14
|
@value = nil
|
13
15
|
@sources = Array(source)
|
14
16
|
end
|
15
17
|
|
18
|
+
# @param [String, Symbol] source
|
16
19
|
def add_source(source)
|
17
20
|
@sources << source
|
18
21
|
end
|
@@ -33,6 +36,7 @@ module Solve
|
|
33
36
|
!@value.nil?
|
34
37
|
end
|
35
38
|
|
39
|
+
# @param [String, Symbol] source
|
36
40
|
def remove_source(source)
|
37
41
|
@sources.delete(source)
|
38
42
|
end
|
@@ -9,10 +9,12 @@ module Solve
|
|
9
9
|
@rows = Array.new
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
# @param [String] artifact
|
13
|
+
# @param [String] source
|
14
|
+
def add(artifact, source)
|
15
|
+
row = rows.detect { |row| row.artifact == artifact }
|
14
16
|
if row.nil?
|
15
|
-
@rows <<
|
17
|
+
@rows << VariableRow.new(artifact, source)
|
16
18
|
else
|
17
19
|
row.add_source(source)
|
18
20
|
end
|
@@ -22,28 +24,33 @@ module Solve
|
|
22
24
|
@rows.detect { |row| row.bound? == false }
|
23
25
|
end
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
+
# @param [String] artifact
|
28
|
+
def find_artifact(artifact)
|
29
|
+
@rows.detect { |row| row.artifact == artifact }
|
27
30
|
end
|
28
31
|
|
32
|
+
# @param [String] source
|
29
33
|
def remove_all_with_only_this_source!(source)
|
30
34
|
with_only_this_source, others = @rows.partition { |row| row.sources == [source] }
|
31
35
|
@rows = others
|
32
36
|
with_only_this_source
|
33
37
|
end
|
34
38
|
|
39
|
+
# @param [String] source
|
35
40
|
def all_from_source(source)
|
36
41
|
@rows.select { |row| row.sources.include?(source) }
|
37
42
|
end
|
38
43
|
|
39
|
-
|
40
|
-
|
41
|
-
|
44
|
+
# @param [String] artifact
|
45
|
+
def before(artifact)
|
46
|
+
artifact_index = @rows.index { |row| row.artifact == artifact }
|
47
|
+
(artifact_index == 0) ? nil : @rows[artifact_index - 1]
|
42
48
|
end
|
43
49
|
|
44
|
-
|
45
|
-
|
46
|
-
@rows
|
50
|
+
# @param [String] artifact
|
51
|
+
def all_after(artifact)
|
52
|
+
artifact_index = @rows.index { |row| row.artifact == artifact }
|
53
|
+
@rows[(artifact_index+1)..-1]
|
47
54
|
end
|
48
55
|
end
|
49
56
|
end
|
data/solve.gemspec
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
require File.expand_path('../lib/solve/gem_version', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
|
-
s.authors = ["Jamie Winsor"]
|
6
|
-
s.email = ["jamie@vialstudios.com"]
|
5
|
+
s.authors = ["Jamie Winsor", "Andrew Garson"]
|
6
|
+
s.email = ["jamie@vialstudios.com", "andrew.garson@gmail.com"]
|
7
7
|
s.description = %q{A Ruby constraint solver}
|
8
8
|
s.summary = s.description
|
9
9
|
s.homepage = "https://github.com/reset/solve"
|
@@ -16,8 +16,11 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.version = Solve::VERSION
|
17
17
|
s.required_ruby_version = ">= 1.9.1"
|
18
18
|
|
19
|
+
s.add_dependency 'json'
|
20
|
+
|
19
21
|
s.add_development_dependency 'thor', '>= 0.16.0'
|
20
22
|
s.add_development_dependency 'rake', '>= 0.9.2.2'
|
23
|
+
s.add_development_dependency 'pry'
|
21
24
|
s.add_development_dependency 'rspec'
|
22
25
|
s.add_development_dependency 'fuubar'
|
23
26
|
s.add_development_dependency 'spork'
|
@@ -51,17 +51,17 @@ describe "Solutions" do
|
|
51
51
|
graph.artifacts("B", "2.0.0").depends("D", ">= 1.3.0")
|
52
52
|
graph.artifacts("B", "2.1.0").depends("D", ">= 2.0.0")
|
53
53
|
|
54
|
-
graph.artifacts("A", "1.0.0").depends("B", ">
|
55
|
-
graph.artifacts("A", "1.0.0").depends("C", "= 2.
|
54
|
+
graph.artifacts("A", "1.0.0").depends("B", "> 2.0.0")
|
55
|
+
graph.artifacts("A", "1.0.0").depends("C", "= 2.1.0")
|
56
56
|
graph.artifacts("A", "1.0.1").depends("B", "> 1.0.0")
|
57
57
|
graph.artifacts("A", "1.0.1").depends("C", "= 2.1.0")
|
58
|
-
graph.artifacts("A", "1.0.2").depends("B", ">
|
59
|
-
graph.artifacts("A", "1.0.2").depends("C", "= 2.
|
58
|
+
graph.artifacts("A", "1.0.2").depends("B", "> 1.0.0")
|
59
|
+
graph.artifacts("A", "1.0.2").depends("C", "= 2.0.0")
|
60
60
|
|
61
61
|
result = Solve.it!(graph, [['A', '~> 1.0.0'], ['D', ">= 2.0.0"]])
|
62
62
|
|
63
63
|
|
64
|
-
result.should eql("A" => "1.0.
|
64
|
+
result.should eql("A" => "1.0.1",
|
65
65
|
"B" => "2.1.0",
|
66
66
|
"C" => "2.1.0",
|
67
67
|
"D" => "2.1.0")
|
@@ -95,4 +95,67 @@ describe "Solutions" do
|
|
95
95
|
"C" => "1.0.0")
|
96
96
|
end
|
97
97
|
|
98
|
+
it "finds the correct solution when there is a diamond shaped dependency" do
|
99
|
+
graph = Solve::Graph.new
|
100
|
+
|
101
|
+
graph.artifacts("A", "1.0.0")
|
102
|
+
.depends("B", "1.0.0")
|
103
|
+
.depends("C", "1.0.0")
|
104
|
+
graph.artifacts("B", "1.0.0")
|
105
|
+
.depends("D", "1.0.0")
|
106
|
+
graph.artifacts("C", "1.0.0")
|
107
|
+
.depends("D", "1.0.0")
|
108
|
+
graph.artifacts("D", "1.0.0")
|
109
|
+
|
110
|
+
result = Solve.it!(graph, [["A", "1.0.0"]])
|
111
|
+
|
112
|
+
result.should eql("A" => "1.0.0",
|
113
|
+
"B" => "1.0.0",
|
114
|
+
"C" => "1.0.0",
|
115
|
+
"D" => "1.0.0")
|
116
|
+
end
|
117
|
+
|
118
|
+
it "gives an empty solution when there are no demands" do
|
119
|
+
graph = Solve::Graph.new
|
120
|
+
result = Solve.it!(graph, [])
|
121
|
+
result.should eql({})
|
122
|
+
end
|
123
|
+
|
124
|
+
it "tries all combinations until it finds a solution" do
|
125
|
+
|
126
|
+
graph = Solve::Graph.new
|
127
|
+
|
128
|
+
graph.artifacts("A", "1.0.0").depends("B", "~> 1.0.0")
|
129
|
+
graph.artifacts("A", "1.0.1").depends("B", "~> 1.0.0")
|
130
|
+
graph.artifacts("A", "1.0.2").depends("B", "~> 1.0.0")
|
131
|
+
|
132
|
+
graph.artifacts("B", "1.0.0").depends("C", "~> 1.0.0")
|
133
|
+
graph.artifacts("B", "1.0.1").depends("C", "~> 1.0.0")
|
134
|
+
graph.artifacts("B", "1.0.2").depends("C", "~> 1.0.0")
|
135
|
+
|
136
|
+
graph.artifacts("C", "1.0.0").depends("D", "1.0.0")
|
137
|
+
graph.artifacts("C", "1.0.1").depends("D", "1.0.0")
|
138
|
+
graph.artifacts("C", "1.0.2").depends("D", "1.0.0")
|
139
|
+
|
140
|
+
# ensure we can't find a solution in the above
|
141
|
+
graph.artifacts("D", "1.0.0").depends("A", "< 0.0.0")
|
142
|
+
|
143
|
+
# Add a solution to the graph that should be reached only after
|
144
|
+
# all of the others have been tried
|
145
|
+
# it must be circular to ensure that no other branch can find it
|
146
|
+
graph.artifacts("A", "0.0.0").depends("B", "0.0.0")
|
147
|
+
graph.artifacts("B", "0.0.0").depends("C", "0.0.0")
|
148
|
+
graph.artifacts("C", "0.0.0").depends("D", "0.0.0")
|
149
|
+
graph.artifacts("D", "0.0.0").depends("A", "0.0.0")
|
150
|
+
|
151
|
+
demands = [["A"]]
|
152
|
+
|
153
|
+
result = Solve.it!(graph, demands)
|
154
|
+
|
155
|
+
result.should eql({ "A" => "0.0.0",
|
156
|
+
"B" => "0.0.0",
|
157
|
+
"C" => "0.0.0",
|
158
|
+
"D" => "0.0.0"})
|
159
|
+
|
160
|
+
end
|
98
161
|
end
|
@@ -204,4 +204,29 @@ describe Solve::Constraint do
|
|
204
204
|
end
|
205
205
|
end
|
206
206
|
end
|
207
|
+
|
208
|
+
describe "#eql?" do
|
209
|
+
|
210
|
+
subject { Solve::Constraint.new("= 1.0.0") }
|
211
|
+
|
212
|
+
it "returns true if the other object is a Solve::Constraint with the same operator and version" do
|
213
|
+
other = Solve::Constraint.new("= 1.0.0")
|
214
|
+
subject.should eql(other)
|
215
|
+
end
|
216
|
+
|
217
|
+
it "returns false if the other object is a Solve::Constraint with the same operator and different version" do
|
218
|
+
other = Solve::Constraint.new("= 9.9.9")
|
219
|
+
subject.should_not eql(other)
|
220
|
+
end
|
221
|
+
|
222
|
+
it "returns false if the other object is a Solve::Constraint with the same version and different operator" do
|
223
|
+
other = Solve::Constraint.new("> 1.0.0")
|
224
|
+
subject.should_not eql(other)
|
225
|
+
end
|
226
|
+
|
227
|
+
it "returns false if the other object is not a Solve::Constraint" do
|
228
|
+
other = "chicken"
|
229
|
+
subject.should_not eql(other)
|
230
|
+
end
|
231
|
+
end
|
207
232
|
end
|
@@ -63,4 +63,28 @@ describe Solve::Demand do
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
end
|
66
|
+
|
67
|
+
describe "equality" do
|
68
|
+
it "returns true when other is a Solve::Demand with the same name and constriant" do
|
69
|
+
other = Solve::Demand.new(solver, name, constraint)
|
70
|
+
|
71
|
+
subject.should eql(other)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "returns false when other isn't a Solve::Demand" do
|
75
|
+
subject.should_not eql("chicken")
|
76
|
+
end
|
77
|
+
|
78
|
+
it "returns false when other is a Solve::Demand with the same name but a different constraint" do
|
79
|
+
other = Solve::Demand.new(solver, name, "< 3.4.5")
|
80
|
+
|
81
|
+
subject.should_not eql(other)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "returns false when other is a Solve::Demand with the same constraint but a different name" do
|
85
|
+
other = Solve::Demand.new(solver, "chicken", constraint)
|
86
|
+
|
87
|
+
subject.should_not eql(other)
|
88
|
+
end
|
89
|
+
end
|
66
90
|
end
|
@@ -226,4 +226,42 @@ describe Solve::Graph do
|
|
226
226
|
subject.has_artifact?(artifact.name, artifact.version).should be_false
|
227
227
|
end
|
228
228
|
end
|
229
|
+
|
230
|
+
describe "eql?" do
|
231
|
+
subject do
|
232
|
+
graph = Solve::Graph.new
|
233
|
+
graph.artifacts("A", "1.0.0").depends("B", "1.0.0")
|
234
|
+
graph.artifacts("A", "2.0.0").depends("C", "1.0.0")
|
235
|
+
graph
|
236
|
+
end
|
237
|
+
|
238
|
+
it "returns false if other isn't a Solve::Graph" do
|
239
|
+
subject.should_not be_eql("chicken")
|
240
|
+
end
|
241
|
+
|
242
|
+
it "returns true if other is a Solve::Graph with the same artifacts and dependencies" do
|
243
|
+
other = Solve::Graph.new
|
244
|
+
other.artifacts("A", "1.0.0").depends("B", "1.0.0")
|
245
|
+
other.artifacts("A", "2.0.0").depends("C", "1.0.0")
|
246
|
+
|
247
|
+
subject.should eql(other)
|
248
|
+
end
|
249
|
+
|
250
|
+
it "returns false if the other is a Solve::Graph with the same artifacts but different dependencies" do
|
251
|
+
other = Solve::Graph.new
|
252
|
+
other.artifacts("A", "1.0.0")
|
253
|
+
other.artifacts("A", "2.0.0")
|
254
|
+
|
255
|
+
subject.should_not eql(other)
|
256
|
+
end
|
257
|
+
|
258
|
+
it "returns false if the other is a Solve::Graph with the same dependencies but different artifacts" do
|
259
|
+
other = Solve::Graph.new
|
260
|
+
other.artifacts("A", "1.0.0").depends("B", "1.0.0")
|
261
|
+
other.artifacts("A", "2.0.0").depends("C", "1.0.0")
|
262
|
+
other.artifacts("B", "1.0.0")
|
263
|
+
|
264
|
+
subject.should_not eql(other)
|
265
|
+
end
|
266
|
+
end
|
229
267
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Solve::Solver::Serializer do
|
4
|
+
it "deserializes a serialized solver to an equivalent solver" do
|
5
|
+
|
6
|
+
graph = Solve::Graph.new
|
7
|
+
|
8
|
+
graph.artifacts("A", "1.0.0").depends("B", "1.0.0")
|
9
|
+
graph.artifacts("B", "1.0.0").depends("C", "1.0.0")
|
10
|
+
graph.artifacts("C", "1.0.0")
|
11
|
+
|
12
|
+
demands = [["A", "1.0.0"]]
|
13
|
+
|
14
|
+
solver = Solve::Solver.new(graph, demands)
|
15
|
+
serializer = Solve::Solver::Serializer.new
|
16
|
+
serialized = serializer.serialize(solver)
|
17
|
+
deserialized = serializer.deserialize(serialized)
|
18
|
+
|
19
|
+
solver.graph.should eql(deserialized.graph)
|
20
|
+
solver.demands.should eql(deserialized.demands)
|
21
|
+
|
22
|
+
result = solver.resolve
|
23
|
+
deserialized_result = deserialized.resolve
|
24
|
+
result.should eql(deserialized_result)
|
25
|
+
end
|
26
|
+
end
|
metadata
CHANGED
@@ -1,19 +1,36 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solve
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Jamie Winsor
|
9
|
+
- Andrew Garson
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
|
-
date: 2012-09-
|
13
|
+
date: 2012-09-29 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: json
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
14
31
|
- !ruby/object:Gem::Dependency
|
15
32
|
name: thor
|
16
|
-
requirement:
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
17
34
|
none: false
|
18
35
|
requirements:
|
19
36
|
- - ! '>='
|
@@ -21,10 +38,15 @@ dependencies:
|
|
21
38
|
version: 0.16.0
|
22
39
|
type: :development
|
23
40
|
prerelease: false
|
24
|
-
version_requirements:
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.16.0
|
25
47
|
- !ruby/object:Gem::Dependency
|
26
48
|
name: rake
|
27
|
-
requirement:
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
28
50
|
none: false
|
29
51
|
requirements:
|
30
52
|
- - ! '>='
|
@@ -32,10 +54,31 @@ dependencies:
|
|
32
54
|
version: 0.9.2.2
|
33
55
|
type: :development
|
34
56
|
prerelease: false
|
35
|
-
version_requirements:
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 0.9.2.2
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: pry
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
type: :development
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
36
79
|
- !ruby/object:Gem::Dependency
|
37
80
|
name: rspec
|
38
|
-
requirement:
|
81
|
+
requirement: !ruby/object:Gem::Requirement
|
39
82
|
none: false
|
40
83
|
requirements:
|
41
84
|
- - ! '>='
|
@@ -43,10 +86,15 @@ dependencies:
|
|
43
86
|
version: '0'
|
44
87
|
type: :development
|
45
88
|
prerelease: false
|
46
|
-
version_requirements:
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
47
95
|
- !ruby/object:Gem::Dependency
|
48
96
|
name: fuubar
|
49
|
-
requirement:
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
50
98
|
none: false
|
51
99
|
requirements:
|
52
100
|
- - ! '>='
|
@@ -54,10 +102,15 @@ dependencies:
|
|
54
102
|
version: '0'
|
55
103
|
type: :development
|
56
104
|
prerelease: false
|
57
|
-
version_requirements:
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
58
111
|
- !ruby/object:Gem::Dependency
|
59
112
|
name: spork
|
60
|
-
requirement:
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
61
114
|
none: false
|
62
115
|
requirements:
|
63
116
|
- - ! '>='
|
@@ -65,10 +118,15 @@ dependencies:
|
|
65
118
|
version: '0'
|
66
119
|
type: :development
|
67
120
|
prerelease: false
|
68
|
-
version_requirements:
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ! '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
69
127
|
- !ruby/object:Gem::Dependency
|
70
128
|
name: yard
|
71
|
-
requirement:
|
129
|
+
requirement: !ruby/object:Gem::Requirement
|
72
130
|
none: false
|
73
131
|
requirements:
|
74
132
|
- - ! '>='
|
@@ -76,10 +134,15 @@ dependencies:
|
|
76
134
|
version: '0'
|
77
135
|
type: :development
|
78
136
|
prerelease: false
|
79
|
-
version_requirements:
|
137
|
+
version_requirements: !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
80
143
|
- !ruby/object:Gem::Dependency
|
81
144
|
name: redcarpet
|
82
|
-
requirement:
|
145
|
+
requirement: !ruby/object:Gem::Requirement
|
83
146
|
none: false
|
84
147
|
requirements:
|
85
148
|
- - ! '>='
|
@@ -87,10 +150,15 @@ dependencies:
|
|
87
150
|
version: '0'
|
88
151
|
type: :development
|
89
152
|
prerelease: false
|
90
|
-
version_requirements:
|
153
|
+
version_requirements: !ruby/object:Gem::Requirement
|
154
|
+
none: false
|
155
|
+
requirements:
|
156
|
+
- - ! '>='
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
91
159
|
- !ruby/object:Gem::Dependency
|
92
160
|
name: guard
|
93
|
-
requirement:
|
161
|
+
requirement: !ruby/object:Gem::Requirement
|
94
162
|
none: false
|
95
163
|
requirements:
|
96
164
|
- - ! '>='
|
@@ -98,10 +166,15 @@ dependencies:
|
|
98
166
|
version: '0'
|
99
167
|
type: :development
|
100
168
|
prerelease: false
|
101
|
-
version_requirements:
|
169
|
+
version_requirements: !ruby/object:Gem::Requirement
|
170
|
+
none: false
|
171
|
+
requirements:
|
172
|
+
- - ! '>='
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
102
175
|
- !ruby/object:Gem::Dependency
|
103
176
|
name: guard-rspec
|
104
|
-
requirement:
|
177
|
+
requirement: !ruby/object:Gem::Requirement
|
105
178
|
none: false
|
106
179
|
requirements:
|
107
180
|
- - ! '>='
|
@@ -109,10 +182,15 @@ dependencies:
|
|
109
182
|
version: '0'
|
110
183
|
type: :development
|
111
184
|
prerelease: false
|
112
|
-
version_requirements:
|
185
|
+
version_requirements: !ruby/object:Gem::Requirement
|
186
|
+
none: false
|
187
|
+
requirements:
|
188
|
+
- - ! '>='
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
version: '0'
|
113
191
|
- !ruby/object:Gem::Dependency
|
114
192
|
name: guard-spork
|
115
|
-
requirement:
|
193
|
+
requirement: !ruby/object:Gem::Requirement
|
116
194
|
none: false
|
117
195
|
requirements:
|
118
196
|
- - ! '>='
|
@@ -120,10 +198,15 @@ dependencies:
|
|
120
198
|
version: '0'
|
121
199
|
type: :development
|
122
200
|
prerelease: false
|
123
|
-
version_requirements:
|
201
|
+
version_requirements: !ruby/object:Gem::Requirement
|
202
|
+
none: false
|
203
|
+
requirements:
|
204
|
+
- - ! '>='
|
205
|
+
- !ruby/object:Gem::Version
|
206
|
+
version: '0'
|
124
207
|
- !ruby/object:Gem::Dependency
|
125
208
|
name: guard-yard
|
126
|
-
requirement:
|
209
|
+
requirement: !ruby/object:Gem::Requirement
|
127
210
|
none: false
|
128
211
|
requirements:
|
129
212
|
- - ! '>='
|
@@ -131,10 +214,15 @@ dependencies:
|
|
131
214
|
version: '0'
|
132
215
|
type: :development
|
133
216
|
prerelease: false
|
134
|
-
version_requirements:
|
217
|
+
version_requirements: !ruby/object:Gem::Requirement
|
218
|
+
none: false
|
219
|
+
requirements:
|
220
|
+
- - ! '>='
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
135
223
|
- !ruby/object:Gem::Dependency
|
136
224
|
name: coolline
|
137
|
-
requirement:
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
138
226
|
none: false
|
139
227
|
requirements:
|
140
228
|
- - ! '>='
|
@@ -142,10 +230,16 @@ dependencies:
|
|
142
230
|
version: '0'
|
143
231
|
type: :development
|
144
232
|
prerelease: false
|
145
|
-
version_requirements:
|
233
|
+
version_requirements: !ruby/object:Gem::Requirement
|
234
|
+
none: false
|
235
|
+
requirements:
|
236
|
+
- - ! '>='
|
237
|
+
- !ruby/object:Gem::Version
|
238
|
+
version: '0'
|
146
239
|
description: A Ruby constraint solver
|
147
240
|
email:
|
148
241
|
- jamie@vialstudios.com
|
242
|
+
- andrew.garson@gmail.com
|
149
243
|
executables: []
|
150
244
|
extensions: []
|
151
245
|
extra_rdoc_files: []
|
@@ -169,7 +263,8 @@ files:
|
|
169
263
|
- lib/solve/solver.rb
|
170
264
|
- lib/solve/solver/constraint_row.rb
|
171
265
|
- lib/solve/solver/constraint_table.rb
|
172
|
-
- lib/solve/solver/
|
266
|
+
- lib/solve/solver/serializer.rb
|
267
|
+
- lib/solve/solver/variable_row.rb
|
173
268
|
- lib/solve/solver/variable_table.rb
|
174
269
|
- lib/solve/version.rb
|
175
270
|
- solve.gemspec
|
@@ -180,6 +275,7 @@ files:
|
|
180
275
|
- spec/unit/solve/demand_spec.rb
|
181
276
|
- spec/unit/solve/dependency_spec.rb
|
182
277
|
- spec/unit/solve/graph_spec.rb
|
278
|
+
- spec/unit/solve/solver/serializer_spec.rb
|
183
279
|
- spec/unit/solve/solver_spec.rb
|
184
280
|
- spec/unit/solve/version_spec.rb
|
185
281
|
- spec/unit/solve_spec.rb
|
@@ -203,10 +299,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
203
299
|
version: '0'
|
204
300
|
segments:
|
205
301
|
- 0
|
206
|
-
hash: -
|
302
|
+
hash: -267791564304481347
|
207
303
|
requirements: []
|
208
304
|
rubyforge_project:
|
209
|
-
rubygems_version: 1.8.
|
305
|
+
rubygems_version: 1.8.23
|
210
306
|
signing_key:
|
211
307
|
specification_version: 3
|
212
308
|
summary: A Ruby constraint solver
|
@@ -218,6 +314,7 @@ test_files:
|
|
218
314
|
- spec/unit/solve/demand_spec.rb
|
219
315
|
- spec/unit/solve/dependency_spec.rb
|
220
316
|
- spec/unit/solve/graph_spec.rb
|
317
|
+
- spec/unit/solve/solver/serializer_spec.rb
|
221
318
|
- spec/unit/solve/solver_spec.rb
|
222
319
|
- spec/unit/solve/version_spec.rb
|
223
320
|
- spec/unit/solve_spec.rb
|