
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>' ```
195 lines
5.8 KiB
Ruby
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
|