doubleshot 0.2.0-java → 0.3.0-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|