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;