From 6a93a28c6987d443d4cd54b2eea1ea70b9823ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 12 Aug 2025 19:19:31 +0200 Subject: [PATCH 01/36] [rubygems/rubygems] Ensure removed commands get their docs removed, too https://github.com/rubygems/rubygems/commit/2a2e45bbe9 --- spec/bundler/other/cli_man_pages_spec.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/spec/bundler/other/cli_man_pages_spec.rb b/spec/bundler/other/cli_man_pages_spec.rb index 6efd2904d6497a..f6560106593cf5 100644 --- a/spec/bundler/other/cli_man_pages_spec.rb +++ b/spec/bundler/other/cli_man_pages_spec.rb @@ -13,9 +13,11 @@ def check_commands!(command_class) command_class.commands.each do |command_name, command| - next if command.is_a?(Bundler::Thor::HiddenCommand) - - if command_class == Bundler::CLI + if command.is_a?(Bundler::Thor::HiddenCommand) + man_page = man_page(command_name) + expect(man_page).not_to exist + expect(main_man_page.read).not_to include("bundle #{command_name}") + elsif command_class == Bundler::CLI man_page = man_page(command_name) expect(man_page).to exist @@ -87,4 +89,8 @@ def man_page_content(command_name) def man_page(command_name) source_root.join("lib/bundler/man/bundle-#{command_name}.1.ronn") end + + def main_man_page + source_root.join("lib/bundler/man/bundle.1.ronn") + end end From 9763e2de016606e4ffdaf83c2ad4da436d4a585d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 12 Aug 2025 19:20:55 +0200 Subject: [PATCH 02/36] [rubygems/rubygems] The `bundle clean` command should not be hidden https://github.com/rubygems/rubygems/commit/eab6366e2e --- lib/bundler/cli.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 47a39069cc1b56..c17dda30f3f8fd 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -559,7 +559,7 @@ def self.source_root File.expand_path("templates", __dir__) end - desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory", hide: true + desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory" method_option "dry-run", type: :boolean, default: false, banner: "Only print out changes, do not clean gems" method_option "force", type: :boolean, default: false, banner: "Forces cleaning up unused gems even if Bundler is configured to use globally installed gems. As a consequence, removes all system gems except for the ones in the current application." def clean From 2e65f4ac3b1d69e7bd9e3e65a696e7f234ec9527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 13 Aug 2025 11:54:46 +0200 Subject: [PATCH 03/36] [rubygems/rubygems] No need to print caller location when raising directly https://github.com/rubygems/rubygems/commit/6c2b06cc16 --- lib/bundler/shared_helpers.rb | 1 - spec/bundler/bundler/shared_helpers_spec.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 2bdaabdaa74525..b020c671430ccb 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -130,7 +130,6 @@ def major_deprecation(major_version, message, removed_message: nil, print_caller caller_location = caller_locations(2, 2).first suffix = " (called at #{caller_location.path}:#{caller_location.lineno})" message += suffix - removed_message += suffix if removed_message end require_relative "../bundler" diff --git a/spec/bundler/bundler/shared_helpers_spec.rb b/spec/bundler/bundler/shared_helpers_spec.rb index 356858070105d1..5045b86e4ec53e 100644 --- a/spec/bundler/bundler/shared_helpers_spec.rb +++ b/spec/bundler/bundler/shared_helpers_spec.rb @@ -542,7 +542,7 @@ expect { subject.major_deprecation(36, "Message", removed_message: "Removal") }. to raise_error(Bundler::DeprecatedError, "[REMOVED] Removal") expect { subject.major_deprecation(35, "Message", removed_message: "Removal", print_caller_location: true) }. - to raise_error(Bundler::DeprecatedError, /^\[REMOVED\] Removal \(called at .*:\d+\)$/) + to raise_error(Bundler::DeprecatedError, "[REMOVED] Removal") end end end From cc1770b968257ca6d6ec5a49b6ef54d4a4853974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 13 Aug 2025 11:53:45 +0200 Subject: [PATCH 04/36] [rubygems/rubygems] Introduce `SharedHelpers.feature_removed!` To directly raise regardless of version. https://github.com/rubygems/rubygems/commit/38fb97cffa --- lib/bundler/errors.rb | 1 + lib/bundler/shared_helpers.rb | 10 ++++++++-- spec/bundler/bundler/shared_helpers_spec.rb | 6 +++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb index 4d1bface5176ed..28da892d31d5ac 100644 --- a/lib/bundler/errors.rb +++ b/lib/bundler/errors.rb @@ -25,6 +25,7 @@ class SolveFailure < BundlerError; status_code(6); end class GemNotFound < BundlerError; status_code(7); end class InstallHookError < BundlerError; status_code(8); end + class RemovedError < BundlerError; status_code(9); end class GemfileNotFound < BundlerError; status_code(10); end class GitError < BundlerError; status_code(11); end class DeprecatedError < BundlerError; status_code(12); end diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index b020c671430ccb..41b7128d36e18e 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -137,14 +137,20 @@ def major_deprecation(major_version, message, removed_message: nil, print_caller feature_flag = Bundler.feature_flag if feature_flag.removed_major?(major_version) - require_relative "errors" - raise DeprecatedError, "[REMOVED] #{removed_message || message}" + feature_removed!(removed_message || message) end return unless feature_flag.deprecated_major?(major_version) && prints_major_deprecations? Bundler.ui.warn("[DEPRECATED] #{message}") end + def feature_removed!(message) + require_relative "../bundler" + + require_relative "errors" + raise RemovedError, "[REMOVED] #{message}" + end + def print_major_deprecations! multiple_gemfiles = search_up(".") do |dir| gemfiles = gemfile_names.select {|gf| File.file? File.expand_path(gf, dir) } diff --git a/spec/bundler/bundler/shared_helpers_spec.rb b/spec/bundler/bundler/shared_helpers_spec.rb index 5045b86e4ec53e..5b3a9c17a7e8d5 100644 --- a/spec/bundler/bundler/shared_helpers_spec.rb +++ b/spec/bundler/bundler/shared_helpers_spec.rb @@ -538,11 +538,11 @@ it "raises the appropriate errors when _past_ the deprecated major version" do expect { subject.major_deprecation(36, "Message") }. - to raise_error(Bundler::DeprecatedError, "[REMOVED] Message") + to raise_error(Bundler::RemovedError, "[REMOVED] Message") expect { subject.major_deprecation(36, "Message", removed_message: "Removal") }. - to raise_error(Bundler::DeprecatedError, "[REMOVED] Removal") + to raise_error(Bundler::RemovedError, "[REMOVED] Removal") expect { subject.major_deprecation(35, "Message", removed_message: "Removal", print_caller_location: true) }. - to raise_error(Bundler::DeprecatedError, "[REMOVED] Removal") + to raise_error(Bundler::RemovedError, "[REMOVED] Removal") end end end From 834b941253b0da4e162c4fc318559f5cb545aadd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 5 Aug 2025 19:20:05 +0200 Subject: [PATCH 05/36] Completely remove `bundle inject` command --- lib/bundler/cli.rb | 8 +- lib/bundler/cli/inject.rb | 60 ---------- lib/bundler/man/bundle-inject.1 | 31 ----- lib/bundler/man/bundle-inject.1.ronn | 32 ------ lib/bundler/man/bundle.1 | 6 - lib/bundler/man/bundle.1.ronn | 6 - lib/bundler/man/index.txt | 1 - spec/bundler/commands/inject_spec.rb | 113 ------------------- spec/bundler/other/major_deprecation_spec.rb | 10 ++ 9 files changed, 12 insertions(+), 255 deletions(-) delete mode 100644 lib/bundler/cli/inject.rb delete mode 100644 lib/bundler/man/bundle-inject.1 delete mode 100644 lib/bundler/man/bundle-inject.1.ronn delete mode 100644 spec/bundler/commands/inject_spec.rb diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index c17dda30f3f8fd..38264c6ebeef80 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -575,12 +575,8 @@ def platform end desc "inject GEM VERSION", "Add the named gem, with version requirements, to the resolved Gemfile", hide: true - method_option "source", type: :string, banner: "Install gem from the given source" - method_option "group", type: :string, banner: "Install gem into a bundler group" - def inject(name, version) - SharedHelpers.major_deprecation 2, "The `inject` command has been replaced by the `add` command" - require_relative "cli/inject" - Inject.new(options.dup, name, version).run + def inject(*) + SharedHelpers.feature_removed! "The `inject` command has been replaced by the `add` command" end desc "lock", "Creates a lockfile without installing" diff --git a/lib/bundler/cli/inject.rb b/lib/bundler/cli/inject.rb deleted file mode 100644 index a09d5c6bda1347..00000000000000 --- a/lib/bundler/cli/inject.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true - -module Bundler - class CLI::Inject - attr_reader :options, :name, :version, :group, :source, :gems - def initialize(options, name, version) - @options = options - @name = name - @version = version || last_version_number - @group = options[:group].split(",") unless options[:group].nil? - @source = options[:source] - @gems = [] - end - - def run - # The required arguments allow Thor to give useful feedback when the arguments - # are incorrect. This adds those first two arguments onto the list as a whole. - gems.unshift(source).unshift(group).unshift(version).unshift(name) - - # Build an array of Dependency objects out of the arguments - deps = [] - # when `inject` support addition of more than one gem, then this loop will - # help. Currently this loop is running once. - gems.each_slice(4) do |gem_name, gem_version, gem_group, gem_source| - ops = Gem::Requirement::OPS.map {|key, _val| key } - has_op = ops.any? {|op| gem_version.start_with? op } - gem_version = "~> #{gem_version}" unless has_op - deps << Bundler::Dependency.new(gem_name, gem_version, "group" => gem_group, "source" => gem_source) - end - - added = Injector.inject(deps, options) - - if added.any? - Bundler.ui.confirm "Added to Gemfile:" - Bundler.ui.confirm(added.map do |d| - name = "'#{d.name}'" - requirement = ", '#{d.requirement}'" - group = ", group: #{d.groups.inspect}" if d.groups != Array(:default) - source = ", source: '#{d.source}'" unless d.source.nil? - %(gem #{name}#{requirement}#{group}#{source}) - end.join("\n")) - else - Bundler.ui.confirm "All gems were already present in the Gemfile" - end - end - - private - - def last_version_number - definition = Bundler.definition(true) - definition.remotely! - specs = definition.index[name].sort_by(&:version) - unless options[:pre] - specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? } - end - spec = specs.last - spec.version.to_s - end - end -end diff --git a/lib/bundler/man/bundle-inject.1 b/lib/bundler/man/bundle-inject.1 deleted file mode 100644 index 7e30e26a4e4637..00000000000000 --- a/lib/bundler/man/bundle-inject.1 +++ /dev/null @@ -1,31 +0,0 @@ -.\" generated with Ronn-NG/v0.10.1 -.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-INJECT" "1" "August 2025" "" -.SH "NAME" -\fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile -.SH "SYNOPSIS" -\fBbundle inject\fR [GEM] [VERSION] [\-\-source=SOURCE] [\-\-group=GROUP] -.SH "DESCRIPTION" -Adds the named gem(s) with their version requirements to the resolved [\fBGemfile(5)\fR][Gemfile(5)]\. -.P -This command will add the gem to both your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock if it isn't listed yet\. -.P -Example: -.IP "" 4 -.nf -bundle install -bundle inject 'rack' '> 0' -.fi -.IP "" 0 -.P -This will inject the 'rack' gem with a version greater than 0 in your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock\. -.P -The \fBbundle inject\fR command was deprecated in Bundler 2\.1 and will be removed in Bundler 4\.0\. -.SH "OPTIONS" -.TP -\fB\-\-source=SOURCE\fR -Install gem from the given source\. -.TP -\fB\-\-group=GROUP\fR -Install gem into a bundler group\. - diff --git a/lib/bundler/man/bundle-inject.1.ronn b/lib/bundler/man/bundle-inject.1.ronn deleted file mode 100644 index 7f6f0fb5b6541b..00000000000000 --- a/lib/bundler/man/bundle-inject.1.ronn +++ /dev/null @@ -1,32 +0,0 @@ -bundle-inject(1) -- Add named gem(s) with version requirements to Gemfile -========================================================================= - -## SYNOPSIS - -`bundle inject` [GEM] [VERSION] [--source=SOURCE] [--group=GROUP] - -## DESCRIPTION - -Adds the named gem(s) with their version requirements to the resolved -[`Gemfile(5)`][Gemfile(5)]. - -This command will add the gem to both your [`Gemfile(5)`][Gemfile(5)] and Gemfile.lock if it -isn't listed yet. - -Example: - - bundle install - bundle inject 'rack' '> 0' - -This will inject the 'rack' gem with a version greater than 0 in your -[`Gemfile(5)`][Gemfile(5)] and Gemfile.lock. - -The `bundle inject` command was deprecated in Bundler 2.1 and will be removed in Bundler 4.0. - -## OPTIONS - -* `--source=SOURCE`: - Install gem from the given source. - -* `--group=GROUP`: - Install gem into a bundler group. diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1 index f87a98150da609..9bee76e58b3423 100644 --- a/lib/bundler/man/bundle.1 +++ b/lib/bundler/man/bundle.1 @@ -94,9 +94,3 @@ Manage Bundler plugins Prints Bundler version information .SH "PLUGINS" When running a command that isn't listed in PRIMARY COMMANDS or UTILITIES, Bundler will try to find an executable on your path named \fBbundler\-\fR and execute it, passing down any extra arguments to it\. -.SH "OBSOLETE" -These commands are obsolete and should no longer be used: -.IP "\(bu" 4 -\fBbundle inject(1)\fR -.IP "" 0 - diff --git a/lib/bundler/man/bundle.1.ronn b/lib/bundler/man/bundle.1.ronn index 8245effabd0bb7..f368b322767b69 100644 --- a/lib/bundler/man/bundle.1.ronn +++ b/lib/bundler/man/bundle.1.ronn @@ -108,9 +108,3 @@ We divide `bundle` subcommands into primary commands and utilities: When running a command that isn't listed in PRIMARY COMMANDS or UTILITIES, Bundler will try to find an executable on your path named `bundler-` and execute it, passing down any extra arguments to it. - -## OBSOLETE - -These commands are obsolete and should no longer be used: - -* `bundle inject(1)` diff --git a/lib/bundler/man/index.txt b/lib/bundler/man/index.txt index 3ea3495f1b9f6c..6cc5eb24cd3e1d 100644 --- a/lib/bundler/man/index.txt +++ b/lib/bundler/man/index.txt @@ -15,7 +15,6 @@ bundle-gem(1) bundle-gem.1 bundle-help(1) bundle-help.1 bundle-info(1) bundle-info.1 bundle-init(1) bundle-init.1 -bundle-inject(1) bundle-inject.1 bundle-install(1) bundle-install.1 bundle-issue(1) bundle-issue.1 bundle-licenses(1) bundle-licenses.1 diff --git a/spec/bundler/commands/inject_spec.rb b/spec/bundler/commands/inject_spec.rb deleted file mode 100644 index c39c2ae35bf600..00000000000000 --- a/spec/bundler/commands/inject_spec.rb +++ /dev/null @@ -1,113 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "bundle inject" do - before :each do - gemfile <<-G - source "https://gem.repo1" - gem "myrack" - G - end - - context "without a lockfile" do - it "locks with the injected gems" do - expect(bundled_app_lock).not_to exist - bundle "inject 'myrack-obama' '> 0'" - expect(bundled_app_lock.read).to match(/myrack-obama/) - end - end - - context "with a lockfile" do - before do - bundle "install" - end - - it "adds the injected gems to the Gemfile" do - expect(bundled_app_gemfile.read).not_to match(/myrack-obama/) - bundle "inject 'myrack-obama' '> 0'" - expect(bundled_app_gemfile.read).to match(/myrack-obama/) - end - - it "locks with the injected gems" do - expect(bundled_app_lock.read).not_to match(/myrack-obama/) - bundle "inject 'myrack-obama' '> 0'" - expect(bundled_app_lock.read).to match(/myrack-obama/) - end - end - - context "with injected gems already in the Gemfile" do - it "doesn't add existing gems" do - bundle "inject 'myrack' '> 0'", raise_on_error: false - expect(err).to match(/cannot specify the same gem twice/i) - end - end - - context "incorrect arguments" do - it "fails when more than 2 arguments are passed" do - bundle "inject gem_name 1 v", raise_on_error: false - expect(err).to eq(<<-E.strip) -ERROR: "bundle inject" was called with arguments ["gem_name", "1", "v"] -Usage: "bundle inject GEM VERSION" - E - end - end - - context "with source option" do - it "add gem with source option in gemfile" do - bundle "inject 'foo' '>0' --source https://gem.repo1" - gemfile = bundled_app_gemfile.read - str = "gem \"foo\", \"> 0\", source: \"https://gem.repo1\"" - expect(gemfile).to include str - end - end - - context "with group option" do - it "add gem with group option in gemfile" do - bundle "inject 'myrack-obama' '>0' --group=development" - gemfile = bundled_app_gemfile.read - str = "gem \"myrack-obama\", \"> 0\", group: :development" - expect(gemfile).to include str - end - - it "add gem with multiple groups in gemfile" do - bundle "inject 'myrack-obama' '>0' --group=development,test" - gemfile = bundled_app_gemfile.read - str = "gem \"myrack-obama\", \"> 0\", groups: [:development, :test]" - expect(gemfile).to include str - end - end - - context "when frozen" do - before do - bundle "install" - bundle "config set --local frozen true" - end - - it "injects anyway" do - bundle "inject 'myrack-obama' '> 0'" - expect(bundled_app_gemfile.read).to match(/myrack-obama/) - end - - it "locks with the injected gems" do - expect(bundled_app_lock.read).not_to match(/myrack-obama/) - bundle "inject 'myrack-obama' '> 0'" - expect(bundled_app_lock.read).to match(/myrack-obama/) - end - - it "restores frozen afterwards" do - bundle "inject 'myrack-obama' '> 0'" - config = Psych.load(bundled_app(".bundle/config").read) - expect(config["BUNDLE_DEPLOYMENT"] || config["BUNDLE_FROZEN"]).to eq("true") - end - - it "doesn't allow Gemfile changes" do - gemfile <<-G - source "https://gem.repo1" - gem "myrack-obama" - G - bundle "inject 'myrack' '> 0'", raise_on_error: false - expect(err).to match(/the lockfile can't be updated because frozen mode is set/) - - expect(bundled_app_lock.read).not_to match(/myrack-obama/) - end - end -end diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index d57abe45f35259..d17770d00c708c 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -653,6 +653,16 @@ pending "fails with a helpful message", bundler: "4" end + context "bundle inject" do + before do + bundle "inject", raise_on_error: false + end + + it "fails with a helpful message" do + expect(err).to include "The `inject` command has been replaced by the `add` command" + end + end + context "bundle plugin install --local_git" do before do build_git "foo" do |s| From 3b6c82ca4f6ee915e670c5be2342b10ed99c5844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 5 Aug 2025 18:32:44 +0200 Subject: [PATCH 06/36] Completely remove `bundle viz` command --- lib/bundler.rb | 1 - lib/bundler/cli.rb | 20 +-- lib/bundler/cli/viz.rb | 31 ---- lib/bundler/graph.rb | 152 ------------------- lib/bundler/man/bundle-viz.1 | 30 ---- lib/bundler/man/bundle-viz.1.ronn | 36 ----- lib/bundler/man/bundle.1 | 3 - lib/bundler/man/bundle.1.ronn | 3 - lib/bundler/man/index.txt | 1 - spec/bundler/commands/viz_spec.rb | 144 ------------------ spec/bundler/other/major_deprecation_spec.rb | 9 +- tool/bundler/test_gems.rb | 1 - tool/bundler/test_gems.rb.lock | 6 - 13 files changed, 6 insertions(+), 431 deletions(-) delete mode 100644 lib/bundler/cli/viz.rb delete mode 100644 lib/bundler/graph.rb delete mode 100644 lib/bundler/man/bundle-viz.1 delete mode 100644 lib/bundler/man/bundle-viz.1.ronn delete mode 100644 spec/bundler/commands/viz_spec.rb diff --git a/lib/bundler.rb b/lib/bundler.rb index 761679ec8dfeea..f5b2abcd101891 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -54,7 +54,6 @@ module Bundler autoload :FREEBSD, File.expand_path("bundler/constants", __dir__) autoload :GemHelper, File.expand_path("bundler/gem_helper", __dir__) autoload :GemVersionPromoter, File.expand_path("bundler/gem_version_promoter", __dir__) - autoload :Graph, File.expand_path("bundler/graph", __dir__) autoload :Index, File.expand_path("bundler/index", __dir__) autoload :Injector, File.expand_path("bundler/injector", __dir__) autoload :Installer, File.expand_path("bundler/installer", __dir__) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 38264c6ebeef80..8acf25eba6b1c9 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -513,23 +513,9 @@ def licenses end end - unless Bundler.feature_flag.bundler_4_mode? - desc "viz [OPTIONS]", "Generates a visual dependency graph", hide: true - long_desc <<-D - Viz generates a PNG file of the current Gemfile as a dependency graph. - Viz requires the ruby-graphviz gem (and its dependencies). - The associated gems must also be installed via 'bundle install'. - D - method_option :file, type: :string, default: "gem_graph", aliases: "-f", banner: "The name to use for the generated file. see format option" - method_option :format, type: :string, default: "png", aliases: "-F", banner: "This is output format option. Supported format is png, jpg, svg, dot ..." - method_option :requirements, type: :boolean, default: false, aliases: "-R", banner: "Set to show the version of each required dependency." - method_option :version, type: :boolean, default: false, aliases: "-v", banner: "Set to show each gem version." - method_option :without, type: :array, default: [], aliases: "-W", banner: "Exclude gems that are part of the specified named group." - def viz - SharedHelpers.major_deprecation 2, "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph" - require_relative "cli/viz" - Viz.new(options.dup).run - end + desc "viz [OPTIONS]", "Generates a visual dependency graph", hide: true + def viz + SharedHelpers.feature_removed! "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph" end desc "gem NAME [OPTIONS]", "Creates a skeleton for creating a rubygem" diff --git a/lib/bundler/cli/viz.rb b/lib/bundler/cli/viz.rb deleted file mode 100644 index 5c09e009959e49..00000000000000 --- a/lib/bundler/cli/viz.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -module Bundler - class CLI::Viz - attr_reader :options, :gem_name - def initialize(options) - @options = options - end - - def run - # make sure we get the right `graphviz`. There is also a `graphviz` - # gem we're not built to support - gem "ruby-graphviz" - require "graphviz" - - options[:without] = options[:without].join(":").tr(" ", ":").split(":") - output_file = File.expand_path(options[:file]) - - graph = Graph.new(Bundler.load, output_file, options[:version], options[:requirements], options[:format], options[:without]) - graph.viz - rescue LoadError => e - Bundler.ui.error e.inspect - Bundler.ui.warn "Make sure you have the graphviz ruby gem. You can install it with:" - Bundler.ui.warn "`gem install ruby-graphviz`" - rescue StandardError => e - raise unless e.message.to_s.include?("GraphViz not installed or dot not in PATH") - Bundler.ui.error e.message - Bundler.ui.warn "Please install GraphViz. On a Mac with Homebrew, you can run `brew install graphviz`." - end - end -end diff --git a/lib/bundler/graph.rb b/lib/bundler/graph.rb deleted file mode 100644 index b22b17a453bcce..00000000000000 --- a/lib/bundler/graph.rb +++ /dev/null @@ -1,152 +0,0 @@ -# frozen_string_literal: true - -require "set" -module Bundler - class Graph - GRAPH_NAME = :Gemfile - - def initialize(env, output_file, show_version = false, show_requirements = false, output_format = "png", without = []) - @env = env - @output_file = output_file - @show_version = show_version - @show_requirements = show_requirements - @output_format = output_format - @without_groups = without.map(&:to_sym) - - @groups = [] - @relations = Hash.new {|h, k| h[k] = Set.new } - @node_options = {} - @edge_options = {} - - _populate_relations - end - - attr_reader :groups, :relations, :node_options, :edge_options, :output_file, :output_format - - def viz - GraphVizClient.new(self).run - end - - private - - def _populate_relations - parent_dependencies = _groups.values.to_set.flatten - loop do - break if parent_dependencies.empty? - - tmp = Set.new - parent_dependencies.each do |dependency| - child_dependencies = spec_for_dependency(dependency).runtime_dependencies.to_set - @relations[dependency.name] += child_dependencies.map(&:name).to_set - tmp += child_dependencies - - @node_options[dependency.name] = _make_label(dependency, :node) - child_dependencies.each do |c_dependency| - @edge_options["#{dependency.name}_#{c_dependency.name}"] = _make_label(c_dependency, :edge) - end - end - parent_dependencies = tmp - end - end - - def _groups - relations = Hash.new {|h, k| h[k] = Set.new } - @env.current_dependencies.each do |dependency| - dependency.groups.each do |group| - next if @without_groups.include?(group) - - relations[group.to_s].add(dependency) - @relations[group.to_s].add(dependency.name) - - @node_options[group.to_s] ||= _make_label(group, :node) - @edge_options["#{group}_#{dependency.name}"] = _make_label(dependency, :edge) - end - end - @groups = relations.keys - relations - end - - def _make_label(symbol_or_string_or_dependency, element_type) - case element_type.to_sym - when :node - if symbol_or_string_or_dependency.is_a?(Gem::Dependency) - label = symbol_or_string_or_dependency.name.dup - label << "\n#{spec_for_dependency(symbol_or_string_or_dependency).version}" if @show_version - else - label = symbol_or_string_or_dependency.to_s - end - when :edge - label = nil - if symbol_or_string_or_dependency.respond_to?(:requirements_list) && @show_requirements - tmp = symbol_or_string_or_dependency.requirements_list.join(", ") - label = tmp if tmp != ">= 0" - end - else - raise ArgumentError, "2nd argument is invalid" - end - label.nil? ? {} : { label: label } - end - - def spec_for_dependency(dependency) - @env.requested_specs.find {|s| s.name == dependency.name } - end - - class GraphVizClient - def initialize(graph_instance) - @graph_name = graph_instance.class::GRAPH_NAME - @groups = graph_instance.groups - @relations = graph_instance.relations - @node_options = graph_instance.node_options - @edge_options = graph_instance.edge_options - @output_file = graph_instance.output_file - @output_format = graph_instance.output_format - end - - def g - @g ||= ::GraphViz.digraph(@graph_name, concentrate: true, normalize: true, nodesep: 0.55) do |g| - g.edge[:weight] = 2 - g.edge[:fontname] = g.node[:fontname] = "Arial, Helvetica, SansSerif" - g.edge[:fontsize] = 12 - end - end - - def run - @groups.each do |group| - g.add_nodes( - group, { - style: "filled", - fillcolor: "#B9B9D5", - shape: "box3d", - fontsize: 16, - }.merge(@node_options[group]) - ) - end - - @relations.each do |parent, children| - children.each do |child| - if @groups.include?(parent) - g.add_nodes(child, { style: "filled", fillcolor: "#B9B9D5" }.merge(@node_options[child])) - g.add_edges(parent, child, { constraint: false }.merge(@edge_options["#{parent}_#{child}"])) - else - g.add_nodes(child, @node_options[child]) - g.add_edges(parent, child, @edge_options["#{parent}_#{child}"]) - end - end - end - - if @output_format.to_s == "debug" - $stdout.puts g.output none: String - Bundler.ui.info "debugging bundle viz..." - else - begin - g.output @output_format.to_sym => "#{@output_file}.#{@output_format}" - Bundler.ui.info "#{@output_file}.#{@output_format}" - rescue ArgumentError => e - warn "Unsupported output format. See Ruby-Graphviz/lib/graphviz/constants.rb" - raise e - end - end - end - end - end -end diff --git a/lib/bundler/man/bundle-viz.1 b/lib/bundler/man/bundle-viz.1 deleted file mode 100644 index f2570103dcc546..00000000000000 --- a/lib/bundler/man/bundle-viz.1 +++ /dev/null @@ -1,30 +0,0 @@ -.\" generated with Ronn-NG/v0.10.1 -.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-VIZ" "1" "August 2025" "" -.SH "NAME" -\fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile -.SH "SYNOPSIS" -\fBbundle viz\fR [\-\-file=FILE] [\-\-format=FORMAT] [\-\-requirements] [\-\-version] [\-\-without=GROUP GROUP] -.SH "DESCRIPTION" -\fBviz\fR generates a PNG file of the current \fBGemfile(5)\fR as a dependency graph\. \fBviz\fR requires the ruby\-graphviz gem (and its dependencies)\. -.P -The associated gems must also be installed via \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR\. -.P -\fBviz\fR command was deprecated in Bundler 2\.2\. Use bundler\-graph plugin \fIhttps://github\.com/rubygems/bundler\-graph\fR instead\. -.SH "OPTIONS" -.TP -\fB\-\-file=FILE\fR, \fB\-f=FILE\fR -The name to use for the generated file\. See \fB\-\-format\fR option -.TP -\fB\-\-format=FORMAT\fR, \fB\-F=FORMAT\fR -This is output format option\. Supported format is png, jpg, svg, dot \|\.\|\.\|\. -.TP -\fB\-\-requirements\fR, \fB\-R\fR -Set to show the version of each required dependency\. -.TP -\fB\-\-version\fR, \fB\-v\fR -Set to show each gem version\. -.TP -\fB\-\-without=\fR, \fB\-W=\fR -Exclude gems that are part of the specified named group\. - diff --git a/lib/bundler/man/bundle-viz.1.ronn b/lib/bundler/man/bundle-viz.1.ronn deleted file mode 100644 index 730b0eed22e10d..00000000000000 --- a/lib/bundler/man/bundle-viz.1.ronn +++ /dev/null @@ -1,36 +0,0 @@ -bundle-viz(1) -- Generates a visual dependency graph for your Gemfile -===================================================================== - -## SYNOPSIS - -`bundle viz` [--file=FILE] - [--format=FORMAT] - [--requirements] - [--version] - [--without=GROUP GROUP] - -## DESCRIPTION - -`viz` generates a PNG file of the current `Gemfile(5)` as a dependency graph. -`viz` requires the ruby-graphviz gem (and its dependencies). - -The associated gems must also be installed via [`bundle install(1)`](bundle-install.1.html). - -`viz` command was deprecated in Bundler 2.2. Use [bundler-graph plugin](https://github.com/rubygems/bundler-graph) instead. - -## OPTIONS - -* `--file=FILE`, `-f=FILE`: - The name to use for the generated file. See `--format` option - -* `--format=FORMAT`, `-F=FORMAT`: - This is output format option. Supported format is png, jpg, svg, dot ... - -* `--requirements`, `-R`: - Set to show the version of each required dependency. - -* `--version`, `-v`: - Set to show each gem version. - -* `--without=`, `-W=`: - Exclude gems that are part of the specified named group. diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1 index 9bee76e58b3423..ece080b3e9d99e 100644 --- a/lib/bundler/man/bundle.1 +++ b/lib/bundler/man/bundle.1 @@ -66,9 +66,6 @@ Open an installed gem in the editor \fBbundle lock(1)\fR \fIbundle\-lock\.1\.html\fR Generate a lockfile for your dependencies .TP -\fBbundle viz(1)\fR \fIbundle\-viz\.1\.html\fR (deprecated) -Generate a visual representation of your dependencies -.TP \fBbundle init(1)\fR \fIbundle\-init\.1\.html\fR Generate a simple \fBGemfile\fR, placed in the current directory .TP diff --git a/lib/bundler/man/bundle.1.ronn b/lib/bundler/man/bundle.1.ronn index f368b322767b69..1c2b3df7afa88b 100644 --- a/lib/bundler/man/bundle.1.ronn +++ b/lib/bundler/man/bundle.1.ronn @@ -76,9 +76,6 @@ We divide `bundle` subcommands into primary commands and utilities: * [`bundle lock(1)`](bundle-lock.1.html): Generate a lockfile for your dependencies -* [`bundle viz(1)`](bundle-viz.1.html) (deprecated): - Generate a visual representation of your dependencies - * [`bundle init(1)`](bundle-init.1.html): Generate a simple `Gemfile`, placed in the current directory diff --git a/lib/bundler/man/index.txt b/lib/bundler/man/index.txt index 6cc5eb24cd3e1d..f610ba852a6d97 100644 --- a/lib/bundler/man/index.txt +++ b/lib/bundler/man/index.txt @@ -29,4 +29,3 @@ bundle-remove(1) bundle-remove.1 bundle-show(1) bundle-show.1 bundle-update(1) bundle-update.1 bundle-version(1) bundle-version.1 -bundle-viz(1) bundle-viz.1 diff --git a/spec/bundler/commands/viz_spec.rb b/spec/bundler/commands/viz_spec.rb deleted file mode 100644 index 0ad285104c4e38..00000000000000 --- a/spec/bundler/commands/viz_spec.rb +++ /dev/null @@ -1,144 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "bundle viz", if: Bundler.which("dot") do - before do - base_system_gems "rexml", "ruby-graphviz" - end - - it "graphs gems from the Gemfile" do - install_gemfile <<-G - source "https://gem.repo1" - gem "myrack" - gem "myrack-obama" - G - - bundle "viz" - expect(out).to include("gem_graph.png") - - bundle "viz", format: "debug" - expect(out).to eq(<<~DOT.strip) - digraph Gemfile { - concentrate = "true"; - normalize = "true"; - nodesep = "0.55"; - edge[ weight = "2"]; - node[ fontname = "Arial, Helvetica, SansSerif"]; - edge[ fontname = "Arial, Helvetica, SansSerif" , fontsize = "12"]; - default [style = "filled", fillcolor = "#B9B9D5", shape = "box3d", fontsize = "16", label = "default"]; - myrack [style = "filled", fillcolor = "#B9B9D5", label = "myrack"]; - default -> myrack [constraint = "false"]; - "myrack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "myrack-obama"]; - default -> "myrack-obama" [constraint = "false"]; - "myrack-obama" -> myrack; - } - debugging bundle viz... - DOT - end - - it "graphs gems that are prereleases" do - build_repo2 do - build_gem "myrack", "1.3.pre" - end - - install_gemfile <<-G - source "https://gem.repo2" - gem "myrack", "= 1.3.pre" - gem "myrack-obama" - G - - bundle "viz" - expect(out).to include("gem_graph.png") - - bundle "viz", format: :debug, version: true - expect(out).to eq(<<~EOS.strip) - digraph Gemfile { - concentrate = "true"; - normalize = "true"; - nodesep = "0.55"; - edge[ weight = "2"]; - node[ fontname = "Arial, Helvetica, SansSerif"]; - edge[ fontname = "Arial, Helvetica, SansSerif" , fontsize = "12"]; - default [style = "filled", fillcolor = "#B9B9D5", shape = "box3d", fontsize = "16", label = "default"]; - myrack [style = "filled", fillcolor = "#B9B9D5", label = "myrack\\n1.3.pre"]; - default -> myrack [constraint = "false"]; - "myrack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "myrack-obama\\n1.0"]; - default -> "myrack-obama" [constraint = "false"]; - "myrack-obama" -> myrack; - } - debugging bundle viz... - EOS - end - - context "with another gem that has a graphviz file" do - before do - update_repo4 do - build_gem "graphviz", "999" do |s| - s.write("lib/graphviz.rb", "abort 'wrong graphviz gem loaded'") - end - end - - system_gems "graphviz-999", gem_repo: gem_repo4 - end - - it "loads the correct ruby-graphviz gem" do - install_gemfile <<-G - source "https://gem.repo1" - gem "myrack" - gem "myrack-obama" - G - - bundle "viz", format: "debug" - expect(out).to eq(<<~DOT.strip) - digraph Gemfile { - concentrate = "true"; - normalize = "true"; - nodesep = "0.55"; - edge[ weight = "2"]; - node[ fontname = "Arial, Helvetica, SansSerif"]; - edge[ fontname = "Arial, Helvetica, SansSerif" , fontsize = "12"]; - default [style = "filled", fillcolor = "#B9B9D5", shape = "box3d", fontsize = "16", label = "default"]; - myrack [style = "filled", fillcolor = "#B9B9D5", label = "myrack"]; - default -> myrack [constraint = "false"]; - "myrack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "myrack-obama"]; - default -> "myrack-obama" [constraint = "false"]; - "myrack-obama" -> myrack; - } - debugging bundle viz... - DOT - end - end - - context "--without option" do - it "one group" do - install_gemfile <<-G - source "https://gem.repo1" - gem "activesupport" - - group :rails do - gem "rails" - end - G - - bundle "viz --without=rails" - expect(out).to include("gem_graph.png") - end - - it "two groups" do - install_gemfile <<-G - source "https://gem.repo1" - gem "activesupport" - - group :myrack do - gem "myrack" - end - - group :rails do - gem "rails" - end - G - - bundle "viz --without=rails:myrack" - expect(out).to include("gem_graph.png") - end - end -end diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index d17770d00c708c..dcefbe0afa4397 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -642,15 +642,12 @@ context "bundle viz" do before do - create_file "gems.rb", "source 'https://gem.repo1'" - bundle "viz" + bundle "viz", raise_on_error: false end - it "prints a deprecation warning" do - expect(deprecations).to include "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph" + it "fails with a helpful message" do + expect(err).to include "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph" end - - pending "fails with a helpful message", bundler: "4" end context "bundle inject" do diff --git a/tool/bundler/test_gems.rb b/tool/bundler/test_gems.rb index 28113cd299d4f0..9776a96b90eded 100644 --- a/tool/bundler/test_gems.rb +++ b/tool/bundler/test_gems.rb @@ -11,7 +11,6 @@ gem "rb_sys" gem "fiddle" gem "rubygems-generate_index", "~> 1.1" -gem "ruby-graphviz" gem "psych" gem "etc", platforms: [:ruby, :windows] gem "open3" diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index 99760ac573af8d..fa61796f0f825f 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -33,9 +33,6 @@ GEM rake-compiler-dock (1.9.1) rb_sys (0.9.111) rake-compiler-dock (= 1.9.1) - rexml (3.4.1) - ruby-graphviz (1.2.5) - rexml ruby2_keywords (0.0.5) rubygems-generate_index (1.1.3) compact_index (~> 0.15.0) @@ -69,7 +66,6 @@ DEPENDENCIES rack-test (~> 2.1) rake (~> 13.1) rb_sys - ruby-graphviz rubygems-generate_index (~> 1.1) shellwords sinatra (~> 4.1) @@ -95,8 +91,6 @@ CHECKSUMS rake (13.3.0) sha256=96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493 rake-compiler-dock (1.9.1) sha256=e73720a29aba9c114728ce39cc0d8eef69ba61d88e7978c57bac171724cd4d53 rb_sys (0.9.111) sha256=65822fd8d57c248cd893db0efe01bc6edc15fcbea3ba6666091e35430c1cbaf0 - rexml (3.4.1) sha256=c74527a9a0a04b4ec31dbe0dc4ed6004b960af943d8db42e539edde3a871abca - ruby-graphviz (1.2.5) sha256=1c2bb44e3f6da9e2b829f5e7fd8d75a521485fb6b4d1fc66ff0f93f906121504 ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef rubygems-generate_index (1.1.3) sha256=3571424322666598e9586a906485e1543b617f87644913eaf137d986a3393f5c shellwords (0.2.2) sha256=b8695a791de2f71472de5abdc3f4332f6535a4177f55d8f99e7e44266cd32f94 From 50d4622637b73715d20c5112f38d6fae0109c78f Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Tue, 12 Aug 2025 20:33:03 -0400 Subject: [PATCH 07/36] [rubygems/rubygems] Fix `bundle lock` regression when using `update` and `lockfile` flags: - Ref #8917 - ### Problem Prior to Bundler 2.5.6, running `bundle lock --update foo --lockfile Gemfile_bumped.lock` would update only the foo gem and write the lockfile to the `Gemfile_bumped.lock`. In Bundler 2.5.6 and above running the same command, updates absolutely all gems. This change is related to #7047 ### Solution We decided to expose the `write_lock` method rather than going through a complex deprecation cycle of the `lock` method. This commit applies the same business logic as prios to 2.5.6 where, we build the definition using the existing lockfile, make changes to the definition and dump it into the desired lockfile. https://github.com/rubygems/rubygems/commit/c88f00c41d --- lib/bundler/cli/lock.rb | 10 ++-- lib/bundler/definition.rb | 76 +++++++++++++++--------------- spec/bundler/commands/lock_spec.rb | 38 +++++++++++++++ 3 files changed, 81 insertions(+), 43 deletions(-) diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb index b60c82e3a14a9a..2f78868936c108 100644 --- a/lib/bundler/cli/lock.rb +++ b/lib/bundler/cli/lock.rb @@ -35,11 +35,8 @@ def run update = { bundler: bundler } end - file = options[:lockfile] - file = file ? Pathname.new(file).expand_path : Bundler.default_lockfile - Bundler.settings.temporary(frozen: false) do - definition = Bundler.definition(update, file) + definition = Bundler.definition(update, Bundler.default_lockfile) definition.add_checksums if options["add-checksums"] Bundler::CLI::Common.configure_gem_version_promoter(definition, options) if options[:update] @@ -71,8 +68,11 @@ def run if print puts definition.to_lock else + file = options[:lockfile] + file = file ? Pathname.new(file).expand_path : Bundler.default_lockfile + puts "Writing lockfile to #{file}" - definition.lock + definition.write_lock(file, false) end end diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 52f9c6e1255c36..6cbaa9c396a68b 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -373,6 +373,44 @@ def lock(file_or_preserve_unknown_sections = false, preserve_unknown_sections_or write_lock(target_lockfile, preserve_unknown_sections) end + def write_lock(file, preserve_unknown_sections) + return if Definition.no_lock || file.nil? + + contents = to_lock + + # Convert to \r\n if the existing lock has them + # i.e., Windows with `git config core.autocrlf=true` + contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match?("\r\n") + + if @locked_bundler_version + locked_major = @locked_bundler_version.segments.first + current_major = bundler_version_to_lock.segments.first + + updating_major = locked_major < current_major + end + + preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler)) + + if File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections) + return if Bundler.frozen_bundle? + SharedHelpers.filesystem_access(file) { FileUtils.touch(file) } + return + end + + if Bundler.frozen_bundle? + Bundler.ui.error "Cannot write a changed lockfile while frozen." + return + end + + begin + SharedHelpers.filesystem_access(file) do |p| + File.open(p, "wb") {|f| f.puts(contents) } + end + rescue ReadOnlyFileSystemError + raise ProductionError, lockfile_changes_summary("file system is read-only") + end + end + def locked_ruby_version return unless ruby_version if @unlocking_ruby || !@locked_ruby_version @@ -574,44 +612,6 @@ def lockfile_exists? lockfile && File.exist?(lockfile) end - def write_lock(file, preserve_unknown_sections) - return if Definition.no_lock || file.nil? - - contents = to_lock - - # Convert to \r\n if the existing lock has them - # i.e., Windows with `git config core.autocrlf=true` - contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match?("\r\n") - - if @locked_bundler_version - locked_major = @locked_bundler_version.segments.first - current_major = bundler_version_to_lock.segments.first - - updating_major = locked_major < current_major - end - - preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler)) - - if File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections) - return if Bundler.frozen_bundle? - SharedHelpers.filesystem_access(file) { FileUtils.touch(file) } - return - end - - if Bundler.frozen_bundle? - Bundler.ui.error "Cannot write a changed lockfile while frozen." - return - end - - begin - SharedHelpers.filesystem_access(file) do |p| - File.open(p, "wb") {|f| f.puts(contents) } - end - rescue ReadOnlyFileSystemError - raise ProductionError, lockfile_changes_summary("file system is read-only") - end - end - def resolver @resolver ||= new_resolver(resolution_base) end diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index 19c1a5ca8bf0ab..5a31d1733a5556 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -311,6 +311,44 @@ expect { read_lockfile }.to raise_error(Errno::ENOENT) end + it "updates a specific gem and write to a custom location" do + build_repo4 do + build_gem "uri", %w[1.0.2 1.0.3] + build_gem "warning", %w[1.4.0 1.5.0] + end + + gemfile <<~G + source "https://gem.repo4" + + gem "uri" + gem "warning" + G + + lockfile <<~L + GEM + remote: https://gem.repo4 + specs: + uri (1.0.2) + warning (1.4.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + uri + warning + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock --update uri --lockfile=lock" + + lockfile_content = read_lockfile("lock") + expect(lockfile_content).to include("uri (1.0.3)") + expect(lockfile_content).to include("warning (1.4.0)") + end + it "writes to custom location using --lockfile when a default lockfile is present" do gemfile_with_rails_weakling_and_foo_from_repo4 From b4c9ccefd6a34a24dfc509966cd679063813100f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 13 Aug 2025 21:55:34 +0200 Subject: [PATCH 08/36] [rubygems/rubygems] Remove unnecessary subject from one spec https://github.com/rubygems/rubygems/commit/a761581e89 --- spec/bundler/commands/cache_spec.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index 20d817a47dfe61..6e05ea4f6d599c 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -299,11 +299,6 @@ bundle "install" end - subject do - bundle "config set --local frozen true" - bundle :cache, raise_on_error: false - end - it "tries to install with frozen" do bundle "config set deployment true" gemfile <<-G @@ -311,7 +306,8 @@ gem "myrack" gem "myrack-obama" G - subject + bundle "config set --local frozen true" + bundle :cache, raise_on_error: false expect(exitstatus).to eq(16) expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile") From 6f8738db259f1f9b59e373ca98d8fe105d319257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 13 Aug 2025 21:56:58 +0200 Subject: [PATCH 09/36] [rubygems/rubygems] Remove unnecessary install Since the path is changed later by the `deployment` setting, it makes no difference. So just create a lockfile. https://github.com/rubygems/rubygems/commit/2dd6f65642 --- spec/bundler/commands/cache_spec.rb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index 6e05ea4f6d599c..6fffc05e271260 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -296,7 +296,21 @@ source "https://gem.repo1" gem "myrack" G - bundle "install" + lockfile <<-L + GEM + remote: https://gem.repo1/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + + BUNDLED WITH + #{Bundler::VERSION} + L end it "tries to install with frozen" do From 2052dd1cfc2f8241b89ae017ba60a096989a7be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 13 Aug 2025 21:58:12 +0200 Subject: [PATCH 10/36] [rubygems/rubygems] It's not necessary to set `deployment` https://github.com/rubygems/rubygems/commit/eaa51203ca --- spec/bundler/commands/cache_spec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index 6fffc05e271260..973cf5add5524a 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -311,23 +311,22 @@ BUNDLED WITH #{Bundler::VERSION} L + bundle "config set --local frozen true" end it "tries to install with frozen" do - bundle "config set deployment true" gemfile <<-G source "https://gem.repo1" gem "myrack" gem "myrack-obama" G - bundle "config set --local frozen true" bundle :cache, raise_on_error: false expect(exitstatus).to eq(16) expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile") expect(err).to include("* myrack-obama") bundle "env" - expect(out).to include("frozen").or include("deployment") + expect(out).to include("frozen") end end From 7488b48a76af7359ea121f42db12b12d6b439b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 27 Aug 2025 19:48:39 +0200 Subject: [PATCH 11/36] [rubygems/rubygems] Improve spec wording and implementation Limit the `before` block to set `frozen`, which is the context of the spec, and change the implementation to only write the Gemfile once, with a gem being removed from what's in the lockfile. https://github.com/rubygems/rubygems/commit/4d0314ff37 --- spec/bundler/commands/cache_spec.rb | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index 973cf5add5524a..db5ec9cf212b12 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -292,6 +292,10 @@ context "with frozen configured" do before do + bundle "config set --local frozen true" + end + + it "tries to install but fails when the lockfile is out of sync" do gemfile <<-G source "https://gem.repo1" gem "myrack" @@ -301,29 +305,23 @@ remote: https://gem.repo1/ specs: myrack (1.0.0) + myrack-obama (1.0) + myrack PLATFORMS #{lockfile_platforms} DEPENDENCIES myrack + myrack-obama BUNDLED WITH #{Bundler::VERSION} L - bundle "config set --local frozen true" - end - - it "tries to install with frozen" do - gemfile <<-G - source "https://gem.repo1" - gem "myrack" - gem "myrack-obama" - G bundle :cache, raise_on_error: false expect(exitstatus).to eq(16) expect(err).to include("frozen mode") - expect(err).to include("You have added to the Gemfile") + expect(err).to include("You have deleted from the Gemfile") expect(err).to include("* myrack-obama") bundle "env" expect(out).to include("frozen") From 2558350c0488ccd1a71b5f1e86f7fdbccad5056e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 13 Aug 2025 19:36:21 +0200 Subject: [PATCH 12/36] [rubygems/rubygems] Fix `bundle cache` failing in frozen mode if vendor/cache is empty https://github.com/rubygems/rubygems/commit/36c5af9156 --- lib/bundler/cli/install.rb | 2 -- lib/bundler/definition.rb | 6 ++++-- spec/bundler/commands/cache_spec.rb | 29 +++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index 074afd64ebd078..5eeba0c8d84508 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -42,8 +42,6 @@ def run "before deploying." end - options[:local] = true if Bundler.app_cache.exist? - Bundler.settings.set_command_option :deployment, true if options[:deployment] Bundler.settings.set_command_option :frozen, true if options[:frozen] end diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 6cbaa9c396a68b..e400c38cec54dc 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -189,12 +189,14 @@ def check! def setup_domain!(options = {}) prefer_local! if options[:"prefer-local"] + sources.cached! + if options[:add_checksums] || (!options[:local] && install_needed?) - remotely! + sources.remote! true else Bundler.settings.set_command_option(:jobs, 1) unless install_needed? # to avoid the overhead of Bundler::Worker - with_cache! + sources.local! false end end diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index db5ec9cf212b12..fd8d0381c9a1ec 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -326,6 +326,35 @@ bundle "env" expect(out).to include("frozen") end + + it "caches gems without installing when lockfile is in sync, and --no-install is passed, even if vendor/cache directory is initially empty" do + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + lockfile <<-L + GEM + remote: https://gem.repo1/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + + BUNDLED WITH + #{Bundler::VERSION} + L + app_cache = bundled_app("vendor/cache") + FileUtils.mkdir_p app_cache + + bundle "cache --no-install" + expect(out).not_to include("Installing myrack 1.0.0") + expect(out).to include("Fetching myrack 1.0.0") + expect(app_cache.join("myrack-1.0.0.gem")).to exist + end end context "with gems with extensions" do From c75de0d5f261832450f774c70c1e80b7250793ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 14 Aug 2025 06:49:48 +0200 Subject: [PATCH 13/36] [rubygems/rubygems] Add extra spec for another issue that the previous patch fixed https://github.com/rubygems/rubygems/commit/4fc57c69b2 --- spec/bundler/commands/cache_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index fd8d0381c9a1ec..2747b0e316048a 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -416,6 +416,22 @@ expect(the_bundle).to include_gems "myrack 1.0.0" end + it "does not hit the remote at all in non frozen mode either" do + build_repo2 + install_gemfile <<-G + source "https://gem.repo2" + gem "myrack" + G + + bundle :cache + pristine_system_gems + FileUtils.rm_r gem_repo2 + + bundle "config set --local path vendor/bundle" + bundle :install + expect(the_bundle).to include_gems "myrack 1.0.0" + end + it "does not hit the remote at all when cache_all_platforms configured" do build_repo2 install_gemfile <<-G From 1c7c0107a465b0f37d07a3aa5dc43b5427a1380a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 27 Aug 2025 21:10:35 +0200 Subject: [PATCH 14/36] [rubygems/rubygems] Add another spec for yet another issue fixed by the previous patch https://github.com/rubygems/rubygems/commit/fbb8340186 --- spec/bundler/commands/cache_spec.rb | 40 ++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index 2747b0e316048a..be47152cc7c03a 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -291,6 +291,8 @@ end context "with frozen configured" do + let(:app_cache) { bundled_app("vendor/cache") } + before do bundle "config set --local frozen true" end @@ -347,7 +349,6 @@ BUNDLED WITH #{Bundler::VERSION} L - app_cache = bundled_app("vendor/cache") FileUtils.mkdir_p app_cache bundle "cache --no-install" @@ -355,6 +356,43 @@ expect(out).to include("Fetching myrack 1.0.0") expect(app_cache.join("myrack-1.0.0.gem")).to exist end + + it "completes a partial cache when lockfile is in sync, even if the already cached gem is no longer available remotely" do + build_repo4 do + build_gem "foo", "1.0.0" + end + + build_gem "bar", "1.0.0", path: bundled_app("vendor/cache") + + gemfile <<-G + source "https://gem.repo4" + gem "foo" + gem "bar" + G + lockfile <<-L + GEM + remote: https://gem.repo4/ + specs: + foo (1.0.0) + bar (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo + bar + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "cache --no-install" + expect(out).to include("Fetching foo 1.0.0") + expect(out).not_to include("Fetching bar 1.0.0") + expect(app_cache.join("foo-1.0.0.gem")).to exist + expect(app_cache.join("bar-1.0.0.gem")).to exist + end end context "with gems with extensions" do From d20936c9f59a5be8f4de6057d47cbc417111b0ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 15 Jul 2025 14:36:31 +0200 Subject: [PATCH 15/36] [rubygems/rubygems] Improve `deprecations` helper Previous implementation included an empty element in the list of deprecations. https://github.com/rubygems/rubygems/commit/59eb233039 --- spec/bundler/support/helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb index 438037e08c6165..17c38c77607d4f 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -39,7 +39,7 @@ def err_without_deprecations end def deprecations - err.split("\n").select {|l| l =~ MAJOR_DEPRECATION }.join("\n").split(MAJOR_DEPRECATION) + err.split("\n").filter_map {|l| l.sub(MAJOR_DEPRECATION, "") if l.match?(MAJOR_DEPRECATION) } end def run(cmd, *args) From 2d3fd1fd2841e13cc65c72e870e3288923770a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 14 Aug 2025 07:14:16 +0200 Subject: [PATCH 16/36] [rubygems/rubygems] Deprecate `bundle cache --frozen` and `bundle cache --no-prune` Since they are remembered flags, too. I also restored a previous spec about `--frozen`, but didn't bother with `--no-prune`. https://github.com/rubygems/rubygems/commit/7802f016fc --- lib/bundler/cli.rb | 4 ++ spec/bundler/commands/cache_spec.rb | 33 +++++++++++++++ spec/bundler/other/major_deprecation_spec.rb | 44 ++++++++++++++++++++ 3 files changed, 81 insertions(+) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 8acf25eba6b1c9..0d993e570220e7 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -415,6 +415,10 @@ def cache print_remembered_flag_deprecation("--all", "cache_all", "true") if ARGV.include?("--all") print_remembered_flag_deprecation("--no-all", "cache_all", "false") if ARGV.include?("--no-all") + %w[frozen no-prune].each do |option| + remembered_flag_deprecation(option) + end + if flag_passed?("--path") message = "The `--path` flag is deprecated because its semantics are unclear. " \ diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index be47152cc7c03a..ff70d0337dd2f4 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -290,6 +290,39 @@ end end + it "enforces frozen mode when --frozen is passed" do + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + gem "myrack-obama" + G + + lockfile <<-L + GEM + remote: https://gem.repo1/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "cache --frozen", raise_on_error: false + + expect(exitstatus).to eq(16) + expect(err).to include("frozen mode") + expect(err).to include("You have added to the Gemfile") + expect(err).to include("* myrack-obama") + bundle "env" + expect(out).to include("frozen") + end + context "with frozen configured" do let(:app_cache) { bundled_app("vendor/cache") } diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index dcefbe0afa4397..d36a9ee40c0db4 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -265,6 +265,50 @@ pending "fails with a helpful error", bundler: "4" end + context "bundle cache --frozen" do + before do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle "cache --frozen", raise_on_error: false + end + + it "should print a deprecation warning" do + expect(deprecations).to include( + "The `--frozen` flag is deprecated because it relies on being " \ + "remembered across bundler invocations, which bundler will no " \ + "longer do in future versions. Instead please use `bundle config set " \ + "frozen true`, and stop using this flag" + ) + end + + pending "fails with a helpful error", bundler: "4" + end + + context "bundle cache --no-prune" do + before do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle "cache --no-prune", raise_on_error: false + end + + it "should print a deprecation warning" do + expect(deprecations).to include( + "The `--no-prune` flag is deprecated because it relies on being " \ + "remembered across bundler invocations, which bundler will no " \ + "longer do in future versions. Instead please use `bundle config set " \ + "no_prune true`, and stop using this flag" + ) + end + + pending "fails with a helpful error", bundler: "4" + end + describe "bundle config" do describe "old list interface" do before do From cbd0de84f6325a66d9d97b17073252bc0e2bb512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 6 Aug 2025 19:38:03 +0200 Subject: [PATCH 17/36] [rubygems/rubygems] Improve source list management testing This approach better simulate that ["http://gems.example.com/"] is the default list of sources, rather than ["https://rubygems.org/"]. https://github.com/rubygems/rubygems/commit/55130c259a --- lib/rubygems/defaults.rb | 2 +- test/rubygems/helper.rb | 3 ++- test/rubygems/test_gem.rb | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/rubygems/defaults.rb b/lib/rubygems/defaults.rb index db07681a17e218..90f09fc1917481 100644 --- a/lib/rubygems/defaults.rb +++ b/lib/rubygems/defaults.rb @@ -13,7 +13,7 @@ module Gem # An Array of the default sources that come with RubyGems def self.default_sources - %w[https://rubygems.org/] + @default_sources ||= %w[https://rubygems.org/] end ## diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index 51c99a1bc5e211..b797960ac936a0 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -400,8 +400,9 @@ def setup Gem::RemoteFetcher.fetcher = Gem::FakeFetcher.new @gem_repo = "http://gems.example.com/" + Gem.instance_variable_set :@default_sources, [@gem_repo] + Gem.instance_variable_set :@sources, nil @uri = Gem::URI.parse @gem_repo - Gem.sources.replace [@gem_repo] Gem.searcher = nil Gem::SpecFetcher.fetcher = nil diff --git a/test/rubygems/test_gem.rb b/test/rubygems/test_gem.rb index 49e81fcedb24bb..74c8953904f238 100644 --- a/test/rubygems/test_gem.rb +++ b/test/rubygems/test_gem.rb @@ -586,6 +586,7 @@ def test_default_path_vendor_dir end def test_self_default_sources + Gem.remove_instance_variable :@default_sources assert_equal %w[https://rubygems.org/], Gem.default_sources end @@ -1198,6 +1199,8 @@ def test_self_sources Gem.sources = nil Gem.configuration.sources = %w[http://test.example.com/] assert_equal %w[http://test.example.com/], Gem.sources + ensure + Gem.configuration.sources = nil end def test_try_activate_returns_true_for_activated_specs From 0775c239e111e7738a89cb3e44f585c6e1c158da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 6 Aug 2025 19:43:16 +0200 Subject: [PATCH 18/36] [rubygems/rubygems] Let `gem sources` be more clear about which sources it's displaying https://github.com/rubygems/rubygems/commit/2afefa4a48 --- lib/rubygems/commands/sources_command.rb | 20 ++++++++++++++++--- .../test_gem_commands_sources_command.rb | 4 ++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb index 976f4a4ea2ae92..a86d8d501494cf 100644 --- a/lib/rubygems/commands/sources_command.rb +++ b/lib/rubygems/commands/sources_command.rb @@ -128,7 +128,7 @@ def description # :nodoc: Without any arguments the sources lists your currently configured sources: $ gem sources - *** CURRENT SOURCES *** + *** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW *** https://rubygems.org @@ -164,10 +164,18 @@ def description # :nodoc: end def list # :nodoc: - say "*** CURRENT SOURCES ***" + if configured_sources + header = "*** CURRENT SOURCES ***" + list = configured_sources + else + header = "*** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW ***" + list = Gem.sources + end + + say header say - Gem.sources.each do |src| + list.each do |src| say src end end @@ -224,4 +232,10 @@ def remove_cache_file(desc, path) # :nodoc: say "*** Unable to remove #{desc} source cache ***" end end + + private + + def configured_sources + Gem.configuration.sources + end end diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb index 5e675e5c844729..ac43a274499d62 100644 --- a/test/rubygems/test_gem_commands_sources_command.rb +++ b/test/rubygems/test_gem_commands_sources_command.rb @@ -32,7 +32,7 @@ def test_execute end expected = <<-EOF -*** CURRENT SOURCES *** +*** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW *** #{@gem_repo} EOF @@ -476,7 +476,7 @@ def test_execute_list end expected = <<-EOF -*** CURRENT SOURCES *** +*** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW *** #{@gem_repo} EOF From 92df7e456a05240977e778d6947372e422129669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 6 Aug 2025 20:01:33 +0200 Subject: [PATCH 19/36] [rubygems/rubygems] Add missing `gem sources --remove` test https://github.com/rubygems/rubygems/commit/1f779adc47 --- .../rubygems/test_gem_commands_sources_command.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb index ac43a274499d62..5f15d038c996b6 100644 --- a/test/rubygems/test_gem_commands_sources_command.rb +++ b/test/rubygems/test_gem_commands_sources_command.rb @@ -517,6 +517,21 @@ def test_execute_remove_no_network assert_equal "", @ui.error end + def test_execute_remove_not_present + spec_fetcher + + @cmd.handle_options %W[--remove https://does.not.exist] + + use_ui @ui do + @cmd.execute + end + + expected = "source https://does.not.exist not present in cache\n" + + assert_equal expected, @ui.output + assert_equal "", @ui.error + end + def test_execute_update @cmd.handle_options %w[--update] From e89eb0b973c6d257085dc3cd8d564ea94d07ed94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 6 Aug 2025 20:27:03 +0200 Subject: [PATCH 20/36] [rubygems/rubygems] Simplify test for trailing slash handling when adding sources https://github.com/rubygems/rubygems/commit/9c9fd4799f --- .../test_gem_commands_sources_command.rb | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb index 5f15d038c996b6..4d54844aecc468 100644 --- a/test/rubygems/test_gem_commands_sources_command.rb +++ b/test/rubygems/test_gem_commands_sources_command.rb @@ -274,21 +274,8 @@ def test_execute_add_redundant_source def test_execute_add_redundant_source_trailing_slash spec_fetcher - # Remove pre-existing gem source (w/ slash) repo_with_slash = "http://gems.example.com/" - @cmd.handle_options %W[--remove #{repo_with_slash}] - use_ui @ui do - @cmd.execute - end - source = Gem::Source.new repo_with_slash - assert_equal false, Gem.sources.include?(source) - - expected = <<-EOF -#{repo_with_slash} removed from sources - EOF - - assert_equal expected, @ui.output - assert_equal "", @ui.error + Gem.configuration.sources = [repo_with_slash] # Re-add pre-existing gem source (w/o slash) repo_without_slash = "http://gems.example.com" @@ -300,8 +287,7 @@ def test_execute_add_redundant_source_trailing_slash assert_equal true, Gem.sources.include?(source) expected = <<-EOF -http://gems.example.com/ removed from sources -http://gems.example.com added to sources +source http://gems.example.com already present in the cache EOF assert_equal expected, @ui.output @@ -316,13 +302,14 @@ def test_execute_add_redundant_source_trailing_slash assert_equal true, Gem.sources.include?(source) expected = <<-EOF -http://gems.example.com/ removed from sources -http://gems.example.com added to sources +source http://gems.example.com already present in the cache source http://gems.example.com/ already present in the cache EOF assert_equal expected, @ui.output assert_equal "", @ui.error + ensure + Gem.configuration.sources = nil end def test_execute_add_http_rubygems_org From 566cd152fbb6762d34c93ba82f94714bde49a575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 6 Aug 2025 21:34:13 +0200 Subject: [PATCH 21/36] [rubygems/rubygems] Refactor duplicated test logic https://github.com/rubygems/rubygems/commit/6fcc20f884 --- .../test_gem_commands_sources_command.rb | 125 ++++-------------- 1 file changed, 26 insertions(+), 99 deletions(-) diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb index 4d54844aecc468..9229130dafabd7 100644 --- a/test/rubygems/test_gem_commands_sources_command.rb +++ b/test/rubygems/test_gem_commands_sources_command.rb @@ -42,21 +42,7 @@ def test_execute end def test_execute_add - spec_fetcher do |fetcher| - fetcher.spec "a", 1 - end - - specs = Gem::Specification.map do |spec| - [spec.name, spec.version, spec.original_platform] - end - - specs_dump_gz = StringIO.new - Zlib::GzipWriter.wrap specs_dump_gz do |io| - Marshal.dump specs, io - end - - @fetcher.data["#{@new_repo}/specs.#{@marshal_version}.gz"] = - specs_dump_gz.string + setup_fake_source(@new_repo) @cmd.handle_options %W[--add #{@new_repo}] @@ -77,20 +63,8 @@ def test_execute_add def test_execute_add_allow_typo_squatting_source rubygems_org = "https://rubyems.org" - spec_fetcher do |fetcher| - fetcher.spec("a", 1) - end + setup_fake_source(rubygems_org) - specs = Gem::Specification.map do |spec| - [spec.name, spec.version, spec.original_platform] - end - - specs_dump_gz = StringIO.new - Zlib::GzipWriter.wrap(specs_dump_gz) do |io| - Marshal.dump(specs, io) - end - - @fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string @cmd.handle_options %W[--add #{rubygems_org}] ui = Gem::MockGemUi.new("y") @@ -111,20 +85,8 @@ def test_execute_add_allow_typo_squatting_source def test_execute_add_allow_typo_squatting_source_forced rubygems_org = "https://rubyems.org" - spec_fetcher do |fetcher| - fetcher.spec("a", 1) - end - - specs = Gem::Specification.map do |spec| - [spec.name, spec.version, spec.original_platform] - end - - specs_dump_gz = StringIO.new - Zlib::GzipWriter.wrap(specs_dump_gz) do |io| - Marshal.dump(specs, io) - end + setup_fake_source(rubygems_org) - @fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string @cmd.handle_options %W[--force --add #{rubygems_org}] @cmd.execute @@ -141,21 +103,7 @@ def test_execute_add_allow_typo_squatting_source_forced def test_execute_add_deny_typo_squatting_source rubygems_org = "https://rubyems.org" - spec_fetcher do |fetcher| - fetcher.spec("a", 1) - end - - specs = Gem::Specification.map do |spec| - [spec.name, spec.version, spec.original_platform] - end - - specs_dump_gz = StringIO.new - Zlib::GzipWriter.wrap(specs_dump_gz) do |io| - Marshal.dump(specs, io) - end - - @fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] = - specs_dump_gz.string + setup_fake_source(rubygems_org) @cmd.handle_options %W[--add #{rubygems_org}] @@ -315,21 +263,7 @@ def test_execute_add_redundant_source_trailing_slash def test_execute_add_http_rubygems_org http_rubygems_org = "http://rubygems.org/" - spec_fetcher do |fetcher| - fetcher.spec "a", 1 - end - - specs = Gem::Specification.map do |spec| - [spec.name, spec.version, spec.original_platform] - end - - specs_dump_gz = StringIO.new - Zlib::GzipWriter.wrap specs_dump_gz do |io| - Marshal.dump specs, io - end - - @fetcher.data["#{http_rubygems_org}/specs.#{@marshal_version}.gz"] = - specs_dump_gz.string + setup_fake_source(http_rubygems_org) @cmd.handle_options %W[--add #{http_rubygems_org}] @@ -353,20 +287,8 @@ def test_execute_add_http_rubygems_org def test_execute_add_http_rubygems_org_forced rubygems_org = "http://rubygems.org" - spec_fetcher do |fetcher| - fetcher.spec("a", 1) - end + setup_fake_source(rubygems_org) - specs = Gem::Specification.map do |spec| - [spec.name, spec.version, spec.original_platform] - end - - specs_dump_gz = StringIO.new - Zlib::GzipWriter.wrap(specs_dump_gz) do |io| - Marshal.dump(specs, io) - end - - @fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string @cmd.handle_options %W[--force --add #{rubygems_org}] @cmd.execute @@ -383,21 +305,7 @@ def test_execute_add_http_rubygems_org_forced def test_execute_add_https_rubygems_org https_rubygems_org = "https://rubygems.org/" - spec_fetcher do |fetcher| - fetcher.spec "a", 1 - end - - specs = Gem::Specification.map do |spec| - [spec.name, spec.version, spec.original_platform] - end - - specs_dump_gz = StringIO.new - Zlib::GzipWriter.wrap specs_dump_gz do |io| - Marshal.dump specs, io - end - - @fetcher.data["#{https_rubygems_org}/specs.#{@marshal_version}.gz"] = - specs_dump_gz.string + setup_fake_source(https_rubygems_org) @cmd.handle_options %W[--add #{https_rubygems_org}] @@ -533,4 +441,23 @@ def test_execute_update assert_equal "source cache successfully updated\n", @ui.output assert_equal "", @ui.error end + + private + + def setup_fake_source(uri) + spec_fetcher do |fetcher| + fetcher.spec "a", 1 + end + + specs = Gem::Specification.map do |spec| + [spec.name, spec.version, spec.original_platform] + end + + specs_dump_gz = StringIO.new + Zlib::GzipWriter.wrap specs_dump_gz do |io| + Marshal.dump specs, io + end + + @fetcher.data["#{uri}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string + end end From 6dcd100a28c79b25491183611d874c480d24fb3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 6 Aug 2025 21:40:48 +0200 Subject: [PATCH 22/36] [rubygems/rubygems] Change trailing slash test to not work on default sources https://github.com/rubygems/rubygems/commit/b2daf4707b --- test/rubygems/test_gem_commands_sources_command.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb index 9229130dafabd7..973ad9cfa96e04 100644 --- a/test/rubygems/test_gem_commands_sources_command.rb +++ b/test/rubygems/test_gem_commands_sources_command.rb @@ -220,13 +220,14 @@ def test_execute_add_redundant_source end def test_execute_add_redundant_source_trailing_slash - spec_fetcher + repo_with_slash = "http://sample.repo/" - repo_with_slash = "http://gems.example.com/" Gem.configuration.sources = [repo_with_slash] + setup_fake_source(repo_with_slash) + # Re-add pre-existing gem source (w/o slash) - repo_without_slash = "http://gems.example.com" + repo_without_slash = repo_with_slash.delete_suffix("/") @cmd.handle_options %W[--add #{repo_without_slash}] use_ui @ui do @cmd.execute @@ -235,7 +236,7 @@ def test_execute_add_redundant_source_trailing_slash assert_equal true, Gem.sources.include?(source) expected = <<-EOF -source http://gems.example.com already present in the cache +source #{repo_without_slash} already present in the cache EOF assert_equal expected, @ui.output @@ -250,8 +251,8 @@ def test_execute_add_redundant_source_trailing_slash assert_equal true, Gem.sources.include?(source) expected = <<-EOF -source http://gems.example.com already present in the cache -source http://gems.example.com/ already present in the cache +source #{repo_without_slash} already present in the cache +source #{repo_with_slash} already present in the cache EOF assert_equal expected, @ui.output From 4a6fa17d1dc56352542e3584ba880b54d9277ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 6 Aug 2025 21:47:09 +0200 Subject: [PATCH 23/36] [rubygems/rubygems] Fix trailing slashes not considered when removing sources https://github.com/rubygems/rubygems/commit/d86d9b3596 --- lib/rubygems/commands/sources_command.rb | 6 +++-- .../test_gem_commands_sources_command.rb | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb index a86d8d501494cf..8f809e94023155 100644 --- a/lib/rubygems/commands/sources_command.rb +++ b/lib/rubygems/commands/sources_command.rb @@ -202,8 +202,10 @@ def execute end def remove_source(source_uri) # :nodoc: - if Gem.sources.include? source_uri - Gem.sources.delete source_uri + source = Gem::Source.new source_uri + + if Gem.sources.include? source + Gem.sources.delete source Gem.configuration.write say "#{source_uri} removed from sources" diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb index 973ad9cfa96e04..45cb0662f8a0c6 100644 --- a/test/rubygems/test_gem_commands_sources_command.rb +++ b/test/rubygems/test_gem_commands_sources_command.rb @@ -428,6 +428,32 @@ def test_execute_remove_not_present assert_equal "", @ui.error end + def test_execute_remove_redundant_source_trailing_slash + repo_with_slash = "http://sample.repo/" + + Gem.configuration.sources = [repo_with_slash] + + setup_fake_source(repo_with_slash) + + repo_without_slash = repo_with_slash.delete_suffix("/") + + @cmd.handle_options %W[--remove #{repo_without_slash}] + use_ui @ui do + @cmd.execute + end + source = Gem::Source.new repo_without_slash + assert_equal false, Gem.sources.include?(source) + + expected = <<-EOF +#{repo_without_slash} removed from sources + EOF + + assert_equal expected, @ui.output + assert_equal "", @ui.error + ensure + Gem.configuration.sources = nil + end + def test_execute_update @cmd.handle_options %w[--update] From 358735b3ac34bf63a35bc53624424e4ce795816c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 6 Aug 2025 22:03:11 +0200 Subject: [PATCH 24/36] [rubygems/rubygems] Change more source tests to act on configured sources https://github.com/rubygems/rubygems/commit/7f0c90b3f0 --- .../test_gem_commands_sources_command.rb | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb index 45cb0662f8a0c6..47e316973ae9fd 100644 --- a/test/rubygems/test_gem_commands_sources_command.rb +++ b/test/rubygems/test_gem_commands_sources_command.rb @@ -382,24 +382,32 @@ def test_execute_list end def test_execute_remove - @cmd.handle_options %W[--remove #{@gem_repo}] + Gem.configuration.sources = [@new_repo] + + setup_fake_source(@new_repo) + + @cmd.handle_options %W[--remove #{@new_repo}] use_ui @ui do @cmd.execute end - expected = "#{@gem_repo} removed from sources\n" + expected = "#{@new_repo} removed from sources\n" assert_equal expected, @ui.output assert_equal "", @ui.error + ensure + Gem.configuration.sources = nil end def test_execute_remove_no_network + Gem.configuration.sources = [@new_repo] + spec_fetcher - @cmd.handle_options %W[--remove #{@gem_repo}] + @cmd.handle_options %W[--remove #{@new_repo}] - @fetcher.data["#{@gem_repo}Marshal.#{Gem.marshal_version}"] = proc do + @fetcher.data["#{@new_repo}Marshal.#{Gem.marshal_version}"] = proc do raise Gem::RemoteFetcher::FetchError end @@ -407,10 +415,12 @@ def test_execute_remove_no_network @cmd.execute end - expected = "#{@gem_repo} removed from sources\n" + expected = "#{@new_repo} removed from sources\n" assert_equal expected, @ui.output assert_equal "", @ui.error + ensure + Gem.configuration.sources = nil end def test_execute_remove_not_present From 30b344c7445ad8a1b21fd27f6c2f2cf3f9ea8af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 6 Aug 2025 22:32:00 +0200 Subject: [PATCH 25/36] [rubygems/rubygems] Change code examples to not use the default source https://github.com/rubygems/rubygems/commit/0ccf323734 --- lib/rubygems/commands/sources_command.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb index 8f809e94023155..1b5543cbf14b61 100644 --- a/lib/rubygems/commands/sources_command.rb +++ b/lib/rubygems/commands/sources_command.rb @@ -149,16 +149,16 @@ def description # :nodoc: To add a source use the --add argument: - $ gem sources --add https://rubygems.org - https://rubygems.org added to sources + $ gem sources --add https://my.private.source + https://my.private.source added to sources RubyGems will check to see if gems can be installed from the source given before it is added. To remove a source use the --remove argument: - $ gem sources --remove https://rubygems.org/ - https://rubygems.org/ removed from sources + $ gem sources --remove https://my.private.source/ + https://my.private.source/ removed from sources EOF end From cdb8c9e254d1e0b8de036bc2709915d146af0271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Mon, 1 Sep 2025 20:13:20 +0200 Subject: [PATCH 26/36] [rubygems/rubygems] Improve error output when removing a source through `gem sources` "Not present in cache" felt a bit unclear, so I changed the reason to: "No configured sources" or "source not present in configured sources", also pointing explicitly to the configuration file where RubyGems is looking for the source to be removed. https://github.com/rubygems/rubygems/commit/2bae554eff --- lib/rubygems/commands/sources_command.rb | 15 ++++++++++++--- .../test_gem_commands_sources_command.rb | 19 ++++++++++++++++++- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb index 1b5543cbf14b61..c878333b141bf2 100644 --- a/lib/rubygems/commands/sources_command.rb +++ b/lib/rubygems/commands/sources_command.rb @@ -204,13 +204,15 @@ def execute def remove_source(source_uri) # :nodoc: source = Gem::Source.new source_uri - if Gem.sources.include? source + if configured_sources&.include? source Gem.sources.delete source Gem.configuration.write say "#{source_uri} removed from sources" + elsif configured_sources + say "source #{source_uri} cannot be removed because it's not present in #{config_file_name}" else - say "source #{source_uri} not present in cache" + say "source #{source_uri} cannot be removed because there are no configured sources in #{config_file_name}" end end @@ -238,6 +240,13 @@ def remove_cache_file(desc, path) # :nodoc: private def configured_sources - Gem.configuration.sources + return @configured_sources if defined?(@configured_sources) + + configuration_sources = Gem.configuration.sources + @configured_sources = Gem::SourceList.from(configuration_sources) if configuration_sources + end + + def config_file_name + Gem.configuration.config_file_name end end diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb index 47e316973ae9fd..9f0adf5104d0d8 100644 --- a/test/rubygems/test_gem_commands_sources_command.rb +++ b/test/rubygems/test_gem_commands_sources_command.rb @@ -424,6 +424,23 @@ def test_execute_remove_no_network end def test_execute_remove_not_present + Gem.configuration.sources = ["https://other.repo"] + + @cmd.handle_options %W[--remove #{@new_repo}] + + use_ui @ui do + @cmd.execute + end + + expected = "source #{@new_repo} cannot be removed because it's not present in #{Gem.configuration.config_file_name}\n" + + assert_equal expected, @ui.output + assert_equal "", @ui.error + ensure + Gem.configuration.sources = nil + end + + def test_execute_remove_nothing_configured spec_fetcher @cmd.handle_options %W[--remove https://does.not.exist] @@ -432,7 +449,7 @@ def test_execute_remove_not_present @cmd.execute end - expected = "source https://does.not.exist not present in cache\n" + expected = "source https://does.not.exist cannot be removed because there are no configured sources in #{Gem.configuration.config_file_name}\n" assert_equal expected, @ui.output assert_equal "", @ui.error From cc2a70da2781d10016746e5fd03de9cc2f7b5ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Mon, 1 Sep 2025 20:27:30 +0200 Subject: [PATCH 27/36] [rubygems/rubygems] Warn when trying to remove a default source that's the only configured sources https://github.com/rubygems/rubygems/commit/ef78de5b69 --- lib/rubygems/commands/sources_command.rb | 10 +++++- .../test_gem_commands_sources_command.rb | 34 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb index c878333b141bf2..9e2f156da374ce 100644 --- a/lib/rubygems/commands/sources_command.rb +++ b/lib/rubygems/commands/sources_command.rb @@ -208,7 +208,11 @@ def remove_source(source_uri) # :nodoc: Gem.sources.delete source Gem.configuration.write - say "#{source_uri} removed from sources" + if default_sources.include?(source) && configured_sources.one? + alert_warning "Removing a default source when it is the only source has no effect. Add a different source to #{config_file_name} if you want to stop using it as a source." + else + say "#{source_uri} removed from sources" + end elsif configured_sources say "source #{source_uri} cannot be removed because it's not present in #{config_file_name}" else @@ -239,6 +243,10 @@ def remove_cache_file(desc, path) # :nodoc: private + def default_sources + Gem::SourceList.from(Gem.default_sources) + end + def configured_sources return @configured_sources if defined?(@configured_sources) diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb index 9f0adf5104d0d8..e004123c905ced 100644 --- a/test/rubygems/test_gem_commands_sources_command.rb +++ b/test/rubygems/test_gem_commands_sources_command.rb @@ -455,6 +455,40 @@ def test_execute_remove_nothing_configured assert_equal "", @ui.error end + def test_remove_default_also_present_in_configuration + Gem.configuration.sources = [@gem_repo] + + @cmd.handle_options %W[--remove #{@gem_repo}] + + use_ui @ui do + @cmd.execute + end + + expected = "WARNING: Removing a default source when it is the only source has no effect. Add a different source to #{Gem.configuration.config_file_name} if you want to stop using it as a source.\n" + + assert_equal "", @ui.output + assert_equal expected, @ui.error + ensure + Gem.configuration.sources = nil + end + + def test_remove_default_also_present_in_configuration_when_there_are_more_configured_sources + Gem.configuration.sources = [@gem_repo, "https://other.repo"] + + @cmd.handle_options %W[--remove #{@gem_repo}] + + use_ui @ui do + @cmd.execute + end + + expected = "#{@gem_repo} removed from sources\n" + + assert_equal expected, @ui.output + assert_equal "", @ui.error + ensure + Gem.configuration.sources = nil + end + def test_execute_remove_redundant_source_trailing_slash repo_with_slash = "http://sample.repo/" From c1a46fefe85930526b34157684bbcbf495c90dc7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 02:21:40 +0000 Subject: [PATCH 28/36] [rubygems/rubygems] Bump the rb-sys group across 2 directories with 1 update Bumps the rb-sys group with 1 update in the /test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib directory: [rb-sys](https://github.com/oxidize-rb/rb-sys). Bumps the rb-sys group with 1 update in the /test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example directory: [rb-sys](https://github.com/oxidize-rb/rb-sys). Updates `rb-sys` from 0.9.116 to 0.9.117 - [Release notes](https://github.com/oxidize-rb/rb-sys/releases) - [Commits](https://github.com/oxidize-rb/rb-sys/compare/v0.9.116...v0.9.117) Updates `rb-sys` from 0.9.116 to 0.9.117 - [Release notes](https://github.com/oxidize-rb/rb-sys/releases) - [Commits](https://github.com/oxidize-rb/rb-sys/compare/v0.9.116...v0.9.117) --- updated-dependencies: - dependency-name: rb-sys dependency-version: 0.9.117 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: rb-sys - dependency-name: rb-sys dependency-version: 0.9.117 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: rb-sys ... Signed-off-by: dependabot[bot] https://github.com/rubygems/rubygems/commit/02c46294ec --- .../custom_name/ext/custom_name_lib/Cargo.lock | 8 ++++---- .../custom_name/ext/custom_name_lib/Cargo.toml | 2 +- .../rust_ruby_example/Cargo.lock | 8 ++++---- .../rust_ruby_example/Cargo.toml | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock index 4851de09d075b2..6302e9ee37b60a 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock @@ -152,18 +152,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.116" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7059846f68396df83155779c75336ca24567741cb95256e6308c9fcc370e8dad" +checksum = "f900d1ce4629a2ebffaf5de74bd8f9c1188d4c5ed406df02f97e22f77a006f44" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.116" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac217510df41b9ffc041573e68d7a02aaff770c49943c7494441c4b224b0ecd0" +checksum = "ef1e9c857028f631056bcd6d88cec390c751e343ce2223ddb26d23eb4a151d59" dependencies = [ "bindgen", "lazy_static", diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml index 7cb12fa8a6c16c..e26fa352a81156 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.116" +rb-sys = "0.9.117" diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock index 9740b435e7bb8f..4bbe8932a2b8a4 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock @@ -145,18 +145,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.116" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7059846f68396df83155779c75336ca24567741cb95256e6308c9fcc370e8dad" +checksum = "f900d1ce4629a2ebffaf5de74bd8f9c1188d4c5ed406df02f97e22f77a006f44" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.116" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac217510df41b9ffc041573e68d7a02aaff770c49943c7494441c4b224b0ecd0" +checksum = "ef1e9c857028f631056bcd6d88cec390c751e343ce2223ddb26d23eb4a151d59" dependencies = [ "bindgen", "lazy_static", diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml index b389cff5425e5e..55d5c5bde7f645 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.116" +rb-sys = "0.9.117" From aae9e8262d7b9d57299de7af5a59e6dc1997a893 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 29 Aug 2025 14:44:15 +0900 Subject: [PATCH 29/36] Added x64-mswin64-140 to lockfiles --- tool/bundler/dev_gems.rb.lock | 1 + tool/bundler/rubocop_gems.rb.lock | 1 + tool/bundler/standard_gems.rb.lock | 1 + tool/bundler/test_gems.rb.lock | 1 + tool/bundler/vendor_gems.rb.lock | 1 + 5 files changed, 5 insertions(+) diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index 182e346f060cb6..ff1bcfcf63e1fb 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -69,6 +69,7 @@ PLATFORMS ruby universal-java x64-mingw-ucrt + x64-mswin64-140 x86-linux x86_64-darwin x86_64-linux diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index aa2fedcce80432..c26b12dbf1cb87 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -88,6 +88,7 @@ PLATFORMS ruby universal-java x64-mingw-ucrt + x64-mswin64-140 x86_64-darwin x86_64-linux diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index 655b5c7296130c..cfc47e25fd9a28 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -104,6 +104,7 @@ PLATFORMS ruby universal-java x64-mingw-ucrt + x64-mswin64-140 x86_64-darwin x86_64-linux diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index fa61796f0f825f..7e2e15e9159ae2 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -52,6 +52,7 @@ PLATFORMS ruby universal-java x64-mingw-ucrt + x64-mswin64-140 x86_64-darwin x86_64-linux diff --git a/tool/bundler/vendor_gems.rb.lock b/tool/bundler/vendor_gems.rb.lock index 825107514a6f1a..a9208d0971ca32 100644 --- a/tool/bundler/vendor_gems.rb.lock +++ b/tool/bundler/vendor_gems.rb.lock @@ -47,6 +47,7 @@ PLATFORMS ruby universal-java x64-mingw-ucrt + x64-mswin64-140 x86_64-darwin x86_64-linux From 94ddf47624ad6f1288ca721e880be99aee838485 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 26 Aug 2025 17:13:04 -0500 Subject: [PATCH 30/36] [ruby/strscan] [DOC] Fix link (https://github.com/ruby/strscan/pull/162) https://github.com/ruby/strscan/commit/a9340ab377 --- doc/strscan/strscan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/strscan/strscan.md b/doc/strscan/strscan.md index 1211a687c271a8..aa7bd8ba847d82 100644 --- a/doc/strscan/strscan.md +++ b/doc/strscan/strscan.md @@ -37,7 +37,7 @@ Some examples here assume that certain helper methods are defined: - `match_values_cleared?(scanner)`: Returns whether the scanner's [match values][9] are cleared. -See examples [here][ext/strscan/helper_methods_md.html]. +See examples at [helper methods](doc/strscan/helper_methods.md). ## The `StringScanner` \Object From 794e4a3a26e33bdc0338a1302f68cfc6fb252eae Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 27 Aug 2025 20:43:20 +0200 Subject: [PATCH 31/36] [ruby/json] Fix a -Wreturn-type warning Fix: https://github.com/ruby/json/pull/843 https://github.com/ruby/json/commit/d3f7f0452b Co-Authored-By: Takashi Kokubun --- ext/json/generator/generator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index d810927c58087c..8fcc980d478f80 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -1846,7 +1846,7 @@ static VALUE cState_allow_duplicate_key_p(VALUE self) return Qtrue; case JSON_DEPRECATED: return Qnil; - case JSON_RAISE: + default: return Qfalse; } } From 3b38ab9b735f9bc66f66764756978c3d37a57a57 Mon Sep 17 00:00:00 2001 From: Scott Myron Date: Fri, 15 Aug 2025 09:23:21 -0500 Subject: [PATCH 32/36] [ruby/json] Ensure the SWAR encoder in the java extension checks every byte. https://github.com/ruby/json/commit/9ebe105144 --- test/json/json_generator_test.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 4315d109d8ec68..6b42de2ad29abe 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -504,6 +504,18 @@ def test_backslash json = '["\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\""]' assert_equal json, generate(data) # + data = '"""""' + json = '"\"\"\"\"\""' + assert_equal json, generate(data) + # + data = "abc\n" + json = '"abc\\n"' + assert_equal json, generate(data) + # + data = "\nabc" + json = '"\\nabc"' + assert_equal json, generate(data) + # data = ["'"] json = '["\\\'"]' assert_equal '["\'"]', generate(data) From 242680efc5aab477b78b1aaa7299feec5d98bb06 Mon Sep 17 00:00:00 2001 From: Robin Miller Date: Mon, 1 Sep 2025 22:40:34 -0600 Subject: [PATCH 33/36] [ruby/json] Added testing for JSON.unsafe_load. Fixes NoMethodError when passing proc to JSON.unsafe_load, matching the changes made in https://github.com/ruby/json/commit/73d2137fd3ad. https://github.com/ruby/json/commit/77292cbc9b --- ext/json/lib/json/common.rb | 10 ++- test/json/json_common_interface_test.rb | 81 +++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb index 45200a83bceda6..f8aefcd2895db8 100644 --- a/ext/json/lib/json/common.rb +++ b/ext/json/lib/json/common.rb @@ -703,9 +703,13 @@ def unsafe_load(source, proc = nil, options = nil) if opts[:allow_blank] && (source.nil? || source.empty?) source = 'null' end - result = parse(source, opts) - recurse_proc(result, &proc) if proc - result + + if proc + opts = opts.dup + opts[:on_load] = proc.to_proc + end + + parse(source, opts) end # :call-seq: diff --git a/test/json/json_common_interface_test.rb b/test/json/json_common_interface_test.rb index 745400faa14d3a..03e1522655902d 100644 --- a/test/json/json_common_interface_test.rb +++ b/test/json/json_common_interface_test.rb @@ -162,6 +162,87 @@ def test_load_null assert_raise(JSON::ParserError) { JSON.load('', nil, :allow_blank => false) } end + def test_unsafe_load + string_able_klass = Class.new do + def initialize(str) + @str = str + end + + def to_str + @str + end + end + + io_able_klass = Class.new do + def initialize(str) + @str = str + end + + def to_io + StringIO.new(@str) + end + end + + assert_equal @hash, JSON.unsafe_load(@json) + tempfile = Tempfile.open('@json') + tempfile.write @json + tempfile.rewind + assert_equal @hash, JSON.unsafe_load(tempfile) + stringio = StringIO.new(@json) + stringio.rewind + assert_equal @hash, JSON.unsafe_load(stringio) + string_able = string_able_klass.new(@json) + assert_equal @hash, JSON.unsafe_load(string_able) + io_able = io_able_klass.new(@json) + assert_equal @hash, JSON.unsafe_load(io_able) + assert_equal nil, JSON.unsafe_load(nil) + assert_equal nil, JSON.unsafe_load('') + ensure + tempfile.close! + end + + def test_unsafe_load_with_proc + visited = [] + JSON.unsafe_load('{"foo": [1, 2, 3], "bar": {"baz": "plop"}}', proc { |o| visited << JSON.dump(o); o }) + + expected = [ + '"foo"', + '1', + '2', + '3', + '[1,2,3]', + '"bar"', + '"baz"', + '"plop"', + '{"baz":"plop"}', + '{"foo":[1,2,3],"bar":{"baz":"plop"}}', + ] + assert_equal expected, visited + end + + def test_unsafe_load_default_options + too_deep = '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' + assert JSON.unsafe_load(too_deep, nil).is_a?(Array) + nan_json = '{ "foo": NaN }' + assert JSON.unsafe_load(nan_json, nil)['foo'].nan? + assert_equal nil, JSON.unsafe_load(nil, nil) + t = Time.now + assert_equal t, JSON.unsafe_load(JSON(t)) + end + + def test_unsafe_load_with_options + nan_json = '{ "foo": NaN }' + assert_raise(JSON::ParserError) { JSON.unsafe_load(nan_json, nil, :allow_nan => false)['foo'].nan? } + # make sure it still uses the defaults when something is provided + assert JSON.unsafe_load(nan_json, nil, :allow_blank => true)['foo'].nan? + end + + def test_unsafe_load_null + assert_equal nil, JSON.unsafe_load(nil, nil, :allow_blank => true) + assert_raise(TypeError) { JSON.unsafe_load(nil, nil, :allow_blank => false) } + assert_raise(JSON::ParserError) { JSON.unsafe_load('', nil, :allow_blank => false) } + end + def test_dump too_deep = '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' obj = eval(too_deep) From 9af5af2c7d97e9563818ecc422ad6c1ff5db3ac1 Mon Sep 17 00:00:00 2001 From: Robin Miller Date: Mon, 1 Sep 2025 22:52:16 -0600 Subject: [PATCH 34/36] [ruby/json] Update method docs for JSON.load and JSON.unsafe_load to show the correct use of proc argument. https://github.com/ruby/json/commit/92654cd99b --- ext/json/lib/json/common.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb index f8aefcd2895db8..ae9b787a007000 100644 --- a/ext/json/lib/json/common.rb +++ b/ext/json/lib/json/common.rb @@ -662,6 +662,7 @@ def pretty_generate(obj, opts = nil) # when Array # obj.map! {|v| deserialize_obj v } # end + # obj # }) # pp ruby # Output: @@ -826,6 +827,7 @@ def unsafe_load(source, proc = nil, options = nil) # when Array # obj.map! {|v| deserialize_obj v } # end + # obj # }) # pp ruby # Output: From 837d74104cf9339a344cf9f33016aa73b2c51e72 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 3 Sep 2025 14:41:10 +0200 Subject: [PATCH 35/36] Use Time.new because JSON.unsafe_load losts fractions of a second and JSON.unsafe_load with Time object returns String ``` 1) Failure: JSONCommonInterfaceTest#test_unsafe_load_default_options [/path/to/ruby/test/json/json_common_interface_test.rb:230]: <2025-09-03 14:28:31.293635 +0200> expected but was <"2025-09-03 14:28:31 +0200">. ``` --- test/json/json_common_interface_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/json/json_common_interface_test.rb b/test/json/json_common_interface_test.rb index 03e1522655902d..077d7e1539666e 100644 --- a/test/json/json_common_interface_test.rb +++ b/test/json/json_common_interface_test.rb @@ -226,8 +226,8 @@ def test_unsafe_load_default_options nan_json = '{ "foo": NaN }' assert JSON.unsafe_load(nan_json, nil)['foo'].nan? assert_equal nil, JSON.unsafe_load(nil, nil) - t = Time.now - assert_equal t, JSON.unsafe_load(JSON(t)) + t = Time.new(2025, 9, 3, 14, 50, 0) + assert_equal t.to_s, JSON.unsafe_load(JSON(t)).to_s end def test_unsafe_load_with_options From 45e183988898136a71dcd66525f941c61a8d647e Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 3 Sep 2025 18:13:08 +0100 Subject: [PATCH 36/36] ZJIT: Handle `opt_case_dispatch` insn (#14433) ZJIT: Handle opt_case_dispatch insn --- test/ruby/test_zjit.rb | 18 ++++++++++++++++++ zjit/src/hir.rs | 6 ++++++ 2 files changed, 24 insertions(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 0353a48eecab72..348907c79b7bec 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -2240,6 +2240,24 @@ def foo = 1 } end + def test_opt_case_dispatch + assert_compiles '[true, false]', %q{ + def test(x) + case x + when :foo + true + else + false + end + end + + results = [] + results << test(:foo) + results << test(1) + results + }, insns: [:opt_case_dispatch] + end + private # Assert that every method call in `test_script` can be compiled by ZJIT diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 8a5dd2e3989515..1e5153b71101da 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -3274,6 +3274,12 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { }); queue.push_back((state.clone(), target, target_idx)); } + YARVINSN_opt_case_dispatch => { + // TODO: Some keys are visible at compile time, so in the future we can + // compile jump targets for certain cases + // Pop the key from the stack and fallback to the === branches for now + state.stack_pop()?; + } YARVINSN_opt_new => { let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); fun.push_insn(block, Insn::CheckInterrupts { state: exit_id });