| 
									
										
										
										
											2020-10-10 14:16:11 +02:00
										 |  |  | # typed: false | 
					
						
							| 
									
										
										
										
											2019-04-19 15:38:03 +09:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-09 09:32:54 +01:00
										 |  |  | require "timeout" | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-01 12:29:21 +02:00
										 |  |  | require "utils/user" | 
					
						
							| 
									
										
										
										
											2018-09-03 19:39:07 +01:00
										 |  |  | require "cask/artifact/abstract_artifact" | 
					
						
							| 
									
										
										
										
											2020-07-27 18:04:29 +02:00
										 |  |  | require "cask/pkg" | 
					
						
							| 
									
										
										
										
											2019-02-06 19:04:29 +01:00
										 |  |  | require "extend/hash_validator" | 
					
						
							|  |  |  | using HashValidator | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-06 08:29:14 +02:00
										 |  |  | module Cask | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |   module Artifact | 
					
						
							| 
									
										
										
										
											2020-08-19 10:23:41 +02:00
										 |  |  |     # Abstract superclass for uninstall artifacts. | 
					
						
							|  |  |  |     # | 
					
						
							|  |  |  |     # @api private | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |     class AbstractUninstall < AbstractArtifact | 
					
						
							| 
									
										
										
										
											2020-10-20 12:03:48 +02:00
										 |  |  |       extend T::Sig | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |       ORDERED_DIRECTIVES = [ | 
					
						
							| 
									
										
										
										
											2016-10-14 20:33:16 +02:00
										 |  |  |         :early_script, | 
					
						
							|  |  |  |         :launchctl, | 
					
						
							|  |  |  |         :quit, | 
					
						
							|  |  |  |         :signal, | 
					
						
							|  |  |  |         :login_item, | 
					
						
							|  |  |  |         :kext, | 
					
						
							|  |  |  |         :script, | 
					
						
							|  |  |  |         :pkgutil, | 
					
						
							|  |  |  |         :delete, | 
					
						
							|  |  |  |         :trash, | 
					
						
							|  |  |  |         :rmdir, | 
					
						
							|  |  |  |       ].freeze | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |       def self.from_args(cask, **directives) | 
					
						
							|  |  |  |         new(cask, directives) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       attr_reader :directives | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def initialize(cask, directives) | 
					
						
							| 
									
										
										
										
											2019-02-06 19:04:29 +01:00
										 |  |  |         directives.assert_valid_keys!(*ORDERED_DIRECTIVES) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |         super(cask) | 
					
						
							| 
									
										
										
										
											2020-07-13 22:48:53 +10:00
										 |  |  |         directives[:signal] = Array(directives[:signal]).flatten.each_slice(2).to_a | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |         @directives = directives | 
					
						
							| 
									
										
										
										
											2017-11-24 17:44:01 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return unless directives.key?(:kext) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         cask.caveats do | 
					
						
							|  |  |  |           kext | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-04 14:59:18 +02:00
										 |  |  |       def to_h | 
					
						
							|  |  |  |         directives.to_h | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-20 12:03:48 +02:00
										 |  |  |       sig { returns(String) } | 
					
						
							| 
									
										
										
										
											2017-09-11 23:29:38 +02:00
										 |  |  |       def summarize | 
					
						
							| 
									
										
										
										
											2020-07-13 22:48:53 +10:00
										 |  |  |         to_h.flat_map { |key, val| Array(val).map { |v| "#{key.inspect} => #{v.inspect}" } }.join(", ") | 
					
						
							| 
									
										
										
										
											2017-09-11 23:29:38 +02:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |       private | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def dispatch_uninstall_directives(**options) | 
					
						
							| 
									
										
										
										
											2019-02-06 19:04:29 +01:00
										 |  |  |         ORDERED_DIRECTIVES.each do |directive_sym| | 
					
						
							|  |  |  |           dispatch_uninstall_directive(directive_sym, **options) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-06 19:04:29 +01:00
										 |  |  |       def dispatch_uninstall_directive(directive_sym, **options) | 
					
						
							|  |  |  |         return unless directives.key?(directive_sym) | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-06 19:04:29 +01:00
										 |  |  |         args = directives[directive_sym] | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-06 19:04:29 +01:00
										 |  |  |         send("uninstall_#{directive_sym}", *(args.is_a?(Hash) ? [args] : args), **options) | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |       def stanza | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |         self.class.dsl_key | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |       # Preserve prior functionality of script which runs first. Should rarely be needed. | 
					
						
							|  |  |  |       # :early_script should not delete files, better defer that to :script. | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       # If cask writers never need :early_script it may be removed in the future. | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |       def uninstall_early_script(directives, **options) | 
					
						
							|  |  |  |         uninstall_script(directives, directive_name: :early_script, **options) | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |       # :launchctl must come before :quit/:signal for cases where app would instantly re-launch | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |       def uninstall_launchctl(*services, command: nil, **_) | 
					
						
							| 
									
										
										
										
											2020-09-11 10:29:21 +01:00
										 |  |  |         booleans = [false, true] | 
					
						
							| 
									
										
										
										
											2017-03-08 03:03:36 +01:00
										 |  |  |         services.each do |service| | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |           ohai "Removing launchctl service #{service}" | 
					
						
							| 
									
										
										
										
											2020-09-11 10:29:21 +01:00
										 |  |  |           booleans.each do |with_sudo| | 
					
						
							| 
									
										
										
										
											2018-09-02 16:15:09 +01:00
										 |  |  |             plist_status = command.run( | 
					
						
							|  |  |  |               "/bin/launchctl", | 
					
						
							|  |  |  |               args: ["list", service], | 
					
						
							|  |  |  |               sudo: with_sudo, print_stderr: false | 
					
						
							|  |  |  |             ).stdout | 
					
						
							| 
									
										
										
										
											2020-05-22 08:52:26 +01:00
										 |  |  |             if plist_status.start_with?("{") | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |               command.run!("/bin/launchctl", args: ["remove", service], sudo: with_sudo) | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |               sleep 1
 | 
					
						
							|  |  |  |             end | 
					
						
							| 
									
										
										
										
											2018-11-05 02:00:40 +01:00
										 |  |  |             paths = [ | 
					
						
							| 
									
										
										
										
											2019-04-20 14:07:29 +09:00
										 |  |  |               +"/Library/LaunchAgents/#{service}.plist", | 
					
						
							|  |  |  |               +"/Library/LaunchDaemons/#{service}.plist", | 
					
						
							| 
									
										
										
										
											2018-11-05 02:00:40 +01:00
										 |  |  |             ] | 
					
						
							| 
									
										
										
										
											2019-04-20 14:07:29 +09:00
										 |  |  |             paths.each { |elt| elt.prepend(ENV["HOME"]).freeze } unless with_sudo | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |             paths = paths.map { |elt| Pathname(elt) }.select(&:exist?) | 
					
						
							|  |  |  |             paths.each do |path| | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |               command.run!("/bin/rm", args: ["-f", "--", path], sudo: with_sudo) | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |             end | 
					
						
							|  |  |  |             # undocumented and untested: pass a path to uninstall :launchctl | 
					
						
							|  |  |  |             next unless Pathname(service).exist? | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |             command.run!("/bin/launchctl", args: ["unload", "-w", "--", service], sudo: with_sudo) | 
					
						
							| 
									
										
										
										
											2018-11-05 22:40:07 +01:00
										 |  |  |             command.run!("/bin/rm", args: ["-f", "--", service], sudo: with_sudo) | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |             sleep 1
 | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-05 22:40:07 +01:00
										 |  |  |       def running_processes(bundle_id) | 
					
						
							|  |  |  |         system_command!("/bin/launchctl", args: ["list"]) | 
					
						
							| 
									
										
										
										
											2020-12-26 14:01:22 +01:00
										 |  |  |           .stdout.lines.drop(1) | 
					
						
							| 
									
										
										
										
											2018-11-05 22:40:07 +01:00
										 |  |  |           .map { |line| line.chomp.split("\t") } | 
					
						
							|  |  |  |           .map { |pid, state, id| [pid.to_i, state.to_i, id] } | 
					
						
							|  |  |  |           .select do |(pid, _, id)| | 
					
						
							| 
									
										
										
										
											2020-12-26 14:01:22 +01:00
										 |  |  |             pid.nonzero? && /\A(?:application\.)?#{Regexp.escape(bundle_id)}(?:\.\d+){0,2}\Z/.match?(id) | 
					
						
							| 
									
										
										
										
											2018-11-05 22:40:07 +01:00
										 |  |  |           end | 
					
						
							| 
									
										
										
										
											2017-03-08 03:03:36 +01:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-20 12:03:48 +02:00
										 |  |  |       sig { returns(String) } | 
					
						
							| 
									
										
										
										
											2019-10-22 05:34:39 +02:00
										 |  |  |       def automation_access_instructions | 
					
						
							| 
									
										
										
										
											2020-07-06 15:31:47 -04:00
										 |  |  |         <<~EOS | 
					
						
							|  |  |  |           Enable Automation access for "Terminal → System Events" in: | 
					
						
							|  |  |  |             System Preferences → Security & Privacy → Privacy → Automation | 
					
						
							|  |  |  |           if you haven't already. | 
					
						
							|  |  |  |         EOS | 
					
						
							| 
									
										
										
										
											2019-10-22 05:34:39 +02:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |       # :quit/:signal must come before :kext so the kext will not be in use by a running process | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |       def uninstall_quit(*bundle_ids, command: nil, **_) | 
					
						
							| 
									
										
										
										
											2017-03-08 03:03:36 +01:00
										 |  |  |         bundle_ids.each do |bundle_id| | 
					
						
							| 
									
										
										
										
											2019-09-15 01:08:04 +02:00
										 |  |  |           next unless running?(bundle_id) | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-01 12:29:21 +02:00
										 |  |  |           unless User.current.gui? | 
					
						
							| 
									
										
										
										
											2019-09-13 05:50:26 +02:00
										 |  |  |             opoo "Not logged into a GUI; skipping quitting application ID '#{bundle_id}'." | 
					
						
							|  |  |  |             next | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-05 02:00:40 +01:00
										 |  |  |           ohai "Quitting application '#{bundle_id}'..." | 
					
						
							| 
									
										
										
										
											2016-11-09 09:32:54 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |           begin | 
					
						
							| 
									
										
										
										
											2018-11-05 02:00:40 +01:00
										 |  |  |             Timeout.timeout(10) do | 
					
						
							| 
									
										
										
										
											2016-11-09 09:32:54 +01:00
										 |  |  |               Kernel.loop do | 
					
						
							| 
									
										
										
										
											2018-11-05 02:00:40 +01:00
										 |  |  |                 next unless quit(bundle_id).success? | 
					
						
							| 
									
										
										
										
											2019-02-19 13:12:52 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-15 01:08:04 +02:00
										 |  |  |                 next if running?(bundle_id) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 puts "Application '#{bundle_id}' quit successfully." | 
					
						
							|  |  |  |                 break | 
					
						
							| 
									
										
										
										
											2016-11-09 09:32:54 +01:00
										 |  |  |               end | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |           rescue Timeout::Error | 
					
						
							| 
									
										
										
										
											2019-10-22 05:34:39 +02:00
										 |  |  |             opoo "Application '#{bundle_id}' did not quit. #{automation_access_instructions}" | 
					
						
							| 
									
										
										
										
											2016-11-09 09:32:54 +01:00
										 |  |  |           end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-15 01:08:04 +02:00
										 |  |  |       def running?(bundle_id) | 
					
						
							|  |  |  |         script = <<~JAVASCRIPT | 
					
						
							|  |  |  |           'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           ObjC.import('stdlib') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           function run(argv) { | 
					
						
							|  |  |  |             try { | 
					
						
							|  |  |  |               var app = Application(argv[0]) | 
					
						
							|  |  |  |               if (app.running()) { | 
					
						
							|  |  |  |                 $.exit(0) | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             } catch (err) { } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             $.exit(1) | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         JAVASCRIPT | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         system_command("osascript", args:         ["-l", "JavaScript", "-e", script, bundle_id], | 
					
						
							|  |  |  |                                     print_stderr: true).status.success? | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-05 02:00:40 +01:00
										 |  |  |       def quit(bundle_id) | 
					
						
							|  |  |  |         script = <<~JAVASCRIPT | 
					
						
							|  |  |  |           'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           ObjC.import('stdlib') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           function run(argv) { | 
					
						
							|  |  |  |             var app = Application(argv[0]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             try { | 
					
						
							|  |  |  |               app.quit() | 
					
						
							|  |  |  |             } catch (err) { | 
					
						
							|  |  |  |               if (app.running()) { | 
					
						
							|  |  |  |                 $.exit(1) | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             $.exit(0) | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         JAVASCRIPT | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         system_command "osascript", args:         ["-l", "JavaScript", "-e", script, bundle_id], | 
					
						
							| 
									
										
										
										
											2019-09-15 01:08:04 +02:00
										 |  |  |                                     print_stderr: false | 
					
						
							| 
									
										
										
										
											2018-11-05 02:00:40 +01:00
										 |  |  |       end | 
					
						
							|  |  |  |       private :quit | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |       # :signal should come after :quit so it can be used as a backup when :quit fails | 
					
						
							| 
									
										
										
										
											2017-09-11 23:29:38 +02:00
										 |  |  |       def uninstall_signal(*signals, command: nil, **_) | 
					
						
							|  |  |  |         signals.each do |pair| | 
					
						
							| 
									
										
										
										
											2019-02-19 13:11:32 +00:00
										 |  |  |           raise CaskInvalidError.new(cask, "Each #{stanza} :signal must consist of 2 elements.") unless pair.size == 2
 | 
					
						
							| 
									
										
										
										
											2017-03-08 03:03:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-09 09:32:54 +01:00
										 |  |  |           signal, bundle_id = pair | 
					
						
							|  |  |  |           ohai "Signalling '#{signal}' to application ID '#{bundle_id}'" | 
					
						
							| 
									
										
										
										
											2018-11-05 22:40:07 +01:00
										 |  |  |           pids = running_processes(bundle_id).map(&:first) | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |           next unless pids.any? | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |           # Note that unlike :quit, signals are sent from the current user (not | 
					
						
							| 
									
										
										
										
											2017-03-08 03:03:36 +01:00
										 |  |  |           # upgraded to the superuser). This is a todo item for the future, but | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |           # 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. | 
					
						
							| 
									
										
										
										
											2016-11-09 09:32:54 +01:00
										 |  |  |           odebug "Unix ids are #{pids.inspect} for processes with bundle identifier #{bundle_id}" | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |           Process.kill(signal, *pids) | 
					
						
							|  |  |  |           sleep 3
 | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-25 01:05:53 +02:00
										 |  |  |       def uninstall_login_item(*login_items, command: nil, upgrade: false, **_) | 
					
						
							|  |  |  |         return if upgrade | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-12 23:45:31 +02:00
										 |  |  |         apps = cask.artifacts.select { |a| a.class.dsl_key == :app } | 
					
						
							|  |  |  |         derived_login_items = apps.map { |a| { path: a.target } } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         [*derived_login_items, *login_items].each do |item| | 
					
						
							|  |  |  |           type, id = if item.respond_to?(:key) && item.key?(:path) | 
					
						
							|  |  |  |             ["path", item[:path]] | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             ["name", item] | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           ohai "Removing login item #{id}" | 
					
						
							| 
									
										
										
										
											2019-10-22 05:34:39 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |           result = system_command( | 
					
						
							| 
									
										
										
										
											2018-11-05 02:00:40 +01:00
										 |  |  |             "osascript", | 
					
						
							| 
									
										
										
										
											2018-09-02 16:15:09 +01:00
										 |  |  |             args: [ | 
					
						
							|  |  |  |               "-e", | 
					
						
							| 
									
										
										
										
											2019-08-12 23:45:31 +02:00
										 |  |  |               %Q(tell application "System Events" to delete every login item whose #{type} is #{id.to_s.inspect}), | 
					
						
							| 
									
										
										
										
											2018-09-02 16:15:09 +01:00
										 |  |  |             ], | 
					
						
							|  |  |  |           ) | 
					
						
							| 
									
										
										
										
											2019-10-22 05:34:39 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |           opoo "Removal of login item #{id} failed. #{automation_access_instructions}" unless result.success? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |           sleep 1
 | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |       # :kext should be unloaded before attempting to delete the relevant file | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |       def uninstall_kext(*kexts, command: nil, **_) | 
					
						
							| 
									
										
										
										
											2017-03-08 03:03:36 +01:00
										 |  |  |         kexts.each do |kext| | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |           ohai "Unloading kernel extension #{kext}" | 
					
						
							| 
									
										
										
										
											2018-11-05 22:40:07 +01:00
										 |  |  |           is_loaded = system_command!("/usr/sbin/kextstat", args: ["-l", "-b", kext], sudo: true).stdout | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |           if is_loaded.length > 1
 | 
					
						
							| 
									
										
										
										
											2018-11-05 22:40:07 +01:00
										 |  |  |             system_command!("/sbin/kextunload", args: ["-b", kext], sudo: true) | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |             sleep 1
 | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2018-11-05 22:40:07 +01:00
										 |  |  |           system_command!("/usr/sbin/kextfind", args: ["-b", kext], sudo: true).stdout.chomp.lines.each do |kext_path| | 
					
						
							| 
									
										
										
										
											2016-11-18 22:53:25 +01:00
										 |  |  |             ohai "Removing kernel extension #{kext_path}" | 
					
						
							| 
									
										
										
										
											2018-11-05 22:40:07 +01:00
										 |  |  |             system_command!("/bin/rm", args: ["-rf", kext_path], sudo: true) | 
					
						
							| 
									
										
										
										
											2016-11-18 22:53:25 +01:00
										 |  |  |           end | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |       # :script must come before :pkgutil, :delete, or :trash so that the script file is not already deleted | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |       def uninstall_script(directives, directive_name: :script, force: false, command: nil, **_) | 
					
						
							|  |  |  |         # TODO: Create a common `Script` class to run this and Artifact::Installer. | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |         executable, script_arguments = self.class.read_script_arguments(directives, | 
					
						
							|  |  |  |                                                                         "uninstall", | 
					
						
							| 
									
										
										
										
											2017-03-12 22:09:13 +01:00
										 |  |  |                                                                         { must_succeed: true, sudo: false }, | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |                                                                         { print_stdout: true }, | 
					
						
							|  |  |  |                                                                         directive_name) | 
					
						
							| 
									
										
										
										
											2017-03-08 03:03:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |         ohai "Running uninstall script #{executable}" | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |         raise CaskInvalidError.new(cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil? | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-25 21:05:35 +01:00
										 |  |  |         executable_path = staged_path_join_executable(executable) | 
					
						
							| 
									
										
										
										
											2017-02-16 21:53:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-25 21:05:35 +01:00
										 |  |  |         if (executable_path.absolute? && !executable_path.exist?) || | 
					
						
							|  |  |  |            (!executable_path.absolute? && (which executable_path).nil?) | 
					
						
							| 
									
										
										
										
											2017-02-16 21:53:22 +01:00
										 |  |  |           message = "uninstall script #{executable} does not exist" | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |           raise CaskError, "#{message}." unless force | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-26 15:21:24 -05:00
										 |  |  |           opoo "#{message}; skipping." | 
					
						
							| 
									
										
										
										
											2017-02-16 21:53:22 +01:00
										 |  |  |           return | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |         command.run(executable_path, script_arguments) | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |         sleep 1
 | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |       def uninstall_pkgutil(*pkgs, command: nil, **_) | 
					
						
							| 
									
										
										
										
											2021-03-26 02:28:51 +05:30
										 |  |  |         ohai "Uninstalling packages; your password may be necessary:" | 
					
						
							| 
									
										
										
										
											2017-03-08 03:03:36 +01:00
										 |  |  |         pkgs.each do |regex| | 
					
						
							| 
									
										
										
										
											2018-09-06 08:29:14 +02:00
										 |  |  |           ::Cask::Pkg.all_matching(regex, command).each do |pkg| | 
					
						
							| 
									
										
										
										
											2017-03-08 03:03:36 +01:00
										 |  |  |             puts pkg.package_id | 
					
						
							|  |  |  |             pkg.uninstall | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def each_resolved_path(action, paths) | 
					
						
							| 
									
										
										
										
											2017-06-24 07:01:35 +02:00
										 |  |  |         return enum_for(:each_resolved_path, action, paths) unless block_given? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-08 03:03:36 +01:00
										 |  |  |         paths.each do |path| | 
					
						
							|  |  |  |           resolved_path = Pathname.new(path) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-06 11:46:30 +01:00
										 |  |  |           resolved_path = resolved_path.expand_path if path.to_s.start_with?("~") | 
					
						
							| 
									
										
										
										
											2017-03-08 03:03:36 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |           if resolved_path.relative? || resolved_path.split.any? { |part| part.to_s == ".." } | 
					
						
							|  |  |  |             opoo "Skipping #{Formatter.identifier(action)} for relative path '#{path}'." | 
					
						
							|  |  |  |             next | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if MacOS.undeletable?(resolved_path) | 
					
						
							|  |  |  |             opoo "Skipping #{Formatter.identifier(action)} for undeletable path '#{path}'." | 
					
						
							|  |  |  |             next | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-17 14:07:14 -04:00
										 |  |  |           begin | 
					
						
							|  |  |  |             yield path, Pathname.glob(resolved_path) | 
					
						
							|  |  |  |           rescue Errno::EPERM | 
					
						
							|  |  |  |             raise if File.readable?(File.expand_path("~/Library/Application Support/com.apple.TCC")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             odie "Unable to remove some files. Please enable Full Disk Access for your terminal under " \ | 
					
						
							|  |  |  |                  "System Preferences → Security & Privacy → Privacy → Full Disk Access." | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |       def uninstall_delete(*paths, command: nil, **_) | 
					
						
							| 
									
										
										
										
											2017-03-08 03:03:36 +01:00
										 |  |  |         return if paths.empty? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ohai "Removing files:" | 
					
						
							|  |  |  |         each_resolved_path(:delete, paths) do |path, resolved_paths| | 
					
						
							|  |  |  |           puts path | 
					
						
							| 
									
										
										
										
											2018-09-02 16:15:09 +01:00
										 |  |  |           command.run!( | 
					
						
							|  |  |  |             "/usr/bin/xargs", | 
					
						
							| 
									
										
										
										
											2018-11-02 17:18:07 +00:00
										 |  |  |             args:  ["-0", "--", "/bin/rm", "-r", "-f", "--"], | 
					
						
							| 
									
										
										
										
											2018-09-02 16:15:09 +01:00
										 |  |  |             input: resolved_paths.join("\0"), | 
					
						
							| 
									
										
										
										
											2018-11-02 17:18:07 +00:00
										 |  |  |             sudo:  true, | 
					
						
							| 
									
										
										
										
											2018-09-02 16:15:09 +01:00
										 |  |  |           ) | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |       def uninstall_trash(*paths, **options) | 
					
						
							| 
									
										
										
										
											2017-06-16 17:01:30 +02:00
										 |  |  |         return if paths.empty? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-24 07:01:35 +02:00
										 |  |  |         resolved_paths = each_resolved_path(:trash, paths).to_a | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-06 15:30:57 -04:00
										 |  |  |         ohai "Trashing files:", resolved_paths.map(&:first) | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |         trash_paths(*resolved_paths.flat_map(&:last), **options) | 
					
						
							| 
									
										
										
										
											2017-06-24 08:34:01 +02:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |       def trash_paths(*paths, command: nil, **_) | 
					
						
							| 
									
										
										
										
											2019-05-21 12:56:23 +00:00
										 |  |  |         return if paths.empty? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-23 05:21:21 +02:00
										 |  |  |         stdout, stderr, = system_command HOMEBREW_LIBRARY_PATH/"cask/utils/trash.swift", | 
					
						
							|  |  |  |                                          args:         paths, | 
					
						
							|  |  |  |                                          print_stderr: false | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         trashed = stdout.split(":").sort | 
					
						
							|  |  |  |         untrashable = stderr.split(":").sort | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return trashed, untrashable if untrashable.empty? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         untrashable.delete_if do |path| | 
					
						
							|  |  |  |           Utils.gain_permissions(path, ["-R"], SystemCommand) do | 
					
						
							|  |  |  |             system_command! HOMEBREW_LIBRARY_PATH/"cask/utils/trash.swift", | 
					
						
							|  |  |  |                             args:         [path], | 
					
						
							|  |  |  |                             print_stderr: false | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           true | 
					
						
							|  |  |  |         rescue | 
					
						
							|  |  |  |           false | 
					
						
							| 
									
										
										
										
											2019-10-02 13:36:05 +00:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2019-10-01 11:12:23 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-26 15:21:24 -05:00
										 |  |  |         opoo "The following files could not be trashed, please do so manually:" | 
					
						
							| 
									
										
										
										
											2019-10-23 05:21:21 +02:00
										 |  |  |         $stderr.puts untrashable | 
					
						
							| 
									
										
										
										
											2017-10-01 01:47:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-23 05:21:21 +02:00
										 |  |  |         [trashed, untrashable] | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-10 18:14:53 +00:00
										 |  |  |       def all_dirs?(*directories) | 
					
						
							|  |  |  |         directories.all?(&:directory?) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def recursive_rmdir(*directories, command: nil, **_) | 
					
						
							|  |  |  |         success = true | 
					
						
							|  |  |  |         each_resolved_path(:rmdir, directories) do |_path, resolved_paths| | 
					
						
							|  |  |  |           resolved_paths.select(&method(:all_dirs?)).each do |resolved_path| | 
					
						
							|  |  |  |             puts resolved_path.sub(Dir.home, "~") | 
					
						
							| 
									
										
										
										
											2017-03-08 03:03:36 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             if (ds_store = resolved_path.join(".DS_Store")).exist? | 
					
						
							| 
									
										
										
										
											2017-04-06 00:33:31 +02:00
										 |  |  |               command.run!("/bin/rm", args: ["-f", "--", ds_store], sudo: true, print_stderr: false) | 
					
						
							| 
									
										
										
										
											2017-03-08 03:03:36 +01:00
										 |  |  |             end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-10 18:14:53 +00:00
										 |  |  |             unless recursive_rmdir(*resolved_path.children, command: command) | 
					
						
							|  |  |  |               success = false | 
					
						
							|  |  |  |               next | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             status = command.run("/bin/rmdir", args: ["--", resolved_path], sudo: true, print_stderr: false).success? | 
					
						
							|  |  |  |             success &= status | 
					
						
							| 
									
										
										
										
											2017-03-08 03:03:36 +01:00
										 |  |  |           end | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2020-01-10 18:14:53 +00:00
										 |  |  |         success | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def uninstall_rmdir(*args) | 
					
						
							|  |  |  |         return if args.empty? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ohai "Removing directories if empty:" | 
					
						
							|  |  |  |         recursive_rmdir(*args) | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |