Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions bundler/lib/bundler/man/bundle-config.1
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ When set, no funding requests will be printed\.
\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR)
When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\.
.TP
\fBignore_ruby_upper_bounds\fR (\fBBUNDLE_IGNORE_RUBY_UPPER_BOUNDS\fR)
When set, Bundler ignores upper bound constraints (\fB<\fR and \fB<=\fR) on \fBrequired_ruby_version\fR in gem metadata\. Useful when gems haven't updated their gemspecs for newer Ruby versions but still work fine\.
.TP
\fBignore_rubygems_upper_bounds\fR (\fBBUNDLE_IGNORE_RUBYGEMS_UPPER_BOUNDS\fR)
When set, Bundler ignores upper bound constraints (\fB<\fR and \fB<=\fR) on \fBrequired_rubygems_version\fR in gem metadata\. Useful when gems haven't updated their gemspecs for newer RubyGems versions but still work fine\.
.TP
\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR)
Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\.
.TP
Expand Down
8 changes: 8 additions & 0 deletions bundler/lib/bundler/man/bundle-config.1.ronn
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,14 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
* `ignore_messages` (`BUNDLE_IGNORE_MESSAGES`):
When set, no post install messages will be printed. To silence a single gem,
use dot notation like `ignore_messages.httparty true`.
* `ignore_ruby_upper_bounds` (`BUNDLE_IGNORE_RUBY_UPPER_BOUNDS`):
When set, Bundler ignores upper bound constraints (`<` and `<=`) on
`required_ruby_version` in gem metadata. Useful when gems haven't updated
their gemspecs for newer Ruby versions but still work fine.
* `ignore_rubygems_upper_bounds` (`BUNDLE_IGNORE_RUBYGEMS_UPPER_BOUNDS`):
When set, Bundler ignores upper bound constraints (`<` and `<=`) on
`required_rubygems_version` in gem metadata. Useful when gems haven't
updated their gemspecs for newer RubyGems versions but still work fine.
* `init_gems_rb` (`BUNDLE_INIT_GEMS_RB`):
Generate a `gems.rb` instead of a `Gemfile` when running `bundle init`.
* `jobs` (`BUNDLE_JOBS`):
Expand Down
31 changes: 29 additions & 2 deletions bundler/lib/bundler/match_metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,21 @@ def matches_current_metadata?
end

def matches_current_ruby?
@required_ruby_version.satisfied_by?(Gem.ruby_version)
requirement = if Bundler.settings[:ignore_ruby_upper_bounds]
remove_upper_bounds(@required_ruby_version)
else
@required_ruby_version
end
requirement.satisfied_by?(Gem.ruby_version)
end

def matches_current_rubygems?
@required_rubygems_version.satisfied_by?(Gem.rubygems_version)
requirement = if Bundler.settings[:ignore_rubygems_upper_bounds]
remove_upper_bounds(@required_rubygems_version)
else
@required_rubygems_version
end
requirement.satisfied_by?(Gem.rubygems_version)
end

def expanded_dependencies
Expand All @@ -24,7 +34,24 @@ def expanded_dependencies
def metadata_dependency(name, requirement)
return if requirement.nil? || requirement.none?

if (name == "Ruby" && Bundler.settings[:ignore_ruby_upper_bounds]) ||
(name == "RubyGems" && Bundler.settings[:ignore_rubygems_upper_bounds])
requirement = remove_upper_bounds(requirement)
return if requirement.none?
end

Gem::Dependency.new("#{name}\0", requirement)
end

private

def remove_upper_bounds(requirement)
filtered = requirement.requirements.reject {|op, _| ["<", "<="].include?(op) }
if filtered.empty?
Gem::Requirement.default
else
Gem::Requirement.new(filtered.map {|op, v| "#{op} #{v}" })
end
Comment on lines +48 to +54
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove_upper_bounds always allocates (reject array + new requirement) even when the requirement has no </<= constraints. Since this method can run on hot paths (metadata checks + expanded deps), consider fast-pathing by returning the original requirement when no upper-bound operators are present, and avoid allocating the ['<','<='] array on every call (e.g., a frozen constant or simple op == '<' || op == '<=').

Copilot uses AI. Check for mistakes.
end
end
end
2 changes: 2 additions & 0 deletions bundler/lib/bundler/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class Settings
git.allow_insecure
global_gem_cache
ignore_messages
ignore_ruby_upper_bounds
ignore_rubygems_upper_bounds
init_gems_rb
inline
lockfile_checksums
Expand Down
124 changes: 124 additions & 0 deletions spec/bundler/match_metadata_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# frozen_string_literal: true

RSpec.describe Bundler::MatchMetadata do
let(:klass) do
Class.new do
include Bundler::MatchMetadata

attr_accessor :required_ruby_version, :required_rubygems_version, :dependencies

def initialize(ruby_version, rubygems_version)
@required_ruby_version = ruby_version
@required_rubygems_version = rubygems_version
@dependencies = []
end

alias_method :runtime_dependencies, :dependencies
end
end

describe "#matches_current_ruby?" do
context "when the ruby version has an upper bound that excludes the current ruby" do
let(:spec) { klass.new(Gem::Requirement.new(">= 3.0", "< 3.1"), Gem::Requirement.default) }

it "returns false by default" do
expect(spec.matches_current_ruby?).to be false
end

it "returns true when ignore_ruby_upper_bounds is set" do
bundle "config set ignore_ruby_upper_bounds true"
expect(spec.matches_current_ruby?).to be true
end
Comment on lines +28 to +31
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These examples spawn a separate bundle config set ... process just to toggle settings. In this spec suite there’s already a bundle_config helper that edits .bundle/config directly, and it’s much faster/less flaky for in-process expectations like matches_current_*?. Consider switching these to bundle_config "ignore_ruby_upper_bounds true" / bundle_config "ignore_rubygems_upper_bounds true" (or Bundler.settings.temporary(...)) to keep the spec lightweight.

Copilot uses AI. Check for mistakes.
end

context "when the ruby version has only a lower bound" do
let(:spec) { klass.new(Gem::Requirement.new(">= 2.0"), Gem::Requirement.default) }

it "returns true regardless of setting" do
expect(spec.matches_current_ruby?).to be true

bundle "config set ignore_ruby_upper_bounds true"
expect(spec.matches_current_ruby?).to be true
end
end

context "when the ruby version has only an upper bound" do
let(:spec) { klass.new(Gem::Requirement.new("< 3.1"), Gem::Requirement.default) }

it "returns false by default" do
expect(spec.matches_current_ruby?).to be false
end

it "returns true when ignore_ruby_upper_bounds is set (upper bound removed, defaults to >= 0)" do
bundle "config set ignore_ruby_upper_bounds true"
expect(spec.matches_current_ruby?).to be true
end
end
end

describe "#matches_current_rubygems?" do
context "when the rubygems version has an upper bound that excludes the current rubygems" do
let(:spec) { klass.new(Gem::Requirement.default, Gem::Requirement.new(">= 3.0", "< 3.1")) }

it "returns false by default" do
expect(spec.matches_current_rubygems?).to be false
end

it "returns true when ignore_rubygems_upper_bounds is set" do
bundle "config set ignore_rubygems_upper_bounds true"
expect(spec.matches_current_rubygems?).to be true
end
end

context "when the rubygems version has only a lower bound" do
let(:spec) { klass.new(Gem::Requirement.default, Gem::Requirement.new(">= 2.0")) }

it "returns true regardless of setting" do
expect(spec.matches_current_rubygems?).to be true

bundle "config set ignore_rubygems_upper_bounds true"
expect(spec.matches_current_rubygems?).to be true
end
end
Comment on lines +59 to +82
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage looks asymmetric between Ruby and RubyGems: there’s a “only an upper bound” case for matches_current_ruby?, but not for matches_current_rubygems?. Since remove_upper_bounds returns the default requirement when only upper bounds exist, it would be good to add a RubyGems-only-upper-bound example (and similarly for metadata_dependency returning nil when required_rubygems_version is only an upper bound) to prevent regressions.

Copilot uses AI. Check for mistakes.
end

describe "#metadata_dependency" do
context "when ignore_ruby_upper_bounds is set" do
before { bundle "config set ignore_ruby_upper_bounds true" }

it "removes upper bounds from Ruby dependency" do
spec = klass.new(Gem::Requirement.new(">= 3.0", "< 3.1"), Gem::Requirement.default)
dep = spec.send(:metadata_dependency, "Ruby", spec.required_ruby_version)
expect(dep.requirement).to eq(Gem::Requirement.new(">= 3.0"))
end

it "returns nil when only upper bounds exist" do
spec = klass.new(Gem::Requirement.new("< 3.1"), Gem::Requirement.default)
dep = spec.send(:metadata_dependency, "Ruby", spec.required_ruby_version)
expect(dep).to be_nil
end

it "does not affect RubyGems dependency" do
spec = klass.new(Gem::Requirement.default, Gem::Requirement.new(">= 3.0", "< 4.0"))
dep = spec.send(:metadata_dependency, "RubyGems", spec.required_rubygems_version)
expect(dep.requirement).to eq(Gem::Requirement.new(">= 3.0", "< 4.0"))
end
end

context "when ignore_rubygems_upper_bounds is set" do
before { bundle "config set ignore_rubygems_upper_bounds true" }

it "removes upper bounds from RubyGems dependency" do
spec = klass.new(Gem::Requirement.default, Gem::Requirement.new(">= 3.0", "< 4.0"))
dep = spec.send(:metadata_dependency, "RubyGems", spec.required_rubygems_version)
expect(dep.requirement).to eq(Gem::Requirement.new(">= 3.0"))
end

it "does not affect Ruby dependency" do
spec = klass.new(Gem::Requirement.new(">= 3.0", "< 3.1"), Gem::Requirement.default)
dep = spec.send(:metadata_dependency, "Ruby", spec.required_ruby_version)
expect(dep.requirement).to eq(Gem::Requirement.new(">= 3.0", "< 3.1"))
end
end
end
end
Loading