Fix handling unreadable casks

When casks are unreadable (e.g. have invalid syntax, the cask file
cannot be found) then it's not been possible to uninstall them, list
them or perform any operation which iterates through all casks.

Handle these various cases by falling back to creating a `Cask::Cask`
object using just the name/token and latest installed version on disk.

This provides enough functionality to be able to verbosely list these
casks, not error on listing and, most importantly, uninstall/reinstall
them.

Fixes https://github.com/Homebrew/homebrew-cask/issues/62223
This commit is contained in:
Mike McQuaid 2022-05-16 17:27:13 -04:00
parent 8a5f6645b8
commit 94148c3bc8
No known key found for this signature in database
GPG Key ID: 3338A31AFDB1D829
4 changed files with 24 additions and 23 deletions

View File

@ -46,14 +46,19 @@ module Cask
path.children.select(&:directory?).sort.map do |path| path.children.select(&:directory?).sort.map do |path|
token = path.basename.to_s token = path.basename.to_s
if (tap_path = CaskLoader.tap_paths(token).first) begin
CaskLoader::FromTapPathLoader.new(tap_path).load(config: config) if (tap_path = CaskLoader.tap_paths(token).first)
elsif (caskroom_path = Pathname.glob(path.join(".metadata/*/*/*/*.rb")).first) CaskLoader::FromTapPathLoader.new(tap_path).load(config: config)
CaskLoader::FromPathLoader.new(caskroom_path).load(config: config) elsif (caskroom_path = Pathname.glob(path.join(".metadata/*/*/*/*.rb")).first)
else CaskLoader::FromPathLoader.new(caskroom_path).load(config: config)
CaskLoader.load(token, config: config) else
CaskLoader.load(token, config: config)
end
rescue CaskUnavailableError
# Don't blow up because of a single unavailable cask.
nil
end end
end end.compact
end end
end end
end end

View File

@ -39,14 +39,7 @@ module Cask
casks.each do |cask| casks.each do |cask|
odebug "Uninstalling Cask #{cask}" odebug "Uninstalling Cask #{cask}"
if cask.installed? raise CaskNotInstalledError, cask if !cask.installed? && !force
if (installed_caskfile = cask.installed_caskfile) && installed_caskfile.exist?
# Use the same cask file that was used for installation, if possible.
cask = CaskLoader.load(installed_caskfile)
end
else
raise CaskNotInstalledError, cask unless force
end
Installer.new(cask, **options).uninstall Installer.new(cask, **options).uninstall

View File

@ -32,14 +32,7 @@ module Cask
casks.each do |cask| casks.each do |cask|
odebug "Zapping Cask #{cask}" odebug "Zapping Cask #{cask}"
if cask.installed? raise CaskNotInstalledError, cask if !cask.installed? && !force
if (installed_caskfile = cask.installed_caskfile) && installed_caskfile.exist?
# Use the same cask file that was used for installation, if possible.
cask = CaskLoader.load(installed_caskfile)
end
else
raise CaskNotInstalledError, cask unless force
end
Installer.new(cask, verbose: verbose, force: force).zap Installer.new(cask, verbose: verbose, force: force).zap
end end

View File

@ -148,6 +148,16 @@ module Homebrew
return cask return cask
rescue Cask::CaskUnreadableError => e rescue Cask::CaskUnreadableError => e
# If we're trying to get a keg-like Cask, do our best to handle it
# not being readable and return something that can be used.
if [:latest_kegs, :default_kegs, :kegs].include?(method)
cask_version = Cask::Cask.new(name, config: config).versions.first
cask = Cask::Cask.new(name, config: config) do
version cask_version if cask_version
end
return cask
end
# Need to rescue before `CaskUnavailableError` (superclass of this) # Need to rescue before `CaskUnavailableError` (superclass of this)
# The cask was found, but there's a problem with its implementation # The cask was found, but there's a problem with its implementation
unreadable_error ||= e unreadable_error ||= e