Merge pull request #6155 from rolandwalker/add_zap_stanza

functionality and docs for zap stanza and verb
This commit is contained in:
Roland Walker 2014-09-13 12:55:57 -04:00
commit ce64b38723
17 changed files with 478 additions and 29 deletions

View File

@ -76,6 +76,7 @@ This will both uninstall the Cask and remove symlinks which were created in
* `home` -- opens the homepage of the given Cask; or with no arguments, the homebrew-cask project page
* `alfred` -- modifies Alfred's scope to include the Caskroom
* `update` -- a synonym for `brew update`
* `zap` -- try to remove *all* files associated with a Cask (including resources which may be shared with other applications)
The following commands are for Cask authors:

View File

@ -17,6 +17,7 @@ Cask Domain-Specific Language (DSL) which are not needed in most cases.
* [Pkg Stanza Details](#pkg-stanza-details)
* [Depends_on Stanza Details](#depends_on-stanza-details)
* [Uninstall Stanza Details](#uninstall-stanza-details)
* [Zap Stanza Details](#zap-stanza-details)
* [Arbitrary Ruby Methods](#arbitrary-ruby-methods)
* [Revisions to the Cask DSL](#revisions-to-the-cask-dsl)
@ -93,6 +94,7 @@ Each Cask must declare one or more *artifacts* (i.e. something to install)
| name | multiple occurrences allowed? | value |
| ---------------------- |------------------------------ | ----------- |
| `uninstall` | yes | procedures to uninstall a Cask. Optional unless the `pkg` stanza is used. (see also [Uninstall Stanza Details](#uninstall-stanza-details))
| `zap` | yes | additional procedures for a more complete uninstall, including user files and shared resources. (see also [Zap Stanza Details](#zap-stanza-details))
| `nested_container` | yes | relative path to an inner container that must be extracted before moving on with the installation; this allows us to support dmg inside tar, zip inside dmg, etc.
| `depends_on` | yes | a list of dependencies required by this Cask (see also [Depends_on Stanza Details](#depends_on-stanza-details))
| `caveats` | yes | a string or Ruby block providing the user with Cask-specific information at install time (see also [Caveats Stanza Details](#caveats-stanza-details))
@ -430,7 +432,7 @@ of the following key/value pairs as arguments to `uninstall`.
- `:args` - array of arguments to the uninstall script
- `:input` - array of lines of input to be sent to `stdin` of the script
- `:must_succeed` - set to `false` if the script is allowed to fail
* `:files` (array) - absolute paths of files or directories to remove. `:files` should only be used as a last resort. `:pkgutil` is strongly preferred
* `:files` (array) - single-quoted, absolute paths of files or directories to remove. `:files` should only be used as a last resort. `:pkgutil` is strongly preferred
Each `uninstall` technique is applied according to the order above. The order
in which `uninstall` keys appear in the Cask file is ignored.
@ -551,6 +553,24 @@ IDs inside a kext bundle you have located on disk can be listed using the comman
$ ./developer/bin/list_id_in_kext </path/to/name.kext>
```
### Uninstall Key :files
`:files` should only be used as a last resort, if other `uninstall` methods
are insufficient.
Arguments to `uninstall :files` should be static, single-quoted, absolute
paths.
* Only single quotes should be used.
* Double-quotes should not be used. `ENV['HOME']` and other variables
should not be interpolated in the value.
* Only absolute paths should be given.
* No tilde expansion is performed (`~` characters are literal).
* No glob expansion is performed (*eg* `*` characters are literal), though
glob expansion is a desired future feature.
To remove user-specific files, use the `zap` stanza.
### Working With a pkg File Manually
Advanced users may wish to work with a `pkg` file manually, without having the
@ -587,6 +607,37 @@ A fully manual method for finding bundle ids in a package file follows:
5. Once bundle ids have been identified, the unpacked package directory can be deleted.
## Zap Stanza Details
### Zap Stanza Purpose
The `zap` stanza describes a more complete uninstallation of resources
associated with a Cask. The `zap` procedures will never be performed
by default, but only if the user invokes the `zap` verb:
```bash
$ brew cask zap td-toolbelt # also removes org.ruby-lang.installer
```
`zap` stanzas may remove:
* Preference files and caches stored within the user's `~/Library` directory.
* Shared resources such as application updaters. Since shared resources
may be removed, other applications may be affected by `brew cask zap`.
Understanding that is the responsibility of the end user.
### Zap Stanza Syntax
The form of `zap` stanza follows the [`uninstall` stanza](#uninstall-stanza-details).
All of the same directives are available.
`zap` differs from `uninstall` in the interpretation of values paired with
the `:files` key:
* Leading tilde characters (`~`) will be expanded to home directories, as
in the shell.
## Arbitrary Ruby Methods
In the exceptional case that the Cask DSL is insufficient, it is possible to

View File

@ -41,6 +41,7 @@ This notice will be removed for the final form.**
* stub - not yet functional
* `depends_on :java`
* stub - not yet functional
* `zap`
## Renames (1.0)

View File

@ -89,6 +89,22 @@ names, and other aspects of this manual are still subject to change.
* `uninstall` or `rm` or `remove` <Cask>:
Uninstall <Cask>.
* `zap` <Cask>:
Unconditionally remove _all_ files associated with <Cask>.
Implicitly performs all actions associated with `uninstall`, even if
the Cask does not appear to be currently installed.
Removes all staged versions of the Cask distribution found under
`/opt/homebrew-cask/Caskroom/<Cask>`
If the Cask definition contains a `zap` stanza, performs additional
`zap` actions as defined there, such as removing local preference
files. `zap` actions are variable, depending on the level of detail
defined by the Cask author.
**`zap` may remove resources which are shared between applications.**
* `search` or `-S`:
Display all Casks available for install.

View File

@ -24,6 +24,10 @@ class Cask::Artifact::Base
cask.artifacts[self.artifact_dsl_key].any?
end
def zap_phase
odebug "Nothing to do. The #{self.class.artifact_name} artifact has no zap phase."
end
# todo: this sort of logic would make more sense in dsl.rb, or a
# constructor called from dsl.rb, so long as that isn't slow.
def self.read_script_arguments(arguments, stanza, default_arguments={}, override_arguments={}, key=nil)

View File

@ -1,7 +1,28 @@
class Cask::Artifact::UninstallBase < Cask::Artifact::Base
# todo: 500 is also hardcoded in cask/pkg.rb, but much of
# that logic is probably in the wrong location
PATH_ARG_SLICE_SIZE = 500
# todo: these methods were consolidated here from separate
# sources and, should be refactored for consistency
# sources and should be refactored for consistency
def self.expand_path_strings(path_strings)
path_strings.map do |path_string|
%r{\A~}.match(path_string) ? Pathname.new(path_string).expand_path : Pathname.new(path_string)
end
end
def self.remove_relative_path_strings(action, path_strings)
relative = path_strings.reject do |path_string|
%r{\A/}.match(path_string)
end
relative.each do |path_string|
opoo %Q{Skipping #{action} for relative path #{path_string}}
end
path_strings - relative
end
def install_phase
odebug "Nothing to do. The uninstall artifact has no install phase."
@ -11,7 +32,7 @@ class Cask::Artifact::UninstallBase < Cask::Artifact::Base
dispatch_uninstall_directives(self.class.artifact_dsl_key)
end
def dispatch_uninstall_directives(stanza)
def dispatch_uninstall_directives(stanza, expand_tilde=false)
directives_set = @cask.artifacts[stanza]
ohai "Running #{stanza} process for #{@cask}; your password may be necessary"
@ -123,26 +144,32 @@ class Cask::Artifact::UninstallBase < Cask::Artifact::Base
end
directives_set.select{ |h| h.key?(:delete) }.each do |directives|
Array(directives[:delete]).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)
Array(directives[:delete]).flatten.each_slice(PATH_ARG_SLICE_SIZE) do |path_slice|
ohai "Removing files: #{path_slice.utf8_inspect}"
path_slice = self.class.expand_path_strings(path_slice) if expand_tilde
path_slice = self.class.remove_relative_path_strings(:delete, path_slice)
@command.run!('/bin/rm', :args => path_slice.unshift('-rf', '--'), :sudo => true)
end
end
# :trash functionality is stubbed as a synonym for :delete
# todo: make :trash work differently, moving files to the Trash
directives_set.select{ |h| h.key?(:trash) }.each do |directives|
Array(directives[:trash]).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)
Array(directives[:trash]).flatten.each_slice(PATH_ARG_SLICE_SIZE) do |path_slice|
ohai "Removing files: #{path_slice.utf8_inspect}"
path_slice = self.class.expand_path_strings(path_slice) if expand_tilde
path_slice = self.class.remove_relative_path_strings(:trash, path_slice)
@command.run!('/bin/rm', :args => path_slice.unshift('-rf', '--'), :sudo => true)
end
end
# todo: remove support for deprecated :files both here and elsewhere
directives_set.select{ |h| h.key?(:files) }.each do |directives|
Array(directives[: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)
Array(directives[:files]).flatten.each_slice(PATH_ARG_SLICE_SIZE) do |path_slice|
ohai "Removing files: #{path_slice.utf8_inspect}"
path_slice = self.class.expand_path_strings(path_slice) if expand_tilde
path_slice = self.class.remove_relative_path_strings(:delete, path_slice) # :delete for messages
@command.run!('/bin/rm', :args => path_slice.unshift('-rf', '--'), :sudo => true)
end
end
end

View File

@ -8,6 +8,7 @@ class Cask::Artifact::Zap < Cask::Artifact::UninstallBase
end
def zap_phase
dispatch_uninstall_directives(self.class.artifact_dsl_key)
expand_tilde = true
dispatch_uninstall_directives(self.class.artifact_dsl_key, expand_tilde)
end
end

View File

@ -20,6 +20,7 @@ require 'cask/cli/list'
require 'cask/cli/search'
require 'cask/cli/uninstall'
require 'cask/cli/update'
require 'cask/cli/zap'
require 'cask/cli/internal_use_base'
require 'cask/cli/internal_dump'

15
lib/cask/cli/zap.rb Normal file
View File

@ -0,0 +1,15 @@
class Cask::CLI::Zap < Cask::CLI::Base
def self.run(*args)
raise CaskUnspecifiedError if args.empty?
cask_names = args.reject { |a| a.chars.first == '-' }
cask_names.each do |cask_name|
odebug "Zapping Cask #{cask_name}"
cask = Cask.load(cask_name)
Cask::Installer.new(cask).zap
end
end
def self.help
"zaps all files associated with the cask of the given name"
end
end

View File

@ -50,7 +50,7 @@ class Cask::Installer
extract_primary_container
install_artifacts
rescue
purge_files
purge_versioned_files
raise
end
@ -125,7 +125,7 @@ class Cask::Installer
def uninstall
odebug "Cask::Installer.uninstall"
uninstall_artifacts
purge_files
purge_versioned_files
end
def uninstall_artifacts
@ -138,26 +138,43 @@ class Cask::Installer
end
end
def purge_files
odebug "Purging files"
def zap
odebug "Zap: implied uninstall"
uninstall_artifacts
if Cask::Artifact::Zap.me?(@cask)
odebug "Dispatching zap directives"
Cask::Artifact::Zap.new(@cask, @command).zap_phase
end
purge_caskroom_path
end
# versioned staged distribution
if @cask.destination_path.respond_to?(:rmtree) and @cask.destination_path.exist?
# this feels like a class method, but uses @command
def permissions_rmtree(path)
if path.respond_to?(:rmtree) and path.exist?
begin
@cask.destination_path.rmtree
path.rmtree
rescue
# in case of permissions problems
if @cask.destination_path.exist?
@command.run('/bin/chmod', :args => ['-R', '--', 'u+rwx', @cask.destination_path])
@command.run('/bin/chmod', :args => ['-R', '-N', @cask.destination_path])
@cask.destination_path.rmtree
if path.exist?
@command.run('/bin/chmod', :args => ['-R', '--', 'u+rwx', path])
@command.run('/bin/chmod', :args => ['-R', '-N', path])
path.rmtree
end
end
end
end
if @cask.metadata_versioned_container_path.respond_to?(:children) and @cask.metadata_versioned_container_path.exist?
def purge_versioned_files
odebug "Purging files for version #{@cask.version} of Cask #{@cask}"
# versioned staged distribution
permissions_rmtree(@cask.destination_path)
# Homebrew-cask metadata
if @cask.metadata_versioned_container_path.respond_to?(:children) and
@cask.metadata_versioned_container_path.exist?
@cask.metadata_versioned_container_path.children.each do |subdir|
subdir.rmtree unless PERSISTENT_METADATA_SUBDIRS.include?(subdir.basename)
permissions_rmtree subdir unless PERSISTENT_METADATA_SUBDIRS.include?(subdir.basename)
end
end
if @cask.metadata_versioned_container_path.respond_to?(:rmdir_if_possible)
@ -170,4 +187,9 @@ class Cask::Installer
# toplevel staged distribution
@cask.caskroom_path.rmdir_if_possible
end
def purge_caskroom_path
odebug "Purging all staged versions of Cask #{@cask}"
permissions_rmtree(@cask.caskroom_path)
end
end

View File

@ -17,8 +17,17 @@ describe Cask::Artifact::Uninstall do
end
end
describe 'zap_phase' do
it 'does nothing, because the zap_phase method is a no-op' do
uninstall_artifact = Cask::Artifact::Uninstall.new(@cask, Cask::FakeSystemCommand)
shutup do
uninstall_artifact.zap_phase
end
end
end
describe 'uninstall_phase' do
# todo: uninstall tests for :signal (implementation does not use SystemComment)
# todo: uninstall tests for :signal (implementation does not use SystemCommand)
it 'runs the specified uninstaller for the cask' do
uninstall_artifact = Cask::Artifact::Uninstall.new(@cask, Cask::FakeSystemCommand)
@ -26,6 +35,7 @@ describe Cask::Artifact::Uninstall do
Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/bin/osascript', '-e', 'tell application id "my.fancy.package.app" to quit'])
Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', @cask.destination_path/'MyFancyPkg'/'FancyUninstaller.tool', '--please'])
Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', '/bin/rm', '-rf', '--', '/permissible/absolute/path'])
shutup do
uninstall_artifact.uninstall_phase

View File

@ -0,0 +1,206 @@
require 'test_helper'
# todo
# - test that zap removes an alternate version of the same Cask
describe Cask::Artifact::Zap do
before {
@cask = Cask.load('with-zap')
shutup do
TestHelper.install_without_artifacts(@cask)
end
}
describe 'install_phase' do
it 'does nothing, because the install_phase method is a no-op' do
zap_artifact = Cask::Artifact::Zap.new(@cask, Cask::FakeSystemCommand)
shutup do
zap_artifact.install_phase
end
end
end
describe 'uninstall_phase' do
it 'does nothing, because the uninstall_phase method is a no-op' do
zap_artifact = Cask::Artifact::Zap.new(@cask, Cask::FakeSystemCommand)
shutup do
zap_artifact.uninstall_phase
end
end
end
describe 'zap_phase' do
# todo: zap tests for :signal (implementation does not use SystemCommand)
it 'runs the specified zap procedures for the cask' do
zap_artifact = Cask::Artifact::Zap.new(@cask, Cask::FakeSystemCommand)
Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/bin/osascript', '-e', 'tell application "System Events" to count processes whose bundle identifier is "my.fancy.package.app"'], '1')
Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/bin/osascript', '-e', 'tell application id "my.fancy.package.app" to quit'])
Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', @cask.destination_path/'MyFancyPkg'/'FancyUninstaller.tool', '--please'])
Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', '/bin/rm', '-rf', '--',
Pathname.new('~/Library/Preferences/my.fancy.app.plist').expand_path])
shutup do
zap_artifact.zap_phase
end
end
it 'can zap using pkgutil, launchctl, and file lists' do
cask = Cask.load('with-pkgutil-zap')
zap_artifact = Cask::Artifact::Zap.new(cask, Cask::FakeSystemCommand)
Cask::FakeSystemCommand.stubs_command(
['/usr/sbin/pkgutil', '--pkgs=my.fancy.package.*'],
[
'my.fancy.package.main',
'my.fancy.package.agent',
].join("\n")
)
Cask::FakeSystemCommand.stubs_command(
['/usr/sbin/pkgutil', '--only-files', '--files', 'my.fancy.package.main'],
[
'fancy/bin/fancy.exe',
'fancy/var/fancy.data',
].join("\n")
)
Cask::FakeSystemCommand.stubs_command(
['/usr/sbin/pkgutil', '--only-dirs', '--files', 'my.fancy.package.main'],
[
'fancy',
'fancy/bin',
'fancy/var',
].join("\n")
)
Cask::FakeSystemCommand.stubs_command(
['/usr/sbin/pkgutil', '--files', 'my.fancy.package.main'],
[
'fancy',
'fancy/bin',
'fancy/var',
'fancy/bin/fancy.exe',
'fancy/var/fancy.data',
].join("\n")
)
Cask::FakeSystemCommand.stubs_command(
['/usr/sbin/pkgutil', '--pkg-info-plist', 'my.fancy.package.main'],
<<-PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>install-location</key>
<string>tmp</string>
<key>volume</key>
<string>/</string>
</dict>
</plist>
PLIST
)
Cask::FakeSystemCommand.stubs_command(
['/bin/launchctl', 'list', '-x', 'my.fancy.package.service'],
"launchctl list returned unknown response\n"
)
Cask::FakeSystemCommand.stubs_command(
['/usr/bin/sudo', '-E', '--', '/bin/launchctl', 'list', '-x', 'my.fancy.package.service'],
<<-"PLIST"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
\t<key>Label</key>
\t<string>my.fancy.package.service</string>
\t<key>LastExitStatus</key>
\t<integer>0</integer>
\t<key>LimitLoadToSessionType</key>
\t<string>System</string>
\t<key>OnDemand</key>
\t<true/>
\t<key>ProgramArguments</key>
\t<array>
\t\t<string>argument</string>
\t</array>
\t<key>TimeOut</key>
\t<integer>30</integer>
</dict>
</plist>
PLIST
)
Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', '/bin/launchctl', 'remove', 'my.fancy.package.service'])
Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', '/bin/launchctl', 'unload', '-w', '--', 'my.fancy.package.service'])
Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/sbin/kextstat', '-l', '-b', 'my.fancy.package.kernelextension'], 'loaded')
Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', '/sbin/kextunload', '-b', '--', 'my.fancy.package.kernelextension'])
Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/sbin/pkgutil', '--forget', 'my.fancy.package.main'])
Cask::FakeSystemCommand.stubs_command(
['/usr/sbin/pkgutil', '--only-files', '--files', 'my.fancy.package.agent'],
[
'fancy/agent/fancy-agent.exe',
'fancy/agent/fancy-agent.pid',
'fancy/agent/fancy-agent.log',
].join("\n")
)
Cask::FakeSystemCommand.stubs_command(
['/usr/sbin/pkgutil', '--only-dirs', '--files', 'my.fancy.package.agent'],
[
'fancy',
'fancy/agent',
].join("\n")
)
Cask::FakeSystemCommand.stubs_command(
['/usr/sbin/pkgutil', '--files', 'my.fancy.package.agent'],
[
'fancy',
'fancy/agent',
'fancy/agent/fancy-agent.exe',
'fancy/agent/fancy-agent.pid',
'fancy/agent/fancy-agent.log',
].join("\n")
)
Cask::FakeSystemCommand.stubs_command(
['/usr/sbin/pkgutil', '--pkg-info-plist', 'my.fancy.package.agent'],
<<-PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>install-location</key>
<string>tmp</string>
<key>volume</key>
<string>/</string>
</dict>
</plist>
PLIST
)
%w[
/tmp/fancy
/tmp/fancy/agent
/tmp/fancy/bin
/tmp/fancy/var
].each do |dir|
Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/bin/chmod', '--', '777', '#{dir}'])
end
Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/sbin/pkgutil', '--forget', 'my.fancy.package.agent'])
Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/bin/rm', '-f', '--',
Pathname.new('/tmp/fancy/bin/fancy.exe'),
Pathname.new('/tmp/fancy/var/fancy.data')])
Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/bin/rm', '-f', '--',
Pathname.new('/tmp/fancy/agent/fancy-agent.exe'),
Pathname.new('/tmp/fancy/agent/fancy-agent.pid'),
Pathname.new('/tmp/fancy/agent/fancy-agent.log')])
# No assertions after call since all assertions are implicit from the interactions setup above.
# TODO: verify rmdir commands (requires setting up actual file tree or faking out .exists?
shutup do
zap_artifact.zap_phase
end
end
end
end

65
test/cask/cli/zap_test.rb Normal file
View File

@ -0,0 +1,65 @@
require 'test_helper'
describe Cask::CLI::Zap do
it "shows an error when a bad cask is provided" do
lambda {
Cask::CLI::Zap.run('notacask')
}.must_raise CaskUnavailableError
end
it "can zap and unlink multiple casks at once" do
caffeine = Cask.load('local-caffeine')
transmission = Cask.load('local-transmission')
shutup do
Cask::Installer.new(caffeine).install
Cask::Installer.new(transmission).install
end
caffeine.must_be :installed?
transmission.must_be :installed?
shutup do
Cask::CLI::Zap.run('local-caffeine', 'local-transmission')
end
caffeine.wont_be :installed?
Cask.appdir.join('Transmission.app').wont_be :symlink?
transmission.wont_be :installed?
Cask.appdir.join('Caffeine.app').wont_be :symlink?
end
# todo
# Explicit test that both zap and uninstall directives get dispatched.
# The above tests that implicitly.
#
# it "dispatches both uninstall and zap stanzas" do
# with_zap = Cask.load('with-zap')
#
# shutup do
# Cask::Installer.new(with_zap).install
# end
#
# with_zap.must_be :installed?
#
# Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/bin/osascript', '-e', 'tell application "System Events" to count processes whose bundle identifier is "my.fancy.package.app"'], '1')
# Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/bin/osascript', '-e', 'tell application id "my.fancy.package.app" to quit'])
# Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/bin/osascript', '-e', 'tell application "System Events" to count processes whose bundle identifier is "my.fancy.package.app.from.uninstall"'], '1')
# Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/bin/osascript', '-e', 'tell application id "my.fancy.package.app.from.uninstall" to quit'])
#
# Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', with_zap.destination_path/'MyFancyPkg'/'FancyUninstaller.tool', '--please'])
# Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', '/bin/rm', '-rf', '--',
# Pathname.new('~/Library/Preferences/my.fancy.app.plist').expand_path])
#
# shutup do
# Cask::CLI::Zap.run('with-zap')
# end
# with_zap.wont_be :installed?
# end
it "raises an exception when no cask is specified" do
lambda {
Cask::CLI::Uninstall.run
}.must_raise CaskUnspecifiedError
end
end

View File

@ -5,5 +5,10 @@ class WithInstallable < TestCask
sha256 '8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b'
pkg 'MyFancyPkg/Fancy.pkg'
uninstall :script => { :executable => 'MyFancyPkg/FancyUninstaller.tool', :args => %w[--please] },
:quit => 'my.fancy.package.app'
:quit => 'my.fancy.package.app',
:files => [
'/permissible/absolute/path',
'~/impermissible/path/with/tilde',
'impermissible/relative/path',
]
end

View File

@ -0,0 +1,10 @@
class WithPkgutilZap < TestCask
url TestHelper.local_binary('MyFancyPkg.zip')
homepage 'http://example.com/fancy-pkg'
version '1.2.3'
sha256 '8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b'
pkg 'Fancy.pkg'
zap :pkgutil => 'my.fancy.package.*',
:kext => 'my.fancy.package.kernelextension',
:launchctl => 'my.fancy.package.service'
end

View File

@ -0,0 +1,14 @@
class WithZap < TestCask
url TestHelper.local_binary('MyFancyPkg.zip')
homepage 'http://example.com/fancy-pkg'
version '1.2.3'
sha256 '8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b'
pkg 'MyFancyPkg/Fancy.pkg'
uninstall :quit => 'my.fancy.package.app.from.uninstall'
zap :script => {
:executable => 'MyFancyPkg/FancyUninstaller.tool',
:args => %w[--please]
},
:quit => 'my.fancy.package.app',
:files => '~/Library/Preferences/my.fancy.app.plist'
end

View File

@ -3,7 +3,7 @@ module Cask::CleanupHooks
super
Cask.installed.each do |cask|
Cask::Installer.new(cask).tap do |installer|
installer.purge_files
installer.purge_versioned_files
end
end
end