Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/uninstall: check for dependent casks #10575

Merged
merged 5 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Library/Homebrew/cmd/uninstall.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def uninstall

Uninstall.uninstall_kegs(
kegs_by_rack,
casks: casks,
force: args.force?,
ignore_dependencies: args.ignore_dependencies?,
named_args: args.named,
Expand Down
21 changes: 14 additions & 7 deletions Library/Homebrew/keg.rb
Original file line number Diff line number Diff line change
Expand Up @@ -137,18 +137,19 @@ def to_s
LIBTOOL_EXTENSIONS = %w[.la .lai].freeze

# Given an array of kegs, this method will try to find some other kegs
# that depend on them. If it does, it returns:
# or casks that depend on them. If it does, it returns:
#
# - some kegs in the passed array that have installed dependents
# - some installed dependents of those kegs.
#
# If it doesn't, it returns nil.
#
# Note that nil will be returned if the only installed dependents
# in the passed kegs are other kegs in the array.
# Note that nil will be returned if the only installed dependents of the
# passed kegs are other kegs in the array or casks present in the casks
# parameter.
#
# For efficiency, we don't bother trying to get complete data.
def self.find_some_installed_dependents(kegs)
def self.find_some_installed_dependents(kegs, casks: [])
keg_names = kegs.select(&:optlinked?).map(&:name)
keg_formulae = []
kegs_by_source = kegs.group_by do |keg|
Expand All @@ -167,10 +168,16 @@ def self.find_some_installed_dependents(kegs)
all_dependents = []

# Don't include dependencies of kegs that were in the given array.
formulae_to_check = Formula.installed - keg_formulae
dependents_to_check = (Formula.installed - keg_formulae) + (Cask::Caskroom.casks - casks)

dependents_to_check.each do |dependent|
required = case dependent
when Formula
dependent.missing_dependencies(hide: keg_names)
when Cask::Cask
CaskDependent.new(dependent).runtime_dependencies.map(&:to_formula)
end

formulae_to_check.each do |dependent|
required = dependent.missing_dependencies(hide: keg_names)
required_kegs = required.map do |f|
f_kegs = kegs_by_source[[f.name, f.tap]]
next unless f_kegs
Expand Down
26 changes: 26 additions & 0 deletions Library/Homebrew/test/keg_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -455,5 +455,31 @@ def unreliable_dependencies(deps)
dependencies [{ "full_name" => "foo", "version" => "1.1" }] # different version
expect(described_class.find_some_installed_dependents([keg])).to eq([[keg], ["bar"]])
end

def stub_cask_name(name, version, dependency)
c = Cask::CaskLoader.load(+<<-RUBY)
cask "#{name}" do
version "#{version}"

url "c-1"
depends_on formula: "#{dependency}"
end
RUBY

stub_cask_loader c
c
end

def setup_test_cask(name, version, dependency)
c = stub_cask_name(name, version, dependency)
Cask::Caskroom.path.join(name, c.version).mkpath
c
end

specify "identify dependent casks" do
setup_test_cask("qux", "1.0.0", "foo")
dependents = described_class.find_some_installed_dependents([keg]).last
expect(dependents.include?("qux")).to eq(true)
end
end
end
25 changes: 20 additions & 5 deletions Library/Homebrew/test/uninstall_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,46 @@

describe Homebrew::Uninstall do
let(:dependency) { formula("dependency") { url "f-1" } }
let(:dependent) do
formula("dependent") do

let(:dependent_formula) do
formula("dependent_formula") do
url "f-1"
depends_on "dependency"
end
end

let(:dependent_cask) do
Cask::CaskLoader.load(+<<-RUBY)
cask "dependent_cask" do
version "1.0.0"

url "c-1"
depends_on formula: "dependency"
end
RUBY
end

let(:kegs_by_rack) { { dependency.rack => [Keg.new(dependency.latest_installed_prefix)] } }

before do
[dependency, dependent].each do |f|
[dependency, dependent_formula].each do |f|
f.latest_installed_prefix.mkpath
Keg.new(f.latest_installed_prefix).optlink
end

tab = Tab.empty
tab.homebrew_version = "1.1.6"
tab.tabfile = dependent.latest_installed_prefix/Tab::FILENAME
tab.tabfile = dependent_formula.latest_installed_prefix/Tab::FILENAME
tab.runtime_dependencies = [
{ "full_name" => "dependency", "version" => "1" },
]
tab.write

Cask::Caskroom.path.join("dependent_cask", dependent_cask.version).mkpath

stub_formula_loader dependency
stub_formula_loader dependent
stub_formula_loader dependent_formula
stub_cask_loader dependent_cask
end

describe "::handle_unsatisfied_dependents" do
Expand Down
11 changes: 6 additions & 5 deletions Library/Homebrew/uninstall.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ module Homebrew
module Uninstall
module_function

def uninstall_kegs(kegs_by_rack, force: false, ignore_dependencies: false, named_args: [])
def uninstall_kegs(kegs_by_rack, casks: [], force: false, ignore_dependencies: false, named_args: [])
handle_unsatisfied_dependents(kegs_by_rack,
casks: casks,
ignore_dependencies: ignore_dependencies,
named_args: named_args)
return if Homebrew.failed?
Expand Down Expand Up @@ -96,18 +97,18 @@ def uninstall_kegs(kegs_by_rack, force: false, ignore_dependencies: false, named
end
end

def handle_unsatisfied_dependents(kegs_by_rack, ignore_dependencies: false, named_args: [])
def handle_unsatisfied_dependents(kegs_by_rack, casks: [], ignore_dependencies: false, named_args: [])
return if ignore_dependencies

all_kegs = kegs_by_rack.values.flatten(1)
check_for_dependents(all_kegs, named_args: named_args)
check_for_dependents(all_kegs, casks: casks, named_args: named_args)
rescue MethodDeprecatedError
# Silently ignore deprecations when uninstalling.
nil
end

def check_for_dependents(kegs, named_args: [])
return false unless result = Keg.find_some_installed_dependents(kegs)
def check_for_dependents(kegs, casks: [], named_args: [])
return false unless result = Keg.find_some_installed_dependents(kegs, casks: casks)

if Homebrew::EnvConfig.developer?
DeveloperDependentsMessage.new(*result, named_args: named_args).output
Expand Down