solve 0.3.0 → 0.3.1
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.
- 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
|