brew/Library/Homebrew/cli/named_args.rb
Misty De Meo c06848c487
Fix printing MultipleVersionsInstalledError details
The refactor in 6e8f5d0958247e4b4d629866099ed2836a0e0863 means that the
exception no longer exposes the name of the package with multiple versions,
and as a result the rescuer is unable to print this information.

Because we now have a path in which MultipleVersionsInstalledError doesn't
have the name at all, we can't reasonably restore the old behaviour.
And since rack resolution happens purely internal to the function that
raises the exception, the caller has no way to know what name to use.
However, since the exception text gets printed anyway, we can just move
this text into the exception itself.

Fixes the following error:

```
Error: mpd has multiple installed versions
Error: undefined method `name' for #<MultipleVersionsInstalledError:0x00007fc6009d8870>
/usr/local/Homebrew/Library/Homebrew/cmd/uninstall.rb:137:in `rescue in uninstall'
/usr/local/Homebrew/Library/Homebrew/cmd/uninstall.rb:135:in `uninstall'
/usr/local/Homebrew/Library/Homebrew/brew.rb:119:in `<main>'
```
2020-09-09 11:56:53 -07:00

195 lines
5.8 KiB
Ruby

# frozen_string_literal: true
require "cask/cask_loader"
require "delegate"
require "formulary"
require "missing_formula"
module Homebrew
module CLI
# Helper class for loading formulae/casks from named arguments.
#
# @api private
class NamedArgs < SimpleDelegator
def initialize(*args, override_spec: nil, force_bottle: false, flags: [])
@args = args
@override_spec = override_spec
@force_bottle = force_bottle
@flags = flags
super(@args)
end
def to_formulae
@to_formulae ||= (downcased_unique_named - homebrew_tap_cask_names).map do |name|
Formulary.factory(name, spec, force_bottle: @force_bottle, flags: @flags)
end.uniq(&:name).freeze
end
def to_formulae_and_casks
@to_formulae_and_casks ||= begin
formulae_and_casks = []
downcased_unique_named.each do |name|
formulae_and_casks << Formulary.factory(name, spec)
warn_if_cask_conflicts(name, "formula")
rescue FormulaUnavailableError
begin
formulae_and_casks << Cask::CaskLoader.load(name)
rescue Cask::CaskUnavailableError
raise "No available formula or cask with the name \"#{name}\""
end
end
formulae_and_casks.freeze
end
end
def to_resolved_formulae
@to_resolved_formulae ||= (downcased_unique_named - homebrew_tap_cask_names).map do |name|
Formulary.resolve(name, spec: spec(nil), force_bottle: @force_bottle, flags: @flags)
end.uniq(&:name).freeze
end
def to_resolved_formulae_to_casks
@to_resolved_formulae_to_casks ||= begin
resolved_formulae = []
casks = []
downcased_unique_named.each do |name|
resolved_formulae << Formulary.resolve(name, spec: spec(nil), force_bottle: @force_bottle, flags: @flags)
warn_if_cask_conflicts(name, "formula")
rescue FormulaUnavailableError
begin
casks << Cask::CaskLoader.load(name)
rescue Cask::CaskUnavailableError
raise "No available formula or cask with the name \"#{name}\""
end
end
[resolved_formulae.freeze, casks.freeze].freeze
end
end
def to_formulae_paths
@to_formulae_paths ||= (downcased_unique_named - homebrew_tap_cask_names).map do |name|
Formulary.path(name)
end.uniq.freeze
end
def to_casks
@to_casks ||= downcased_unique_named.map(&Cask::CaskLoader.method(:load)).freeze
end
def to_kegs
@to_kegs ||= downcased_unique_named.map do |name|
resolve_keg name
rescue NoSuchKegError => e
if (reason = Homebrew::MissingFormula.suggest_command(name, "uninstall"))
$stderr.puts reason
end
raise e
end.freeze
end
def to_kegs_to_casks
@to_kegs_to_casks ||= begin
kegs = []
casks = []
downcased_unique_named.each do |name|
kegs << resolve_keg(name)
warn_if_cask_conflicts(name, "keg")
rescue NoSuchKegError, FormulaUnavailableError
begin
casks << Cask::CaskLoader.load(name)
rescue Cask::CaskUnavailableError
raise "No installed keg or cask with the name \"#{name}\""
end
end
[kegs.freeze, casks.freeze].freeze
end
end
def homebrew_tap_cask_names
downcased_unique_named.grep(HOMEBREW_CASK_TAP_CASK_REGEX)
end
private
def downcased_unique_named
# Only lowercase names, not paths, bottle filenames or URLs
map do |arg|
if arg.include?("/") || arg.end_with?(".tar.gz") || File.exist?(arg)
arg
else
arg.downcase
end
end.uniq
end
def spec(default = :stable)
@override_spec || default
end
def resolve_keg(name)
raise UsageError if name.blank?
require "keg"
rack = Formulary.to_rack(name.downcase)
dirs = rack.directory? ? rack.subdirs : []
raise NoSuchKegError, rack.basename if dirs.empty?
linked_keg_ref = HOMEBREW_LINKED_KEGS/rack.basename
opt_prefix = HOMEBREW_PREFIX/"opt/#{rack.basename}"
begin
if opt_prefix.symlink? && opt_prefix.directory?
Keg.new(opt_prefix.resolved_path)
elsif linked_keg_ref.symlink? && linked_keg_ref.directory?
Keg.new(linked_keg_ref.resolved_path)
elsif dirs.length == 1
Keg.new(dirs.first)
else
f = if name.include?("/") || File.exist?(name)
Formulary.factory(name)
else
Formulary.from_rack(rack)
end
unless (prefix = f.latest_installed_prefix).directory?
raise MultipleVersionsInstalledError, <<~EOS
#{rack.basename} has multiple installed versions
Run `brew uninstall --force #{rack.basename}` to remove all versions.
EOS
end
Keg.new(prefix)
end
rescue FormulaUnavailableError
raise MultipleVersionsInstalledError, <<~EOS
Multiple kegs installed to #{rack}
However we don't know which one you refer to.
Please delete (with rm -rf!) all but one and then try again.
EOS
end
end
def warn_if_cask_conflicts(ref, loaded_type)
cask = Cask::CaskLoader.load ref
message = "Treating #{ref} as a #{loaded_type}."
message += " For the cask, use #{cask.tap.name}/#{cask.token}" if cask.tap.present?
opoo message.freeze
rescue Cask::CaskUnavailableError
# No ref conflict with a cask, do nothing
end
end
end
end