diff options
Diffstat (limited to 'lib/bundler/rubygems_ext.rb')
-rw-r--r-- | lib/bundler/rubygems_ext.rb | 242 |
1 files changed, 178 insertions, 64 deletions
diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index 2bd2dcb451..e0582beba2 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -2,16 +2,39 @@ require "pathname" +require "rubygems" unless defined?(Gem) + require "rubygems/specification" -# Possible use in Gem::Specification#source below and require -# shouldn't be deferred. +# We can't let `Gem::Source` be autoloaded in the `Gem::Specification#source` +# redefinition below, so we need to load it upfront. The reason is that if +# Bundler monkeypatches are loaded before RubyGems activates an executable (for +# example, through `ruby -rbundler -S irb`), gem activation might end up calling +# the redefined `Gem::Specification#source` and triggering the `Gem::Source` +# autoload. That would result in requiring "rubygems/source" inside another +# require, which would trigger a monitor error and cause the `autoload` to +# eventually fail. A better solution is probably to completely avoid autoloading +# `Gem::Source` from the redefined `Gem::Specification#source`. require "rubygems/source" +require_relative "match_metadata" +require_relative "force_platform" require_relative "match_platform" +# Cherry-pick fixes to `Gem.ruby_version` to be useful for modern Bundler +# versions and ignore patchlevels +# (https://github.com/rubygems/rubygems/pull/5472, +# https://github.com/rubygems/rubygems/pull/5486). May be removed once RubyGems +# 3.3.12 support is dropped. +unless Gem.ruby_version.to_s == RUBY_VERSION || RUBY_PATCHLEVEL == -1 + Gem.instance_variable_set(:@ruby_version, Gem::Version.new(RUBY_VERSION)) +end + module Gem class Specification + include ::Bundler::MatchMetadata + include ::Bundler::MatchPlatform + attr_accessor :remote, :location, :relative_loaded_from remove_method :source @@ -24,12 +47,8 @@ module Gem alias_method :rg_loaded_from, :loaded_from def full_gem_path - # this cannot check source.is_a?(Bundler::Plugin::API::Source) - # because that _could_ trip the autoload, and if there are unresolved - # gems at that time, this method could be called inside another require, - # thus raising with that constant being undefined. Better to check a method - if source.respond_to?(:path) || (source.respond_to?(:bundler_plugin_api_source?) && source.bundler_plugin_api_source?) - Pathname.new(loaded_from).dirname.expand_path(source.root).to_s.tap{|x| x.untaint if RUBY_VERSION < "2.7" } + if source.respond_to?(:root) + Pathname.new(loaded_from).dirname.expand_path(source.root).to_s else rg_full_gem_path end @@ -49,7 +68,9 @@ module Gem alias_method :rg_extension_dir, :extension_dir def extension_dir - @bundler_extension_dir ||= if source.respond_to?(:extension_dir_name) + # following instance variable is already used in original method + # and that is the reason to prefix it with bundler_ and add rubocop exception + @bundler_extension_dir ||= if source.respond_to?(:extension_dir_name) # rubocop:disable Naming/MemoizedInstanceVariableName unique_extension_dir = [source.extension_dir_name, File.basename(full_gem_path)].uniq.join("-") File.expand_path(File.join(extensions_dir, unique_extension_dir)) else @@ -57,11 +78,28 @@ module Gem end end - remove_method :gem_dir if instance_methods(false).include?(:gem_dir) + remove_method :gem_dir def gem_dir full_gem_path end + unless const_defined?(:LATEST_RUBY_WITHOUT_PATCH_VERSIONS) + LATEST_RUBY_WITHOUT_PATCH_VERSIONS = Gem::Version.new("2.1") + + alias_method :rg_required_ruby_version=, :required_ruby_version= + def required_ruby_version=(req) + self.rg_required_ruby_version = req + + @required_ruby_version.requirements.map! do |op, v| + if v >= LATEST_RUBY_WITHOUT_PATCH_VERSIONS && v.release.segments.size == 4 + [op == "~>" ? "=" : op, Gem::Version.new(v.segments.tap {|s| s.delete_at(3) }.join("."))] + else + [op, v] + end + end + end + end + def groups @groups ||= [] end @@ -85,6 +123,10 @@ module Gem dependencies - development_dependencies end + def deleted_gem? + !default_gem? && !File.directory?(full_gem_path) + end + private def dependencies_to_gemfile(dependencies, group = nil) @@ -105,10 +147,18 @@ module Gem end class Dependency - attr_accessor :source, :groups, :all_sources + include ::Bundler::ForcePlatform + + attr_accessor :source, :groups alias_method :eql?, :== + def force_ruby_platform + return @force_ruby_platform if defined?(@force_ruby_platform) && !@force_ruby_platform.nil? + + @force_ruby_platform = default_force_ruby_platform + end + def encode_with(coder) to_yaml_properties.each do |ivar| coder[ivar.to_s.sub(/^@/, "")] = instance_variable_get(ivar) @@ -116,7 +166,7 @@ module Gem end def to_yaml_properties - instance_variables.reject {|p| ["@source", "@groups", "@all_sources"].include?(p.to_s) } + instance_variables.reject {|p| ["@source", "@groups"].include?(p.to_s) } end def to_lock @@ -129,35 +179,7 @@ module Gem end end - # comparison is done order independently since rubygems 3.2.0.rc.2 - unless Gem::Requirement.new("> 1", "< 2") == Gem::Requirement.new("< 2", "> 1") - class Requirement - module OrderIndependentComparison - def ==(other) - if _requirements_sorted? && other._requirements_sorted? - super - else - _with_sorted_requirements == other._with_sorted_requirements - end - end - - protected - - def _requirements_sorted? - return @_are_requirements_sorted if defined?(@_are_requirements_sorted) - strings = as_list - @_are_requirements_sorted = strings == strings.sort - end - - def _with_sorted_requirements - @_with_sorted_requirements ||= _requirements_sorted? ? self : self.class.new(as_list.sort) - end - end - - prepend OrderIndependentComparison - end - end - + # Requirements using lambda operator differentiate trailing zeros since rubygems 3.2.6 if Gem::Requirement.new("~> 2.0").hash == Gem::Requirement.new("~> 2.0.0").hash class Requirement module CorrectHashForLambdaOperator @@ -174,41 +196,133 @@ module Gem end end + require "rubygems/platform" + class Platform - JAVA = Gem::Platform.new("java") unless defined?(JAVA) - MSWIN = Gem::Platform.new("mswin32") unless defined?(MSWIN) - MSWIN64 = Gem::Platform.new("mswin64") unless defined?(MSWIN64) - MINGW = Gem::Platform.new("x86-mingw32") unless defined?(MINGW) - X64_MINGW = Gem::Platform.new("x64-mingw32") unless defined?(X64_MINGW) + JAVA = Gem::Platform.new("java") + MSWIN = Gem::Platform.new("mswin32") + MSWIN64 = Gem::Platform.new("mswin64") + MINGW = Gem::Platform.new("x86-mingw32") + X64_MINGW = [Gem::Platform.new("x64-mingw32"), + Gem::Platform.new("x64-mingw-ucrt")].freeze + WINDOWS = [MSWIN, MSWIN64, MINGW, X64_MINGW].flatten.freeze + X64_LINUX = Gem::Platform.new("x86_64-linux") + X64_LINUX_MUSL = Gem::Platform.new("x86_64-linux-musl") + + if X64_LINUX === X64_LINUX_MUSL + remove_method :=== + + def ===(other) + return nil unless Gem::Platform === other + + # universal-mingw32 matches x64-mingw-ucrt + return true if (@cpu == "universal" || other.cpu == "universal") && + @os.start_with?("mingw") && other.os.start_with?("mingw") + + # cpu + ([nil,"universal"].include?(@cpu) || [nil, "universal"].include?(other.cpu) || @cpu == other.cpu || + (@cpu == "arm" && other.cpu.start_with?("arm"))) && + + # os + @os == other.os && + + # version + ( + (@os != "linux" && (@version.nil? || other.version.nil?)) || + (@os == "linux" && (normalized_linux_version_ext == other.normalized_linux_version_ext || ["musl#{@version}", "musleabi#{@version}", "musleabihf#{@version}"].include?(other.version))) || + @version == other.version + ) + end + + # This is a copy of RubyGems 3.3.23 or higher `normalized_linux_method`. + # Once only 3.3.23 is supported, we can use the method in RubyGems. + def normalized_linux_version_ext + return nil unless @version + + without_gnu_nor_abi_modifiers = @version.sub(/\Agnu/, "").sub(/eabi(hf)?\Z/, "") + return nil if without_gnu_nor_abi_modifiers.empty? - undef_method :hash if method_defined? :hash - def hash - @cpu.hash ^ @os.hash ^ @version.hash + without_gnu_nor_abi_modifiers + end end - undef_method :eql? if method_defined? :eql? - alias_method :eql?, :== + if RUBY_ENGINE == "truffleruby" && !defined?(REUSE_AS_BINARY_ON_TRUFFLERUBY) + REUSE_AS_BINARY_ON_TRUFFLERUBY = %w[libv8 libv8-node sorbet-static].freeze + end end - require "rubygems/util" + Platform.singleton_class.module_eval do + unless Platform.singleton_methods.include?(:match_spec?) + def match_spec?(spec) + match_gem?(spec.platform, spec.name) + end - Util.singleton_class.module_eval do - if Util.singleton_methods.include?(:glob_files_in_dir) # since 3.0.0.beta.2 - remove_method :glob_files_in_dir + def match_gem?(platform, gem_name) + match_platforms?(platform, Gem.platforms) + end end - def glob_files_in_dir(glob, base_path) - if RUBY_VERSION >= "2.5" - Dir.glob(glob, :base => base_path).map! {|f| File.expand_path(f, base_path) } - else - Dir.glob(File.join(base_path.to_s.gsub(/[\[\]]/, '\\\\\\&'), glob)).map! {|f| File.expand_path(f) } + match_platforms_defined = Gem::Platform.respond_to?(:match_platforms?, true) + + if !match_platforms_defined || Gem::Platform.send(:match_platforms?, Gem::Platform::X64_LINUX_MUSL, [Gem::Platform::X64_LINUX]) + + private + + remove_method :match_platforms? if match_platforms_defined + + def match_platforms?(platform, platforms) + platforms.any? do |local_platform| + platform.nil? || + local_platform == platform || + (local_platform != Gem::Platform::RUBY && platform =~ local_platform) + end end end end -end -module Gem - class Specification - include ::Bundler::MatchPlatform + # On universal Rubies, resolve the "universal" arch to the real CPU arch, without changing the extension directory. + class BasicSpecification + if /^universal\.(?<arch>.*?)-/ =~ (CROSS_COMPILING || RUBY_PLATFORM) + local_platform = Platform.local + if local_platform.cpu == "universal" + ORIGINAL_LOCAL_PLATFORM = local_platform.to_s.freeze + + local_platform.cpu = if arch == "arm64e" # arm64e is only permitted for Apple system binaries + "arm64" + else + arch + end + + def extensions_dir + @extensions_dir ||= + Gem.default_ext_dir_for(base_dir) || File.join(base_dir, "extensions", ORIGINAL_LOCAL_PLATFORM, Gem.extension_api_version) + end + end + end + end + + require "rubygems/name_tuple" + + class NameTuple + # Versions of RubyGems before about 3.5.0 don't to_s the platform. + unless Gem::NameTuple.new("a", Gem::Version.new("1"), Gem::Platform.new("x86_64-linux")).platform.is_a?(String) + alias_method :initialize_with_platform, :initialize + + def initialize(name, version, platform=Gem::Platform::RUBY) + if Gem::Platform === platform + initialize_with_platform(name, version, platform.to_s) + else + initialize_with_platform(name, version, platform) + end + end + end + + def lock_name + if platform == Gem::Platform::RUBY + "#{name} (#{version})" + else + "#{name} (#{version}-#{platform})" + end + end end end |