rubygems-update 4.0.8 → 4.0.9

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f238fb097512e0240397998ccc336ee348bedcf0618f1968a60e41272d77539
4
- data.tar.gz: 0e9260dd18ac27c9bcea0d941a5b179a66a7626f4300ee02eab89dd7ecfa9a44
3
+ metadata.gz: 83f0e21ae1b782d7223eeef031d3e934a1418acd8c25d45f57ab1aaae1974399
4
+ data.tar.gz: 1a2f70f09a5f83ade52b6b414566d155058ac7143a86e1c52f1fe72caf1b738f
5
5
  SHA512:
6
- metadata.gz: 0136f709eb566f26bffa87d5c7c87b1ee62e6310c66a5f174f65591f46eb7a26aeafeaf244934be67d91d6263e2d66368256b44f97a6f92cda8c269561bfcb6c
7
- data.tar.gz: 8d87a706aad6a67bfdd877103e4aa2e0e021d217dbdf57f41cde9e1c0fcda6a7f53a50f98fef5bd2d471ac90f2e1fa0f64622a79023476510ea339b46c05d945
6
+ metadata.gz: 1fdb7b4a286c354a34e07f4226019fd0bb20eed57bb5cec941096d1263fde3fcd116c26206021b954a27477f329d324cfeb868462625c67131c3fae023ebbedd
7
+ data.tar.gz: 02ad6fca35638236ffee0c971d15702a33ec632998c88af3dc914b612f2bee1a7e191405dbbe2376ccbbc1b020bf6a692dd3c1ff81cf4c582190e2d523f4ae8c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.0.9 / 2026-03-25
4
+
5
+ ### Enhancements:
6
+
7
+ * Fix: include owner role in `gem owner`. Pull request [#9403](https://github.com/ruby/rubygems/pull/9403) by gjtorikian
8
+ * Installs bundler 4.0.9 as a default gem.
9
+
10
+ ### Bug fixes:
11
+
12
+ * Fix: Ensure trailing slash is added to source URIs added via gem sources. Pull request [#9055](https://github.com/ruby/rubygems/pull/9055) by zirni
13
+
14
+ ### Documentation:
15
+
16
+ * [DOC] Fix link. Pull request [#9409](https://github.com/ruby/rubygems/pull/9409) by BurdetteLamar
17
+
3
18
  ## 4.0.8 / 2026-03-11
4
19
 
5
20
  ### Enhancements:
data/bundler/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.0.9 / 2026-03-25
4
+
5
+ ### Enhancements:
6
+
7
+ * Check the git version only **once** per `bundle install`. Pull request [#9406](https://github.com/ruby/rubygems/pull/9406) by Edouard-chin
8
+ * Normalize the number of workers when performing parallel operations. Pull request [#9400](https://github.com/ruby/rubygems/pull/9400) by Edouard-chin
9
+ * Add exponential backoff to bundler retries. Pull request [#9163](https://github.com/ruby/rubygems/pull/9163) by ChrisBr
10
+ * Introduce a priority queue. Pull request [#9389](https://github.com/ruby/rubygems/pull/9389) by Edouard-chin
11
+ * Split the download and install process of a gem. Pull request [#9381](https://github.com/ruby/rubygems/pull/9381) by Edouard-chin
12
+
13
+ ### Bug fixes:
14
+
15
+ * Retry git fetch without --depth for dumb HTTP transport. Pull request [#9405](https://github.com/ruby/rubygems/pull/9405) by hsbt
16
+
3
17
  ## 4.0.8 (2026-03-11)
4
18
 
5
19
  ### Enhancements:
@@ -4,8 +4,8 @@ module Bundler
4
4
  # Represents metadata from when the Bundler gem was built.
5
5
  module BuildMetadata
6
6
  # begin ivars
7
- @built_at = "2026-03-11".freeze
8
- @git_commit_sha = "1a32b76b73".freeze
7
+ @built_at = nil
8
+ @git_commit_sha = "3ce4a32411".freeze
9
9
  # end ivars
10
10
 
11
11
  # A hash representation of the build metadata.
@@ -53,7 +53,7 @@ module Bundler
53
53
  true
54
54
  end.map(&:name)
55
55
 
56
- jobs = installer.send(:installation_parallelization)
56
+ jobs = Bundler.settings.installation_parallelization
57
57
  pristine_count = definition.specs.count - installed_specs.count
58
58
  # allow a pristining a single gem to skip the parallel worker
59
59
  jobs = [jobs, pristine_count].min
@@ -1122,7 +1122,9 @@ module Bundler
1122
1122
  end
1123
1123
 
1124
1124
  def preload_git_source_worker
1125
- @preload_git_source_worker ||= Bundler::Worker.new(5, "Git source preloading", ->(source, _) { source.specs })
1125
+ workers = Bundler.settings.installation_parallelization
1126
+
1127
+ @preload_git_source_worker ||= Bundler::Worker.new(workers, "Git source preloading", ->(source, _) { source.specs })
1126
1128
  end
1127
1129
 
1128
1130
  def preload_git_sources
@@ -8,7 +8,7 @@ module Bundler
8
8
  def initialize(*)
9
9
  super
10
10
 
11
- @pool_size = 5
11
+ @pool_size = Bundler.settings.installation_parallelization
12
12
  end
13
13
 
14
14
  def request(*args)
@@ -25,6 +25,20 @@ module Bundler
25
25
  [false, specific_failure_message(e)]
26
26
  end
27
27
 
28
+ def download
29
+ spec.source.download(
30
+ spec,
31
+ force: force,
32
+ local: local,
33
+ build_args: Array(spec_settings),
34
+ previous_spec: previous_spec,
35
+ )
36
+
37
+ [true, nil]
38
+ rescue Bundler::BundlerError => e
39
+ [false, specific_failure_message(e)]
40
+ end
41
+
28
42
  private
29
43
 
30
44
  def specific_failure_message(e)
@@ -24,6 +24,10 @@ module Bundler
24
24
  state == :enqueued
25
25
  end
26
26
 
27
+ def enqueue_with_priority?
28
+ state == :installable && spec.extensions.any?
29
+ end
30
+
27
31
  def failed?
28
32
  state == :failed
29
33
  end
@@ -32,6 +36,12 @@ module Bundler
32
36
  state == :none
33
37
  end
34
38
 
39
+ def ready_to_install?(installed_specs)
40
+ return false unless state == :downloaded
41
+
42
+ spec.extensions.none? || dependencies_installed?(installed_specs)
43
+ end
44
+
35
45
  def has_post_install_message?
36
46
  !post_install_message.empty?
37
47
  end
@@ -84,6 +94,7 @@ module Bundler
84
94
 
85
95
  def call
86
96
  if @rake
97
+ do_download(@rake, 0)
87
98
  do_install(@rake, 0)
88
99
  Gem::Specification.reset
89
100
  end
@@ -107,26 +118,54 @@ module Bundler
107
118
  end
108
119
 
109
120
  def install_with_worker
110
- enqueue_specs
111
- process_specs until finished_installing?
121
+ installed_specs = {}
122
+ enqueue_specs(installed_specs)
123
+
124
+ process_specs(installed_specs) until finished_installing?
112
125
  end
113
126
 
114
127
  def install_serially
115
128
  until finished_installing?
116
129
  raise "failed to find a spec to enqueue while installing serially" unless spec_install = @specs.find(&:ready_to_enqueue?)
117
130
  spec_install.state = :enqueued
131
+ do_download(spec_install, 0)
118
132
  do_install(spec_install, 0)
119
133
  end
120
134
  end
121
135
 
122
136
  def worker_pool
123
137
  @worker_pool ||= Bundler::Worker.new @size, "Parallel Installer", lambda {|spec_install, worker_num|
124
- do_install(spec_install, worker_num)
138
+ case spec_install.state
139
+ when :enqueued
140
+ do_download(spec_install, worker_num)
141
+ when :installable
142
+ do_install(spec_install, worker_num)
143
+ else
144
+ spec_install
145
+ end
125
146
  }
126
147
  end
127
148
 
128
- def do_install(spec_install, worker_num)
149
+ def do_download(spec_install, worker_num)
129
150
  Plugin.hook(Plugin::Events::GEM_BEFORE_INSTALL, spec_install)
151
+
152
+ gem_installer = Bundler::GemInstaller.new(
153
+ spec_install.spec, @installer, @standalone, worker_num, @force, @local
154
+ )
155
+
156
+ success, message = gem_installer.download
157
+
158
+ if success
159
+ spec_install.state = :downloaded
160
+ else
161
+ spec_install.error = "#{message}\n\n#{require_tree_for_spec(spec_install.spec)}"
162
+ spec_install.state = :failed
163
+ end
164
+
165
+ spec_install
166
+ end
167
+
168
+ def do_install(spec_install, worker_num)
130
169
  gem_installer = Bundler::GemInstaller.new(
131
170
  spec_install.spec, @installer, @standalone, worker_num, @force, @local
132
171
  )
@@ -147,9 +186,19 @@ module Bundler
147
186
  # Some specs might've had to wait til this spec was installed to be
148
187
  # processed so the call to `enqueue_specs` is important after every
149
188
  # dequeue.
150
- def process_specs
151
- worker_pool.deq
152
- enqueue_specs
189
+ def process_specs(installed_specs)
190
+ spec = worker_pool.deq
191
+
192
+ if spec.installed?
193
+ installed_specs[spec.name] = true
194
+ return
195
+ elsif spec.failed?
196
+ return
197
+ elsif spec.ready_to_install?(installed_specs)
198
+ spec.state = :installable
199
+ end
200
+
201
+ worker_pool.enq(spec, priority: spec.enqueue_with_priority?)
153
202
  end
154
203
 
155
204
  def finished_installing?
@@ -185,18 +234,15 @@ module Bundler
185
234
  # Later we call this lambda again to install specs that depended on
186
235
  # previously installed specifications. We continue until all specs
187
236
  # are installed.
188
- def enqueue_specs
189
- installed_specs = {}
190
- @specs.each do |spec|
191
- next unless spec.installed?
192
- installed_specs[spec.name] = true
193
- end
194
-
237
+ def enqueue_specs(installed_specs)
195
238
  @specs.each do |spec|
196
- if spec.ready_to_enqueue? && spec.dependencies_installed?(installed_specs)
197
- spec.state = :enqueued
198
- worker_pool.enq spec
239
+ if spec.installed?
240
+ installed_specs[spec.name] = true
241
+ next
199
242
  end
243
+
244
+ spec.state = :enqueued
245
+ worker_pool.enq spec
200
246
  end
201
247
  end
202
248
  end
@@ -189,21 +189,13 @@ module Bundler
189
189
  standalone = options[:standalone]
190
190
  force = options[:force]
191
191
  local = options[:local] || options[:"prefer-local"]
192
- jobs = installation_parallelization
192
+ jobs = Bundler.settings.installation_parallelization
193
193
  spec_installations = ParallelInstaller.call(self, @definition.specs, jobs, standalone, force, local: local)
194
194
  spec_installations.each do |installation|
195
195
  post_install_messages[installation.name] = installation.post_install_message if installation.has_post_install_message?
196
196
  end
197
197
  end
198
198
 
199
- def installation_parallelization
200
- if jobs = Bundler.settings[:jobs]
201
- return jobs
202
- end
203
-
204
- Bundler.settings.processor_count
205
- end
206
-
207
199
  def load_plugins
208
200
  Gem.load_plugins
209
201
 
@@ -146,7 +146,7 @@ When set, no post install messages will be printed\. To silence a single gem, us
146
146
  Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\.
147
147
  .TP
148
148
  \fBjobs\fR (\fBBUNDLE_JOBS\fR)
149
- The number of gems Bundler can install in parallel\. Defaults to the number of available processors\.
149
+ The number of gems Bundler can download and install in parallel\. Defaults to the number of available processors\.
150
150
  .TP
151
151
  \fBlockfile\fR (\fBBUNDLE_LOCKFILE\fR)
152
152
  The path to the lockfile that bundler should use\. By default, Bundler adds \fB\.lock\fR to the end of the \fBgemfile\fR entry\. Can be set to \fBfalse\fR in the Gemfile to disable lockfile creation entirely (see gemfile(5))\.
@@ -192,8 +192,8 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
192
192
  * `init_gems_rb` (`BUNDLE_INIT_GEMS_RB`):
193
193
  Generate a `gems.rb` instead of a `Gemfile` when running `bundle init`.
194
194
  * `jobs` (`BUNDLE_JOBS`):
195
- The number of gems Bundler can install in parallel. Defaults to the number of
196
- available processors.
195
+ The number of gems Bundler can download and install in parallel.
196
+ Defaults to the number of available processors.
197
197
  * `lockfile` (`BUNDLE_LOCKFILE`):
198
198
  The path to the lockfile that bundler should use. By default, Bundler adds
199
199
  `.lock` to the end of the `gemfile` entry. Can be set to `false` in the
@@ -74,6 +74,14 @@ module Bundler
74
74
  {}
75
75
  end
76
76
 
77
+ # Download the gem specified by the spec at appropriate path.
78
+ #
79
+ # A source plugin can implement this method to split the download and the
80
+ # installation of a gem.
81
+ #
82
+ # @return [Boolean] Whether the download of the gem succeeded.
83
+ def download(spec, opts); end
84
+
77
85
  # Install the gem specified by the spec at appropriate path.
78
86
  # `install_path` provides a sufficient default, if the source can only
79
87
  # satisfy one gem, but is not binding.
@@ -110,7 +110,8 @@ module Bundler
110
110
  paths = {}
111
111
 
112
112
  specs.each do |spec|
113
- spec.source.install spec
113
+ spec.source.download(spec)
114
+ spec.source.install(spec)
114
115
 
115
116
  paths[spec.name] = spec
116
117
  end
@@ -6,6 +6,8 @@ module Bundler
6
6
  attr_accessor :name, :total_runs, :current_run
7
7
 
8
8
  class << self
9
+ attr_accessor :default_base_delay
10
+
9
11
  def default_attempts
10
12
  default_retries + 1
11
13
  end
@@ -16,11 +18,17 @@ module Bundler
16
18
  end
17
19
  end
18
20
 
19
- def initialize(name, exceptions = nil, retries = self.class.default_retries)
21
+ # Set default base delay for exponential backoff
22
+ self.default_base_delay = 1.0
23
+
24
+ def initialize(name, exceptions = nil, retries = self.class.default_retries, opts = {})
20
25
  @name = name
21
26
  @retries = retries
22
27
  @exceptions = Array(exceptions) || []
23
28
  @total_runs = @retries + 1 # will run once, then upto attempts.times
29
+ @base_delay = opts[:base_delay] || self.class.default_base_delay
30
+ @max_delay = opts[:max_delay] || 60.0
31
+ @jitter = opts[:jitter] || 0.5
24
32
  end
25
33
 
26
34
  def attempt(&block)
@@ -48,9 +56,27 @@ module Bundler
48
56
  Bundler.ui.info "" unless Bundler.ui.debug?
49
57
  raise e
50
58
  end
51
- return true unless name
52
- Bundler.ui.info "" unless Bundler.ui.debug? # Add new line in case dots preceded this
53
- Bundler.ui.warn "Retrying #{name} due to error (#{current_run.next}/#{total_runs}): #{e.class} #{e.message}", true
59
+ if name
60
+ Bundler.ui.info "" unless Bundler.ui.debug? # Add new line in case dots preceded this
61
+ Bundler.ui.warn "Retrying #{name} due to error (#{current_run.next}/#{total_runs}): #{e.class} #{e.message}", true
62
+ end
63
+ backoff_sleep if @base_delay > 0
64
+ true
65
+ end
66
+
67
+ def backoff_sleep
68
+ # Exponential backoff: delay = base_delay * 2^(attempt - 1)
69
+ # Add jitter to prevent thundering herd: random value between 0 and jitter seconds
70
+ delay = @base_delay * (2**(@current_run - 1))
71
+ delay = [@max_delay, delay].min
72
+ jitter_amount = rand * @jitter
73
+ total_delay = delay + jitter_amount
74
+ Bundler.ui.debug "Sleeping for #{total_delay.round(2)} seconds before retry"
75
+ sleep(total_delay)
76
+ end
77
+
78
+ def sleep(duration)
79
+ Kernel.sleep(duration)
54
80
  end
55
81
 
56
82
  def keep_trying?
@@ -63,6 +63,7 @@ module Bundler
63
63
  end
64
64
 
65
65
  def install(spec)
66
+ spec.source.download(spec)
66
67
  spec.source.install(spec)
67
68
  end
68
69
 
@@ -303,6 +303,10 @@ module Bundler
303
303
  @app_cache_path ||= self[:cache_path] || "vendor/cache"
304
304
  end
305
305
 
306
+ def installation_parallelization
307
+ self[:jobs] || processor_count
308
+ end
309
+
306
310
  def validate!
307
311
  all.each do |raw_key|
308
312
  [@local_config, @env_config, @global_config].each do |settings|
@@ -57,6 +57,29 @@ module Bundler
57
57
  attr_accessor :path, :uri, :branch, :tag, :ref, :explicit_ref
58
58
  attr_writer :revision
59
59
 
60
+ def self.version
61
+ @version ||= full_version[/((\.?\d+)+).*/, 1]
62
+ end
63
+
64
+ def self.full_version
65
+ @full_version ||= begin
66
+ raise GitNotInstalledError.new unless Bundler.git_present?
67
+
68
+ require "open3"
69
+ out, err, status = Open3.capture3("git", "--version")
70
+
71
+ raise GitCommandError.new("--version", SharedHelpers.pwd, err) unless status.success?
72
+ Bundler.ui.warn err unless err.empty?
73
+
74
+ out.sub(/git version\s*/, "").strip
75
+ end
76
+ end
77
+
78
+ def self.reset
79
+ @version = nil
80
+ @full_version = nil
81
+ end
82
+
60
83
  def initialize(path, uri, options = {}, revision = nil, git = nil)
61
84
  @path = path
62
85
  @uri = uri
@@ -92,11 +115,11 @@ module Bundler
92
115
  end
93
116
 
94
117
  def version
95
- @version ||= full_version.match(/((\.?\d+)+).*/)[1]
118
+ self.class.version
96
119
  end
97
120
 
98
121
  def full_version
99
- @full_version ||= git_local("--version").sub(/git version\s*/, "").strip
122
+ self.class.full_version
100
123
  end
101
124
 
102
125
  def checkout
@@ -156,7 +179,7 @@ module Bundler
156
179
  private
157
180
 
158
181
  def git_remote_fetch(args)
159
- command = ["fetch", "--force", "--quiet", "--no-tags", *args, "--", configured_uri, refspec].compact
182
+ command = fetch_command(args)
160
183
  command_with_no_credentials = check_allowed(command)
161
184
 
162
185
  Bundler::Retry.new("`#{command_with_no_credentials}` at #{path}", [MissingGitRevisionError]).attempts do
@@ -166,6 +189,11 @@ module Bundler
166
189
  if err.include?("couldn't find remote ref") || err.include?("not our ref")
167
190
  raise MissingGitRevisionError.new(command_with_no_credentials, path, commit || explicit_ref, credential_filtered_uri)
168
191
  else
192
+ if shallow?
193
+ args -= depth_args
194
+ command = fetch_command(args)
195
+ command_with_no_credentials = check_allowed(command)
196
+ end
169
197
  raise GitCommandError.new(command_with_no_credentials, path, err)
170
198
  end
171
199
  end
@@ -178,7 +206,8 @@ module Bundler
178
206
  FileUtils.mkdir_p(p)
179
207
  end
180
208
 
181
- command = ["clone", "--bare", "--no-hardlinks", "--quiet", *extra_clone_args, "--", configured_uri, path.to_s]
209
+ clone_args = extra_clone_args
210
+ command = clone_command(clone_args)
182
211
  command_with_no_credentials = check_allowed(command)
183
212
 
184
213
  Bundler::Retry.new("`#{command_with_no_credentials}`", [MissingGitRevisionError]).attempts do
@@ -189,13 +218,10 @@ module Bundler
189
218
  err.include?("Remote branch #{branch_option} not found") # git 2.49 or higher
190
219
  raise MissingGitRevisionError.new(command_with_no_credentials, nil, explicit_ref, credential_filtered_uri)
191
220
  else
192
- idx = command.index("--depth")
193
- if idx
194
- command.delete_at(idx)
195
- command.delete_at(idx)
221
+ if shallow?
222
+ clone_args -= depth_args
223
+ command = clone_command(clone_args)
196
224
  command_with_no_credentials = check_allowed(command)
197
-
198
- err += "Retrying without --depth argument."
199
225
  end
200
226
  raise GitCommandError.new(command_with_no_credentials, path, err)
201
227
  end
@@ -204,14 +230,14 @@ module Bundler
204
230
 
205
231
  def clone_needs_unshallow?
206
232
  return false unless path.join("shallow").exist?
207
- return true if full_clone?
233
+ return true unless shallow?
208
234
 
209
235
  @revision && @revision != head_revision
210
236
  end
211
237
 
212
238
  def extra_ref
213
239
  return false if not_pinned?
214
- return true unless full_clone?
240
+ return true if shallow?
215
241
 
216
242
  ref.start_with?("refs/")
217
243
  end
@@ -427,8 +453,16 @@ module Bundler
427
453
  args
428
454
  end
429
455
 
456
+ def fetch_command(args)
457
+ ["fetch", "--force", "--quiet", "--no-tags", *args, "--", configured_uri, refspec].compact
458
+ end
459
+
460
+ def clone_command(args)
461
+ ["clone", "--bare", "--no-hardlinks", "--quiet", *args, "--", configured_uri, path.to_s]
462
+ end
463
+
430
464
  def depth_args
431
- return [] if full_clone?
465
+ return [] unless shallow?
432
466
 
433
467
  ["--depth", depth.to_s]
434
468
  end
@@ -443,8 +477,8 @@ module Bundler
443
477
  branch || tag
444
478
  end
445
479
 
446
- def full_clone?
447
- depth.nil?
480
+ def shallow?
481
+ !depth.nil?
448
482
  end
449
483
 
450
484
  def needs_allow_any_sha1_in_want?
@@ -9,6 +9,7 @@ module Bundler
9
9
 
10
10
  # Ask for X gems per API request
11
11
  API_REQUEST_SIZE = 100
12
+ REQUIRE_MUTEX = Mutex.new
12
13
 
13
14
  attr_accessor :remotes
14
15
 
@@ -21,6 +22,8 @@ module Bundler
21
22
  @allow_local = options["allow_local"] || false
22
23
  @prefer_local = false
23
24
  @checksum_store = Checksum::Store.new
25
+ @gem_installers = {}
26
+ @gem_installers_mutex = Mutex.new
24
27
 
25
28
  Array(options["remotes"]).reverse_each {|r| add_remote(r) }
26
29
 
@@ -162,49 +165,40 @@ module Bundler
162
165
  end
163
166
  end
164
167
 
165
- def install(spec, options = {})
168
+ def download(spec, options = {})
166
169
  if (spec.default_gem? && !cached_built_in_gem(spec, local: options[:local])) || (installed?(spec) && !options[:force])
167
- print_using_message "Using #{version_message(spec, options[:previous_spec])}"
168
- return nil # no post-install message
170
+ return true
169
171
  end
170
172
 
171
- path = fetch_gem_if_possible(spec, options[:previous_spec])
172
- raise GemNotFound, "Could not find #{spec.file_name} for installation" unless path
173
-
174
- return if Bundler.settings[:no_install]
175
-
176
- install_path = rubygems_dir
177
- bin_path = Bundler.system_bindir
178
-
179
- require_relative "../rubygems_gem_installer"
180
-
181
- installer = Bundler::RubyGemsGemInstaller.at(
182
- path,
183
- security_policy: Bundler.rubygems.security_policies[Bundler.settings["trust-policy"]],
184
- install_dir: install_path.to_s,
185
- bin_dir: bin_path.to_s,
186
- ignore_dependencies: true,
187
- wrappers: true,
188
- env_shebang: true,
189
- build_args: options[:build_args],
190
- bundler_extension_cache_path: extension_cache_path(spec)
191
- )
173
+ installer = rubygems_gem_installer(spec, options)
192
174
 
193
175
  if spec.remote
194
176
  s = begin
195
177
  installer.spec
196
178
  rescue Gem::Package::FormatError
197
- Bundler.rm_rf(path)
179
+ Bundler.rm_rf(installer.gem)
198
180
  raise
199
181
  rescue Gem::Security::Exception => e
200
182
  raise SecurityError,
201
- "The gem #{File.basename(path, ".gem")} can't be installed because " \
183
+ "The gem #{installer.gem} can't be installed because " \
202
184
  "the security policy didn't allow it, with the message: #{e.message}"
203
185
  end
204
186
 
205
187
  spec.__swap__(s)
206
188
  end
207
189
 
190
+ spec
191
+ end
192
+
193
+ def install(spec, options = {})
194
+ if (spec.default_gem? && !cached_built_in_gem(spec, local: options[:local])) || (installed?(spec) && !options[:force])
195
+ print_using_message "Using #{version_message(spec, options[:previous_spec])}"
196
+ return nil # no post-install message
197
+ end
198
+
199
+ return if Bundler.settings[:no_install]
200
+
201
+ installer = rubygems_gem_installer(spec, options)
208
202
  spec.source.checksum_store.register(spec, installer.gem_checksum)
209
203
 
210
204
  message = "Installing #{version_message(spec, options[:previous_spec])}"
@@ -511,6 +505,34 @@ module Bundler
511
505
  return unless remote = spec.remote
512
506
  remote.cache_slug
513
507
  end
508
+
509
+ # We are using a mutex to reaed and write from/to the hash.
510
+ # The reason this double synchronization was added is for performance
511
+ # and lock the mutex for the shortest possible amount of time. Otherwise,
512
+ # all threads are fighting over this mutex and when it gets acquired it gets locked
513
+ # until a threads finishes downloading a gem, leaving the other threads waiting
514
+ # doing nothing.
515
+ def rubygems_gem_installer(spec, options)
516
+ @gem_installers_mutex.synchronize { @gem_installers[spec.name] } || begin
517
+ path = fetch_gem_if_possible(spec, options[:previous_spec])
518
+ raise GemNotFound, "Could not find #{spec.file_name} for installation" unless path
519
+
520
+ REQUIRE_MUTEX.synchronize { require_relative "../rubygems_gem_installer" }
521
+
522
+ installer = Bundler::RubyGemsGemInstaller.at(
523
+ path,
524
+ security_policy: Bundler.rubygems.security_policies[Bundler.settings["trust-policy"]],
525
+ install_dir: rubygems_dir.to_s,
526
+ bin_dir: Bundler.system_bindir.to_s,
527
+ ignore_dependencies: true,
528
+ wrappers: true,
529
+ env_shebang: true,
530
+ build_args: options[:build_args],
531
+ bundler_extension_cache_path: extension_cache_path(spec)
532
+ )
533
+ @gem_installers_mutex.synchronize { @gem_installers[spec.name] ||= installer }
534
+ end
535
+ end
514
536
  end
515
537
  end
516
538
  end
@@ -31,6 +31,8 @@ module Bundler
31
31
  message
32
32
  end
33
33
 
34
+ def download(*); end
35
+
34
36
  def can_lock?(spec)
35
37
  spec.source == self
36
38
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: false
2
2
 
3
3
  module Bundler
4
- VERSION = "4.0.8".freeze
4
+ VERSION = "4.0.9".freeze
5
5
 
6
6
  def self.bundler_major_version
7
7
  @bundler_major_version ||= gem_version.segments.first
@@ -22,6 +22,7 @@ module Bundler
22
22
  def initialize(size, name, func)
23
23
  @name = name
24
24
  @request_queue = Thread::Queue.new
25
+ @request_queue_with_priority = Thread::Queue.new
25
26
  @response_queue = Thread::Queue.new
26
27
  @func = func
27
28
  @size = size
@@ -32,9 +33,10 @@ module Bundler
32
33
  # Enqueue a request to be executed in the worker pool
33
34
  #
34
35
  # @param obj [String] mostly it is name of spec that should be downloaded
35
- def enq(obj)
36
+ def enq(obj, priority: false)
37
+ queue = priority ? @request_queue_with_priority : @request_queue
36
38
  create_threads unless @threads
37
- @request_queue.enq obj
39
+ queue.enq obj
38
40
  end
39
41
 
40
42
  # Retrieves results of job function being executed in worker pool
@@ -52,7 +54,13 @@ module Bundler
52
54
 
53
55
  def process_queue(i)
54
56
  loop do
55
- obj = @request_queue.deq
57
+ obj = begin
58
+ @request_queue_with_priority.deq(true)
59
+ rescue ThreadError
60
+ @request_queue.deq(false, timeout: 0.05)
61
+ end
62
+
63
+ next if obj.nil?
56
64
  break if obj.equal? POISON
57
65
  @response_queue.enq apply_func(obj, i)
58
66
  end
@@ -79,7 +79,8 @@ permission to.
79
79
 
80
80
  say "Owners for gem: #{name}"
81
81
  owners.each do |owner|
82
- say "- #{owner["email"] || owner["handle"] || owner["id"]}"
82
+ identifier = owner["email"] || owner["handle"] || owner["id"]
83
+ say "- #{identifier} (#{owner["role"]})"
83
84
  end
84
85
  end
85
86
  end
@@ -50,11 +50,8 @@ class Gem::Commands::SourcesCommand < Gem::Command
50
50
  end
51
51
 
52
52
  def add_source(source_uri) # :nodoc:
53
- check_rubygems_https source_uri
54
-
55
- source = Gem::Source.new source_uri
56
-
57
- check_typo_squatting(source)
53
+ source = build_new_source(source_uri)
54
+ source_uri = source.uri.to_s
58
55
 
59
56
  begin
60
57
  if Gem.sources.include? source
@@ -76,11 +73,8 @@ class Gem::Commands::SourcesCommand < Gem::Command
76
73
  end
77
74
 
78
75
  def append_source(source_uri) # :nodoc:
79
- check_rubygems_https source_uri
80
-
81
- source = Gem::Source.new source_uri
82
-
83
- check_typo_squatting(source)
76
+ source = build_new_source(source_uri)
77
+ source_uri = source.uri.to_s
84
78
 
85
79
  begin
86
80
  source.load_specs :released
@@ -103,11 +97,8 @@ class Gem::Commands::SourcesCommand < Gem::Command
103
97
  end
104
98
 
105
99
  def prepend_source(source_uri) # :nodoc:
106
- check_rubygems_https source_uri
107
-
108
- source = Gem::Source.new source_uri
109
-
110
- check_typo_squatting(source)
100
+ source = build_new_source(source_uri)
101
+ source_uri = source.uri.to_s
111
102
 
112
103
  begin
113
104
  source.load_specs :released
@@ -141,6 +132,19 @@ Do you want to add this source?
141
132
  end
142
133
  end
143
134
 
135
+ def normalize_source_uri(source_uri) # :nodoc:
136
+ # Ensure the source URI has a trailing slash for proper RFC 2396 path merging
137
+ # Without a trailing slash, the last path segment is treated as a file and removed
138
+ # during relative path resolution (e.g., "/blish" + "gems/foo.gem" = "/gems/foo.gem")
139
+ # With a trailing slash, it's treated as a directory (e.g., "/blish/" + "gems/foo.gem" = "/blish/gems/foo.gem")
140
+ uri = Gem::URI.parse(source_uri)
141
+ uri.path = uri.path.gsub(%r{/+\z}, "") + "/" if uri.path && !uri.path.empty?
142
+ uri.to_s
143
+ rescue Gem::URI::Error
144
+ # If parsing fails, return the original URI and let later validation handle it
145
+ source_uri
146
+ end
147
+
144
148
  def check_rubygems_https(source_uri) # :nodoc:
145
149
  uri = Gem::URI source_uri
146
150
 
@@ -273,7 +277,8 @@ To remove a source use the --remove argument:
273
277
  end
274
278
 
275
279
  def remove_source(source_uri) # :nodoc:
276
- source = Gem::Source.new source_uri
280
+ source = build_source(source_uri)
281
+ source_uri = source.uri.to_s
277
282
 
278
283
  if configured_sources&.include? source
279
284
  Gem.sources.delete source
@@ -328,4 +333,16 @@ To remove a source use the --remove argument:
328
333
  def config_file_name
329
334
  Gem.configuration.config_file_name
330
335
  end
336
+
337
+ def build_source(source_uri)
338
+ source_uri = normalize_source_uri(source_uri)
339
+ Gem::Source.new(source_uri)
340
+ end
341
+
342
+ def build_new_source(source_uri)
343
+ source = build_source(source_uri)
344
+ check_rubygems_https(source.uri.to_s)
345
+ check_typo_squatting(source)
346
+ source
347
+ end
331
348
  end
data/lib/rubygems.rb CHANGED
@@ -9,7 +9,7 @@
9
9
  require "rbconfig"
10
10
 
11
11
  module Gem
12
- VERSION = "4.0.8"
12
+ VERSION = "4.0.9"
13
13
  end
14
14
 
15
15
  require_relative "rubygems/defaults"
@@ -37,7 +37,7 @@ require_relative "rubygems/win_platform"
37
37
  # Further RubyGems documentation can be found at:
38
38
  #
39
39
  # * {RubyGems Guides}[https://guides.rubygems.org]
40
- # * {RubyGems API}[https://www.rubydoc.info/github/ruby/rubygems] (also available from
40
+ # * {RubyGems API}[https://guides.rubygems.org/rubygems-org-api/] (also available from
41
41
  # <tt>gem server</tt>)
42
42
  #
43
43
  # == RubyGems Plugins
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubygems-update
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.8
4
+ version: 4.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Weirich
@@ -724,7 +724,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
724
724
  - !ruby/object:Gem::Version
725
725
  version: '0'
726
726
  requirements: []
727
- rubygems_version: 4.0.3
727
+ rubygems_version: 4.0.6
728
728
  specification_version: 4
729
729
  summary: RubyGems is a package management framework for Ruby. This gem is downloaded
730
730
  and installed by `gem update --system`, so that the `gem` CLI can update itself.