diff --git a/Library/Homebrew/build.rb b/Library/Homebrew/build.rb index 34903c9aa3..14209dd81a 100644 --- a/Library/Homebrew/build.rb +++ b/Library/Homebrew/build.rb @@ -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 diff --git a/Library/Homebrew/debrew.rb b/Library/Homebrew/debrew.rb index e8df1aeec7..fbddadbd53 100644 --- a/Library/Homebrew/debrew.rb +++ b/Library/Homebrew/debrew.rb @@ -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 diff --git a/Library/Homebrew/debrew/exception.rb b/Library/Homebrew/debrew/exception.rb deleted file mode 100644 index da8e56dae0..0000000000 --- a/Library/Homebrew/debrew/exception.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Exception - attr_accessor :continuation - - def restart(&block) - continuation.call block - end -end diff --git a/Library/Homebrew/debrew/menu.rb b/Library/Homebrew/debrew/menu.rb deleted file mode 100644 index c59c500b8e..0000000000 --- a/Library/Homebrew/debrew/menu.rb +++ /dev/null @@ -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 diff --git a/Library/Homebrew/debrew/raise_plus.rb b/Library/Homebrew/debrew/raise_plus.rb deleted file mode 100644 index 29bf4106a1..0000000000 --- a/Library/Homebrew/debrew/raise_plus.rb +++ /dev/null @@ -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 diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index bb76a69cd7..d457da4303 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -12,6 +12,7 @@ require 'formula_cellar_checks' require 'install_renamed' require 'cmd/tap' require 'hooks/bottles' +require 'debrew' class FormulaInstaller include FormulaCellarChecks diff --git a/Library/Homebrew/test/test_raise_plus.rb b/Library/Homebrew/test/test_raise_plus.rb deleted file mode 100644 index 109c6e7377..0000000000 --- a/Library/Homebrew/test/test_raise_plus.rb +++ /dev/null @@ -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