Skip to content

Commit b1e6f31

Browse files
authored
Merge pull request #21078 from Homebrew/cask-uninstall-dependents-check
Prevent users from uninstalling casks with dependents
2 parents a0c7c86 + c51f74b commit b1e6f31

File tree

5 files changed

+277
-136
lines changed

5 files changed

+277
-136
lines changed

Library/Homebrew/cask/uninstall.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# typed: strict
22
# frozen_string_literal: true
33

4+
require "cask_dependent"
5+
require "dependents_message"
46
require "utils/output"
57

68
module Cask
@@ -19,5 +21,27 @@ def self.uninstall_casks(*casks, binaries: false, force: false, verbose: false)
1921
Installer.new(cask, binaries:, force:, verbose:).uninstall
2022
end
2123
end
24+
25+
sig { params(casks: ::Cask::Cask, named_args: T::Array[String]).void }
26+
def self.check_dependent_casks(*casks, named_args: [])
27+
dependents = []
28+
all_requireds = casks.map(&:token)
29+
requireds = Set.new
30+
caskroom = ::Cask::Caskroom.casks
31+
32+
caskroom.each do |dependent|
33+
d = CaskDependent.new(dependent)
34+
dependencies = d.recursive_requirements.filter_map { |r| r.cask if r.is_a?(CaskDependent::Requirement) }
35+
found_dependents = dependencies.intersection(all_requireds)
36+
next if found_dependents.empty?
37+
38+
requireds += found_dependents
39+
dependents << dependent.token
40+
end
41+
42+
return if dependents.empty?
43+
44+
DependentsMessage.new(requireds.to_a, dependents, named_args:).output
45+
end
2246
end
2347
end

Library/Homebrew/cmd/uninstall.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ def run
6161
named_args: args.named,
6262
)
6363

64+
Cask::Uninstall.check_dependent_casks(*casks, named_args: args.named) unless args.ignore_dependencies?
65+
return if Homebrew.failed?
66+
6467
if args.zap?
6568
casks.each do |cask|
6669
odebug "Zapping Cask #{cask}"
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
require "utils/output"
5+
6+
class DependentsMessage
7+
include ::Utils::Output::Mixin
8+
9+
sig { returns(T::Array[T.any(String, Keg)]) }
10+
attr_reader :reqs
11+
12+
sig { returns(T::Array[String]) }
13+
attr_reader :deps, :named_args
14+
15+
sig { params(requireds: T::Array[T.any(String, Keg)], dependents: T::Array[String], named_args: T::Array[String]).void }
16+
def initialize(requireds, dependents, named_args: [])
17+
@reqs = requireds
18+
@deps = dependents
19+
@named_args = named_args
20+
end
21+
22+
sig { void }
23+
def output
24+
ofail <<~EOS
25+
Refusing to uninstall #{reqs.to_sentence}
26+
because #{reqs.one? ? "it" : "they"} #{are_required_by_deps}.
27+
You can override this and force removal with:
28+
#{sample_command}
29+
EOS
30+
end
31+
32+
protected
33+
34+
sig { returns(String) }
35+
def sample_command
36+
"brew uninstall --ignore-dependencies #{named_args.join(" ")}"
37+
end
38+
39+
sig { returns(String) }
40+
def are_required_by_deps
41+
"#{reqs.one? ? "is" : "are"} required by #{deps.to_sentence}, " \
42+
"which #{deps.one? ? "is" : "are"} currently installed"
43+
end
44+
end

0 commit comments

Comments
 (0)