bundler 4.0.21 → 4.1.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. checksums.yaml +4 -4
  2. data/{lib/bundler/vendor/connection_pool/LICENSE → MIT.txt} +8 -6
  3. data/bundler.gemspec +23 -6
  4. data/lib/bundler/build_metadata.rb +2 -2
  5. data/lib/bundler/checksum.rb +40 -25
  6. data/lib/bundler/ci_detector.rb +1 -1
  7. data/lib/bundler/cli/add.rb +3 -4
  8. data/lib/bundler/cli/cache.rb +4 -0
  9. data/lib/bundler/cli/clean.rb +5 -0
  10. data/lib/bundler/cli/common.rb +37 -1
  11. data/lib/bundler/cli/config.rb +4 -2
  12. data/lib/bundler/cli/gem.rb +0 -17
  13. data/lib/bundler/cli/install.rb +4 -5
  14. data/lib/bundler/cli/lock.rb +1 -2
  15. data/lib/bundler/cli/outdated.rb +8 -3
  16. data/lib/bundler/cli/pristine.rb +2 -1
  17. data/lib/bundler/cli/update.rb +21 -12
  18. data/lib/bundler/cli.rb +14 -11
  19. data/lib/bundler/definition.rb +61 -24
  20. data/lib/bundler/dependency.rb +4 -0
  21. data/lib/bundler/dsl.rb +77 -11
  22. data/lib/bundler/endpoint_specification.rb +51 -14
  23. data/lib/bundler/env.rb +7 -24
  24. data/lib/bundler/errors.rb +34 -5
  25. data/lib/bundler/fetcher/compact_index.rb +14 -5
  26. data/lib/bundler/fetcher/connection_pools.rb +150 -0
  27. data/lib/bundler/fetcher/downloader.rb +31 -16
  28. data/lib/bundler/fetcher.rb +8 -47
  29. data/lib/bundler/injector.rb +3 -3
  30. data/lib/bundler/inline.rb +44 -4
  31. data/lib/bundler/installer/parallel_installer.rb +25 -30
  32. data/lib/bundler/installer.rb +20 -6
  33. data/lib/bundler/lazy_specification.rb +44 -17
  34. data/lib/bundler/lockfile_generator.rb +24 -2
  35. data/lib/bundler/lockfile_parser.rb +43 -5
  36. data/lib/bundler/man/bundle-add.1 +7 -4
  37. data/lib/bundler/man/bundle-add.1.ronn +8 -4
  38. data/lib/bundler/man/bundle-binstubs.1 +1 -1
  39. data/lib/bundler/man/bundle-cache.1 +2 -2
  40. data/lib/bundler/man/bundle-cache.1.ronn +2 -2
  41. data/lib/bundler/man/bundle-check.1 +1 -1
  42. data/lib/bundler/man/bundle-clean.1 +1 -1
  43. data/lib/bundler/man/bundle-config.1 +38 -6
  44. data/lib/bundler/man/bundle-config.1.ronn +143 -10
  45. data/lib/bundler/man/bundle-console.1 +1 -1
  46. data/lib/bundler/man/bundle-doctor.1 +1 -1
  47. data/lib/bundler/man/bundle-env.1 +1 -1
  48. data/lib/bundler/man/bundle-exec.1 +1 -1
  49. data/lib/bundler/man/bundle-fund.1 +1 -1
  50. data/lib/bundler/man/bundle-gem.1 +1 -1
  51. data/lib/bundler/man/bundle-help.1 +1 -1
  52. data/lib/bundler/man/bundle-info.1 +1 -1
  53. data/lib/bundler/man/bundle-init.1 +1 -1
  54. data/lib/bundler/man/bundle-install.1 +2 -2
  55. data/lib/bundler/man/bundle-install.1.ronn +4 -4
  56. data/lib/bundler/man/bundle-issue.1 +1 -1
  57. data/lib/bundler/man/bundle-licenses.1 +1 -1
  58. data/lib/bundler/man/bundle-list.1 +1 -1
  59. data/lib/bundler/man/bundle-lock.1 +3 -3
  60. data/lib/bundler/man/bundle-lock.1.ronn +5 -5
  61. data/lib/bundler/man/bundle-open.1 +1 -1
  62. data/lib/bundler/man/bundle-outdated.1 +1 -1
  63. data/lib/bundler/man/bundle-platform.1 +1 -1
  64. data/lib/bundler/man/bundle-plugin.1 +1 -1
  65. data/lib/bundler/man/bundle-pristine.1 +1 -1
  66. data/lib/bundler/man/bundle-remove.1 +1 -1
  67. data/lib/bundler/man/bundle-show.1 +1 -1
  68. data/lib/bundler/man/bundle-update.1 +3 -3
  69. data/lib/bundler/man/bundle-update.1.ronn +3 -5
  70. data/lib/bundler/man/bundle-version.1 +1 -1
  71. data/lib/bundler/man/bundle.1 +1 -1
  72. data/lib/bundler/man/gemfile.5 +45 -1
  73. data/lib/bundler/man/gemfile.5.ronn +53 -0
  74. data/lib/bundler/match_metadata.rb +20 -0
  75. data/lib/bundler/match_platform.rb +25 -2
  76. data/lib/bundler/match_remote_metadata.rb +21 -6
  77. data/lib/bundler/materialization.rb +2 -1
  78. data/lib/bundler/override.rb +69 -0
  79. data/lib/bundler/plugin/dsl.rb +3 -5
  80. data/lib/bundler/plugin/events.rb +55 -13
  81. data/lib/bundler/plugin/index.rb +87 -11
  82. data/lib/bundler/plugin/installer/path.rb +1 -1
  83. data/lib/bundler/plugin/installer.rb +13 -5
  84. data/lib/bundler/plugin.rb +58 -30
  85. data/lib/bundler/remote_specification.rb +6 -3
  86. data/lib/bundler/resolver/base.rb +2 -1
  87. data/lib/bundler/resolver/incompatibility.rb +1 -1
  88. data/lib/bundler/resolver.rb +73 -23
  89. data/lib/bundler/ruby_version.rb +0 -2
  90. data/lib/bundler/rubygems_ext.rb +71 -1
  91. data/lib/bundler/rubygems_gem_installer.rb +41 -2
  92. data/lib/bundler/rubygems_integration.rb +7 -2
  93. data/lib/bundler/runtime.rb +69 -2
  94. data/lib/bundler/self_manager.rb +9 -15
  95. data/lib/bundler/settings.rb +373 -30
  96. data/lib/bundler/shared_helpers.rb +2 -1
  97. data/lib/bundler/source/git/git_proxy.rb +2 -10
  98. data/lib/bundler/source/git.rb +15 -6
  99. data/lib/bundler/source/path/installer.rb +1 -1
  100. data/lib/bundler/source/rubygems/remote.rb +10 -5
  101. data/lib/bundler/source/rubygems.rb +48 -12
  102. data/lib/bundler/spec_set.rb +16 -4
  103. data/lib/bundler/stub_specification.rb +2 -1
  104. data/lib/bundler/templates/newgem/Gemfile.tt +4 -4
  105. data/lib/bundler/templates/newgem/newgem.gemspec.tt +3 -3
  106. data/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +1 -1
  107. data/lib/bundler/templates/newgem/test/minitest/test_newgem.rb.tt +1 -1
  108. data/lib/bundler/ui/shell.rb +0 -4
  109. data/lib/bundler/ui/silent.rb +0 -4
  110. data/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb +35 -2
  111. data/lib/bundler/vendor/thor/lib/thor/base.rb +2 -1
  112. data/lib/bundler/vendor/thor/lib/thor/shell/basic.rb +14 -6
  113. data/lib/bundler/vendor/thor/lib/thor/shell/color.rb +2 -0
  114. data/lib/bundler/vendor/thor/lib/thor/version.rb +1 -1
  115. data/lib/bundler/vendor/thor/lib/thor.rb +34 -1
  116. data/lib/bundler/vendored_pub_grub.rb +5 -2
  117. data/lib/bundler/vendored_securerandom.rb +11 -8
  118. data/lib/bundler/vendored_uri.rb +14 -14
  119. data/lib/bundler/version.rb +1 -1
  120. data/lib/bundler/worker.rb +3 -2
  121. data/lib/bundler.rb +1 -1
  122. data/lib/rubygems/compact_index_client/cache.rb +122 -0
  123. data/lib/rubygems/compact_index_client/cache_file.rb +147 -0
  124. data/lib/rubygems/compact_index_client/http_fetcher.rb +88 -0
  125. data/lib/rubygems/compact_index_client/parser.rb +90 -0
  126. data/lib/rubygems/compact_index_client/updater.rb +113 -0
  127. data/lib/rubygems/compact_index_client.rb +111 -0
  128. data/lib/rubygems/cooldown_settings.rb +57 -0
  129. data/lib/rubygems/credential_store/native/linux.rb +97 -0
  130. data/lib/rubygems/credential_store/native/macos.rb +88 -0
  131. data/lib/rubygems/credential_store/native/windows.rb +130 -0
  132. data/lib/rubygems/credential_store.rb +332 -0
  133. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/assignment.rb +1 -1
  134. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/basic_package_source.rb +7 -7
  135. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/failure_writer.rb +1 -1
  136. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/incompatibility.rb +4 -4
  137. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/package.rb +1 -1
  138. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/partial_solution.rb +1 -1
  139. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/rubygems.rb +3 -3
  140. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/solve_failure.rb +1 -1
  141. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/static_package_source.rb +3 -3
  142. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/strategy.rb +2 -2
  143. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/term.rb +1 -1
  144. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version.rb +1 -1
  145. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_constraint.rb +3 -3
  146. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_range.rb +1 -1
  147. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_solver.rb +2 -2
  148. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_union.rb +1 -1
  149. data/lib/rubygems/vendor/pub_grub/lib/pub_grub.rb +57 -0
  150. data/lib/{bundler → rubygems}/vendor/securerandom/lib/securerandom.rb +10 -6
  151. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/common.rb +119 -119
  152. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/file.rb +13 -13
  153. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ftp.rb +24 -24
  154. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/generic.rb +132 -132
  155. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/http.rb +18 -18
  156. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/https.rb +4 -4
  157. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ldap.rb +14 -14
  158. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ldaps.rb +4 -4
  159. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/mailto.rb +21 -21
  160. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/rfc2396_parser.rb +39 -39
  161. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/rfc3986_parser.rb +14 -14
  162. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/version.rb +1 -1
  163. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ws.rb +12 -12
  164. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/wss.rb +4 -4
  165. data/lib/{bundler → rubygems}/vendor/uri/lib/uri.rb +35 -31
  166. data/lib/rubygems/yaml_serializer.rb +880 -0
  167. metadata +77 -68
  168. data/CHANGELOG.md +0 -5784
  169. data/LICENSE.md +0 -22
  170. data/README.md +0 -58
  171. data/lib/bundler/compact_index_client/cache.rb +0 -107
  172. data/lib/bundler/compact_index_client/cache_file.rb +0 -148
  173. data/lib/bundler/compact_index_client/parser.rb +0 -87
  174. data/lib/bundler/compact_index_client/updater.rb +0 -105
  175. data/lib/bundler/compact_index_client.rb +0 -92
  176. data/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb +0 -227
  177. data/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb +0 -3
  178. data/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb +0 -56
  179. data/lib/bundler/vendor/connection_pool/lib/connection_pool.rb +0 -230
  180. data/lib/bundler/vendor/net-http-persistent/README.rdoc +0 -82
  181. data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/connection.rb +0 -41
  182. data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/pool.rb +0 -65
  183. data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb +0 -80
  184. data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb +0 -1153
  185. data/lib/bundler/vendor/pub_grub/lib/pub_grub.rb +0 -31
  186. data/lib/bundler/vendored_persistent.rb +0 -11
  187. data/lib/bundler/yaml_serializer.rb +0 -98
  188. /data/lib/{bundler → rubygems}/vendor/pub_grub/LICENSE.txt +0 -0
  189. /data/lib/{bundler → rubygems}/vendor/securerandom/COPYING +0 -0
  190. /data/lib/{bundler → rubygems}/vendor/uri/COPYING +0 -0
@@ -2,6 +2,15 @@
2
2
 
3
3
  require_relative "base"
4
4
  require_relative "../worker"
5
+ require "rubygems/compact_index_client"
6
+
7
+ # Route the client's filesystem access through Bundler's helper so
8
+ # permission problems raise friendly Bundler errors. The respond_to?
9
+ # check covers a host RubyGems that already loaded its own copy of the
10
+ # client from before the hook existed.
11
+ if Gem::CompactIndexClient.respond_to?(:filesystem_access=)
12
+ Gem::CompactIndexClient.filesystem_access = Bundler::SharedHelpers.method(:filesystem_access)
13
+ end
5
14
 
6
15
  module Bundler
7
16
  class Fetcher
@@ -11,12 +20,12 @@ module Bundler
11
20
  undef_method(method_name)
12
21
  define_method(method_name) do |*args, &blk|
13
22
  method.bind_call(self, *args, &blk)
14
- rescue NetworkDownError, CompactIndexClient::Updater::MismatchedChecksumError => e
23
+ rescue NetworkDownError, Gem::CompactIndexClient::Updater::MismatchedChecksumError => e
15
24
  raise HTTPError, e.message
16
25
  rescue AuthenticationRequiredError, BadAuthenticationError
17
26
  # Fail since we got a 401 from the server.
18
27
  raise
19
- rescue HTTPError => e
28
+ rescue HTTPError, Gem::CompactIndexClient::Error => e
20
29
  Bundler.ui.trace(e)
21
30
  nil
22
31
  end
@@ -35,7 +44,7 @@ module Bundler
35
44
  until remaining_gems.empty?
36
45
  log_specs { "Looking up gems #{remaining_gems.inspect}" }
37
46
  deps = fetch_gem_infos(remaining_gems).flatten(1)
38
- next_gems = deps.flat_map {|d| d[CompactIndexClient::INFO_DEPS].flat_map(&:first) }.uniq
47
+ next_gems = deps.flat_map {|d| d[Gem::CompactIndexClient::INFO_DEPS].flat_map(&:first) }.uniq
39
48
  deps.each {|dep| gem_info << dep }
40
49
  complete_gems.concat(deps.map(&:first)).uniq!
41
50
  remaining_gems = next_gems - complete_gems
@@ -53,7 +62,7 @@ module Bundler
53
62
  end
54
63
  # Read info file checksums out of /versions, so we can know if gems are up to date
55
64
  compact_index_client.available?
56
- rescue CompactIndexClient::Updater::MismatchedChecksumError => e
65
+ rescue Gem::CompactIndexClient::Updater::MismatchedChecksumError => e
57
66
  Bundler.ui.debug(e.message)
58
67
  nil
59
68
  end
@@ -75,7 +84,7 @@ module Bundler
75
84
  def compact_index_client
76
85
  @compact_index_client ||=
77
86
  SharedHelpers.filesystem_access(cache_path) do
78
- CompactIndexClient.new(cache_path, client_fetcher)
87
+ Gem::CompactIndexClient.new(cache_path, client_fetcher)
79
88
  end
80
89
  end
81
90
 
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems/request"
4
+ require "rubygems/remote_fetcher"
5
+
6
+ module Bundler
7
+ class Fetcher
8
+ # Hands out pools of configured Gem::Net::HTTP connections, compatible
9
+ # with the checkout/checkin interface that Gem::Request expects.
10
+ # Connection policy (proxy, SSL, timeouts) follows Bundler settings
11
+ # first, falling back to RubyGems configuration.
12
+ class ConnectionPools
13
+ # A fixed-size pool of connections to a single host, sharing the
14
+ # interface of Gem::Request::HTTPPool.
15
+ class Pool
16
+ attr_reader :cert_files, :proxy_uri
17
+
18
+ def initialize(connections, uri, proxy_uri, size)
19
+ @connections = connections
20
+ @uri = uri
21
+ @proxy_uri = proxy_uri
22
+ @cert_files = connections.cert_files
23
+ @queue = Thread::SizedQueue.new(size)
24
+ size.times { @queue.push(nil) }
25
+ end
26
+
27
+ def checkout
28
+ @queue.pop || @connections.build_connection(@uri, @proxy_uri)
29
+ end
30
+
31
+ def checkin(connection)
32
+ @queue.push(connection)
33
+ end
34
+ end
35
+
36
+ attr_reader :override_headers, :cert_files
37
+
38
+ def initialize(size:, timeout:)
39
+ @size = size
40
+ @timeout = timeout
41
+ @override_headers = {}
42
+ @cert_files = Gem::Request.get_cert_files
43
+ @pools = {}
44
+ @pool_mutex = Thread::Mutex.new
45
+ end
46
+
47
+ # Performs a GET request through Gem::Request, which handles resetting
48
+ # and retrying stale connections, and returns the raw response.
49
+ def request(uri, headers = nil)
50
+ Gem::Request.new(uri, Gem::Net::HTTP::Get, nil, pool_for(uri)).fetch do |request|
51
+ @override_headers.each {|key, value| request[key] = value }
52
+ headers&.each {|key, value| request[key] = value }
53
+ end
54
+ end
55
+
56
+ def pool_for(uri)
57
+ key = [uri.scheme, uri.hostname, uri.port]
58
+ @pool_mutex.synchronize do
59
+ @pools[key] ||= Pool.new(self, uri, proxy_for(uri), @size)
60
+ end
61
+ end
62
+
63
+ # The proxy that will be used for +uri+, or nil. RubyGems configuration
64
+ # takes precedence over the environment, like Gem::RemoteFetcher.
65
+ # Unlike Gem::Request, an https URI falls back to `http_proxy` when no
66
+ # https-specific proxy is set, which is what the previous
67
+ # net-http-persistent based transport did.
68
+ def proxy_for(uri)
69
+ proxy = if config_proxy = Gem.configuration[:http_proxy]
70
+ Gem::Request.proxy_uri(config_proxy)
71
+ else
72
+ env_proxy_for(uri)
73
+ end
74
+ return unless proxy
75
+ # NO_PROXY="*" bypasses the proxy for every host, which
76
+ # Gem::URI::Generic.use_proxy? does not understand.
77
+ return if no_proxy_env.strip == "*"
78
+ return unless Gem::URI::Generic.use_proxy?(uri.hostname, nil, uri.port, no_proxy_env)
79
+ proxy
80
+ end
81
+
82
+ def build_connection(uri, proxy_uri) # :nodoc:
83
+ args = [uri.hostname, uri.port]
84
+ args += if proxy_uri
85
+ [proxy_uri.hostname, proxy_uri.port,
86
+ Gem::UriFormatter.new(proxy_uri.user).unescape,
87
+ Gem::UriFormatter.new(proxy_uri.password).unescape]
88
+ else
89
+ [nil, nil]
90
+ end
91
+
92
+ connection = Gem::Request::ConnectionPools.client.new(*args)
93
+ configure_ssl(connection) if uri.scheme == "https"
94
+ connection.open_timeout = @timeout
95
+ connection.read_timeout = @timeout
96
+ # Stale connections are already resent by Gem::Request#perform_request,
97
+ # and Bundler::Retry retries whole requests, so the Gem::Net::HTTP
98
+ # level retry would only multiply the time spent on a timing out host.
99
+ connection.max_retries = 0
100
+ connection.start
101
+ connection
102
+ end
103
+
104
+ private
105
+
106
+ def env_proxy_for(uri)
107
+ proxy = Gem::Request.get_proxy_from_env(uri.scheme)
108
+ proxy = Gem::Request.get_proxy_from_env("http") if proxy == :no_proxy && uri.scheme == "https"
109
+ proxy == :no_proxy ? nil : proxy
110
+ end
111
+
112
+ def no_proxy_env
113
+ ENV["no_proxy"] || ENV["NO_PROXY"] || ""
114
+ end
115
+
116
+ def configure_ssl(connection)
117
+ connection.use_ssl = true
118
+ connection.verify_mode = Bundler.settings[:ssl_verify_mode] || OpenSSL::SSL::VERIFY_PEER
119
+ connection.cert_store = bundler_cert_store
120
+
121
+ ssl_client_cert = Bundler.settings[:ssl_client_cert] ||
122
+ (Gem.configuration.ssl_client_cert if
123
+ Gem.configuration.respond_to?(:ssl_client_cert))
124
+ return unless ssl_client_cert
125
+
126
+ pem = File.read(ssl_client_cert)
127
+ connection.cert = OpenSSL::X509::Certificate.new(pem)
128
+ connection.key = OpenSSL::PKey.read(pem)
129
+ end
130
+
131
+ def bundler_cert_store
132
+ store = OpenSSL::X509::Store.new
133
+ ssl_ca_cert = Bundler.settings[:ssl_ca_cert] ||
134
+ (Gem.configuration.ssl_ca_cert if
135
+ Gem.configuration.respond_to?(:ssl_ca_cert))
136
+ if ssl_ca_cert
137
+ if File.directory? ssl_ca_cert
138
+ store.add_path ssl_ca_cert
139
+ else
140
+ store.add_file ssl_ca_cert
141
+ end
142
+ else
143
+ store.set_default_paths
144
+ cert_files.each {|c| store.add_file c }
145
+ end
146
+ store
147
+ end
148
+ end
149
+ end
150
+ end
@@ -6,14 +6,19 @@ module Bundler
6
6
  HTTP_NON_RETRYABLE_ERRORS = [
7
7
  SocketError,
8
8
  Errno::EADDRNOTAVAIL,
9
+ Errno::ECONNREFUSED,
10
+ Errno::EHOSTDOWN,
11
+ Errno::EHOSTUNREACH,
9
12
  Errno::ENETDOWN,
10
13
  Errno::ENETUNREACH,
11
- Gem::Net::HTTP::Persistent::Error,
12
- Errno::EHOSTUNREACH,
13
14
  ].freeze
14
15
 
16
+ # The vendored net-http raises Gem::Timeout::Error, but when Gem::Net is
17
+ # the real Net (hosts without a vendored net-http), timeouts are plain
18
+ # Timeout::Error subclasses instead.
15
19
  HTTP_RETRYABLE_ERRORS = [
16
20
  Gem::Timeout::Error,
21
+ *(::Timeout::Error if defined?(::Timeout::Error)),
17
22
  EOFError,
18
23
  Errno::EINVAL,
19
24
  Errno::ECONNRESET,
@@ -25,11 +30,11 @@ module Bundler
25
30
  Zlib::BufError,
26
31
  ].freeze
27
32
 
28
- attr_reader :connection
33
+ attr_reader :connections
29
34
  attr_reader :redirect_limit
30
35
 
31
- def initialize(connection, redirect_limit)
32
- @connection = connection
36
+ def initialize(connections, redirect_limit)
37
+ @connections = connections
33
38
  @redirect_limit = redirect_limit
34
39
  end
35
40
 
@@ -84,23 +89,25 @@ module Bundler
84
89
  filtered_uri = URICredentialsFilter.credential_filtered_uri(uri)
85
90
 
86
91
  Bundler.ui.debug "HTTP GET #{filtered_uri}"
87
- req = Gem::Net::HTTP::Get.new uri.request_uri, headers
88
- if uri.user
89
- user = CGI.unescape(uri.user)
90
- password = uri.password ? CGI.unescape(uri.password) : nil
91
- req.basic_auth(user, password)
92
+ connections.request(uri, headers)
93
+ rescue Gem::RemoteFetcher::FetchError => e
94
+ Bundler.ui.trace e
95
+
96
+ case e.message
97
+ when /certificate verify failed/
98
+ raise CertificateFailureError.new(uri)
99
+ when /host is down|host down/i
100
+ raise network_down_error(uri, filtered_uri)
101
+ else
102
+ raise HTTPError, "Network error while fetching #{filtered_uri}" \
103
+ " (#{e})"
92
104
  end
93
- connection.request(uri, req)
94
105
  rescue OpenSSL::SSL::SSLError
95
106
  raise CertificateFailureError.new(uri)
96
107
  rescue *HTTP_NON_RETRYABLE_ERRORS => e
97
108
  Bundler.ui.trace e
98
109
 
99
- host = uri.host
100
- host_port = "#{host}:#{uri.port}"
101
- host = host_port if filtered_uri.to_s.include?(host_port)
102
- raise NetworkDownError, "Could not reach host #{host}. Check your network " \
103
- "connection and try again."
110
+ raise network_down_error(uri, filtered_uri)
104
111
  rescue *HTTP_RETRYABLE_ERRORS => e
105
112
  Bundler.ui.trace e
106
113
 
@@ -110,6 +117,14 @@ module Bundler
110
117
 
111
118
  private
112
119
 
120
+ def network_down_error(uri, filtered_uri)
121
+ host = uri.host
122
+ host_port = "#{host}:#{uri.port}"
123
+ host = host_port if filtered_uri.to_s.include?(host_port)
124
+ NetworkDownError.new("Could not reach host #{host}. Check your network " \
125
+ "connection and try again.")
126
+ end
127
+
113
128
  def https?(uri)
114
129
  uri.scheme&.casecmp("https")&.zero?
115
130
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "vendored_persistent"
3
+ require_relative "vendored_net_http"
4
4
  require_relative "vendored_timeout"
5
5
  require_relative "vendored_securerandom"
6
6
  require "zlib"
@@ -10,6 +10,7 @@ module Bundler
10
10
  class Fetcher
11
11
  autoload :Base, File.expand_path("fetcher/base", __dir__)
12
12
  autoload :CompactIndex, File.expand_path("fetcher/compact_index", __dir__)
13
+ autoload :ConnectionPools, File.expand_path("fetcher/connection_pools", __dir__)
13
14
  autoload :Downloader, File.expand_path("fetcher/downloader", __dir__)
14
15
  autoload :Dependency, File.expand_path("fetcher/dependency", __dir__)
15
16
  autoload :Index, File.expand_path("fetcher/index", __dir__)
@@ -137,7 +138,7 @@ module Bundler
137
138
  @remote = remote
138
139
 
139
140
  Socket.do_not_reverse_lookup = true
140
- connection # create persistent connection
141
+ connection # set up the connection pools eagerly so SSL support is checked upfront
141
142
  end
142
143
 
143
144
  def uri
@@ -176,13 +177,13 @@ module Bundler
176
177
  def specs(gem_names, source)
177
178
  index = Bundler::Index.new
178
179
 
179
- fetch_specs(gem_names).each do |name, version, platform, dependencies, metadata|
180
+ fetch_specs(gem_names).each do |name, version, suffix, dependencies, metadata|
180
181
  spec = if dependencies
181
- EndpointSpecification.new(name, version, platform, self, dependencies, metadata).tap do |es|
182
+ EndpointSpecification.new(name, version, suffix, self, dependencies, metadata).tap do |es|
182
183
  source.checksum_store.replace(es, es.checksum)
183
184
  end
184
185
  else
185
- RemoteSpecification.new(name, version, platform, self)
186
+ RemoteSpecification.new(name, version, suffix, self)
186
187
  end
187
188
  spec.source = source
188
189
  spec.remote = @remote
@@ -237,7 +238,7 @@ module Bundler
237
238
  end
238
239
 
239
240
  def http_proxy
240
- return unless uri = connection.proxy_uri
241
+ return unless uri = connection.proxy_for(remote_uri)
241
242
  uri.to_s
242
243
  end
243
244
 
@@ -307,28 +308,7 @@ module Bundler
307
308
  end
308
309
  end
309
310
 
310
- con = Gem::Net::HTTP::Persistent.new name: "bundler", proxy: :ENV
311
- if gem_proxy = Gem.configuration[:http_proxy]
312
- con.proxy = Gem::URI.parse(gem_proxy) if gem_proxy != :no_proxy
313
- end
314
-
315
- if remote_uri.scheme == "https"
316
- con.verify_mode = (Bundler.settings[:ssl_verify_mode] ||
317
- OpenSSL::SSL::VERIFY_PEER)
318
- con.cert_store = bundler_cert_store
319
- end
320
-
321
- ssl_client_cert = Bundler.settings[:ssl_client_cert] ||
322
- (Gem.configuration.ssl_client_cert if
323
- Gem.configuration.respond_to?(:ssl_client_cert))
324
- if ssl_client_cert
325
- pem = File.read(ssl_client_cert)
326
- con.cert = OpenSSL::X509::Certificate.new(pem)
327
- con.key = OpenSSL::PKey.read(pem)
328
- end
329
-
330
- con.read_timeout = Fetcher.api_timeout
331
- con.open_timeout = Fetcher.api_timeout
311
+ con = ConnectionPools.new(size: Bundler.settings.processor_count, timeout: Fetcher.api_timeout)
332
312
  con.override_headers["User-Agent"] = user_agent
333
313
  con.override_headers["X-Gemfile-Source"] = @remote.original_uri.to_s if @remote.original_uri
334
314
  con
@@ -341,25 +321,6 @@ module Bundler
341
321
  paths.find {|path| File.file? path }
342
322
  end
343
323
 
344
- def bundler_cert_store
345
- store = OpenSSL::X509::Store.new
346
- ssl_ca_cert = Bundler.settings[:ssl_ca_cert] ||
347
- (Gem.configuration.ssl_ca_cert if
348
- Gem.configuration.respond_to?(:ssl_ca_cert))
349
- if ssl_ca_cert
350
- if File.directory? ssl_ca_cert
351
- store.add_path ssl_ca_cert
352
- else
353
- store.add_file ssl_ca_cert
354
- end
355
- else
356
- store.set_default_paths
357
- require "rubygems/request"
358
- Gem::Request.get_cert_files.each {|c| store.add_file c }
359
- end
360
- store
361
- end
362
-
363
324
  def remote_uri
364
325
  @remote.uri
365
326
  end
@@ -89,10 +89,10 @@ module Bundler
89
89
  def version_prefix
90
90
  if @options[:strict]
91
91
  "= "
92
- elsif @options[:optimistic]
93
- ">= "
94
- else
92
+ elsif @options[:pessimistic]
95
93
  "~> "
94
+ else
95
+ ">= "
96
96
  end
97
97
  end
98
98
 
@@ -62,13 +62,52 @@ def gemfile(force_latest_compatible = false, options = {}, &gemfile)
62
62
  definition.validate_runtime!
63
63
 
64
64
  if force_latest_compatible || definition.missing_specs?
65
- Bundler.settings.temporary(inline: true, no_install: false) do
66
- installer = Bundler::Installer.install(Bundler.root, definition, system: true)
67
- installer.post_install_messages.each do |name, message|
68
- Bundler.ui.info "Post-install message from #{name}:\n#{message}"
65
+ do_install = -> do
66
+ Bundler.settings.temporary(inline: true, no_install: false) do
67
+ installer = Bundler::Installer.install(Bundler.root, definition, system: true)
68
+ installer.post_install_messages.each do |name, message|
69
+ Bundler.ui.info "Post-install message from #{name}:\n#{message}"
70
+ end
69
71
  end
70
72
  end
73
+
74
+ # When possible we do the install in a subprocess because to install
75
+ # gems we need to require some default gems like `securerandom` etc
76
+ # which may later conflict with the Gemfile requirements.
77
+ installed_in_fork = false
78
+ if Process.respond_to?(:fork)
79
+ Gem.load_yaml # Avoid NameError on Ruby 3.2's safe_yaml.rb after Gem::Specification.reset
80
+ _, status = Process.waitpid2(Process.fork do
81
+ $VERBOSE = nil
82
+ do_install.call end)
83
+ exit(status.exitstatus || status.to_i) unless status.success?
84
+
85
+ installed_in_fork = true
86
+
87
+ Bundler.reset!
88
+ Gem::Specification.reset
89
+ Bundler.instance_variable_set(:@bundle_path, Pathname.new(Gem.dir))
90
+
91
+ builder = Bundler::Dsl.new
92
+ builder.instance_eval(&gemfile)
93
+ builder.check_primary_source_safety
94
+
95
+ definition = builder.to_definition(nil, true)
96
+ definition.validate_runtime!
97
+ else
98
+ do_install.call
99
+ end
100
+ end
101
+
102
+ configure_forked_definition = ->(d) do
103
+ d.sources.rubygems_sources.each(&:remote!)
104
+ d.sources.git_sources.each do |source|
105
+ source.cached!
106
+ source.instance_variable_set(:@copied, true)
107
+ end
108
+ def d.lock(*); end
71
109
  end
110
+ configure_forked_definition.call(definition) if installed_in_fork
72
111
 
73
112
  begin
74
113
  runtime = Bundler::Runtime.new(nil, definition).setup
@@ -84,6 +123,7 @@ def gemfile(force_latest_compatible = false, options = {}, &gemfile)
84
123
  builder.dependencies.delete_if {|d| d.name == name }
85
124
  builder.instance_eval { gem name, activated_version }
86
125
  definition = builder.to_definition(nil, true)
126
+ configure_forked_definition.call(definition) if installed_in_fork
87
127
 
88
128
  retry
89
129
  end
@@ -20,10 +20,6 @@ module Bundler
20
20
  state == :installed
21
21
  end
22
22
 
23
- def enqueued?
24
- state == :enqueued
25
- end
26
-
27
23
  def enqueue_with_priority?
28
24
  state == :installable && spec.extensions.any?
29
25
  end
@@ -100,7 +96,8 @@ module Bundler
100
96
  handle_error if failed_specs.any?
101
97
  @specs
102
98
  ensure
103
- worker_pool&.stop
99
+ @worker_pool&.stop
100
+ @download_worker_pool&.stop
104
101
  end
105
102
 
106
103
  private
@@ -170,17 +167,18 @@ module Bundler
170
167
  end
171
168
  end
172
169
 
170
+ def download_worker_pool
171
+ @download_worker_pool ||= Bundler::Worker.new(@size, "Gem Downloader",
172
+ ->(spec_install, worker_num) { do_download(spec_install, worker_num) }, response_queue: response_queue)
173
+ end
174
+
173
175
  def worker_pool
174
- @worker_pool ||= Bundler::Worker.new @size, "Parallel Installer", lambda {|spec_install, worker_num|
175
- case spec_install.state
176
- when :enqueued
177
- do_download(spec_install, worker_num)
178
- when :installable
179
- do_install(spec_install, worker_num)
180
- else
181
- spec_install
182
- end
183
- }
176
+ @worker_pool ||= Bundler::Worker.new(@size, "Parallel Installer",
177
+ ->(spec_install, worker_num) { do_install(spec_install, worker_num) }, response_queue: response_queue)
178
+ end
179
+
180
+ def response_queue
181
+ @response_queue ||= Thread::Queue.new
184
182
  end
185
183
 
186
184
  def do_download(spec_install, worker_num)
@@ -218,24 +216,24 @@ module Bundler
218
216
  spec_install
219
217
  end
220
218
 
221
- # Dequeue a spec and save its post-install message and then enqueue the
222
- # remaining specs.
223
- # Some specs might've had to wait til this spec was installed to be
224
- # processed so the call to `enqueue_specs` is important after every
225
- # dequeue.
219
+ # Process one completed download or installation. Downloads can finish
220
+ # before their dependencies are installed, so check all downloaded specs
221
+ # after each completion and enqueue any that are now installable.
226
222
  def process_specs(installed_specs)
227
223
  spec = worker_pool.deq
228
224
 
229
225
  if spec.installed?
230
226
  installed_specs[spec.name] = true
231
- return
232
227
  elsif spec.failed?
233
228
  return
234
- elsif spec.ready_to_install?(installed_specs)
235
- spec.state = :installable
236
229
  end
237
230
 
238
- worker_pool.enq(spec, priority: spec.enqueue_with_priority?)
231
+ @specs.each do |candidate|
232
+ next unless candidate.ready_to_install?(installed_specs)
233
+
234
+ candidate.state = :installable
235
+ worker_pool.enq(candidate, priority: candidate.enqueue_with_priority?)
236
+ end
239
237
  end
240
238
 
241
239
  def finished_installing?
@@ -274,11 +272,8 @@ module Bundler
274
272
  t
275
273
  end
276
274
 
277
- # Keys in the remains hash represent uninstalled gems specs.
278
- # We enqueue all gem specs that do not have any dependencies.
279
- # Later we call this lambda again to install specs that depended on
280
- # previously installed specifications. We continue until all specs
281
- # are installed.
275
+ # Queue every missing spec for download. `process_specs` schedules each
276
+ # downloaded spec for installation once its dependencies are installed.
282
277
  def enqueue_specs(installed_specs)
283
278
  @specs.each do |spec|
284
279
  if spec.installed?
@@ -287,7 +282,7 @@ module Bundler
287
282
  end
288
283
 
289
284
  spec.state = :enqueued
290
- worker_pool.enq spec
285
+ download_worker_pool.enq spec
291
286
  end
292
287
  end
293
288
  end
@@ -228,18 +228,32 @@ module Bundler
228
228
  end
229
229
 
230
230
  def ensure_specs_are_compatible!
231
+ overrides = @definition.overrides
231
232
  @definition.specs.each do |spec|
232
- unless spec.matches_current_ruby?
233
- raise InstallError, "#{spec.full_name} requires ruby version #{spec.required_ruby_version}, " \
234
- "which is incompatible with the current version, #{Gem.ruby_version}"
233
+ unless spec.matches_current_ruby_with_overrides?(overrides)
234
+ raise InstallError, incompatibility_message(spec, "requires ruby version #{spec.required_ruby_version}, " \
235
+ "which is incompatible with the current version, #{Gem.ruby_version}")
235
236
  end
236
- unless spec.matches_current_rubygems?
237
- raise InstallError, "#{spec.full_name} requires rubygems version #{spec.required_rubygems_version}, " \
238
- "which is incompatible with the current version, #{Gem.rubygems_version}"
237
+ unless spec.matches_current_rubygems_with_overrides?(overrides)
238
+ raise InstallError, incompatibility_message(spec, "requires rubygems version #{spec.required_rubygems_version}, " \
239
+ "which is incompatible with the current version, #{Gem.rubygems_version}")
239
240
  end
240
241
  end
241
242
  end
242
243
 
244
+ # Build the error raised when a locked spec can't run on the current Ruby or
245
+ # RubyGems. When the offending spec is a platform-specific variant locked in
246
+ # frozen mode, point users at the fix: re-resolving outside frozen mode adds
247
+ # the Ruby (non platform-specific) fallback variant to the lockfile.
248
+ def incompatibility_message(spec, reason)
249
+ message = "#{spec.full_name} #{reason}"
250
+ return message unless Bundler.frozen_bundle? && spec.platform != Gem::Platform::RUBY
251
+
252
+ message + "\n\nThe lockfile only includes the #{spec.platform} variant of #{spec.name}. " \
253
+ "If a compatible Ruby variant is available, run `bundle install` outside of frozen/deployment mode " \
254
+ "to add it to the lockfile as a fallback, then commit the updated #{SharedHelpers.relative_lockfile_path}."
255
+ end
256
+
243
257
  def lock
244
258
  @definition.lock
245
259
  end