From a65c7c8428fb2c50672e8e2f1d27e1416870cb5e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20Rodr=C3=ADguez?=
<2887858+deivid-rodriguez@users.noreply.github.com>
Date: Mon, 15 Sep 2025 20:40:43 +0200
Subject: [PATCH 01/18] [rubygems/rubygems] Fix parallel installation issue
If using a gem with precompiled versions having different dependencies
than the generic version from a path source, and with a lockfile
including a precompiled version, we would materialize the
generic version, but end up using dependencies for the precompiled
version. That will result in the parallel installer missing the
specifications for the extra dependencies of the generic version,
causing a crash.
If we are materializing for installation, make sure we use the
materialized specification when traversing dependencies.
https://github.com/rubygems/rubygems/commit/5f75d75de7
---
lib/bundler/materialization.rb | 2 +-
spec/bundler/install/gemfile/path_spec.rb | 49 +++++++++++++++++++++++
2 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/lib/bundler/materialization.rb b/lib/bundler/materialization.rb
index 43124f25fbedbf..82e48464a73bd9 100644
--- a/lib/bundler/materialization.rb
+++ b/lib/bundler/materialization.rb
@@ -29,7 +29,7 @@ def specs
end
def dependencies
- specs.first.runtime_dependencies.map {|d| [d, platform] }
+ (materialized_spec || specs.first).runtime_dependencies.map {|d| [d, platform] }
end
def materialized_spec
diff --git a/spec/bundler/install/gemfile/path_spec.rb b/spec/bundler/install/gemfile/path_spec.rb
index 55bf11928d6acd..ea59f11bbe9773 100644
--- a/spec/bundler/install/gemfile/path_spec.rb
+++ b/spec/bundler/install/gemfile/path_spec.rb
@@ -831,6 +831,55 @@
end
end
+ context "when platform specific version locked, and having less dependencies that the generic version that's actually installed" do
+ before do
+ build_repo4 do
+ build_gem "racc", "1.8.1"
+ build_gem "mini_portile2", "2.8.2"
+ end
+
+ build_lib "nokogiri", "1.18.9", path: lib_path("nokogiri") do |s|
+ s.add_dependency "mini_portile2", "~> 2.8.2"
+ s.add_dependency "racc", "~> 1.4"
+ end
+
+ gemfile <<~G
+ source "https://gem.repo4"
+
+ gem "nokogiri", path: "#{lib_path("nokogiri")}"
+ G
+
+ lockfile <<~L
+ PATH
+ remote: #{lib_path("nokogiri")}
+ specs:
+ nokogiri (1.18.9)
+ mini_portile2 (~> 2.8.2)
+ racc (~> 1.4)
+ nokogiri (1.18.9-arm64-darwin)
+ racc (~> 1.4)
+
+ GEM
+ remote: https://rubygems.org/
+ specs:
+ racc (1.8.1)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ nokogiri!
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "works" do
+ bundle "install"
+ end
+ end
+
describe "switching sources" do
it "doesn't switch pinned git sources to rubygems when pinning the parent gem to a path source" do
build_gem "foo", "1.0", to_system: true do |s|
From 127a8c0bd3cac022997efd18ce6747698c379d53 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20Rodr=C3=ADguez?=
<2887858+deivid-rodriguez@users.noreply.github.com>
Date: Tue, 9 Sep 2025 19:20:36 +0200
Subject: [PATCH 02/18] [rubygems/rubygems] Make `--local-git` flag to `bundle
plugin install` raise an error
https://github.com/rubygems/rubygems/commit/8bfe317e6d
---
lib/bundler/cli/plugin.rb | 2 +-
lib/bundler/man/bundle-plugin.1 | 9 +--------
lib/bundler/man/bundle-plugin.1.ronn | 8 --------
lib/bundler/plugin/installer.rb | 8 +-------
spec/bundler/other/major_deprecation_spec.rb | 9 +++------
spec/bundler/plugins/install_spec.rb | 7 -------
6 files changed, 6 insertions(+), 37 deletions(-)
diff --git a/lib/bundler/cli/plugin.rb b/lib/bundler/cli/plugin.rb
index fd61ef0d954dea..840394cbe47486 100644
--- a/lib/bundler/cli/plugin.rb
+++ b/lib/bundler/cli/plugin.rb
@@ -10,7 +10,7 @@ class CLI::Plugin < Thor
method_option "source", type: :string, default: nil, banner: "URL of the RubyGems source to fetch the plugin from"
method_option "version", type: :string, default: nil, banner: "The version of the plugin to fetch"
method_option "git", type: :string, default: nil, banner: "URL of the git repo to fetch from"
- method_option "local_git", type: :string, default: nil, banner: "Path of the local git repo to fetch from (deprecated)"
+ method_option "local_git", type: :string, default: nil, banner: "Path of the local git repo to fetch from (removed)"
method_option "branch", type: :string, default: nil, banner: "The git branch to checkout"
method_option "ref", type: :string, default: nil, banner: "The git revision to check out"
method_option "path", type: :string, default: nil, banner: "Path of a local gem to directly use"
diff --git a/lib/bundler/man/bundle-plugin.1 b/lib/bundler/man/bundle-plugin.1
index 9da6927dc4d8f0..25da56247598b5 100644
--- a/lib/bundler/man/bundle-plugin.1
+++ b/lib/bundler/man/bundle-plugin.1
@@ -4,7 +4,7 @@
.SH "NAME"
\fBbundle\-plugin\fR \- Manage Bundler plugins
.SH "SYNOPSIS"
-\fBbundle plugin\fR install PLUGINS [\-\-source=SOURCE] [\-\-version=VERSION] [\-\-git=GIT] [\-\-branch=BRANCH|\-\-ref=REF] [\-\-local\-git=LOCAL_GIT] [\-\-path=PATH]
+\fBbundle plugin\fR install PLUGINS [\-\-source=SOURCE] [\-\-version=VERSION] [\-\-git=GIT] [\-\-branch=BRANCH|\-\-ref=REF] [\-\-path=PATH]
.br
\fBbundle plugin\fR uninstall PLUGINS [\-\-all]
.br
@@ -54,13 +54,6 @@ When you specify \fB\-\-git\fR, you can use \fB\-\-ref\fR to specify any tag, or
Install the plugin gem from a local path\.
.IP
Example: \fBbundle plugin install bundler\-graph \-\-path \.\./bundler\-graph\fR
-.TP
-\fB\-\-local\-git=LOCAL_GIT\fR
-Install the plugin gem from a local Git repository\.
-.IP
-Example: \fBbundle plugin install bundler\-graph \-\-local\-git \.\./bundler\-graph\fR\.
-.IP
-This option is deprecated in favor of \fB\-\-git\fR\.
.SS "uninstall"
Uninstall the plugin(s) specified in PLUGINS\.
.P
diff --git a/lib/bundler/man/bundle-plugin.1.ronn b/lib/bundler/man/bundle-plugin.1.ronn
index efb42407611836..b54e0c08b4e424 100644
--- a/lib/bundler/man/bundle-plugin.1.ronn
+++ b/lib/bundler/man/bundle-plugin.1.ronn
@@ -5,7 +5,6 @@ bundle-plugin(1) -- Manage Bundler plugins
`bundle plugin` install PLUGINS [--source=SOURCE] [--version=VERSION]
[--git=GIT] [--branch=BRANCH|--ref=REF]
- [--local-git=LOCAL_GIT]
[--path=PATH]
`bundle plugin` uninstall PLUGINS [--all]
`bundle plugin` list
@@ -59,13 +58,6 @@ global source specified in Gemfile is ignored.
Example: `bundle plugin install bundler-graph --path ../bundler-graph`
-* `--local-git=LOCAL_GIT`:
- Install the plugin gem from a local Git repository.
-
- Example: `bundle plugin install bundler-graph --local-git ../bundler-graph`.
-
- This option is deprecated in favor of `--git`.
-
### uninstall
Uninstall the plugin(s) specified in PLUGINS.
diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb
index ac3c3ea7f31216..6ead207652f909 100644
--- a/lib/bundler/plugin/installer.rb
+++ b/lib/bundler/plugin/installer.rb
@@ -43,14 +43,8 @@ def definition.lock(*); end
private
def check_sources_consistency!(options)
- if options.key?(:git) && options.key?(:local_git)
- raise InvalidOption, "Remote and local plugin git sources can't be both specified"
- end
-
- # back-compat; local_git is an alias for git
if options.key?(:local_git)
- Bundler::SharedHelpers.major_deprecation(2, "--local_git is deprecated, use --git")
- options[:git] = options.delete(:local_git)
+ raise InvalidOption, "--local_git has been removed, use --git"
end
if (options.keys & [:source, :git, :path]).length > 1
diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb
index 1749d7e6827eaf..668a36b8dbce9d 100644
--- a/spec/bundler/other/major_deprecation_spec.rb
+++ b/spec/bundler/other/major_deprecation_spec.rb
@@ -702,14 +702,11 @@
end
end
- it "prints a deprecation warning" do
- bundle "plugin install foo --local_git #{lib_path("foo-1.0")}"
+ it "fails with a helpful message" do
+ bundle "plugin install foo --local_git #{lib_path("foo-1.0")}", raise_on_error: false
- expect(out).to include("Installed plugin foo")
- expect(deprecations).to include "--local_git is deprecated, use --git"
+ expect(err).to include "--local_git has been removed, use --git"
end
-
- pending "fails with a helpful message", bundler: "4"
end
describe "removing rubocop" do
diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb
index 1ccefabc0b50a9..0cddeb09185bc7 100644
--- a/spec/bundler/plugins/install_spec.rb
+++ b/spec/bundler/plugins/install_spec.rb
@@ -203,13 +203,6 @@ def exec(command, args)
expect(out).to include("Installed plugin foo")
plugin_should_be_installed("foo")
end
-
- it "raises an error when both git and local git sources are specified" do
- bundle "plugin install foo --git /phony/path/project --local_git git@gitphony.com:/repo/project", raise_on_error: false
-
- expect(exitstatus).not_to eq(0)
- expect(err).to eq("Remote and local plugin git sources can't be both specified")
- end
end
context "path plugins" do
From 9dedfb60d596ab7e34f5613650fed10963a24e21 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20Rodr=C3=ADguez?=
<2887858+deivid-rodriguez@users.noreply.github.com>
Date: Tue, 16 Sep 2025 19:01:04 +0200
Subject: [PATCH 03/18] [rubygems/rubygems] Move error handling to right behind
the option
This is where error handling happens in all the other options, so it's
where we'll look when we completely remove error handling for these
removed CLI flags in the next major.
https://github.com/rubygems/rubygems/commit/40d660c607
---
lib/bundler/cli/plugin.rb | 4 ++++
lib/bundler/plugin/installer.rb | 4 ----
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/lib/bundler/cli/plugin.rb b/lib/bundler/cli/plugin.rb
index 840394cbe47486..32fa660fe0a3f1 100644
--- a/lib/bundler/cli/plugin.rb
+++ b/lib/bundler/cli/plugin.rb
@@ -15,6 +15,10 @@ class CLI::Plugin < Thor
method_option "ref", type: :string, default: nil, banner: "The git revision to check out"
method_option "path", type: :string, default: nil, banner: "Path of a local gem to directly use"
def install(*plugins)
+ if options.key?(:local_git)
+ raise InvalidOption, "--local_git has been removed, use --git"
+ end
+
Bundler::Plugin.install(plugins, options)
end
diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb
index 6ead207652f909..853ad9edca4f6f 100644
--- a/lib/bundler/plugin/installer.rb
+++ b/lib/bundler/plugin/installer.rb
@@ -43,10 +43,6 @@ def definition.lock(*); end
private
def check_sources_consistency!(options)
- if options.key?(:local_git)
- raise InvalidOption, "--local_git has been removed, use --git"
- end
-
if (options.keys & [:source, :git, :path]).length > 1
raise InvalidOption, "Only one of --source, --git, or --path may be specified"
end
From 23fb4d50200c39b1df320fdc8ac3ed071923361c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20Rodr=C3=ADguez?=
<2887858+deivid-rodriguez@users.noreply.github.com>
Date: Tue, 9 Sep 2025 19:20:41 +0200
Subject: [PATCH 04/18] [rubygems/rubygems] Make `bundle show --outdated` raise
an error
https://github.com/rubygems/rubygems/commit/3bbbf4a4e5
---
lib/bundler/cli.rb | 5 ++--
lib/bundler/cli/show.rb | 2 +-
lib/bundler/man/bundle-show.1 | 5 +---
lib/bundler/man/bundle-show.1.ronn | 4 ---
spec/bundler/commands/show_spec.rb | 27 --------------------
spec/bundler/other/major_deprecation_spec.rb | 8 +++---
6 files changed, 7 insertions(+), 44 deletions(-)
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb
index 0635260e95c2db..61b6ec526d679a 100644
--- a/lib/bundler/cli.rb
+++ b/lib/bundler/cli.rb
@@ -288,12 +288,11 @@ def update(*gems)
Calling show with [GEM] will list the exact location of that gem on your machine.
D
method_option "paths", type: :boolean, banner: "List the paths of all gems that are required by your Gemfile."
- method_option "outdated", type: :boolean, banner: "Show verbose output including whether gems are outdated."
+ method_option "outdated", type: :boolean, banner: "Show verbose output including whether gems are outdated (removed)."
def show(gem_name = nil)
if ARGV.include?("--outdated")
- message = "the `--outdated` flag to `bundle show` will be removed in favor of `bundle show --verbose`"
removed_message = "the `--outdated` flag to `bundle show` has been removed in favor of `bundle show --verbose`"
- SharedHelpers.major_deprecation(2, message, removed_message: removed_message)
+ raise InvalidOption, removed_message
end
require_relative "cli/show"
Show.new(options, gem_name).run
diff --git a/lib/bundler/cli/show.rb b/lib/bundler/cli/show.rb
index b55eb7bad5aecc..67fdcc797e8592 100644
--- a/lib/bundler/cli/show.rb
+++ b/lib/bundler/cli/show.rb
@@ -6,7 +6,7 @@ class CLI::Show
def initialize(options, gem_name)
@options = options
@gem_name = gem_name
- @verbose = options[:verbose] || options[:outdated]
+ @verbose = options[:verbose]
@latest_specs = fetch_latest_specs if @verbose
end
diff --git a/lib/bundler/man/bundle-show.1 b/lib/bundler/man/bundle-show.1
index 3a6d157903d6bb..901460962cfed6 100644
--- a/lib/bundler/man/bundle-show.1
+++ b/lib/bundler/man/bundle-show.1
@@ -4,7 +4,7 @@
.SH "NAME"
\fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem
.SH "SYNOPSIS"
-\fBbundle show\fR [GEM] [\-\-paths] [\-\-outdated]
+\fBbundle show\fR [GEM] [\-\-paths]
.SH "DESCRIPTION"
Without the [GEM] option, \fBshow\fR will print a list of the names and versions of all gems that are required by your [\fBGemfile(5)\fR][Gemfile(5)], sorted by name\.
.P
@@ -13,7 +13,4 @@ Calling show with [GEM] will list the exact location of that gem on your machine
.TP
\fB\-\-paths\fR
List the paths of all gems that are required by your [\fBGemfile(5)\fR][Gemfile(5)], sorted by gem name\.
-.TP
-\fB\-\-outdated\fR
-Show verbose output including whether gems are outdated\.
diff --git a/lib/bundler/man/bundle-show.1.ronn b/lib/bundler/man/bundle-show.1.ronn
index 6e80b046964704..a6a59a1445dcd3 100644
--- a/lib/bundler/man/bundle-show.1.ronn
+++ b/lib/bundler/man/bundle-show.1.ronn
@@ -5,7 +5,6 @@ bundle-show(1) -- Shows all the gems in your bundle, or the path to a gem
`bundle show` [GEM]
[--paths]
- [--outdated]
## DESCRIPTION
@@ -20,6 +19,3 @@ machine.
* `--paths`:
List the paths of all gems that are required by your [`Gemfile(5)`][Gemfile(5)],
sorted by gem name.
-
-* `--outdated`:
- Show verbose output including whether gems are outdated.
diff --git a/spec/bundler/commands/show_spec.rb b/spec/bundler/commands/show_spec.rb
index ba903ac4957a5a..34e809f1358191 100644
--- a/spec/bundler/commands/show_spec.rb
+++ b/spec/bundler/commands/show_spec.rb
@@ -210,33 +210,6 @@
expect(err).to include("Could not find gem '#{invalid_regexp}'.")
end
end
-
- context "--outdated option" do
- # Regression test for https://github.com/rubygems/bundler/issues/5375
- before do
- build_repo2
- end
-
- it "doesn't update gems to newer versions" do
- install_gemfile <<-G
- source "https://gem.repo2"
- gem "rails"
- G
-
- expect(the_bundle).to include_gem("rails 2.3.2")
-
- update_repo2 do
- build_gem "rails", "3.0.0" do |s|
- s.executables = "rails"
- end
- end
-
- bundle "show --outdated"
-
- bundle "install"
- expect(the_bundle).to include_gem("rails 2.3.2")
- end
- end
end
RSpec.describe "bundle show", bundler: "5" do
diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb
index 668a36b8dbce9d..2df61f430dcfbb 100644
--- a/spec/bundler/other/major_deprecation_spec.rb
+++ b/spec/bundler/other/major_deprecation_spec.rb
@@ -647,14 +647,12 @@
context "with --outdated flag" do
before do
- bundle "show --outdated"
+ bundle "show --outdated", raise_on_error: false
end
- it "prints a deprecation warning informing about its removal" do
- expect(deprecations).to include("the `--outdated` flag to `bundle show` will be removed in favor of `bundle show --verbose`")
+ it "fails with a helpful message" do
+ expect(err).to include("the `--outdated` flag to `bundle show` has been removed in favor of `bundle show --verbose`")
end
-
- pending "fails with a helpful message", bundler: "4"
end
end
From 44a4f88159127e8d49bd3dfddcaa32233f57dd85 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20Rodr=C3=ADguez?=
<2887858+deivid-rodriguez@users.noreply.github.com>
Date: Tue, 9 Sep 2025 19:20:53 +0200
Subject: [PATCH 05/18] [rubygems/rubygems] Switch `lockfile_checksums` to be
true by default
https://github.com/rubygems/rubygems/commit/47c3dc19ee
Co-authored-by: Jonathan Barquero
---
lib/bundler/definition.rb | 2 +-
lib/bundler/feature_flag.rb | 1 -
lib/bundler/man/bundle-config.1 | 2 +-
lib/bundler/man/bundle-config.1.ronn | 2 +-
lib/bundler/settings.rb | 1 +
spec/bundler/commands/lock_spec.rb | 41 ++++++++++++++++++++++++++--
spec/bundler/support/checksums.rb | 2 +-
7 files changed, 43 insertions(+), 8 deletions(-)
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index 32dd13399ec597..49627cc56237ce 100644
--- a/lib/bundler/definition.rb
+++ b/lib/bundler/definition.rb
@@ -136,7 +136,7 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
@locked_sources = []
@originally_locked_specs = @locked_specs
@originally_locked_sources = @locked_sources
- @locked_checksums = Bundler.feature_flag.lockfile_checksums?
+ @locked_checksums = Bundler.settings[:lockfile_checksums]
end
@unlocking_ruby ||= if @ruby_version && locked_ruby_version_object
diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb
index 73e6ddcc68beb5..09a0ae593db7d9 100644
--- a/lib/bundler/feature_flag.rb
+++ b/lib/bundler/feature_flag.rb
@@ -29,7 +29,6 @@ def self.settings_method(name, key, &default)
settings_flag(:cache_all) { bundler_4_mode? }
settings_flag(:global_gem_cache) { bundler_5_mode? }
- settings_flag(:lockfile_checksums) { bundler_4_mode? }
settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") }
settings_flag(:update_requires_all_flag) { bundler_5_mode? }
diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1
index b7276daa89195a..29e830a3b0e03b 100644
--- a/lib/bundler/man/bundle-config.1
+++ b/lib/bundler/man/bundle-config.1
@@ -146,7 +146,7 @@ Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init
The number of gems Bundler can install in parallel\. Defaults to the number of available processors\.
.TP
\fBlockfile_checksums\fR (\fBBUNDLE_LOCKFILE_CHECKSUMS\fR)
-Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\.
+Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\. Defaults to true\.
.TP
\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR)
Whether \fBbundle package\fR should skip installing gems\.
diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn
index 18260c6c931f09..62fce8fa919aa0 100644
--- a/lib/bundler/man/bundle-config.1.ronn
+++ b/lib/bundler/man/bundle-config.1.ronn
@@ -190,7 +190,7 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
The number of gems Bundler can install in parallel. Defaults to the number of
available processors.
* `lockfile_checksums` (`BUNDLE_LOCKFILE_CHECKSUMS`):
- Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources.
+ Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources. Defaults to true.
* `no_install` (`BUNDLE_NO_INSTALL`):
Whether `bundle package` should skip installing gems.
* `no_prune` (`BUNDLE_NO_PRUNE`):
diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb
index ecc3ee8080a239..bfd68690824525 100644
--- a/lib/bundler/settings.rb
+++ b/lib/bundler/settings.rb
@@ -81,6 +81,7 @@ class Settings
"BUNDLE_RETRY" => 3,
"BUNDLE_TIMEOUT" => 10,
"BUNDLE_VERSION" => "lockfile",
+ "BUNDLE_LOCKFILE_CHECKSUMS" => true,
}.freeze
def initialize(root = nil)
diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb
index 5a31d1733a5556..a7460ed695a456 100644
--- a/spec/bundler/commands/lock_spec.rb
+++ b/spec/bundler/commands/lock_spec.rb
@@ -2097,7 +2097,7 @@
L
end
- it "generates checksums by default if configured to do so" do
+ it "generates checksums by default" do
build_repo4 do
build_gem "nokogiri", "1.14.2"
build_gem "nokogiri", "1.14.2" do |s|
@@ -2105,8 +2105,6 @@
end
end
- bundle "config lockfile_checksums true"
-
simulate_platform "x86_64-linux" do
install_gemfile <<-G
source "https://gem.repo4"
@@ -2139,6 +2137,43 @@
L
end
+ it "disables checksums if configured to do so" do
+ build_repo4 do
+ build_gem "nokogiri", "1.14.2"
+ build_gem "nokogiri", "1.14.2" do |s|
+ s.platform = "x86_64-linux"
+ end
+ end
+
+ bundle "config lockfile_checksums false"
+
+ simulate_platform "x86_64-linux" do
+ install_gemfile <<-G
+ source "https://gem.repo4"
+
+ gem "nokogiri"
+ G
+ end
+
+ expect(lockfile).to eq <<~L
+ GEM
+ remote: https://gem.repo4/
+ specs:
+ nokogiri (1.14.2)
+ nokogiri (1.14.2-x86_64-linux)
+
+ PLATFORMS
+ ruby
+ x86_64-linux
+
+ DEPENDENCIES
+ nokogiri
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
context "when re-resolving to include prereleases" do
before do
build_repo4 do
diff --git a/spec/bundler/support/checksums.rb b/spec/bundler/support/checksums.rb
index 8e0dea4a717f96..cf8ea417d67adb 100644
--- a/spec/bundler/support/checksums.rb
+++ b/spec/bundler/support/checksums.rb
@@ -58,7 +58,7 @@ def checksums_section_when_enabled(target_lockfile = nil, &block)
begin
enabled = (target_lockfile || lockfile).match?(/^CHECKSUMS$/)
rescue Errno::ENOENT
- enabled = Bundler.feature_flag.bundler_4_mode?
+ enabled = true
end
checksums_section(enabled, &block)
end
From da130d25e39d37185d7627b0d202a5c69d488c14 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20Rodr=C3=ADguez?=
<2887858+deivid-rodriguez@users.noreply.github.com>
Date: Tue, 9 Sep 2025 19:19:36 +0200
Subject: [PATCH 06/18] [rubygems/rubygems] Completely remove passing `--ext`
to `bundle gem` without a value
https://github.com/rubygems/rubygems/commit/9f34bf6854
Co-authored-by: Martin Emde
---
lib/bundler/cli.rb | 21 ++++++++-----------
spec/bundler/commands/newgem_spec.rb | 22 --------------------
spec/bundler/other/major_deprecation_spec.rb | 12 +++++++++++
3 files changed, 21 insertions(+), 34 deletions(-)
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb
index 61b6ec526d679a..c577f4981470a4 100644
--- a/lib/bundler/cli.rb
+++ b/lib/bundler/cli.rb
@@ -24,7 +24,7 @@ class CLI < Thor
}.freeze
def self.start(*)
- check_deprecated_ext_option(ARGV) if ARGV.include?("--ext")
+ check_invalid_ext_option(ARGV) if ARGV.include?("--ext")
super
ensure
@@ -657,18 +657,15 @@ def self.reformatted_help_args(args)
end
end
- def self.check_deprecated_ext_option(arguments)
- # when deprecated version of `--ext` is called
- # print out deprecation warning and pretend `--ext=c` was provided
- if deprecated_ext_value?(arguments)
- message = "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been deprecated. Please select a language, e.g. `--ext=rust` to generate a Rust extension. This gem will now be generated as if `--ext=c` was used."
+ def self.check_invalid_ext_option(arguments)
+ # when invalid version of `--ext` is called
+ if invalid_ext_value?(arguments)
removed_message = "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been removed. Please select a language, e.g. `--ext=rust` to generate a Rust extension."
- SharedHelpers.major_deprecation 2, message, removed_message: removed_message
- arguments[arguments.index("--ext")] = "--ext=c"
+ raise InvalidOption, removed_message
end
end
- def self.deprecated_ext_value?(arguments)
+ def self.invalid_ext_value?(arguments)
index = arguments.index("--ext")
next_argument = arguments[index + 1]
@@ -676,15 +673,15 @@ def self.deprecated_ext_value?(arguments)
# for example `bundle gem hello --ext c`
return false if EXTENSIONS.include?(next_argument)
- # deprecated call when --ext is called with no value in last position
+ # invalid call when --ext is called with no value in last position
# for example `bundle gem hello_gem --ext`
return true if next_argument.nil?
- # deprecated call when --ext is followed by other parameter
+ # invalid call when --ext is followed by other parameter
# for example `bundle gem --ext --no-ci hello_gem`
return true if next_argument.start_with?("-")
- # deprecated call when --ext is followed by gem name
+ # invalid call when --ext is followed by gem name
# for example `bundle gem --ext hello_gem`
return true if next_argument
diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb
index 0b13344f994154..1ce4a0da09cac1 100644
--- a/spec/bundler/commands/newgem_spec.rb
+++ b/spec/bundler/commands/newgem_spec.rb
@@ -1661,24 +1661,6 @@ def create_temporary_dir(dir)
include_examples "paths that depend on gem name"
- context "--ext parameter with no value" do
- context "is deprecated" do
- it "prints deprecation when used after gem name" do
- bundle ["gem", "--ext", gem_name].compact.join(" ")
- expect(err).to include "[DEPRECATED]"
- expect(err).to include "`--ext` with no arguments has been deprecated"
- expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c")).to exist
- end
-
- it "prints deprecation when used before gem name" do
- bundle ["gem", gem_name, "--ext"].compact.join(" ")
- expect(err).to include "[DEPRECATED]"
- expect(err).to include "`--ext` with no arguments has been deprecated"
- expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c")).to exist
- end
- end
- end
-
context "--ext parameter set with C" do
let(:flags) { "--ext=c" }
@@ -1686,10 +1668,6 @@ def create_temporary_dir(dir)
bundle ["gem", gem_name, flags].compact.join(" ")
end
- it "is not deprecated" do
- expect(err).not_to include "[DEPRECATED] Option `--ext` without explicit value is deprecated."
- end
-
it "builds ext skeleton" do
expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb")).to exist
expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.h")).to exist
diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb
index 2df61f430dcfbb..6117ff61374845 100644
--- a/spec/bundler/other/major_deprecation_spec.rb
+++ b/spec/bundler/other/major_deprecation_spec.rb
@@ -735,4 +735,16 @@
end
end
end
+
+ context " bundle gem --ext parameter with no value" do
+ it "prints error when used before gem name" do
+ bundle "gem --ext foo", raise_on_error: false
+ expect(err).to include "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been removed. Please select a language, e.g. `--ext=rust` to generate a Rust extension."
+ end
+
+ it "prints error when used after gem name" do
+ bundle "gem foo --ext", raise_on_error: false
+ expect(err).to include "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been removed. Please select a language, e.g. `--ext=rust` to generate a Rust extension."
+ end
+ end
end
From 3bf695a767b0f03e39a93e27296499eca1bec64f Mon Sep 17 00:00:00 2001
From: Aaron Patterson
Date: Wed, 17 Sep 2025 18:10:27 +0900
Subject: [PATCH 07/18] [rubygems/rubygems] Pull `Gem.win_platform?` out of a
hot path
`normalize_path` is a pretty hot path, it's called many times per file
in each gem. Since the platform isn't going to change from call to call,
we can conditionally define `normalize_path` based on the value of
`Gem.win_platform?`.
https://github.com/rubygems/rubygems/commit/d5e61411f2
---
lib/rubygems.rb | 27 +--------------------------
lib/rubygems/package.rb | 9 ++++++---
lib/rubygems/win_platform.rb | 31 +++++++++++++++++++++++++++++++
3 files changed, 38 insertions(+), 29 deletions(-)
create mode 100644 lib/rubygems/win_platform.rb
diff --git a/lib/rubygems.rb b/lib/rubygems.rb
index f8f1451ee661a7..8bb8cdfc0486de 100644
--- a/lib/rubygems.rb
+++ b/lib/rubygems.rb
@@ -16,6 +16,7 @@ module Gem
require_relative "rubygems/deprecate"
require_relative "rubygems/errors"
require_relative "rubygems/target_rbconfig"
+require_relative "rubygems/win_platform"
##
# RubyGems is the Ruby standard for publishing and managing third party
@@ -113,18 +114,6 @@ module Gem
module Gem
RUBYGEMS_DIR = __dir__
- ##
- # An Array of Regexps that match windows Ruby platforms.
-
- WIN_PATTERNS = [
- /bccwin/i,
- /cygwin/i,
- /djgpp/i,
- /mingw/i,
- /mswin/i,
- /wince/i,
- ].freeze
-
GEM_DEP_FILES = %w[
gem.deps.rb
gems.rb
@@ -160,8 +149,6 @@ module Gem
DEFAULT_SOURCE_DATE_EPOCH = 315_619_200
- @@win_platform = nil
-
@configuration = nil
@gemdeps = nil
@loaded_specs = {}
@@ -1091,18 +1078,6 @@ def self.use_paths(home, *paths)
self.paths = hash
end
- ##
- # Is this a windows platform?
-
- def self.win_platform?
- if @@win_platform.nil?
- ruby_platform = RbConfig::CONFIG["host_os"]
- @@win_platform = !WIN_PATTERNS.find {|r| ruby_platform =~ r }.nil?
- end
-
- @@win_platform
- end
-
##
# Is this a java platform?
diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb
index cd8dfdf37d1a15..b56d68cc45bb15 100644
--- a/lib/rubygems/package.rb
+++ b/lib/rubygems/package.rb
@@ -7,6 +7,7 @@
# rubocop:enable Style/AsciiComments
+require_relative "win_platform"
require_relative "security"
require_relative "user_interaction"
@@ -518,10 +519,12 @@ def install_location(filename, destination_dir) # :nodoc:
destination
end
- def normalize_path(pathname)
- if Gem.win_platform?
+ if Gem.win_platform?
+ def normalize_path(pathname) # :nodoc:
pathname.downcase
- else
+ end
+ else
+ def normalize_path(pathname) # :nodoc:
pathname
end
end
diff --git a/lib/rubygems/win_platform.rb b/lib/rubygems/win_platform.rb
new file mode 100644
index 00000000000000..78e968fe493084
--- /dev/null
+++ b/lib/rubygems/win_platform.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require "rbconfig"
+
+module Gem
+ ##
+ # An Array of Regexps that match windows Ruby platforms.
+
+ WIN_PATTERNS = [
+ /bccwin/i,
+ /cygwin/i,
+ /djgpp/i,
+ /mingw/i,
+ /mswin/i,
+ /wince/i,
+ ].freeze
+
+ @@win_platform = nil
+
+ ##
+ # Is this a windows platform?
+
+ def self.win_platform?
+ if @@win_platform.nil?
+ ruby_platform = RbConfig::CONFIG["host_os"]
+ @@win_platform = !WIN_PATTERNS.find {|r| ruby_platform =~ r }.nil?
+ end
+
+ @@win_platform
+ end
+end
From a4c277733b10ff1ad484689c1a256032c39cea87 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20Rodr=C3=ADguez?=
<2887858+deivid-rodriguez@users.noreply.github.com>
Date: Tue, 9 Sep 2025 19:19:31 +0200
Subject: [PATCH 08/18] [rubygems/rubygems] Switch `cache_all` to be `true` by
default
And make it a standard setting.
https://github.com/rubygems/rubygems/commit/17e356fa94
---
lib/bundler/feature_flag.rb | 1 -
lib/bundler/settings.rb | 1 +
lib/bundler/source/git.rb | 2 +-
lib/bundler/source/path.rb | 2 +-
spec/bundler/cache/git_spec.rb | 33 +++++++++----------
spec/bundler/cache/path_spec.rb | 10 ++----
spec/bundler/install/deploy_spec.rb | 1 -
.../install/gemfile/specific_platform_spec.rb | 1 -
spec/bundler/lock/lockfile_spec.rb | 1 -
spec/bundler/plugins/source/example_spec.rb | 4 ---
10 files changed, 22 insertions(+), 34 deletions(-)
diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb
index 09a0ae593db7d9..b2b134889573c1 100644
--- a/lib/bundler/feature_flag.rb
+++ b/lib/bundler/feature_flag.rb
@@ -27,7 +27,6 @@ def self.settings_method(name, key, &default)
(1..10).each {|v| define_method("bundler_#{v}_mode?") { @major_version >= v } }
- settings_flag(:cache_all) { bundler_4_mode? }
settings_flag(:global_gem_cache) { bundler_5_mode? }
settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") }
settings_flag(:update_requires_all_flag) { bundler_5_mode? }
diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb
index bfd68690824525..7923ba51c36630 100644
--- a/lib/bundler/settings.rb
+++ b/lib/bundler/settings.rb
@@ -82,6 +82,7 @@ class Settings
"BUNDLE_TIMEOUT" => 10,
"BUNDLE_VERSION" => "lockfile",
"BUNDLE_LOCKFILE_CHECKSUMS" => true,
+ "BUNDLE_CACHE_ALL" => true,
}.freeze
def initialize(root = nil)
diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb
index ddea7714a16f5d..bb12ff52f514fb 100644
--- a/lib/bundler/source/git.rb
+++ b/lib/bundler/source/git.rb
@@ -268,7 +268,7 @@ def local?
private
def cache_to(custom_path, try_migrate: false)
- return unless Bundler.feature_flag.cache_all?
+ return unless Bundler.settings[:cache_all]
app_cache_path = app_cache_path(custom_path)
diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb
index 76bd1c66c19f08..d258270fe09dd9 100644
--- a/lib/bundler/source/path.rb
+++ b/lib/bundler/source/path.rb
@@ -83,7 +83,7 @@ def install(spec, options = {})
def cache(spec, custom_path = nil)
app_cache_path = app_cache_path(custom_path)
- return unless Bundler.feature_flag.cache_all?
+ return unless Bundler.settings[:cache_all]
return if expand(@original_path).to_s.index(root_path.to_s + "/") == 0
unless @original_path.exist?
diff --git a/spec/bundler/cache/git_spec.rb b/spec/bundler/cache/git_spec.rb
index 66eaf65dd1201d..149fad28384446 100644
--- a/spec/bundler/cache/git_spec.rb
+++ b/spec/bundler/cache/git_spec.rb
@@ -13,6 +13,22 @@
end
RSpec.describe "bundle cache with git" do
+ it "does not copy repository to vendor cache when cache_all set to false" do
+ git = build_git "foo"
+ ref = git.ref_for("main", 11)
+
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "foo", :git => '#{lib_path("foo-1.0")}'
+ G
+
+ bundle "config cache_all false"
+ bundle :cache
+ expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).not_to exist
+
+ expect(the_bundle).to include_gems "foo 1.0"
+ end
+
it "copies repository to vendor cache and uses it" do
git = build_git "foo"
ref = git.ref_for("main", 11)
@@ -22,7 +38,6 @@
gem "foo", :git => '#{lib_path("foo-1.0")}'
G
- bundle "config set cache_all true"
bundle :cache
expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.git")).not_to exist
@@ -43,7 +58,6 @@
bundle "config set --local path vendor/bundle"
bundle "install"
- bundle "config set cache_all true"
bundle :cache
expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
@@ -61,7 +75,6 @@
gem "foo", :git => '#{lib_path("foo-1.0")}'
G
- bundle "config set cache_all true"
bundle :cache
bundle :cache
@@ -79,7 +92,6 @@
gem "foo", :git => '#{lib_path("foo-1.0")}'
G
- bundle "config set cache_all true"
bundle :cache
update_git "foo" do |s|
@@ -109,7 +121,6 @@
gem "foo", :git => '#{lib_path("foo-1.0")}'
G
- bundle "config set cache_all true"
bundle :cache
update_git "foo" do |s|
@@ -140,7 +151,6 @@
bundle %(config set local.foo #{lib_path("foo-1.0")})
bundle "install"
- bundle "config set cache_all true"
bundle :cache
expect(bundled_app("vendor/cache/foo-invalid-#{ref}")).to exist
@@ -179,7 +189,6 @@
source "https://gem.repo1"
gem "foo", :git => '#{lib_path("foo-1.0")}'
G
- bundle "config set cache_all true"
bundle :cache, "all-platforms" => true
pristine_system_gems
@@ -196,7 +205,6 @@
source "https://gem.repo1"
gem "foo", :git => '#{lib_path("foo-1.0")}'
G
- bundle "config set cache_all true"
bundle :cache, "all-platforms" => true
pristine_system_gems
@@ -213,7 +221,6 @@
source "https://gem.repo1"
gem "foo", :git => '#{lib_path("foo-1.0")}'
G
- bundle "config set cache_all true"
bundle :cache, "all-platforms" => true
pristine_system_gems
@@ -242,7 +249,6 @@
gem "foo", :git => '#{lib_path("foo-1.0")}'
G
bundle "config set global_gem_cache false"
- bundle "config set cache_all true"
bundle "config path vendor/bundle"
bundle :install
@@ -274,7 +280,6 @@
gem "foo", :git => '#{lib_path("foo-1.0")}'
G
bundle "config set global_gem_cache false"
- bundle "config set cache_all true"
bundle "config path vendor/bundle"
bundle :install
@@ -304,7 +309,6 @@
gem "foo", :git => '#{lib_path("foo-1.0")}'
G
bundle "config set global_gem_cache false"
- bundle "config set cache_all true"
bundle "config path vendor/bundle"
bundle :install
@@ -342,7 +346,6 @@
G
ref = git.ref_for("main", 11)
- bundle "config set cache_all true"
bundle :cache
expect(bundled_app("vendor/cache/has_submodule-1.0-#{ref}")).to exist
@@ -362,7 +365,6 @@
source "https://gem.repo1"
gem "foo", :git => '#{lib_path("foo-1.0")}'
G
- bundle "config set cache_all true"
bundle :cache
ref = git.ref_for("main", 11)
@@ -377,7 +379,6 @@
source "https://gem.repo1"
gem "foo", :git => '#{lib_path("foo-1.0")}'
G
- bundle "config set cache_all true"
bundle :cache, "all-platforms" => true, :install => false
pristine_system_gems
@@ -436,7 +437,6 @@
source "https://gem.repo1"
gem "foo", :git => '#{lib_path("foo-1.0")}'
G
- bundle "config set cache_all true"
# The algorithm for the cache location for a git checkout is
# in Bundle::Source::Git#cache_path
@@ -498,7 +498,6 @@
end
FileUtils.mkdir_p(bundled_app("vendor/cache"))
- bundle "config set cache_all all"
install_gemfile <<-G
source "https://gem.repo1"
diff --git a/spec/bundler/cache/path_spec.rb b/spec/bundler/cache/path_spec.rb
index 2a64397417ba82..6865e54b321fa9 100644
--- a/spec/bundler/cache/path_spec.rb
+++ b/spec/bundler/cache/path_spec.rb
@@ -9,7 +9,6 @@
gem "foo", :path => '#{bundled_app("lib/foo")}'
G
- bundle "config set cache_all true"
bundle :cache
expect(bundled_app("vendor/cache/foo-1.0")).not_to exist
expect(the_bundle).to include_gems "foo 1.0"
@@ -23,7 +22,6 @@
gem "foo", :path => '#{lib_path("foo-1.0")}'
G
- bundle "config set cache_all true"
bundle :cache
expect(bundled_app("vendor/cache/foo-1.0")).to exist
expect(bundled_app("vendor/cache/foo-1.0/.bundlecache")).to be_file
@@ -42,7 +40,6 @@
gem "#{libname}", :path => '#{libpath}'
G
- bundle "config set cache_all true"
bundle :cache
expect(bundled_app("vendor/cache/#{libname}")).to exist
expect(bundled_app("vendor/cache/#{libname}/.bundlecache")).to be_file
@@ -58,7 +55,6 @@
gem "foo", :path => '#{lib_path("foo-1.0")}'
G
- bundle "config set cache_all true"
bundle :cache
build_lib "foo" do |s|
@@ -81,7 +77,6 @@
gem "foo", :path => '#{lib_path("foo-1.0")}'
G
- bundle "config set cache_all true"
bundle :cache
expect(bundled_app("vendor/cache/foo-1.0")).to exist
@@ -97,20 +92,21 @@
expect(bundled_app("vendor/cache/foo-1.0")).not_to exist
end
- it "does not cache path gems by default" do
+ it "does not cache path gems if cache_all is set to false" do
build_lib "foo"
install_gemfile <<-G
source "https://gem.repo1"
gem "foo", :path => '#{lib_path("foo-1.0")}'
G
+ bundle "config cache_all false"
bundle :cache
expect(err).to be_empty
expect(bundled_app("vendor/cache/foo-1.0")).not_to exist
end
- it "caches path gems by default", bundler: "4" do
+ it "caches path gems by default" do
build_lib "foo"
install_gemfile <<-G
diff --git a/spec/bundler/install/deploy_spec.rb b/spec/bundler/install/deploy_spec.rb
index e401f26a16ece2..7d10ef059407bb 100644
--- a/spec/bundler/install/deploy_spec.rb
+++ b/spec/bundler/install/deploy_spec.rb
@@ -474,7 +474,6 @@
bundle :install
expect(the_bundle).to include_gems "foo 1.0"
- bundle "config set cache_all true"
bundle :cache
expect(bundled_app("vendor/cache/foo")).to be_directory
diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb
index 62540f0488de62..09ed9a4faaa7c8 100644
--- a/spec/bundler/install/gemfile/specific_platform_spec.rb
+++ b/spec/bundler/install/gemfile/specific_platform_spec.rb
@@ -336,7 +336,6 @@
#{Bundler::VERSION}
L
- bundle "config set --local cache_all true"
bundle "cache --all-platforms"
expect(err).to be_empty
diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb
index 6b98e0924e41d8..4b767c7415b656 100644
--- a/spec/bundler/lock/lockfile_spec.rb
+++ b/spec/bundler/lock/lockfile_spec.rb
@@ -786,7 +786,6 @@
c.no_checksum "foo", "1.0"
end
- bundle "config set cache_all true"
bundle :cache
bundle :install, local: true
diff --git a/spec/bundler/plugins/source/example_spec.rb b/spec/bundler/plugins/source/example_spec.rb
index f9624463144ab4..da64886f01d224 100644
--- a/spec/bundler/plugins/source/example_spec.rb
+++ b/spec/bundler/plugins/source/example_spec.rb
@@ -124,7 +124,6 @@ def install(spec, opts)
let(:uri_hash) { Digest(:SHA1).hexdigest(lib_path("a-path-gem-1.0").to_s) }
it "copies repository to vendor cache and uses it" do
bundle "install"
- bundle "config set cache_all true"
bundle :cache
expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist
@@ -138,7 +137,6 @@ def install(spec, opts)
it "copies repository to vendor cache and uses it even when installed with `path` configured" do
bundle "config set --local path vendor/bundle"
bundle :install
- bundle "config set cache_all true"
bundle :cache
expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist
@@ -150,7 +148,6 @@ def install(spec, opts)
it "bundler package copies repository to vendor cache" do
bundle "config set --local path vendor/bundle"
bundle :install
- bundle "config set cache_all true"
bundle :cache
expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist
@@ -446,7 +443,6 @@ def installed?
end
G
- bundle "config set cache_all true"
bundle :cache
expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.git")).not_to exist
From 3faf2a31fb49ed56bd550ff4c10d538b9882c099 Mon Sep 17 00:00:00 2001
From: Jean Boussier
Date: Thu, 18 Sep 2025 10:45:44 +0200
Subject: [PATCH 09/18] [ruby/json] parser: Reject invalid surogate pairs more
consistently.
https://github.com/ruby/json/commit/5855f4f603
---
ext/json/parser/parser.c | 7 ++++++-
test/json/json_parser_test.rb | 6 ++++++
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c
index e34f1999d5f191..297031dcf17bb6 100644
--- a/ext/json/parser/parser.c
+++ b/ext/json/parser/parser.c
@@ -713,11 +713,16 @@ static VALUE json_string_unescape(JSON_ParserState *state, const char *string, c
}
if (pe[0] == '\\' && pe[1] == 'u') {
uint32_t sur = unescape_unicode(state, (unsigned char *) pe + 2);
+
+ if ((sur & 0xFC00) != 0xDC00) {
+ raise_parse_error_at("invalid surrogate pair at %s", state, p);
+ }
+
ch = (((ch & 0x3F) << 10) | ((((ch >> 6) & 0xF) + 1) << 16)
| (sur & 0x3FF));
pe += 5;
} else {
- unescape = (char *) "?";
+ raise_parse_error_at("incomplete surrogate pair at %s", state, p);
break;
}
}
diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb
index a9b0624f6bab58..9d387cb808925a 100644
--- a/test/json/json_parser_test.rb
+++ b/test/json/json_parser_test.rb
@@ -319,6 +319,12 @@ def test_invalid_unicode_escape
assert_raise(JSON::ParserError) { parse('"\u111___"') }
end
+ def test_invalid_surogates
+ assert_raise(JSON::ParserError) { parse('"\\uD800"') }
+ assert_raise(JSON::ParserError) { parse('"\\uD800_________________"') }
+ assert_raise(JSON::ParserError) { parse('"\\uD800\\u0041"') }
+ end
+
def test_parse_big_integers
json1 = JSON(orig = (1 << 31) - 1)
assert_equal orig, parse(json1)
From 77cd19658903a4b6b0e69ffb0a6d0860b9fa3cbd Mon Sep 17 00:00:00 2001
From: Robin Miller
Date: Sat, 13 Sep 2025 16:11:20 -0600
Subject: [PATCH 10/18] [ruby/json] Add branch test coverage when available.
Force track all files to prevent implicit file discovery missing files.
https://github.com/ruby/json/commit/6bded942c4
---
test/json/test_helper.rb | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/test/json/test_helper.rb b/test/json/test_helper.rb
index a788804ddeca4e..24d98170bbb934 100644
--- a/test/json/test_helper.rb
+++ b/test/json/test_helper.rb
@@ -1,14 +1,29 @@
$LOAD_PATH.unshift(File.expand_path('../../../ext', __FILE__), File.expand_path('../../../lib', __FILE__))
require 'coverage'
-Coverage.start
+
+branches_supported = Coverage.respond_to?(:supported?) && Coverage.supported?(:branches)
+
+# Coverage module must be started before SimpleCov to work around the cyclic require order.
+# Track both branches and lines, or else SimpleCov misleadingly reports 0/0 = 100% for non-branching files.
+Coverage.start(lines: true,
+ branches: branches_supported)
begin
require 'simplecov'
rescue LoadError
# Don't fail Ruby's test suite
else
- SimpleCov.start
+ SimpleCov.start do
+ # Enabling both coverage types to let SimpleCov know to output them together in reports
+ enable_coverage :line
+ enable_coverage :branch if branches_supported
+
+ # Can't always trust SimpleCov to find files implicitly
+ track_files 'lib/**/*.rb'
+
+ add_filter 'lib/json/truffle_ruby' unless RUBY_ENGINE == 'truffleruby'
+ end
end
require 'json'
From 13471311aa6a35d1def68ea66c2a06df17ccf51d Mon Sep 17 00:00:00 2001
From: Jean Boussier
Date: Thu, 18 Sep 2025 11:20:21 +0200
Subject: [PATCH 11/18] [ruby/json] Only enable test coverage when running the
test suite standalone
https://github.com/ruby/json/commit/08b9eb0ee6
---
test/json/test_helper.rb | 18 ++++++++----------
1 file changed, 8 insertions(+), 10 deletions(-)
diff --git a/test/json/test_helper.rb b/test/json/test_helper.rb
index 24d98170bbb934..24cde4348cdbf7 100644
--- a/test/json/test_helper.rb
+++ b/test/json/test_helper.rb
@@ -1,19 +1,17 @@
$LOAD_PATH.unshift(File.expand_path('../../../ext', __FILE__), File.expand_path('../../../lib', __FILE__))
-require 'coverage'
+if ENV["JSON_COVERAGE"]
+ # This test helper is loaded inside Ruby's own test suite, so we try to not mess it up.
+ require 'coverage'
-branches_supported = Coverage.respond_to?(:supported?) && Coverage.supported?(:branches)
+ branches_supported = Coverage.respond_to?(:supported?) && Coverage.supported?(:branches)
-# Coverage module must be started before SimpleCov to work around the cyclic require order.
-# Track both branches and lines, or else SimpleCov misleadingly reports 0/0 = 100% for non-branching files.
-Coverage.start(lines: true,
- branches: branches_supported)
+ # Coverage module must be started before SimpleCov to work around the cyclic require order.
+ # Track both branches and lines, or else SimpleCov misleadingly reports 0/0 = 100% for non-branching files.
+ Coverage.start(lines: true,
+ branches: branches_supported)
-begin
require 'simplecov'
-rescue LoadError
- # Don't fail Ruby's test suite
-else
SimpleCov.start do
# Enabling both coverage types to let SimpleCov know to output them together in reports
enable_coverage :line
From 5c59fb5a369fe48e9cfaa7f2c16c29e813b1bc65 Mon Sep 17 00:00:00 2001
From: Jean Boussier
Date: Thu, 18 Sep 2025 11:28:05 +0200
Subject: [PATCH 12/18] [ruby/json] Release 2.14.0
https://github.com/ruby/json/commit/55552cafe2
---
ext/json/lib/json/version.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb
index f9ac3e17a947a5..165b93cff084ed 100644
--- a/ext/json/lib/json/version.rb
+++ b/ext/json/lib/json/version.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
module JSON
- VERSION = '2.13.2'
+ VERSION = '2.14.0'
end
From c164394e957e14368436859ae2c531721207a9a8 Mon Sep 17 00:00:00 2001
From: Scott Myron
Date: Thu, 18 Sep 2025 08:50:55 -0500
Subject: [PATCH 13/18] [ruby/json] fix issue reading off the end of the
ByteBuffer if ptr > 0
Fix: https://github.com/ruby/json/issues/859
https://github.com/ruby/json/commit/67ebabec75
Co-Authored-By: Jean Boussier
---
test/json/json_encoding_test.rb | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/test/json/json_encoding_test.rb b/test/json/json_encoding_test.rb
index caf335d521f994..8dce81da590e76 100644
--- a/test/json/json_encoding_test.rb
+++ b/test/json/json_encoding_test.rb
@@ -31,6 +31,12 @@ def test_generate
assert_equal @generated, JSON.generate(@utf_16_data, ascii_only: true)
end
+ def test_generate_shared_string
+ # Ref: https://github.com/ruby/json/issues/859
+ s = "01234567890"
+ assert_equal '"234567890"', JSON.dump(s[2..-1])
+ end
+
def test_unicode
assert_equal '""', ''.to_json
assert_equal '"\\b"', "\b".to_json
From 807faf5445d75d99b77b85ecae2a84e64ae16b26 Mon Sep 17 00:00:00 2001
From: Jean Boussier
Date: Thu, 18 Sep 2025 16:26:02 +0200
Subject: [PATCH 14/18] [ruby/json] Release 2.14.1
https://github.com/ruby/json/commit/51ce76ea66
---
ext/json/lib/json/version.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb
index 165b93cff084ed..9c928e3940e493 100644
--- a/ext/json/lib/json/version.rb
+++ b/ext/json/lib/json/version.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
module JSON
- VERSION = '2.14.0'
+ VERSION = '2.14.1'
end
From dc406e9b5bdbc1ef2fcce81e96156bd945be8048 Mon Sep 17 00:00:00 2001
From: Jean Boussier
Date: Thu, 18 Sep 2025 18:57:26 +0200
Subject: [PATCH 15/18] [ruby/json] Avoid scientific notation before exponent
15
Fix: https://github.com/ruby/json/issues/861
It's not incorrect to use scientific notation, but it tend
to throw people off a bit, so it's best to keep it for very large
numbers.
https://github.com/ruby/json/commit/1566cd01a6
---
ext/json/generator/generator.c | 5 ++-
ext/json/vendor/fpconv.c | 23 +++++++-------
test/json/json_generator_test.rb | 53 ++++++++++++++++++++------------
3 files changed, 48 insertions(+), 33 deletions(-)
diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c
index 8fcc980d478f80..9b67629f961d30 100644
--- a/ext/json/generator/generator.c
+++ b/ext/json/generator/generator.c
@@ -1345,12 +1345,11 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data
}
/* This implementation writes directly into the buffer. We reserve
- * the 28 characters that fpconv_dtoa states as its maximum.
+ * the 32 characters that fpconv_dtoa states as its maximum.
*/
- fbuffer_inc_capa(buffer, 28);
+ fbuffer_inc_capa(buffer, 32);
char* d = buffer->ptr + buffer->len;
int len = fpconv_dtoa(value, d);
-
/* fpconv_dtoa converts a float to its shortest string representation,
* but it adds a ".0" if this is a plain integer.
*/
diff --git a/ext/json/vendor/fpconv.c b/ext/json/vendor/fpconv.c
index 75efd46f11e624..e91c7889c26b5d 100644
--- a/ext/json/vendor/fpconv.c
+++ b/ext/json/vendor/fpconv.c
@@ -29,6 +29,10 @@
#include
#include
+#ifdef JSON_DEBUG
+#include
+#endif
+
#define npowers 87
#define steppowers 8
#define firstpower -348 /* 10 ^ -348 */
@@ -320,15 +324,7 @@ static int emit_digits(char* digits, int ndigits, char* dest, int K, bool neg)
{
int exp = absv(K + ndigits - 1);
- int max_trailing_zeros = 7;
-
- if(neg) {
- max_trailing_zeros -= 1;
- }
-
- /* write plain integer */
- if(K >= 0 && (exp < (ndigits + max_trailing_zeros))) {
-
+ if(K >= 0 && exp < 15) {
memcpy(dest, digits, ndigits);
memset(dest + ndigits, '0', K);
@@ -432,10 +428,12 @@ static int filter_special(double fp, char* dest)
*
* Input:
* fp -> the double to convert, dest -> destination buffer.
- * The generated string will never be longer than 28 characters.
- * Make sure to pass a pointer to at least 28 bytes of memory.
+ * The generated string will never be longer than 32 characters.
+ * Make sure to pass a pointer to at least 32 bytes of memory.
* The emitted string will not be null terminated.
*
+ *
+ *
* Output:
* The number of written characters.
*
@@ -474,6 +472,9 @@ static int fpconv_dtoa(double d, char dest[28])
int ndigits = grisu2(d, digits, &K);
str_len += emit_digits(digits, ndigits, dest + str_len, K, neg);
+#ifdef JSON_DEBUG
+ assert(str_len <= 32);
+#endif
return str_len;
}
diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb
index 6b42de2ad29abe..4fdfa12b0d6aac 100755
--- a/test/json/json_generator_test.rb
+++ b/test/json/json_generator_test.rb
@@ -825,26 +825,41 @@ def test_json_generate_as_json_convert_to_proc
assert_equal object.object_id.to_json, JSON.generate(object, strict: true, as_json: :object_id)
end
- def test_json_generate_float
- values = [-1.0, 1.0, 0.0, 12.2, 7.5 / 3.2, 12.0, 100.0, 1000.0]
- expecteds = ["-1.0", "1.0", "0.0", "12.2", "2.34375", "12.0", "100.0", "1000.0"]
-
- if RUBY_ENGINE == "jruby"
- values << 1746861937.7842371
- expecteds << "1.7468619377842371E9"
- else
- values << 1746861937.7842371
- expecteds << "1746861937.7842371"
- end
-
- if RUBY_ENGINE == "ruby"
- values << -2.2471348024634545e-08 << -2.2471348024634545e-09 << -2.2471348024634545e-10
- expecteds << "-0.000000022471348024634545" << "-0.0000000022471348024634545" << "-2.2471348024634546e-10"
- end
+ def assert_float_roundtrip(expected, actual)
+ assert_equal(expected, JSON.generate(actual))
+ assert_equal(actual, JSON.parse(JSON.generate(actual)), "JSON: #{JSON.generate(actual)}")
+ end
- values.zip(expecteds).each do |value, expected|
- assert_equal expected, value.to_json
- end
+ def test_json_generate_float
+ assert_float_roundtrip "-1.0", -1.0
+ assert_float_roundtrip "1.0", 1.0
+ assert_float_roundtrip "0.0", 0.0
+ assert_float_roundtrip "12.2", 12.2
+ assert_float_roundtrip "2.34375", 7.5 / 3.2
+ assert_float_roundtrip "12.0", 12.0
+ assert_float_roundtrip "100.0", 100.0
+ assert_float_roundtrip "1000.0", 1000.0
+
+ if RUBY_ENGINE == "jruby"
+ assert_float_roundtrip "1.7468619377842371E9", 1746861937.7842371
+ else
+ assert_float_roundtrip "1746861937.7842371", 1746861937.7842371
+ end
+
+ if RUBY_ENGINE == "ruby"
+ assert_float_roundtrip "100000000000000.0", 100000000000000.0
+ assert_float_roundtrip "1e+15", 1e+15
+ assert_float_roundtrip "-100000000000000.0", -100000000000000.0
+ assert_float_roundtrip "-1e+15", -1e+15
+ assert_float_roundtrip "1111111111111111.1", 1111111111111111.1
+ assert_float_roundtrip "1.1111111111111112e+16", 11111111111111111.1
+ assert_float_roundtrip "-1111111111111111.1", -1111111111111111.1
+ assert_float_roundtrip "-1.1111111111111112e+16", -11111111111111111.1
+
+ assert_float_roundtrip "-0.000000022471348024634545", -2.2471348024634545e-08
+ assert_float_roundtrip "-0.0000000022471348024634545", -2.2471348024634545e-09
+ assert_float_roundtrip "-2.2471348024634546e-10", -2.2471348024634545e-10
+ end
end
def test_numbers_of_various_sizes
From 1042a0bdd893a268b23566fcf1bb3a26cbc0e8c6 Mon Sep 17 00:00:00 2001
From: Jean Boussier
Date: Thu, 18 Sep 2025 20:33:41 +0200
Subject: [PATCH 16/18] `JSON::Coder` callback now recieve a second argument to
mark object keys
e.g.
```ruby
{ 1 => 2 }
```
The callback will be invoked for `1` as while it has a native JSON
equivalent, it's not legal as an object name.
---
ext/json/generator/generator.c | 15 ++++++++++++---
test/json/json_coder_test.rb | 12 ++++++++----
test/json/json_generator_test.rb | 2 +-
3 files changed, 21 insertions(+), 8 deletions(-)
diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c
index 9b67629f961d30..6a38cc60a7964f 100644
--- a/ext/json/generator/generator.c
+++ b/ext/json/generator/generator.c
@@ -29,6 +29,7 @@ typedef struct JSON_Generator_StateStruct {
enum duplicate_key_action on_duplicate_key;
+ bool as_json_single_arg;
bool allow_nan;
bool ascii_only;
bool script_safe;
@@ -1033,6 +1034,13 @@ json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg)
}
}
+static VALUE
+json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key)
+{
+ VALUE proc_args[2] = {object, is_key};
+ return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil);
+}
+
static int
json_object_i(VALUE key, VALUE val, VALUE _arg)
{
@@ -1086,7 +1094,7 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
default:
if (data->state->strict) {
if (RTEST(data->state->as_json) && !as_json_called) {
- key = rb_proc_call_with_block(data->state->as_json, 1, &key, Qnil);
+ key = json_call_as_json(data->state, key, Qtrue);
key_type = rb_type(key);
as_json_called = true;
goto start;
@@ -1328,7 +1336,7 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data
/* for NaN and Infinity values we either raise an error or rely on Float#to_s. */
if (!allow_nan) {
if (data->state->strict && data->state->as_json) {
- VALUE casted_obj = rb_proc_call_with_block(data->state->as_json, 1, &obj, Qnil);
+ VALUE casted_obj = json_call_as_json(data->state, obj, Qfalse);
if (casted_obj != obj) {
increase_depth(data);
generate_json(buffer, data, casted_obj);
@@ -1416,7 +1424,7 @@ static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALU
general:
if (data->state->strict) {
if (RTEST(data->state->as_json) && !as_json_called) {
- obj = rb_proc_call_with_block(data->state->as_json, 1, &obj, Qnil);
+ obj = json_call_as_json(data->state, obj, Qfalse);
as_json_called = true;
goto start;
} else {
@@ -1942,6 +1950,7 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg)
else if (key == sym_allow_duplicate_key) { state->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; }
else if (key == sym_as_json) {
VALUE proc = RTEST(val) ? rb_convert_type(val, T_DATA, "Proc", "to_proc") : Qfalse;
+ state->as_json_single_arg = proc && rb_proc_arity(proc) == 1;
state_write_value(data, &state->as_json, proc);
}
return ST_CONTINUE;
diff --git a/test/json/json_coder_test.rb b/test/json/json_coder_test.rb
index fc4aba296858ae..c7248353769969 100755
--- a/test/json/json_coder_test.rb
+++ b/test/json/json_coder_test.rb
@@ -12,7 +12,8 @@ def test_json_coder_with_proc
end
def test_json_coder_with_proc_with_unsupported_value
- coder = JSON::Coder.new do |object|
+ coder = JSON::Coder.new do |object, is_key|
+ assert_equal false, is_key
Object.new
end
assert_raise(JSON::GeneratorError) { coder.dump([Object.new]) }
@@ -20,7 +21,10 @@ def test_json_coder_with_proc_with_unsupported_value
def test_json_coder_hash_key
obj = Object.new
- coder = JSON::Coder.new(&:to_s)
+ coder = JSON::Coder.new do |obj, is_key|
+ assert_equal true, is_key
+ obj.to_s
+ end
assert_equal %({#{obj.to_s.inspect}:1}), coder.dump({ obj => 1 })
coder = JSON::Coder.new { 42 }
@@ -49,14 +53,14 @@ def test_json_coder_load_options
end
def test_json_coder_dump_NaN_or_Infinity
- coder = JSON::Coder.new(&:inspect)
+ coder = JSON::Coder.new { |o| o.inspect }
assert_equal "NaN", coder.load(coder.dump(Float::NAN))
assert_equal "Infinity", coder.load(coder.dump(Float::INFINITY))
assert_equal "-Infinity", coder.load(coder.dump(-Float::INFINITY))
end
def test_json_coder_dump_NaN_or_Infinity_loop
- coder = JSON::Coder.new(&:itself)
+ coder = JSON::Coder.new { |o| o.itself }
error = assert_raise JSON::GeneratorError do
coder.dump(Float::NAN)
end
diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb
index 4fdfa12b0d6aac..a6950f88878db8 100755
--- a/test/json/json_generator_test.rb
+++ b/test/json/json_generator_test.rb
@@ -822,7 +822,7 @@ def test_fragment
def test_json_generate_as_json_convert_to_proc
object = Object.new
- assert_equal object.object_id.to_json, JSON.generate(object, strict: true, as_json: :object_id)
+ assert_equal object.object_id.to_json, JSON.generate(object, strict: true, as_json: -> (o, is_key) { o.object_id })
end
def assert_float_roundtrip(expected, actual)
From bb25ed61094d4bd1400670a6fbbfc5e6537a7ffb Mon Sep 17 00:00:00 2001
From: git
Date: Fri, 19 Sep 2025 10:31:35 +0000
Subject: [PATCH 17/18] Update default gems list at
1042a0bdd893a268b23566fcf1bb3a [ci skip]
---
NEWS.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/NEWS.md b/NEWS.md
index 7534539a21d2c9..5a9277b3992411 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -184,7 +184,7 @@ The following default gems are updated.
* io-console 0.8.1
* io-nonblock 0.3.2
* io-wait 0.3.2
-* json 2.13.2
+* json 2.14.1
* optparse 0.7.0.dev.2
* prism 1.5.1
* psych 5.2.6
From 477b1e79b71af422136df29b259d9bb3ce8449e2 Mon Sep 17 00:00:00 2001
From: Peter Zhu
Date: Thu, 18 Sep 2025 09:26:34 -0400
Subject: [PATCH 18/18] Directly use rb_imemo_new in imemo_fields_new_complex
We should not assume that a complex imemo_field takes only one additional
VALUE space. This is fragile as it will break if we add additional fields
to complex imemo_field.
---
imemo.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/imemo.c b/imemo.c
index 10ac960095675f..5a5ec4a4d382e8 100644
--- a/imemo.c
+++ b/imemo.c
@@ -123,7 +123,7 @@ rb_imemo_fields_new(VALUE owner, size_t capa)
static VALUE
imemo_fields_new_complex(VALUE owner, size_t capa)
{
- VALUE fields = imemo_fields_new(owner, 1);
+ VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields));
IMEMO_OBJ_FIELDS(fields)->as.complex.table = st_init_numtable_with_size(capa);
FL_SET_RAW(fields, OBJ_FIELD_HEAP);
return fields;