Merge pull request #6155 from rolandwalker/add_zap_stanza
functionality and docs for zap stanza and verb
This commit is contained in:
commit
ce64b38723
1
USAGE.md
1
USAGE.md
|
@ -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:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue