This commit is contained in:
@@ -0,0 +1,503 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rubygems" unless defined?(Gem)
|
||||
|
||||
# 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"
|
||||
|
||||
module Gem
|
||||
# Can be removed once RubyGems 3.5.11 support is dropped
|
||||
unless Gem.respond_to?(:freebsd_platform?)
|
||||
def self.freebsd_platform?
|
||||
RbConfig::CONFIG["host_os"].to_s.include?("bsd")
|
||||
end
|
||||
end
|
||||
|
||||
# Can be removed once RubyGems 3.5.18 support is dropped
|
||||
unless Gem.respond_to?(:open_file_with_lock)
|
||||
class << self
|
||||
remove_method :open_file_with_flock if Gem.respond_to?(:open_file_with_flock)
|
||||
|
||||
def open_file_with_flock(path, &block)
|
||||
# read-write mode is used rather than read-only in order to support NFS
|
||||
mode = IO::RDWR | IO::APPEND | IO::CREAT | IO::BINARY
|
||||
mode |= IO::SHARE_DELETE if IO.const_defined?(:SHARE_DELETE)
|
||||
|
||||
File.open(path, mode) do |io|
|
||||
begin
|
||||
io.flock(File::LOCK_EX)
|
||||
rescue Errno::ENOSYS, Errno::ENOTSUP
|
||||
end
|
||||
yield io
|
||||
end
|
||||
end
|
||||
|
||||
def open_file_with_lock(path, &block)
|
||||
file_lock = "#{path}.lock"
|
||||
open_file_with_flock(file_lock, &block)
|
||||
ensure
|
||||
FileUtils.rm_f file_lock
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require "rubygems/platform"
|
||||
|
||||
class Platform
|
||||
# Can be removed once RubyGems 3.6.9 support is dropped
|
||||
unless respond_to?(:generic)
|
||||
JAVA = Gem::Platform.new("java") # :nodoc:
|
||||
MSWIN = Gem::Platform.new("mswin32") # :nodoc:
|
||||
MSWIN64 = Gem::Platform.new("mswin64") # :nodoc:
|
||||
MINGW = Gem::Platform.new("x86-mingw32") # :nodoc:
|
||||
X64_MINGW_LEGACY = Gem::Platform.new("x64-mingw32") # :nodoc:
|
||||
X64_MINGW = Gem::Platform.new("x64-mingw-ucrt") # :nodoc:
|
||||
UNIVERSAL_MINGW = Gem::Platform.new("universal-mingw") # :nodoc:
|
||||
WINDOWS = [MSWIN, MSWIN64, UNIVERSAL_MINGW].freeze # :nodoc:
|
||||
X64_LINUX = Gem::Platform.new("x86_64-linux") # :nodoc:
|
||||
X64_LINUX_MUSL = Gem::Platform.new("x86_64-linux-musl") # :nodoc:
|
||||
|
||||
GENERICS = [JAVA, *WINDOWS].freeze # :nodoc:
|
||||
private_constant :GENERICS
|
||||
|
||||
GENERIC_CACHE = GENERICS.each_with_object({}) {|g, h| h[g] = g } # :nodoc:
|
||||
private_constant :GENERIC_CACHE
|
||||
|
||||
class << self
|
||||
##
|
||||
# Returns the generic platform for the given platform.
|
||||
|
||||
def generic(platform)
|
||||
return Gem::Platform::RUBY if platform.nil? || platform == Gem::Platform::RUBY
|
||||
|
||||
GENERIC_CACHE[platform] ||= begin
|
||||
found = GENERICS.find do |match|
|
||||
platform === match
|
||||
end
|
||||
found || Gem::Platform::RUBY
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the platform specificity match for the given spec platform and user platform.
|
||||
|
||||
def platform_specificity_match(spec_platform, user_platform)
|
||||
return -1 if spec_platform == user_platform
|
||||
return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY
|
||||
|
||||
os_match(spec_platform, user_platform) +
|
||||
cpu_match(spec_platform, user_platform) * 10 +
|
||||
version_match(spec_platform, user_platform) * 100
|
||||
end
|
||||
|
||||
##
|
||||
# Sorts and filters the best platform match for the given matching specs and platform.
|
||||
|
||||
def sort_and_filter_best_platform_match(matching, platform)
|
||||
return matching if matching.one?
|
||||
|
||||
exact = matching.select {|spec| spec.platform == platform }
|
||||
return exact if exact.any?
|
||||
|
||||
sorted_matching = sort_best_platform_match(matching, platform)
|
||||
exemplary_spec = sorted_matching.first
|
||||
|
||||
sorted_matching.take_while {|spec| same_specificity?(platform, spec, exemplary_spec) && same_deps?(spec, exemplary_spec) }
|
||||
end
|
||||
|
||||
##
|
||||
# Sorts the best platform match for the given matching specs and platform.
|
||||
|
||||
def sort_best_platform_match(matching, platform)
|
||||
matching.sort_by.with_index do |spec, i|
|
||||
[
|
||||
platform_specificity_match(spec.platform, platform),
|
||||
i, # for stable sort
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def same_specificity?(platform, spec, exemplary_spec)
|
||||
platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform)
|
||||
end
|
||||
|
||||
def same_deps?(spec, exemplary_spec)
|
||||
spec.required_ruby_version == exemplary_spec.required_ruby_version &&
|
||||
spec.required_rubygems_version == exemplary_spec.required_rubygems_version &&
|
||||
spec.dependencies.sort == exemplary_spec.dependencies.sort
|
||||
end
|
||||
|
||||
def os_match(spec_platform, user_platform)
|
||||
if spec_platform.os == user_platform.os
|
||||
0
|
||||
else
|
||||
1
|
||||
end
|
||||
end
|
||||
|
||||
def cpu_match(spec_platform, user_platform)
|
||||
if spec_platform.cpu == user_platform.cpu
|
||||
0
|
||||
elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm")
|
||||
0
|
||||
elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal"
|
||||
1
|
||||
else
|
||||
2
|
||||
end
|
||||
end
|
||||
|
||||
def version_match(spec_platform, user_platform)
|
||||
if spec_platform.version == user_platform.version
|
||||
0
|
||||
elsif spec_platform.version.nil?
|
||||
1
|
||||
else
|
||||
2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
require "rubygems/specification"
|
||||
|
||||
# Can be removed once RubyGems 3.5.14 support is dropped
|
||||
VALIDATES_FOR_RESOLUTION = Specification.new.respond_to?(:validate_for_resolution).freeze
|
||||
|
||||
class Specification
|
||||
# Can be removed once RubyGems 3.5.15 support is dropped
|
||||
correct_array_attributes = @@default_value.select {|_k,v| v.is_a?(Array) }.keys
|
||||
unless @@array_attributes == correct_array_attributes
|
||||
@@array_attributes = correct_array_attributes # rubocop:disable Style/ClassVars
|
||||
end
|
||||
|
||||
require_relative "match_metadata"
|
||||
require_relative "match_platform"
|
||||
|
||||
include ::Bundler::MatchMetadata
|
||||
|
||||
attr_accessor :remote, :relative_loaded_from
|
||||
|
||||
module AllowSettingSource
|
||||
attr_writer :source
|
||||
|
||||
def source
|
||||
(defined?(@source) && @source) || super
|
||||
end
|
||||
end
|
||||
|
||||
prepend AllowSettingSource
|
||||
|
||||
alias_method :rg_full_gem_path, :full_gem_path
|
||||
alias_method :rg_loaded_from, :loaded_from
|
||||
|
||||
def full_gem_path
|
||||
if source.respond_to?(:root)
|
||||
File.expand_path(File.dirname(loaded_from), source.root)
|
||||
else
|
||||
rg_full_gem_path
|
||||
end
|
||||
end
|
||||
|
||||
def loaded_from
|
||||
if relative_loaded_from
|
||||
source.path.join(relative_loaded_from).to_s
|
||||
else
|
||||
rg_loaded_from
|
||||
end
|
||||
end
|
||||
|
||||
def load_paths
|
||||
full_require_paths
|
||||
end
|
||||
|
||||
alias_method :rg_extension_dir, :extension_dir
|
||||
def extension_dir
|
||||
# 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
|
||||
rg_extension_dir
|
||||
end
|
||||
end
|
||||
|
||||
# Can be removed once RubyGems 3.5.21 support is dropped
|
||||
remove_method :gem_dir if method_defined?(:gem_dir, false)
|
||||
|
||||
def gem_dir
|
||||
full_gem_path
|
||||
end
|
||||
|
||||
def insecurely_materialized?
|
||||
false
|
||||
end
|
||||
|
||||
def groups
|
||||
@groups ||= []
|
||||
end
|
||||
|
||||
def git_version
|
||||
return unless loaded_from && source.is_a?(Bundler::Source::Git)
|
||||
" #{source.revision[0..6]}"
|
||||
end
|
||||
|
||||
def to_gemfile(path = nil)
|
||||
gemfile = String.new("source 'https://rubygems.org'\n")
|
||||
gemfile << dependencies_to_gemfile(nondevelopment_dependencies)
|
||||
unless development_dependencies.empty?
|
||||
gemfile << "\n"
|
||||
gemfile << dependencies_to_gemfile(development_dependencies, :development)
|
||||
end
|
||||
gemfile
|
||||
end
|
||||
|
||||
def nondevelopment_dependencies
|
||||
dependencies - development_dependencies
|
||||
end
|
||||
|
||||
def installation_missing?
|
||||
!default_gem? && !File.directory?(full_gem_path)
|
||||
end
|
||||
|
||||
def lock_name
|
||||
@lock_name ||= name_tuple.lock_name
|
||||
end
|
||||
|
||||
unless VALIDATES_FOR_RESOLUTION
|
||||
def validate_for_resolution
|
||||
SpecificationPolicy.new(self).validate_for_resolution
|
||||
end
|
||||
end
|
||||
|
||||
if Gem.rubygems_version < Gem::Version.new("3.5.22")
|
||||
module FixPathSourceMissingExtensions
|
||||
def missing_extensions?
|
||||
return false if %w[Bundler::Source::Path Bundler::Source::Gemspec].include?(source.class.name)
|
||||
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
prepend FixPathSourceMissingExtensions
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dependencies_to_gemfile(dependencies, group = nil)
|
||||
gemfile = String.new
|
||||
if dependencies.any?
|
||||
gemfile << "group :#{group} do\n" if group
|
||||
dependencies.each do |dependency|
|
||||
gemfile << " " if group
|
||||
gemfile << %(gem "#{dependency.name}")
|
||||
req = dependency.requirements_list.first
|
||||
gemfile << %(, "#{req}") if req
|
||||
gemfile << "\n"
|
||||
end
|
||||
gemfile << "end\n" if group
|
||||
end
|
||||
gemfile
|
||||
end
|
||||
end
|
||||
|
||||
unless VALIDATES_FOR_RESOLUTION
|
||||
class SpecificationPolicy
|
||||
def validate_for_resolution
|
||||
validate_required!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module BetterPermissionError
|
||||
def data
|
||||
super
|
||||
rescue Errno::EACCES
|
||||
raise Bundler::PermissionError.new(loaded_from, :read)
|
||||
end
|
||||
end
|
||||
|
||||
require "rubygems/stub_specification"
|
||||
|
||||
class StubSpecification
|
||||
prepend BetterPermissionError
|
||||
end
|
||||
|
||||
class Dependency
|
||||
require_relative "force_platform"
|
||||
|
||||
include ::Bundler::ForcePlatform
|
||||
|
||||
attr_reader :force_ruby_platform
|
||||
|
||||
attr_accessor :source, :groups
|
||||
|
||||
alias_method :eql?, :==
|
||||
|
||||
unless method_defined?(:encode_with, false)
|
||||
def encode_with(coder)
|
||||
[:@name, :@requirement, :@type, :@prerelease, :@version_requirements].each do |ivar|
|
||||
coder[ivar.to_s.sub(/^@/, "")] = instance_variable_get(ivar)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to_lock
|
||||
out = String.new(" #{name}")
|
||||
unless requirement.none?
|
||||
reqs = requirement.requirements.map {|o, v| "#{o} #{v}" }.sort.reverse
|
||||
out << " (#{reqs.join(", ")})"
|
||||
end
|
||||
out
|
||||
end
|
||||
|
||||
if Gem.rubygems_version < Gem::Version.new("3.5.22")
|
||||
module FilterIgnoredSpecs
|
||||
def matching_specs(platform_only = false)
|
||||
super.reject(&:ignored?)
|
||||
end
|
||||
end
|
||||
|
||||
prepend FilterIgnoredSpecs
|
||||
end
|
||||
end
|
||||
|
||||
# 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
|
||||
|
||||
# Can be removed once RubyGems 3.5.22 support is dropped
|
||||
unless new.respond_to?(:ignored?)
|
||||
def ignored?
|
||||
return @ignored unless @ignored.nil?
|
||||
|
||||
@ignored = missing_extensions?
|
||||
end
|
||||
end
|
||||
|
||||
# Can be removed once RubyGems 3.6.9 support is dropped
|
||||
unless new.respond_to?(:installable_on_platform?)
|
||||
include(::Bundler::MatchPlatform)
|
||||
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
|
||||
|
||||
unless Gem.rubygems_version >= Gem::Version.new("3.5.19")
|
||||
class Resolver::ActivationRequest
|
||||
remove_method :installed?
|
||||
|
||||
def installed?
|
||||
case @spec
|
||||
when Gem::Resolver::VendorSpecification then
|
||||
true
|
||||
else
|
||||
this_spec = full_spec
|
||||
|
||||
Gem::Specification.any? do |s|
|
||||
s == this_spec && s.base_dir == this_spec.base_dir
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Gem.rubygems_version >= Gem::Version.new("3.6.7")
|
||||
module UnfreezeCompactIndexParsedResponse
|
||||
def parse(line)
|
||||
version, platform, dependencies, requirements = super
|
||||
[version, platform, dependencies.frozen? ? dependencies.dup : dependencies, requirements.frozen? ? requirements.dup : requirements]
|
||||
end
|
||||
end
|
||||
|
||||
Resolver::APISet::GemParser.prepend(UnfreezeCompactIndexParsedResponse)
|
||||
end
|
||||
|
||||
# RubyGems before 4.0.13 split compact index dependency/requirement entries
|
||||
# on every colon, which mangles metadata values that contain colons such as
|
||||
# the `created_at` timestamps the cooldown feature relies on. Split only on
|
||||
# the first colon so those values survive on older RubyGems.
|
||||
#
|
||||
# The module is defined unconditionally so it stays testable on any RubyGems,
|
||||
# but only prepended when the host RubyGems still has the buggy behavior.
|
||||
module SplitCompactIndexEntryOnFirstColon
|
||||
private
|
||||
|
||||
def parse_dependency(string)
|
||||
dependency = string.split(":", 2)
|
||||
dependency[-1] = dependency[-1].split("&") if dependency.size > 1
|
||||
dependency[0] = -dependency[0]
|
||||
dependency
|
||||
end
|
||||
end
|
||||
|
||||
unless Gem.rubygems_version >= Gem::Version.new("4.0.13")
|
||||
Resolver::APISet::GemParser.prepend(SplitCompactIndexEntryOnFirstColon)
|
||||
end
|
||||
|
||||
if Gem.rubygems_version < Gem::Version.new("3.6.0")
|
||||
class Package; end
|
||||
require "rubygems/package/tar_reader"
|
||||
require "rubygems/package/tar_reader/entry"
|
||||
|
||||
module FixFullNameEncoding
|
||||
def full_name
|
||||
super.force_encoding(Encoding::UTF_8)
|
||||
end
|
||||
end
|
||||
|
||||
Package::TarReader::Entry.prepend(FixFullNameEncoding)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user