mirror of
https://github.com/ruby/ruby.git
synced 2026-01-26 04:07:58 +00:00
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.
272 lines
8.2 KiB
Ruby
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
|