bundler 1.8.9 → 1.9.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +9 -7
  3. data/CHANGELOG.md +8 -33
  4. data/Rakefile +51 -8
  5. data/lib/bundler/cli/gem.rb +20 -1
  6. data/lib/bundler/cli/install.rb +1 -1
  7. data/lib/bundler/definition.rb +8 -12
  8. data/lib/bundler/dep_proxy.rb +2 -2
  9. data/lib/bundler/installer.rb +12 -18
  10. data/lib/bundler/resolver.rb +168 -383
  11. data/lib/bundler/rubygems_ext.rb +1 -1
  12. data/lib/bundler/rubygems_integration.rb +6 -14
  13. data/lib/bundler/runtime.rb +3 -0
  14. data/lib/bundler/shared_helpers.rb +12 -7
  15. data/lib/bundler/source.rb +0 -5
  16. data/lib/bundler/source/path.rb +2 -1
  17. data/lib/bundler/source/path/installer.rb +0 -2
  18. data/lib/bundler/source/rubygems.rb +9 -11
  19. data/lib/bundler/templates/newgem/Rakefile.tt +0 -1
  20. data/lib/bundler/templates/newgem/newgem.gemspec.tt +1 -1
  21. data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo.rb +5 -0
  22. data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/dependency_graph.rb +266 -0
  23. data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/errors.rb +69 -0
  24. data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/gem_metadata.rb +3 -0
  25. data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/modules/specification_provider.rb +90 -0
  26. data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/modules/ui.rb +63 -0
  27. data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/resolution.rb +412 -0
  28. data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/resolver.rb +43 -0
  29. data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/state.rb +43 -0
  30. data/lib/bundler/vendor/{thor.rb → thor-0.19.1/lib/thor.rb} +57 -53
  31. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/actions.rb +34 -34
  32. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/actions/create_file.rb +7 -7
  33. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/actions/create_link.rb +2 -2
  34. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/actions/directory.rb +11 -11
  35. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/actions/empty_directory.rb +2 -2
  36. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/actions/file_manipulation.rb +14 -14
  37. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/actions/inject_into_file.rb +24 -24
  38. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/base.rb +71 -71
  39. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/command.rb +8 -8
  40. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/core_ext/hash_with_indifferent_access.rb +2 -2
  41. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/core_ext/io_binary_read.rb +1 -1
  42. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/core_ext/ordered_hash.rb +2 -2
  43. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/error.rb +3 -3
  44. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/group.rb +27 -27
  45. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/invocation.rb +16 -11
  46. data/lib/bundler/vendor/thor-0.19.1/lib/thor/line_editor.rb +17 -0
  47. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/line_editor/basic.rb +2 -2
  48. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/line_editor/readline.rb +7 -7
  49. data/lib/bundler/vendor/thor-0.19.1/lib/thor/parser.rb +4 -0
  50. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/parser/argument.rb +7 -7
  51. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/parser/arguments.rb +10 -10
  52. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/parser/option.rb +14 -10
  53. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/parser/options.rb +12 -12
  54. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/rake_compat.rb +14 -14
  55. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/runner.rb +76 -76
  56. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/shell.rb +18 -18
  57. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/shell/basic.rb +31 -30
  58. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/shell/color.rb +10 -10
  59. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/shell/html.rb +28 -28
  60. data/lib/bundler/vendor/{thor → thor-0.19.1/lib/thor}/util.rb +61 -61
  61. data/lib/bundler/vendor/thor-0.19.1/lib/thor/version.rb +3 -0
  62. data/lib/bundler/vendored_molinillo.rb +5 -0
  63. data/lib/bundler/vendored_thor.rb +3 -6
  64. data/lib/bundler/version.rb +1 -1
  65. metadata +44 -35
  66. data/lib/bundler/vendor/.document +0 -0
  67. data/lib/bundler/vendor/thor/line_editor.rb +0 -17
  68. data/lib/bundler/vendor/thor/parser.rb +0 -4
  69. data/lib/bundler/vendor/thor/version.rb +0 -3
@@ -0,0 +1,69 @@
1
+ module Bundler::Molinillo
2
+ # An error that occurred during the resolution process
3
+ class ResolverError < StandardError; end
4
+
5
+ # An error caused by searching for a dependency that is completely unknown,
6
+ # i.e. has no versions available whatsoever.
7
+ class NoSuchDependencyError < ResolverError
8
+ # @return [Object] the dependency that could not be found
9
+ attr_accessor :dependency
10
+
11
+ # @return [Array<Object>] the specifications that depended upon {#dependency}
12
+ attr_accessor :required_by
13
+
14
+ # @param [Object] dependency @see {#dependency}
15
+ # @param [Array<Object>] required_by @see {#required_by}
16
+ def initialize(dependency, required_by = [])
17
+ @dependency = dependency
18
+ @required_by = required_by
19
+ super()
20
+ end
21
+
22
+ def message
23
+ sources = required_by.map { |r| "`#{r}`" }.join(' and ')
24
+ message = "Unable to find a specification for `#{dependency}`"
25
+ message << " depended upon by #{sources}" unless sources.empty?
26
+ message
27
+ end
28
+ end
29
+
30
+ # An error caused by attempting to fulfil a dependency that was circular
31
+ #
32
+ # @note This exception will be thrown iff a {Vertex} is added to a
33
+ # {DependencyGraph} that has a {DependencyGraph::Vertex#path_to?} an
34
+ # existing {DependencyGraph::Vertex}
35
+ class CircularDependencyError < ResolverError
36
+ # [Set<Object>] the dependencies responsible for causing the error
37
+ attr_reader :dependencies
38
+
39
+ # @param [Array<DependencyGraph::Vertex>] nodes the nodes in the dependency
40
+ # that caused the error
41
+ def initialize(nodes)
42
+ super "There is a circular dependency between #{nodes.map(&:name).join(' and ')}"
43
+ @dependencies = nodes.map(&:payload).to_set
44
+ end
45
+ end
46
+
47
+ # An error caused by conflicts in version
48
+ class VersionConflict < ResolverError
49
+ # @return [{String => Resolution::Conflict}] the conflicts that caused
50
+ # resolution to fail
51
+ attr_reader :conflicts
52
+
53
+ # @param [{String => Resolution::Conflict}] conflicts see {#conflicts}
54
+ def initialize(conflicts)
55
+ pairs = []
56
+ conflicts.values.flatten.map(&:requirements).flatten.each do |conflicting|
57
+ conflicting.each do |source, conflict_requirements|
58
+ conflict_requirements.each do |c|
59
+ pairs << [c, source]
60
+ end
61
+ end
62
+ end
63
+
64
+ super "Unable to satisfy the following requirements:\n\n" \
65
+ "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}"
66
+ @conflicts = conflicts
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module Bundler::Molinillo
2
+ VERSION = '0.2.1'
3
+ end
@@ -0,0 +1,90 @@
1
+ module Bundler::Molinillo
2
+ # Provides information about specifcations and dependencies to the resolver,
3
+ # allowing the {Resolver} class to remain generic while still providing power
4
+ # and flexibility.
5
+ #
6
+ # This module contains the methods that users of Bundler::Molinillo must to implement,
7
+ # using knowledge of their own model classes.
8
+ module SpecificationProvider
9
+ # Search for the specifications that match the given dependency.
10
+ # The specifications in the returned array will be considered in reverse
11
+ # order, so the latest version ought to be last.
12
+ # @note This method should be 'pure', i.e. the return value should depend
13
+ # only on the `dependency` parameter.
14
+ #
15
+ # @param [Object] dependency
16
+ # @return [Array<Object>] the specifications that satisfy the given
17
+ # `dependency`.
18
+ def search_for(dependency)
19
+ []
20
+ end
21
+
22
+ # Returns the dependencies of `specification`.
23
+ # @note This method should be 'pure', i.e. the return value should depend
24
+ # only on the `specification` parameter.
25
+ #
26
+ # @param [Object] specification
27
+ # @return [Array<Object>] the dependencies that are required by the given
28
+ # `specification`.
29
+ def dependencies_for(specification)
30
+ []
31
+ end
32
+
33
+ # Determines whether the given `requirement` is satisfied by the given
34
+ # `spec`, in the context of the current `activated` dependency graph.
35
+ #
36
+ # @param [Object] requirement
37
+ # @param [DependencyGraph] activated the current dependency graph in the
38
+ # resolution process.
39
+ # @param [Object] spec
40
+ # @return [Boolean] whether `requirement` is satisfied by `spec` in the
41
+ # context of the current `activated` dependency graph.
42
+ def requirement_satisfied_by?(requirement, activated, spec)
43
+ true
44
+ end
45
+
46
+ # Returns the name for the given `dependency`.
47
+ # @note This method should be 'pure', i.e. the return value should depend
48
+ # only on the `dependency` parameter.
49
+ #
50
+ # @param [Object] dependency
51
+ # @return [String] the name for the given `dependency`.
52
+ def name_for(dependency)
53
+ dependency.to_s
54
+ end
55
+
56
+ # @return [String] the name of the source of explicit dependencies, i.e.
57
+ # those passed to {Resolver#resolve} directly.
58
+ def name_for_explicit_dependency_source
59
+ 'user-specified dependency'
60
+ end
61
+
62
+ # @return [String] the name of the source of 'locked' dependencies, i.e.
63
+ # those passed to {Resolver#resolve} directly as the `base`
64
+ def name_for_locking_dependency_source
65
+ 'Lockfile'
66
+ end
67
+
68
+ # Sort dependencies so that the ones that are easiest to resolve are first.
69
+ # Easiest to resolve is (usually) defined by:
70
+ # 1) Is this dependency already activated?
71
+ # 2) How relaxed are the requirements?
72
+ # 3) Are there any conflicts for this dependency?
73
+ # 4) How many possibilities are there to satisfy this dependency?
74
+ #
75
+ # @param [Array<Object>] dependencies
76
+ # @param [DependencyGraph] activated the current dependency graph in the
77
+ # resolution process.
78
+ # @param [{String => Array<Conflict>}] conflicts
79
+ # @return [Array<Object>] a sorted copy of `dependencies`.
80
+ def sort_dependencies(dependencies, activated, conflicts)
81
+ dependencies.sort_by do |dependency|
82
+ name = name_for(dependency)
83
+ [
84
+ activated.vertex_named(name).payload ? 0 : 1,
85
+ conflicts[name] ? 0 : 1,
86
+ ]
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,63 @@
1
+ module Bundler::Molinillo
2
+ # Conveys information about the resolution process to a user.
3
+ module UI
4
+ # The {IO} object that should be used to print output. `STDOUT`, by default.
5
+ #
6
+ # @return [IO]
7
+ def output
8
+ STDOUT
9
+ end
10
+
11
+ # Called roughly every {#progress_rate}, this method should convey progress
12
+ # to the user.
13
+ #
14
+ # @return [void]
15
+ def indicate_progress
16
+ output.print '.' unless debug?
17
+ end
18
+
19
+ # How often progress should be conveyed to the user via
20
+ # {#indicate_progress}, in seconds. A third of a second, by default.
21
+ #
22
+ # @return [Float]
23
+ def progress_rate
24
+ 0.33
25
+ end
26
+
27
+ # Called before resolution begins.
28
+ #
29
+ # @return [void]
30
+ def before_resolution
31
+ output.print 'Resolving dependencies...'
32
+ end
33
+
34
+ # Called after resolution ends (either successfully or with an error).
35
+ # By default, prints a newline.
36
+ #
37
+ # @return [void]
38
+ def after_resolution
39
+ output.puts
40
+ end
41
+
42
+ # Conveys debug information to the user.
43
+ #
44
+ # @param [Integer] depth the current depth of the resolution process.
45
+ # @return [void]
46
+ def debug(depth = 0)
47
+ if debug?
48
+ debug_info = yield
49
+ debug_info = debug_info.inspect unless debug_info.is_a?(String)
50
+ output.puts debug_info.split("\n").map { |s| ' ' * depth + s }
51
+ end
52
+ end
53
+
54
+ # Whether or not debug messages should be printed.
55
+ # By default, whether or not the `MOLINILLO_DEBUG` environment variable is
56
+ # set.
57
+ #
58
+ # @return [Boolean]
59
+ def debug?
60
+ @debug_mode ||= ENV['MOLINILLO_DEBUG']
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,412 @@
1
+ module Bundler::Molinillo
2
+ class Resolver
3
+ # A specific resolution from a given {Resolver}
4
+ class Resolution
5
+ # A conflict that the resolution process encountered
6
+ # @attr [Object] requirement the requirement that immediately led to the conflict
7
+ # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict
8
+ # @attr [Object, nil] existing the existing spec that was in conflict with
9
+ # the {#possibility}
10
+ # @attr [Object] possibility the spec that was unable to be activated due
11
+ # to a conflict
12
+ # @attr [Object] locked_requirement the relevant locking requirement.
13
+ # @attr [Array<Array<Object>>] requirement_trees the different requirement
14
+ # trees that led to every requirement for the conflicting name.
15
+ Conflict = Struct.new(
16
+ :requirement,
17
+ :requirements,
18
+ :existing,
19
+ :possibility,
20
+ :locked_requirement,
21
+ :requirement_trees
22
+ )
23
+
24
+ # @return [SpecificationProvider] the provider that knows about
25
+ # dependencies, requirements, specifications, versions, etc.
26
+ attr_reader :specification_provider
27
+
28
+ # @return [UI] the UI that knows how to communicate feedback about the
29
+ # resolution process back to the user
30
+ attr_reader :resolver_ui
31
+
32
+ # @return [DependencyGraph] the base dependency graph to which
33
+ # dependencies should be 'locked'
34
+ attr_reader :base
35
+
36
+ # @return [Array] the dependencies that were explicitly required
37
+ attr_reader :original_requested
38
+
39
+ # @param [SpecificationProvider] specification_provider
40
+ # see {#specification_provider}
41
+ # @param [UI] resolver_ui see {#resolver_ui}
42
+ # @param [Array] requested see {#original_requested}
43
+ # @param [DependencyGraph] base see {#base}
44
+ def initialize(specification_provider, resolver_ui, requested, base)
45
+ @specification_provider = specification_provider
46
+ @resolver_ui = resolver_ui
47
+ @original_requested = requested
48
+ @base = base
49
+ @states = []
50
+ @iteration_counter = 0
51
+ end
52
+
53
+ # Resolves the {#original_requested} dependencies into a full dependency
54
+ # graph
55
+ # @raise [ResolverError] if successful resolution is impossible
56
+ # @return [DependencyGraph] the dependency graph of successfully resolved
57
+ # dependencies
58
+ def resolve
59
+ start_resolution
60
+
61
+ while state
62
+ break unless state.requirements.any? || state.requirement
63
+ indicate_progress
64
+ if state.respond_to?(:pop_possibility_state) # DependencyState
65
+ debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" }
66
+ state.pop_possibility_state.tap { |s| states.push(s) if s }
67
+ end
68
+ process_topmost_state
69
+ end
70
+
71
+ activated.freeze
72
+ ensure
73
+ end_resolution
74
+ end
75
+
76
+ private
77
+
78
+ # Sets up the resolution process
79
+ # @return [void]
80
+ def start_resolution
81
+ @started_at = Time.now
82
+
83
+ states.push(initial_state)
84
+
85
+ debug { "Starting resolution (#{@started_at})" }
86
+ resolver_ui.before_resolution
87
+ end
88
+
89
+ # Ends the resolution process
90
+ # @return [void]
91
+ def end_resolution
92
+ resolver_ui.after_resolution
93
+ debug do
94
+ "Finished resolution (#{@iteration_counter} steps) " \
95
+ "(Took #{(ended_at = Time.now) - @started_at} seconds) (#{ended_at})"
96
+ end
97
+ debug { 'Unactivated: ' + Hash[activated.vertices.reject { |_n, v| v.payload }].keys.join(', ') } if state
98
+ debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state
99
+ end
100
+
101
+ require 'molinillo/state'
102
+ require 'molinillo/modules/specification_provider'
103
+
104
+ # @return [Integer] the number of resolver iterations in between calls to
105
+ # {#resolver_ui}'s {UI#indicate_progress} method
106
+ attr_accessor :iteration_rate
107
+
108
+ # @return [Time] the time at which resolution began
109
+ attr_accessor :started_at
110
+
111
+ # @return [Array<ResolutionState>] the stack of states for the resolution
112
+ attr_accessor :states
113
+
114
+ ResolutionState.new.members.each do |member|
115
+ define_method member do |*args, &block|
116
+ state.send(member, *args, &block)
117
+ end
118
+ end
119
+
120
+ SpecificationProvider.instance_methods(false).each do |instance_method|
121
+ define_method instance_method do |*args, &block|
122
+ begin
123
+ specification_provider.send(instance_method, *args, &block)
124
+ rescue NoSuchDependencyError => error
125
+ if state
126
+ vertex = activated.vertex_named(name_for error.dependency)
127
+ error.required_by += vertex.incoming_edges.map { |e| e.origin.name }
128
+ error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty?
129
+ end
130
+ raise
131
+ end
132
+ end
133
+ end
134
+
135
+ # Processes the topmost available {RequirementState} on the stack
136
+ # @return [void]
137
+ def process_topmost_state
138
+ if possibility
139
+ attempt_to_activate
140
+ else
141
+ create_conflict if state.is_a? PossibilityState
142
+ unwind_for_conflict until possibility && state.is_a?(DependencyState)
143
+ end
144
+ end
145
+
146
+ # @return [Object] the current possibility that the resolution is trying
147
+ # to activate
148
+ def possibility
149
+ possibilities.last
150
+ end
151
+
152
+ # @return [RequirementState] the current state the resolution is
153
+ # operating upon
154
+ def state
155
+ states.last
156
+ end
157
+
158
+ # Creates the initial state for the resolution, based upon the
159
+ # {#requested} dependencies
160
+ # @return [DependencyState] the initial state for the resolution
161
+ def initial_state
162
+ graph = DependencyGraph.new.tap do |dg|
163
+ original_requested.each { |r| dg.add_root_vertex(name_for(r), nil).tap { |v| v.explicit_requirements << r } }
164
+ end
165
+
166
+ requirements = sort_dependencies(original_requested, graph, {})
167
+ initial_requirement = requirements.shift
168
+ DependencyState.new(
169
+ initial_requirement && name_for(initial_requirement),
170
+ requirements,
171
+ graph,
172
+ initial_requirement,
173
+ initial_requirement && search_for(initial_requirement),
174
+ 0,
175
+ {}
176
+ )
177
+ end
178
+
179
+ # Unwinds the states stack because a conflict has been encountered
180
+ # @return [void]
181
+ def unwind_for_conflict
182
+ debug(depth) { "Unwinding for conflict: #{requirement}" }
183
+ conflicts.tap do |c|
184
+ states.slice!((state_index_for_unwind + 1)..-1)
185
+ raise VersionConflict.new(c) unless state
186
+ state.conflicts = c
187
+ end
188
+ end
189
+
190
+ # @return [Integer] The index to which the resolution should unwind in the
191
+ # case of conflict.
192
+ def state_index_for_unwind
193
+ current_requirement = requirement
194
+ existing_requirement = requirement_for_existing_name(name)
195
+ until current_requirement.nil?
196
+ current_state = find_state_for(current_requirement)
197
+ return states.index(current_state) if state_any?(current_state)
198
+ current_requirement = parent_of(current_requirement)
199
+ end
200
+
201
+ until existing_requirement.nil?
202
+ existing_state = find_state_for(existing_requirement)
203
+ return states.index(existing_state) if state_any?(existing_state)
204
+ existing_requirement = parent_of(existing_requirement)
205
+ end
206
+ -1
207
+ end
208
+
209
+ # @return [Object] the requirement that led to `requirement` being added
210
+ # to the list of requirements.
211
+ def parent_of(requirement)
212
+ return nil unless requirement
213
+ seen = false
214
+ state = states.reverse_each.find do |s|
215
+ seen ||= s.requirement == requirement
216
+ seen && s.requirement != requirement && !s.requirements.include?(requirement)
217
+ end
218
+ state && state.requirement
219
+ end
220
+
221
+ # @return [Object] the requirement that led to a version of a possibility
222
+ # with the given name being activated.
223
+ def requirement_for_existing_name(name)
224
+ return nil unless activated.vertex_named(name).payload
225
+ states.reverse_each.find { |s| !s.activated.vertex_named(name).payload }.requirement
226
+ end
227
+
228
+ # @return [ResolutionState] the state whose `requirement` is the given
229
+ # `requirement`.
230
+ def find_state_for(requirement)
231
+ return nil unless requirement
232
+ states.reverse_each.find { |i| requirement == i.requirement && i.is_a?(DependencyState) }
233
+ end
234
+
235
+ # @return [Boolean] whether or not the given state has any possibilities
236
+ # left.
237
+ def state_any?(state)
238
+ state && state.possibilities.any?
239
+ end
240
+
241
+ # @return [Conflict] a {Conflict} that reflects the failure to activate
242
+ # the {#possibility} in conjunction with the current {#state}
243
+ def create_conflict
244
+ vertex = activated.vertex_named(name)
245
+ requirements = {
246
+ name_for_explicit_dependency_source => vertex.explicit_requirements,
247
+ name_for_locking_dependency_source => Array(locked_requirement_named(name)),
248
+ }
249
+ vertex.incoming_edges.each { |edge| (requirements[edge.origin.payload] ||= []).unshift(*edge.requirements) }
250
+ conflicts[name] = Conflict.new(
251
+ requirement,
252
+ Hash[requirements.select { |_, r| !r.empty? }],
253
+ vertex.payload,
254
+ possibility,
255
+ locked_requirement_named(name),
256
+ requirement_trees
257
+ )
258
+ end
259
+
260
+ # @return [Array<Array<Object>>] The different requirement
261
+ # trees that led to every requirement for the current spec.
262
+ def requirement_trees
263
+ activated.vertex_named(name).requirements.map { |r| requirement_tree_for(r) }
264
+ end
265
+
266
+ # @return [Array<Object>] the list of requirements that led to
267
+ # `requirement` being required.
268
+ def requirement_tree_for(requirement)
269
+ tree = []
270
+ while requirement
271
+ tree.unshift(requirement)
272
+ requirement = parent_of(requirement)
273
+ end
274
+ tree
275
+ end
276
+
277
+ # Indicates progress roughly once every second
278
+ # @return [void]
279
+ def indicate_progress
280
+ @iteration_counter += 1
281
+ @progress_rate ||= resolver_ui.progress_rate
282
+ if iteration_rate.nil?
283
+ if Time.now - started_at >= @progress_rate
284
+ self.iteration_rate = @iteration_counter
285
+ end
286
+ end
287
+
288
+ if iteration_rate && (@iteration_counter % iteration_rate) == 0
289
+ resolver_ui.indicate_progress
290
+ end
291
+ end
292
+
293
+ # Calls the {#resolver_ui}'s {UI#debug} method
294
+ # @param [Integer] depth the depth of the {#states} stack
295
+ # @param [Proc] block a block that yields a {#to_s}
296
+ # @return [void]
297
+ def debug(depth = 0, &block)
298
+ resolver_ui.debug(depth, &block)
299
+ end
300
+
301
+ # Attempts to activate the current {#possibility}
302
+ # @return [void]
303
+ def attempt_to_activate
304
+ debug(depth) { 'Attempting to activate ' + possibility.to_s }
305
+ existing_node = activated.vertex_named(name)
306
+ if existing_node.payload
307
+ debug(depth) { "Found existing spec (#{existing_node.payload})" }
308
+ attempt_to_activate_existing_spec(existing_node)
309
+ else
310
+ attempt_to_activate_new_spec
311
+ end
312
+ end
313
+
314
+ # Attempts to activate the current {#possibility} (given that it has
315
+ # already been activated)
316
+ # @return [void]
317
+ def attempt_to_activate_existing_spec(existing_node)
318
+ existing_spec = existing_node.payload
319
+ if requirement_satisfied_by?(requirement, activated, existing_spec)
320
+ new_requirements = requirements.dup
321
+ push_state_for_requirements(new_requirements)
322
+ else
323
+ return if attempt_to_swap_possibility
324
+ create_conflict
325
+ debug(depth) { "Unsatisfied by existing spec (#{existing_node.payload})" }
326
+ unwind_for_conflict
327
+ end
328
+ end
329
+
330
+ # Attempts to swp the current {#possibility} with the already-activated
331
+ # spec with the given name
332
+ # @return [Boolean] Whether the possibility was swapped into {#activated}
333
+ def attempt_to_swap_possibility
334
+ swapped = activated.dup
335
+ swapped.vertex_named(name).payload = possibility
336
+ return unless swapped.vertex_named(name).requirements.
337
+ all? { |r| requirement_satisfied_by?(r, swapped, possibility) }
338
+ attempt_to_activate_new_spec
339
+ end
340
+
341
+ # Attempts to activate the current {#possibility} (given that it hasn't
342
+ # already been activated)
343
+ # @return [void]
344
+ def attempt_to_activate_new_spec
345
+ satisfied = begin
346
+ locked_requirement = locked_requirement_named(name)
347
+ requested_spec_satisfied = requirement_satisfied_by?(requirement, activated, possibility)
348
+ locked_spec_satisfied = !locked_requirement ||
349
+ requirement_satisfied_by?(locked_requirement, activated, possibility)
350
+ debug(depth) { 'Unsatisfied by requested spec' } unless requested_spec_satisfied
351
+ debug(depth) { 'Unsatisfied by locked spec' } unless locked_spec_satisfied
352
+ requested_spec_satisfied && locked_spec_satisfied
353
+ end
354
+ if satisfied
355
+ activate_spec
356
+ else
357
+ create_conflict
358
+ unwind_for_conflict
359
+ end
360
+ end
361
+
362
+ # @param [String] requirement_name the spec name to search for
363
+ # @return [Object] the locked spec named `requirement_name`, if one
364
+ # is found on {#base}
365
+ def locked_requirement_named(requirement_name)
366
+ vertex = base.vertex_named(requirement_name)
367
+ vertex && vertex.payload
368
+ end
369
+
370
+ # Add the current {#possibility} to the dependency graph of the current
371
+ # {#state}
372
+ # @return [void]
373
+ def activate_spec
374
+ conflicts.delete(name)
375
+ debug(depth) { 'Activated ' + name + ' at ' + possibility.to_s }
376
+ vertex = activated.vertex_named(name)
377
+ vertex.payload = possibility
378
+ require_nested_dependencies_for(possibility)
379
+ end
380
+
381
+ # Requires the dependencies that the recently activated spec has
382
+ # @param [Object] activated_spec the specification that has just been
383
+ # activated
384
+ # @return [void]
385
+ def require_nested_dependencies_for(activated_spec)
386
+ nested_dependencies = dependencies_for(activated_spec)
387
+ debug(depth) { "Requiring nested dependencies (#{nested_dependencies.map(&:to_s).join(', ')})" }
388
+ nested_dependencies.each { |d| activated.add_child_vertex name_for(d), nil, [name_for(activated_spec)], d }
389
+
390
+ push_state_for_requirements(requirements + nested_dependencies)
391
+ end
392
+
393
+ # Pushes a new {DependencyState} that encapsulates both existing and new
394
+ # requirements
395
+ # @param [Array] new_requirements
396
+ # @return [void]
397
+ def push_state_for_requirements(new_requirements)
398
+ new_requirements = sort_dependencies(new_requirements.uniq, activated, conflicts)
399
+ new_requirement = new_requirements.shift
400
+ states.push DependencyState.new(
401
+ new_requirement ? name_for(new_requirement) : '',
402
+ new_requirements,
403
+ activated.dup,
404
+ new_requirement,
405
+ new_requirement ? search_for(new_requirement) : [],
406
+ depth,
407
+ conflicts.dup
408
+ )
409
+ end
410
+ end
411
+ end
412
+ end