Skip to content

Commit

Permalink
Merge pull request #4865 from rolandwalker/allow_all_uninstall
Browse files Browse the repository at this point in the history
DSL: allow all Casks to use `uninstall` stanzas
  • Loading branch information
rolandwalker committed Jun 28, 2014
2 parents 4303119 + ed5ea5e commit 5215201
Show file tree
Hide file tree
Showing 19 changed files with 382 additions and 336 deletions.
3 changes: 3 additions & 0 deletions lib/cask/artifact.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Cask::Artifact; end

require 'cask/artifact/base'
require 'cask/artifact/uninstall_base'
require 'cask/artifact/symlinked'
require 'cask/artifact/hardlinked'

Expand All @@ -19,6 +20,7 @@ module Cask::Artifact; end
require 'cask/artifact/caskroom_only'
require 'cask/artifact/input_method'
require 'cask/artifact/screen_saver'
require 'cask/artifact/uninstall'


module Cask::Artifact
Expand All @@ -42,6 +44,7 @@ def self.artifacts
Cask::Artifact::Binary,
Cask::Artifact::InputMethod,
Cask::Artifact::ScreenSaver,
Cask::Artifact::Uninstall,
Cask::Artifact::AfterBlock,
]
end
Expand Down
4 changes: 2 additions & 2 deletions lib/cask/artifact/after_block.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ def self.me?(cask)
cask.artifacts[:after_uninstall].any?
end

def install
def install_phase
@cask.artifacts[:after_install].each { |block| @cask.instance_eval &block }
end

def uninstall
def uninstall_phase
@cask.artifacts[:after_uninstall].each { |block| @cask.instance_eval &block }
end
end
4 changes: 2 additions & 2 deletions lib/cask/artifact/before_block.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ def self.me?(cask)
cask.artifacts[:before_uninstall].any?
end

def install
def install_phase
@cask.artifacts[:before_install].each { |block| @cask.instance_eval &block }
end

def uninstall
def uninstall_phase
@cask.artifacts[:before_uninstall].each { |block| @cask.instance_eval &block }
end
end
2 changes: 1 addition & 1 deletion lib/cask/artifact/binary.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class Cask::Artifact::Binary < Cask::Artifact::Symlinked
def install
def install_phase
super unless Cask.no_binaries
end
end
4 changes: 2 additions & 2 deletions lib/cask/artifact/nested_container.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
class Cask::Artifact::NestedContainer < Cask::Artifact::Base
def install
def install_phase
@cask.artifacts[:nested_container].each { |container| extract(container) }
end

def uninstall
def uninstall_phase
# no need to take action; we will get removed by rmtree of parent
end

Expand Down
147 changes: 3 additions & 144 deletions lib/cask/artifact/pkg.rb
Original file line number Diff line number Diff line change
@@ -1,40 +1,8 @@
class Cask::Artifact::Pkg < Cask::Artifact::Base
# this class actually covers two keys, :install and :uninstall
def self.artifact_dsl_key
:install
end

def self.read_script_arguments(uninstall_options, key)
script_arguments = uninstall_options[key]

# backwards-compatible string value
if script_arguments.kind_of?(String)
script_arguments = { :executable => script_arguments }
end

# key sanity
permitted_keys = [:args, :input, :executable, :must_succeed]
unknown_keys = script_arguments.keys - permitted_keys
unless unknown_keys.empty?
opoo %Q{Unknown arguments to uninstall :#{key} -- :#{unknown_keys.join(", :")} (ignored). Running "brew update && brew upgrade brew-cask && brew cleanup && brew cask cleanup" will likely fix it.}
end
script_arguments.reject! {|k,v| ! permitted_keys.include?(k)}

# extract executable
if script_arguments.key?(:executable)
executable = script_arguments.delete(:executable)
else
executable = nil
end

unless script_arguments.key?(:must_succeed)
script_arguments[:must_succeed] = true
end

script_arguments.merge!(:sudo => true, :print => true)
return executable, script_arguments
end

def load_pkg_description(pkg_description)
@pkg_relative_path = pkg_description.shift
@pkg_install_opts = pkg_description.shift
Expand All @@ -58,12 +26,12 @@ def pkg_relative_path
@pkg_relative_path
end

def install
def install_phase
@cask.artifacts[:install].each { |pkg_description| run_installer(pkg_description) }
end

def uninstall
manually_uninstall(@cask.artifacts[:uninstall])
def uninstall_phase
# Do nothing. Must be handled explicitly by a separate :uninstall stanza.
end

def run_installer(pkg_description)
Expand All @@ -81,113 +49,4 @@ def run_installer(pkg_description)
args << '-allowUntrusted' if pkg_install_opts :allow_untrusted
@command.run!('/usr/sbin/installer', {:sudo => true, :args => args, :print => true})
end

def manually_uninstall(uninstall_set)
ohai "Running uninstall process for #{@cask}; your password may be necessary"

uninstall_set.each do |uninstall_options|
unknown_keys = uninstall_options.keys - [:early_script, :launchctl, :quit, :signal, :kext, :script, :pkgutil, :files]
unless unknown_keys.empty?
opoo %Q{Unknown arguments to uninstall: #{unknown_keys.join(", ")}. Running "brew update && brew upgrade brew-cask && brew cleanup && brew cask cleanup" will likely fix it.}
end
end

# Preserve prior functionality of script which runs first. Should rarely be needed.
# :early_script should not delete files, better defer that to :script.
# If Cask writers never need :early_script it may be removed in the future.
uninstall_set.select{ |h| h.key?(:early_script) }.each do |uninstall_options|
executable, script_arguments = self.class.read_script_arguments(uninstall_options, :early_script)
ohai "Running uninstall script #{executable}"
raise CaskInvalidError.new(@cask, 'uninstall :early_script without :executable') if executable.nil?
@command.run(@cask.destination_path.join(executable), script_arguments)
sleep 1
end

# :launchctl must come before :quit/:signal for cases where app would instantly re-launch
uninstall_set.select{ |h| h.key?(:launchctl) }.each do |uninstall_options|
Array(uninstall_options[:launchctl]).each do |service|
ohai "Removing launchctl service #{service}"
[false, true].each do |with_sudo|
xml_status = @command.run('/bin/launchctl', :args => ['list', '-x', service], :sudo => with_sudo)
if %r{^<\?xml}.match(xml_status)
@command.run('/bin/launchctl', :args => ['unload', '-w', '--', service], :sudo => with_sudo)
sleep 1
@command.run!('/bin/launchctl', :args => ['remove', service], :sudo => with_sudo)
sleep 1
end
end
end
end

# :quit/:signal must come before :kext so the kext will not be in use by a running process
uninstall_set.select{ |h| h.key?(:quit) }.each do |uninstall_options|
Array(uninstall_options[:quit]).each do |id|
ohai "Quitting application ID #{id}"
num_running = @command.run!('/usr/bin/osascript', :args => ['-e', %Q{tell application "System Events" to count processes whose bundle identifier is "#{id}"}], :sudo => true).to_i
if num_running > 0
@command.run!('/usr/bin/osascript', :args => ['-e', %Q{tell application id "#{id}" to quit}], :sudo => true)
sleep 3
end
end
end

# :signal should come after :quit so it can be used as a backup when :quit fails
uninstall_set.select{ |h| h.key?(:signal) }.each do |uninstall_options|
Array(uninstall_options[:signal]).flatten.each_slice(2) do |pair|
raise CaskInvalidError.new(@cask, 'Each uninstall :signal must have 2 elements.') unless pair.length == 2
signal, id = pair
ohai "Signalling '#{signal}' to application ID '#{id}'"
pid_string = @command.run!('/usr/bin/osascript', :args => ['-e', %Q{tell application "System Events" to get the unix id of every process whose bundle identifier is "#{id}"}], :sudo => true)
if pid_string.match(%r{\A\d+(?:\s*,\s*\d+)*\Z}) # sanity check
pids = pid_string.split(%r{\s*,\s*}).map(&:strip).map(&:to_i)
if pids.length > 0
# Note that unlike :quit, signals are sent from the
# current user (not upgraded to the superuser). This is a
# todo item for the future, but there should be some
# additional thought/safety checks about that, as a
# misapplied "kill" by root could bring down the system.
# The fact that we learned the pid from AppleScript is
# already some degree of protection, though indirect.
Process.kill(signal, *pids)
sleep 3
end
end
end
end

# :kext should be unloaded before attempting to delete the relevant file
uninstall_set.select{ |h| h.key?(:kext) }.each do |uninstall_options|
Array(uninstall_options[:kext]).each do |kext|
ohai "Unloading kernel extension #{kext}"
is_loaded = @command.run!('/usr/sbin/kextstat', :args => ['-l', '-b', kext], :sudo => true)
if is_loaded.length > 1
@command.run!('/sbin/kextunload', :args => ['-b', '--', kext], :sudo => true)
sleep 1
end
end
end

# :script must come before :pkgutil or :files so that the script file is not already deleted
uninstall_set.select{ |h| h.key?(:script) }.each do |uninstall_options|
executable, script_arguments = self.class.read_script_arguments(uninstall_options, :script)
raise CaskInvalidError.new(@cask, 'uninstall :script without :executable.') if executable.nil?
@command.run(@cask.destination_path.join(executable), script_arguments)
sleep 1
end

uninstall_set.select{ |h| h.key?(:pkgutil) }.each do |uninstall_options|
ohai "Removing files from pkgutil Bill-of-Materials"
Array(uninstall_options[:pkgutil]).each do |regexp|
pkgs = Cask::Pkg.all_matching(regexp, @command)
pkgs.each(&:uninstall)
end
end

uninstall_set.select{ |h| h.key?(:files) }.each do |uninstall_options|
Array(uninstall_options[:files]).flatten.each_slice(500) do |file_slice|
ohai "Removing files: #{file_slice.utf8_inspect}"
@command.run!('/bin/rm', :args => file_slice.unshift('-rf', '--'), :sudo => true)
end
end
end
end
4 changes: 2 additions & 2 deletions lib/cask/artifact/symlinked.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,12 @@ def unlink(artifact_spec)
end
end

def install
def install_phase
# the sort is for predictability between Ruby versions
@cask.artifacts[self.class.artifact_dsl_key].sort.each { |artifact| link(artifact) }
end

def uninstall
def uninstall_phase
# the sort is for predictability between Ruby versions
@cask.artifacts[self.class.artifact_dsl_key].sort.each { |artifact| unlink(artifact) }
end
Expand Down
2 changes: 2 additions & 0 deletions lib/cask/artifact/uninstall.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class Cask::Artifact::Uninstall < Cask::Artifact::UninstallBase
end
Loading

0 comments on commit 5215201

Please sign in to comment.