[rubygems/rubygems] Allow disabling RubyGems require decorations

Currently Bundler needs to do cumbersome operations to revert custom
RubyGems require on a `bundler/setup` context. This causes issues when
third party gems also monkeypatch require, since Bundler will also undo
those decorations.

This commit allows it to use the simpler approach of properly telling
RubyGems that it needs to default to built-in require without any extra
magic.

https://github.com/rubygems/rubygems/commit/1df5009e14

Co-authored-by: Xavier Noria <fxn@hashref.com>
This commit is contained in:
David Rodríguez 2023-01-30 18:10:56 +01:00 committed by Hiroshi SHIBATA
parent 022acb9593
commit 4cbfd87e5a
Notes: git 2023-01-31 01:49:29 +00:00
2 changed files with 138 additions and 124 deletions

View File

@ -181,6 +181,8 @@ module Gem
@default_source_date_epoch = nil
@discover_gems_on_require = true
##
# Try to activate a gem containing +path+. Returns true if
# activation succeeded or wasn't needed because it was already
@ -1162,8 +1164,16 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
# RubyGems distributors (like operating system package managers) can
# disable RubyGems update by setting this to error message printed to
# end-users on gem update --system instead of actual update.
attr_accessor :disable_system_update_message
##
# Whether RubyGems should enhance builtin `require` to automatically
# check whether the path required is present in installed gems, and
# automatically activate them and add them to `$LOAD_PATH`.
attr_accessor :discover_gems_on_require
##
# Hash of loaded Gem::Specification keyed by name

View File

@ -34,137 +34,141 @@ module Kernel
# that file has already been loaded is preserved.
def require(path) # :doc:
if RUBYGEMS_ACTIVATION_MONITOR.respond_to?(:mon_owned?)
monitor_owned = RUBYGEMS_ACTIVATION_MONITOR.mon_owned?
end
RUBYGEMS_ACTIVATION_MONITOR.enter
return gem_original_require(path) unless Gem.discover_gems_on_require
path = path.to_path if path.respond_to? :to_path
if spec = Gem.find_unresolved_default_spec(path)
# Ensure -I beats a default gem
resolved_path = begin
rp = nil
load_path_check_index = Gem.load_path_insert_index - Gem.activated_gem_paths
Gem.suffixes.each do |s|
$LOAD_PATH[0...load_path_check_index].each do |lp|
safe_lp = lp.dup.tap(&Gem::UNTAINT)
begin
if File.symlink? safe_lp # for backward compatibility
next
end
rescue SecurityError
RUBYGEMS_ACTIVATION_MONITOR.exit
raise
end
full_path = File.expand_path(File.join(safe_lp, "#{path}#{s}"))
if File.file?(full_path)
rp = full_path
break
end
end
break if rp
end
rp
begin
if RUBYGEMS_ACTIVATION_MONITOR.respond_to?(:mon_owned?)
monitor_owned = RUBYGEMS_ACTIVATION_MONITOR.mon_owned?
end
begin
Kernel.send(:gem, spec.name, Gem::Requirement.default_prerelease)
rescue Exception
RUBYGEMS_ACTIVATION_MONITOR.exit
raise
end unless resolved_path
end
# If there are no unresolved deps, then we can use just try
# normal require handle loading a gem from the rescue below.
if Gem::Specification.unresolved_deps.empty?
RUBYGEMS_ACTIVATION_MONITOR.exit
return gem_original_require(path)
end
# If +path+ is for a gem that has already been loaded, don't
# bother trying to find it in an unresolved gem, just go straight
# to normal require.
#--
# TODO request access to the C implementation of this to speed up RubyGems
if Gem::Specification.find_active_stub_by_path(path)
RUBYGEMS_ACTIVATION_MONITOR.exit
return gem_original_require(path)
end
# Attempt to find +path+ in any unresolved gems...
found_specs = Gem::Specification.find_in_unresolved path
# If there are no directly unresolved gems, then try and find +path+
# in any gems that are available via the currently unresolved gems.
# For example, given:
#
# a => b => c => d
#
# If a and b are currently active with c being unresolved and d.rb is
# requested, then find_in_unresolved_tree will find d.rb in d because
# it's a dependency of c.
#
if found_specs.empty?
found_specs = Gem::Specification.find_in_unresolved_tree path
found_specs.each do |found_spec|
found_spec.activate
end
# We found +path+ directly in an unresolved gem. Now we figure out, of
# the possible found specs, which one we should activate.
else
# Check that all the found specs are just different
# versions of the same gem
names = found_specs.map(&:name).uniq
if names.size > 1
RUBYGEMS_ACTIVATION_MONITOR.exit
raise Gem::LoadError, "#{path} found in multiple gems: #{names.join ', '}"
end
# Ok, now find a gem that has no conflicts, starting
# at the highest version.
valid = found_specs.find {|s| !s.has_conflicts? }
unless valid
le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate"
le.name = names.first
RUBYGEMS_ACTIVATION_MONITOR.exit
raise le
end
valid.activate
end
RUBYGEMS_ACTIVATION_MONITOR.exit
return gem_original_require(path)
rescue LoadError => load_error
if load_error.path == path
RUBYGEMS_ACTIVATION_MONITOR.enter
begin
require_again = Gem.try_activate(path)
ensure
RUBYGEMS_ACTIVATION_MONITOR.exit
path = path.to_path if path.respond_to? :to_path
if spec = Gem.find_unresolved_default_spec(path)
# Ensure -I beats a default gem
resolved_path = begin
rp = nil
load_path_check_index = Gem.load_path_insert_index - Gem.activated_gem_paths
Gem.suffixes.each do |s|
$LOAD_PATH[0...load_path_check_index].each do |lp|
safe_lp = lp.dup.tap(&Gem::UNTAINT)
begin
if File.symlink? safe_lp # for backward compatibility
next
end
rescue SecurityError
RUBYGEMS_ACTIVATION_MONITOR.exit
raise
end
full_path = File.expand_path(File.join(safe_lp, "#{path}#{s}"))
if File.file?(full_path)
rp = full_path
break
end
end
break if rp
end
rp
end
begin
Kernel.send(:gem, spec.name, Gem::Requirement.default_prerelease)
rescue Exception
RUBYGEMS_ACTIVATION_MONITOR.exit
raise
end unless resolved_path
end
return gem_original_require(path) if require_again
end
# If there are no unresolved deps, then we can use just try
# normal require handle loading a gem from the rescue below.
raise load_error
ensure
if RUBYGEMS_ACTIVATION_MONITOR.respond_to?(:mon_owned?)
if monitor_owned != (ow = RUBYGEMS_ACTIVATION_MONITOR.mon_owned?)
STDERR.puts [$$, Thread.current, $!, $!.backtrace].inspect if $!
raise "CRITICAL: RUBYGEMS_ACTIVATION_MONITOR.owned?: before #{monitor_owned} -> after #{ow}"
if Gem::Specification.unresolved_deps.empty?
RUBYGEMS_ACTIVATION_MONITOR.exit
return gem_original_require(path)
end
# If +path+ is for a gem that has already been loaded, don't
# bother trying to find it in an unresolved gem, just go straight
# to normal require.
#--
# TODO request access to the C implementation of this to speed up RubyGems
if Gem::Specification.find_active_stub_by_path(path)
RUBYGEMS_ACTIVATION_MONITOR.exit
return gem_original_require(path)
end
# Attempt to find +path+ in any unresolved gems...
found_specs = Gem::Specification.find_in_unresolved path
# If there are no directly unresolved gems, then try and find +path+
# in any gems that are available via the currently unresolved gems.
# For example, given:
#
# a => b => c => d
#
# If a and b are currently active with c being unresolved and d.rb is
# requested, then find_in_unresolved_tree will find d.rb in d because
# it's a dependency of c.
#
if found_specs.empty?
found_specs = Gem::Specification.find_in_unresolved_tree path
found_specs.each do |found_spec|
found_spec.activate
end
# We found +path+ directly in an unresolved gem. Now we figure out, of
# the possible found specs, which one we should activate.
else
# Check that all the found specs are just different
# versions of the same gem
names = found_specs.map(&:name).uniq
if names.size > 1
RUBYGEMS_ACTIVATION_MONITOR.exit
raise Gem::LoadError, "#{path} found in multiple gems: #{names.join ', '}"
end
# Ok, now find a gem that has no conflicts, starting
# at the highest version.
valid = found_specs.find {|s| !s.has_conflicts? }
unless valid
le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate"
le.name = names.first
RUBYGEMS_ACTIVATION_MONITOR.exit
raise le
end
valid.activate
end
RUBYGEMS_ACTIVATION_MONITOR.exit
return gem_original_require(path)
rescue LoadError => load_error
if load_error.path == path
RUBYGEMS_ACTIVATION_MONITOR.enter
begin
require_again = Gem.try_activate(path)
ensure
RUBYGEMS_ACTIVATION_MONITOR.exit
end
return gem_original_require(path) if require_again
end
raise load_error
ensure
if RUBYGEMS_ACTIVATION_MONITOR.respond_to?(:mon_owned?)
if monitor_owned != (ow = RUBYGEMS_ACTIVATION_MONITOR.mon_owned?)
STDERR.puts [$$, Thread.current, $!, $!.backtrace].inspect if $!
raise "CRITICAL: RUBYGEMS_ACTIVATION_MONITOR.owned?: before #{monitor_owned} -> after #{ow}"
end
end
end
end