ruby/lib/bundler/rubygems_ext.rb

374 строки
12 KiB
Ruby

# frozen_string_literal: true
require "pathname"
require "rubygems/specification"
# 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
attr_writer :source
def source
(defined?(@source) && @source) || Gem::Source::Installed.new
end
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)
Pathname.new(loaded_from).dirname.expand_path(source.root).to_s.tap {|x| x.untaint if RUBY_VERSION < "2.7" }
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
alias_method :rg_missing_extensions?, :missing_extensions?
def missing_extensions?
# When we use this methods with local gemspec, we don't handle
# build status of extension correctly. So We need to find extension
# files in require_paths.
# TODO: Gem::Specification couldn't access extension name from extconf.rb
# so we find them with heuristic way. We should improve it.
if source.respond_to?(:root)
return false if raw_require_paths.any? do |path|
ext_dir = File.join(full_gem_path, path)
File.exist?(File.join(ext_dir, "#{name}.#{RbConfig::CONFIG["DLEXT"]}")) ||
!Dir.glob(File.join(ext_dir, name, "*.#{RbConfig::CONFIG["DLEXT"]}")).empty?
end
end
rg_missing_extensions?
end
remove_method :gem_dir if instance_methods(false).include?(: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
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
# Backfill missing YAML require when not defined. Fixed since 3.1.0.pre1.
module YamlBackfiller
def to_yaml(opts = {})
Gem.load_yaml unless defined?(::YAML)
super(opts)
end
end
prepend YamlBackfiller
def nondevelopment_dependencies
dependencies - development_dependencies
end
def deleted_gem?
!default_gem? && !File.directory?(full_gem_path)
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
class Dependency
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)
end
end
def to_yaml_properties
instance_variables.reject {|p| ["@source", "@groups"].include?(p.to_s) }
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
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)
return unless Gem::Requirement === other
if _requirements_sorted? && other._requirements_sorted?
super
else
_with_sorted_requirements == other._with_sorted_requirements
end
end
protected
def _requirements_sorted?
return @_requirements_sorted if defined?(@_requirements_sorted)
strings = as_list
@_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
if Gem::Requirement.new("~> 2.0").hash == Gem::Requirement.new("~> 2.0.0").hash
class Requirement
module CorrectHashForLambdaOperator
def hash
if requirements.any? {|r| r.first == "~>" }
requirements.map {|r| r.first == "~>" ? [r[0], r[1].to_s] : r }.sort.hash
else
super
end
end
end
prepend CorrectHashForLambdaOperator
end
end
require "rubygems/platform"
class Platform
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?
without_gnu_nor_abi_modifiers
end
end
if RUBY_ENGINE == "truffleruby" && !defined?(REUSE_AS_BINARY_ON_TRUFFLERUBY)
REUSE_AS_BINARY_ON_TRUFFLERUBY = %w[libv8 libv8-node sorbet-static].freeze
end
end
Platform.singleton_class.module_eval do
unless Platform.singleton_methods.include?(:match_spec?)
def match_spec?(spec)
match_gem?(spec.platform, spec.name)
end
def match_gem?(platform, gem_name)
match_platforms?(platform, Gem.platforms)
end
end
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
# On universal Rubies, resolve the "universal" arch to the real CPU arch, without changing the extension directory.
class Specification
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
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/util"
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
end
def glob_files_in_dir(glob, base_path)
Dir.glob(glob, :base => base_path).map! {|f| File.expand_path(f, base_path) }
end
end
end