Much better CTRL-C handling

Let's not show weird error messages when user interrupts during various stages of brew initialization.

Tested by doing `for x in $(brew search); do brew install $x; done` and pressing CTRL-C at random short intervals.
This commit is contained in:
Max Howell 2012-08-22 15:50:27 -04:00
parent fb8c7e0aaf
commit 20ce16a3ff
4 changed files with 53 additions and 42 deletions

View File

@ -1,51 +1,54 @@
#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby #!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -W0
# This script is called by formula_installer as a separate instance. # This script is called by formula_installer as a separate instance.
# Rationale: Formula can use __END__, Formula can change ENV # Rationale: Formula can use __END__, Formula can change ENV
# Thrown exceptions are propogated back to the parent process over a pipe # Thrown exceptions are propogated back to the parent process over a pipe
require 'global' STD_TRAP = trap("INT") { exit! 130 } # no backtrace thanks
at_exit do at_exit do
# the whole of everything must be run in at_exit because the formula has to # the whole of everything must be run in at_exit because the formula has to
# be the run script as __END__ must work for *that* formula. # be the run script as __END__ must work for *that* formula.
main
end
error_pipe = nil require 'global'
begin def main
# The main Homebrew process expects to eventually see EOF on the error # The main Homebrew process expects to eventually see EOF on the error
# pipe in FormulaInstaller#build. However, if any child process fails to # pipe in FormulaInstaller#build. However, if any child process fails to
# terminate (i.e, fails to close the descriptor), this won't happen, and # terminate (i.e, fails to close the descriptor), this won't happen, and
# the installer will hang. Set close-on-exec to prevent this. # the installer will hang. Set close-on-exec to prevent this.
# Whether it is *wise* to launch daemons from formulae is a separate # Whether it is *wise* to launch daemons from formulae is a separate
# question altogether. # question altogether.
if ENV['HOMEBREW_ERROR_PIPE'] if ENV['HOMEBREW_ERROR_PIPE']
require 'fcntl' require 'fcntl'
error_pipe = IO.new(ENV['HOMEBREW_ERROR_PIPE'].to_i, 'w') error_pipe = IO.new(ENV['HOMEBREW_ERROR_PIPE'].to_i, 'w')
error_pipe.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) error_pipe.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
end end
raise $! if $! # an exception was already thrown when parsing the formula raise $! if $! # an exception was already thrown when parsing the formula
require 'hardware' trap("INT", STD_TRAP) # restore default CTRL-C handler
require 'keg'
# Force any future invocations of sudo to require the user's password to be require 'hardware'
# re-entered. This is in-case any build script call sudo. Certainly this is require 'keg'
# can be inconvenient for the user. But we need to be safe.
system "/usr/bin/sudo -k"
install(Formula.factory($0)) # Force any future invocations of sudo to require the user's password to be
rescue Exception => e # re-entered. This is in-case any build script call sudo. Certainly this is
unless error_pipe.nil? # can be inconvenient for the user. But we need to be safe.
Marshal.dump(e, error_pipe) system "/usr/bin/sudo -k"
error_pipe.close
exit! 1 install(Formula.factory($0))
else rescue Exception => e
onoe e unless error_pipe.nil?
puts e.backtrace Marshal.dump(e, error_pipe)
exit! 2 error_pipe.close
end exit! 1
else
onoe e
puts e.backtrace
exit! 2
end end
end end

View File

@ -238,6 +238,7 @@ class FormulaInstaller
Process.wait Process.wait
data = read.read data = read.read
raise Marshal.load(data) unless data.nil? or data.empty? raise Marshal.load(data) unless data.nil? or data.empty?
raise Interrupt if $?.exitstatus == 130
raise "Suspicious installation failure" unless $?.success? raise "Suspicious installation failure" unless $?.success?
end end
@ -266,15 +267,18 @@ class FormulaInstaller
end end
keg = Keg.new(f.prefix) keg = Keg.new(f.prefix)
keg.link
rescue Exception => e
onoe "The `brew link` step did not complete successfully."
puts "The formula built, but is not symlinked into #{HOMEBREW_PREFIX}."
puts "You can try again using `brew link #{f.name}`."
keg.unlink
ohai e, e.backtrace if ARGV.debug? begin
@show_summary_heading = true keg.link
rescue Exception => e
onoe "The `brew link` step did not complete successfully"
puts "The formula built, but is not symlinked into #{HOMEBREW_PREFIX}"
puts "You can try again using `brew link #{f.name}'"
ohai e, e.backtrace if ARGV.debug?
@show_summary_heading = true
ignore_interrupts{ keg.unlink }
raise unless e.kind_of? RuntimeError
end
end end
def fix_install_names def fix_install_names

View File

@ -217,7 +217,7 @@ def inreplace path, before=nil, after=nil
end end
def ignore_interrupts def ignore_interrupts
std_trap = trap("INT") {} std_trap = trap("INT") { puts "One sec, just cleaning up" }
yield yield
ensure ensure
trap("INT", std_trap) trap("INT", std_trap)

View File

@ -1,6 +1,8 @@
#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -W0 #!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -W0
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
std_trap = trap("INT") { exit! 130 } # no backtrace thanks
HOMEBREW_BREW_FILE = ENV['HOMEBREW_BREW_FILE'] = File.expand_path(__FILE__) HOMEBREW_BREW_FILE = ENV['HOMEBREW_BREW_FILE'] = File.expand_path(__FILE__)
require 'pathname' require 'pathname'
@ -49,6 +51,8 @@ rescue LoadError => e
end end
begin begin
trap("INT", std_trap) # restore default CTRL-C handler
aliases = {'ls' => :list, aliases = {'ls' => :list,
'homepage' => :home, 'homepage' => :home,
'-S' => :search, '-S' => :search,