Merge pull request #11134 from Bo98/ignorable-formulary
Introduce Ignorable module and provide the facility to try and ignore errors when loading historical formulae
This commit is contained in:
commit
33711461f0
@ -3,10 +3,7 @@
|
|||||||
|
|
||||||
require "mutex_m"
|
require "mutex_m"
|
||||||
require "debrew/irb"
|
require "debrew/irb"
|
||||||
require "warnings"
|
require "ignorable"
|
||||||
Warnings.ignore(/warning: callcc is obsolete; use Fiber instead/) do
|
|
||||||
require "continuation"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Helper module for debugging formulae.
|
# Helper module for debugging formulae.
|
||||||
#
|
#
|
||||||
@ -14,31 +11,6 @@ end
|
|||||||
module Debrew
|
module Debrew
|
||||||
extend Mutex_m
|
extend Mutex_m
|
||||||
|
|
||||||
# Marks exceptions which can be ignored and provides
|
|
||||||
# the ability to jump back to where it was raised.
|
|
||||||
module Ignorable
|
|
||||||
attr_accessor :continuation
|
|
||||||
|
|
||||||
def ignore
|
|
||||||
continuation.call
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Module for allowing to ignore exceptions.
|
|
||||||
module Raise
|
|
||||||
def raise(*)
|
|
||||||
callcc do |continuation|
|
|
||||||
super
|
|
||||||
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
||||||
e.extend(Ignorable)
|
|
||||||
e.continuation = continuation
|
|
||||||
super(e)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias fail raise
|
|
||||||
end
|
|
||||||
|
|
||||||
# Module for allowing to debug formulae.
|
# Module for allowing to debug formulae.
|
||||||
module Formula
|
module Formula
|
||||||
def install
|
def install
|
||||||
@ -106,28 +78,28 @@ module Debrew
|
|||||||
|
|
||||||
class << self
|
class << self
|
||||||
extend Predicable
|
extend Predicable
|
||||||
alias original_raise raise
|
|
||||||
attr_predicate :active?
|
attr_predicate :active?
|
||||||
attr_reader :debugged_exceptions
|
attr_reader :debugged_exceptions
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.debrew
|
def self.debrew
|
||||||
@active = true
|
@active = true
|
||||||
Object.include Raise
|
Ignorable.hook_raise
|
||||||
|
|
||||||
begin
|
begin
|
||||||
yield
|
yield
|
||||||
rescue SystemExit
|
rescue SystemExit
|
||||||
original_raise
|
raise
|
||||||
rescue Exception => e # rubocop:disable Lint/RescueException
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
||||||
e.ignore if debug(e) == :ignore # execution jumps back to where the exception was thrown
|
e.ignore if debug(e) == :ignore # execution jumps back to where the exception was thrown
|
||||||
ensure
|
ensure
|
||||||
|
Ignorable.unhook_raise
|
||||||
@active = false
|
@active = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.debug(e)
|
def self.debug(e)
|
||||||
original_raise(e) if !active? || !debugged_exceptions.add?(e) || !try_lock
|
raise(e) if !active? || !debugged_exceptions.add?(e) || !try_lock
|
||||||
|
|
||||||
begin
|
begin
|
||||||
puts e.backtrace.first.to_s
|
puts e.backtrace.first.to_s
|
||||||
@ -137,15 +109,15 @@ module Debrew
|
|||||||
Menu.choose do |menu|
|
Menu.choose do |menu|
|
||||||
menu.prompt = "Choose an action: "
|
menu.prompt = "Choose an action: "
|
||||||
|
|
||||||
menu.choice(:raise) { original_raise(e) }
|
menu.choice(:raise) { raise(e) }
|
||||||
menu.choice(:ignore) { return :ignore } if e.is_a?(Ignorable)
|
menu.choice(:ignore) { return :ignore } if e.is_a?(Ignorable::ExceptionMixin)
|
||||||
menu.choice(:backtrace) { puts e.backtrace }
|
menu.choice(:backtrace) { puts e.backtrace }
|
||||||
|
|
||||||
if e.is_a?(Ignorable)
|
if e.is_a?(Ignorable::ExceptionMixin)
|
||||||
menu.choice(:irb) do
|
menu.choice(:irb) do
|
||||||
puts "When you exit this IRB session, execution will continue."
|
puts "When you exit this IRB session, execution will continue."
|
||||||
set_trace_func proc { |event, _, _, id, binding, klass|
|
set_trace_func proc { |event, _, _, id, binding, klass|
|
||||||
if klass == Raise && id == :raise && event == "return"
|
if klass == Object && id == :raise && event == "return"
|
||||||
set_trace_func(nil)
|
set_trace_func(nil)
|
||||||
synchronize { IRB.start_within(binding) }
|
synchronize { IRB.start_within(binding) }
|
||||||
end
|
end
|
||||||
|
|||||||
@ -5,8 +5,6 @@ require "irb"
|
|||||||
|
|
||||||
# @private
|
# @private
|
||||||
module IRB
|
module IRB
|
||||||
def self.parse_opts(argv: nil); end
|
|
||||||
|
|
||||||
def self.start_within(binding)
|
def self.start_within(binding)
|
||||||
unless @setup_done
|
unless @setup_done
|
||||||
setup(nil, argv: [])
|
setup(nil, argv: [])
|
||||||
@ -19,7 +17,7 @@ module IRB
|
|||||||
@CONF[:IRB_RC]&.call(irb.context)
|
@CONF[:IRB_RC]&.call(irb.context)
|
||||||
@CONF[:MAIN_CONTEXT] = irb.context
|
@CONF[:MAIN_CONTEXT] = irb.context
|
||||||
|
|
||||||
trap("SIGINT") do
|
prev_trap = trap("SIGINT") do
|
||||||
irb.signal_handle
|
irb.signal_handle
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -28,6 +26,7 @@ module IRB
|
|||||||
irb.eval_input
|
irb.eval_input
|
||||||
end
|
end
|
||||||
ensure
|
ensure
|
||||||
|
trap("SIGINT", prev_trap)
|
||||||
irb_at_exit
|
irb_at_exit
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -45,7 +45,7 @@ class FormulaVersions
|
|||||||
|
|
||||||
yield @formula_at_revision[rev] ||= begin
|
yield @formula_at_revision[rev] ||= begin
|
||||||
contents = file_contents_at_revision(rev)
|
contents = file_contents_at_revision(rev)
|
||||||
nostdout { Formulary.from_contents(name, path, contents) }
|
nostdout { Formulary.from_contents(name, path, contents, ignore_errors: true) }
|
||||||
end
|
end
|
||||||
rescue *IGNORED_EXCEPTIONS => e
|
rescue *IGNORED_EXCEPTIONS => e
|
||||||
# We rescue these so that we can skip bad versions and
|
# We rescue these so that we can skip bad versions and
|
||||||
|
|||||||
@ -47,24 +47,35 @@ module Formulary
|
|||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.load_formula(name, path, contents, namespace, flags:)
|
def self.load_formula(name, path, contents, namespace, flags:, ignore_errors:)
|
||||||
raise "Formula loading disabled by HOMEBREW_DISABLE_LOAD_FORMULA!" if Homebrew::EnvConfig.disable_load_formula?
|
raise "Formula loading disabled by HOMEBREW_DISABLE_LOAD_FORMULA!" if Homebrew::EnvConfig.disable_load_formula?
|
||||||
|
|
||||||
require "formula"
|
require "formula"
|
||||||
|
require "ignorable"
|
||||||
|
|
||||||
mod = Module.new
|
mod = Module.new
|
||||||
remove_const(namespace) if const_defined?(namespace)
|
remove_const(namespace) if const_defined?(namespace)
|
||||||
const_set(namespace, mod)
|
const_set(namespace, mod)
|
||||||
|
|
||||||
begin
|
eval_formula = lambda do
|
||||||
# Set `BUILD_FLAGS` in the formula's namespace so we can
|
# Set `BUILD_FLAGS` in the formula's namespace so we can
|
||||||
# access them from within the formula's class scope.
|
# access them from within the formula's class scope.
|
||||||
mod.const_set(:BUILD_FLAGS, flags)
|
mod.const_set(:BUILD_FLAGS, flags)
|
||||||
mod.module_eval(contents, path)
|
mod.module_eval(contents, path)
|
||||||
rescue NameError, ArgumentError, ScriptError, MethodDeprecatedError, MacOSVersionError => e
|
rescue NameError, ArgumentError, ScriptError, MethodDeprecatedError, MacOSVersionError => e
|
||||||
|
if e.is_a?(Ignorable::ExceptionMixin)
|
||||||
|
e.ignore
|
||||||
|
else
|
||||||
remove_const(namespace)
|
remove_const(namespace)
|
||||||
raise FormulaUnreadableError.new(name, e)
|
raise FormulaUnreadableError.new(name, e)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
if ignore_errors
|
||||||
|
Ignorable.hook_raise(&eval_formula)
|
||||||
|
else
|
||||||
|
eval_formula.call
|
||||||
|
end
|
||||||
|
|
||||||
class_name = class_s(name)
|
class_name = class_s(name)
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@ -79,10 +90,10 @@ module Formulary
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.load_formula_from_path(name, path, flags:)
|
def self.load_formula_from_path(name, path, flags:, ignore_errors:)
|
||||||
contents = path.open("r") { |f| ensure_utf8_encoding(f).read }
|
contents = path.open("r") { |f| ensure_utf8_encoding(f).read }
|
||||||
namespace = "FormulaNamespace#{Digest::MD5.hexdigest(path.to_s)}"
|
namespace = "FormulaNamespace#{Digest::MD5.hexdigest(path.to_s)}"
|
||||||
klass = load_formula(name, path, contents, namespace, flags: flags)
|
klass = load_formula(name, path, contents, namespace, flags: flags, ignore_errors: ignore_errors)
|
||||||
cache[path] = klass
|
cache[path] = klass
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -150,23 +161,24 @@ module Formulary
|
|||||||
# Gets the formula instance.
|
# Gets the formula instance.
|
||||||
# `alias_path` can be overridden here in case an alias was used to refer to
|
# `alias_path` can be overridden here in case an alias was used to refer to
|
||||||
# a formula that was loaded in another way.
|
# a formula that was loaded in another way.
|
||||||
def get_formula(spec, alias_path: nil, force_bottle: false, flags: [])
|
def get_formula(spec, alias_path: nil, force_bottle: false, flags: [], ignore_errors: false)
|
||||||
alias_path ||= self.alias_path
|
alias_path ||= self.alias_path
|
||||||
klass(flags: flags).new(name, path, spec, alias_path: alias_path, force_bottle: force_bottle)
|
klass(flags: flags, ignore_errors: ignore_errors)
|
||||||
|
.new(name, path, spec, alias_path: alias_path, force_bottle: force_bottle)
|
||||||
end
|
end
|
||||||
|
|
||||||
def klass(flags:)
|
def klass(flags:, ignore_errors:)
|
||||||
load_file(flags: flags) unless Formulary.formula_class_defined?(path)
|
load_file(flags: flags, ignore_errors: ignore_errors) unless Formulary.formula_class_defined?(path)
|
||||||
Formulary.formula_class_get(path)
|
Formulary.formula_class_get(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_file(flags:)
|
def load_file(flags:, ignore_errors:)
|
||||||
$stderr.puts "#{$PROGRAM_NAME} (#{self.class.name}): loading #{path}" if debug?
|
$stderr.puts "#{$PROGRAM_NAME} (#{self.class.name}): loading #{path}" if debug?
|
||||||
raise FormulaUnavailableError, name unless path.file?
|
raise FormulaUnavailableError, name unless path.file?
|
||||||
|
|
||||||
Formulary.load_formula_from_path(name, path, flags: flags)
|
Formulary.load_formula_from_path(name, path, flags: flags, ignore_errors: ignore_errors)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -191,10 +203,11 @@ module Formulary
|
|||||||
super name, Formulary.path(full_name)
|
super name, Formulary.path(full_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_formula(spec, force_bottle: false, flags: [], **)
|
def get_formula(spec, force_bottle: false, flags: [], ignore_errors: false, **)
|
||||||
formula = begin
|
formula = begin
|
||||||
contents = Utils::Bottles.formula_contents @bottle_filename, name: name
|
contents = Utils::Bottles.formula_contents @bottle_filename, name: name
|
||||||
Formulary.from_contents(name, path, contents, spec, force_bottle: force_bottle, flags: flags)
|
Formulary.from_contents(name, path, contents, spec, force_bottle: force_bottle,
|
||||||
|
flags: flags, ignore_errors: ignore_errors)
|
||||||
rescue FormulaUnreadableError => e
|
rescue FormulaUnreadableError => e
|
||||||
opoo <<~EOS
|
opoo <<~EOS
|
||||||
Unreadable formula in #{@bottle_filename}:
|
Unreadable formula in #{@bottle_filename}:
|
||||||
@ -245,7 +258,7 @@ module Formulary
|
|||||||
super formula, HOMEBREW_CACHE_FORMULA/File.basename(uri.path)
|
super formula, HOMEBREW_CACHE_FORMULA/File.basename(uri.path)
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_file(flags:)
|
def load_file(flags:, ignore_errors:)
|
||||||
if %r{githubusercontent.com/[\w-]+/[\w-]+/[a-f0-9]{40}(?:/Formula)?/(?<formula_name>[\w+-.@]+).rb} =~ url
|
if %r{githubusercontent.com/[\w-]+/[\w-]+/[a-f0-9]{40}(?:/Formula)?/(?<formula_name>[\w+-.@]+).rb} =~ url
|
||||||
raise UsageError, "Installation of #{formula_name} from a GitHub commit URL is unsupported! " \
|
raise UsageError, "Installation of #{formula_name} from a GitHub commit URL is unsupported! " \
|
||||||
"`brew extract #{formula_name}` to a stable tap on GitHub instead."
|
"`brew extract #{formula_name}` to a stable tap on GitHub instead."
|
||||||
@ -309,7 +322,7 @@ module Formulary
|
|||||||
[name, path]
|
[name, path]
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_formula(spec, alias_path: nil, force_bottle: false, flags: [])
|
def get_formula(spec, alias_path: nil, force_bottle: false, flags: [], ignore_errors: false)
|
||||||
super
|
super
|
||||||
rescue FormulaUnreadableError => e
|
rescue FormulaUnreadableError => e
|
||||||
raise TapFormulaUnreadableError.new(tap, name, e.formula_error), "", e.backtrace
|
raise TapFormulaUnreadableError.new(tap, name, e.formula_error), "", e.backtrace
|
||||||
@ -319,7 +332,7 @@ module Formulary
|
|||||||
raise TapFormulaUnavailableError.new(tap, name), "", e.backtrace
|
raise TapFormulaUnavailableError.new(tap, name), "", e.backtrace
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_file(flags:)
|
def load_file(flags:, ignore_errors:)
|
||||||
super
|
super
|
||||||
rescue MethodDeprecatedError => e
|
rescue MethodDeprecatedError => e
|
||||||
e.issues_url = tap.issues_url || tap.to_s
|
e.issues_url = tap.issues_url || tap.to_s
|
||||||
@ -348,10 +361,10 @@ module Formulary
|
|||||||
super name, path
|
super name, path
|
||||||
end
|
end
|
||||||
|
|
||||||
def klass(flags:)
|
def klass(flags:, ignore_errors:)
|
||||||
$stderr.puts "#{$PROGRAM_NAME} (#{self.class.name}): loading #{path}" if debug?
|
$stderr.puts "#{$PROGRAM_NAME} (#{self.class.name}): loading #{path}" if debug?
|
||||||
namespace = "FormulaNamespace#{Digest::MD5.hexdigest(contents.to_s)}"
|
namespace = "FormulaNamespace#{Digest::MD5.hexdigest(contents.to_s)}"
|
||||||
Formulary.load_formula(name, path, contents, namespace, flags: flags)
|
Formulary.load_formula(name, path, contents, namespace, flags: flags, ignore_errors: ignore_errors)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -362,7 +375,10 @@ module Formulary
|
|||||||
# * a formula pathname
|
# * a formula pathname
|
||||||
# * a formula URL
|
# * a formula URL
|
||||||
# * a local bottle reference
|
# * a local bottle reference
|
||||||
def self.factory(ref, spec = :stable, alias_path: nil, from: nil, force_bottle: false, flags: [])
|
def self.factory(
|
||||||
|
ref, spec = :stable, alias_path: nil, from: nil,
|
||||||
|
force_bottle: false, flags: [], ignore_errors: false
|
||||||
|
)
|
||||||
raise ArgumentError, "Formulae must have a ref!" unless ref
|
raise ArgumentError, "Formulae must have a ref!" unless ref
|
||||||
|
|
||||||
cache_key = "#{ref}-#{spec}-#{alias_path}-#{from}"
|
cache_key = "#{ref}-#{spec}-#{alias_path}-#{from}"
|
||||||
@ -372,7 +388,8 @@ module Formulary
|
|||||||
end
|
end
|
||||||
|
|
||||||
formula = loader_for(ref, from: from).get_formula(spec, alias_path: alias_path,
|
formula = loader_for(ref, from: from).get_formula(spec, alias_path: alias_path,
|
||||||
force_bottle: force_bottle, flags: flags)
|
force_bottle: force_bottle, flags: flags,
|
||||||
|
ignore_errors: ignore_errors)
|
||||||
if factory_cached?
|
if factory_cached?
|
||||||
cache[:formulary_factory] ||= {}
|
cache[:formulary_factory] ||= {}
|
||||||
cache[:formulary_factory][cache_key] ||= formula
|
cache[:formulary_factory][cache_key] ||= formula
|
||||||
@ -433,9 +450,13 @@ module Formulary
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Return a {Formula} instance directly from contents.
|
# Return a {Formula} instance directly from contents.
|
||||||
def self.from_contents(name, path, contents, spec = :stable, alias_path: nil, force_bottle: false, flags: [])
|
def self.from_contents(
|
||||||
|
name, path, contents, spec = :stable, alias_path: nil,
|
||||||
|
force_bottle: false, flags: [], ignore_errors: false
|
||||||
|
)
|
||||||
FormulaContentsLoader.new(name, path, contents)
|
FormulaContentsLoader.new(name, path, contents)
|
||||||
.get_formula(spec, alias_path: alias_path, force_bottle: force_bottle, flags: flags)
|
.get_formula(spec, alias_path: alias_path, force_bottle: force_bottle,
|
||||||
|
flags: flags, ignore_errors: ignore_errors)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.to_rack(ref)
|
def self.to_rack(ref)
|
||||||
|
|||||||
58
Library/Homebrew/ignorable.rb
Normal file
58
Library/Homebrew/ignorable.rb
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "warnings"
|
||||||
|
Warnings.ignore(/warning: callcc is obsolete; use Fiber instead/) do
|
||||||
|
require "continuation"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Provides the ability to optionally ignore errors raised and continue execution.
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
module Ignorable
|
||||||
|
# Marks exceptions which can be ignored and provides
|
||||||
|
# the ability to jump back to where it was raised.
|
||||||
|
module ExceptionMixin
|
||||||
|
attr_accessor :continuation
|
||||||
|
|
||||||
|
def ignore
|
||||||
|
continuation.call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.hook_raise
|
||||||
|
Object.class_eval do
|
||||||
|
alias_method :original_raise, :raise
|
||||||
|
|
||||||
|
def raise(*)
|
||||||
|
callcc do |continuation|
|
||||||
|
super
|
||||||
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
||||||
|
unless e.is_a?(ScriptError)
|
||||||
|
e.extend(ExceptionMixin)
|
||||||
|
e.continuation = continuation
|
||||||
|
end
|
||||||
|
super(e)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
alias_method :fail, :raise
|
||||||
|
end
|
||||||
|
|
||||||
|
return unless block_given?
|
||||||
|
|
||||||
|
yield
|
||||||
|
unhook_raise
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.unhook_raise
|
||||||
|
Object.class_eval do
|
||||||
|
# False positive - https://github.com/rubocop/rubocop/issues/5022
|
||||||
|
# rubocop:disable Lint/DuplicateMethods
|
||||||
|
alias_method :raise, :original_raise
|
||||||
|
alias_method :fail, :original_raise
|
||||||
|
# rubocop:enable Lint/DuplicateMethods
|
||||||
|
undef :original_raise
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user