summaryrefslogtreecommitdiff
path: root/lib/bundler/rubygems_ext.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bundler/rubygems_ext.rb')
-rw-r--r--lib/bundler/rubygems_ext.rb242
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