rubygems-update 2.4.8 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubygems-update might be problematic. Click here for more details.

Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CODE_OF_CONDUCT.md +40 -0
  5. data/CVE-2015-3900.txt +40 -0
  6. data/History.txt +173 -2
  7. data/Manifest.txt +14 -1
  8. data/Rakefile +36 -1
  9. data/lib/rubygems.rb +32 -14
  10. data/lib/rubygems/basic_specification.rb +31 -9
  11. data/lib/rubygems/commands/dependency_command.rb +25 -15
  12. data/lib/rubygems/commands/environment_command.rb +2 -0
  13. data/lib/rubygems/commands/help_command.rb +0 -10
  14. data/lib/rubygems/commands/install_command.rb +1 -1
  15. data/lib/rubygems/commands/list_command.rb +1 -1
  16. data/lib/rubygems/commands/pristine_command.rb +11 -1
  17. data/lib/rubygems/commands/query_command.rb +1 -1
  18. data/lib/rubygems/commands/sources_command.rb +1 -1
  19. data/lib/rubygems/commands/update_command.rb +2 -2
  20. data/lib/rubygems/config_file.rb +4 -4
  21. data/lib/rubygems/core_ext/kernel_require.rb +2 -2
  22. data/lib/rubygems/dependency.rb +9 -6
  23. data/lib/rubygems/dependency_list.rb +3 -0
  24. data/lib/rubygems/ext/builder.rb +2 -0
  25. data/lib/rubygems/ext/ext_conf_builder.rb +6 -1
  26. data/lib/rubygems/indexer.rb +26 -91
  27. data/lib/rubygems/installer.rb +58 -26
  28. data/lib/rubygems/installer_test_case.rb +2 -2
  29. data/lib/rubygems/package.rb +18 -6
  30. data/lib/rubygems/package/old.rb +2 -2
  31. data/lib/rubygems/package/tar_reader/entry.rb +7 -1
  32. data/lib/rubygems/package/tar_test_case.rb +12 -3
  33. data/lib/rubygems/package/tar_writer.rb +19 -1
  34. data/lib/rubygems/platform.rb +3 -2
  35. data/lib/rubygems/rdoc.rb +1 -2
  36. data/lib/rubygems/remote_fetcher.rb +25 -6
  37. data/lib/rubygems/request/connection_pools.rb +8 -4
  38. data/lib/rubygems/request_set.rb +3 -4
  39. data/lib/rubygems/request_set/gem_dependency_api.rb +2 -2
  40. data/lib/rubygems/request_set/lockfile.rb +1 -1
  41. data/lib/rubygems/request_set/lockfile/parser.rb +54 -43
  42. data/lib/rubygems/request_set/lockfile/tokenizer.rb +16 -13
  43. data/lib/rubygems/resolver.rb +47 -242
  44. data/lib/rubygems/resolver/activation_request.rb +2 -1
  45. data/lib/rubygems/resolver/conflict.rb +0 -1
  46. data/lib/rubygems/resolver/dependency_request.rb +4 -1
  47. data/lib/rubygems/resolver/git_specification.rb +1 -2
  48. data/lib/rubygems/resolver/molinillo.rb +1 -0
  49. data/lib/rubygems/resolver/molinillo/lib/molinillo.rb +5 -0
  50. data/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb +266 -0
  51. data/lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb +69 -0
  52. data/lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb +3 -0
  53. data/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb +99 -0
  54. data/lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb +63 -0
  55. data/lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb +430 -0
  56. data/lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb +43 -0
  57. data/lib/rubygems/resolver/molinillo/lib/molinillo/state.rb +51 -0
  58. data/lib/rubygems/resolver/specification.rb +1 -1
  59. data/lib/rubygems/specification.rb +256 -86
  60. data/lib/rubygems/stub_specification.rb +37 -29
  61. data/lib/rubygems/test_case.rb +65 -28
  62. data/lib/rubygems/test_utilities.rb +18 -18
  63. data/lib/rubygems/text.rb +0 -2
  64. data/lib/rubygems/uninstaller.rb +1 -1
  65. data/lib/rubygems/util.rb +4 -4
  66. data/lib/rubygems/util/licenses.rb +309 -0
  67. data/lib/rubygems/util/list.rb +9 -21
  68. data/lib/rubygems/version.rb +24 -14
  69. data/test/rubygems/simple_gem.rb +1 -1
  70. data/test/rubygems/test_config.rb +10 -1
  71. data/test/rubygems/test_gem.rb +58 -11
  72. data/test/rubygems/test_gem_available_set.rb +2 -1
  73. data/test/rubygems/test_gem_commands_cleanup_command.rb +6 -5
  74. data/test/rubygems/test_gem_commands_dependency_command.rb +9 -1
  75. data/test/rubygems/test_gem_commands_install_command.rb +17 -28
  76. data/test/rubygems/test_gem_commands_mirror.rb +0 -13
  77. data/test/rubygems/test_gem_commands_outdated_command.rb +2 -3
  78. data/test/rubygems/test_gem_commands_pristine_command.rb +33 -5
  79. data/test/rubygems/test_gem_commands_query_command.rb +123 -158
  80. data/test/rubygems/test_gem_commands_server_command.rb +2 -2
  81. data/test/rubygems/test_gem_commands_specification_command.rb +4 -4
  82. data/test/rubygems/test_gem_commands_stale_command.rb +2 -0
  83. data/test/rubygems/test_gem_commands_uninstall_command.rb +5 -4
  84. data/test/rubygems/test_gem_commands_unpack_command.rb +4 -6
  85. data/test/rubygems/test_gem_commands_update_command.rb +22 -52
  86. data/test/rubygems/test_gem_commands_which_command.rb +1 -0
  87. data/test/rubygems/test_gem_config_file.rb +1 -1
  88. data/test/rubygems/test_gem_dependency.rb +7 -3
  89. data/test/rubygems/test_gem_dependency_installer.rb +5 -5
  90. data/test/rubygems/test_gem_doctor.rb +1 -1
  91. data/test/rubygems/test_gem_ext_builder.rb +2 -0
  92. data/test/rubygems/test_gem_ext_configure_builder.rb +8 -4
  93. data/test/rubygems/test_gem_ext_ext_conf_builder.rb +25 -21
  94. data/test/rubygems/test_gem_indexer.rb +4 -4
  95. data/test/rubygems/test_gem_install_update_options.rb +2 -2
  96. data/test/rubygems/test_gem_installer.rb +32 -26
  97. data/test/rubygems/test_gem_package.rb +46 -1
  98. data/test/rubygems/test_gem_package_tar_reader_entry.rb +8 -1
  99. data/test/rubygems/test_gem_package_tar_writer.rb +10 -1
  100. data/test/rubygems/test_gem_package_task.rb +5 -2
  101. data/test/rubygems/test_gem_platform.rb +11 -0
  102. data/test/rubygems/test_gem_remote_fetcher.rb +64 -3
  103. data/test/rubygems/test_gem_request.rb +1 -1
  104. data/test/rubygems/test_gem_request_connection_pools.rb +10 -1
  105. data/test/rubygems/test_gem_request_set.rb +5 -8
  106. data/test/rubygems/test_gem_request_set_lockfile.rb +2 -4
  107. data/test/rubygems/test_gem_request_set_lockfile_tokenizer.rb +1 -1
  108. data/test/rubygems/test_gem_resolver.rb +12 -31
  109. data/test/rubygems/test_gem_resolver_git_specification.rb +1 -0
  110. data/test/rubygems/test_gem_resolver_installer_set.rb +7 -11
  111. data/test/rubygems/test_gem_resolver_lock_specification.rb +3 -2
  112. data/test/rubygems/test_gem_security_trust_dir.rb +2 -0
  113. data/test/rubygems/test_gem_server.rb +4 -0
  114. data/test/rubygems/test_gem_specification.rb +344 -61
  115. data/test/rubygems/test_gem_stream_ui.rb +6 -6
  116. data/test/rubygems/test_gem_stub_specification.rb +21 -6
  117. data/test/rubygems/test_gem_text.rb +2 -0
  118. data/test/rubygems/test_gem_uninstaller.rb +2 -1
  119. data/test/rubygems/test_gem_util.rb +8 -0
  120. data/test/rubygems/test_require.rb +156 -125
  121. data/util/generate_spdx_license_list.rb +21 -0
  122. data/util/update_bundled_ca_certificates.rb +2 -1
  123. metadata +42 -6
  124. metadata.gz.sig +0 -0
  125. data/lib/rubygems/util/stringio.rb +0 -34
@@ -0,0 +1,63 @@
1
+ module Gem::Resolver::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,430 @@
1
+ module Gem::Resolver::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
+ # @return [Integer] the number of resolver iterations in between calls to
77
+ # {#resolver_ui}'s {UI#indicate_progress} method
78
+ attr_accessor :iteration_rate
79
+ private :iteration_rate
80
+
81
+ # @return [Time] the time at which resolution began
82
+ attr_accessor :started_at
83
+ private :started_at
84
+
85
+ # @return [Array<ResolutionState>] the stack of states for the resolution
86
+ attr_accessor :states
87
+ private :states
88
+
89
+ private
90
+
91
+ # Sets up the resolution process
92
+ # @return [void]
93
+ def start_resolution
94
+ @started_at = Time.now
95
+
96
+ handle_missing_or_push_dependency_state(initial_state)
97
+
98
+ debug { "Starting resolution (#{@started_at})" }
99
+ resolver_ui.before_resolution
100
+ end
101
+
102
+ # Ends the resolution process
103
+ # @return [void]
104
+ def end_resolution
105
+ resolver_ui.after_resolution
106
+ debug do
107
+ "Finished resolution (#{@iteration_counter} steps) " \
108
+ "(Took #{(ended_at = Time.now) - @started_at} seconds) (#{ended_at})"
109
+ end
110
+ debug { 'Unactivated: ' + Hash[activated.vertices.reject { |_n, v| v.payload }].keys.join(', ') } if state
111
+ debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state
112
+ end
113
+
114
+ require 'rubygems/resolver/molinillo/lib/molinillo/state'
115
+ require 'rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider'
116
+
117
+ ResolutionState.new.members.each do |member|
118
+ define_method member do |*args, &block|
119
+ current_state = state || ResolutionState.empty
120
+ current_state.send(member, *args, &block)
121
+ end
122
+ end
123
+
124
+ SpecificationProvider.instance_methods(false).each do |instance_method|
125
+ define_method instance_method do |*args, &block|
126
+ begin
127
+ specification_provider.send(instance_method, *args, &block)
128
+ rescue NoSuchDependencyError => error
129
+ if state
130
+ vertex = activated.vertex_named(name_for error.dependency)
131
+ error.required_by += vertex.incoming_edges.map { |e| e.origin.name }
132
+ error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty?
133
+ end
134
+ raise
135
+ end
136
+ end
137
+ end
138
+
139
+ # Processes the topmost available {RequirementState} on the stack
140
+ # @return [void]
141
+ def process_topmost_state
142
+ if possibility
143
+ attempt_to_activate
144
+ else
145
+ create_conflict if state.is_a? PossibilityState
146
+ unwind_for_conflict until possibility && state.is_a?(DependencyState)
147
+ end
148
+ end
149
+
150
+ # @return [Object] the current possibility that the resolution is trying
151
+ # to activate
152
+ def possibility
153
+ possibilities.last
154
+ end
155
+
156
+ # @return [RequirementState] the current state the resolution is
157
+ # operating upon
158
+ def state
159
+ states.last
160
+ end
161
+
162
+ # Creates the initial state for the resolution, based upon the
163
+ # {#requested} dependencies
164
+ # @return [DependencyState] the initial state for the resolution
165
+ def initial_state
166
+ graph = DependencyGraph.new.tap do |dg|
167
+ original_requested.each { |r| dg.add_root_vertex(name_for(r), nil).tap { |v| v.explicit_requirements << r } }
168
+ end
169
+
170
+ requirements = sort_dependencies(original_requested, graph, {})
171
+ initial_requirement = requirements.shift
172
+ DependencyState.new(
173
+ initial_requirement && name_for(initial_requirement),
174
+ requirements,
175
+ graph,
176
+ initial_requirement,
177
+ initial_requirement && search_for(initial_requirement),
178
+ 0,
179
+ {}
180
+ )
181
+ end
182
+
183
+ # Unwinds the states stack because a conflict has been encountered
184
+ # @return [void]
185
+ def unwind_for_conflict
186
+ debug(depth) { "Unwinding for conflict: #{requirement}" }
187
+ conflicts.tap do |c|
188
+ states.slice!((state_index_for_unwind + 1)..-1)
189
+ raise VersionConflict.new(c) unless state
190
+ state.conflicts = c
191
+ end
192
+ end
193
+
194
+ # @return [Integer] The index to which the resolution should unwind in the
195
+ # case of conflict.
196
+ def state_index_for_unwind
197
+ current_requirement = requirement
198
+ existing_requirement = requirement_for_existing_name(name)
199
+ until current_requirement.nil?
200
+ current_state = find_state_for(current_requirement)
201
+ return states.index(current_state) if state_any?(current_state)
202
+ current_requirement = parent_of(current_requirement)
203
+ end
204
+
205
+ until existing_requirement.nil?
206
+ existing_state = find_state_for(existing_requirement)
207
+ return states.index(existing_state) if state_any?(existing_state)
208
+ existing_requirement = parent_of(existing_requirement)
209
+ end
210
+ -1
211
+ end
212
+
213
+ # @return [Object] the requirement that led to `requirement` being added
214
+ # to the list of requirements.
215
+ def parent_of(requirement)
216
+ return nil unless requirement
217
+ seen = false
218
+ state = states.reverse_each.find do |s|
219
+ seen ||= s.requirement == requirement
220
+ seen && s.requirement != requirement && !s.requirements.include?(requirement)
221
+ end
222
+ state && state.requirement
223
+ end
224
+
225
+ # @return [Object] the requirement that led to a version of a possibility
226
+ # with the given name being activated.
227
+ def requirement_for_existing_name(name)
228
+ return nil unless activated.vertex_named(name).payload
229
+ states.reverse_each.find { |s| !s.activated.vertex_named(name).payload }.requirement
230
+ end
231
+
232
+ # @return [ResolutionState] the state whose `requirement` is the given
233
+ # `requirement`.
234
+ def find_state_for(requirement)
235
+ return nil unless requirement
236
+ states.reverse_each.find { |i| requirement == i.requirement && i.is_a?(DependencyState) }
237
+ end
238
+
239
+ # @return [Boolean] whether or not the given state has any possibilities
240
+ # left.
241
+ def state_any?(state)
242
+ state && state.possibilities.any?
243
+ end
244
+
245
+ # @return [Conflict] a {Conflict} that reflects the failure to activate
246
+ # the {#possibility} in conjunction with the current {#state}
247
+ def create_conflict
248
+ vertex = activated.vertex_named(name)
249
+ requirements = {
250
+ name_for_explicit_dependency_source => vertex.explicit_requirements,
251
+ name_for_locking_dependency_source => Array(locked_requirement_named(name)),
252
+ }
253
+ vertex.incoming_edges.each { |edge| (requirements[edge.origin.payload] ||= []).unshift(*edge.requirements) }
254
+ conflicts[name] = Conflict.new(
255
+ requirement,
256
+ Hash[requirements.select { |_, r| !r.empty? }],
257
+ vertex.payload,
258
+ possibility,
259
+ locked_requirement_named(name),
260
+ requirement_trees
261
+ )
262
+ end
263
+
264
+ # @return [Array<Array<Object>>] The different requirement
265
+ # trees that led to every requirement for the current spec.
266
+ def requirement_trees
267
+ activated.vertex_named(name).requirements.map { |r| requirement_tree_for(r) }
268
+ end
269
+
270
+ # @return [Array<Object>] the list of requirements that led to
271
+ # `requirement` being required.
272
+ def requirement_tree_for(requirement)
273
+ tree = []
274
+ while requirement
275
+ tree.unshift(requirement)
276
+ requirement = parent_of(requirement)
277
+ end
278
+ tree
279
+ end
280
+
281
+ # Indicates progress roughly once every second
282
+ # @return [void]
283
+ def indicate_progress
284
+ @iteration_counter += 1
285
+ @progress_rate ||= resolver_ui.progress_rate
286
+ if iteration_rate.nil?
287
+ if Time.now - started_at >= @progress_rate
288
+ self.iteration_rate = @iteration_counter
289
+ end
290
+ end
291
+
292
+ if iteration_rate && (@iteration_counter % iteration_rate) == 0
293
+ resolver_ui.indicate_progress
294
+ end
295
+ end
296
+
297
+ # Calls the {#resolver_ui}'s {UI#debug} method
298
+ # @param [Integer] depth the depth of the {#states} stack
299
+ # @param [Proc] block a block that yields a {#to_s}
300
+ # @return [void]
301
+ def debug(depth = 0, &block)
302
+ resolver_ui.debug(depth, &block)
303
+ end
304
+
305
+ # Attempts to activate the current {#possibility}
306
+ # @return [void]
307
+ def attempt_to_activate
308
+ debug(depth) { 'Attempting to activate ' + possibility.to_s }
309
+ existing_node = activated.vertex_named(name)
310
+ if existing_node.payload
311
+ debug(depth) { "Found existing spec (#{existing_node.payload})" }
312
+ attempt_to_activate_existing_spec(existing_node)
313
+ else
314
+ attempt_to_activate_new_spec
315
+ end
316
+ end
317
+
318
+ # Attempts to activate the current {#possibility} (given that it has
319
+ # already been activated)
320
+ # @return [void]
321
+ def attempt_to_activate_existing_spec(existing_node)
322
+ existing_spec = existing_node.payload
323
+ if requirement_satisfied_by?(requirement, activated, existing_spec)
324
+ new_requirements = requirements.dup
325
+ push_state_for_requirements(new_requirements)
326
+ else
327
+ return if attempt_to_swap_possibility
328
+ create_conflict
329
+ debug(depth) { "Unsatisfied by existing spec (#{existing_node.payload})" }
330
+ unwind_for_conflict
331
+ end
332
+ end
333
+
334
+ # Attempts to swp the current {#possibility} with the already-activated
335
+ # spec with the given name
336
+ # @return [Boolean] Whether the possibility was swapped into {#activated}
337
+ def attempt_to_swap_possibility
338
+ swapped = activated.dup
339
+ swapped.vertex_named(name).payload = possibility
340
+ return unless swapped.vertex_named(name).requirements.
341
+ all? { |r| requirement_satisfied_by?(r, swapped, possibility) }
342
+ attempt_to_activate_new_spec
343
+ end
344
+
345
+ # Attempts to activate the current {#possibility} (given that it hasn't
346
+ # already been activated)
347
+ # @return [void]
348
+ def attempt_to_activate_new_spec
349
+ satisfied = begin
350
+ locked_requirement = locked_requirement_named(name)
351
+ requested_spec_satisfied = requirement_satisfied_by?(requirement, activated, possibility)
352
+ locked_spec_satisfied = !locked_requirement ||
353
+ requirement_satisfied_by?(locked_requirement, activated, possibility)
354
+ debug(depth) { 'Unsatisfied by requested spec' } unless requested_spec_satisfied
355
+ debug(depth) { 'Unsatisfied by locked spec' } unless locked_spec_satisfied
356
+ requested_spec_satisfied && locked_spec_satisfied
357
+ end
358
+ if satisfied
359
+ activate_spec
360
+ else
361
+ create_conflict
362
+ unwind_for_conflict
363
+ end
364
+ end
365
+
366
+ # @param [String] requirement_name the spec name to search for
367
+ # @return [Object] the locked spec named `requirement_name`, if one
368
+ # is found on {#base}
369
+ def locked_requirement_named(requirement_name)
370
+ vertex = base.vertex_named(requirement_name)
371
+ vertex && vertex.payload
372
+ end
373
+
374
+ # Add the current {#possibility} to the dependency graph of the current
375
+ # {#state}
376
+ # @return [void]
377
+ def activate_spec
378
+ conflicts.delete(name)
379
+ debug(depth) { 'Activated ' + name + ' at ' + possibility.to_s }
380
+ vertex = activated.vertex_named(name)
381
+ vertex.payload = possibility
382
+ require_nested_dependencies_for(possibility)
383
+ end
384
+
385
+ # Requires the dependencies that the recently activated spec has
386
+ # @param [Object] activated_spec the specification that has just been
387
+ # activated
388
+ # @return [void]
389
+ def require_nested_dependencies_for(activated_spec)
390
+ nested_dependencies = dependencies_for(activated_spec)
391
+ debug(depth) { "Requiring nested dependencies (#{nested_dependencies.map(&:to_s).join(', ')})" }
392
+ nested_dependencies.each { |d| activated.add_child_vertex name_for(d), nil, [name_for(activated_spec)], d }
393
+
394
+ push_state_for_requirements(requirements + nested_dependencies)
395
+ end
396
+
397
+ # Pushes a new {DependencyState} that encapsulates both existing and new
398
+ # requirements
399
+ # @param [Array] new_requirements
400
+ # @return [void]
401
+ def push_state_for_requirements(new_requirements, new_activated = activated.dup)
402
+ new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts)
403
+ new_requirement = new_requirements.shift
404
+ new_name = new_requirement ? name_for(new_requirement) : ''
405
+ possibilities = new_requirement ? search_for(new_requirement) : []
406
+ handle_missing_or_push_dependency_state DependencyState.new(
407
+ new_name, new_requirements, new_activated,
408
+ new_requirement, possibilities, depth, conflicts.dup
409
+ )
410
+ end
411
+
412
+ # Pushes a new {DependencyState}.
413
+ # If the {#specification_provider} says to
414
+ # {SpecificationProvider#allow_missing?} that particular requirement, and
415
+ # there are no possibilities for that requirement, then `state` is not
416
+ # pushed, and the node in {#activated} is removed, and we continue
417
+ # resolving the remaining requirements.
418
+ # @param [DependencyState] state
419
+ # @return [void]
420
+ def handle_missing_or_push_dependency_state(state)
421
+ if state.requirement && state.possibilities.empty? && allow_missing?(state.requirement)
422
+ state.activated.detach_vertex_named(state.name)
423
+ push_state_for_requirements(state.requirements, state.activated)
424
+ else
425
+ states.push state
426
+ end
427
+ end
428
+ end
429
+ end
430
+ end