From 6f0aabe70700654fdd19d1800fa61f4cdea25266 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Thu, 22 Sep 2016 13:27:33 +0100 Subject: [PATCH] uninstall: refuse when dependants still installed Closes #934. --- Library/Homebrew/cmd/uninstall.rb | 9 ++++ Library/Homebrew/formula.rb | 7 ++++ Library/Homebrew/keg.rb | 15 +++++++ Library/Homebrew/test/test_keg.rb | 69 +++++++++++++++++++++++++++---- 4 files changed, 93 insertions(+), 7 deletions(-) diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index 8bcfc31fbb..3d40c23509 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -16,6 +16,15 @@ module Homebrew if !ARGV.force? ARGV.kegs.each do |keg| + dependants = keg.installed_dependants + if dependants.any? + dependants_output = dependants.map { |k| "#{k.name} #{k.version}" }.join(", ") + conjugation = dependants.count == 1 ? "is" : "are" + ofail "Refusing to uninstall #{keg} because it is required by #{dependants_output}, which #{conjugation} currently installed." + puts "Remove it anyway with `brew uninstall --force #{keg.name}`." + next + end + keg.lock do puts "Uninstalling #{keg}... (#{keg.abv})" keg.unlink diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 69cab88c47..c2467b7bce 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -1337,6 +1337,13 @@ class Formula end end + # Clear caches of .racks and .installed. + # @private + def self.clear_cache + @racks = nil + @installed = nil + end + # An array of all racks currently installed. # @private def self.racks diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index d2c9e12e8c..0f58d688c7 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -292,6 +292,21 @@ class Keg PkgVersion.parse(path.basename.to_s) end + def formula + Formulary.from_keg(self) + end + + def installed_dependants + Formula.installed.flat_map(&:installed_kegs).select do |keg| + Tab.for_keg(keg).runtime_dependencies.any? do |dep| + # Resolve formula rather than directly comparing names + # in case of conflicts between formulae from different taps. + dep_formula = Formulary.factory(dep["full_name"]) + dep_formula == formula && dep["version"] == version.to_s + end + end + end + def find(*args, &block) path.find(*args, &block) end diff --git a/Library/Homebrew/test/test_keg.rb b/Library/Homebrew/test/test_keg.rb index 3abf3fc273..41ccb27eec 100644 --- a/Library/Homebrew/test/test_keg.rb +++ b/Library/Homebrew/test/test_keg.rb @@ -5,15 +5,22 @@ require "stringio" class LinkTests < Homebrew::TestCase include FileUtils - def setup - keg = HOMEBREW_CELLAR.join("foo", "1.0") - keg.join("bin").mkpath + def setup_test_keg(name, version) + path = HOMEBREW_CELLAR.join(name, version) + path.join("bin").mkpath %w[hiworld helloworld goodbye_cruel_world].each do |file| - touch keg.join("bin", file) + touch path.join("bin", file) end - @keg = Keg.new(keg) + keg = Keg.new(path) + @kegs ||= [] + @kegs << keg + keg + end + + def setup + @keg = setup_test_keg("foo", "1.0") @dst = HOMEBREW_PREFIX.join("bin", "helloworld") @nonexistent = Pathname.new("/some/nonexistent/path") @@ -27,8 +34,10 @@ class LinkTests < Homebrew::TestCase end def teardown - @keg.unlink - @keg.uninstall + @kegs.each do |keg| + keg.unlink + keg.uninstall + end $stdout = @old_stdout @@ -305,3 +314,49 @@ class LinkTests < Homebrew::TestCase keg.uninstall end end + +class InstalledDependantsTests < LinkTests + def stub_formula_name(name) + stub_formula_loader formula(name) { url "foo-1.0" } + end + + def setup_test_keg(name, version) + stub_formula_name(name) + keg = super + Formula.clear_cache + keg + end + + def setup + super + @dependant = setup_test_keg("bar", "1.0") + end + + def dependencies(deps) + tab = Tab.for_keg(@dependant) + tab.tabfile = @dependant.join("INSTALL_RECEIPT.json") + tab.runtime_dependencies = deps + tab.write + end + + def test_no_dependencies + dependencies [] + assert_empty @keg.installed_dependants + end + + def test_same_name_different_version + dependencies [{ "full_name" => "foo", "version" => "1.1" }] + assert_empty @keg.installed_dependants + end + + def test_different_name_same_version + stub_formula_name("baz") + dependencies [{ "full_name" => "baz", "version" => @keg.version.to_s }] + assert_empty @keg.installed_dependants + end + + def test_same_name_and_version + dependencies [{ "full_name" => "foo", "version" => "1.0" }] + assert_equal [@dependant], @keg.installed_dependants + end +end