Class: Cask::Artifact::AbstractUninstall Private

Inherits:
AbstractArtifact show all
Includes:
OS::Mac::Cask::Artifact::AbstractUninstall, SystemCommand::Mixin
Defined in:
cask/artifact/abstract_uninstall.rb

Overview

This class is part of a private API. This class may only be used in the Homebrew/brew repository. Third parties should avoid using this class if possible, as it may be removed or changed without warning.

Abstract superclass for uninstall artifacts.

Direct Known Subclasses

Uninstall, Zap

Constant Summary collapse

ORDERED_DIRECTIVES =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

[
  :early_script,
  :launchctl,
  :quit,
  :signal,
  :login_item,
  :kext,
  :script,
  :pkgutil,
  :delete,
  :trash,
  :rmdir,
].freeze
METADATA_KEYS =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

[
  :on_upgrade,
].freeze
UPGRADE_REINSTALL_SKIP_DIRECTIVES =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

[:signal].freeze

Constants inherited from AbstractArtifact

Cask::Artifact::AbstractArtifact::DirectivesType

Class Attribute Summary collapse

Instance Attribute Summary collapse

Attributes inherited from AbstractArtifact

#cask

Class Method Summary collapse

Instance Method Summary collapse

Methods included from SystemCommand::Mixin

#command_sandbox, #system_command, #system_command!

Methods inherited from AbstractArtifact

#cask_sandbox, #config, dirmethod, dsl_key, english_article, english_name, read_script_arguments, #requires_sudo?, #run_cask_sandbox, #sort_order, #staged_path_join_executable, #to_args

Methods included from Utils::Output::Mixin

#issue_reporting_message, #odebug, #odeprecated, #odie, #odisabled, #ofail, #oh1, #oh1_title, #ohai, #ohai_title, #onoe, #opoo, #opoo_once, #opoo_outside_github_actions, #opoo_without_github_actions_annotation, #pretty_cannot_install, #pretty_deprecated, #pretty_disabled, #pretty_duration, #pretty_install_status, #pretty_installed, #pretty_uninstalled, #pretty_unmarked, #pretty_upgradable, #pretty_warning

Constructor Details

#initialize(cask, **directives) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'cask/artifact/abstract_uninstall.rb', line 93

def initialize(cask, **directives)
  ::Utils::Data.assert_valid_keys(directives, *ORDERED_DIRECTIVES, *METADATA_KEYS)

  super
  directives[:signal] = Array(directives[:signal]).flatten.each_slice(2).to_a
  @directives = directives

  # This is already included when loading from the API.
  return if cask.loaded_from_api?
  return unless directives.key?(:kext)

  cask.caveats do
    T.bind(self, ::Cask::DSL::Caveats)
    kext
  end
end

Class Attribute Details

.ancestor_bundle_ids ⇒ Array<String>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Bundle IDs of the applications this brew process is running inside. The ancestry of the brew process cannot change while it runs, so the lookup is shared by every artifact of every cask in the same invocation.

Returns:



436
437
438
439
440
441
442
443
444
445
446
# File 'cask/artifact/abstract_uninstall.rb', line 436

def ancestor_bundle_ids
  @ancestor_bundle_ids ||= begin
    pids = [Process.pid]
    # `launchd` (PID 1) reports a parent of `0` rather than failing.
    while (ppid = parent_pid(pids.last)) && ppid > 1 && pids.exclude?(ppid)
      pids << ppid
    end

    pids.filter_map { |pid| bundle_identifier_for_pid(pid) }
  end
end

Instance Attribute Details

#directives ⇒ Hash{Symbol => DirectivesType} (readonly)

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



90
91
92
# File 'cask/artifact/abstract_uninstall.rb', line 90

def directives
  @directives
end

Class Method Details

.bundle_identifier_for_pid(_pid) ⇒ String?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



455
# File 'cask/artifact/abstract_uninstall.rb', line 455

def bundle_identifier_for_pid(_pid) = nil

.dispatch_directives(artifacts, command:, deferred: false, force: false, successor: nil, upgrade: false, reinstall: false, quit: true) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Dispatches the directives of one or more uninstall/zap stanzas in a single ORDERED_DIRECTIVES pass, the earlier stanza's first at each step, so that zapping runs zap directives alongside the uninstall stanza's. Each stanza's deferred_directives are skipped, and are the only ones run when deferred:.

Parameters:

  • artifacts (Array<AbstractUninstall>)
  • command (T.class_of(SystemCommand))
  • deferred (Boolean) (defaults to: false)
  • force (Boolean) (defaults to: false)
  • successor (Cask, nil) (defaults to: nil)
  • upgrade (Boolean) (defaults to: false)
  • reinstall (Boolean) (defaults to: false)
  • quit (Boolean) (defaults to: true)


64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'cask/artifact/abstract_uninstall.rb', line 64

def self.dispatch_directives(artifacts, command:, deferred: false, force: false, successor: nil,
                             upgrade: false, reinstall: false, quit: true)
  ORDERED_DIRECTIVES.each do |directive_sym|
    next if directive_sym == :quit && !quit

    artifacts.each do |artifact|
      next if artifact.deferred_directives.include?(directive_sym) != deferred

      if (upgrade || reinstall) &&
         UPGRADE_REINSTALL_SKIP_DIRECTIVES.include?(directive_sym) &&
         Array(artifact.directives[:on_upgrade]).map { |name| name.to_s.to_sym }.exclude?(directive_sym)
        next
      end

      artifact.dispatch_uninstall_directive(directive_sym, command:, force:, successor:, upgrade:)
    end
  end
end

.from_args(cask, **directives) ⇒ AbstractUninstall

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



44
45
46
# File 'cask/artifact/abstract_uninstall.rb', line 44

def self.from_args(cask, **directives)
  new(cask, **directives)
end

.owner_uid(_pid) ⇒ Integer?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



458
# File 'cask/artifact/abstract_uninstall.rb', line 458

def owner_uid(_pid) = nil

.parent_pid(_pid) ⇒ Integer?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



452
# File 'cask/artifact/abstract_uninstall.rb', line 452

def parent_pid(_pid) = nil

Instance Method Details

#bundle_ids_to_reopen ⇒ Array<String>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



121
122
123
# File 'cask/artifact/abstract_uninstall.rb', line 121

def bundle_ids_to_reopen
  @bundle_ids_to_reopen ||= T.let([], T.nilable(T::Array[String]))
end

#deferred_directives ⇒ Array<Symbol>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Directives that wait until the cask's artifacts have been removed and its uninstall_postflight steps have run, so that directories which held only those artifacts are seen as empty.

Returns:



87
# File 'cask/artifact/abstract_uninstall.rb', line 87

def deferred_directives = [:rmdir]

#dispatch_uninstall_directive(directive_sym, command:, force: false, successor: nil, upgrade: false) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Runs this stanza's directive_sym directive, if it has one. The uninstall_* methods are dispatched dynamically, so they take these options uniformly and ignore the ones they do not need.

Parameters:

  • directive_sym (Symbol)
  • command (T.class_of(SystemCommand))
  • force (Boolean) (defaults to: false)
  • successor (Cask, nil) (defaults to: nil)
  • upgrade (Boolean) (defaults to: false)


238
239
240
241
242
243
244
245
# File 'cask/artifact/abstract_uninstall.rb', line 238

def dispatch_uninstall_directive(directive_sym, command:, force: false, successor: nil, upgrade: false)
  return unless directives.key?(directive_sym)

  args = directives[directive_sym]

  send(:"uninstall_#{directive_sym}", *(args.is_a?(Hash) ? [args] : args),
       command:, force:, successor:, upgrade:)
end

#each_resolved_path(action, paths, &_block) ⇒ T.untyped

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This returns T::Enumerable[[Pathname, T::Array[Pathname]]] when called without a block, but sorbet doesn't support overloads.

Parameters:

Returns:

  • (T.untyped)


182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'cask/artifact/abstract_uninstall.rb', line 182

def each_resolved_path(action, paths, &_block)
  return enum_for(:each_resolved_path, action, paths) unless block_given?

  paths.each do |path|
    resolved_path = Pathname.new(path.to_s.sub(%r{^~(?=(?:/|$))}, Dir.home))

    if resolved_path.relative?
      opoo "Skipping #{Formatter.identifier(action)} for relative path '#{path}'."
      next
    end

    if resolved_path.each_filename.to_a.intersect?([".", ".."])
      opoo "Skipping #{Formatter.identifier(action)} for path with relative segments '#{path}'."
      next
    end

    begin
      resolved_paths = Pathname.glob(resolved_path).reject do |target|
        next false unless undeletable?(target)

        opoo "Skipping #{Formatter.identifier(action)} for undeletable path '#{target}'."
        true
      end
      yield path, resolved_paths
    rescue Errno::EPERM
      raise if ::Cask::Utils.full_disk_access_enabled?

      odie "Unable to remove some files. Please enable Full Disk Access for your terminal under " \
           "#{::Cask::Utils.privacy_security_preference_pane("Full Disk Access")}."
    end
  end
end

#find_launchctl_with_wildcard(search) ⇒ Array<String>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



216
217
218
219
220
221
222
223
224
# File 'cask/artifact/abstract_uninstall.rb', line 216

def find_launchctl_with_wildcard(search)
  regex = wildcard_pattern(search)
  system_command!("/bin/launchctl", args: ["list"])
    .stdout.lines.drop(1) # skip stdout column headers
    .filter_map do |line|
      pid, _state, id = line.chomp.split(/\s+/)
      id if pid.to_i.nonzero? && id&.match?(regex)
    end
end

#summarize ⇒ String

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



116
117
118
# File 'cask/artifact/abstract_uninstall.rb', line 116

def summarize
  to_h.flat_map { |key, val| Array(val).map { |v| "#{key.inspect} => #{v.inspect}" } }.join(", ")
end

#to_h ⇒ Hash{Symbol => DirectivesType}

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



111
112
113
# File 'cask/artifact/abstract_uninstall.rb', line 111

def to_h
  directives.to_h
end

#uninstall_quit(*bundle_ids, command: nil, upgrade: false, **_kwargs) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

:quit/:signal must come before :kext so the kext will not be in use by a running process

Parameters:

  • bundle_ids (String)
  • command (T.class_of(SystemCommand), nil) (defaults to: nil)
  • upgrade (Boolean) (defaults to: false)
  • _kwargs (T.anything)


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'cask/artifact/abstract_uninstall.rb', line 134

def uninstall_quit(*bundle_ids, command: nil, upgrade: false, **_kwargs)
  bundle_ids = bundle_ids.flat_map { |bundle_id| expand_bundle_id(bundle_id) }

  bundle_ids.each do |bundle_id|
    next unless running?(bundle_id)

    unless User.current&.gui?
      opoo "Not logged into a GUI; skipping quitting application ID '#{bundle_id}'."
      next
    end

    if hosting_brew?(bundle_id)
      opoo "Skipping quitting application '#{bundle_id}' as `brew` is running inside it."
      next
    end

    ohai "Quitting application '#{bundle_id}'..."

    quit_succeeded = T.let(false, T::Boolean)
    begin
      Timeout.timeout(10) do
        Kernel.loop do
          next unless quit(bundle_id).success?

          next if running?(bundle_id)

          puts "Application '#{bundle_id}' quit successfully."
          quit_succeeded = true
          break
        end
      end
    rescue Timeout::Error
      opoo "Application '#{bundle_id}' did not quit. #{automation_access_instructions}"
    end

    bundle_ids_to_reopen << bundle_id if upgrade && quit_succeeded
  end
end