ruby/lib/bundled_gems.rb
Daisuke Aritomo ca9c61800b Suppress bundled gem warning on `binding.irb'
This patch silences the "this won't work in the next version of Ruby"
warning displayed when irb is autoloaded via `binding.irb`.

    main.rb:1: warning: irb used to be loaded from the standard library, but is not part of the default gems since Ruby 4.0.0.
    You can add irb to your Gemfile or gemspec to fix this error.
    /.../irb.rb:9: warning: reline used to be loaded from the standard library, but is not part of the default gems since Ruby 4.0.0.
    You can add reline to your Gemfile or gemspec to fix this error.

    From: main.rb @ line 1 :

     => 1: binding.irb

    /.../input-method.rb:284: warning: rdoc used to be loaded from the standard library, but is not part of the default gems since Ruby 4.0.0.
    You can add rdoc to your Gemfile or gemspec to fix this error.

This warning is incorrect and misleading: users should not need to
include irb (and its dependencies) to their Gemfiles to use
`binding.irb`, even in future versions of Ruby. It is agreed that the
runtime takes care of that.
2026-01-15 17:20:34 +09:00

272 lines
8.2 KiB
Ruby

# -*- frozen-string-literal: true -*-
# :stopdoc:
module Gem
end
# :startdoc:
module Gem::BUNDLED_GEMS # :nodoc:
SINCE = {
"racc" => "3.3.0",
"abbrev" => "3.4.0",
"base64" => "3.4.0",
"bigdecimal" => "3.4.0",
"csv" => "3.4.0",
"drb" => "3.4.0",
"getoptlong" => "3.4.0",
"mutex_m" => "3.4.0",
"nkf" => "3.4.0",
"observer" => "3.4.0",
"resolv-replace" => "3.4.0",
"rinda" => "3.4.0",
"syslog" => "3.4.0",
"ostruct" => "4.0.0",
"pstore" => "4.0.0",
"rdoc" => "4.0.0",
"win32ole" => "4.0.0",
"fiddle" => "4.0.0",
"logger" => "4.0.0",
"benchmark" => "4.0.0",
"irb" => "4.0.0",
"reline" => "4.0.0",
# "readline" => "4.0.0", # This is wrapper for reline. We don't warn for this.
"tsort" => "4.1.0",
}.freeze
EXACT = {
"kconv" => "nkf",
}.freeze
WARNED = {} # unfrozen
conf = ::RbConfig::CONFIG
LIBDIR = (conf["rubylibdir"] + "/").freeze
ARCHDIR = (conf["rubyarchdir"] + "/").freeze
dlext = [conf["DLEXT"], "so"].uniq
DLEXT = /\.#{Regexp.union(dlext)}\z/
LIBEXT = /\.#{Regexp.union("rb", *dlext)}\z/
def self.replace_require(specs)
return if [::Kernel.singleton_class, ::Kernel].any? {|klass| klass.respond_to?(:no_warning_require) }
spec_names = specs.to_a.each_with_object({}) {|spec, h| h[spec.name] = true }
[::Kernel.singleton_class, ::Kernel].each do |kernel_class|
kernel_class.send(:alias_method, :no_warning_require, :require)
kernel_class.send(:define_method, :require) do |name|
if message = ::Gem::BUNDLED_GEMS.warning?(name, specs: spec_names)
Kernel.warn message, uplevel: ::Gem::BUNDLED_GEMS.uplevel
end
kernel_class.send(:no_warning_require, name)
end
if kernel_class == ::Kernel
kernel_class.send(:private, :require)
else
kernel_class.send(:public, :require)
end
end
end
def self.uplevel
frame_count = 0
require_labels = ["replace_require", "require"]
uplevel = 0
require_found = false
Thread.each_caller_location do |cl|
frame_count += 1
if require_found
unless require_labels.include?(cl.base_label)
return uplevel
end
else
if require_labels.include?(cl.base_label)
require_found = true
end
end
uplevel += 1
# Don't show script name when bundle exec and call ruby script directly.
if cl.path.end_with?("bundle")
return
end
end
require_found ? 1 : (frame_count - 1).nonzero?
end
def self.warning?(name, specs: nil)
# name can be a feature name or a file path with String or Pathname
feature = File.path(name).sub(LIBEXT, "")
# The actual checks needed to properly identify the gem being required
# are costly (see [Bug #20641]), so we first do a much cheaper check
# to exclude the vast majority of candidates.
subfeature = if feature.include?("/")
# bootsnap expands `require "csv"` to `require "#{LIBDIR}/csv.rb"`,
# and `require "syslog"` to `require "#{ARCHDIR}/syslog.so"`.
feature.delete_prefix!(ARCHDIR)
feature.delete_prefix!(LIBDIR)
# 1. A segment for the EXACT mapping and SINCE check
# 2. A segment for the SINCE check for dashed names
# 3. A segment to check if there's a subfeature
segments = feature.split("/", 3)
name = segments.shift
name = EXACT[name] || name
if !SINCE[name]
name = "#{name}-#{segments.shift}"
return unless SINCE[name]
end
segments.any?
else
name = EXACT[feature] || feature
return unless SINCE[name]
false
end
if suppress_list = Thread.current[:__bundled_gems_warning_suppression]
return if suppress_list.include?(name) || suppress_list.include?(feature)
end
return if specs.include?(name)
# Don't warn if a hyphenated gem provides this feature
# (e.g., benchmark-ips provides benchmark/ips, not the benchmark gem)
if subfeature
feature_parts = feature.split("/")
if feature_parts.size >= 2
hyphenated_gem = "#{feature_parts[0]}-#{feature_parts[1]}"
return if specs.include?(hyphenated_gem)
end
end
return if WARNED[name]
WARNED[name] = true
level = RUBY_VERSION < SINCE[name] ? :warning : :error
if subfeature
"#{feature} is found in #{name}, which"
else
"#{feature} #{level == :warning ? "was loaded" : "used to be loaded"} from the standard library, but"
end + build_message(name, level)
end
def self.build_message(name, level)
msg = if level == :warning
" will no longer be part of the default gems starting from Ruby #{SINCE[name]}"
else
" is not part of the default gems since Ruby #{SINCE[name]}."
end
if defined?(Bundler)
motivation = level == :warning ? "silence this warning" : "fix this error"
msg += "\nYou can add #{name} to your Gemfile or gemspec to #{motivation}."
# We detect the gem name from caller_locations. First we walk until we find `require`
# then take the first frame that's not from `require`.
#
# Additionally, we need to skip Bootsnap and Zeitwerk if present, these
# gems decorate Kernel#require, so they are not really the ones issuing
# the require call users should be warned about. Those are upwards.
frames_to_skip = 3
location = nil
require_found = false
Thread.each_caller_location do |cl|
if frames_to_skip >= 1
frames_to_skip -= 1
next
end
if require_found
if cl.base_label != "require"
location = cl.path
break
end
else
if cl.base_label == "require"
require_found = true
end
end
end
if location && File.file?(location) && !location.start_with?(Gem::BUNDLED_GEMS::LIBDIR)
caller_gem = nil
Gem.path.each do |path|
if location =~ %r{#{path}/gems/([\w\-\.]+)}
caller_gem = $1
break
end
end
if caller_gem
msg += "\nAlso please contact the author of #{caller_gem} to request adding #{name} into its gemspec."
end
end
else
msg += " Install #{name} from RubyGems."
end
msg
end
def self.force_activate(gem)
require "bundler"
Bundler.reset!
# Build and activate a temporary definition containing the original gems + the requested gem
builder = Bundler::Dsl.new
lockfile = nil
if Bundler::SharedHelpers.in_bundle? && Bundler.definition.gemfiles.size > 0
Bundler.definition.gemfiles.each {|gemfile| builder.eval_gemfile(gemfile) }
lockfile = begin
Bundler.default_lockfile
rescue Bundler::GemfileNotFound
nil
end
else
# Fake BUNDLE_GEMFILE and BUNDLE_LOCKFILE to let checks pass
orig_gemfile = ENV["BUNDLE_GEMFILE"]
orig_lockfile = ENV["BUNDLE_LOCKFILE"]
Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile"
Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", "Gemfile.lock"
end
builder.gem gem
definition = builder.to_definition(lockfile, nil)
definition.validate_runtime!
begin
orig_ui = Bundler.ui
orig_no_lock = Bundler::Definition.no_lock
ui = Bundler::UI::Shell.new
ui.level = "silent"
Bundler.ui = ui
Bundler::Definition.no_lock = true
Bundler::Runtime.new(nil, definition).setup
rescue Bundler::GemNotFound
warn "Failed to activate #{gem}, please install it with 'gem install #{gem}'"
ensure
ENV['BUNDLE_GEMFILE'] = orig_gemfile if orig_gemfile
ENV['BUNDLE_LOCKFILE'] = orig_lockfile if orig_lockfile
Bundler.ui = orig_ui
Bundler::Definition.no_lock = orig_no_lock
end
end
end
# for RubyGems without Bundler environment.
# If loading library is not part of the default gems and the bundled gems, warn it.
class LoadError
def message # :nodoc:
return super unless path
name = path.tr("/", "-")
if !defined?(Bundler) && Gem::BUNDLED_GEMS::SINCE[name] && !Gem::BUNDLED_GEMS::WARNED[name]
warn name + Gem::BUNDLED_GEMS.build_message(name, :error), uplevel: Gem::BUNDLED_GEMS.uplevel
end
super
end
end