doubleshot 0.2.0-java → 0.3.0-java
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/Doubleshot +16 -6
- data/README-OLD.textile +216 -0
- data/README.textile +38 -182
- data/lib/doubleshot.rb +100 -39
- data/lib/doubleshot/commands/gem.rb +15 -12
- data/lib/doubleshot/commands/test.rb +38 -5
- data/lib/doubleshot/configuration.rb +2 -2
- data/lib/doubleshot/dependencies/dependency.rb +1 -1
- data/lib/doubleshot/dependencies/gem_dependency.rb +2 -10
- data/lib/doubleshot/dependencies/jar_dependency.rb +12 -2
- data/lib/doubleshot/lockfile.rb +9 -6
- data/lib/doubleshot/pom.rb +15 -2
- data/lib/doubleshot/resolver.rb +1 -0
- data/lib/doubleshot/resolver/gem_resolver.rb +45 -0
- data/lib/doubleshot/resolver/gem_resolver/artifact.rb +146 -0
- data/lib/doubleshot/resolver/gem_resolver/demand.rb +57 -0
- data/lib/doubleshot/resolver/gem_resolver/dependency.rb +57 -0
- data/lib/doubleshot/resolver/gem_resolver/errors.rb +37 -0
- data/lib/doubleshot/resolver/gem_resolver/gem_source.rb +58 -0
- data/lib/doubleshot/resolver/gem_resolver/graph.rb +200 -0
- data/lib/doubleshot/resolver/gem_resolver/solver.rb +279 -0
- data/lib/doubleshot/resolver/gem_resolver/solver/constraint_row.rb +29 -0
- data/lib/doubleshot/resolver/gem_resolver/solver/constraint_table.rb +35 -0
- data/lib/doubleshot/resolver/gem_resolver/solver/variable_row.rb +47 -0
- data/lib/doubleshot/resolver/gem_resolver/solver/variable_table.rb +59 -0
- data/lib/doubleshot/resolver/gem_resolver/source.rb +36 -0
- data/lib/doubleshot/resolver/jar_resolver.rb +1 -3
- data/lib/ruby/gem/requirement.rb +9 -0
- data/target/doubleshot.jar +0 -0
- data/test/compiler_spec.rb +31 -3
- data/test/configuration_spec.rb +11 -3
- data/test/dependencies/gem_dependency_spec.rb +3 -17
- data/test/dependencies/jar_dependency_spec.rb +20 -0
- data/test/helper.rb +3 -1
- data/test/helpers/stub_source.rb +120 -0
- data/test/lockfile_spec.rb +9 -17
- data/test/pom_spec.rb +31 -1
- data/test/resolver/gem_resolver/artifact_spec.rb +106 -0
- data/test/resolver/gem_resolver/demand_spec.rb +70 -0
- data/test/resolver/gem_resolver/dependency_spec.rb +33 -0
- data/test/resolver/gem_resolver/gem_source_spec.rb +28 -0
- data/test/resolver/gem_resolver/graph_spec.rb +239 -0
- data/test/resolver/gem_resolver/solver_spec.rb +449 -0
- data/test/resolver/gem_resolver/source_spec.rb +18 -0
- data/test/resolver/gem_resolver_spec.rb +102 -0
- metadata +35 -73
- data/lib/doubleshot/jar.rb +0 -62
@@ -0,0 +1,279 @@
|
|
1
|
+
require "doubleshot/resolver/gem_resolver/solver/constraint_row"
|
2
|
+
require "doubleshot/resolver/gem_resolver/solver/constraint_table"
|
3
|
+
require "doubleshot/resolver/gem_resolver/solver/variable_row"
|
4
|
+
require "doubleshot/resolver/gem_resolver/solver/variable_table"
|
5
|
+
|
6
|
+
class Doubleshot
|
7
|
+
class Resolver
|
8
|
+
class GemResolver
|
9
|
+
class Solver
|
10
|
+
class << self
|
11
|
+
# Create a key to identify a demand on a Doubleshot::Resolver::GemResolver::Solver.
|
12
|
+
#
|
13
|
+
# @param [Doubleshot::Resolver::GemResolver::Demand] demand
|
14
|
+
#
|
15
|
+
# @raise [NoSolutionError]
|
16
|
+
#
|
17
|
+
# @return [Symbol]
|
18
|
+
def demand_key(demand)
|
19
|
+
"#{demand.name}-#{demand.constraint}".to_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns all of the versions which satisfy all of the given constraints
|
23
|
+
#
|
24
|
+
# @param [Array<Gem::Requirement>, Array<String>] constraints
|
25
|
+
# @param [Array<Gem::Version>, Array<String>] versions
|
26
|
+
#
|
27
|
+
# @return [Array<Gem::Version>]
|
28
|
+
def satisfy_all(constraints, versions)
|
29
|
+
constraints = Array(constraints).collect do |con|
|
30
|
+
con.is_a?(Gem::Requirement) ? con : Gem::Requirement.new(con.to_s)
|
31
|
+
end.uniq
|
32
|
+
|
33
|
+
versions = Array(versions).collect do |ver|
|
34
|
+
ver.is_a?(Gem::Version) ? ver : Gem::Version.new(ver.to_s)
|
35
|
+
end.uniq
|
36
|
+
|
37
|
+
versions.select do |ver|
|
38
|
+
constraints.all? { |constraint| constraint.satisfied_by?(ver) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return the best version from the given list of versions for the given list of constraints
|
43
|
+
#
|
44
|
+
# @param [Array<Gem::Requirement>, Array<String>] constraints
|
45
|
+
# @param [Array<Gem::Version>, Array<String>] versions
|
46
|
+
#
|
47
|
+
# @raise [NoSolutionError] if version matches the given constraints
|
48
|
+
#
|
49
|
+
# @return [Gem::Version]
|
50
|
+
def satisfy_best(constraints, versions)
|
51
|
+
solution = satisfy_all(constraints, versions)
|
52
|
+
|
53
|
+
if solution.empty?
|
54
|
+
raise Errors::NoSolutionError
|
55
|
+
end
|
56
|
+
|
57
|
+
solution.sort.last
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# The world as we know it
|
62
|
+
#
|
63
|
+
# @return [Doubleshot::Resolver::GemResolver::Graph]
|
64
|
+
attr_reader :graph
|
65
|
+
attr_reader :demands
|
66
|
+
attr_reader :ui
|
67
|
+
|
68
|
+
attr_reader :domain
|
69
|
+
attr_reader :variable_table
|
70
|
+
attr_reader :constraint_table
|
71
|
+
attr_reader :possible_values
|
72
|
+
|
73
|
+
# @param [Doubleshot::Resolver::GemResolver::Graph] graph
|
74
|
+
# @param [Array<String>, Array<Array<String, String>>] demands
|
75
|
+
# @param [#say] ui
|
76
|
+
def initialize(graph, demands = Array.new, ui=nil)
|
77
|
+
@graph = graph
|
78
|
+
@demands = Hash.new
|
79
|
+
@ui = ui.respond_to?(:say) ? ui : nil
|
80
|
+
|
81
|
+
@domain = Hash.new
|
82
|
+
@possible_values = Hash.new
|
83
|
+
@constraint_table = ConstraintTable.new
|
84
|
+
@variable_table = VariableTable.new
|
85
|
+
|
86
|
+
Array(demands).each do |l_demand|
|
87
|
+
demands(*l_demand)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [Hash]
|
92
|
+
def resolve
|
93
|
+
trace("Attempting to find a solution")
|
94
|
+
seed_demand_dependencies
|
95
|
+
|
96
|
+
while unbound_variable = variable_table.first_unbound
|
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}")
|
100
|
+
|
101
|
+
while possible_value = possible_values_for_unbound.shift
|
102
|
+
possible_artifact = graph.get_artifact(unbound_variable.artifact, possible_value.version)
|
103
|
+
possible_dependencies = possible_artifact.dependencies
|
104
|
+
all_ok = possible_dependencies.all? { |dependency| can_add_new_constraint?(dependency) }
|
105
|
+
if all_ok
|
106
|
+
trace("Attempting to use #{possible_artifact}")
|
107
|
+
add_dependencies(possible_dependencies, possible_artifact)
|
108
|
+
unbound_variable.bind(possible_value)
|
109
|
+
break
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
unless unbound_variable.bound?
|
114
|
+
trace("Could not find an acceptable value for #{unbound_variable.artifact}")
|
115
|
+
backtrack(unbound_variable)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
solution = {}.tap do |solution|
|
120
|
+
variable_table.rows.each do |variable|
|
121
|
+
solution[variable.artifact] = variable.value.version.to_s
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
trace("Found Solution")
|
126
|
+
trace(solution)
|
127
|
+
|
128
|
+
solution
|
129
|
+
end
|
130
|
+
|
131
|
+
# @overload demands(name, constraint)
|
132
|
+
# Return the Doubleshot::Resolver::GemResolver::Demand from the collection of demands
|
133
|
+
# with the given name and constraint.
|
134
|
+
#
|
135
|
+
# @param [#to_s]
|
136
|
+
# @param [Gem::Requirement, #to_s]
|
137
|
+
#
|
138
|
+
# @return [Doubleshot::Resolver::GemResolver::Demand]
|
139
|
+
# @overload demands(name)
|
140
|
+
# Return the Doubleshot::Resolver::GemResolver::Demand from the collection of demands
|
141
|
+
# with the given name.
|
142
|
+
#
|
143
|
+
# @param [#to_s]
|
144
|
+
#
|
145
|
+
# @return [Doubleshot::Resolver::GemResolver::Demand]
|
146
|
+
# @overload demands
|
147
|
+
# Return the collection of demands
|
148
|
+
#
|
149
|
+
# @return [Array<Doubleshot::Resolver::GemResolver::Demand>]
|
150
|
+
def demands(*args)
|
151
|
+
if args.empty?
|
152
|
+
return demand_collection
|
153
|
+
end
|
154
|
+
if args.length > 2
|
155
|
+
raise ArgumentError, "Unexpected number of arguments. You gave: #{args.length}. Expected: 2 or less."
|
156
|
+
end
|
157
|
+
|
158
|
+
name, constraint = args
|
159
|
+
constraint ||= ">= 0"
|
160
|
+
|
161
|
+
if name.nil?
|
162
|
+
raise ArgumentError, "A name must be specified. You gave: #{args}."
|
163
|
+
end
|
164
|
+
|
165
|
+
demand = Demand.new(self, name, constraint)
|
166
|
+
add_demand(demand)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Add a Doubleshot::Resolver::GemResolver::Demand to the collection of demands and
|
170
|
+
# return the added Doubleshot::Resolver::GemResolver::Demand. No change will be made
|
171
|
+
# if the demand is already a member of the collection.
|
172
|
+
#
|
173
|
+
# @param [Doubleshot::Resolver::GemResolver::Demand] demand
|
174
|
+
#
|
175
|
+
# @return [Doubleshot::Resolver::GemResolver::Demand]
|
176
|
+
def add_demand(demand)
|
177
|
+
unless has_demand?(demand)
|
178
|
+
@demands[self.class.demand_key(demand)] = demand
|
179
|
+
end
|
180
|
+
|
181
|
+
demand
|
182
|
+
end
|
183
|
+
alias_method :demand, :add_demand
|
184
|
+
|
185
|
+
# @param [Doubleshot::Resolver::GemResolver::Demand, nil] demand
|
186
|
+
def remove_demand(demand)
|
187
|
+
if has_demand?(demand)
|
188
|
+
@demands.delete(self.class.demand_key(demand))
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# @param [Doubleshot::Resolver::GemResolver::Demand] demand
|
193
|
+
#
|
194
|
+
# @return [Boolean]
|
195
|
+
def has_demand?(demand)
|
196
|
+
@demands.has_key?(self.class.demand_key(demand))
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
# @return [Array<Doubleshot::Resolver::GemResolver::Demand>]
|
202
|
+
def demand_collection
|
203
|
+
@demands.collect { |name, demand| demand }
|
204
|
+
end
|
205
|
+
|
206
|
+
def seed_demand_dependencies
|
207
|
+
add_dependencies(demands, :root)
|
208
|
+
end
|
209
|
+
|
210
|
+
def can_add_new_constraint?(dependency)
|
211
|
+
current_binding = variable_table.find_artifact(dependency.name)
|
212
|
+
#haven't seen it before, haven't bound it yet or the binding is ok
|
213
|
+
current_binding.nil? || current_binding.value.nil? || dependency.constraint.satisfied_by?(current_binding.value.version)
|
214
|
+
end
|
215
|
+
|
216
|
+
def possible_values_for(variable)
|
217
|
+
possible_values_for_variable = possible_values[variable.artifact]
|
218
|
+
if possible_values_for_variable.nil?
|
219
|
+
constraints_for_variable = constraint_table.constraints_on_artifact(variable.artifact)
|
220
|
+
all_values_for_variable = domain[variable.artifact]
|
221
|
+
possible_values_for_variable = constraints_for_variable.inject(all_values_for_variable) do |remaining_values, constraint|
|
222
|
+
remaining_values.reject { |value| !constraint.satisfied_by?(value.version) }
|
223
|
+
end
|
224
|
+
possible_values[variable.artifact] = possible_values_for_variable
|
225
|
+
end
|
226
|
+
possible_values_for_variable
|
227
|
+
end
|
228
|
+
|
229
|
+
def add_dependencies(dependencies, source)
|
230
|
+
dependencies.each do |dependency|
|
231
|
+
trace("Adding constraint #{dependency.name} #{dependency.constraint} from #{source}")
|
232
|
+
variable_table.add(dependency.name, source)
|
233
|
+
constraint_table.add(dependency, source)
|
234
|
+
dependency_domain = graph.versions(dependency.name, dependency.constraint)
|
235
|
+
domain[dependency.name] = [(domain[dependency.name] || []), dependency_domain]
|
236
|
+
.flatten
|
237
|
+
.uniq
|
238
|
+
.sort { |left, right| right.version <=> left.version }
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def reset_possible_values_for(variable)
|
243
|
+
possible_values[variable.artifact] = nil
|
244
|
+
possible_values_for(variable)
|
245
|
+
end
|
246
|
+
|
247
|
+
def backtrack(unbound_variable)
|
248
|
+
previous_variable = variable_table.before(unbound_variable.artifact)
|
249
|
+
|
250
|
+
if previous_variable.nil?
|
251
|
+
trace("Cannot backtrack any further")
|
252
|
+
raise Errors::NoSolutionError
|
253
|
+
end
|
254
|
+
|
255
|
+
trace("Unbinding #{previous_variable.artifact}")
|
256
|
+
|
257
|
+
source = previous_variable.value
|
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
|
267
|
+
previous_variable.unbind
|
268
|
+
variable_table.all_after(previous_variable.artifact).each do |variable|
|
269
|
+
new_possibles = reset_possible_values_for(variable)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def trace(message)
|
274
|
+
ui.say(message) unless ui.nil?
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
class Doubleshot
|
4
|
+
class Resolver
|
5
|
+
class GemResolver
|
6
|
+
class Solver
|
7
|
+
class ConstraintRow
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
attr_reader :source
|
11
|
+
|
12
|
+
def_delegator :dependency, :name
|
13
|
+
def_delegator :dependency, :constraint
|
14
|
+
|
15
|
+
# @param [Doubleshot::Resolver::GemResolver::Dependency] dependency
|
16
|
+
# @param [String, Symbol] source
|
17
|
+
def initialize(dependency, source)
|
18
|
+
@dependency = dependency
|
19
|
+
@source = source
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :dependency
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Doubleshot
|
2
|
+
class Resolver
|
3
|
+
class GemResolver
|
4
|
+
class Solver
|
5
|
+
class ConstraintTable
|
6
|
+
attr_reader :rows
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@rows = Array.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [Doubleshot::Resolver::GemResolver::Dependency] dependency
|
13
|
+
# @param [String, Symbol] source
|
14
|
+
#
|
15
|
+
# @return [Array<ConstraintRow>]
|
16
|
+
def add(dependency, source)
|
17
|
+
@rows << ConstraintRow.new(dependency, source)
|
18
|
+
end
|
19
|
+
|
20
|
+
def constraints_on_artifact(name)
|
21
|
+
@rows.select do |row|
|
22
|
+
row.name == name
|
23
|
+
end.map { |row| row.constraint }
|
24
|
+
end
|
25
|
+
|
26
|
+
def remove_constraints_from_source!(source)
|
27
|
+
from_source, others = @rows.partition { |row| row.source == source }
|
28
|
+
@rows = others
|
29
|
+
from_source
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Doubleshot
|
2
|
+
class Resolver
|
3
|
+
class GemResolver
|
4
|
+
class Solver
|
5
|
+
class VariableRow
|
6
|
+
attr_reader :artifact
|
7
|
+
attr_reader :value
|
8
|
+
attr_reader :sources
|
9
|
+
|
10
|
+
# @param [String] artifact
|
11
|
+
# @param [String, Symbol] source
|
12
|
+
def initialize(artifact, source)
|
13
|
+
@artifact = artifact
|
14
|
+
@value = nil
|
15
|
+
@sources = Array(source)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [String, Symbol] source
|
19
|
+
def add_source(source)
|
20
|
+
@sources << source
|
21
|
+
end
|
22
|
+
|
23
|
+
def last_source
|
24
|
+
@sources.last
|
25
|
+
end
|
26
|
+
|
27
|
+
def bind(value)
|
28
|
+
@value = value
|
29
|
+
end
|
30
|
+
|
31
|
+
def unbind
|
32
|
+
@value = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def bound?
|
36
|
+
!@value.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param [String, Symbol] source
|
40
|
+
def remove_source(source)
|
41
|
+
@sources.delete(source)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class Doubleshot
|
2
|
+
class Resolver
|
3
|
+
class GemResolver
|
4
|
+
class Solver
|
5
|
+
class VariableTable
|
6
|
+
attr_reader :rows
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@rows = Array.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [String] artifact
|
13
|
+
# @param [String] source
|
14
|
+
def add(artifact, source)
|
15
|
+
row = rows.detect { |row| row.artifact == artifact }
|
16
|
+
if row.nil?
|
17
|
+
@rows << VariableRow.new(artifact, source)
|
18
|
+
else
|
19
|
+
row.add_source(source)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def first_unbound
|
24
|
+
@rows.detect { |row| row.bound? == false }
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [String] artifact
|
28
|
+
def find_artifact(artifact)
|
29
|
+
@rows.detect { |row| row.artifact == artifact }
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param [String] source
|
33
|
+
def remove_all_with_only_this_source!(source)
|
34
|
+
with_only_this_source, others = @rows.partition { |row| row.sources == [source] }
|
35
|
+
@rows = others
|
36
|
+
with_only_this_source
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param [String] source
|
40
|
+
def all_from_source(source)
|
41
|
+
@rows.select { |row| row.sources.include?(source) }
|
42
|
+
end
|
43
|
+
|
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]
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param [String] artifact
|
51
|
+
def all_after(artifact)
|
52
|
+
artifact_index = @rows.index { |row| row.artifact == artifact }
|
53
|
+
@rows[(artifact_index+1)..-1]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|