bundler 1.9.0 → 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 (328) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1157 -6
  3. data/README.md +33 -6
  4. data/bundler.gemspec +51 -18
  5. data/exe/bundle +31 -0
  6. data/{bin → exe}/bundle_ruby +10 -6
  7. data/exe/bundler +4 -0
  8. data/lib/bundler.rb +326 -207
  9. data/lib/bundler/build_metadata.rb +53 -0
  10. data/lib/bundler/capistrano.rb +9 -3
  11. data/lib/bundler/cli.rb +522 -141
  12. data/lib/bundler/cli/add.rb +35 -0
  13. data/lib/bundler/cli/binstubs.rb +22 -11
  14. data/lib/bundler/cli/cache.rb +7 -6
  15. data/lib/bundler/cli/check.rb +11 -8
  16. data/lib/bundler/cli/clean.rb +7 -8
  17. data/lib/bundler/cli/common.rb +53 -7
  18. data/lib/bundler/cli/config.rb +84 -49
  19. data/lib/bundler/cli/console.rb +13 -8
  20. data/lib/bundler/cli/doctor.rb +140 -0
  21. data/lib/bundler/cli/exec.rb +77 -16
  22. data/lib/bundler/cli/gem.rb +120 -52
  23. data/lib/bundler/cli/info.rb +50 -0
  24. data/lib/bundler/cli/init.rb +21 -7
  25. data/lib/bundler/cli/inject.rb +37 -10
  26. data/lib/bundler/cli/install.rb +139 -78
  27. data/lib/bundler/cli/issue.rb +40 -0
  28. data/lib/bundler/cli/list.rb +58 -0
  29. data/lib/bundler/cli/lock.rb +63 -0
  30. data/lib/bundler/cli/open.rb +9 -6
  31. data/lib/bundler/cli/outdated.rb +221 -35
  32. data/lib/bundler/cli/package.rb +11 -7
  33. data/lib/bundler/cli/platform.rb +7 -4
  34. data/lib/bundler/cli/plugin.rb +24 -0
  35. data/lib/bundler/cli/pristine.rb +47 -0
  36. data/lib/bundler/cli/remove.rb +18 -0
  37. data/lib/bundler/cli/show.rb +11 -10
  38. data/lib/bundler/cli/update.rb +47 -29
  39. data/lib/bundler/cli/viz.rb +12 -8
  40. data/lib/bundler/compact_index_client.rb +109 -0
  41. data/lib/bundler/compact_index_client/cache.rb +118 -0
  42. data/lib/bundler/compact_index_client/updater.rb +116 -0
  43. data/lib/bundler/compatibility_guard.rb +14 -0
  44. data/lib/bundler/constants.rb +3 -1
  45. data/lib/bundler/current_ruby.rb +47 -137
  46. data/lib/bundler/definition.rb +599 -230
  47. data/lib/bundler/dep_proxy.rb +15 -10
  48. data/lib/bundler/dependency.rb +54 -25
  49. data/lib/bundler/deployment.rb +12 -2
  50. data/lib/bundler/deprecate.rb +33 -4
  51. data/lib/bundler/dsl.rb +383 -99
  52. data/lib/bundler/endpoint_specification.rb +72 -7
  53. data/lib/bundler/env.rb +121 -41
  54. data/lib/bundler/environment_preserver.rb +59 -0
  55. data/lib/bundler/errors.rb +158 -0
  56. data/lib/bundler/feature_flag.rb +74 -0
  57. data/lib/bundler/fetcher.rb +171 -280
  58. data/lib/bundler/fetcher/base.rb +52 -0
  59. data/lib/bundler/fetcher/compact_index.rb +126 -0
  60. data/lib/bundler/fetcher/dependency.rb +82 -0
  61. data/lib/bundler/fetcher/downloader.rb +84 -0
  62. data/lib/bundler/fetcher/index.rb +52 -0
  63. data/lib/bundler/friendly_errors.rb +113 -58
  64. data/lib/bundler/gem_helper.rb +73 -46
  65. data/lib/bundler/gem_helpers.rb +85 -9
  66. data/lib/bundler/gem_remote_fetcher.rb +43 -0
  67. data/lib/bundler/gem_tasks.rb +6 -1
  68. data/lib/bundler/gem_version_promoter.rb +190 -0
  69. data/lib/bundler/gemdeps.rb +29 -0
  70. data/lib/bundler/graph.rb +32 -49
  71. data/lib/bundler/index.rb +79 -67
  72. data/lib/bundler/injector.rb +219 -30
  73. data/lib/bundler/inline.rb +74 -0
  74. data/lib/bundler/installer.rb +191 -206
  75. data/lib/bundler/installer/gem_installer.rb +85 -0
  76. data/lib/bundler/installer/parallel_installer.rb +233 -0
  77. data/lib/bundler/installer/standalone.rb +53 -0
  78. data/lib/bundler/lazy_specification.rb +53 -13
  79. data/lib/bundler/lockfile_generator.rb +95 -0
  80. data/lib/bundler/lockfile_parser.rb +157 -62
  81. data/lib/bundler/match_platform.rb +15 -4
  82. data/lib/bundler/mirror.rb +223 -0
  83. data/lib/bundler/plugin.rb +292 -0
  84. data/lib/bundler/plugin/api.rb +81 -0
  85. data/lib/bundler/plugin/api/source.rb +306 -0
  86. data/lib/bundler/plugin/dsl.rb +53 -0
  87. data/lib/bundler/plugin/events.rb +61 -0
  88. data/lib/bundler/plugin/index.rb +162 -0
  89. data/lib/bundler/plugin/installer.rb +96 -0
  90. data/lib/bundler/plugin/installer/git.rb +38 -0
  91. data/lib/bundler/plugin/installer/rubygems.rb +27 -0
  92. data/lib/bundler/plugin/source_list.rb +27 -0
  93. data/lib/bundler/process_lock.rb +24 -0
  94. data/lib/bundler/psyched_yaml.rb +17 -6
  95. data/lib/bundler/remote_specification.rb +68 -11
  96. data/lib/bundler/resolver.rb +263 -229
  97. data/lib/bundler/resolver/spec_group.rb +106 -0
  98. data/lib/bundler/retry.rb +25 -19
  99. data/lib/bundler/ruby_dsl.rb +9 -2
  100. data/lib/bundler/ruby_version.rb +101 -66
  101. data/lib/bundler/rubygems_ext.rb +77 -37
  102. data/lib/bundler/rubygems_gem_installer.rb +106 -0
  103. data/lib/bundler/rubygems_integration.rb +450 -163
  104. data/lib/bundler/runtime.rb +133 -103
  105. data/lib/bundler/settings.rb +344 -83
  106. data/lib/bundler/settings/validator.rb +102 -0
  107. data/lib/bundler/setup.rb +7 -3
  108. data/lib/bundler/shared_helpers.rb +284 -54
  109. data/lib/bundler/similarity_detector.rb +21 -21
  110. data/lib/bundler/source.rb +68 -15
  111. data/lib/bundler/source/gemspec.rb +18 -0
  112. data/lib/bundler/source/git.rb +90 -55
  113. data/lib/bundler/source/git/git_proxy.rb +135 -35
  114. data/lib/bundler/source/metadata.rb +62 -0
  115. data/lib/bundler/source/path.rb +84 -61
  116. data/lib/bundler/source/path/installer.rb +53 -17
  117. data/lib/bundler/source/rubygems.rb +282 -122
  118. data/lib/bundler/source/rubygems/remote.rb +69 -0
  119. data/lib/bundler/source_list.rb +107 -22
  120. data/lib/bundler/spec_set.rb +83 -45
  121. data/lib/bundler/ssl_certs/certificate_manager.rb +8 -7
  122. data/lib/bundler/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem +21 -0
  123. data/lib/bundler/ssl_certs/{DigiCertHighAssuranceEVRootCA.pem → rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem} +0 -0
  124. data/lib/bundler/ssl_certs/{AddTrustExternalCARoot-2048.pem → rubygems.org/AddTrustExternalCARoot.pem} +0 -0
  125. data/lib/bundler/stub_specification.rb +108 -0
  126. data/lib/bundler/templates/.document +1 -0
  127. data/lib/bundler/templates/Executable +19 -6
  128. data/lib/bundler/templates/Executable.bundler +105 -0
  129. data/lib/bundler/templates/Executable.standalone +6 -4
  130. data/lib/bundler/templates/Gemfile +4 -1
  131. data/lib/bundler/templates/gems.rb +8 -0
  132. data/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt +68 -7
  133. data/lib/bundler/templates/newgem/Gemfile.tt +4 -2
  134. data/lib/bundler/templates/newgem/LICENSE.txt.tt +1 -1
  135. data/lib/bundler/templates/newgem/README.md.tt +19 -11
  136. data/lib/bundler/templates/newgem/Rakefile.tt +10 -6
  137. data/lib/bundler/templates/newgem/bin/console.tt +1 -1
  138. data/lib/bundler/templates/newgem/bin/setup.tt +2 -1
  139. data/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt +4 -4
  140. data/lib/bundler/templates/newgem/ext/newgem/newgem.h.tt +3 -3
  141. data/lib/bundler/templates/newgem/gitignore.tt +5 -1
  142. data/lib/bundler/templates/newgem/lib/newgem.rb.tt +7 -6
  143. data/lib/bundler/templates/newgem/lib/newgem/version.rb.tt +4 -4
  144. data/lib/bundler/templates/newgem/newgem.gemspec.tt +31 -15
  145. data/lib/bundler/templates/newgem/rspec.tt +1 -0
  146. data/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +3 -5
  147. data/lib/bundler/templates/newgem/spec/spec_helper.rb.tt +14 -2
  148. data/lib/bundler/templates/newgem/test/{test_newgem.rb.tt → newgem_test.rb.tt} +2 -2
  149. data/lib/bundler/templates/newgem/test/test_helper.rb.tt +4 -0
  150. data/lib/bundler/templates/newgem/travis.yml.tt +7 -0
  151. data/lib/bundler/ui.rb +5 -3
  152. data/lib/bundler/ui/rg_proxy.rb +5 -7
  153. data/lib/bundler/ui/shell.rb +69 -18
  154. data/lib/bundler/ui/silent.rb +26 -1
  155. data/lib/bundler/uri_credentials_filter.rb +37 -0
  156. data/lib/bundler/vendor/fileutils/lib/fileutils.rb +1638 -0
  157. data/lib/bundler/vendor/molinillo/lib/molinillo.rb +12 -0
  158. data/lib/bundler/vendor/molinillo/lib/molinillo/compatibility.rb +26 -0
  159. data/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb +57 -0
  160. data/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb +81 -0
  161. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb +223 -0
  162. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb +36 -0
  163. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb +66 -0
  164. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb +62 -0
  165. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb +63 -0
  166. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb +61 -0
  167. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb +126 -0
  168. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb +46 -0
  169. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb +36 -0
  170. data/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb +136 -0
  171. data/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb +143 -0
  172. data/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb +6 -0
  173. data/lib/bundler/vendor/{Molinillo-0.2.1 → molinillo}/lib/molinillo/modules/specification_provider.rb +11 -0
  174. data/lib/bundler/vendor/{Molinillo-0.2.1 → molinillo}/lib/molinillo/modules/ui.rb +6 -2
  175. data/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb +837 -0
  176. data/lib/bundler/vendor/{Molinillo-0.2.1 → molinillo}/lib/molinillo/resolver.rb +6 -3
  177. data/lib/bundler/vendor/molinillo/lib/molinillo/state.rb +58 -0
  178. data/lib/bundler/vendor/{net → net-http-persistent/lib/net}/http/faster.rb +1 -0
  179. data/lib/bundler/vendor/{net → net-http-persistent/lib/net}/http/persistent.rb +27 -24
  180. data/lib/bundler/vendor/{net → net-http-persistent/lib/net}/http/persistent/ssl_reuse.rb +2 -1
  181. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor.rb +47 -22
  182. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/actions.rb +31 -29
  183. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/actions/create_file.rb +3 -2
  184. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/actions/create_link.rb +3 -2
  185. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/actions/directory.rb +3 -3
  186. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/actions/empty_directory.rb +16 -8
  187. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/actions/file_manipulation.rb +66 -18
  188. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/actions/inject_into_file.rb +18 -16
  189. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/base.rb +67 -44
  190. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/command.rb +13 -11
  191. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/core_ext/hash_with_indifferent_access.rb +21 -1
  192. data/lib/bundler/vendor/thor/lib/thor/core_ext/io_binary_read.rb +12 -0
  193. data/lib/bundler/vendor/thor/lib/thor/core_ext/ordered_hash.rb +129 -0
  194. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/error.rb +3 -3
  195. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/group.rb +14 -14
  196. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/invocation.rb +4 -5
  197. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/line_editor.rb +2 -2
  198. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/line_editor/basic.rb +2 -0
  199. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/line_editor/readline.rb +0 -0
  200. data/lib/bundler/vendor/thor/lib/thor/parser.rb +4 -0
  201. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/parser/argument.rb +4 -7
  202. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/parser/arguments.rb +16 -16
  203. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/parser/option.rb +42 -21
  204. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/parser/options.rb +13 -10
  205. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/rake_compat.rb +1 -1
  206. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/runner.rb +35 -33
  207. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/shell.rb +4 -4
  208. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/shell/basic.rb +49 -33
  209. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/shell/color.rb +2 -2
  210. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/shell/html.rb +5 -5
  211. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/util.rb +8 -7
  212. data/lib/bundler/vendor/{thor-0.19.1 → thor}/lib/thor/version.rb +1 -1
  213. data/lib/bundler/vendored_fileutils.rb +9 -0
  214. data/lib/bundler/vendored_molinillo.rb +4 -5
  215. data/lib/bundler/vendored_persistent.rb +45 -4
  216. data/lib/bundler/vendored_thor.rb +8 -5
  217. data/lib/bundler/version.rb +23 -1
  218. data/lib/bundler/version_ranges.rb +76 -0
  219. data/lib/bundler/vlad.rb +8 -2
  220. data/lib/bundler/worker.rb +39 -6
  221. data/lib/bundler/yaml_serializer.rb +90 -0
  222. data/man/bundle-add.1 +58 -0
  223. data/man/bundle-add.1.txt +52 -0
  224. data/man/bundle-add.ronn +40 -0
  225. data/man/bundle-binstubs.1 +40 -0
  226. data/man/bundle-binstubs.1.txt +48 -0
  227. data/man/bundle-binstubs.ronn +43 -0
  228. data/man/bundle-check.1 +31 -0
  229. data/man/bundle-check.1.txt +33 -0
  230. data/man/bundle-check.ronn +26 -0
  231. data/man/bundle-clean.1 +24 -0
  232. data/man/bundle-clean.1.txt +26 -0
  233. data/man/bundle-clean.ronn +18 -0
  234. data/man/bundle-config.1 +497 -0
  235. data/man/bundle-config.1.txt +529 -0
  236. data/man/bundle-config.ronn +256 -31
  237. data/man/bundle-doctor.1 +44 -0
  238. data/man/bundle-doctor.1.txt +44 -0
  239. data/man/bundle-doctor.ronn +33 -0
  240. data/man/bundle-exec.1 +165 -0
  241. data/man/bundle-exec.1.txt +178 -0
  242. data/man/bundle-exec.ronn +19 -3
  243. data/man/bundle-gem.1 +80 -0
  244. data/man/bundle-gem.1.txt +91 -0
  245. data/man/bundle-gem.ronn +78 -0
  246. data/man/bundle-info.1 +20 -0
  247. data/man/bundle-info.1.txt +21 -0
  248. data/man/bundle-info.ronn +17 -0
  249. data/man/bundle-init.1 +25 -0
  250. data/man/bundle-init.1.txt +34 -0
  251. data/man/bundle-init.ronn +29 -0
  252. data/man/bundle-inject.1 +33 -0
  253. data/man/bundle-inject.1.txt +32 -0
  254. data/man/bundle-inject.ronn +22 -0
  255. data/man/bundle-install.1 +308 -0
  256. data/man/bundle-install.1.txt +396 -0
  257. data/man/bundle-install.ronn +64 -67
  258. data/man/bundle-list.1 +50 -0
  259. data/man/bundle-list.1.txt +43 -0
  260. data/man/bundle-list.ronn +33 -0
  261. data/man/bundle-lock.1 +84 -0
  262. data/man/bundle-lock.1.txt +93 -0
  263. data/man/bundle-lock.ronn +94 -0
  264. data/man/bundle-open.1 +32 -0
  265. data/man/bundle-open.1.txt +29 -0
  266. data/man/bundle-open.ronn +19 -0
  267. data/man/bundle-outdated.1 +155 -0
  268. data/man/bundle-outdated.1.txt +131 -0
  269. data/man/bundle-outdated.ronn +111 -0
  270. data/man/bundle-package.1 +55 -0
  271. data/man/bundle-package.1.txt +79 -0
  272. data/man/bundle-package.ronn +14 -8
  273. data/man/bundle-platform.1 +61 -0
  274. data/man/bundle-platform.1.txt +57 -0
  275. data/man/bundle-platform.ronn +1 -1
  276. data/man/bundle-pristine.1 +34 -0
  277. data/man/bundle-pristine.1.txt +44 -0
  278. data/man/bundle-pristine.ronn +34 -0
  279. data/man/bundle-remove.1 +31 -0
  280. data/man/bundle-remove.1.txt +34 -0
  281. data/man/bundle-remove.ronn +23 -0
  282. data/man/bundle-show.1 +23 -0
  283. data/man/bundle-show.1.txt +27 -0
  284. data/man/bundle-show.ronn +21 -0
  285. data/man/bundle-update.1 +394 -0
  286. data/man/bundle-update.1.txt +391 -0
  287. data/man/bundle-update.ronn +180 -18
  288. data/man/bundle-viz.1 +39 -0
  289. data/man/bundle-viz.1.txt +39 -0
  290. data/man/bundle-viz.ronn +30 -0
  291. data/man/bundle.1 +136 -0
  292. data/man/bundle.1.txt +116 -0
  293. data/man/bundle.ronn +46 -33
  294. data/man/gemfile.5 +689 -0
  295. data/man/gemfile.5.ronn +127 -79
  296. data/man/gemfile.5.txt +653 -0
  297. data/man/index.txt +25 -7
  298. metadata +242 -95
  299. data/.gitignore +0 -16
  300. data/.rspec +0 -3
  301. data/.travis.yml +0 -110
  302. data/CODE_OF_CONDUCT.md +0 -40
  303. data/CONTRIBUTING.md +0 -32
  304. data/DEVELOPMENT.md +0 -119
  305. data/ISSUES.md +0 -96
  306. data/Rakefile +0 -302
  307. data/UPGRADING.md +0 -103
  308. data/bin/bundle +0 -21
  309. data/bin/bundler +0 -21
  310. data/lib/bundler/anonymizable_uri.rb +0 -32
  311. data/lib/bundler/environment.rb +0 -42
  312. data/lib/bundler/gem_installer.rb +0 -9
  313. data/lib/bundler/gem_path_manipulation.rb +0 -8
  314. data/lib/bundler/ssl_certs/AddTrustExternalCARoot.pem +0 -32
  315. data/lib/bundler/ssl_certs/Class3PublicPrimaryCertificationAuthority.pem +0 -14
  316. data/lib/bundler/ssl_certs/EntrustnetSecureServerCertificationAuthority.pem +0 -28
  317. data/lib/bundler/ssl_certs/GeoTrustGlobalCA.pem +0 -20
  318. data/lib/bundler/templates/newgem/.travis.yml.tt +0 -3
  319. data/lib/bundler/templates/newgem/test/minitest_helper.rb.tt +0 -4
  320. data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo.rb +0 -5
  321. data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/dependency_graph.rb +0 -266
  322. data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/errors.rb +0 -69
  323. data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/gem_metadata.rb +0 -3
  324. data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/resolution.rb +0 -412
  325. data/lib/bundler/vendor/Molinillo-0.2.1/lib/molinillo/state.rb +0 -43
  326. data/lib/bundler/vendor/thor-0.19.1/lib/thor/core_ext/io_binary_read.rb +0 -10
  327. data/lib/bundler/vendor/thor-0.19.1/lib/thor/core_ext/ordered_hash.rb +0 -98
  328. data/lib/bundler/vendor/thor-0.19.1/lib/thor/parser.rb +0 -4
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler::Molinillo
4
+ # An error that occurred during the resolution process
5
+ class ResolverError < StandardError; end
6
+
7
+ # An error caused by searching for a dependency that is completely unknown,
8
+ # i.e. has no versions available whatsoever.
9
+ class NoSuchDependencyError < ResolverError
10
+ # @return [Object] the dependency that could not be found
11
+ attr_accessor :dependency
12
+
13
+ # @return [Array<Object>] the specifications that depended upon {#dependency}
14
+ attr_accessor :required_by
15
+
16
+ # Initializes a new error with the given missing dependency.
17
+ # @param [Object] dependency @see {#dependency}
18
+ # @param [Array<Object>] required_by @see {#required_by}
19
+ def initialize(dependency, required_by = [])
20
+ @dependency = dependency
21
+ @required_by = required_by.uniq
22
+ super()
23
+ end
24
+
25
+ # The error message for the missing dependency, including the specifications
26
+ # that had this dependency.
27
+ def message
28
+ sources = required_by.map { |r| "`#{r}`" }.join(' and ')
29
+ message = "Unable to find a specification for `#{dependency}`"
30
+ message += " depended upon by #{sources}" unless sources.empty?
31
+ message
32
+ end
33
+ end
34
+
35
+ # An error caused by attempting to fulfil a dependency that was circular
36
+ #
37
+ # @note This exception will be thrown iff a {Vertex} is added to a
38
+ # {DependencyGraph} that has a {DependencyGraph::Vertex#path_to?} an
39
+ # existing {DependencyGraph::Vertex}
40
+ class CircularDependencyError < ResolverError
41
+ # [Set<Object>] the dependencies responsible for causing the error
42
+ attr_reader :dependencies
43
+
44
+ # Initializes a new error with the given circular vertices.
45
+ # @param [Array<DependencyGraph::Vertex>] vertices the vertices in the dependency
46
+ # that caused the error
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
50
+ end
51
+ end
52
+
53
+ # An error caused by conflicts in version
54
+ class VersionConflict < ResolverError
55
+ # @return [{String => Resolution::Conflict}] the conflicts that caused
56
+ # resolution to fail
57
+ attr_reader :conflicts
58
+
59
+ # @return [SpecificationProvider] the specification provider used during
60
+ # resolution
61
+ attr_reader :specification_provider
62
+
63
+ # Initializes a new error with the given version conflicts.
64
+ # @param [{String => Resolution::Conflict}] conflicts see {#conflicts}
65
+ # @param [SpecificationProvider] specification_provider see {#specification_provider}
66
+ def initialize(conflicts, specification_provider)
67
+ pairs = []
68
+ Compatibility.flat_map(conflicts.values.flatten, &:requirements).each do |conflicting|
69
+ conflicting.each do |source, conflict_requirements|
70
+ conflict_requirements.each do |c|
71
+ pairs << [c, source]
72
+ end
73
+ end
74
+ end
75
+
76
+ super "Unable to satisfy the following requirements:\n\n" \
77
+ "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}"
78
+
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
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler::Molinillo
4
+ # The version of Bundler::Molinillo.
5
+ VERSION = '0.6.6'.freeze
6
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bundler::Molinillo
2
4
  # Provides information about specifcations and dependencies to the resolver,
3
5
  # allowing the {Resolver} class to remain generic while still providing power
@@ -86,5 +88,14 @@ module Bundler::Molinillo
86
88
  ]
87
89
  end
88
90
  end
91
+
92
+ # Returns whether this dependency, which has no possible matching
93
+ # specifications, can safely be ignored.
94
+ #
95
+ # @param [Object] dependency
96
+ # @return [Boolean] whether this dependency can safely be skipped.
97
+ def allow_missing?(dependency)
98
+ false
99
+ end
89
100
  end
90
101
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bundler::Molinillo
2
4
  # Conveys information about the resolution process to a user.
3
5
  module UI
@@ -47,7 +49,8 @@ module Bundler::Molinillo
47
49
  if debug?
48
50
  debug_info = yield
49
51
  debug_info = debug_info.inspect unless debug_info.is_a?(String)
50
- 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
51
54
  end
52
55
  end
53
56
 
@@ -57,7 +60,8 @@ module Bundler::Molinillo
57
60
  #
58
61
  # @return [Boolean]
59
62
  def debug?
60
- @debug_mode ||= ENV['MOLINILLO_DEBUG']
63
+ return @debug_mode if defined?(@debug_mode)
64
+ @debug_mode = ENV['MOLINILLO_DEBUG']
61
65
  end
62
66
  end
63
67
  end
@@ -0,0 +1,837 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler::Molinillo
4
+ class Resolver
5
+ # A specific resolution from a given {Resolver}
6
+ class Resolution
7
+ # A conflict that the resolution process encountered
8
+ # @attr [Object] requirement the requirement that immediately led to the conflict
9
+ # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict
10
+ # @attr [Object, nil] existing the existing spec that was in conflict with
11
+ # the {#possibility}
12
+ # @attr [Object] possibility_set the set of specs that was unable to be
13
+ # activated due to a conflict.
14
+ # @attr [Object] locked_requirement the relevant locking requirement.
15
+ # @attr [Array<Array<Object>>] requirement_trees the different requirement
16
+ # trees that led to every requirement for the conflicting name.
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.
20
+ Conflict = Struct.new(
21
+ :requirement,
22
+ :requirements,
23
+ :existing,
24
+ :possibility_set,
25
+ :locked_requirement,
26
+ :requirement_trees,
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
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
+
131
+ # @return [SpecificationProvider] the provider that knows about
132
+ # dependencies, requirements, specifications, versions, etc.
133
+ attr_reader :specification_provider
134
+
135
+ # @return [UI] the UI that knows how to communicate feedback about the
136
+ # resolution process back to the user
137
+ attr_reader :resolver_ui
138
+
139
+ # @return [DependencyGraph] the base dependency graph to which
140
+ # dependencies should be 'locked'
141
+ attr_reader :base
142
+
143
+ # @return [Array] the dependencies that were explicitly required
144
+ attr_reader :original_requested
145
+
146
+ # Initializes a new resolution.
147
+ # @param [SpecificationProvider] specification_provider
148
+ # see {#specification_provider}
149
+ # @param [UI] resolver_ui see {#resolver_ui}
150
+ # @param [Array] requested see {#original_requested}
151
+ # @param [DependencyGraph] base see {#base}
152
+ def initialize(specification_provider, resolver_ui, requested, base)
153
+ @specification_provider = specification_provider
154
+ @resolver_ui = resolver_ui
155
+ @original_requested = requested
156
+ @base = base
157
+ @states = []
158
+ @iteration_counter = 0
159
+ @parents_of = Hash.new { |h, k| h[k] = [] }
160
+ end
161
+
162
+ # Resolves the {#original_requested} dependencies into a full dependency
163
+ # graph
164
+ # @raise [ResolverError] if successful resolution is impossible
165
+ # @return [DependencyGraph] the dependency graph of successfully resolved
166
+ # dependencies
167
+ def resolve
168
+ start_resolution
169
+
170
+ while state
171
+ break if !state.requirement && state.requirements.empty?
172
+ indicate_progress
173
+ if state.respond_to?(:pop_possibility_state) # DependencyState
174
+ debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" }
175
+ state.pop_possibility_state.tap do |s|
176
+ if s
177
+ states.push(s)
178
+ activated.tag(s)
179
+ end
180
+ end
181
+ end
182
+ process_topmost_state
183
+ end
184
+
185
+ resolve_activated_specs
186
+ ensure
187
+ end_resolution
188
+ end
189
+
190
+ # @return [Integer] the number of resolver iterations in between calls to
191
+ # {#resolver_ui}'s {UI#indicate_progress} method
192
+ attr_accessor :iteration_rate
193
+ private :iteration_rate
194
+
195
+ # @return [Time] the time at which resolution began
196
+ attr_accessor :started_at
197
+ private :started_at
198
+
199
+ # @return [Array<ResolutionState>] the stack of states for the resolution
200
+ attr_accessor :states
201
+ private :states
202
+
203
+ private
204
+
205
+ # Sets up the resolution process
206
+ # @return [void]
207
+ def start_resolution
208
+ @started_at = Time.now
209
+
210
+ handle_missing_or_push_dependency_state(initial_state)
211
+
212
+ debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" }
213
+ resolver_ui.before_resolution
214
+ end
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
+
229
+ # Ends the resolution process
230
+ # @return [void]
231
+ def end_resolution
232
+ resolver_ui.after_resolution
233
+ debug do
234
+ "Finished resolution (#{@iteration_counter} steps) " \
235
+ "(Took #{(ended_at = Time.now) - @started_at} seconds) (#{ended_at})"
236
+ end
237
+ debug { 'Unactivated: ' + Hash[activated.vertices.reject { |_n, v| v.payload }].keys.join(', ') } if state
238
+ debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state
239
+ end
240
+
241
+ require 'bundler/vendor/molinillo/lib/molinillo/state'
242
+ require 'bundler/vendor/molinillo/lib/molinillo/modules/specification_provider'
243
+
244
+ require 'bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state'
245
+ require 'bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider'
246
+
247
+ include Bundler::Molinillo::Delegates::ResolutionState
248
+ include Bundler::Molinillo::Delegates::SpecificationProvider
249
+
250
+ # Processes the topmost available {RequirementState} on the stack
251
+ # @return [void]
252
+ def process_topmost_state
253
+ if possibility
254
+ attempt_to_activate
255
+ else
256
+ create_conflict
257
+ unwind_for_conflict
258
+ end
259
+ rescue CircularDependencyError => underlying_error
260
+ create_conflict(underlying_error)
261
+ unwind_for_conflict
262
+ end
263
+
264
+ # @return [Object] the current possibility that the resolution is trying
265
+ # to activate
266
+ def possibility
267
+ possibilities.last
268
+ end
269
+
270
+ # @return [RequirementState] the current state the resolution is
271
+ # operating upon
272
+ def state
273
+ states.last
274
+ end
275
+
276
+ # Creates the initial state for the resolution, based upon the
277
+ # {#requested} dependencies
278
+ # @return [DependencyState] the initial state for the resolution
279
+ def initial_state
280
+ graph = DependencyGraph.new.tap do |dg|
281
+ original_requested.each do |requested|
282
+ vertex = dg.add_vertex(name_for(requested), nil, true)
283
+ vertex.explicit_requirements << requested
284
+ end
285
+ dg.tag(:initial_state)
286
+ end
287
+
288
+ requirements = sort_dependencies(original_requested, graph, {})
289
+ initial_requirement = requirements.shift
290
+ DependencyState.new(
291
+ initial_requirement && name_for(initial_requirement),
292
+ requirements,
293
+ graph,
294
+ initial_requirement,
295
+ possibilities_for_requirement(initial_requirement, graph),
296
+ 0,
297
+ {},
298
+ []
299
+ )
300
+ end
301
+
302
+ # Unwinds the states stack because a conflict has been encountered
303
+ # @return [void]
304
+ def unwind_for_conflict
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}" }
308
+ conflicts.tap do |c|
309
+ sliced_states = states.slice!((details_for_unwind.state_index + 1)..-1)
310
+ raise_error_unless_state(c)
311
+ activated.rewind_to(sliced_states.first || :initial_state) if sliced_states
312
+ state.conflicts = c
313
+ state.unused_unwind_options = unwind_options
314
+ filter_possibilities_after_unwind(details_for_unwind)
315
+ index = states.size - 1
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
+ 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
509
+ end
510
+
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])
568
+ end
569
+ end
570
+
571
+ # @return [Object] the requirement that led to `requirement` being added
572
+ # to the list of requirements.
573
+ def parent_of(requirement)
574
+ return unless requirement
575
+ return unless index = @parents_of[requirement].last
576
+ return unless parent_state = @states[index]
577
+ parent_state.requirement
578
+ end
579
+
580
+ # @return [Object] the requirement that led to a version of a possibility
581
+ # with the given name being activated.
582
+ def requirement_for_existing_name(name)
583
+ return nil unless vertex = activated.vertex_named(name)
584
+ return nil unless vertex.payload
585
+ states.find { |s| s.name == name }.requirement
586
+ end
587
+
588
+ # @return [ResolutionState] the state whose `requirement` is the given
589
+ # `requirement`.
590
+ def find_state_for(requirement)
591
+ return nil unless requirement
592
+ states.find { |i| requirement == i.requirement }
593
+ end
594
+
595
+ # @return [Conflict] a {Conflict} that reflects the failure to activate
596
+ # the {#possibility} in conjunction with the current {#state}
597
+ def create_conflict(underlying_error = nil)
598
+ vertex = activated.vertex_named(name)
599
+ locked_requirement = locked_requirement_named(name)
600
+
601
+ requirements = {}
602
+ unless vertex.explicit_requirements.empty?
603
+ requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements
604
+ end
605
+ requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement
606
+ vertex.incoming_edges.each do |edge|
607
+ (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement)
608
+ end
609
+
610
+ activated_by_name = {}
611
+ activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload }
612
+ conflicts[name] = Conflict.new(
613
+ requirement,
614
+ requirements,
615
+ vertex.payload && vertex.payload.latest_version,
616
+ possibility,
617
+ locked_requirement,
618
+ requirement_trees,
619
+ activated_by_name,
620
+ underlying_error
621
+ )
622
+ end
623
+
624
+ # @return [Array<Array<Object>>] The different requirement
625
+ # trees that led to every requirement for the current spec.
626
+ def requirement_trees
627
+ vertex = activated.vertex_named(name)
628
+ vertex.requirements.map { |r| requirement_tree_for(r) }
629
+ end
630
+
631
+ # @return [Array<Object>] the list of requirements that led to
632
+ # `requirement` being required.
633
+ def requirement_tree_for(requirement)
634
+ tree = []
635
+ while requirement
636
+ tree.unshift(requirement)
637
+ requirement = parent_of(requirement)
638
+ end
639
+ tree
640
+ end
641
+
642
+ # Indicates progress roughly once every second
643
+ # @return [void]
644
+ def indicate_progress
645
+ @iteration_counter += 1
646
+ @progress_rate ||= resolver_ui.progress_rate
647
+ if iteration_rate.nil?
648
+ if Time.now - started_at >= @progress_rate
649
+ self.iteration_rate = @iteration_counter
650
+ end
651
+ end
652
+
653
+ if iteration_rate && (@iteration_counter % iteration_rate) == 0
654
+ resolver_ui.indicate_progress
655
+ end
656
+ end
657
+
658
+ # Calls the {#resolver_ui}'s {UI#debug} method
659
+ # @param [Integer] depth the depth of the {#states} stack
660
+ # @param [Proc] block a block that yields a {#to_s}
661
+ # @return [void]
662
+ def debug(depth = 0, &block)
663
+ resolver_ui.debug(depth, &block)
664
+ end
665
+
666
+ # Attempts to activate the current {#possibility}
667
+ # @return [void]
668
+ def attempt_to_activate
669
+ debug(depth) { 'Attempting to activate ' + possibility.to_s }
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)
674
+ else
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
688
+ end
689
+ end
690
+
691
+ # Attempts to update the existing vertex's `PossibilitySet` with a filtered version
692
+ # @return [void]
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)
697
+ new_requirements = requirements.dup
698
+ push_state_for_requirements(new_requirements, false)
699
+ else
700
+ create_conflict
701
+ debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" }
702
+ unwind_for_conflict
703
+ end
704
+ end
705
+
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)
712
+ end
713
+
714
+ # @param [String] requirement_name the spec name to search for
715
+ # @return [Object] the locked spec named `requirement_name`, if one
716
+ # is found on {#base}
717
+ def locked_requirement_named(requirement_name)
718
+ vertex = base.vertex_named(requirement_name)
719
+ vertex && vertex.payload
720
+ end
721
+
722
+ # Add the current {#possibility} to the dependency graph of the current
723
+ # {#state}
724
+ # @return [void]
725
+ def activate_new_spec
726
+ conflicts.delete(name)
727
+ debug(depth) { "Activated #{name} at #{possibility}" }
728
+ activated.set_payload(name, possibility)
729
+ require_nested_dependencies_for(possibility)
730
+ end
731
+
732
+ # Requires the dependencies that the recently activated spec has
733
+ # @param [Object] activated_possibility the PossibilitySet that has just been
734
+ # activated
735
+ # @return [void]
736
+ def require_nested_dependencies_for(possibility_set)
737
+ nested_dependencies = dependencies_for(possibility_set.latest_version)
738
+ debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" }
739
+ nested_dependencies.each do |d|
740
+ activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d)
741
+ parent_index = states.size - 1
742
+ parents = @parents_of[d]
743
+ parents << parent_index if parents.empty?
744
+ end
745
+
746
+ push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?)
747
+ end
748
+
749
+ # Pushes a new {DependencyState} that encapsulates both existing and new
750
+ # requirements
751
+ # @param [Array] new_requirements
752
+ # @return [void]
753
+ def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated)
754
+ new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort
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
760
+ new_name = new_requirement ? name_for(new_requirement) : ''.freeze
761
+ possibilities = possibilities_for_requirement(new_requirement)
762
+ handle_missing_or_push_dependency_state DependencyState.new(
763
+ new_name, new_requirements, new_activated,
764
+ new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup
765
+ )
766
+ end
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
+
819
+ # Pushes a new {DependencyState}.
820
+ # If the {#specification_provider} says to
821
+ # {SpecificationProvider#allow_missing?} that particular requirement, and
822
+ # there are no possibilities for that requirement, then `state` is not
823
+ # pushed, and the vertex in {#activated} is removed, and we continue
824
+ # resolving the remaining requirements.
825
+ # @param [DependencyState] state
826
+ # @return [void]
827
+ def handle_missing_or_push_dependency_state(state)
828
+ if state.requirement && state.possibilities.empty? && allow_missing?(state.requirement)
829
+ state.activated.detach_vertex_named(state.name)
830
+ push_state_for_requirements(state.requirements.dup, false, state.activated)
831
+ else
832
+ states.push(state).tap { activated.tag(state) }
833
+ end
834
+ end
835
+ end
836
+ end
837
+ end