ruby/lib/rubygems/command.rb

661 строка
16 KiB
Ruby
Исходник Обычный вид История

# frozen_string_literal: true
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'optparse'
Fix `ruby setup.rb` warnings Currently we get the following warnings on `ruby setup.rb`: ``` /home/deivid/.rbenv/versions/2.7.1/lib/ruby/site_ruby/2.7.0/rubygems/exceptions.rb:281: warning: already initialized constant Gem::UnsatisfiableDepedencyError /home/deivid/Code/rubygems/lib/rubygems/exceptions.rb:281: warning: previous definition of UnsatisfiableDepedencyError was here /home/deivid/.rbenv/versions/2.7.1/lib/ruby/site_ruby/2.7.0/rubygems/user_interaction.rb:557: warning: already initialized constant Gem::StreamUI::ThreadedDownloadReporter::MUTEX /home/deivid/Code/rubygems/lib/rubygems/user_interaction.rb:557: warning: previous definition of MUTEX was here /home/deivid/.rbenv/versions/2.7.1/lib/ruby/site_ruby/2.7.0/rubygems/ext/builder.rb:20: warning: already initialized constant Gem::Ext::Builder::CHDIR_MUTEX /home/deivid/Code/rubygems/lib/rubygems/ext/builder.rb:20: warning: previous definition of CHDIR_MUTEX was here /home/deivid/.rbenv/versions/2.7.1/lib/ruby/site_ruby/2.7.0/rubygems/ext/ext_conf_builder.rb:14: warning: already initialized constant Gem::Ext::ExtConfBuilder::FileEntry /home/deivid/Code/rubygems/lib/rubygems/ext/ext_conf_builder.rb:14: warning: previous definition of FileEntry was here /home/deivid/.rbenv/versions/2.7.1/lib/ruby/site_ruby/2.7.0/rubygems/version.rb:158: warning: already initialized constant Gem::Version::VERSION_PATTERN /home/deivid/Code/rubygems/lib/rubygems/version.rb:158: warning: previous definition of VERSION_PATTERN was here /home/deivid/.rbenv/versions/2.7.1/lib/ruby/site_ruby/2.7.0/rubygems/version.rb:159: warning: already initialized constant Gem::Version::ANCHORED_VERSION_PATTERN /home/deivid/Code/rubygems/lib/rubygems/version.rb:159: warning: previous definition of ANCHORED_VERSION_PATTERN was here /home/deivid/.rbenv/versions/2.7.1/lib/ruby/site_ruby/2.7.0/rubygems/requirement.rb:14: warning: already initialized constant Gem::Requirement::OPS /home/deivid/Code/rubygems/lib/rubygems/requirement.rb:14: warning: previous definition of OPS was here /home/deivid/.rbenv/versions/2.7.1/lib/ruby/site_ruby/2.7.0/rubygems/requirement.rb:24: warning: already initialized constant Gem::Requirement::SOURCE_SET_REQUIREMENT /home/deivid/Code/rubygems/lib/rubygems/requirement.rb:24: warning: previous definition of SOURCE_SET_REQUIREMENT was here /home/deivid/.rbenv/versions/2.7.1/lib/ruby/site_ruby/2.7.0/rubygems/requirement.rb:27: warning: already initialized constant Gem::Requirement::PATTERN_RAW /home/deivid/Code/rubygems/lib/rubygems/requirement.rb:27: warning: previous definition of PATTERN_RAW was here /home/deivid/.rbenv/versions/2.7.1/lib/ruby/site_ruby/2.7.0/rubygems/requirement.rb:32: warning: already initialized constant Gem::Requirement::PATTERN /home/deivid/Code/rubygems/lib/rubygems/requirement.rb:32: warning: previous definition of PATTERN was here /home/deivid/.rbenv/versions/2.7.1/lib/ruby/site_ruby/2.7.0/rubygems/requirement.rb:37: warning: already initialized constant Gem::Requirement::DefaultRequirement /home/deivid/Code/rubygems/lib/rubygems/requirement.rb:37: warning: previous definition of DefaultRequirement was here /home/deivid/.rbenv/versions/2.7.1/lib/ruby/site_ruby/2.7.0/rubygems/requirement.rb:42: warning: already initialized constant Gem::Requirement::DefaultPrereleaseRequirement /home/deivid/Code/rubygems/lib/rubygems/requirement.rb:42: warning: previous definition of DefaultPrereleaseRequirement was here /home/deivid/.rbenv/versions/2.7.1/lib/ruby/site_ruby/2.7.0/rubygems/requirement.rb:311: warning: already initialized constant Gem::Version::Requirement /home/deivid/Code/rubygems/lib/rubygems/requirement.rb:311: warning: previous definition of Requirement was here /home/deivid/.rbenv/versions/2.7.1/lib/ruby/site_ruby/2.7.0/rubygems/command.rb:626: warning: already initialized constant Gem::Command::HELP /home/deivid/Code/rubygems/lib/rubygems/command.rb:626: warning: previous definition of HELP was here Successfully built RubyGem Name: bundler Version: 2.2.0.dev File: bundler-2.2.0.dev.gem Bundler 2.2.0.dev installed RubyGems 3.2.0.pre1 installed Regenerating binstubs Regenerating plugins ------------------------------------------------------------------------------ RubyGems installed the following executables: /home/deivid/.rbenv/versions/2.7.1/bin/gem /home/deivid/.rbenv/versions/2.7.1/bin/bundle ``` This is because the `$LOAD_PATH` entry added by `setup.rb` is relatively and when the offending require happens, we're installing `bundler` and have switched folders to `bundler/`. So the require fallsback to the system rubygems. To avoid that, add an absolute path to the `$LOAD_PATH`. On jruby, somehow the $LOAD_PATH is manipulated so that we end up requiring stuff inside the built package even if we have specified the `-I` flag, so we get redefinition warnings anyways. I'm not sure about the root cause, but relative requiring fixes it, and it's faster anyways.
2020-05-16 16:42:27 +03:00
require_relative 'requirement'
require_relative 'user_interaction'
##
# Base class for all Gem commands. When creating a new gem command, define
# #initialize, #execute, #arguments, #defaults_str, #description and #usage
# (as appropriate). See the above mentioned methods for details.
#
# A very good example to look at is Gem::Commands::ContentsCommand
class Gem::Command
include Gem::UserInteraction
OptionParser.accept Symbol do |value|
value.to_sym
end
##
# The name of the command.
attr_reader :command
##
# The options for the command.
attr_reader :options
##
# The default options for the command.
attr_accessor :defaults
##
# The name of the command for command-line invocation.
attr_accessor :program_name
##
# A short description of the command.
attr_accessor :summary
##
# Arguments used when building gems
def self.build_args
@build_args ||= []
end
def self.build_args=(value)
@build_args = value
end
def self.common_options
@common_options ||= []
end
def self.add_common_option(*args, &handler)
Gem::Command.common_options << [args, handler]
end
def self.extra_args
@extra_args ||= []
end
def self.extra_args=(value)
case value
when Array
@extra_args = value
when String
@extra_args = value.split(' ')
end
end
##
# Return an array of extra arguments for the command. The extra arguments
# come from the gem configuration file read at program startup.
def self.specific_extra_args(cmd)
specific_extra_args_hash[cmd]
end
##
# Add a list of extra arguments for the given command. +args+ may be an
# array or a string to be split on white space.
def self.add_specific_extra_args(cmd,args)
args = args.split(/\s+/) if args.kind_of? String
specific_extra_args_hash[cmd] = args
end
##
# Accessor for the specific extra args hash (self initializing).
def self.specific_extra_args_hash
@specific_extra_args_hash ||= Hash.new do |h,k|
h[k] = Array.new
end
end
##
# Initializes a generic gem command named +command+. +summary+ is a short
# description displayed in `gem help commands`. +defaults+ are the default
# options. Defaults should be mirrored in #defaults_str, unless there are
# none.
#
# When defining a new command subclass, use add_option to add command-line
# switches.
#
# Unhandled arguments (gem names, files, etc.) are left in
# <tt>options[:args]</tt>.
def initialize(command, summary=nil, defaults={})
@command = command
@summary = summary
@program_name = "gem #{command}"
@defaults = defaults
@options = defaults.dup
@option_groups = Hash.new {|h,k| h[k] = [] }
@deprecated_options = { command => {} }
@parser = nil
@when_invoked = nil
end
##
# True if +long+ begins with the characters from +short+.
def begins?(long, short)
return false if short.nil?
long[0, short.length] == short
end
##
# Override to provide command handling.
#
# #options will be filled in with your parsed options, unparsed options will
# be left in <tt>options[:args]</tt>.
#
# See also: #get_all_gem_names, #get_one_gem_name,
# #get_one_optional_argument
def execute
raise Gem::Exception, "generic command has no actions"
end
##
# Display to the user that a gem couldn't be found and reasons why
#--
def show_lookup_failure(gem_name, version, errors, suppress_suggestions = false, required_by = nil)
gem = "'#{gem_name}' (#{version})"
msg = String.new "Could not find a valid gem #{gem}"
if errors and !errors.empty?
msg << ", here is why:\n"
errors.each {|x| msg << " #{x.wordy}\n" }
else
if required_by and gem != required_by
msg << " (required by #{required_by}) in any repository"
else
msg << " in any repository"
end
end
alert_error msg
unless suppress_suggestions
suggestions = Gem::SpecFetcher.fetcher.suggest_gems_from_name(gem_name, :latest, 10)
unless suggestions.empty?
alert_error "Possible alternatives: #{suggestions.join(", ")}"
end
end
end
##
# Get all gem names from the command line.
def get_all_gem_names
args = options[:args]
if args.nil? or args.empty?
raise Gem::CommandLineError,
"Please specify at least one gem name (e.g. gem build GEMNAME)"
end
args.select {|arg| arg !~ /^-/ }
end
##
# Get all [gem, version] from the command line.
#
# An argument in the form gem:ver is pull apart into the gen name and version,
# respectively.
def get_all_gem_names_and_versions
get_all_gem_names.map do |name|
if /\A(.*):(#{Gem::Requirement::PATTERN_RAW})\z/ =~ name
[$1, $2]
else
[name]
end
end
end
##
# Get a single gem name from the command line. Fail if there is no gem name
# or if there is more than one gem name given.
def get_one_gem_name
args = options[:args]
if args.nil? or args.empty?
raise Gem::CommandLineError,
"Please specify a gem name on the command line (e.g. gem build GEMNAME)"
end
if args.size > 1
raise Gem::CommandLineError,
"Too many gem names (#{args.join(', ')}); please specify only one"
end
args.first
end
##
# Get a single optional argument from the command line. If more than one
# argument is given, return only the first. Return nil if none are given.
def get_one_optional_argument
args = options[:args] || []
args.first
end
##
# Override to provide details of the arguments a command takes. It should
# return a left-justified string, one argument per line.
#
# For example:
#
# def usage
# "#{program_name} FILE [FILE ...]"
# end
#
# def arguments
# "FILE name of file to find"
# end
def arguments
""
end
##
# Override to display the default values of the command options. (similar to
# +arguments+, but displays the default values).
#
# For example:
#
# def defaults_str
# --no-gems-first --no-all
# end
def defaults_str
""
end
##
# Override to display a longer description of what this command does.
def description
nil
end
##
# Override to display the usage for an individual gem command.
#
# The text "[options]" is automatically appended to the usage text.
def usage
program_name
end
##
# Display the help message for the command.
def show_help
parser.program_name = usage
say parser
end
##
# Invoke the command with the given list of arguments.
def invoke(*args)
invoke_with_build_args args, nil
end
##
# Invoke the command with the given list of normal arguments
# and additional build arguments.
def invoke_with_build_args(args, build_args)
handle_options args
options[:build_args] = build_args
if options[:silent]
old_ui = self.ui
self.ui = ui = Gem::SilentUI.new
end
if options[:help]
show_help
elsif @when_invoked
@when_invoked.call options
else
execute
end
ensure
if ui
self.ui = old_ui
ui.close
end
end
##
# Call the given block when invoked.
#
# Normal command invocations just executes the +execute+ method of the
# command. Specifying an invocation block allows the test methods to
# override the normal action of a command to determine that it has been
# invoked correctly.
def when_invoked(&block)
@when_invoked = block
end
##
# Add a command-line option and handler to the command.
#
# See OptionParser#make_switch for an explanation of +opts+.
#
# +handler+ will be called with two values, the value of the argument and
# the options hash.
#
# If the first argument of add_option is a Symbol, it's used to group
# options in output. See `gem help list` for an example.
def add_option(*opts, &handler) # :yields: value, options
group_name = Symbol === opts.first ? opts.shift : :options
@option_groups[group_name] << [opts, handler]
end
##
# Remove previously defined command-line argument +name+.
def remove_option(name)
@option_groups.each do |_, option_list|
option_list.reject! {|args, _| args.any? {|x| x.is_a?(String) && x =~ /^#{name}/ } }
end
end
##
# Mark a command-line option as deprecated, and optionally specify a
# deprecation horizon.
#
# Note that with the current implementation, every version of the option needs
# to be explicitly deprecated, so to deprecate an option defined as
#
# add_option('-t', '--[no-]test', 'Set test mode') do |value, options|
# # ... stuff ...
# end
#
# you would need to explicitly add a call to `deprecate_option` for every
# version of the option you want to deprecate, like
#
# deprecate_option('-t')
# deprecate_option('--test')
# deprecate_option('--no-test')
def deprecate_option(name, version: nil, extra_msg: nil)
@deprecated_options[command].merge!({ name => { "rg_version_to_expire" => version, "extra_msg" => extra_msg } })
end
def check_deprecated_options(options)
options.each do |option|
if option_is_deprecated?(option)
deprecation = @deprecated_options[command][option]
version_to_expire = deprecation["rg_version_to_expire"]
deprecate_option_msg = if version_to_expire
"The \"#{option}\" option has been deprecated and will be removed in Rubygems #{version_to_expire}."
else
"The \"#{option}\" option has been deprecated and will be removed in future versions of Rubygems."
end
extra_msg = deprecation["extra_msg"]
deprecate_option_msg += " #{extra_msg}" if extra_msg
alert_warning(deprecate_option_msg)
end
end
end
##
# Merge a set of command options with the set of default options (without
# modifying the default option hash).
def merge_options(new_options)
@options = @defaults.clone
new_options.each {|k,v| @options[k] = v }
end
##
# True if the command handles the given argument list.
def handles?(args)
begin
parser.parse!(args.dup)
return true
rescue
return false
end
end
##
# Handle the given list of arguments by parsing them and recording the
# results.
def handle_options(args)
args = add_extra_args(args)
check_deprecated_options(args)
@options = Marshal.load Marshal.dump @defaults # deep copy
parser.parse!(args)
@options[:args] = args
end
##
# Adds extra args from ~/.gemrc
def add_extra_args(args)
result = []
s_extra = Gem::Command.specific_extra_args(@command)
extra = Gem::Command.extra_args + s_extra
until extra.empty? do
ex = []
ex << extra.shift
ex << extra.shift if extra.first.to_s =~ /^[^-]/ # rubocop:disable Performance/StartWith
result << ex if handles?(ex)
end
result.flatten!
result.concat(args)
result
end
def deprecated?
false
end
private
def option_is_deprecated?(option)
@deprecated_options[command].has_key?(option)
end
def add_parser_description # :nodoc:
return unless description
formatted = description.split("\n\n").map do |chunk|
wrap chunk, 80 - 4
end.join "\n"
@parser.separator nil
@parser.separator " Description:"
formatted.split("\n").each do |line|
@parser.separator " #{line.rstrip}"
end
end
def add_parser_options # :nodoc:
@parser.separator nil
regular_options = @option_groups.delete :options
configure_options "", regular_options
@option_groups.sort_by {|n,_| n.to_s }.each do |group_name, option_list|
@parser.separator nil
configure_options group_name, option_list
end
end
##
# Adds a section with +title+ and +content+ to the parser help view. Used
# for adding command arguments and default arguments.
def add_parser_run_info(title, content)
return if content.empty?
@parser.separator nil
@parser.separator " #{title}:"
content.split(/\n/).each do |line|
@parser.separator " #{line}"
end
end
def add_parser_summary # :nodoc:
return unless @summary
@parser.separator nil
@parser.separator " Summary:"
wrap(@summary, 80 - 4).split("\n").each do |line|
@parser.separator " #{line.strip}"
end
end
##
# Create on demand parser.
def parser
create_option_parser if @parser.nil?
@parser
end
##
# Creates an option parser and fills it in with the help info for the
# command.
def create_option_parser
@parser = OptionParser.new
add_parser_options
@parser.separator nil
configure_options "Common", Gem::Command.common_options
add_parser_run_info "Arguments", arguments
add_parser_summary
add_parser_description
add_parser_run_info "Defaults", defaults_str
end
def configure_options(header, option_list)
return if option_list.nil? or option_list.empty?
header = header.to_s.empty? ? '' : "#{header} "
@parser.separator " #{header}Options:"
option_list.each do |args, handler|
@parser.on(*args) do |value|
handler.call(value, @options)
end
end
@parser.separator ''
end
##
# Wraps +text+ to +width+
def wrap(text, width) # :doc:
text.gsub(/(.{1,#{width}})( +|$\n?)|(.{1,#{width}})/, "\\1\\3\n")
end
# ----------------------------------------------------------------
# Add the options common to all commands.
add_common_option('-h', '--help',
'Get help on this command') do |value, options|
options[:help] = true
end
add_common_option('-V', '--[no-]verbose',
'Set the verbose level of output') do |value, options|
# Set us to "really verbose" so the progress meter works
if Gem.configuration.verbose and value
Gem.configuration.verbose = 1
else
Gem.configuration.verbose = value
end
end
add_common_option('-q', '--quiet', 'Silence command progress meter') do |value, options|
Gem.configuration.verbose = false
end
add_common_option("--silent",
"Silence RubyGems output") do |value, options|
options[:silent] = true
end
# Backtrace and config-file are added so they show up in the help
# commands. Both options are actually handled before the other
# options get parsed.
add_common_option('--config-file FILE',
'Use this config file instead of default') do
end
add_common_option('--backtrace',
'Show stack backtrace on errors') do
end
add_common_option('--debug',
'Turn on Ruby debugging') do
end
add_common_option('--norc',
'Avoid loading any .gemrc file') do
end
# :stopdoc:
HELP = <<-HELP.freeze
RubyGems is a package manager for Ruby.
Usage:
gem -h/--help
gem -v/--version
gem command [arguments...] [options...]
Examples:
gem install rake
gem list --local
gem build package.gemspec
gem help install
Further help:
gem help commands list all 'gem' commands
gem help examples show some examples of usage
gem help gem_dependencies gem dependencies file guide
gem help platforms gem platforms guide
gem help <COMMAND> show help on COMMAND
(e.g. 'gem help install')
gem server present a web page at
http://localhost:8808/
with info about installed gems
Further information:
https://guides.rubygems.org
HELP
# :startdoc:
end
##
# \Commands will be placed in this namespace
module Gem::Commands
end