bundler 1.15.4 → 1.16.0.pre.1

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

Potentially problematic release.


This version of bundler might be problematic. Click here for more details.

Files changed (251) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +43 -0
  3. data/README.md +12 -7
  4. data/exe/bundle +1 -1
  5. data/exe/bundle_ruby +4 -3
  6. data/lib/bundler.rb +47 -37
  7. data/lib/bundler/build_metadata.rb +38 -0
  8. data/lib/bundler/capistrano.rb +5 -0
  9. data/lib/bundler/cli.rb +155 -67
  10. data/lib/bundler/cli/add.rb +0 -1
  11. data/lib/bundler/cli/binstubs.rb +9 -7
  12. data/lib/bundler/cli/cache.rb +5 -4
  13. data/lib/bundler/cli/check.rb +3 -5
  14. data/lib/bundler/cli/clean.rb +5 -6
  15. data/lib/bundler/cli/common.rb +11 -2
  16. data/lib/bundler/cli/config.rb +2 -1
  17. data/lib/bundler/cli/console.rb +2 -1
  18. data/lib/bundler/cli/doctor.rb +1 -0
  19. data/lib/bundler/cli/exec.rb +2 -1
  20. data/lib/bundler/cli/gem.rb +3 -2
  21. data/lib/bundler/cli/info.rb +0 -1
  22. data/lib/bundler/cli/init.rb +17 -6
  23. data/lib/bundler/cli/inject.rb +1 -0
  24. data/lib/bundler/cli/install.rb +61 -61
  25. data/lib/bundler/cli/issue.rb +1 -1
  26. data/lib/bundler/cli/list.rb +22 -0
  27. data/lib/bundler/cli/lock.rb +0 -1
  28. data/lib/bundler/cli/open.rb +2 -2
  29. data/lib/bundler/cli/outdated.rb +13 -8
  30. data/lib/bundler/cli/package.rb +9 -6
  31. data/lib/bundler/cli/platform.rb +1 -0
  32. data/lib/bundler/cli/plugin.rb +1 -0
  33. data/lib/bundler/cli/pristine.rb +9 -2
  34. data/lib/bundler/cli/show.rb +0 -1
  35. data/lib/bundler/cli/update.rb +31 -5
  36. data/lib/bundler/cli/viz.rb +1 -0
  37. data/lib/bundler/compact_index_client.rb +1 -0
  38. data/lib/bundler/compact_index_client/cache.rb +1 -0
  39. data/lib/bundler/compact_index_client/updater.rb +3 -2
  40. data/lib/bundler/compatibility_guard.rb +14 -0
  41. data/lib/bundler/constants.rb +1 -0
  42. data/lib/bundler/current_ruby.rb +5 -4
  43. data/lib/bundler/definition.rb +140 -95
  44. data/lib/bundler/dep_proxy.rb +2 -0
  45. data/lib/bundler/dependency.rb +6 -7
  46. data/lib/bundler/deployment.rb +1 -1
  47. data/lib/bundler/deprecate.rb +1 -0
  48. data/lib/bundler/dsl.rb +97 -62
  49. data/lib/bundler/endpoint_specification.rb +9 -0
  50. data/lib/bundler/env.rb +63 -27
  51. data/lib/bundler/environment_preserver.rb +26 -6
  52. data/lib/bundler/errors.rb +1 -0
  53. data/lib/bundler/feature_flag.rb +39 -4
  54. data/lib/bundler/fetcher.rb +15 -8
  55. data/lib/bundler/fetcher/base.rb +1 -0
  56. data/lib/bundler/fetcher/compact_index.rb +2 -11
  57. data/lib/bundler/fetcher/dependency.rb +1 -0
  58. data/lib/bundler/fetcher/downloader.rb +1 -0
  59. data/lib/bundler/fetcher/index.rb +1 -0
  60. data/lib/bundler/friendly_errors.rb +2 -1
  61. data/lib/bundler/gem_helper.rb +14 -9
  62. data/lib/bundler/gem_helpers.rb +1 -0
  63. data/lib/bundler/gem_remote_fetcher.rb +1 -0
  64. data/lib/bundler/gem_tasks.rb +1 -0
  65. data/lib/bundler/gem_version_promoter.rb +1 -0
  66. data/lib/bundler/gemdeps.rb +1 -0
  67. data/lib/bundler/graph.rb +1 -0
  68. data/lib/bundler/index.rb +15 -8
  69. data/lib/bundler/injector.rb +25 -22
  70. data/lib/bundler/inline.rb +5 -7
  71. data/lib/bundler/installer.rb +93 -45
  72. data/lib/bundler/installer/gem_installer.rb +2 -0
  73. data/lib/bundler/installer/parallel_installer.rb +73 -42
  74. data/lib/bundler/installer/standalone.rb +1 -0
  75. data/lib/bundler/lazy_specification.rb +2 -1
  76. data/lib/bundler/lockfile_generator.rb +95 -0
  77. data/lib/bundler/lockfile_parser.rb +10 -4
  78. data/lib/bundler/match_platform.rb +1 -0
  79. data/lib/bundler/mirror.rb +6 -3
  80. data/lib/bundler/plugin.rb +1 -0
  81. data/lib/bundler/plugin/api/source.rb +8 -0
  82. data/lib/bundler/plugin/installer.rb +7 -6
  83. data/lib/bundler/plugin/source_list.rb +7 -8
  84. data/lib/bundler/process_lock.rb +24 -0
  85. data/lib/bundler/psyched_yaml.rb +1 -0
  86. data/lib/bundler/remote_specification.rb +1 -0
  87. data/lib/bundler/resolver.rb +138 -191
  88. data/lib/bundler/resolver/spec_group.rb +111 -0
  89. data/lib/bundler/retry.rb +1 -0
  90. data/lib/bundler/ruby_dsl.rb +1 -0
  91. data/lib/bundler/ruby_version.rb +1 -0
  92. data/lib/bundler/rubygems_ext.rb +5 -4
  93. data/lib/bundler/rubygems_gem_installer.rb +23 -0
  94. data/lib/bundler/rubygems_integration.rb +56 -27
  95. data/lib/bundler/runtime.rb +3 -5
  96. data/lib/bundler/settings.rb +177 -76
  97. data/lib/bundler/settings/validator.rb +79 -0
  98. data/lib/bundler/setup.rb +1 -0
  99. data/lib/bundler/shared_helpers.rb +86 -26
  100. data/lib/bundler/similarity_detector.rb +1 -0
  101. data/lib/bundler/source.rb +32 -0
  102. data/lib/bundler/source/gemspec.rb +1 -0
  103. data/lib/bundler/source/git.rb +21 -16
  104. data/lib/bundler/source/git/git_proxy.rb +14 -10
  105. data/lib/bundler/source/metadata.rb +63 -0
  106. data/lib/bundler/source/path.rb +8 -8
  107. data/lib/bundler/source/path/installer.rb +2 -0
  108. data/lib/bundler/source/rubygems.rb +131 -84
  109. data/lib/bundler/source/rubygems/remote.rb +3 -0
  110. data/lib/bundler/source_list.rb +75 -15
  111. data/lib/bundler/spec_set.rb +2 -1
  112. data/lib/bundler/ssl_certs/certificate_manager.rb +2 -1
  113. data/lib/bundler/stub_specification.rb +1 -0
  114. data/lib/bundler/templates/Executable +4 -0
  115. data/lib/bundler/templates/Executable.bundler +105 -0
  116. data/lib/bundler/templates/Gemfile +1 -0
  117. data/lib/bundler/templates/gems.rb +8 -0
  118. data/lib/bundler/templates/newgem/README.md.tt +1 -1
  119. data/lib/bundler/templates/newgem/gitignore.tt +0 -1
  120. data/lib/bundler/templates/newgem/newgem.gemspec.tt +4 -1
  121. data/lib/bundler/templates/newgem/rspec.tt +1 -0
  122. data/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +0 -2
  123. data/lib/bundler/ui.rb +1 -0
  124. data/lib/bundler/ui/rg_proxy.rb +1 -0
  125. data/lib/bundler/ui/shell.rb +15 -4
  126. data/lib/bundler/ui/silent.rb +1 -0
  127. data/lib/bundler/uri_credentials_filter.rb +1 -0
  128. data/lib/bundler/vendor/fileutils/lib/fileutils.rb +1638 -0
  129. data/lib/bundler/vendor/molinillo/lib/molinillo.rb +2 -0
  130. data/lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb +26 -0
  131. data/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb +7 -0
  132. data/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb +1 -0
  133. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb +3 -2
  134. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb +1 -0
  135. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb +1 -0
  136. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb +1 -0
  137. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb +1 -0
  138. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb +1 -0
  139. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb +1 -0
  140. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb +1 -0
  141. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb +1 -0
  142. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb +3 -2
  143. data/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb +69 -6
  144. data/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb +2 -1
  145. data/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb +1 -0
  146. data/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb +3 -1
  147. data/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb +487 -148
  148. data/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb +1 -0
  149. data/lib/bundler/vendor/molinillo/lib/molinillo/state.rb +8 -4
  150. data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb +1 -1
  151. data/lib/bundler/vendored_fileutils.rb +9 -0
  152. data/lib/bundler/vendored_molinillo.rb +1 -0
  153. data/lib/bundler/vendored_persistent.rb +34 -0
  154. data/lib/bundler/vendored_thor.rb +1 -0
  155. data/lib/bundler/version.rb +6 -2
  156. data/lib/bundler/version_ranges.rb +1 -0
  157. data/lib/bundler/vlad.rb +5 -0
  158. data/lib/bundler/worker.rb +1 -0
  159. data/lib/bundler/yaml_serializer.rb +3 -3
  160. data/man/bundle-add.1 +43 -0
  161. data/man/bundle-add.1.txt +40 -0
  162. data/man/bundle-binstubs.1 +40 -0
  163. data/man/bundle-binstubs.1.txt +48 -0
  164. data/man/bundle-binstubs.ronn +14 -0
  165. data/man/bundle-check.1 +31 -0
  166. data/man/bundle-check.1.txt +32 -0
  167. data/man/bundle-clean.1 +24 -0
  168. data/man/bundle-clean.1.txt +26 -0
  169. data/man/bundle-config.1 +455 -0
  170. data/man/bundle-config.1.txt +491 -0
  171. data/man/bundle-config.ronn +133 -79
  172. data/man/bundle-exec.1 +165 -0
  173. data/man/bundle-exec.1.txt +178 -0
  174. data/man/bundle-exec.ronn +7 -0
  175. data/man/bundle-gem.1 +80 -0
  176. data/man/bundle-gem.1.txt +91 -0
  177. data/man/bundle-gem.ronn +2 -1
  178. data/man/bundle-info.1 +20 -0
  179. data/man/bundle-info.1.txt +21 -0
  180. data/man/bundle-init.1 +20 -0
  181. data/man/bundle-init.1.txt +24 -0
  182. data/man/bundle-inject.1 +33 -0
  183. data/man/bundle-inject.1.txt +32 -0
  184. data/man/bundle-install.1 +305 -0
  185. data/man/bundle-install.1.txt +385 -0
  186. data/man/bundle-install.ronn +32 -32
  187. data/man/bundle-list.1 +20 -0
  188. data/man/bundle-list.1.txt +21 -0
  189. data/man/bundle-list.ronn +15 -0
  190. data/man/bundle-lock.1 +84 -0
  191. data/man/bundle-lock.1.txt +93 -0
  192. data/man/bundle-open.1 +32 -0
  193. data/man/bundle-open.1.txt +29 -0
  194. data/man/bundle-outdated.1 +151 -0
  195. data/man/bundle-outdated.1.txt +127 -0
  196. data/man/bundle-outdated.ronn +1 -1
  197. data/man/bundle-package.1 +55 -0
  198. data/man/bundle-package.1.txt +79 -0
  199. data/man/bundle-package.ronn +5 -0
  200. data/man/bundle-platform.1 +61 -0
  201. data/man/bundle-platform.1.txt +57 -0
  202. data/man/bundle-pristine.1 +34 -0
  203. data/man/bundle-pristine.1.txt +44 -0
  204. data/man/bundle-pristine.ronn +24 -3
  205. data/man/bundle-show.1 +23 -0
  206. data/man/bundle-show.1.txt +25 -0
  207. data/man/bundle-update.1 +390 -0
  208. data/man/bundle-update.1.txt +386 -0
  209. data/man/bundle-update.ronn +2 -2
  210. data/man/bundle-viz.1 +39 -0
  211. data/man/bundle-viz.1.txt +38 -0
  212. data/man/bundle-viz.ronn +5 -5
  213. data/man/bundle.1 +132 -0
  214. data/man/bundle.1.txt +113 -0
  215. data/man/bundle.ronn +5 -2
  216. data/man/gemfile.5 +679 -0
  217. data/man/gemfile.5.ronn +31 -0
  218. data/man/gemfile.5.txt +636 -0
  219. data/man/index.txt +23 -0
  220. metadata +21 -36
  221. data/.codeclimate.yml +0 -25
  222. data/.gitignore +0 -18
  223. data/.rspec +0 -3
  224. data/.rubocop.yml +0 -131
  225. data/.rubocop_todo.yml +0 -418
  226. data/.travis.yml +0 -122
  227. data/CODE_OF_CONDUCT.md +0 -42
  228. data/CONTRIBUTING.md +0 -17
  229. data/Rakefile +0 -338
  230. data/bin/rake +0 -19
  231. data/bin/rspec +0 -15
  232. data/bin/rubocop +0 -17
  233. data/bin/with_rubygems +0 -39
  234. data/bundler.gemspec +0 -48
  235. data/doc/README.md +0 -30
  236. data/doc/TROUBLESHOOTING.md +0 -64
  237. data/doc/contributing/BUG_TRIAGE.md +0 -36
  238. data/doc/contributing/COMMUNITY.md +0 -13
  239. data/doc/contributing/GETTING_HELP.md +0 -11
  240. data/doc/contributing/HOW_YOU_CAN_HELP.md +0 -27
  241. data/doc/contributing/ISSUES.md +0 -51
  242. data/doc/contributing/README.md +0 -38
  243. data/doc/development/NEW_FEATURES.md +0 -10
  244. data/doc/development/PULL_REQUESTS.md +0 -40
  245. data/doc/development/README.md +0 -19
  246. data/doc/development/RELEASING.md +0 -9
  247. data/doc/development/SETUP.md +0 -27
  248. data/doc/documentation/README.md +0 -29
  249. data/doc/documentation/VISION.md +0 -26
  250. data/doc/documentation/WRITING.md +0 -54
  251. data/task/release.rake +0 -116
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'bundler/vendor/molinillo/lib/molinillo/compatibility'
2
4
  require 'bundler/vendor/molinillo/lib/molinillo/gem_metadata'
3
5
  require 'bundler/vendor/molinillo/lib/molinillo/errors'
4
6
  require 'bundler/vendor/molinillo/lib/molinillo/resolver'
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler::Molinillo
4
+ # Hacks needed for old Ruby versions.
5
+ module Compatibility
6
+ module_function
7
+
8
+ if [].respond_to?(:flat_map)
9
+ # Flat map
10
+ # @param [Enumerable] enum an enumerable object
11
+ # @block the block to flat-map with
12
+ # @return The enum, flat-mapped
13
+ def flat_map(enum, &blk)
14
+ enum.flat_map(&blk)
15
+ end
16
+ else
17
+ # Flat map
18
+ # @param [Enumerable] enum an enumerable object
19
+ # @block the block to flat-map with
20
+ # @return The enum, flat-mapped
21
+ def flat_map(enum, &blk)
22
+ enum.map(&blk).flatten(1)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  # @!visibility private
4
5
  module Delegates
@@ -45,6 +46,12 @@ module Bundler::Molinillo
45
46
  current_state = state || Bundler::Molinillo::ResolutionState.empty
46
47
  current_state.conflicts
47
48
  end
49
+
50
+ # (see Bundler::Molinillo::ResolutionState#unused_unwind_options)
51
+ def unused_unwind_options
52
+ current_state = state || Bundler::Molinillo::ResolutionState.empty
53
+ current_state.unused_unwind_options
54
+ end
48
55
  end
49
56
  end
50
57
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  module Delegates
4
5
  # Delegates all {Bundler::Molinillo::SpecificationProvider} methods to a
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'set'
3
4
  require 'tsort'
4
5
 
@@ -147,8 +148,8 @@ module Bundler::Molinillo
147
148
  vertex = add_vertex(name, payload, root)
148
149
  vertex.explicit_requirements << requirement if root
149
150
  parent_names.each do |parent_name|
150
- parent_node = vertex_named(parent_name)
151
- add_edge(parent_node, vertex, requirement)
151
+ parent_vertex = vertex_named(parent_name)
152
+ add_edge(parent_vertex, vertex, requirement)
152
153
  end
153
154
  vertex
154
155
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  class DependencyGraph
4
5
  # An action that modifies a {DependencyGraph} that is reversible.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
3
4
  module Bundler::Molinillo
4
5
  class DependencyGraph
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
3
4
  module Bundler::Molinillo
4
5
  class DependencyGraph
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
3
4
  module Bundler::Molinillo
4
5
  class DependencyGraph
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
3
4
  module Bundler::Molinillo
4
5
  class DependencyGraph
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular'
3
4
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex'
4
5
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge'
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
3
4
  module Bundler::Molinillo
4
5
  class DependencyGraph
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action'
3
4
  module Bundler::Molinillo
4
5
  class DependencyGraph
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  class DependencyGraph
4
5
  # A vertex in a {DependencyGraph} that encapsulates a {#name} and a
@@ -53,7 +54,7 @@ module Bundler::Molinillo
53
54
  # {#descendent?}
54
55
  def recursive_predecessors
55
56
  vertices = predecessors
56
- vertices += vertices.map(&:recursive_predecessors).flatten(1)
57
+ vertices += Compatibility.flat_map(vertices, &:recursive_predecessors)
57
58
  vertices.uniq!
58
59
  vertices
59
60
  end
@@ -68,7 +69,7 @@ module Bundler::Molinillo
68
69
  # {#ancestor?}
69
70
  def recursive_successors
70
71
  vertices = successors
71
- vertices += vertices.map(&:recursive_successors).flatten(1)
72
+ vertices += Compatibility.flat_map(vertices, &:recursive_successors)
72
73
  vertices.uniq!
73
74
  vertices
74
75
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  # An error that occurred during the resolution process
4
5
  class ResolverError < StandardError; end
@@ -41,11 +42,11 @@ module Bundler::Molinillo
41
42
  attr_reader :dependencies
42
43
 
43
44
  # Initializes a new error with the given circular vertices.
44
- # @param [Array<DependencyGraph::Vertex>] nodes the nodes in the dependency
45
+ # @param [Array<DependencyGraph::Vertex>] vertices the vertices in the dependency
45
46
  # that caused the error
46
- def initialize(nodes)
47
- super "There is a circular dependency between #{nodes.map(&:name).join(' and ')}"
48
- @dependencies = nodes.map(&:payload).to_set
47
+ def initialize(vertices)
48
+ super "There is a circular dependency between #{vertices.map(&:name).join(' and ')}"
49
+ @dependencies = vertices.map { |vertex| vertex.payload.possibilities.last }.to_set
49
50
  end
50
51
  end
51
52
 
@@ -55,11 +56,16 @@ module Bundler::Molinillo
55
56
  # resolution to fail
56
57
  attr_reader :conflicts
57
58
 
59
+ # @return [SpecificationProvider] the specification provider used during
60
+ # resolution
61
+ attr_reader :specification_provider
62
+
58
63
  # Initializes a new error with the given version conflicts.
59
64
  # @param [{String => Resolution::Conflict}] conflicts see {#conflicts}
60
- def initialize(conflicts)
65
+ # @param [SpecificationProvider] specification_provider see {#specification_provider}
66
+ def initialize(conflicts, specification_provider)
61
67
  pairs = []
62
- conflicts.values.flatten.map(&:requirements).flatten.each do |conflicting|
68
+ Compatibility.flat_map(conflicts.values.flatten, &:requirements).each do |conflicting|
63
69
  conflicting.each do |source, conflict_requirements|
64
70
  conflict_requirements.each do |c|
65
71
  pairs << [c, source]
@@ -69,7 +75,64 @@ module Bundler::Molinillo
69
75
 
70
76
  super "Unable to satisfy the following requirements:\n\n" \
71
77
  "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}"
78
+
72
79
  @conflicts = conflicts
80
+ @specification_provider = specification_provider
81
+ end
82
+
83
+ require 'bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider'
84
+ include Delegates::SpecificationProvider
85
+
86
+ # @return [String] An error message that includes requirement trees,
87
+ # which is much more detailed & customizable than the default message
88
+ # @param [Hash] opts the options to create a message with.
89
+ # @option opts [String] :solver_name The user-facing name of the solver
90
+ # @option opts [String] :possibility_type The generic name of a possibility
91
+ # @option opts [Proc] :reduce_trees A proc that reduced the list of requirement trees
92
+ # @option opts [Proc] :printable_requirement A proc that pretty-prints requirements
93
+ # @option opts [Proc] :additional_message_for_conflict A proc that appends additional
94
+ # messages for each conflict
95
+ # @option opts [Proc] :version_for_spec A proc that returns the version number for a
96
+ # possibility
97
+ def message_with_trees(opts = {})
98
+ solver_name = opts.delete(:solver_name) { self.class.name.split('::').first }
99
+ possibility_type = opts.delete(:possibility_type) { 'possibility named' }
100
+ reduce_trees = opts.delete(:reduce_trees) { proc { |trees| trees.uniq.sort_by(&:to_s) } }
101
+ printable_requirement = opts.delete(:printable_requirement) { proc { |req| req.to_s } }
102
+ additional_message_for_conflict = opts.delete(:additional_message_for_conflict) { proc {} }
103
+ version_for_spec = opts.delete(:version_for_spec) { proc(&:to_s) }
104
+
105
+ conflicts.sort.reduce(''.dup) do |o, (name, conflict)|
106
+ o << %(\n#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":\n)
107
+ if conflict.locked_requirement
108
+ o << %( In snapshot (#{name_for_locking_dependency_source}):\n)
109
+ o << %( #{printable_requirement.call(conflict.locked_requirement)}\n)
110
+ o << %(\n)
111
+ end
112
+ o << %( In #{name_for_explicit_dependency_source}:\n)
113
+ trees = reduce_trees.call(conflict.requirement_trees)
114
+
115
+ o << trees.map do |tree|
116
+ t = ''.dup
117
+ depth = 2
118
+ tree.each do |req|
119
+ t << ' ' * depth << req.to_s
120
+ unless tree.last == req
121
+ if spec = conflict.activated_by_name[name_for(req)]
122
+ t << %( was resolved to #{version_for_spec.call(spec)}, which)
123
+ end
124
+ t << %( depends on)
125
+ end
126
+ t << %(\n)
127
+ depth += 1
128
+ end
129
+ t
130
+ end.join("\n")
131
+
132
+ additional_message_for_conflict.call(o, name, conflict)
133
+
134
+ o
135
+ end.strip
73
136
  end
74
137
  end
75
138
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  # The version of Bundler::Molinillo.
4
- VERSION = '0.5.7'.freeze
5
+ VERSION = '0.6.2'.freeze
5
6
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  # Provides information about specifcations and dependencies to the resolver,
4
5
  # allowing the {Resolver} class to remain generic while still providing power
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  # Conveys information about the resolution process to a user.
4
5
  module UI
@@ -48,7 +49,8 @@ module Bundler::Molinillo
48
49
  if debug?
49
50
  debug_info = yield
50
51
  debug_info = debug_info.inspect unless debug_info.is_a?(String)
51
- output.puts debug_info.split("\n").map { |s| ' ' * depth + s }
52
+ debug_info = debug_info.split("\n").map { |s| ":#{depth.to_s.rjust 4}: #{s}" }
53
+ output.puts debug_info
52
54
  end
53
55
  end
54
56
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bundler::Molinillo
3
4
  class Resolver
4
5
  # A specific resolution from a given {Resolver}
@@ -8,22 +9,125 @@ module Bundler::Molinillo
8
9
  # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict
9
10
  # @attr [Object, nil] existing the existing spec that was in conflict with
10
11
  # the {#possibility}
11
- # @attr [Object] possibility the spec that was unable to be activated due
12
- # to a conflict
12
+ # @attr [Object] possibility_set the set of specs that was unable to be
13
+ # activated due to a conflict.
13
14
  # @attr [Object] locked_requirement the relevant locking requirement.
14
15
  # @attr [Array<Array<Object>>] requirement_trees the different requirement
15
16
  # trees that led to every requirement for the conflicting name.
16
17
  # @attr [{String=>Object}] activated_by_name the already-activated specs.
18
+ # @attr [Object] underlying_error an error that has occurred during resolution, and
19
+ # will be raised at the end of it if no resolution is found.
17
20
  Conflict = Struct.new(
18
21
  :requirement,
19
22
  :requirements,
20
23
  :existing,
21
- :possibility,
24
+ :possibility_set,
22
25
  :locked_requirement,
23
26
  :requirement_trees,
24
- :activated_by_name
27
+ :activated_by_name,
28
+ :underlying_error
25
29
  )
26
30
 
31
+ class Conflict
32
+ # @return [Object] a spec that was unable to be activated due to a conflict
33
+ def possibility
34
+ possibility_set && possibility_set.latest_version
35
+ end
36
+ end
37
+
38
+ # A collection of possibility states that share the same dependencies
39
+ # @attr [Array] dependencies the dependencies for this set of possibilities
40
+ # @attr [Array] possibilities the possibilities
41
+ PossibilitySet = Struct.new(:dependencies, :possibilities)
42
+
43
+ class PossibilitySet
44
+ # String representation of the possibility set, for debugging
45
+ def to_s
46
+ "[#{possibilities.join(', ')}]"
47
+ end
48
+
49
+ # @return [Object] most up-to-date dependency in the possibility set
50
+ def latest_version
51
+ possibilities.last
52
+ end
53
+ end
54
+
55
+ # Details of the state to unwind to when a conflict occurs, and the cause of the unwind
56
+ # @attr [Integer] state_index the index of the state to unwind to
57
+ # @attr [Object] state_requirement the requirement of the state we're unwinding to
58
+ # @attr [Array] requirement_tree for the requirement we're relaxing
59
+ # @attr [Array] conflicting_requirements the requirements that combined to cause the conflict
60
+ # @attr [Array] requirement_trees for the conflict
61
+ # @attr [Array] requirements_unwound_to_instead array of unwind requirements that were chosen over this unwind
62
+ UnwindDetails = Struct.new(
63
+ :state_index,
64
+ :state_requirement,
65
+ :requirement_tree,
66
+ :conflicting_requirements,
67
+ :requirement_trees,
68
+ :requirements_unwound_to_instead
69
+ )
70
+
71
+ class UnwindDetails
72
+ include Comparable
73
+
74
+ # We compare UnwindDetails when choosing which state to unwind to. If
75
+ # two options have the same state_index we prefer the one most
76
+ # removed from a requirement that caused the conflict. Both options
77
+ # would unwind to the same state, but a `grandparent` option will
78
+ # filter out fewer of its possibilities after doing so - where a state
79
+ # is both a `parent` and a `grandparent` to requirements that have
80
+ # caused a conflict this is the correct behaviour.
81
+ # @param [UnwindDetail] other UnwindDetail to be compared
82
+ # @return [Integer] integer specifying ordering
83
+ def <=>(other)
84
+ if state_index > other.state_index
85
+ 1
86
+ elsif state_index == other.state_index
87
+ reversed_requirement_tree_index <=> other.reversed_requirement_tree_index
88
+ else
89
+ -1
90
+ end
91
+ end
92
+
93
+ # @return [Integer] index of state requirement in reversed requirement tree
94
+ # (the conflicting requirement itself will be at position 0)
95
+ def reversed_requirement_tree_index
96
+ @reversed_requirement_tree_index ||=
97
+ if state_requirement
98
+ requirement_tree.reverse.index(state_requirement)
99
+ else
100
+ 999_999
101
+ end
102
+ end
103
+
104
+ # @return [Boolean] where the requirement of the state we're unwinding
105
+ # to directly caused the conflict. Note: in this case, it is
106
+ # impossible for the state we're unwinding to to be a parent of
107
+ # any of the other conflicting requirements (or we would have
108
+ # circularity)
109
+ def unwinding_to_primary_requirement?
110
+ requirement_tree.last == state_requirement
111
+ end
112
+
113
+ # @return [Array] array of sub-dependencies to avoid when choosing a
114
+ # new possibility for the state we've unwound to. Only relevant for
115
+ # non-primary unwinds
116
+ def sub_dependencies_to_avoid
117
+ @requirements_to_avoid ||=
118
+ requirement_trees.map do |tree|
119
+ index = tree.index(state_requirement)
120
+ tree[index + 1] if index
121
+ end.compact
122
+ end
123
+
124
+ # @return [Array] array of all the requirements that led to the need for
125
+ # this unwind
126
+ def all_requirements
127
+ @all_requirements ||= requirement_trees.flatten(1)
128
+ end
129
+ end
130
+
27
131
  # @return [SpecificationProvider] the provider that knows about
28
132
  # dependencies, requirements, specifications, versions, etc.
29
133
  attr_reader :specification_provider
@@ -64,7 +168,7 @@ module Bundler::Molinillo
64
168
  start_resolution
65
169
 
66
170
  while state
67
- break unless state.requirements.any? || state.requirement
171
+ break if !state.requirement && state.requirements.empty?
68
172
  indicate_progress
69
173
  if state.respond_to?(:pop_possibility_state) # DependencyState
70
174
  debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" }
@@ -78,7 +182,7 @@ module Bundler::Molinillo
78
182
  process_topmost_state
79
183
  end
80
184
 
81
- activated.freeze
185
+ resolve_activated_specs
82
186
  ensure
83
187
  end_resolution
84
188
  end
@@ -109,6 +213,19 @@ module Bundler::Molinillo
109
213
  resolver_ui.before_resolution
110
214
  end
111
215
 
216
+ def resolve_activated_specs
217
+ activated.vertices.each do |_, vertex|
218
+ next unless vertex.payload
219
+
220
+ latest_version = vertex.payload.possibilities.reverse_each.find do |possibility|
221
+ vertex.requirements.uniq.all? { |req| requirement_satisfied_by?(req, activated, possibility) }
222
+ end
223
+
224
+ activated.set_payload(vertex.name, latest_version)
225
+ end
226
+ activated.freeze
227
+ end
228
+
112
229
  # Ends the resolution process
113
230
  # @return [void]
114
231
  def end_resolution
@@ -136,9 +253,12 @@ module Bundler::Molinillo
136
253
  if possibility
137
254
  attempt_to_activate
138
255
  else
139
- create_conflict if state.is_a? PossibilityState
140
- unwind_for_conflict until possibility && state.is_a?(DependencyState)
256
+ create_conflict
257
+ unwind_for_conflict
141
258
  end
259
+ rescue CircularDependencyError => underlying_error
260
+ create_conflict(underlying_error)
261
+ unwind_for_conflict
142
262
  end
143
263
 
144
264
  # @return [Object] the current possibility that the resolution is trying
@@ -158,7 +278,10 @@ module Bundler::Molinillo
158
278
  # @return [DependencyState] the initial state for the resolution
159
279
  def initial_state
160
280
  graph = DependencyGraph.new.tap do |dg|
161
- original_requested.each { |r| dg.add_vertex(name_for(r), nil, true).tap { |v| v.explicit_requirements << r } }
281
+ original_requested.each do |requested|
282
+ vertex = dg.add_vertex(name_for(requested), nil, true)
283
+ vertex.explicit_requirements << requested
284
+ end
162
285
  dg.tag(:initial_state)
163
286
  end
164
287
 
@@ -169,45 +292,276 @@ module Bundler::Molinillo
169
292
  requirements,
170
293
  graph,
171
294
  initial_requirement,
172
- initial_requirement && search_for(initial_requirement),
295
+ possibilities_for_requirement(initial_requirement, graph),
173
296
  0,
174
- {}
297
+ {},
298
+ []
175
299
  )
176
300
  end
177
301
 
178
302
  # Unwinds the states stack because a conflict has been encountered
179
303
  # @return [void]
180
304
  def unwind_for_conflict
181
- debug(depth) { "Unwinding for conflict: #{requirement} to #{state_index_for_unwind / 2}" }
305
+ details_for_unwind = build_details_for_unwind
306
+ unwind_options = unused_unwind_options
307
+ debug(depth) { "Unwinding for conflict: #{requirement} to #{details_for_unwind.state_index / 2}" }
182
308
  conflicts.tap do |c|
183
- sliced_states = states.slice!((state_index_for_unwind + 1)..-1)
184
- raise VersionConflict.new(c) unless state
309
+ sliced_states = states.slice!((details_for_unwind.state_index + 1)..-1)
310
+ raise_error_unless_state(c)
185
311
  activated.rewind_to(sliced_states.first || :initial_state) if sliced_states
186
312
  state.conflicts = c
313
+ state.unused_unwind_options = unwind_options
314
+ filter_possibilities_after_unwind(details_for_unwind)
187
315
  index = states.size - 1
188
316
  @parents_of.each { |_, a| a.reject! { |i| i >= index } }
317
+ state.unused_unwind_options.reject! { |uw| uw.state_index >= index }
318
+ end
319
+ end
320
+
321
+ # Raises a VersionConflict error, or any underlying error, if there is no
322
+ # current state
323
+ # @return [void]
324
+ def raise_error_unless_state(conflicts)
325
+ return if state
326
+
327
+ error = conflicts.values.map(&:underlying_error).compact.first
328
+ raise error || VersionConflict.new(conflicts, specification_provider)
329
+ end
330
+
331
+ # @return [UnwindDetails] Details of the nearest index to which we could unwind
332
+ def build_details_for_unwind
333
+ # Get the possible unwinds for the current conflict
334
+ current_conflict = conflicts[name]
335
+ binding_requirements = binding_requirements_for_conflict(current_conflict)
336
+ unwind_details = unwind_options_for_requirements(binding_requirements)
337
+
338
+ last_detail_for_current_unwind = unwind_details.sort.last
339
+ current_detail = last_detail_for_current_unwind
340
+
341
+ # Look for past conflicts that could be unwound to affect the
342
+ # requirement tree for the current conflict
343
+ relevant_unused_unwinds = unused_unwind_options.select do |alternative|
344
+ intersecting_requirements =
345
+ last_detail_for_current_unwind.all_requirements &
346
+ alternative.requirements_unwound_to_instead
347
+ next if intersecting_requirements.empty?
348
+ # Find the highest index unwind whilst looping through
349
+ current_detail = alternative if alternative > current_detail
350
+ alternative
351
+ end
352
+
353
+ # Add the current unwind options to the `unused_unwind_options` array.
354
+ # The "used" option will be filtered out during `unwind_for_conflict`.
355
+ state.unused_unwind_options += unwind_details.reject { |detail| detail.state_index == -1 }
356
+
357
+ # Update the requirements_unwound_to_instead on any relevant unused unwinds
358
+ relevant_unused_unwinds.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement }
359
+ unwind_details.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement }
360
+
361
+ current_detail
362
+ end
363
+
364
+ # @param [Array<Object>] array of requirements that combine to create a conflict
365
+ # @return [Array<UnwindDetails>] array of UnwindDetails that have a chance
366
+ # of resolving the passed requirements
367
+ def unwind_options_for_requirements(binding_requirements)
368
+ unwind_details = []
369
+
370
+ trees = []
371
+ binding_requirements.reverse_each do |r|
372
+ partial_tree = [r]
373
+ trees << partial_tree
374
+ unwind_details << UnwindDetails.new(-1, nil, partial_tree, binding_requirements, trees, [])
375
+
376
+ # If this requirement has alternative possibilities, check if any would
377
+ # satisfy the other requirements that created this conflict
378
+ requirement_state = find_state_for(r)
379
+ if conflict_fixing_possibilities?(requirement_state, binding_requirements)
380
+ unwind_details << UnwindDetails.new(
381
+ states.index(requirement_state),
382
+ r,
383
+ partial_tree,
384
+ binding_requirements,
385
+ trees,
386
+ []
387
+ )
388
+ end
389
+
390
+ # Next, look at the parent of this requirement, and check if the requirement
391
+ # could have been avoided if an alternative PossibilitySet had been chosen
392
+ parent_r = parent_of(r)
393
+ next if parent_r.nil?
394
+ partial_tree.unshift(parent_r)
395
+ requirement_state = find_state_for(parent_r)
396
+ if requirement_state.possibilities.any? { |set| !set.dependencies.include?(r) }
397
+ unwind_details << UnwindDetails.new(
398
+ states.index(requirement_state),
399
+ parent_r,
400
+ partial_tree,
401
+ binding_requirements,
402
+ trees,
403
+ []
404
+ )
405
+ end
406
+
407
+ # Finally, look at the grandparent and up of this requirement, looking
408
+ # for any possibilities that wouldn't create their parent requirement
409
+ grandparent_r = parent_of(parent_r)
410
+ until grandparent_r.nil?
411
+ partial_tree.unshift(grandparent_r)
412
+ requirement_state = find_state_for(grandparent_r)
413
+ if requirement_state.possibilities.any? { |set| !set.dependencies.include?(parent_r) }
414
+ unwind_details << UnwindDetails.new(
415
+ states.index(requirement_state),
416
+ grandparent_r,
417
+ partial_tree,
418
+ binding_requirements,
419
+ trees,
420
+ []
421
+ )
422
+ end
423
+ parent_r = grandparent_r
424
+ grandparent_r = parent_of(parent_r)
425
+ end
426
+ end
427
+
428
+ unwind_details
429
+ end
430
+
431
+ # @param [DependencyState] state
432
+ # @param [Array] array of requirements
433
+ # @return [Boolean] whether or not the given state has any possibilities
434
+ # that could satisfy the given requirements
435
+ def conflict_fixing_possibilities?(state, binding_requirements)
436
+ return false unless state
437
+
438
+ state.possibilities.any? do |possibility_set|
439
+ possibility_set.possibilities.any? do |poss|
440
+ possibility_satisfies_requirements?(poss, binding_requirements)
441
+ end
442
+ end
443
+ end
444
+
445
+ # Filter's a state's possibilities to remove any that would not fix the
446
+ # conflict we've just rewound from
447
+ # @param [UnwindDetails] details of the conflict just unwound from
448
+ # @return [void]
449
+ def filter_possibilities_after_unwind(unwind_details)
450
+ return unless state && !state.possibilities.empty?
451
+
452
+ if unwind_details.unwinding_to_primary_requirement?
453
+ filter_possibilities_for_primary_unwind(unwind_details)
454
+ else
455
+ filter_possibilities_for_parent_unwind(unwind_details)
456
+ end
457
+ end
458
+
459
+ # Filter's a state's possibilities to remove any that would not satisfy
460
+ # the requirements in the conflict we've just rewound from
461
+ # @param [UnwindDetails] details of the conflict just unwound from
462
+ # @return [void]
463
+ def filter_possibilities_for_primary_unwind(unwind_details)
464
+ all_requirements = unwind_details.conflicting_requirements
465
+
466
+ state.possibilities.reject! do |possibility_set|
467
+ possibility_set.possibilities.none? do |poss|
468
+ possibility_satisfies_requirements?(poss, all_requirements)
469
+ end
189
470
  end
190
471
  end
191
472
 
192
- # @return [Integer] The index to which the resolution should unwind in the
193
- # case of conflict.
194
- def state_index_for_unwind
195
- current_requirement = requirement
196
- existing_requirement = requirement_for_existing_name(name)
197
- index = -1
198
- [current_requirement, existing_requirement].each do |r|
199
- until r.nil?
200
- current_state = find_state_for(r)
201
- if state_any?(current_state)
202
- current_index = states.index(current_state)
203
- index = current_index if current_index > index
204
- break
473
+ # @param [Object] possibility a single possibility
474
+ # @param [Array] requirements an array of requirements
475
+ # @return [Boolean] whether the possibility satisfies all of the
476
+ # given requirements
477
+ def possibility_satisfies_requirements?(possibility, requirements)
478
+ name = name_for(possibility)
479
+
480
+ activated.tag(:swap)
481
+ activated.set_payload(name, possibility) if activated.vertex_named(name)
482
+ satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) }
483
+ activated.rewind_to(:swap)
484
+
485
+ satisfied
486
+ end
487
+
488
+ # Filter's a state's possibilities to remove any that would (eventually)
489
+ # create a requirement in the conflict we've just rewound from
490
+ # @param [UnwindDetails] details of the conflict just unwound from
491
+ # @return [void]
492
+ def filter_possibilities_for_parent_unwind(unwind_details)
493
+ unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index }
494
+ unwinds_to_state << unwind_details
495
+
496
+ primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq
497
+ parent_unwinds = unwinds_to_state.uniq - primary_unwinds
498
+
499
+ allowed_possibility_sets = Compatibility.flat_map(primary_unwinds) do |unwind|
500
+ states[unwind.state_index].possibilities.select do |possibility_set|
501
+ possibility_set.possibilities.any? do |poss|
502
+ possibility_satisfies_requirements?(poss, unwind.conflicting_requirements)
205
503
  end
206
- r = parent_of(r)
207
504
  end
208
505
  end
209
506
 
210
- index
507
+ requirements_to_avoid = Compatibility.flat_map(parent_unwinds, &:sub_dependencies_to_avoid)
508
+
509
+ state.possibilities.reject! do |possibility_set|
510
+ !allowed_possibility_sets.include?(possibility_set) &&
511
+ (requirements_to_avoid - possibility_set.dependencies).empty?
512
+ end
513
+ end
514
+
515
+ # @param [Conflict] conflict
516
+ # @return [Array] minimal array of requirements that would cause the passed
517
+ # conflict to occur.
518
+ def binding_requirements_for_conflict(conflict)
519
+ return [conflict.requirement] if conflict.possibility.nil?
520
+
521
+ possible_binding_requirements = conflict.requirements.values.flatten(1).uniq
522
+
523
+ # When there’s a `CircularDependency` error the conflicting requirement
524
+ # (the one causing the circular) won’t be `conflict.requirement`
525
+ # (which won’t be for the right state, because we won’t have created it,
526
+ # because it’s circular).
527
+ # We need to make sure we have that requirement in the conflict’s list,
528
+ # otherwise we won’t be able to unwind properly, so we just return all
529
+ # the requirements for the conflict.
530
+ return possible_binding_requirements if conflict.underlying_error
531
+
532
+ possibilities = search_for(conflict.requirement)
533
+
534
+ # If all the requirements together don't filter out all possibilities,
535
+ # then the only two requirements we need to consider are the initial one
536
+ # (where the dependency's version was first chosen) and the last
537
+ if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities)
538
+ return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact
539
+ end
540
+
541
+ # Loop through the possible binding requirements, removing each one
542
+ # that doesn't bind. Use a `reverse_each` as we want the earliest set of
543
+ # binding requirements, and don't use `reject!` as we wish to refine the
544
+ # array *on each iteration*.
545
+ binding_requirements = possible_binding_requirements.dup
546
+ possible_binding_requirements.reverse_each do |req|
547
+ next if req == conflict.requirement
548
+ unless binding_requirement_in_set?(req, binding_requirements, possibilities)
549
+ binding_requirements -= [req]
550
+ end
551
+ end
552
+
553
+ binding_requirements
554
+ end
555
+
556
+ # @param [Object] requirement we wish to check
557
+ # @param [Array] array of requirements
558
+ # @param [Array] array of possibilities the requirements will be used to filter
559
+ # @return [Boolean] whether or not the given requirement is required to filter
560
+ # out all elements of the array of possibilities.
561
+ def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities)
562
+ possibilities.any? do |poss|
563
+ possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement])
564
+ end
211
565
  end
212
566
 
213
567
  # @return [Object] the requirement that led to `requirement` being added
@@ -222,7 +576,8 @@ module Bundler::Molinillo
222
576
  # @return [Object] the requirement that led to a version of a possibility
223
577
  # with the given name being activated.
224
578
  def requirement_for_existing_name(name)
225
- return nil unless activated.vertex_named(name).payload
579
+ return nil unless vertex = activated.vertex_named(name)
580
+ return nil unless vertex.payload
226
581
  states.find { |s| s.name == name }.requirement
227
582
  end
228
583
 
@@ -230,18 +585,12 @@ module Bundler::Molinillo
230
585
  # `requirement`.
231
586
  def find_state_for(requirement)
232
587
  return nil unless requirement
233
- states.reverse_each.find { |i| requirement == i.requirement && i.is_a?(DependencyState) }
234
- end
235
-
236
- # @return [Boolean] whether or not the given state has any possibilities
237
- # left.
238
- def state_any?(state)
239
- state && state.possibilities.any?
588
+ states.find { |i| requirement == i.requirement }
240
589
  end
241
590
 
242
591
  # @return [Conflict] a {Conflict} that reflects the failure to activate
243
592
  # the {#possibility} in conjunction with the current {#state}
244
- def create_conflict
593
+ def create_conflict(underlying_error = nil)
245
594
  vertex = activated.vertex_named(name)
246
595
  locked_requirement = locked_requirement_named(name)
247
596
 
@@ -250,18 +599,21 @@ module Bundler::Molinillo
250
599
  requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements
251
600
  end
252
601
  requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement
253
- vertex.incoming_edges.each { |edge| (requirements[edge.origin.payload] ||= []).unshift(edge.requirement) }
602
+ vertex.incoming_edges.each do |edge|
603
+ (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement)
604
+ end
254
605
 
255
606
  activated_by_name = {}
256
- activated.each { |v| activated_by_name[v.name] = v.payload if v.payload }
607
+ activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload }
257
608
  conflicts[name] = Conflict.new(
258
609
  requirement,
259
610
  requirements,
260
- vertex.payload,
611
+ vertex.payload && vertex.payload.latest_version,
261
612
  possibility,
262
613
  locked_requirement,
263
614
  requirement_trees,
264
- activated_by_name
615
+ activated_by_name,
616
+ underlying_error
265
617
  )
266
618
  end
267
619
 
@@ -311,116 +663,48 @@ module Bundler::Molinillo
311
663
  # @return [void]
312
664
  def attempt_to_activate
313
665
  debug(depth) { 'Attempting to activate ' + possibility.to_s }
314
- existing_node = activated.vertex_named(name)
315
- if existing_node.payload
316
- debug(depth) { "Found existing spec (#{existing_node.payload})" }
317
- attempt_to_activate_existing_spec(existing_node)
318
- else
319
- attempt_to_activate_new_spec
320
- end
321
- end
322
-
323
- # Attempts to activate the current {#possibility} (given that it has
324
- # already been activated)
325
- # @return [void]
326
- def attempt_to_activate_existing_spec(existing_node)
327
- existing_spec = existing_node.payload
328
- if requirement_satisfied_by?(requirement, activated, existing_spec)
329
- new_requirements = requirements.dup
330
- push_state_for_requirements(new_requirements, false)
666
+ existing_vertex = activated.vertex_named(name)
667
+ if existing_vertex.payload
668
+ debug(depth) { "Found existing spec (#{existing_vertex.payload})" }
669
+ attempt_to_filter_existing_spec(existing_vertex)
331
670
  else
332
- return if attempt_to_swap_possibility
333
- create_conflict
334
- debug(depth) { "Unsatisfied by existing spec (#{existing_node.payload})" }
335
- unwind_for_conflict
336
- end
337
- end
338
-
339
- # Attempts to swp the current {#possibility} with the already-activated
340
- # spec with the given name
341
- # @return [Boolean] Whether the possibility was swapped into {#activated}
342
- def attempt_to_swap_possibility
343
- activated.tag(:swap)
344
- vertex = activated.vertex_named(name)
345
- activated.set_payload(name, possibility)
346
- if !vertex.requirements.
347
- all? { |r| requirement_satisfied_by?(r, activated, possibility) } ||
348
- !new_spec_satisfied?
349
- activated.rewind_to(:swap)
350
- return
351
- end
352
- fixup_swapped_children(vertex)
353
- activate_spec
354
- end
355
-
356
- # Ensures there are no orphaned successors to the given {vertex}.
357
- # @param [DependencyGraph::Vertex] vertex the vertex to fix up.
358
- # @return [void]
359
- def fixup_swapped_children(vertex) # rubocop:disable Metrics/CyclomaticComplexity
360
- payload = vertex.payload
361
- deps = dependencies_for(payload).group_by(&method(:name_for))
362
- vertex.outgoing_edges.each do |outgoing_edge|
363
- requirement = outgoing_edge.requirement
364
- parent_index = @parents_of[requirement].last
365
- succ = outgoing_edge.destination
366
- matching_deps = Array(deps[succ.name])
367
- dep_matched = matching_deps.include?(requirement)
368
-
369
- # only push the current index when it was originally required by the
370
- # same named spec
371
- if parent_index && states[parent_index].name == name
372
- @parents_of[requirement].push(states.size - 1)
671
+ latest = possibility.latest_version
672
+ # use reject!(!satisfied) for 1.8.7 compatibility
673
+ possibility.possibilities.reject! do |possibility|
674
+ !requirement_satisfied_by?(requirement, activated, possibility)
373
675
  end
374
-
375
- if matching_deps.empty? && !succ.root? && succ.predecessors.to_a == [vertex]
376
- debug(depth) { "Removing orphaned spec #{succ.name} after swapping #{name}" }
377
- succ.requirements.each { |r| @parents_of.delete(r) }
378
-
379
- removed_names = activated.detach_vertex_named(succ.name).map(&:name)
380
- requirements.delete_if do |r|
381
- # the only removed vertices are those with no other requirements,
382
- # so it's safe to delete only based upon name here
383
- removed_names.include?(name_for(r))
384
- end
385
- elsif !dep_matched
386
- debug(depth) { "Removing orphaned dependency #{requirement} after swapping #{name}" }
387
- # also reset if we're removing the edge, but only if its parent has
388
- # already been fixed up
389
- @parents_of[requirement].push(states.size - 1) if @parents_of[requirement].empty?
390
-
391
- activated.delete_edge(outgoing_edge)
392
- requirements.delete(requirement)
676
+ if possibility.latest_version.nil?
677
+ # ensure there's a possibility for better error messages
678
+ possibility.possibilities << latest if latest
679
+ create_conflict
680
+ unwind_for_conflict
681
+ else
682
+ activate_new_spec
393
683
  end
394
684
  end
395
685
  end
396
686
 
397
- # Attempts to activate the current {#possibility} (given that it hasn't
398
- # already been activated)
687
+ # Attempts to update the existing vertex's `PossibilitySet` with a filtered version
399
688
  # @return [void]
400
- def attempt_to_activate_new_spec
401
- if new_spec_satisfied?
402
- activate_spec
689
+ def attempt_to_filter_existing_spec(vertex)
690
+ filtered_set = filtered_possibility_set(vertex)
691
+ if !filtered_set.possibilities.empty?
692
+ activated.set_payload(name, filtered_set)
693
+ new_requirements = requirements.dup
694
+ push_state_for_requirements(new_requirements, false)
403
695
  else
404
696
  create_conflict
697
+ debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" }
405
698
  unwind_for_conflict
406
699
  end
407
700
  end
408
701
 
409
- # @return [Boolean] whether the current spec is satisfied as a new
410
- # possibility.
411
- def new_spec_satisfied?
412
- unless requirement_satisfied_by?(requirement, activated, possibility)
413
- debug(depth) { 'Unsatisfied by requested spec' }
414
- return false
415
- end
416
-
417
- locked_requirement = locked_requirement_named(name)
418
-
419
- locked_spec_satisfied = !locked_requirement ||
420
- requirement_satisfied_by?(locked_requirement, activated, possibility)
421
- debug(depth) { 'Unsatisfied by locked spec' } unless locked_spec_satisfied
422
-
423
- locked_spec_satisfied
702
+ # Generates a filtered version of the existing vertex's `PossibilitySet` using the
703
+ # current state's `requirement`
704
+ # @param [Object] existing vertex
705
+ # @return [PossibilitySet] filtered possibility set
706
+ def filtered_possibility_set(vertex)
707
+ PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities)
424
708
  end
425
709
 
426
710
  # @param [String] requirement_name the spec name to search for
@@ -434,7 +718,7 @@ module Bundler::Molinillo
434
718
  # Add the current {#possibility} to the dependency graph of the current
435
719
  # {#state}
436
720
  # @return [void]
437
- def activate_spec
721
+ def activate_new_spec
438
722
  conflicts.delete(name)
439
723
  debug(depth) { "Activated #{name} at #{possibility}" }
440
724
  activated.set_payload(name, possibility)
@@ -442,14 +726,14 @@ module Bundler::Molinillo
442
726
  end
443
727
 
444
728
  # Requires the dependencies that the recently activated spec has
445
- # @param [Object] activated_spec the specification that has just been
729
+ # @param [Object] activated_possibility the PossibilitySet that has just been
446
730
  # activated
447
731
  # @return [void]
448
- def require_nested_dependencies_for(activated_spec)
449
- nested_dependencies = dependencies_for(activated_spec)
732
+ def require_nested_dependencies_for(possibility_set)
733
+ nested_dependencies = dependencies_for(possibility_set.latest_version)
450
734
  debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" }
451
735
  nested_dependencies.each do |d|
452
- activated.add_child_vertex(name_for(d), nil, [name_for(activated_spec)], d)
736
+ activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d)
453
737
  parent_index = states.size - 1
454
738
  parents = @parents_of[d]
455
739
  parents << parent_index if parents.empty?
@@ -464,20 +748,75 @@ module Bundler::Molinillo
464
748
  # @return [void]
465
749
  def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated)
466
750
  new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort
467
- new_requirement = new_requirements.shift
751
+ new_requirement = nil
752
+ loop do
753
+ new_requirement = new_requirements.shift
754
+ break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement }
755
+ end
468
756
  new_name = new_requirement ? name_for(new_requirement) : ''.freeze
469
- possibilities = new_requirement ? search_for(new_requirement) : []
757
+ possibilities = possibilities_for_requirement(new_requirement)
470
758
  handle_missing_or_push_dependency_state DependencyState.new(
471
759
  new_name, new_requirements, new_activated,
472
- new_requirement, possibilities, depth, conflicts.dup
760
+ new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup
473
761
  )
474
762
  end
475
763
 
764
+ # Checks a proposed requirement with any existing locked requirement
765
+ # before generating an array of possibilities for it.
766
+ # @param [Object] the proposed requirement
767
+ # @return [Array] possibilities
768
+ def possibilities_for_requirement(requirement, activated = self.activated)
769
+ return [] unless requirement
770
+ if locked_requirement_named(name_for(requirement))
771
+ return locked_requirement_possibility_set(requirement, activated)
772
+ end
773
+
774
+ group_possibilities(search_for(requirement))
775
+ end
776
+
777
+ # @param [Object] the proposed requirement
778
+ # @return [Array] possibility set containing only the locked requirement, if any
779
+ def locked_requirement_possibility_set(requirement, activated = self.activated)
780
+ all_possibilities = search_for(requirement)
781
+ locked_requirement = locked_requirement_named(name_for(requirement))
782
+
783
+ # Longwinded way to build a possibilities array with either the locked
784
+ # requirement or nothing in it. Required, since the API for
785
+ # locked_requirement isn't guaranteed.
786
+ locked_possibilities = all_possibilities.select do |possibility|
787
+ requirement_satisfied_by?(locked_requirement, activated, possibility)
788
+ end
789
+
790
+ group_possibilities(locked_possibilities)
791
+ end
792
+
793
+ # Build an array of PossibilitySets, with each element representing a group of
794
+ # dependency versions that all have the same sub-dependency version constraints
795
+ # and are contiguous.
796
+ # @param [Array] an array of possibilities
797
+ # @return [Array] an array of possibility sets
798
+ def group_possibilities(possibilities)
799
+ possibility_sets = []
800
+ current_possibility_set = nil
801
+
802
+ possibilities.reverse_each do |possibility|
803
+ dependencies = dependencies_for(possibility)
804
+ if current_possibility_set && current_possibility_set.dependencies == dependencies
805
+ current_possibility_set.possibilities.unshift(possibility)
806
+ else
807
+ possibility_sets.unshift(PossibilitySet.new(dependencies, [possibility]))
808
+ current_possibility_set = possibility_sets.first
809
+ end
810
+ end
811
+
812
+ possibility_sets
813
+ end
814
+
476
815
  # Pushes a new {DependencyState}.
477
816
  # If the {#specification_provider} says to
478
817
  # {SpecificationProvider#allow_missing?} that particular requirement, and
479
818
  # there are no possibilities for that requirement, then `state` is not
480
- # pushed, and the node in {#activated} is removed, and we continue
819
+ # pushed, and the vertex in {#activated} is removed, and we continue
481
820
  # resolving the remaining requirements.
482
821
  # @param [DependencyState] state
483
822
  # @return [void]