Merge RubyGems 3.5.5 and Bundler 2.5.5 (#9676)

* Merge RubyGems-3.5.4 and Bundler-2.5.4

* Merge RubyGems-3.5.5 and Bundler-2.5.5

* Make tests play with upstream Ruby tests

CI broke in https://github.com/ruby/ruby/pull/9604 because if any Ruby
tests run `require 'net/http'`, they will pollute the
`$LOADED_FEATURES` for the RubyGems tests. We can fix this by renaming
the test default gem from `net-http` to `my-http`.

See https://github.com/rubygems/rubygems/pull/7379#issuecomment-1901241299
for more details.

---------

Co-authored-by: Stan Hu <stanhu@gmail.com>
This commit is contained in:
Hiroshi SHIBATA 2024-02-05 23:51:04 +09:00 committed by GitHub
parent 7f97e3540c
commit ac526abcd6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 323 additions and 128 deletions

View file

@ -42,7 +42,7 @@ module Bundler
else else
file.write(response.body) file.write(response.body)
end end
CacheFile.write(etag_path, etag(response)) CacheFile.write(etag_path, etag_from_response(response))
true true
end end
end end
@ -53,13 +53,13 @@ module Bundler
response = @fetcher.call(remote_path, request_headers(etag)) response = @fetcher.call(remote_path, request_headers(etag))
return true if response.is_a?(Gem::Net::HTTPNotModified) return true if response.is_a?(Gem::Net::HTTPNotModified)
CacheFile.write(local_path, response.body, parse_digests(response)) CacheFile.write(local_path, response.body, parse_digests(response))
CacheFile.write(etag_path, etag(response)) CacheFile.write(etag_path, etag_from_response(response))
end end
def request_headers(etag, range_start = nil) def request_headers(etag, range_start = nil)
headers = {} headers = {}
headers["Range"] = "bytes=#{range_start}-" if range_start headers["Range"] = "bytes=#{range_start}-" if range_start
headers["If-None-Match"] = etag if etag headers["If-None-Match"] = %("#{etag}") if etag
headers headers
end end
@ -77,7 +77,7 @@ module Bundler
etag etag
end end
def etag(response) def etag_from_response(response)
return unless response["ETag"] return unless response["ETag"]
etag = response["ETag"].delete_prefix("W/") etag = response["ETag"].delete_prefix("W/")
return if etag.delete_prefix!('"') && !etag.delete_suffix!('"') return if etag.delete_prefix!('"') && !etag.delete_suffix!('"')

View file

@ -312,10 +312,6 @@ module Bundler
end end
end end
def should_complete_platforms?
!lockfile_exists? && generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform]
end
def spec_git_paths def spec_git_paths
sources.git_sources.map {|s| File.realpath(s.path) if File.exist?(s.path) }.compact sources.git_sources.map {|s| File.realpath(s.path) if File.exist?(s.path) }.compact
end end
@ -517,6 +513,10 @@ module Bundler
private private
def should_add_extra_platforms?
!lockfile_exists? && generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform]
end
def lockfile_exists? def lockfile_exists?
lockfile && File.exist?(lockfile) lockfile && File.exist?(lockfile)
end end
@ -600,7 +600,9 @@ module Bundler
result = SpecSet.new(resolver.start) result = SpecSet.new(resolver.start)
@resolved_bundler_version = result.find {|spec| spec.name == "bundler" }&.version @resolved_bundler_version = result.find {|spec| spec.name == "bundler" }&.version
@platforms = result.complete_platforms!(platforms) if should_complete_platforms? @platforms = result.add_extra_platforms!(platforms) if should_add_extra_platforms?
result.complete_platforms!(platforms)
SpecSet.new(result.for(dependencies, false, @platforms)) SpecSet.new(result.for(dependencies, false, @platforms))
end end

View file

@ -102,9 +102,6 @@ module Bundler
# if there's already a dependency with this name we try to prefer one # if there's already a dependency with this name we try to prefer one
if current = @dependencies.find {|d| d.name == dep.name } if current = @dependencies.find {|d| d.name == dep.name }
# Always prefer the dependency from the Gemfile
@dependencies.delete(current) if current.gemspec_dev_dep?
if current.requirement != dep.requirement if current.requirement != dep.requirement
current_requirement_open = current.requirements_list.include?(">= 0") current_requirement_open = current.requirements_list.include?(">= 0")
@ -116,8 +113,6 @@ module Bundler
Bundler.ui.warn "A gemspec development dependency (#{gemspec_dep.name}, #{gemspec_dep.requirement}) is being overridden by a Gemfile dependency (#{gemfile_dep.name}, #{gemfile_dep.requirement}).\n" \ Bundler.ui.warn "A gemspec development dependency (#{gemspec_dep.name}, #{gemspec_dep.requirement}) is being overridden by a Gemfile dependency (#{gemfile_dep.name}, #{gemfile_dep.requirement}).\n" \
"This behaviour may change in the future. Please remove either of them, or make sure they both have the same requirement\n" "This behaviour may change in the future. Please remove either of them, or make sure they both have the same requirement\n"
end end
return if dep.gemspec_dev_dep?
else else
update_prompt = "" update_prompt = ""
@ -135,8 +130,13 @@ module Bundler
"You specified: #{current.name} (#{current.requirement}) and #{dep.name} (#{dep.requirement})" \ "You specified: #{current.name} (#{current.requirement}) and #{dep.name} (#{dep.requirement})" \
"#{update_prompt}" "#{update_prompt}"
end end
elsif current.gemspec_dev_dep? || dep.gemspec_dev_dep? end
return if dep.gemspec_dev_dep?
# Always prefer the dependency from the Gemfile
if current.gemspec_dev_dep?
@dependencies.delete(current)
elsif dep.gemspec_dev_dep?
return
elsif current.source != dep.source elsif current.source != dep.source
raise GemfileError, "You cannot specify the same gem twice coming from different sources.\n" \ raise GemfileError, "You cannot specify the same gem twice coming from different sources.\n" \
"You specified that #{dep.name} (#{dep.requirement}) should come from " \ "You specified that #{dep.name} (#{dep.requirement}) should come from " \

View file

@ -302,9 +302,9 @@ Note that any configured credentials will be redacted by informative commands su
.P .P
Also note that to guarantee a sane mapping between valid environment variable names and valid host names, bundler makes the following transformations: Also note that to guarantee a sane mapping between valid environment variable names and valid host names, bundler makes the following transformations:
.IP "\(bu" 4 .IP "\(bu" 4
Any \fB\-\fR characters in a host name are mapped to a triple dash (\fB___\fR) in the corresponding environment variable\. Any \fB\-\fR characters in a host name are mapped to a triple underscore (\fB___\fR) in the corresponding environment variable\.
.IP "\(bu" 4 .IP "\(bu" 4
Any \fB\.\fR characters in a host name are mapped to a double dash (\fB__\fR) in the corresponding environment variable\. Any \fB\.\fR characters in a host name are mapped to a double underscore (\fB__\fR) in the corresponding environment variable\.
.IP "" 0 .IP "" 0
.P .P
This means that if you have a gem server named \fBmy\.gem\-host\.com\fR, you'll need to use the \fBBUNDLE_MY__GEM___HOST__COM\fR variable to configure credentials for it through ENV\. This means that if you have a gem server named \fBmy\.gem\-host\.com\fR, you'll need to use the \fBBUNDLE_MY__GEM___HOST__COM\fR variable to configure credentials for it through ENV\.

View file

@ -388,10 +388,10 @@ copy-pasting bundler output.
Also note that to guarantee a sane mapping between valid environment variable Also note that to guarantee a sane mapping between valid environment variable
names and valid host names, bundler makes the following transformations: names and valid host names, bundler makes the following transformations:
* Any `-` characters in a host name are mapped to a triple dash (`___`) in the * Any `-` characters in a host name are mapped to a triple underscore (`___`) in the
corresponding environment variable. corresponding environment variable.
* Any `.` characters in a host name are mapped to a double dash (`__`) in the * Any `.` characters in a host name are mapped to a double underscore (`__`) in the
corresponding environment variable. corresponding environment variable.
This means that if you have a gem server named `my.gem-host.com`, you'll need to This means that if you have a gem server named `my.gem-host.com`, you'll need to

View file

@ -52,32 +52,14 @@ module Bundler
specs.uniq specs.uniq
end end
def complete_platforms!(platforms) def add_extra_platforms!(platforms)
return platforms.concat([Gem::Platform::RUBY]).uniq if @specs.empty? return platforms.concat([Gem::Platform::RUBY]).uniq if @specs.empty?
new_platforms = @specs.flat_map {|spec| spec.source.specs.search([spec.name, spec.version]).map(&:platform) }.uniq.select do |platform| new_platforms = all_platforms.select do |platform|
next if platforms.include?(platform) next if platforms.include?(platform)
next unless GemHelpers.generic(platform) == Gem::Platform::RUBY next unless GemHelpers.generic(platform) == Gem::Platform::RUBY
new_specs = [] complete_platform(platform)
valid_platform = lookup.all? do |_, specs|
spec = specs.first
matching_specs = spec.source.specs.search([spec.name, spec.version])
platform_spec = GemHelpers.select_best_platform_match(matching_specs, platform).find do |s|
s.matches_current_metadata? && valid_dependencies?(s)
end
if platform_spec
new_specs << LazySpecification.from_spec(platform_spec)
true
else
false
end
end
next unless valid_platform
@specs.concat(new_specs.uniq)
end end
return platforms if new_platforms.empty? return platforms if new_platforms.empty?
@ -86,12 +68,15 @@ module Bundler
less_specific_platform = new_platforms.find {|platform| platform != Gem::Platform::RUBY && platform === Bundler.local_platform } less_specific_platform = new_platforms.find {|platform| platform != Gem::Platform::RUBY && platform === Bundler.local_platform }
platforms.delete(Bundler.local_platform) if less_specific_platform platforms.delete(Bundler.local_platform) if less_specific_platform
@sorted = nil
@lookup = nil
platforms platforms
end end
def complete_platforms!(platforms)
platforms.each do |platform|
complete_platform(platform)
end
end
def validate_deps(s) def validate_deps(s)
s.runtime_dependencies.each do |dep| s.runtime_dependencies.each do |dep|
next if dep.name == "bundler" next if dep.name == "bundler"
@ -110,14 +95,14 @@ module Bundler
def []=(key, value) def []=(key, value)
@specs << value @specs << value
@lookup = nil
@sorted = nil reset!
end end
def delete(specs) def delete(specs)
specs.each {|spec| @specs.delete(spec) } specs.each {|spec| @specs.delete(spec) }
@lookup = nil
@sorted = nil reset!
end end
def sort! def sort!
@ -175,8 +160,8 @@ module Bundler
def delete_by_name(name) def delete_by_name(name)
@specs.reject! {|spec| spec.name == name } @specs.reject! {|spec| spec.name == name }
@lookup = nil
@sorted = nil reset!
end end
def what_required(spec) def what_required(spec)
@ -212,6 +197,42 @@ module Bundler
private private
def reset!
@sorted = nil
@lookup = nil
end
def complete_platform(platform)
new_specs = []
valid_platform = lookup.all? do |_, specs|
spec = specs.first
matching_specs = spec.source.specs.search([spec.name, spec.version])
platform_spec = GemHelpers.select_best_platform_match(matching_specs, platform).find do |s|
s.matches_current_metadata? && valid_dependencies?(s)
end
if platform_spec
new_specs << LazySpecification.from_spec(platform_spec) unless specs.include?(platform_spec)
true
else
false
end
end
if valid_platform && new_specs.any?
@specs.concat(new_specs)
reset!
end
valid_platform
end
def all_platforms
@specs.flat_map {|spec| spec.source.specs.search([spec.name, spec.version]).map(&:platform) }.uniq
end
def valid_dependencies?(s) def valid_dependencies?(s)
validate_deps(s) == :valid validate_deps(s) == :valid
end end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: false # frozen_string_literal: false
module Bundler module Bundler
VERSION = "2.5.3".freeze VERSION = "2.5.5".freeze
def self.bundler_major_version def self.bundler_major_version
@bundler_major_version ||= VERSION.split(".").first.to_i @bundler_major_version ||= VERSION.split(".").first.to_i

View file

@ -9,7 +9,7 @@
require "rbconfig" require "rbconfig"
module Gem module Gem
VERSION = "3.5.3" VERSION = "3.5.5"
end end
# Must be first since it unloads the prelude from 1.9.2 # Must be first since it unloads the prelude from 1.9.2
@ -1216,6 +1216,13 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
## ##
# Find a Gem::Specification of default gem from +path+ # Find a Gem::Specification of default gem from +path+
def find_default_spec(path)
@path_to_default_spec_map[path]
end
##
# Find an unresolved Gem::Specification of default gem from +path+
def find_unresolved_default_spec(path) def find_unresolved_default_spec(path)
default_spec = @path_to_default_spec_map[path] default_spec = @path_to_default_spec_map[path]
return default_spec if default_spec && loaded_specs[default_spec.name] != default_spec return default_spec if default_spec && loaded_specs[default_spec.name] != default_spec

View file

@ -244,7 +244,7 @@ command to remove old versions.
@installer = Gem::DependencyInstaller.new update_options @installer = Gem::DependencyInstaller.new update_options
say "Updating #{name}" unless options[:system] && options[:silent] say "Updating #{name}" unless options[:system]
begin begin
@installer.install name, Gem::Requirement.new(version) @installer.install name, Gem::Requirement.new(version)
rescue Gem::InstallError, Gem::DependencyError => e rescue Gem::InstallError, Gem::DependencyError => e
@ -282,7 +282,7 @@ command to remove old versions.
check_oldest_rubygems version check_oldest_rubygems version
installed_gems = Gem::Specification.find_all_by_name "rubygems-update", requirement installed_gems = Gem::Specification.find_all_by_name "rubygems-update", requirement
installed_gems = update_gem("rubygems-update", version) if installed_gems.empty? || installed_gems.first.version != version installed_gems = update_gem("rubygems-update", requirement) if installed_gems.empty? || installed_gems.first.version != version
return if installed_gems.empty? return if installed_gems.empty?
install_rubygems installed_gems.first install_rubygems installed_gems.first
@ -294,9 +294,7 @@ command to remove old versions.
args << "--prefix" << Gem.prefix if Gem.prefix args << "--prefix" << Gem.prefix if Gem.prefix
args << "--no-document" unless options[:document].include?("rdoc") || options[:document].include?("ri") args << "--no-document" unless options[:document].include?("rdoc") || options[:document].include?("ri")
args << "--no-format-executable" if options[:no_format_executable] args << "--no-format-executable" if options[:no_format_executable]
args << "--previous-version" << Gem::VERSION if args << "--previous-version" << Gem::VERSION
options[:system] == true ||
Gem::Version.new(options[:system]) >= Gem::Version.new(2)
args args
end end

View file

@ -39,7 +39,14 @@ module Kernel
RUBYGEMS_ACTIVATION_MONITOR.synchronize do RUBYGEMS_ACTIVATION_MONITOR.synchronize do
path = File.path(path) path = File.path(path)
if spec = Gem.find_unresolved_default_spec(path) # If +path+ belongs to a default gem, we activate it and then go straight
# to normal require
if spec = Gem.find_default_spec(path)
name = spec.name
next if Gem.loaded_specs[name]
# Ensure -I beats a default gem # Ensure -I beats a default gem
resolved_path = begin resolved_path = begin
rp = nil rp = nil
@ -57,8 +64,10 @@ module Kernel
rp rp
end end
Kernel.send(:gem, spec.name, Gem::Requirement.default_prerelease) unless Kernel.send(:gem, name, Gem::Requirement.default_prerelease) unless
resolved_path resolved_path
next
end end
# If there are no unresolved deps, then we can use just try # If there are no unresolved deps, then we can use just try

View file

@ -24,7 +24,7 @@ module Gem
default_spec_cache_dir = File.join Gem.user_home, ".gem", "specs" default_spec_cache_dir = File.join Gem.user_home, ".gem", "specs"
unless File.exist?(default_spec_cache_dir) unless File.exist?(default_spec_cache_dir)
default_spec_cache_dir = File.join Gem.data_home, "gem", "specs" default_spec_cache_dir = File.join Gem.cache_home, "gem", "specs"
end end
default_spec_cache_dir default_spec_cache_dir

View file

@ -301,7 +301,7 @@ class Gem::Specification < Gem::BasicSpecification
# #
# Usage: # Usage:
# #
# spec.description = <<-EOF # spec.description = <<~EOF
# Rake is a Make-like program implemented in Ruby. Tasks and # Rake is a Make-like program implemented in Ruby. Tasks and
# dependencies are specified in standard Ruby syntax. # dependencies are specified in standard Ruby syntax.
# EOF # EOF

View file

@ -21,7 +21,7 @@ RSpec.describe Bundler::CompactIndexClient::Updater do
before do before do
allow(response).to receive(:[]).with("Repr-Digest") { nil } allow(response).to receive(:[]).with("Repr-Digest") { nil }
allow(response).to receive(:[]).with("Digest") { nil } allow(response).to receive(:[]).with("Digest") { nil }
allow(response).to receive(:[]).with("ETag") { "thisisanetag" } allow(response).to receive(:[]).with("ETag") { '"thisisanetag"' }
end end
it "downloads the file without attempting append" do it "downloads the file without attempting append" do
@ -57,7 +57,7 @@ RSpec.describe Bundler::CompactIndexClient::Updater do
let(:headers) do let(:headers) do
{ {
"If-None-Match" => "LocalEtag", "If-None-Match" => '"LocalEtag"',
"Range" => "bytes=2-", "Range" => "bytes=2-",
} }
end end
@ -76,7 +76,7 @@ RSpec.describe Bundler::CompactIndexClient::Updater do
it "appends the file if etags do not match" do it "appends the file if etags do not match" do
expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response)
allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" }
allow(response).to receive(:[]).with("ETag") { "NewEtag" } allow(response).to receive(:[]).with("ETag") { '"NewEtag"' }
allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true } allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true }
allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { false } allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { false }
allow(response).to receive(:body) { "c123" } allow(response).to receive(:body) { "c123" }
@ -90,7 +90,7 @@ RSpec.describe Bundler::CompactIndexClient::Updater do
it "replaces the file if response ignores range" do it "replaces the file if response ignores range" do
expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response)
allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" }
allow(response).to receive(:[]).with("ETag") { "NewEtag" } allow(response).to receive(:[]).with("ETag") { '"NewEtag"' }
allow(response).to receive(:body) { full_body } allow(response).to receive(:body) { full_body }
updater.update(remote_path, local_path, etag_path) updater.update(remote_path, local_path, etag_path)
@ -107,8 +107,8 @@ RSpec.describe Bundler::CompactIndexClient::Updater do
full_response = double(:full_response, body: full_body, is_a?: false) full_response = double(:full_response, body: full_body, is_a?: false)
allow(full_response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } allow(full_response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" }
allow(full_response).to receive(:[]).with("ETag") { "NewEtag" } allow(full_response).to receive(:[]).with("ETag") { '"NewEtag"' }
expect(fetcher).to receive(:call).once.with(remote_path, { "If-None-Match" => "LocalEtag" }).and_return(full_response) expect(fetcher).to receive(:call).once.with(remote_path, { "If-None-Match" => '"LocalEtag"' }).and_return(full_response)
updater.update(remote_path, local_path, etag_path) updater.update(remote_path, local_path, etag_path)
@ -123,7 +123,7 @@ RSpec.describe Bundler::CompactIndexClient::Updater do
"Range" => "bytes=2-", "Range" => "bytes=2-",
# This MD5 feature should be deleted after sufficient time has passed since release. # This MD5 feature should be deleted after sufficient time has passed since release.
# From then on, requests that still don't have a saved etag will be made without this header. # From then on, requests that still don't have a saved etag will be made without this header.
"If-None-Match" => Digest::MD5.hexdigest(local_body), "If-None-Match" => %("#{Digest::MD5.hexdigest(local_body)}"),
} }
end end
@ -135,13 +135,13 @@ RSpec.describe Bundler::CompactIndexClient::Updater do
updater.update(remote_path, local_path, etag_path) updater.update(remote_path, local_path, etag_path)
expect(local_path.read).to eq("abc") expect(local_path.read).to eq("abc")
expect(etag_path.read).to eq(headers["If-None-Match"]) expect(%("#{etag_path.read}")).to eq(headers["If-None-Match"])
end end
it "appends the file" do it "appends the file" do
expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response)
allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" }
allow(response).to receive(:[]).with("ETag") { "OpaqueEtag" } allow(response).to receive(:[]).with("ETag") { '"OpaqueEtag"' }
allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true } allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true }
allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { false } allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { false }
allow(response).to receive(:body) { "c123" } allow(response).to receive(:body) { "c123" }
@ -156,7 +156,7 @@ RSpec.describe Bundler::CompactIndexClient::Updater do
expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response)
allow(response).to receive(:[]).with("Repr-Digest") { nil } allow(response).to receive(:[]).with("Repr-Digest") { nil }
allow(response).to receive(:[]).with("Digest") { nil } allow(response).to receive(:[]).with("Digest") { nil }
allow(response).to receive(:[]).with("ETag") { "OpaqueEtag" } allow(response).to receive(:[]).with("ETag") { '"OpaqueEtag"' }
allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { false } allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { false }
allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { false } allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { false }
allow(response).to receive(:body) { full_body } allow(response).to receive(:body) { full_body }
@ -180,8 +180,8 @@ RSpec.describe Bundler::CompactIndexClient::Updater do
full_response = double(:full_response, body: full_body, is_a?: false) full_response = double(:full_response, body: full_body, is_a?: false)
allow(full_response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } allow(full_response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" }
allow(full_response).to receive(:[]).with("ETag") { "NewEtag" } allow(full_response).to receive(:[]).with("ETag") { '"NewEtag"' }
expect(fetcher).to receive(:call).once.with(remote_path, { "If-None-Match" => "LocalEtag" }).and_return(full_response) expect(fetcher).to receive(:call).once.with(remote_path, { "If-None-Match" => '"LocalEtag"' }).and_return(full_response)
updater.update(remote_path, local_path, etag_path) updater.update(remote_path, local_path, etag_path)

View file

@ -460,6 +460,35 @@ RSpec.describe "bundle install with gem sources" do
expect(the_bundle).to include_gems("rubocop 1.37.1") expect(the_bundle).to include_gems("rubocop 1.37.1")
end end
it "includes the gem without warning if two gemspecs add it with the same requirement" do
gem1 = tmp.join("my-gem-1")
gem2 = tmp.join("my-gem-2")
build_lib "my-gem", path: gem1 do |s|
s.add_development_dependency "rubocop", "~> 1.36.0"
end
build_lib "my-gem-2", path: gem2 do |s|
s.add_development_dependency "rubocop", "~> 1.36.0"
end
build_repo4 do
build_gem "rubocop", "1.36.0"
end
gemfile <<~G
source "#{file_uri_for(gem_repo4)}"
gemspec path: "#{gem1}"
gemspec path: "#{gem2}"
G
bundle :install
expect(err).to be_empty
expect(the_bundle).to include_gems("rubocop 1.36.0")
end
it "warns when a Gemfile dependency is overriding a gemspec development dependency, with different requirements" do it "warns when a Gemfile dependency is overriding a gemspec development dependency, with different requirements" do
build_lib "my-gem", path: bundled_app do |s| build_lib "my-gem", path: bundled_app do |s|
s.add_development_dependency "rails", ">= 5" s.add_development_dependency "rails", ">= 5"

View file

@ -1380,7 +1380,7 @@ RSpec.describe "bundle update --bundler" do
build_bundler "999.0.0" build_bundler "999.0.0"
end end
install_gemfile <<-G install_gemfile <<-G, artifice: nil, env: { "BUNDLER_IGNORE_DEFAULT_GEM" => "true" }
source "#{file_uri_for(gem_repo4)}" source "#{file_uri_for(gem_repo4)}"
gem "rack" gem "rack"
G G

View file

@ -924,15 +924,19 @@ RSpec.describe "compact index api" do
gem 'rack', '0.9.1' gem 'rack', '0.9.1'
G G
rake_info_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index",
"localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "info", "rack")
bundle :install, artifice: "compact_index" bundle :install, artifice: "compact_index"
# We must remove the etag so that we don't ignore the range and get a 304 Not Modified.
rake_info_etag_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index",
"localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "info-etags", "rack-11690b09f16021ff06a6857d784a1870")
File.unlink(rake_info_etag_path) if File.exist?(rake_info_etag_path)
rake_info_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index",
"localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "info", "rack")
expected_rack_info_content = File.read(rake_info_path) expected_rack_info_content = File.read(rake_info_path)
# Modify the cache files. We expect them to be reset to the normal ones when we re-run :install # Modify the cache files to make the range not satisfiable
File.open(rake_info_path, "a") {|f| f << "this is different" } File.open(rake_info_path, "a") {|f| f << "0.9.2 |checksum:c55b525b421fd833a93171ad3d7f04528ca8e87d99ac273f8933038942a5888c" }
# Update the Gemfile so the next install does its normal things # Update the Gemfile so the next install does its normal things
gemfile <<-G gemfile <<-G

View file

@ -1195,6 +1195,46 @@ RSpec.describe "the lockfile format" do
G G
end end
it "adds compatible platform specific variants to the lockfile, even if resolution fallback to RUBY due to some other incompatible platform specific variant" do
simulate_platform "arm64-darwin-23" do
build_repo4 do
build_gem "google-protobuf", "3.25.1"
build_gem "google-protobuf", "3.25.1" do |s|
s.platform = "arm64-darwin-23"
end
build_gem "google-protobuf", "3.25.1" do |s|
s.platform = "x64-mingw-ucrt"
s.required_ruby_version = "> #{Gem.ruby_version}"
end
end
gemfile <<-G
source "#{file_uri_for(gem_repo4)}"
gem "google-protobuf"
G
bundle "lock --add-platform x64-mingw-ucrt"
expect(lockfile).to eq <<~L
GEM
remote: #{file_uri_for(gem_repo4)}/
specs:
google-protobuf (3.25.1)
google-protobuf (3.25.1-arm64-darwin-23)
PLATFORMS
arm64-darwin-23
ruby
x64-mingw-ucrt
DEPENDENCIES
google-protobuf
BUNDLED WITH
#{Bundler::VERSION}
L
end
end
it "persists the spec's specific platform to the lockfile" do it "persists the spec's specific platform to the lockfile" do
build_repo2 do build_repo2 do
build_gem "platform_specific", "1.0" do |s| build_gem "platform_specific", "1.0" do |s|

View file

@ -2,7 +2,7 @@
require_relative "../path" require_relative "../path"
$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s)) $LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords,base64}-*/lib")].map(&:to_s))
require "sinatra/base" require "sinatra/base"

View file

@ -19,8 +19,8 @@ class CompactIndexAPI < Endpoint
def etag_response def etag_response
response_body = yield response_body = yield
etag = Digest::MD5.hexdigest(response_body) etag = Digest::MD5.hexdigest(response_body)
return if not_modified?(etag)
headers "ETag" => quote(etag) headers "ETag" => quote(etag)
return if not_modified?(etag)
headers "Repr-Digest" => "sha-256=:#{Digest::SHA256.base64digest(response_body)}:" headers "Repr-Digest" => "sha-256=:#{Digest::SHA256.base64digest(response_body)}:"
headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60" headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60"
content_type "text/plain" content_type "text/plain"
@ -35,7 +35,6 @@ class CompactIndexAPI < Endpoint
etags = parse_etags(request.env["HTTP_IF_NONE_MATCH"]) etags = parse_etags(request.env["HTTP_IF_NONE_MATCH"])
return unless etags.include?(etag) return unless etags.include?(etag)
headers "ETag" => quote(etag)
status 304 status 304
body "" body ""
end end

View file

@ -2,7 +2,7 @@
require_relative "../../path" require_relative "../../path"
$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s)) $LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords,base64}-*/lib")].map(&:to_s))
require "sinatra/base" require "sinatra/base"

View file

@ -2,7 +2,7 @@
require_relative "../path" require_relative "../path"
$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s)) $LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords,base64}-*/lib")].map(&:to_s))
require "sinatra/base" require "sinatra/base"

View file

@ -86,7 +86,7 @@ module Spec
puts success_message puts success_message
puts puts
else else
system("git status --porcelain") system("git diff")
puts puts
puts error_message puts error_message

View file

@ -105,39 +105,32 @@ class Gem::TestCase < Test::Unit::TestCase
refute File.directory?(path), msg refute File.directory?(path), msg
end end
# https://github.com/seattlerb/minitest/blob/21d9e804b63c619f602f3f4ece6c71b48974707a/lib/minitest/assertions.rb#L188 # Originally copied from minitest/assertions.rb
def _synchronize
yield
end
# https://github.com/seattlerb/minitest/blob/21d9e804b63c619f602f3f4ece6c71b48974707a/lib/minitest/assertions.rb#L546
def capture_subprocess_io def capture_subprocess_io
_synchronize do require "tempfile"
require "tempfile"
captured_stdout = Tempfile.new("out") captured_stdout = Tempfile.new("out")
captured_stderr = Tempfile.new("err") captured_stderr = Tempfile.new("err")
orig_stdout = $stdout.dup orig_stdout = $stdout.dup
orig_stderr = $stderr.dup orig_stderr = $stderr.dup
$stdout.reopen captured_stdout $stdout.reopen captured_stdout
$stderr.reopen captured_stderr $stderr.reopen captured_stderr
yield yield
$stdout.rewind $stdout.rewind
$stderr.rewind $stderr.rewind
return captured_stdout.read, captured_stderr.read [captured_stdout.read, captured_stderr.read]
ensure ensure
$stdout.reopen orig_stdout $stdout.reopen orig_stdout
$stderr.reopen orig_stderr $stderr.reopen orig_stderr
orig_stdout.close orig_stdout.close
orig_stderr.close orig_stderr.close
captured_stdout.close! captured_stdout.close!
captured_stderr.close! captured_stderr.close!
end
end end
## ##

View file

@ -79,7 +79,6 @@ class TestGemCommandsUpdateCommand < Gem::TestCase
end end
out = @ui.output.split "\n" out = @ui.output.split "\n"
assert_equal "Updating rubygems-update", out.shift
assert_equal "Installing RubyGems 9", out.shift assert_equal "Installing RubyGems 9", out.shift
assert_equal "RubyGems system software updated", out.shift assert_equal "RubyGems system software updated", out.shift
@ -123,7 +122,6 @@ class TestGemCommandsUpdateCommand < Gem::TestCase
end end
out = @ui.output.split "\n" out = @ui.output.split "\n"
assert_equal "Updating rubygems-update", out.shift
assert_empty out assert_empty out
err = @ui.error.split "\n" err = @ui.error.split "\n"
@ -132,6 +130,34 @@ class TestGemCommandsUpdateCommand < Gem::TestCase
assert_empty err assert_empty err
end end
def test_execute_system_when_latest_does_not_support_your_ruby_but_previous_one_does
spec_fetcher do |fetcher|
fetcher.download "rubygems-update", 9 do |s|
s.files = %w[setup.rb]
s.required_ruby_version = "> 9"
end
fetcher.download "rubygems-update", 8 do |s|
s.files = %w[setup.rb]
end
end
@cmd.options[:args] = []
@cmd.options[:system] = true
use_ui @ui do
@cmd.execute
end
err = @ui.error.split "\n"
assert_empty err
out = @ui.output.split "\n"
assert_equal "Installing RubyGems 8", out.shift
assert_equal "RubyGems system software updated", out.shift
assert_empty out
end
def test_execute_system_multiple def test_execute_system_multiple
spec_fetcher do |fetcher| spec_fetcher do |fetcher|
fetcher.download "rubygems-update", 8 do |s| fetcher.download "rubygems-update", 8 do |s|
@ -151,7 +177,6 @@ class TestGemCommandsUpdateCommand < Gem::TestCase
end end
out = @ui.output.split "\n" out = @ui.output.split "\n"
assert_equal "Updating rubygems-update", out.shift
assert_equal "Installing RubyGems 9", out.shift assert_equal "Installing RubyGems 9", out.shift
assert_equal "RubyGems system software updated", out.shift assert_equal "RubyGems system software updated", out.shift
@ -185,7 +210,6 @@ class TestGemCommandsUpdateCommand < Gem::TestCase
end end
out = @ui.output.split "\n" out = @ui.output.split "\n"
assert_equal "Updating rubygems-update", out.shift
assert_equal "Installing RubyGems 9", out.shift assert_equal "Installing RubyGems 9", out.shift
assert_equal "RubyGems system software updated", out.shift assert_equal "RubyGems system software updated", out.shift
@ -242,7 +266,6 @@ class TestGemCommandsUpdateCommand < Gem::TestCase
end end
out = @ui.output.split "\n" out = @ui.output.split "\n"
assert_equal "Updating rubygems-update", out.shift
assert_equal "Installing RubyGems 8", out.shift assert_equal "Installing RubyGems 8", out.shift
assert_equal "RubyGems system software updated", out.shift assert_equal "RubyGems system software updated", out.shift
@ -353,7 +376,6 @@ class TestGemCommandsUpdateCommand < Gem::TestCase
end end
out = @ui.output.split "\n" out = @ui.output.split "\n"
assert_equal "Updating rubygems-update", out.shift
assert_equal "Installing RubyGems 9", out.shift assert_equal "Installing RubyGems 9", out.shift
assert_equal "RubyGems system software updated", out.shift assert_equal "RubyGems system software updated", out.shift

View file

@ -152,18 +152,18 @@ dependencies = [
[[package]] [[package]]
name = "rb-sys" name = "rb-sys"
version = "0.9.84" version = "0.9.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3def04a96a36ef8681a2b2e26c01683b93a8630175c845fa06cab76c5a8c7ce0" checksum = "7285f2a7b92f58ab198e3fd59a71d2861478f9c4642f41e83582385818941697"
dependencies = [ dependencies = [
"rb-sys-build", "rb-sys-build",
] ]
[[package]] [[package]]
name = "rb-sys-build" name = "rb-sys-build"
version = "0.9.84" version = "0.9.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c017d134afd764dd43c2faa91aa50b698a3bb4ff30e83113da483c789e74be8c" checksum = "71583945f94dabb6c0dfa63f1b71e929c1901e1e288ef3739ab8bed3b7069550"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"lazy_static", "lazy_static",

View file

@ -7,4 +7,4 @@ edition = "2021"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
rb-sys = "0.9.84" rb-sys = "0.9.86"

View file

@ -145,18 +145,18 @@ dependencies = [
[[package]] [[package]]
name = "rb-sys" name = "rb-sys"
version = "0.9.83" version = "0.9.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e5b8d560b60790a3e60e56e73a8c7be88ac14e6af39fc82b5eca72c71753840" checksum = "7285f2a7b92f58ab198e3fd59a71d2861478f9c4642f41e83582385818941697"
dependencies = [ dependencies = [
"rb-sys-build", "rb-sys-build",
] ]
[[package]] [[package]]
name = "rb-sys-build" name = "rb-sys-build"
version = "0.9.83" version = "0.9.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2d2bfd00002007d7e9ad93d0397437933040caf452d260c26dbef5fd95ae1a6" checksum = "71583945f94dabb6c0dfa63f1b71e929c1901e1e288ef3739ab8bed3b7069550"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"lazy_static", "lazy_static",

View file

@ -7,4 +7,4 @@ edition = "2021"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
rb-sys = "0.9.83" rb-sys = "0.9.86"

View file

@ -489,6 +489,17 @@ class TestGemRequire < Gem::TestCase
assert_equal %w[default-3.0], loaded_spec_names assert_equal %w[default-3.0], loaded_spec_names
end end
def test_normal_gem_does_not_shadow_default_gem
default_gem_spec = new_default_spec("foo", "2.0", nil, "foo.rb")
install_default_gems(default_gem_spec)
normal_gem_spec = util_spec("fake-foo", "3.0", nil, "lib/foo.rb")
install_specs(normal_gem_spec)
assert_require "foo"
assert_equal %w[foo-2.0], loaded_spec_names
end
def test_normal_gems_with_overridden_load_error_message def test_normal_gems_with_overridden_load_error_message
normal_gem_spec = util_spec("normal", "3.0", nil, "lib/normal/gem.rb") normal_gem_spec = util_spec("normal", "3.0", nil, "lib/normal/gem.rb")
@ -529,6 +540,65 @@ class TestGemRequire < Gem::TestCase
assert_equal %w[default-3.0.0.rc2], loaded_spec_names assert_equal %w[default-3.0.0.rc2], loaded_spec_names
end end
def test_default_gem_with_unresolved_gems_depending_on_it
my_http_old = util_spec "my-http", "0.1.1", nil, "lib/my/http.rb"
install_gem my_http_old
my_http_default = new_default_spec "my-http", "0.3.0", nil, "my/http.rb"
install_default_gems my_http_default
faraday_1 = util_spec "faraday", "1", { "my-http" => ">= 0" }
install_gem faraday_1
faraday_2 = util_spec "faraday", "2", { "my-http" => ">= 0" }
install_gem faraday_2
chef = util_spec "chef", "1", { "faraday" => [">= 1", "< 3"] }, "lib/chef.rb"
install_gem chef
assert_require "chef"
assert_require "my/http"
end
def test_default_gem_required_circulary_with_unresolved_gems_depending_on_it
my_http_old = util_spec "my-http", "0.1.1", nil, "lib/my/http.rb"
install_gem my_http_old
my_http_default = new_default_spec "my-http", "0.3.0", nil, "my/http.rb"
my_http_default_path = File.join(@tempdir, "default_gems", "lib", "my/http.rb")
install_default_gems my_http_default
File.write(my_http_default_path, 'require "my/http"')
faraday_1 = util_spec "faraday", "1", { "my-http" => ">= 0" }
install_gem faraday_1
faraday_2 = util_spec "faraday", "2", { "my-http" => ">= 0" }
install_gem faraday_2
chef = util_spec "chef", "1", { "faraday" => [">= 1", "< 3"] }, "lib/chef.rb"
install_gem chef
assert_require "chef"
out, err = capture_output do
assert_require "my/http"
end
assert_empty out
circular_require_warning = false
err_lines = err.split("\n").reject do |line|
if line.include?("circular require")
circular_require_warning = true
elsif circular_require_warning # ignore backtrace lines for circular require warning
circular_require_warning = line.start_with?(/[\s]/)
end
end
assert_empty err_lines
end
def loaded_spec_names def loaded_spec_names
Gem.loaded_specs.values.map(&:full_name).sort Gem.loaded_specs.values.map(&:full_name).sort
end end

View file

@ -13,7 +13,7 @@ gem "parallel", "~> 1.19"
gem "rspec-core", "~> 3.12" gem "rspec-core", "~> 3.12"
gem "rspec-expectations", "~> 3.12" gem "rspec-expectations", "~> 3.12"
gem "rspec-mocks", "~> 3.12" gem "rspec-mocks", "~> 3.12"
gem "uri", "~> 0.12.0" gem "uri", "~> 0.13.0"
group :doc do group :doc do
gem "nronn", "~> 0.11.1", platform: :ruby gem "nronn", "~> 0.11.1", platform: :ruby

View file

@ -3,6 +3,7 @@
source "https://rubygems.org" source "https://rubygems.org"
gem "rack", "~> 2.0" gem "rack", "~> 2.0"
gem "base64"
gem "webrick", "1.7.0" gem "webrick", "1.7.0"
gem "rack-test", "~> 1.1" gem "rack-test", "~> 1.1"
gem "compact_index", "~> 0.15.0" gem "compact_index", "~> 0.15.0"