bundler 1.13.6 → 1.17.3

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 (323) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +554 -9
  3. data/README.md +28 -5
  4. data/bundler.gemspec +40 -11
  5. data/exe/bundle +4 -8
  6. data/exe/bundle_ruby +4 -3
  7. data/lib/bundler.rb +162 -68
  8. data/lib/bundler/build_metadata.rb +53 -0
  9. data/lib/bundler/capistrano.rb +5 -0
  10. data/lib/bundler/cli.rb +360 -118
  11. data/lib/bundler/cli/add.rb +35 -0
  12. data/lib/bundler/cli/binstubs.rb +18 -10
  13. data/lib/bundler/cli/cache.rb +6 -5
  14. data/lib/bundler/cli/check.rb +4 -6
  15. data/lib/bundler/cli/clean.rb +6 -7
  16. data/lib/bundler/cli/common.rb +47 -1
  17. data/lib/bundler/cli/config.rb +26 -7
  18. data/lib/bundler/cli/console.rb +2 -1
  19. data/lib/bundler/cli/doctor.rb +63 -18
  20. data/lib/bundler/cli/exec.rb +12 -5
  21. data/lib/bundler/cli/gem.rb +59 -21
  22. data/lib/bundler/cli/info.rb +50 -0
  23. data/lib/bundler/cli/init.rb +21 -7
  24. data/lib/bundler/cli/inject.rb +13 -4
  25. data/lib/bundler/cli/install.rb +72 -101
  26. data/lib/bundler/cli/issue.rb +40 -0
  27. data/lib/bundler/cli/list.rb +58 -0
  28. data/lib/bundler/cli/lock.rb +9 -6
  29. data/lib/bundler/cli/open.rb +4 -3
  30. data/lib/bundler/cli/outdated.rb +175 -60
  31. data/lib/bundler/cli/package.rb +9 -6
  32. data/lib/bundler/cli/platform.rb +2 -1
  33. data/lib/bundler/cli/plugin.rb +1 -0
  34. data/lib/bundler/cli/pristine.rb +47 -0
  35. data/lib/bundler/cli/remove.rb +18 -0
  36. data/lib/bundler/cli/show.rb +2 -2
  37. data/lib/bundler/cli/update.rb +44 -34
  38. data/lib/bundler/cli/viz.rb +5 -1
  39. data/lib/bundler/compact_index_client.rb +109 -0
  40. data/lib/bundler/compact_index_client/cache.rb +118 -0
  41. data/lib/bundler/compact_index_client/updater.rb +116 -0
  42. data/lib/bundler/compatibility_guard.rb +14 -0
  43. data/lib/bundler/constants.rb +1 -0
  44. data/lib/bundler/current_ruby.rb +17 -8
  45. data/lib/bundler/definition.rb +353 -182
  46. data/lib/bundler/dep_proxy.rb +3 -1
  47. data/lib/bundler/dependency.rb +22 -10
  48. data/lib/bundler/deployment.rb +1 -1
  49. data/lib/bundler/deprecate.rb +15 -3
  50. data/lib/bundler/dsl.rb +122 -64
  51. data/lib/bundler/endpoint_specification.rb +13 -3
  52. data/lib/bundler/env.rb +110 -38
  53. data/lib/bundler/environment_preserver.rb +27 -6
  54. data/lib/bundler/errors.rb +24 -0
  55. data/lib/bundler/feature_flag.rb +74 -0
  56. data/lib/bundler/fetcher.rb +18 -11
  57. data/lib/bundler/fetcher/base.rb +1 -0
  58. data/lib/bundler/fetcher/compact_index.rb +7 -5
  59. data/lib/bundler/fetcher/dependency.rb +3 -2
  60. data/lib/bundler/fetcher/downloader.rb +25 -7
  61. data/lib/bundler/fetcher/index.rb +3 -2
  62. data/lib/bundler/friendly_errors.rb +33 -7
  63. data/lib/bundler/gem_helper.rb +25 -11
  64. data/lib/bundler/gem_helpers.rb +70 -1
  65. data/lib/bundler/gem_remote_fetcher.rb +1 -0
  66. data/lib/bundler/gem_tasks.rb +1 -0
  67. data/lib/bundler/gem_version_promoter.rb +17 -2
  68. data/lib/bundler/gemdeps.rb +29 -0
  69. data/lib/bundler/graph.rb +1 -0
  70. data/lib/bundler/index.rb +28 -15
  71. data/lib/bundler/injector.rb +216 -33
  72. data/lib/bundler/inline.rb +12 -12
  73. data/lib/bundler/installer.rb +139 -53
  74. data/lib/bundler/installer/gem_installer.rb +15 -5
  75. data/lib/bundler/installer/parallel_installer.rb +113 -28
  76. data/lib/bundler/installer/standalone.rb +1 -0
  77. data/lib/bundler/lazy_specification.rb +31 -3
  78. data/lib/bundler/lockfile_generator.rb +95 -0
  79. data/lib/bundler/lockfile_parser.rb +50 -37
  80. data/lib/bundler/match_platform.rb +13 -3
  81. data/lib/bundler/mirror.rb +10 -5
  82. data/lib/bundler/plugin.rb +22 -8
  83. data/lib/bundler/plugin/api.rb +2 -1
  84. data/lib/bundler/plugin/api/source.rb +17 -4
  85. data/lib/bundler/plugin/events.rb +61 -0
  86. data/lib/bundler/plugin/index.rb +9 -2
  87. data/lib/bundler/plugin/installer.rb +7 -6
  88. data/lib/bundler/plugin/source_list.rb +7 -8
  89. data/lib/bundler/process_lock.rb +24 -0
  90. data/lib/bundler/psyched_yaml.rb +10 -0
  91. data/lib/bundler/remote_specification.rb +30 -1
  92. data/lib/bundler/resolver.rb +187 -194
  93. data/lib/bundler/resolver/spec_group.rb +106 -0
  94. data/lib/bundler/retry.rb +5 -1
  95. data/lib/bundler/ruby_dsl.rb +1 -0
  96. data/lib/bundler/ruby_version.rb +12 -2
  97. data/lib/bundler/rubygems_ext.rb +23 -8
  98. data/lib/bundler/rubygems_gem_installer.rb +90 -0
  99. data/lib/bundler/rubygems_integration.rb +193 -70
  100. data/lib/bundler/runtime.rb +39 -22
  101. data/lib/bundler/settings.rb +245 -85
  102. data/lib/bundler/settings/validator.rb +102 -0
  103. data/lib/bundler/setup.rb +4 -7
  104. data/lib/bundler/shared_helpers.rb +183 -40
  105. data/lib/bundler/similarity_detector.rb +1 -0
  106. data/lib/bundler/source.rb +58 -1
  107. data/lib/bundler/source/gemspec.rb +1 -0
  108. data/lib/bundler/source/git.rb +52 -23
  109. data/lib/bundler/source/git/git_proxy.rb +30 -14
  110. data/lib/bundler/source/metadata.rb +62 -0
  111. data/lib/bundler/source/path.rb +42 -16
  112. data/lib/bundler/source/path/installer.rb +4 -2
  113. data/lib/bundler/source/rubygems.rb +171 -82
  114. data/lib/bundler/source/rubygems/remote.rb +12 -2
  115. data/lib/bundler/source_list.rb +75 -15
  116. data/lib/bundler/spec_set.rb +67 -32
  117. data/lib/bundler/ssl_certs/certificate_manager.rb +2 -1
  118. data/lib/bundler/stub_specification.rb +86 -2
  119. data/lib/bundler/templates/.document +1 -0
  120. data/lib/bundler/templates/Executable +13 -1
  121. data/lib/bundler/templates/Executable.bundler +105 -0
  122. data/lib/bundler/templates/Executable.standalone +5 -5
  123. data/lib/bundler/templates/Gemfile +3 -0
  124. data/lib/bundler/templates/gems.rb +8 -0
  125. data/lib/bundler/templates/newgem/Gemfile.tt +4 -2
  126. data/lib/bundler/templates/newgem/LICENSE.txt.tt +1 -1
  127. data/lib/bundler/templates/newgem/README.md.tt +14 -8
  128. data/lib/bundler/templates/newgem/Rakefile.tt +5 -5
  129. data/lib/bundler/templates/newgem/bin/console.tt +1 -1
  130. data/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt +4 -4
  131. data/lib/bundler/templates/newgem/ext/newgem/newgem.h.tt +3 -3
  132. data/lib/bundler/templates/newgem/gitignore.tt +5 -1
  133. data/lib/bundler/templates/newgem/lib/newgem.rb.tt +7 -6
  134. data/lib/bundler/templates/newgem/lib/newgem/version.rb.tt +4 -4
  135. data/lib/bundler/templates/newgem/newgem.gemspec.tt +21 -12
  136. data/lib/bundler/templates/newgem/rspec.tt +1 -0
  137. data/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +1 -3
  138. data/lib/bundler/templates/newgem/spec/spec_helper.rb.tt +13 -1
  139. data/lib/bundler/templates/newgem/test/newgem_test.rb.tt +1 -1
  140. data/lib/bundler/templates/newgem/test/test_helper.rb.tt +3 -3
  141. data/lib/bundler/templates/newgem/{.travis.yml.tt → travis.yml.tt} +2 -0
  142. data/lib/bundler/ui.rb +1 -0
  143. data/lib/bundler/ui/rg_proxy.rb +1 -0
  144. data/lib/bundler/ui/shell.rb +30 -10
  145. data/lib/bundler/ui/silent.rb +21 -1
  146. data/lib/bundler/uri_credentials_filter.rb +1 -0
  147. data/lib/bundler/vendor/fileutils/lib/fileutils.rb +1638 -0
  148. data/lib/bundler/vendor/molinillo/lib/molinillo.rb +2 -0
  149. data/lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb +26 -0
  150. data/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb +7 -0
  151. data/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb +1 -0
  152. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb +26 -6
  153. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb +2 -1
  154. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb +12 -4
  155. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb +3 -2
  156. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb +63 -0
  157. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb +11 -3
  158. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb +13 -1
  159. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb +3 -2
  160. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb +3 -2
  161. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb +18 -5
  162. data/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb +75 -7
  163. data/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb +2 -1
  164. data/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb +1 -0
  165. data/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb +3 -1
  166. data/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb +499 -128
  167. data/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb +1 -0
  168. data/lib/bundler/vendor/molinillo/lib/molinillo/state.rb +8 -4
  169. data/lib/bundler/vendor/{net → net-http-persistent/lib/net}/http/faster.rb +1 -0
  170. data/lib/bundler/vendor/{net → net-http-persistent/lib/net}/http/persistent.rb +27 -24
  171. data/lib/bundler/vendor/{net → net-http-persistent/lib/net}/http/persistent/ssl_reuse.rb +2 -1
  172. data/lib/bundler/vendor/thor/lib/thor.rb +46 -21
  173. data/lib/bundler/vendor/thor/lib/thor/actions.rb +24 -22
  174. data/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb +2 -1
  175. data/lib/bundler/vendor/thor/lib/thor/actions/create_link.rb +2 -1
  176. data/lib/bundler/vendor/thor/lib/thor/actions/directory.rb +2 -2
  177. data/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb +16 -8
  178. data/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb +66 -18
  179. data/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb +17 -15
  180. data/lib/bundler/vendor/thor/lib/thor/base.rb +55 -32
  181. data/lib/bundler/vendor/thor/lib/thor/command.rb +13 -11
  182. data/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +21 -1
  183. data/lib/bundler/vendor/thor/lib/thor/core_ext/io_binary_read.rb +7 -5
  184. data/lib/bundler/vendor/thor/lib/thor/core_ext/ordered_hash.rb +94 -63
  185. data/lib/bundler/vendor/thor/lib/thor/error.rb +3 -3
  186. data/lib/bundler/vendor/thor/lib/thor/group.rb +13 -13
  187. data/lib/bundler/vendor/thor/lib/thor/invocation.rb +4 -5
  188. data/lib/bundler/vendor/thor/lib/thor/line_editor/basic.rb +2 -0
  189. data/lib/bundler/vendor/thor/lib/thor/parser/argument.rb +4 -7
  190. data/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb +16 -16
  191. data/lib/bundler/vendor/thor/lib/thor/parser/option.rb +42 -21
  192. data/lib/bundler/vendor/thor/lib/thor/parser/options.rb +13 -10
  193. data/lib/bundler/vendor/thor/lib/thor/runner.rb +31 -29
  194. data/lib/bundler/vendor/thor/lib/thor/shell.rb +1 -1
  195. data/lib/bundler/vendor/thor/lib/thor/shell/basic.rb +49 -33
  196. data/lib/bundler/vendor/thor/lib/thor/shell/color.rb +1 -1
  197. data/lib/bundler/vendor/thor/lib/thor/shell/html.rb +4 -4
  198. data/lib/bundler/vendor/thor/lib/thor/util.rb +8 -7
  199. data/lib/bundler/vendor/thor/lib/thor/version.rb +1 -1
  200. data/lib/bundler/vendored_fileutils.rb +9 -0
  201. data/lib/bundler/vendored_molinillo.rb +1 -0
  202. data/lib/bundler/vendored_persistent.rb +43 -3
  203. data/lib/bundler/vendored_thor.rb +6 -2
  204. data/lib/bundler/version.rb +19 -2
  205. data/lib/bundler/version_ranges.rb +76 -0
  206. data/lib/bundler/vlad.rb +5 -0
  207. data/lib/bundler/worker.rb +30 -6
  208. data/lib/bundler/yaml_serializer.rb +4 -4
  209. data/man/bundle-add.1 +58 -0
  210. data/man/bundle-add.1.txt +52 -0
  211. data/man/bundle-add.ronn +40 -0
  212. data/{lib/bundler/man/bundle-binstubs → man/bundle-binstubs.1} +11 -1
  213. data/man/bundle-binstubs.1.txt +48 -0
  214. data/man/bundle-binstubs.ronn +15 -1
  215. data/man/bundle-check.1 +31 -0
  216. data/man/bundle-check.1.txt +33 -0
  217. data/man/bundle-check.ronn +26 -0
  218. data/man/bundle-clean.1 +24 -0
  219. data/man/bundle-clean.1.txt +26 -0
  220. data/man/bundle-clean.ronn +18 -0
  221. data/man/bundle-config.1 +497 -0
  222. data/man/bundle-config.1.txt +529 -0
  223. data/man/bundle-config.ronn +233 -61
  224. data/man/bundle-doctor.1 +44 -0
  225. data/man/bundle-doctor.1.txt +44 -0
  226. data/man/bundle-doctor.ronn +33 -0
  227. data/{lib/bundler/man/bundle-exec → man/bundle-exec.1} +6 -3
  228. data/man/bundle-exec.1.txt +178 -0
  229. data/man/bundle-exec.ronn +10 -3
  230. data/{lib/bundler/man/bundle-gem → man/bundle-gem.1} +4 -4
  231. data/man/bundle-gem.1.txt +91 -0
  232. data/man/bundle-gem.ronn +3 -2
  233. data/man/bundle-info.1 +20 -0
  234. data/man/bundle-info.1.txt +21 -0
  235. data/man/bundle-info.ronn +17 -0
  236. data/man/bundle-init.1 +25 -0
  237. data/man/bundle-init.1.txt +34 -0
  238. data/man/bundle-init.ronn +29 -0
  239. data/man/bundle-inject.1 +33 -0
  240. data/man/bundle-inject.1.txt +32 -0
  241. data/man/bundle-inject.ronn +22 -0
  242. data/{lib/bundler/man/bundle-install → man/bundle-install.1} +32 -29
  243. data/man/bundle-install.1.txt +396 -0
  244. data/man/bundle-install.ronn +45 -36
  245. data/man/bundle-list.1 +50 -0
  246. data/man/bundle-list.1.txt +43 -0
  247. data/man/bundle-list.ronn +33 -0
  248. data/{lib/bundler/man/bundle-lock → man/bundle-lock.1} +43 -2
  249. data/man/bundle-lock.1.txt +93 -0
  250. data/man/bundle-lock.ronn +47 -0
  251. data/man/bundle-open.1 +32 -0
  252. data/man/bundle-open.1.txt +29 -0
  253. data/man/bundle-open.ronn +19 -0
  254. data/man/bundle-outdated.1 +155 -0
  255. data/man/bundle-outdated.1.txt +131 -0
  256. data/man/bundle-outdated.ronn +111 -0
  257. data/{lib/bundler/man/bundle-package → man/bundle-package.1} +6 -3
  258. data/man/bundle-package.1.txt +79 -0
  259. data/man/bundle-package.ronn +7 -2
  260. data/{lib/bundler/man/bundle-platform → man/bundle-platform.1} +1 -1
  261. data/man/bundle-platform.1.txt +57 -0
  262. data/man/bundle-pristine.1 +34 -0
  263. data/man/bundle-pristine.1.txt +44 -0
  264. data/man/bundle-pristine.ronn +34 -0
  265. data/man/bundle-remove.1 +31 -0
  266. data/man/bundle-remove.1.txt +34 -0
  267. data/man/bundle-remove.ronn +23 -0
  268. data/man/bundle-show.1 +23 -0
  269. data/man/bundle-show.1.txt +27 -0
  270. data/man/bundle-show.ronn +21 -0
  271. data/man/bundle-update.1 +394 -0
  272. data/man/bundle-update.1.txt +391 -0
  273. data/man/bundle-update.ronn +172 -16
  274. data/man/bundle-viz.1 +39 -0
  275. data/man/bundle-viz.1.txt +39 -0
  276. data/man/bundle-viz.ronn +30 -0
  277. data/{lib/bundler/man/bundle → man/bundle.1} +44 -28
  278. data/man/bundle.1.txt +116 -0
  279. data/man/bundle.ronn +39 -27
  280. data/{lib/bundler/man → man}/gemfile.5 +67 -84
  281. data/man/gemfile.5.ronn +77 -55
  282. data/man/gemfile.5.txt +653 -0
  283. data/man/index.txt +25 -8
  284. metadata +118 -58
  285. data/.codeclimate.yml +0 -25
  286. data/.gitignore +0 -16
  287. data/.rspec +0 -3
  288. data/.rubocop.yml +0 -128
  289. data/.rubocop_todo.yml +0 -248
  290. data/.travis.yml +0 -108
  291. data/CODE_OF_CONDUCT.md +0 -42
  292. data/CONTRIBUTING.md +0 -36
  293. data/DEVELOPMENT.md +0 -148
  294. data/ISSUES.md +0 -100
  295. data/Rakefile +0 -333
  296. data/bin/rake +0 -19
  297. data/bin/rspec +0 -15
  298. data/bin/rubocop +0 -17
  299. data/bin/with_rubygems +0 -39
  300. data/lib/bundler/man/bundle-binstubs.txt +0 -33
  301. data/lib/bundler/man/bundle-config +0 -254
  302. data/lib/bundler/man/bundle-config.txt +0 -282
  303. data/lib/bundler/man/bundle-exec.txt +0 -171
  304. data/lib/bundler/man/bundle-gem.txt +0 -90
  305. data/lib/bundler/man/bundle-install.txt +0 -385
  306. data/lib/bundler/man/bundle-lock.txt +0 -52
  307. data/lib/bundler/man/bundle-package.txt +0 -74
  308. data/lib/bundler/man/bundle-platform.txt +0 -57
  309. data/lib/bundler/man/bundle-update +0 -221
  310. data/lib/bundler/man/bundle-update.txt +0 -227
  311. data/lib/bundler/man/bundle.txt +0 -104
  312. data/lib/bundler/man/gemfile.5.txt +0 -636
  313. data/lib/bundler/postit_trampoline.rb +0 -68
  314. data/lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb +0 -79
  315. data/lib/bundler/vendor/compact_index_client/lib/compact_index_client/cache.rb +0 -112
  316. data/lib/bundler/vendor/compact_index_client/lib/compact_index_client/updater.rb +0 -80
  317. data/lib/bundler/vendor/compact_index_client/lib/compact_index_client/version.rb +0 -4
  318. data/lib/bundler/vendor/postit/lib/postit.rb +0 -15
  319. data/lib/bundler/vendor/postit/lib/postit/environment.rb +0 -44
  320. data/lib/bundler/vendor/postit/lib/postit/installer.rb +0 -28
  321. data/lib/bundler/vendor/postit/lib/postit/parser.rb +0 -21
  322. data/lib/bundler/vendor/postit/lib/postit/setup.rb +0 -12
  323. data/lib/bundler/vendor/postit/lib/postit/version.rb +0 -3
@@ -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
@@ -17,7 +18,7 @@ module Bundler::Molinillo
17
18
  # @param [Array<Object>] required_by @see {#required_by}
18
19
  def initialize(dependency, required_by = [])
19
20
  @dependency = dependency
20
- @required_by = required_by
21
+ @required_by = required_by.uniq
21
22
  super()
22
23
  end
23
24
 
@@ -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,69 @@ 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
+ incompatible_version_message_for_conflict = opts.delete(:incompatible_version_message_for_conflict) do
105
+ proc do |name, _conflict|
106
+ %(#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":)
107
+ end
108
+ end
109
+
110
+ conflicts.sort.reduce(''.dup) do |o, (name, conflict)|
111
+ o << "\n" << incompatible_version_message_for_conflict.call(name, conflict) << "\n"
112
+ if conflict.locked_requirement
113
+ o << %( In snapshot (#{name_for_locking_dependency_source}):\n)
114
+ o << %( #{printable_requirement.call(conflict.locked_requirement)}\n)
115
+ o << %(\n)
116
+ end
117
+ o << %( In #{name_for_explicit_dependency_source}:\n)
118
+ trees = reduce_trees.call(conflict.requirement_trees)
119
+
120
+ o << trees.map do |tree|
121
+ t = ''.dup
122
+ depth = 2
123
+ tree.each do |req|
124
+ t << ' ' * depth << req.to_s
125
+ unless tree.last == req
126
+ if spec = conflict.activated_by_name[name_for(req)]
127
+ t << %( was resolved to #{version_for_spec.call(spec)}, which)
128
+ end
129
+ t << %( depends on)
130
+ end
131
+ t << %(\n)
132
+ depth += 1
133
+ end
134
+ t
135
+ end.join("\n")
136
+
137
+ additional_message_for_conflict.call(o, name, conflict)
138
+
139
+ o
140
+ end.strip
73
141
  end
74
142
  end
75
143
  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.1'.freeze
5
+ VERSION = '0.6.6'.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
29
+ )
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
25
69
  )
26
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
@@ -52,7 +156,7 @@ module Bundler::Molinillo
52
156
  @base = base
53
157
  @states = []
54
158
  @iteration_counter = 0
55
- @parent_of = {}
159
+ @parents_of = Hash.new { |h, k| h[k] = [] }
56
160
  end
57
161
 
58
162
  # Resolves the {#original_requested} dependencies into a full dependency
@@ -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
@@ -105,10 +209,23 @@ module Bundler::Molinillo
105
209
 
106
210
  handle_missing_or_push_dependency_state(initial_state)
107
211
 
108
- debug { "Starting resolution (#{@started_at})" }
212
+ debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" }
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.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,50 +292,287 @@ 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}" }
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
- @parent_of.reject! { |_, i| i >= index }
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)
189
456
  end
190
457
  end
191
458
 
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
- until current_requirement.nil?
198
- current_state = find_state_for(current_requirement)
199
- return states.index(current_state) if state_any?(current_state)
200
- current_requirement = parent_of(current_requirement)
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
+ unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index }
465
+ unwinds_to_state << unwind_details
466
+ unwind_requirement_sets = unwinds_to_state.map(&:conflicting_requirements)
467
+
468
+ state.possibilities.reject! do |possibility_set|
469
+ possibility_set.possibilities.none? do |poss|
470
+ unwind_requirement_sets.any? do |requirements|
471
+ possibility_satisfies_requirements?(poss, requirements)
472
+ end
473
+ end
474
+ end
475
+ end
476
+
477
+ # @param [Object] possibility a single possibility
478
+ # @param [Array] requirements an array of requirements
479
+ # @return [Boolean] whether the possibility satisfies all of the
480
+ # given requirements
481
+ def possibility_satisfies_requirements?(possibility, requirements)
482
+ name = name_for(possibility)
483
+
484
+ activated.tag(:swap)
485
+ activated.set_payload(name, possibility) if activated.vertex_named(name)
486
+ satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) }
487
+ activated.rewind_to(:swap)
488
+
489
+ satisfied
490
+ end
491
+
492
+ # Filter's a state's possibilities to remove any that would (eventually)
493
+ # create a requirement in the conflict we've just rewound from
494
+ # @param [UnwindDetails] details of the conflict just unwound from
495
+ # @return [void]
496
+ def filter_possibilities_for_parent_unwind(unwind_details)
497
+ unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index }
498
+ unwinds_to_state << unwind_details
499
+
500
+ primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq
501
+ parent_unwinds = unwinds_to_state.uniq - primary_unwinds
502
+
503
+ allowed_possibility_sets = Compatibility.flat_map(primary_unwinds) do |unwind|
504
+ states[unwind.state_index].possibilities.select do |possibility_set|
505
+ possibility_set.possibilities.any? do |poss|
506
+ possibility_satisfies_requirements?(poss, unwind.conflicting_requirements)
507
+ end
508
+ end
201
509
  end
202
510
 
203
- until existing_requirement.nil?
204
- existing_state = find_state_for(existing_requirement)
205
- return states.index(existing_state) if state_any?(existing_state)
206
- existing_requirement = parent_of(existing_requirement)
511
+ requirements_to_avoid = Compatibility.flat_map(parent_unwinds, &:sub_dependencies_to_avoid)
512
+
513
+ state.possibilities.reject! do |possibility_set|
514
+ !allowed_possibility_sets.include?(possibility_set) &&
515
+ (requirements_to_avoid - possibility_set.dependencies).empty?
516
+ end
517
+ end
518
+
519
+ # @param [Conflict] conflict
520
+ # @return [Array] minimal array of requirements that would cause the passed
521
+ # conflict to occur.
522
+ def binding_requirements_for_conflict(conflict)
523
+ return [conflict.requirement] if conflict.possibility.nil?
524
+
525
+ possible_binding_requirements = conflict.requirements.values.flatten(1).uniq
526
+
527
+ # When there’s a `CircularDependency` error the conflicting requirement
528
+ # (the one causing the circular) won’t be `conflict.requirement`
529
+ # (which won’t be for the right state, because we won’t have created it,
530
+ # because it’s circular).
531
+ # We need to make sure we have that requirement in the conflict’s list,
532
+ # otherwise we won’t be able to unwind properly, so we just return all
533
+ # the requirements for the conflict.
534
+ return possible_binding_requirements if conflict.underlying_error
535
+
536
+ possibilities = search_for(conflict.requirement)
537
+
538
+ # If all the requirements together don't filter out all possibilities,
539
+ # then the only two requirements we need to consider are the initial one
540
+ # (where the dependency's version was first chosen) and the last
541
+ if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities)
542
+ return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact
543
+ end
544
+
545
+ # Loop through the possible binding requirements, removing each one
546
+ # that doesn't bind. Use a `reverse_each` as we want the earliest set of
547
+ # binding requirements, and don't use `reject!` as we wish to refine the
548
+ # array *on each iteration*.
549
+ binding_requirements = possible_binding_requirements.dup
550
+ possible_binding_requirements.reverse_each do |req|
551
+ next if req == conflict.requirement
552
+ unless binding_requirement_in_set?(req, binding_requirements, possibilities)
553
+ binding_requirements -= [req]
554
+ end
555
+ end
556
+
557
+ binding_requirements
558
+ end
559
+
560
+ # @param [Object] requirement we wish to check
561
+ # @param [Array] array of requirements
562
+ # @param [Array] array of possibilities the requirements will be used to filter
563
+ # @return [Boolean] whether or not the given requirement is required to filter
564
+ # out all elements of the array of possibilities.
565
+ def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities)
566
+ possibilities.any? do |poss|
567
+ possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement])
207
568
  end
208
- -1
209
569
  end
210
570
 
211
571
  # @return [Object] the requirement that led to `requirement` being added
212
572
  # to the list of requirements.
213
573
  def parent_of(requirement)
214
574
  return unless requirement
215
- return unless index = @parent_of[requirement]
575
+ return unless index = @parents_of[requirement].last
216
576
  return unless parent_state = @states[index]
217
577
  parent_state.requirement
218
578
  end
@@ -220,7 +580,8 @@ module Bundler::Molinillo
220
580
  # @return [Object] the requirement that led to a version of a possibility
221
581
  # with the given name being activated.
222
582
  def requirement_for_existing_name(name)
223
- return nil unless activated.vertex_named(name).payload
583
+ return nil unless vertex = activated.vertex_named(name)
584
+ return nil unless vertex.payload
224
585
  states.find { |s| s.name == name }.requirement
225
586
  end
226
587
 
@@ -228,18 +589,12 @@ module Bundler::Molinillo
228
589
  # `requirement`.
229
590
  def find_state_for(requirement)
230
591
  return nil unless requirement
231
- states.reverse_each.find { |i| requirement == i.requirement && i.is_a?(DependencyState) }
232
- end
233
-
234
- # @return [Boolean] whether or not the given state has any possibilities
235
- # left.
236
- def state_any?(state)
237
- state && state.possibilities.any?
592
+ states.find { |i| requirement == i.requirement }
238
593
  end
239
594
 
240
595
  # @return [Conflict] a {Conflict} that reflects the failure to activate
241
596
  # the {#possibility} in conjunction with the current {#state}
242
- def create_conflict
597
+ def create_conflict(underlying_error = nil)
243
598
  vertex = activated.vertex_named(name)
244
599
  locked_requirement = locked_requirement_named(name)
245
600
 
@@ -248,18 +603,21 @@ module Bundler::Molinillo
248
603
  requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements
249
604
  end
250
605
  requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement
251
- vertex.incoming_edges.each { |edge| (requirements[edge.origin.payload] ||= []).unshift(edge.requirement) }
606
+ vertex.incoming_edges.each do |edge|
607
+ (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement)
608
+ end
252
609
 
253
610
  activated_by_name = {}
254
- activated.each { |v| activated_by_name[v.name] = v.payload if v.payload }
611
+ activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload }
255
612
  conflicts[name] = Conflict.new(
256
613
  requirement,
257
614
  requirements,
258
- vertex.payload,
615
+ vertex.payload && vertex.payload.latest_version,
259
616
  possibility,
260
617
  locked_requirement,
261
618
  requirement_trees,
262
- activated_by_name
619
+ activated_by_name,
620
+ underlying_error
263
621
  )
264
622
  end
265
623
 
@@ -309,91 +667,48 @@ module Bundler::Molinillo
309
667
  # @return [void]
310
668
  def attempt_to_activate
311
669
  debug(depth) { 'Attempting to activate ' + possibility.to_s }
312
- existing_node = activated.vertex_named(name)
313
- if existing_node.payload
314
- debug(depth) { "Found existing spec (#{existing_node.payload})" }
315
- attempt_to_activate_existing_spec(existing_node)
670
+ existing_vertex = activated.vertex_named(name)
671
+ if existing_vertex.payload
672
+ debug(depth) { "Found existing spec (#{existing_vertex.payload})" }
673
+ attempt_to_filter_existing_spec(existing_vertex)
316
674
  else
317
- attempt_to_activate_new_spec
675
+ latest = possibility.latest_version
676
+ # use reject!(!satisfied) for 1.8.7 compatibility
677
+ possibility.possibilities.reject! do |possibility|
678
+ !requirement_satisfied_by?(requirement, activated, possibility)
679
+ end
680
+ if possibility.latest_version.nil?
681
+ # ensure there's a possibility for better error messages
682
+ possibility.possibilities << latest if latest
683
+ create_conflict
684
+ unwind_for_conflict
685
+ else
686
+ activate_new_spec
687
+ end
318
688
  end
319
689
  end
320
690
 
321
- # Attempts to activate the current {#possibility} (given that it has
322
- # already been activated)
691
+ # Attempts to update the existing vertex's `PossibilitySet` with a filtered version
323
692
  # @return [void]
324
- def attempt_to_activate_existing_spec(existing_node)
325
- existing_spec = existing_node.payload
326
- if requirement_satisfied_by?(requirement, activated, existing_spec)
693
+ def attempt_to_filter_existing_spec(vertex)
694
+ filtered_set = filtered_possibility_set(vertex)
695
+ if !filtered_set.possibilities.empty?
696
+ activated.set_payload(name, filtered_set)
327
697
  new_requirements = requirements.dup
328
698
  push_state_for_requirements(new_requirements, false)
329
- else
330
- return if attempt_to_swap_possibility
331
- create_conflict
332
- debug(depth) { "Unsatisfied by existing spec (#{existing_node.payload})" }
333
- unwind_for_conflict
334
- end
335
- end
336
-
337
- # Attempts to swp the current {#possibility} with the already-activated
338
- # spec with the given name
339
- # @return [Boolean] Whether the possibility was swapped into {#activated}
340
- def attempt_to_swap_possibility
341
- activated.tag(:swap)
342
- vertex = activated.vertex_named(name)
343
- activated.set_payload(name, possibility)
344
- if !vertex.requirements.
345
- all? { |r| requirement_satisfied_by?(r, activated, possibility) } ||
346
- !new_spec_satisfied?
347
- activated.rewind_to(:swap)
348
- return
349
- end
350
- fixup_swapped_children(vertex)
351
- activate_spec
352
- end
353
-
354
- # Ensures there are no orphaned successors to the given {vertex}.
355
- # @param [DependencyGraph::Vertex] vertex the vertex to fix up.
356
- # @return [void]
357
- def fixup_swapped_children(vertex)
358
- payload = vertex.payload
359
- dep_names = dependencies_for(payload).map(&method(:name_for))
360
- vertex.successors.each do |succ|
361
- if !dep_names.include?(succ.name) && !succ.root? && succ.predecessors.to_a == [vertex]
362
- debug(depth) { "Removing orphaned spec #{succ.name} after swapping #{name}" }
363
- activated.detach_vertex_named(succ.name)
364
-
365
- all_successor_names = succ.recursive_successors.map(&:name)
366
-
367
- requirements.delete_if do |requirement|
368
- requirement_name = name_for(requirement)
369
- (requirement_name == succ.name) || all_successor_names.include?(requirement_name)
370
- end
371
- end
372
- end
373
- end
374
-
375
- # Attempts to activate the current {#possibility} (given that it hasn't
376
- # already been activated)
377
- # @return [void]
378
- def attempt_to_activate_new_spec
379
- if new_spec_satisfied?
380
- activate_spec
381
699
  else
382
700
  create_conflict
701
+ debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" }
383
702
  unwind_for_conflict
384
703
  end
385
704
  end
386
705
 
387
- # @return [Boolean] whether the current spec is satisfied as a new
388
- # possibility.
389
- def new_spec_satisfied?
390
- locked_requirement = locked_requirement_named(name)
391
- requested_spec_satisfied = requirement_satisfied_by?(requirement, activated, possibility)
392
- locked_spec_satisfied = !locked_requirement ||
393
- requirement_satisfied_by?(locked_requirement, activated, possibility)
394
- debug(depth) { 'Unsatisfied by requested spec' } unless requested_spec_satisfied
395
- debug(depth) { 'Unsatisfied by locked spec' } unless locked_spec_satisfied
396
- requested_spec_satisfied && locked_spec_satisfied
706
+ # Generates a filtered version of the existing vertex's `PossibilitySet` using the
707
+ # current state's `requirement`
708
+ # @param [Object] existing vertex
709
+ # @return [PossibilitySet] filtered possibility set
710
+ def filtered_possibility_set(vertex)
711
+ PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities)
397
712
  end
398
713
 
399
714
  # @param [String] requirement_name the spec name to search for
@@ -407,24 +722,25 @@ module Bundler::Molinillo
407
722
  # Add the current {#possibility} to the dependency graph of the current
408
723
  # {#state}
409
724
  # @return [void]
410
- def activate_spec
725
+ def activate_new_spec
411
726
  conflicts.delete(name)
412
- debug(depth) { 'Activated ' + name + ' at ' + possibility.to_s }
727
+ debug(depth) { "Activated #{name} at #{possibility}" }
413
728
  activated.set_payload(name, possibility)
414
729
  require_nested_dependencies_for(possibility)
415
730
  end
416
731
 
417
732
  # Requires the dependencies that the recently activated spec has
418
- # @param [Object] activated_spec the specification that has just been
733
+ # @param [Object] activated_possibility the PossibilitySet that has just been
419
734
  # activated
420
735
  # @return [void]
421
- def require_nested_dependencies_for(activated_spec)
422
- nested_dependencies = dependencies_for(activated_spec)
736
+ def require_nested_dependencies_for(possibility_set)
737
+ nested_dependencies = dependencies_for(possibility_set.latest_version)
423
738
  debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" }
424
739
  nested_dependencies.each do |d|
425
- activated.add_child_vertex(name_for(d), nil, [name_for(activated_spec)], d)
740
+ activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d)
426
741
  parent_index = states.size - 1
427
- @parent_of[d] ||= parent_index
742
+ parents = @parents_of[d]
743
+ parents << parent_index if parents.empty?
428
744
  end
429
745
 
430
746
  push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?)
@@ -436,20 +752,75 @@ module Bundler::Molinillo
436
752
  # @return [void]
437
753
  def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated)
438
754
  new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort
439
- new_requirement = new_requirements.shift
755
+ new_requirement = nil
756
+ loop do
757
+ new_requirement = new_requirements.shift
758
+ break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement }
759
+ end
440
760
  new_name = new_requirement ? name_for(new_requirement) : ''.freeze
441
- possibilities = new_requirement ? search_for(new_requirement) : []
761
+ possibilities = possibilities_for_requirement(new_requirement)
442
762
  handle_missing_or_push_dependency_state DependencyState.new(
443
763
  new_name, new_requirements, new_activated,
444
- new_requirement, possibilities, depth, conflicts.dup
764
+ new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup
445
765
  )
446
766
  end
447
767
 
768
+ # Checks a proposed requirement with any existing locked requirement
769
+ # before generating an array of possibilities for it.
770
+ # @param [Object] the proposed requirement
771
+ # @return [Array] possibilities
772
+ def possibilities_for_requirement(requirement, activated = self.activated)
773
+ return [] unless requirement
774
+ if locked_requirement_named(name_for(requirement))
775
+ return locked_requirement_possibility_set(requirement, activated)
776
+ end
777
+
778
+ group_possibilities(search_for(requirement))
779
+ end
780
+
781
+ # @param [Object] the proposed requirement
782
+ # @return [Array] possibility set containing only the locked requirement, if any
783
+ def locked_requirement_possibility_set(requirement, activated = self.activated)
784
+ all_possibilities = search_for(requirement)
785
+ locked_requirement = locked_requirement_named(name_for(requirement))
786
+
787
+ # Longwinded way to build a possibilities array with either the locked
788
+ # requirement or nothing in it. Required, since the API for
789
+ # locked_requirement isn't guaranteed.
790
+ locked_possibilities = all_possibilities.select do |possibility|
791
+ requirement_satisfied_by?(locked_requirement, activated, possibility)
792
+ end
793
+
794
+ group_possibilities(locked_possibilities)
795
+ end
796
+
797
+ # Build an array of PossibilitySets, with each element representing a group of
798
+ # dependency versions that all have the same sub-dependency version constraints
799
+ # and are contiguous.
800
+ # @param [Array] an array of possibilities
801
+ # @return [Array] an array of possibility sets
802
+ def group_possibilities(possibilities)
803
+ possibility_sets = []
804
+ current_possibility_set = nil
805
+
806
+ possibilities.reverse_each do |possibility|
807
+ dependencies = dependencies_for(possibility)
808
+ if current_possibility_set && current_possibility_set.dependencies == dependencies
809
+ current_possibility_set.possibilities.unshift(possibility)
810
+ else
811
+ possibility_sets.unshift(PossibilitySet.new(dependencies, [possibility]))
812
+ current_possibility_set = possibility_sets.first
813
+ end
814
+ end
815
+
816
+ possibility_sets
817
+ end
818
+
448
819
  # Pushes a new {DependencyState}.
449
820
  # If the {#specification_provider} says to
450
821
  # {SpecificationProvider#allow_missing?} that particular requirement, and
451
822
  # there are no possibilities for that requirement, then `state` is not
452
- # pushed, and the node in {#activated} is removed, and we continue
823
+ # pushed, and the vertex in {#activated} is removed, and we continue
453
824
  # resolving the remaining requirements.
454
825
  # @param [DependencyState] state
455
826
  # @return [void]