From 7d7a84e99fca816ec352c95965687db240fb56d3 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 23 Dec 2023 09:08:54 +0900 Subject: [PATCH] Merge RubyGems-3.5.3 and Bundler-2.5.3 --- lib/bundler/dependency.rb | 4 +++ lib/bundler/dsl.rb | 19 ++++++---- lib/bundler/version.rb | 2 +- lib/rubygems.rb | 2 +- lib/rubygems/safe_marshal/elements.rb | 8 +++++ lib/rubygems/safe_marshal/reader.rb | 4 ++- lib/rubygems/safe_marshal/visitors/to_ruby.rb | 30 ++++++++++++++++ spec/bundler/commands/install_spec.rb | 29 +++++++++++++++ test/rubygems/test_gem_safe_marshal.rb | 36 +++++++++++++++++-- 9 files changed, 122 insertions(+), 12 deletions(-) diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb index efc39a12f3efe9..77d7a00362b931 100644 --- a/lib/bundler/dependency.rb +++ b/lib/bundler/dependency.rb @@ -68,6 +68,10 @@ def should_include? @should_include && current_env? && current_platform? end + def gemspec_dev_dep? + type == :development + end + def current_env? return true unless @env if @env.is_a?(Hash) diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index a67f7ed8a1ecb7..1eca749617a0da 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -103,16 +103,21 @@ def gem(name, *args) # if there's already a dependency with this name we try to prefer one if current = @dependencies.find {|d| d.name == dep.name } # Always prefer the dependency from the Gemfile - deleted_dep = @dependencies.delete(current) if current.type == :development + @dependencies.delete(current) if current.gemspec_dev_dep? if current.requirement != dep.requirement current_requirement_open = current.requirements_list.include?(">= 0") - if current.type == :development - unless current_requirement_open || dep.type == :development - Bundler.ui.warn "A gemspec development dependency (#{dep.name}, #{current.requirement}) is being overridden by a Gemfile dependency (#{dep.name}, #{dep.requirement}).\n" \ - "This behaviour may change in the future. Please remove either of them, or make sure they both have the same requirement\n" \ + gemspec_dep = [dep, current].find(&:gemspec_dev_dep?) + if gemspec_dep + gemfile_dep = [dep, current].find(&:runtime?) + + unless current_requirement_open + Bundler.ui.warn "A gemspec development dependency (#{gemspec_dep.name}, #{gemspec_dep.requirement}) is being overridden by a Gemfile dependency (#{gemfile_dep.name}, #{gemfile_dep.requirement}).\n" \ + "This behaviour may change in the future. Please remove either of them, or make sure they both have the same requirement\n" end + + return if dep.gemspec_dev_dep? else update_prompt = "" @@ -130,8 +135,8 @@ def gem(name, *args) "You specified: #{current.name} (#{current.requirement}) and #{dep.name} (#{dep.requirement})" \ "#{update_prompt}" end - elsif current.type == :development || dep.type == :development - return if deleted_dep.nil? + elsif current.gemspec_dev_dep? || dep.gemspec_dev_dep? + return if dep.gemspec_dev_dep? elsif current.source != dep.source raise GemfileError, "You cannot specify the same gem twice coming from different sources.\n" \ "You specified that #{dep.name} (#{dep.requirement}) should come from " \ diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index a98d7fab30d7f7..a0ef931d07991c 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "2.5.2".freeze + VERSION = "2.5.3".freeze def self.bundler_major_version @bundler_major_version ||= VERSION.split(".").first.to_i diff --git a/lib/rubygems.rb b/lib/rubygems.rb index fd5683f1be0cff..fa3da5cc1a4caf 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "3.5.2" + VERSION = "3.5.3" end # Must be first since it unloads the prelude from 1.9.2 diff --git a/lib/rubygems/safe_marshal/elements.rb b/lib/rubygems/safe_marshal/elements.rb index 067ab59d19d6e4..f8874b1b2fbccd 100644 --- a/lib/rubygems/safe_marshal/elements.rb +++ b/lib/rubygems/safe_marshal/elements.rb @@ -133,6 +133,14 @@ def initialize(sign, data) end attr_reader :sign, :data end + + class UserClass < Element + def initialize(name, wrapped_object) + @name = name + @wrapped_object = wrapped_object + end + attr_reader :name, :wrapped_object + end end end end diff --git a/lib/rubygems/safe_marshal/reader.rb b/lib/rubygems/safe_marshal/reader.rb index 7c3a703475baa2..740be113e5a217 100644 --- a/lib/rubygems/safe_marshal/reader.rb +++ b/lib/rubygems/safe_marshal/reader.rb @@ -299,7 +299,9 @@ def read_struct end def read_user_class - raise NotImplementedError, "Reading Marshal objects of type user_class is not implemented" + name = read_element + wrapped_object = read_element + Elements::UserClass.new(name, wrapped_object) end end end diff --git a/lib/rubygems/safe_marshal/visitors/to_ruby.rb b/lib/rubygems/safe_marshal/visitors/to_ruby.rb index 58c44fa8bf2789..a9f1d048d492b8 100644 --- a/lib/rubygems/safe_marshal/visitors/to_ruby.rb +++ b/lib/rubygems/safe_marshal/visitors/to_ruby.rb @@ -247,6 +247,30 @@ def visit_Gem_SafeMarshal_Elements_Bignum(b) end end + def visit_Gem_SafeMarshal_Elements_UserClass(r) + if resolve_class(r.name) == ::Hash && r.wrapped_object.is_a?(Elements::Hash) + + hash = register_object({}.compare_by_identity) + + o = r.wrapped_object + o.pairs.each_with_index do |(k, v), i| + push_stack i + k = visit(k) + push_stack k + hash[k] = visit(v) + end + + if o.is_a?(Elements::HashWithDefaultValue) + push_stack :default + hash.default = visit(o.default) + end + + hash + else + raise UnsupportedError.new("Unsupported user class #{resolve_class(r.name)} in marshal stream", stack: formatted_stack) + end + end + def resolve_class(n) @class_cache[n] ||= begin to_s = resolve_symbol_name(n) @@ -375,6 +399,12 @@ def initialize(name:, stack:) end end + class UnsupportedError < Error + def initialize(message, stack:) + super "#{message} @ #{stack.join "."}" + end + end + class FormatError < Error end diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index aa7c54ce4b43fd..7c016ff3d8a145 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -460,6 +460,35 @@ expect(the_bundle).to include_gems("rubocop 1.37.1") end + it "warns when a Gemfile dependency is overriding a gemspec development dependency, with different requirements" do + build_lib "my-gem", path: bundled_app do |s| + s.add_development_dependency "rails", ">= 5" + end + + build_repo4 do + build_gem "rails", "7.0.8" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "rails", "~> 7.0.8" + + gemspec + G + + bundle :install + + expect(err).to include("A gemspec development dependency (rails, >= 5) is being overridden by a Gemfile dependency (rails, ~> 7.0.8).") + expect(err).to include("This behaviour may change in the future. Please remove either of them, or make sure they both have the same requirement") + + # This is not the best behavior I believe, it would be better if both + # requirements are considered if they are compatible, and a version + # satisfying both is chosen. But not sure about changing it right now, so + # I went with a warning for the time being. + expect(the_bundle).to include_gems("rails 7.0.8") + end + it "does not warn if a gem is added once in Gemfile and also inside a gemspec as a development dependency, with same requirements, and different sources" do build_lib "my-gem", path: bundled_app do |s| s.add_development_dependency "activesupport" diff --git a/test/rubygems/test_gem_safe_marshal.rb b/test/rubygems/test_gem_safe_marshal.rb index 29685f458c893a..7f56d8d2903597 100644 --- a/test/rubygems/test_gem_safe_marshal.rb +++ b/test/rubygems/test_gem_safe_marshal.rb @@ -247,9 +247,41 @@ def test_hash_with_default_value end def test_hash_with_compare_by_identity - pend "`read_user_class` not yet implemented" + with_const(Gem::SafeMarshal, :PERMITTED_CLASSES, %w[Hash]) do + assert_safe_load_as Hash.new.compare_by_identity.tap {|h| + h[+"a"] = 1 + h[+"a"] = 2 }, additional_methods: [:compare_by_identity?], equality: false + assert_safe_load_as Hash.new.compare_by_identity, additional_methods: [:compare_by_identity?] + assert_safe_load_as Hash.new(0).compare_by_identity.tap {|h| + h[+"a"] = 1 + h[+"a"] = 2 }, additional_methods: [:compare_by_identity?, :default], equality: false + end + end + + class StringSubclass < ::String + end - assert_safe_load_as Hash.new.compare_by_identity + def test_string_subclass + with_const(Gem::SafeMarshal, :PERMITTED_CLASSES, [StringSubclass.name]) do + with_const(Gem::SafeMarshal, :PERMITTED_IVARS, { StringSubclass.name => %w[E] }) do + e = assert_raise(Gem::SafeMarshal::Visitors::ToRuby::UnsupportedError) do + Gem::SafeMarshal.safe_load Marshal.dump StringSubclass.new("abc") + end + assert_equal "Unsupported user class #{StringSubclass.name} in marshal stream @ root.object", e.message + end + end + end + + class ArraySubclass < ::Array + end + + def test_array_subclass + with_const(Gem::SafeMarshal, :PERMITTED_CLASSES, [ArraySubclass.name]) do + e = assert_raise(Gem::SafeMarshal::Visitors::ToRuby::UnsupportedError) do + Gem::SafeMarshal.safe_load(Marshal.dump(ArraySubclass.new << "abc")) + end + assert_equal "Unsupported user class #{ArraySubclass.name} in marshal stream @ root", e.message + end end def test_frozen_object