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.
Files changed (47) hide show
  1. data/Doubleshot +16 -6
  2. data/README-OLD.textile +216 -0
  3. data/README.textile +38 -182
  4. data/lib/doubleshot.rb +100 -39
  5. data/lib/doubleshot/commands/gem.rb +15 -12
  6. data/lib/doubleshot/commands/test.rb +38 -5
  7. data/lib/doubleshot/configuration.rb +2 -2
  8. data/lib/doubleshot/dependencies/dependency.rb +1 -1
  9. data/lib/doubleshot/dependencies/gem_dependency.rb +2 -10
  10. data/lib/doubleshot/dependencies/jar_dependency.rb +12 -2
  11. data/lib/doubleshot/lockfile.rb +9 -6
  12. data/lib/doubleshot/pom.rb +15 -2
  13. data/lib/doubleshot/resolver.rb +1 -0
  14. data/lib/doubleshot/resolver/gem_resolver.rb +45 -0
  15. data/lib/doubleshot/resolver/gem_resolver/artifact.rb +146 -0
  16. data/lib/doubleshot/resolver/gem_resolver/demand.rb +57 -0
  17. data/lib/doubleshot/resolver/gem_resolver/dependency.rb +57 -0
  18. data/lib/doubleshot/resolver/gem_resolver/errors.rb +37 -0
  19. data/lib/doubleshot/resolver/gem_resolver/gem_source.rb +58 -0
  20. data/lib/doubleshot/resolver/gem_resolver/graph.rb +200 -0
  21. data/lib/doubleshot/resolver/gem_resolver/solver.rb +279 -0
  22. data/lib/doubleshot/resolver/gem_resolver/solver/constraint_row.rb +29 -0
  23. data/lib/doubleshot/resolver/gem_resolver/solver/constraint_table.rb +35 -0
  24. data/lib/doubleshot/resolver/gem_resolver/solver/variable_row.rb +47 -0
  25. data/lib/doubleshot/resolver/gem_resolver/solver/variable_table.rb +59 -0
  26. data/lib/doubleshot/resolver/gem_resolver/source.rb +36 -0
  27. data/lib/doubleshot/resolver/jar_resolver.rb +1 -3
  28. data/lib/ruby/gem/requirement.rb +9 -0
  29. data/target/doubleshot.jar +0 -0
  30. data/test/compiler_spec.rb +31 -3
  31. data/test/configuration_spec.rb +11 -3
  32. data/test/dependencies/gem_dependency_spec.rb +3 -17
  33. data/test/dependencies/jar_dependency_spec.rb +20 -0
  34. data/test/helper.rb +3 -1
  35. data/test/helpers/stub_source.rb +120 -0
  36. data/test/lockfile_spec.rb +9 -17
  37. data/test/pom_spec.rb +31 -1
  38. data/test/resolver/gem_resolver/artifact_spec.rb +106 -0
  39. data/test/resolver/gem_resolver/demand_spec.rb +70 -0
  40. data/test/resolver/gem_resolver/dependency_spec.rb +33 -0
  41. data/test/resolver/gem_resolver/gem_source_spec.rb +28 -0
  42. data/test/resolver/gem_resolver/graph_spec.rb +239 -0
  43. data/test/resolver/gem_resolver/solver_spec.rb +449 -0
  44. data/test/resolver/gem_resolver/source_spec.rb +18 -0
  45. data/test/resolver/gem_resolver_spec.rb +102 -0
  46. metadata +35 -73
  47. 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