Merge RubyGems 4.0.3 and Bundler 4.0.3

This commit is contained in:
Hiroshi SHIBATA 2025-12-23 13:48:03 +09:00
parent ed1aac5486
commit 1c3ef27191
Notes: git 2025-12-23 07:10:32 +00:00
19 changed files with 164 additions and 34 deletions

View File

@ -138,24 +138,16 @@ module Bundler
source.local!
if use_exact_resolved_specifications?
materialize(self) do |matching_specs|
choose_compatible(matching_specs)
spec = materialize(self) {|specs| choose_compatible(specs, fallback_to_non_installable: false) }
return spec if spec
# Exact spec is incompatible; in frozen mode, try to find a compatible platform variant
# In non-frozen mode, return nil to trigger re-resolution and lockfile update
if Bundler.frozen_bundle?
materialize([name, version]) {|specs| resolve_best_platform(specs) }
end
else
materialize([name, version]) do |matching_specs|
target_platform = source.is_a?(Source::Path) ? platform : Bundler.local_platform
installable_candidates = MatchPlatform.select_best_platform_match(matching_specs, target_platform)
specification = choose_compatible(installable_candidates, fallback_to_non_installable: false)
return specification unless specification.nil?
if target_platform != platform
installable_candidates = MatchPlatform.select_best_platform_match(matching_specs, platform)
end
choose_compatible(installable_candidates)
end
materialize([name, version]) {|specs| resolve_best_platform(specs) }
end
end
@ -190,6 +182,39 @@ module Bundler
!source.is_a?(Source::Path) && ruby_platform_materializes_to_ruby_platform?
end
# Try platforms in order of preference until finding a compatible spec.
# Used for legacy lockfiles and as a fallback when the exact locked spec
# is incompatible. Falls back to frozen bundle behavior if none match.
def resolve_best_platform(specs)
find_compatible_platform_spec(specs) || frozen_bundle_fallback(specs)
end
def find_compatible_platform_spec(specs)
candidate_platforms.each do |plat|
candidates = MatchPlatform.select_best_platform_match(specs, plat)
spec = choose_compatible(candidates, fallback_to_non_installable: false)
return spec if spec
end
nil
end
# Platforms to try in order of preference. Ruby platform is last since it
# requires compilation, but works when precompiled gems are incompatible.
def candidate_platforms
target = source.is_a?(Source::Path) ? platform : Bundler.local_platform
[target, platform, Gem::Platform::RUBY].uniq
end
# In frozen mode, accept any candidate. Will error at install time.
# When target differs from locked platform, prefer locked platform's candidates
# to preserve lockfile integrity.
def frozen_bundle_fallback(specs)
target = source.is_a?(Source::Path) ? platform : Bundler.local_platform
fallback_platform = target == platform ? target : platform
candidates = MatchPlatform.select_best_platform_match(specs, fallback_platform)
choose_compatible(candidates)
end
def ruby_platform_materializes_to_ruby_platform?
generic_platform = Bundler.generic_local_platform == Gem::Platform::JAVA ? Gem::Platform::JAVA : Gem::Platform::RUBY

View File

@ -1,7 +1,7 @@
# frozen_string_literal: false
module Bundler
VERSION = "4.0.2".freeze
VERSION = "4.0.3".freeze
def self.bundler_major_version
@bundler_major_version ||= gem_version.segments.first

View File

@ -9,7 +9,7 @@
require "rbconfig"
module Gem
VERSION = "4.0.2"
VERSION = "4.0.3"
end
require_relative "rubygems/defaults"

View File

@ -22,7 +22,7 @@ module Gem
def register(target, obj)
end
# This is ported over from the yaml_tree in 1.9.3
# This is ported over from the YAMLTree implementation in Ruby 1.9.3
def format_time(time)
if time.utc?
time.strftime("%Y-%m-%d %H:%M:%S.%9N Z")

View File

@ -38,7 +38,7 @@ class Gem::RequestSet::Lockfile
end
##
# Creates a new Lockfile for the given +request_set+ and +gem_deps_file+
# Creates a new Lockfile for the given Gem::RequestSet and +gem_deps_file+
# location.
def self.build(request_set, gem_deps_file, dependencies = nil)

View File

@ -143,7 +143,7 @@ class Gem::Security::Policy
end
##
# Ensures the root of +chain+ has a trusted certificate in +trust_dir+ and
# Ensures the root of +chain+ has a trusted certificate in Gem::Security.trust_dir and
# the digests of the two certificates match according to +digester+
def check_trust(chain, digester, trust_dir)

View File

@ -102,7 +102,7 @@ class Gem::Source
end
##
# Fetches a specification for the given +name_tuple+.
# Fetches a specification for the given Gem::NameTuple.
def fetch_spec(name_tuple)
fetcher = Gem::RemoteFetcher.fetcher

View File

@ -2,7 +2,17 @@
RSpec.describe Bundler::Plugin::Events do
context "plugin events" do
before { Bundler::Plugin::Events.send :reset }
before do
@old_constants = Bundler::Plugin::Events.constants.map {|name| [name, Bundler::Plugin::Events.const_get(name)] }
Bundler::Plugin::Events.send :reset
end
after do
Bundler::Plugin::Events.send(:reset)
Hash[@old_constants].each do |name, value|
Bundler::Plugin::Events.send(:define, name, value)
end
end
describe "#define" do
it "raises when redefining a constant" do

View File

@ -279,6 +279,7 @@ RSpec.describe Bundler::Plugin do
s.write "plugins.rb", code
end
@old_constants = Bundler::Plugin::Events.constants.map {|name| [name, Bundler::Plugin::Events.const_get(name)] }
Bundler::Plugin::Events.send(:reset)
Bundler::Plugin::Events.send(:define, :EVENT1, "event-1")
Bundler::Plugin::Events.send(:define, :EVENT2, "event-2")
@ -291,6 +292,13 @@ RSpec.describe Bundler::Plugin do
allow(index).to receive(:load_paths).with("foo-plugin").and_return([])
end
after do
Bundler::Plugin::Events.send(:reset)
Hash[@old_constants].each do |name, value|
Bundler::Plugin::Events.send(:define, name, value)
end
end
let(:code) { <<-RUBY }
Bundler::Plugin::API.hook("event-1") { puts "hook for event 1" }
RUBY

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
RSpec.describe Bundler::URINormalizer do
describe ".normalize_suffix" do
context "when trailing_slash is true" do
it "adds a trailing slash when missing" do
expect(described_class.normalize_suffix("https://example.com", trailing_slash: true)).to eq("https://example.com/")
end
it "keeps the trailing slash when present" do
expect(described_class.normalize_suffix("https://example.com/", trailing_slash: true)).to eq("https://example.com/")
end
end
context "when trailing_slash is false" do
it "removes a trailing slash when present" do
expect(described_class.normalize_suffix("https://example.com/", trailing_slash: false)).to eq("https://example.com")
end
it "keeps the value unchanged when no trailing slash exists" do
expect(described_class.normalize_suffix("https://example.com", trailing_slash: false)).to eq("https://example.com")
end
end
end
end

View File

@ -157,6 +157,11 @@ RSpec.describe "bundle install with specific platforms" do
end
context "when running on a legacy lockfile locked only to ruby" do
# Exercises the legacy lockfile path (use_exact_resolved_specifications? = false)
# because most_specific_locked_platform is ruby, matching the generic platform.
# Key insight: when target (arm64-darwin-22) != platform (ruby), the code tries
# both platforms before falling back, preserving lockfile integrity.
around do |example|
build_repo4 do
build_gem "nokogiri", "1.3.10"
@ -192,13 +197,69 @@ RSpec.describe "bundle install with specific platforms" do
end
it "still installs the generic ruby variant if necessary" do
bundle "install --verbose"
expect(out).to include("Installing nokogiri 1.3.10")
bundle "install"
expect(the_bundle).to include_gem("nokogiri 1.3.10")
expect(the_bundle).not_to include_gem("nokogiri 1.3.10 arm64-darwin")
end
it "still installs the generic ruby variant if necessary, even in frozen mode" do
bundle "install --verbose", env: { "BUNDLE_FROZEN" => "true" }
expect(out).to include("Installing nokogiri 1.3.10")
bundle "install", env: { "BUNDLE_FROZEN" => "true" }
expect(the_bundle).to include_gem("nokogiri 1.3.10")
expect(the_bundle).not_to include_gem("nokogiri 1.3.10 arm64-darwin")
end
end
context "when platform-specific gem has incompatible required_ruby_version" do
# Key insight: candidate_platforms tries [target, platform, ruby] in order.
# Ruby platform is last since it requires compilation, but works when
# precompiled gems are incompatible with the current Ruby version.
#
# Note: This fix requires the lockfile to include both ruby and platform-
# specific variants (typical after `bundle lock --add-platform`). If the
# lockfile only has platform-specific gems, frozen mode cannot help because
# Bundler.setup would still expect the locked (incompatible) gem.
# Exercises the exact spec path (use_exact_resolved_specifications? = true)
# because lockfile has platform-specific entry as most_specific_locked_platform
it "falls back to ruby platform in frozen mode when lockfile includes both variants" do
build_repo4 do
build_gem "nokogiri", "1.18.10"
build_gem "nokogiri", "1.18.10" do |s|
s.platform = "x86_64-linux"
s.required_ruby_version = "< #{Gem.ruby_version}"
end
end
gemfile <<~G
source "https://gem.repo4"
gem "nokogiri"
G
# Lockfile has both ruby and platform-specific gem (typical after `bundle lock --add-platform`)
lockfile <<-L
GEM
remote: https://gem.repo4/
specs:
nokogiri (1.18.10)
nokogiri (1.18.10-x86_64-linux)
PLATFORMS
ruby
x86_64-linux
DEPENDENCIES
nokogiri
BUNDLED WITH
#{Bundler::VERSION}
L
simulate_platform "x86_64-linux" do
bundle "install", env: { "BUNDLE_FROZEN" => "true" }
expect(the_bundle).to include_gem("nokogiri 1.18.10")
expect(the_bundle).not_to include_gem("nokogiri 1.18.10 x86_64-linux")
end
end
end

View File

@ -46,4 +46,4 @@ DEPENDENCIES
tapioca
BUNDLED WITH
4.0.2
4.0.3

View File

@ -36,4 +36,4 @@ DEPENDENCIES
warbler!
BUNDLED WITH
4.0.2
4.0.3

View File

@ -184,6 +184,7 @@ module Spec
"spec/bundler/resolver/candidate_spec.rb",
"spec/bundler/digest_spec.rb",
"spec/bundler/fetcher/gem_remote_fetcher_spec.rb",
"spec/bundler/uri_normalizer_spec.rb",
],
}.freeze
end

View File

@ -1610,7 +1610,7 @@ ERROR: Possible alternatives: non_existent_with_hint
gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out"))
if vc_windows? && nmake_found?
refute_includes(gem_make_out, " -j4")
refute_includes(gem_make_out, "-j4")
else
assert_includes(gem_make_out, "make -j4")
end

View File

@ -129,4 +129,4 @@ CHECKSUMS
turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f
BUNDLED WITH
4.0.2
4.0.3

View File

@ -156,4 +156,4 @@ CHECKSUMS
unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5
BUNDLED WITH
4.0.2
4.0.3

View File

@ -176,4 +176,4 @@ CHECKSUMS
unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5
BUNDLED WITH
4.0.2
4.0.3

View File

@ -103,4 +103,4 @@ CHECKSUMS
tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770
BUNDLED WITH
4.0.2
4.0.3