Rewrite debugger to remove monkeypatches and use of call/cc
This commit is contained in:
parent
67a9164278
commit
3bbc9998a5
@ -8,7 +8,7 @@ require "build_options"
|
|||||||
require "cxxstdlib"
|
require "cxxstdlib"
|
||||||
require "keg"
|
require "keg"
|
||||||
require "extend/ENV"
|
require "extend/ENV"
|
||||||
require "debrew" if ARGV.debug?
|
require "debrew"
|
||||||
require "fcntl"
|
require "fcntl"
|
||||||
|
|
||||||
class Build
|
class Build
|
||||||
@ -109,6 +109,11 @@ class Build
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if ARGV.debug?
|
||||||
|
formula.extend(Debrew::Formula)
|
||||||
|
formula.resources.each { |r| r.extend(Debrew::Resource) }
|
||||||
|
end
|
||||||
|
|
||||||
formula.brew do
|
formula.brew do
|
||||||
if ARGV.flag? '--git'
|
if ARGV.flag? '--git'
|
||||||
system "git", "init"
|
system "git", "init"
|
||||||
@ -129,17 +134,7 @@ class Build
|
|||||||
else
|
else
|
||||||
formula.prefix.mkpath
|
formula.prefix.mkpath
|
||||||
|
|
||||||
formula.resources.each { |r| r.extend(ResourceDebugger) } if ARGV.debug?
|
formula.install
|
||||||
|
|
||||||
begin
|
|
||||||
formula.install
|
|
||||||
rescue Exception => e
|
|
||||||
if ARGV.debug?
|
|
||||||
debrew(e, formula)
|
|
||||||
else
|
|
||||||
raise
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
stdlibs = detect_stdlibs
|
stdlibs = detect_stdlibs
|
||||||
Tab.create(formula, ENV.compiler, stdlibs.first, formula.build).write
|
Tab.create(formula, ENV.compiler, stdlibs.first, formula.build).write
|
||||||
@ -190,7 +185,6 @@ begin
|
|||||||
build = Build.new(formula, options)
|
build = Build.new(formula, options)
|
||||||
build.install
|
build.install
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
e.continuation = nil if ARGV.debug?
|
|
||||||
Marshal.dump(e, error_pipe)
|
Marshal.dump(e, error_pipe)
|
||||||
error_pipe.close
|
error_pipe.close
|
||||||
exit! 1
|
exit! 1
|
||||||
|
@ -1,75 +1,148 @@
|
|||||||
require 'debrew/menu'
|
require "mutex_m"
|
||||||
require 'debrew/raise_plus'
|
require "debrew/irb" unless ENV["HOMEBREW_NO_READLINE"]
|
||||||
require 'set'
|
|
||||||
|
|
||||||
unless ENV['HOMEBREW_NO_READLINE']
|
module Debrew
|
||||||
begin
|
extend Mutex_m
|
||||||
require 'rubygems'
|
|
||||||
require 'ruby-debug'
|
Ignorable = Module.new
|
||||||
rescue LoadError
|
|
||||||
|
module Raise
|
||||||
|
def raise(*)
|
||||||
|
super
|
||||||
|
rescue Exception => e
|
||||||
|
e.extend(Ignorable)
|
||||||
|
super(e) unless Debrew.debug(e) == :ignore
|
||||||
|
end
|
||||||
|
|
||||||
|
alias_method :fail, :raise
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'debrew/irb'
|
module Formula
|
||||||
end
|
def install
|
||||||
|
Debrew.debrew { super }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class Object
|
module Resource
|
||||||
include RaisePlus
|
def unpack(target=nil)
|
||||||
end
|
return super if target
|
||||||
|
super do
|
||||||
|
begin
|
||||||
|
yield self
|
||||||
|
rescue Exception => e
|
||||||
|
Debrew.debug(e)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
module ResourceDebugger
|
class Menu
|
||||||
def stage(target=nil, &block)
|
Entry = Struct.new(:name, :action)
|
||||||
return super if target
|
|
||||||
|
|
||||||
super do
|
attr_accessor :prompt, :entries
|
||||||
begin
|
|
||||||
block.call(self)
|
def initialize
|
||||||
rescue Exception => e
|
@entries = []
|
||||||
if ARGV.debug?
|
end
|
||||||
debrew e
|
|
||||||
|
def choice(name, &action)
|
||||||
|
entries << Entry.new(name.to_s, action)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.choose
|
||||||
|
menu = new
|
||||||
|
yield menu
|
||||||
|
|
||||||
|
choice = nil
|
||||||
|
while choice.nil?
|
||||||
|
menu.entries.each_with_index { |e, i| puts "#{i+1}. #{e.name}" }
|
||||||
|
print menu.prompt unless menu.prompt.nil?
|
||||||
|
|
||||||
|
input = $stdin.gets.chomp
|
||||||
|
|
||||||
|
i = input.to_i
|
||||||
|
if i > 0
|
||||||
|
choice = menu.entries[i-1]
|
||||||
else
|
else
|
||||||
raise
|
possible = menu.entries.find_all { |e| e.name.start_with?(input) }
|
||||||
|
|
||||||
|
case possible.size
|
||||||
|
when 0 then puts "No such option"
|
||||||
|
when 1 then choice = possible.first
|
||||||
|
else puts "Multiple options match: #{possible.map(&:name).join(" ")}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
choice[:action].call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
alias_method :original_raise, :raise
|
||||||
|
end
|
||||||
|
|
||||||
|
@active = false
|
||||||
|
@debugged_exceptions = Set.new
|
||||||
|
|
||||||
|
def self.active?
|
||||||
|
@active
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.debugged_exceptions
|
||||||
|
@debugged_exceptions
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.debrew
|
||||||
|
@active = true
|
||||||
|
Object.send(:include, Raise)
|
||||||
|
|
||||||
|
begin
|
||||||
|
yield
|
||||||
|
rescue Exception => e
|
||||||
|
debug(e)
|
||||||
|
ensure
|
||||||
|
@active = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.debug(e)
|
||||||
|
original_raise(e) unless active? &&
|
||||||
|
debugged_exceptions.add?(e) &&
|
||||||
|
try_lock
|
||||||
|
|
||||||
|
begin
|
||||||
|
puts "#{e.backtrace.first}"
|
||||||
|
puts "#{Tty.red}#{e.class.name}#{Tty.reset}: #{e}"
|
||||||
|
|
||||||
|
loop do
|
||||||
|
Menu.choose do |menu|
|
||||||
|
menu.prompt = "Choose an action: "
|
||||||
|
|
||||||
|
menu.choice(:raise) { original_raise(e) }
|
||||||
|
menu.choice(:ignore) { return :ignore } if Ignorable === e
|
||||||
|
menu.choice(:backtrace) { puts e.backtrace }
|
||||||
|
|
||||||
|
menu.choice(:irb) do
|
||||||
|
puts "When you exit this IRB session, execution will continue."
|
||||||
|
set_trace_func proc { |event, _, _, id, binding, klass|
|
||||||
|
if klass == Raise && id == :raise && event == "return"
|
||||||
|
set_trace_func(nil)
|
||||||
|
synchronize { IRB.start_within(binding) }
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
return :ignore
|
||||||
|
end if Object.const_defined?(:IRB) && Ignorable === e
|
||||||
|
|
||||||
|
menu.choice(:shell) do
|
||||||
|
puts "When you exit this shell, you will return to the menu."
|
||||||
|
interactive_shell
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
unlock
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
$debugged_exceptions = Set.new
|
|
||||||
|
|
||||||
def debrew(exception, formula=nil)
|
|
||||||
raise exception unless $debugged_exceptions.add?(exception)
|
|
||||||
|
|
||||||
puts "#{exception.backtrace.first}"
|
|
||||||
puts "#{Tty.red}#{exception.class.name}#{Tty.reset}: #{exception}"
|
|
||||||
|
|
||||||
begin
|
|
||||||
again = false
|
|
||||||
choose do |menu|
|
|
||||||
menu.prompt = "Choose an action: "
|
|
||||||
menu.choice(:raise) { original_raise exception }
|
|
||||||
menu.choice(:ignore) { exception.restart } if exception.continuation
|
|
||||||
menu.choice(:backtrace) { puts exception.backtrace; again = true }
|
|
||||||
menu.choice(:debug) do
|
|
||||||
puts "When you exit the debugger, execution will continue."
|
|
||||||
exception.restart { debugger }
|
|
||||||
end if Object.const_defined?(:Debugger)
|
|
||||||
menu.choice(:irb) do
|
|
||||||
puts "When you exit this IRB session, execution will continue."
|
|
||||||
exception.restart do
|
|
||||||
# we need to capture the binding after returning from raise
|
|
||||||
set_trace_func proc { |event, file, line, id, binding, classname|
|
|
||||||
if event == 'return'
|
|
||||||
set_trace_func nil
|
|
||||||
IRB.start_within(binding)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end if Object.const_defined?(:IRB) && exception.continuation
|
|
||||||
menu.choice(:shell) do
|
|
||||||
puts "When you exit this shell, you will return to the menu."
|
|
||||||
interactive_shell formula
|
|
||||||
again=true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end while again
|
|
||||||
end
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
class Exception
|
|
||||||
attr_accessor :continuation
|
|
||||||
|
|
||||||
def restart(&block)
|
|
||||||
continuation.call block
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,39 +0,0 @@
|
|||||||
class Menu
|
|
||||||
attr_accessor :prompt
|
|
||||||
attr_accessor :entries
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@entries = []
|
|
||||||
end
|
|
||||||
|
|
||||||
def choice(name, &action)
|
|
||||||
entries << { :name => name, :action => action }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def choose
|
|
||||||
menu = Menu.new
|
|
||||||
yield menu
|
|
||||||
|
|
||||||
choice = nil
|
|
||||||
while choice.nil?
|
|
||||||
menu.entries.each_with_index do |entry, i|
|
|
||||||
puts "#{i+1}. #{entry[:name]}"
|
|
||||||
end
|
|
||||||
print menu.prompt unless menu.prompt.nil?
|
|
||||||
reply = $stdin.gets.chomp
|
|
||||||
|
|
||||||
i = reply.to_i
|
|
||||||
if i > 0
|
|
||||||
choice = menu.entries[i-1]
|
|
||||||
else
|
|
||||||
possible = menu.entries.find_all {|e| e[:name].to_s.start_with? reply }
|
|
||||||
case possible.size
|
|
||||||
when 0 then puts "No such option"
|
|
||||||
when 1 then choice = possible.first
|
|
||||||
else puts "Multiple options match: #{possible.map{|e| e[:name]}.join(' ')}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
choice[:action].call
|
|
||||||
end
|
|
@ -1,46 +0,0 @@
|
|||||||
require 'continuation' if RUBY_VERSION.to_f >= 1.9
|
|
||||||
|
|
||||||
class Exception
|
|
||||||
attr_accessor :continuation
|
|
||||||
|
|
||||||
def restart(&block)
|
|
||||||
continuation.call block
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module RaisePlus
|
|
||||||
alias :original_raise :raise
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def raise(*args)
|
|
||||||
exception = case
|
|
||||||
when args.size == 0
|
|
||||||
$!.nil? ? RuntimeError.exception : $!
|
|
||||||
when args.size == 1 && args[0].is_a?(String)
|
|
||||||
RuntimeError.exception(args[0])
|
|
||||||
when args.size == 2 && args[0].is_a?(Exception)
|
|
||||||
args[0].exception(args[1])
|
|
||||||
when args[0].is_a?(Class) && args[0].ancestors.include?(Exception)
|
|
||||||
args[0].exception(args[1])
|
|
||||||
else
|
|
||||||
args[0]
|
|
||||||
end
|
|
||||||
|
|
||||||
# passing something other than a String or Exception is illegal, but if someone does it anyway,
|
|
||||||
# that object won't have backtrace or continuation methods. in that case, let's pass it on to
|
|
||||||
# the original raise, which will reject it
|
|
||||||
return super exception unless exception.is_a?(Exception)
|
|
||||||
|
|
||||||
# keep original backtrace if reraising
|
|
||||||
exception.set_backtrace(args.size >= 3 ? args[2] : caller) if exception.backtrace.nil?
|
|
||||||
|
|
||||||
blk = callcc do |cc|
|
|
||||||
exception.continuation = cc
|
|
||||||
super exception
|
|
||||||
end
|
|
||||||
blk.call unless blk.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
alias :fail :raise
|
|
||||||
end
|
|
@ -12,6 +12,7 @@ require 'formula_cellar_checks'
|
|||||||
require 'install_renamed'
|
require 'install_renamed'
|
||||||
require 'cmd/tap'
|
require 'cmd/tap'
|
||||||
require 'hooks/bottles'
|
require 'hooks/bottles'
|
||||||
|
require 'debrew'
|
||||||
|
|
||||||
class FormulaInstaller
|
class FormulaInstaller
|
||||||
include FormulaCellarChecks
|
include FormulaCellarChecks
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
require 'testing_env'
|
|
||||||
require 'debrew/raise_plus'
|
|
||||||
|
|
||||||
class RaisePlusTests < Homebrew::TestCase
|
|
||||||
include RaisePlus
|
|
||||||
|
|
||||||
def test_raises_runtime_error_when_no_args
|
|
||||||
assert_raises(RuntimeError) { raise }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_raises_runtime_error_with_string_arg
|
|
||||||
raise "foo"
|
|
||||||
rescue Exception => e
|
|
||||||
assert_kind_of RuntimeError, e
|
|
||||||
assert_equal "foo", e.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_raises_given_exception_with_new_to_s
|
|
||||||
a = Exception.new("foo")
|
|
||||||
raise a, "bar"
|
|
||||||
rescue Exception => e
|
|
||||||
assert_equal "bar", e.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_raises_same_instance
|
|
||||||
a = Exception.new("foo")
|
|
||||||
raise a
|
|
||||||
rescue Exception => e
|
|
||||||
assert_same e, a
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_raises_exception_class
|
|
||||||
assert_raises(StandardError) { raise StandardError }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_raises_type_error_for_bad_args
|
|
||||||
assert_raises(TypeError) { raise 1 }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_raise_is_private
|
|
||||||
assert_raises(NoMethodError) do
|
|
||||||
Object.new.extend(RaisePlus).raise(RuntimeError)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
x
Reference in New Issue
Block a user