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 "keg"
 | 
			
		||||
require "extend/ENV"
 | 
			
		||||
require "debrew" if ARGV.debug?
 | 
			
		||||
require "debrew"
 | 
			
		||||
require "fcntl"
 | 
			
		||||
 | 
			
		||||
class Build
 | 
			
		||||
@ -109,6 +109,11 @@ class Build
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    if ARGV.debug?
 | 
			
		||||
      formula.extend(Debrew::Formula)
 | 
			
		||||
      formula.resources.each { |r| r.extend(Debrew::Resource) }
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    formula.brew do
 | 
			
		||||
      if ARGV.flag? '--git'
 | 
			
		||||
        system "git", "init"
 | 
			
		||||
@ -129,17 +134,7 @@ class Build
 | 
			
		||||
      else
 | 
			
		||||
        formula.prefix.mkpath
 | 
			
		||||
 | 
			
		||||
        formula.resources.each { |r| r.extend(ResourceDebugger) } if ARGV.debug?
 | 
			
		||||
 | 
			
		||||
        begin
 | 
			
		||||
          formula.install
 | 
			
		||||
        rescue Exception => e
 | 
			
		||||
          if ARGV.debug?
 | 
			
		||||
            debrew(e, formula)
 | 
			
		||||
          else
 | 
			
		||||
            raise
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
        formula.install
 | 
			
		||||
 | 
			
		||||
        stdlibs = detect_stdlibs
 | 
			
		||||
        Tab.create(formula, ENV.compiler, stdlibs.first, formula.build).write
 | 
			
		||||
@ -190,7 +185,6 @@ begin
 | 
			
		||||
  build   = Build.new(formula, options)
 | 
			
		||||
  build.install
 | 
			
		||||
rescue Exception => e
 | 
			
		||||
  e.continuation = nil if ARGV.debug?
 | 
			
		||||
  Marshal.dump(e, error_pipe)
 | 
			
		||||
  error_pipe.close
 | 
			
		||||
  exit! 1
 | 
			
		||||
 | 
			
		||||
@ -1,75 +1,148 @@
 | 
			
		||||
require 'debrew/menu'
 | 
			
		||||
require 'debrew/raise_plus'
 | 
			
		||||
require 'set'
 | 
			
		||||
require "mutex_m"
 | 
			
		||||
require "debrew/irb" unless ENV["HOMEBREW_NO_READLINE"]
 | 
			
		||||
 | 
			
		||||
unless ENV['HOMEBREW_NO_READLINE']
 | 
			
		||||
  begin
 | 
			
		||||
    require 'rubygems'
 | 
			
		||||
    require 'ruby-debug'
 | 
			
		||||
  rescue LoadError
 | 
			
		||||
module Debrew
 | 
			
		||||
  extend Mutex_m
 | 
			
		||||
 | 
			
		||||
  Ignorable = Module.new
 | 
			
		||||
 | 
			
		||||
  module Raise
 | 
			
		||||
    def raise(*)
 | 
			
		||||
      super
 | 
			
		||||
    rescue Exception => e
 | 
			
		||||
      e.extend(Ignorable)
 | 
			
		||||
      super(e) unless Debrew.debug(e) == :ignore
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    alias_method :fail, :raise
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  require 'debrew/irb'
 | 
			
		||||
end
 | 
			
		||||
  module Formula
 | 
			
		||||
    def install
 | 
			
		||||
      Debrew.debrew { super }
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
class Object
 | 
			
		||||
  include RaisePlus
 | 
			
		||||
end
 | 
			
		||||
  module Resource
 | 
			
		||||
    def unpack(target=nil)
 | 
			
		||||
      return super if target
 | 
			
		||||
      super do
 | 
			
		||||
        begin
 | 
			
		||||
          yield self
 | 
			
		||||
        rescue Exception => e
 | 
			
		||||
          Debrew.debug(e)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
module ResourceDebugger
 | 
			
		||||
  def stage(target=nil, &block)
 | 
			
		||||
    return super if target
 | 
			
		||||
  class Menu
 | 
			
		||||
    Entry = Struct.new(:name, :action)
 | 
			
		||||
 | 
			
		||||
    super do
 | 
			
		||||
      begin
 | 
			
		||||
        block.call(self)
 | 
			
		||||
      rescue Exception => e
 | 
			
		||||
        if ARGV.debug?
 | 
			
		||||
          debrew e
 | 
			
		||||
    attr_accessor :prompt, :entries
 | 
			
		||||
 | 
			
		||||
    def initialize
 | 
			
		||||
      @entries = []
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
          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
 | 
			
		||||
 | 
			
		||||
      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
 | 
			
		||||
 | 
			
		||||
$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 'cmd/tap'
 | 
			
		||||
require 'hooks/bottles'
 | 
			
		||||
require 'debrew'
 | 
			
		||||
 | 
			
		||||
class FormulaInstaller
 | 
			
		||||
  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