From 6f0aabe70700654fdd19d1800fa61f4cdea25266 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Thu, 22 Sep 2016 13:27:33 +0100 Subject: [PATCH 01/32] 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 From e41c4e502987d49bda9d86ad8ecd1b4e213ba9c5 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 27 Sep 2016 21:56:06 +0100 Subject: [PATCH 02/32] uninstall: better message when dependents remain Suggested by @MikeMcQuaid --- Library/Homebrew/cmd/uninstall.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index 3d40c23509..d9d3ef883a 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -21,7 +21,7 @@ module Homebrew 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}`." + puts "You can override this and force removal with `brew uninstall --force #{keg.name}`." next end From 08f3aecf6b1da82b54a8a103ba5ed440554b9f45 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 27 Sep 2016 21:56:56 +0100 Subject: [PATCH 03/32] uninstall: consistent spelling of "dependent" @ilovezfs pointed out that Homebrew generally uses "dependent", rather than "dependant". --- Library/Homebrew/cmd/uninstall.rb | 10 +++++----- Library/Homebrew/keg.rb | 2 +- Library/Homebrew/test/test_keg.rb | 14 +++++++------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index d9d3ef883a..30806942bc 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -16,11 +16,11 @@ 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." + dependents = keg.installed_dependents + if dependents.any? + dependents_output = dependents.map { |k| "#{k.name} #{k.version}" }.join(", ") + conjugation = dependents.count == 1 ? "is" : "are" + ofail "Refusing to uninstall #{keg} because it is required by #{dependents_output}, which #{conjugation} currently installed." puts "You can override this and force removal with `brew uninstall --force #{keg.name}`." next end diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index 0f58d688c7..23ebd9d98f 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -296,7 +296,7 @@ class Keg Formulary.from_keg(self) end - def installed_dependants + def installed_dependents 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 diff --git a/Library/Homebrew/test/test_keg.rb b/Library/Homebrew/test/test_keg.rb index 41ccb27eec..ac5831b092 100644 --- a/Library/Homebrew/test/test_keg.rb +++ b/Library/Homebrew/test/test_keg.rb @@ -329,34 +329,34 @@ class InstalledDependantsTests < LinkTests def setup super - @dependant = setup_test_keg("bar", "1.0") + @dependent = setup_test_keg("bar", "1.0") end def dependencies(deps) - tab = Tab.for_keg(@dependant) - tab.tabfile = @dependant.join("INSTALL_RECEIPT.json") + tab = Tab.for_keg(@dependent) + tab.tabfile = @dependent.join("INSTALL_RECEIPT.json") tab.runtime_dependencies = deps tab.write end def test_no_dependencies dependencies [] - assert_empty @keg.installed_dependants + assert_empty @keg.installed_dependents end def test_same_name_different_version dependencies [{ "full_name" => "foo", "version" => "1.1" }] - assert_empty @keg.installed_dependants + assert_empty @keg.installed_dependents 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 + assert_empty @keg.installed_dependents end def test_same_name_and_version dependencies [{ "full_name" => "foo", "version" => "1.0" }] - assert_equal [@dependant], @keg.installed_dependants + assert_equal [@dependent], @keg.installed_dependents end end From 563b56701bce58b87d748283633967a81e833f64 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 27 Sep 2016 22:01:22 +0100 Subject: [PATCH 04/32] keg: rename #formula to #to_formula @vladshablinsky pointed out that other Homebrew classes, like Dependency, use #to_formula. --- Library/Homebrew/keg.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index 23ebd9d98f..257594671a 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -292,7 +292,7 @@ class Keg PkgVersion.parse(path.basename.to_s) end - def formula + def to_formula Formulary.from_keg(self) end @@ -302,7 +302,7 @@ class Keg # 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 + dep_formula == to_formula && dep["version"] == version.to_s end end end From 08c898f28075d04c0e9921d72d9d3791f0dbef8e Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 27 Sep 2016 22:01:22 +0100 Subject: [PATCH 05/32] integration tests: testball install overridable --- Library/Homebrew/test/helper/integration_command_test_case.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Library/Homebrew/test/helper/integration_command_test_case.rb b/Library/Homebrew/test/helper/integration_command_test_case.rb index 20c0fc48ea..d18e4e0de7 100644 --- a/Library/Homebrew/test/helper/integration_command_test_case.rb +++ b/Library/Homebrew/test/helper/integration_command_test_case.rb @@ -127,7 +127,6 @@ class IntegrationCommandTestCase < Homebrew::TestCase sha256 "#{TESTBALL_SHA256}" option "with-foo", "Build with foo" - #{content} def install (prefix/"foo"/"test").write("test") if build.with? "foo" @@ -138,6 +137,8 @@ class IntegrationCommandTestCase < Homebrew::TestCase system ENV.cc, "test.c", "-o", bin/"test" end + #{content} + # something here EOS when "foo" From 888c44b238b6a5afa705ca46f6a682ac1c4bb729 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 27 Sep 2016 22:37:03 +0100 Subject: [PATCH 06/32] uninstall: fix dependent order bug --- Library/Homebrew/cmd/uninstall.rb | 2 +- Library/Homebrew/test/test_uninstall.rb | 35 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index 30806942bc..c45e94b79c 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -16,7 +16,7 @@ module Homebrew if !ARGV.force? ARGV.kegs.each do |keg| - dependents = keg.installed_dependents + dependents = keg.installed_dependents - ARGV.kegs if dependents.any? dependents_output = dependents.map { |k| "#{k.name} #{k.version}" }.join(", ") conjugation = dependents.count == 1 ? "is" : "are" diff --git a/Library/Homebrew/test/test_uninstall.rb b/Library/Homebrew/test/test_uninstall.rb index 050934238e..fe14a79c21 100644 --- a/Library/Homebrew/test/test_uninstall.rb +++ b/Library/Homebrew/test/test_uninstall.rb @@ -1,8 +1,43 @@ require "helper/integration_command_test_case" class IntegrationCommandTestUninstall < IntegrationCommandTestCase + def setup + super + @f1_path = setup_test_formula "testball_f1", <<-CONTENT + def install + FileUtils.touch prefix/touch("hello") + end + CONTENT + @f2_path = setup_test_formula "testball_f2", <<-CONTENT + depends_on "testball_f1" + + def install + FileUtils.touch prefix/touch("hello") + end + CONTENT + end + def test_uninstall cmd("install", testball) assert_match "Uninstalling testball", cmd("uninstall", "--force", testball) end + + def test_uninstall_leaving_dependents + cmd("install", "testball_f2") + assert_match "Refusing to uninstall", cmd_fail("uninstall", "testball_f1") + assert_match "Uninstalling #{Formulary.factory(@f2_path).rack}", + cmd("uninstall", "testball_f2") + end + + def test_uninstall_dependent_first + cmd("install", "testball_f2") + assert_match "Uninstalling #{Formulary.factory(@f1_path).rack}", + cmd("uninstall", "testball_f2", "testball_f1") + end + + def test_uninstall_dependent_last + cmd("install", "testball_f2") + assert_match "Uninstalling #{Formulary.factory(@f2_path).rack}", + cmd("uninstall", "testball_f1", "testball_f2") + end end From b42f76939cebb7267edaeb4fc2cb8a00af4b2789 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Wed, 28 Sep 2016 19:14:33 +0100 Subject: [PATCH 07/32] uninstall: reorganise code With the way uninstall is set up at the moment, it's pretty difficult to add functionality to both the --force and normal variants. Extracting the racks and kegs to be uninstalled before uninstalling them should make this easier. --- Library/Homebrew/cmd/uninstall.rb | 65 +++++++++++++++++-------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index c45e94b79c..2d7bb90e4f 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -14,47 +14,54 @@ module Homebrew def uninstall raise KegUnspecifiedError if ARGV.named.empty? - if !ARGV.force? - ARGV.kegs.each do |keg| - dependents = keg.installed_dependents - ARGV.kegs - if dependents.any? - dependents_output = dependents.map { |k| "#{k.name} #{k.version}" }.join(", ") - conjugation = dependents.count == 1 ? "is" : "are" - ofail "Refusing to uninstall #{keg} because it is required by #{dependents_output}, which #{conjugation} currently installed." - puts "You can override this and force removal with `brew uninstall --force #{keg.name}`." - next - end - - keg.lock do - puts "Uninstalling #{keg}... (#{keg.abv})" - keg.unlink - keg.uninstall - rack = keg.rack - rm_pin rack - - if rack.directory? - versions = rack.subdirs.map(&:basename) - verb = versions.length == 1 ? "is" : "are" - puts "#{keg.name} #{versions.join(", ")} #{verb} still installed." - puts "Remove all versions with `brew uninstall --force #{keg.name}`." - end - end + kegs_by_rack = if ARGV.force? + ARGV.named.map do |name| + rack = Formulary.to_rack(name) + [rack, rack.subdirs.map { |d| Keg.new(d) }] end else - ARGV.named.each do |name| - rack = Formulary.to_rack(name) + ARGV.kegs.group_by(&:rack) + end + + kegs_by_rack.each do |rack, kegs| + if ARGV.force? name = rack.basename if rack.directory? puts "Uninstalling #{name}... (#{rack.abv})" - rack.subdirs.each do |d| - keg = Keg.new(d) + kegs.each do |keg| keg.unlink keg.uninstall end end rm_pin rack + else + kegs.each do |keg| + dependents = keg.installed_dependents - ARGV.kegs + if dependents.any? + dependents_output = dependents.map { |k| "#{k.name} #{k.version}" }.join(", ") + conjugation = dependents.count == 1 ? "is" : "are" + ofail "Refusing to uninstall #{keg} because it is required by #{dependents_output}, which #{conjugation} currently installed." + puts "You can override this and force removal with `brew uninstall --force #{keg.name}`." + next + end + + keg.lock do + puts "Uninstalling #{keg}... (#{keg.abv})" + keg.unlink + keg.uninstall + rack = keg.rack + rm_pin rack + + if rack.directory? + versions = rack.subdirs.map(&:basename) + verb = versions.length == 1 ? "is" : "are" + puts "#{keg.name} #{versions.join(", ")} #{verb} still installed." + puts "Remove all versions with `brew uninstall --force #{keg.name}`." + end + end + end end end rescue MultipleVersionsInstalledError => e From ecb1075390a49e292c6add3495504c24e3ec1572 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Wed, 28 Sep 2016 19:21:47 +0100 Subject: [PATCH 08/32] uninstall: check for dependents even with --force --- Library/Homebrew/cmd/uninstall.rb | 26 ++++++++++++++----------- Library/Homebrew/test/test_uninstall.rb | 8 ++++++++ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index 2d7bb90e4f..5ab9f116f3 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -15,14 +15,27 @@ module Homebrew raise KegUnspecifiedError if ARGV.named.empty? kegs_by_rack = if ARGV.force? - ARGV.named.map do |name| + Hash[ARGV.named.map do |name| rack = Formulary.to_rack(name) [rack, rack.subdirs.map { |d| Keg.new(d) }] - end + end] else ARGV.kegs.group_by(&:rack) end + kegs = kegs_by_rack.values.flatten(1) + + kegs.each do |keg| + dependents = keg.installed_dependents - kegs + if dependents.any? + dependents_output = dependents.map { |k| "#{k.name} #{k.version}" }.join(", ") + conjugation = dependents.count == 1 ? "is" : "are" + ofail "Refusing to uninstall #{keg} because it is required by #{dependents_output}, which #{conjugation} currently installed." + # puts "You can override this and force removal with `brew uninstall --force #{keg.name}`." + next + end + end + kegs_by_rack.each do |rack, kegs| if ARGV.force? name = rack.basename @@ -38,15 +51,6 @@ module Homebrew rm_pin rack else kegs.each do |keg| - dependents = keg.installed_dependents - ARGV.kegs - if dependents.any? - dependents_output = dependents.map { |k| "#{k.name} #{k.version}" }.join(", ") - conjugation = dependents.count == 1 ? "is" : "are" - ofail "Refusing to uninstall #{keg} because it is required by #{dependents_output}, which #{conjugation} currently installed." - puts "You can override this and force removal with `brew uninstall --force #{keg.name}`." - next - end - keg.lock do puts "Uninstalling #{keg}... (#{keg.abv})" keg.unlink diff --git a/Library/Homebrew/test/test_uninstall.rb b/Library/Homebrew/test/test_uninstall.rb index fe14a79c21..dde736624e 100644 --- a/Library/Homebrew/test/test_uninstall.rb +++ b/Library/Homebrew/test/test_uninstall.rb @@ -29,6 +29,14 @@ class IntegrationCommandTestUninstall < IntegrationCommandTestCase cmd("uninstall", "testball_f2") end + def test_uninstall_force_leaving_dependents + cmd("install", "testball_f2") + assert_match "Refusing to uninstall", + cmd_fail("uninstall", "testball_f1", "--force") + assert_match "Uninstalling testball_f2", + cmd("uninstall", "testball_f2", "--force") + end + def test_uninstall_dependent_first cmd("install", "testball_f2") assert_match "Uninstalling #{Formulary.factory(@f1_path).rack}", From 13d705c5e7683ccfdb415642013daa34b6a0f1e5 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Wed, 28 Sep 2016 20:53:41 +0100 Subject: [PATCH 09/32] integration tests: preserve HOMEBREW_DEVELOPER This means that run_as_not_developer can be used in integration tests --- Library/Homebrew/test/helper/integration_command_test_case.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Library/Homebrew/test/helper/integration_command_test_case.rb b/Library/Homebrew/test/helper/integration_command_test_case.rb index d18e4e0de7..2f137e14a3 100644 --- a/Library/Homebrew/test/helper/integration_command_test_case.rb +++ b/Library/Homebrew/test/helper/integration_command_test_case.rb @@ -73,10 +73,12 @@ class IntegrationCommandTestCase < Homebrew::TestCase cmd_args << "-rintegration_mocks" cmd_args << (HOMEBREW_LIBRARY_PATH/"brew.rb").resolved_path.to_s cmd_args += args + developer = ENV["HOMEBREW_DEVELOPER"] Bundler.with_original_env do ENV["HOMEBREW_BREW_FILE"] = HOMEBREW_PREFIX/"bin/brew" ENV["HOMEBREW_INTEGRATION_TEST"] = cmd_id_from_args(args) ENV["HOMEBREW_TEST_TMPDIR"] = TEST_TMPDIR + ENV["HOMEBREW_DEVELOPER"] = developer env.each_pair do |k, v| ENV[k] = v end From 7792acda52c9c3b810959ac2c20066d3cf1fbe15 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Wed, 28 Sep 2016 20:55:24 +0100 Subject: [PATCH 10/32] uninstall: allow dependent checks to be by-passed Dependent can be bypassed with `--ignore-dependencies`. This is now the default for `HOMEBREW_DEVELOPER`s. --- Library/Homebrew/cmd/uninstall.rb | 29 +++++++----- Library/Homebrew/test/test_uninstall.rb | 62 ++++++++++++++++++++----- 2 files changed, 69 insertions(+), 22 deletions(-) diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index 5ab9f116f3..b9e50db234 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -23,17 +23,10 @@ module Homebrew ARGV.kegs.group_by(&:rack) end - kegs = kegs_by_rack.values.flatten(1) - - kegs.each do |keg| - dependents = keg.installed_dependents - kegs - if dependents.any? - dependents_output = dependents.map { |k| "#{k.name} #{k.version}" }.join(", ") - conjugation = dependents.count == 1 ? "is" : "are" - ofail "Refusing to uninstall #{keg} because it is required by #{dependents_output}, which #{conjugation} currently installed." - # puts "You can override this and force removal with `brew uninstall --force #{keg.name}`." - next - end + # --ignore-dependencies, to be consistent with install + unless ARGV.include?("--ignore-dependencies") || ARGV.homebrew_developer? + kegs = kegs_by_rack.values.flatten(1) + return if check_for_dependents kegs end kegs_by_rack.each do |rack, kegs| @@ -81,6 +74,20 @@ module Homebrew end end + def check_for_dependents(kegs) + kegs.each do |keg| + dependents = keg.installed_dependents - kegs + if dependents.any? + dependents_output = dependents.map { |k| "#{k.name} #{k.version}" }.join(", ") + conjugation = dependents.count == 1 ? "is" : "are" + ofail "Refusing to uninstall #{keg} because it is required by #{dependents_output}, which #{conjugation} currently installed." + puts "You can override this and force removal with `brew uninstall --ignore-dependencies #{keg.name}`." + return true + end + end + false + end + def rm_pin(rack) Formulary.from_rack(rack).unpin rescue diff --git a/Library/Homebrew/test/test_uninstall.rb b/Library/Homebrew/test/test_uninstall.rb index dde736624e..ede1e321b3 100644 --- a/Library/Homebrew/test/test_uninstall.rb +++ b/Library/Homebrew/test/test_uninstall.rb @@ -17,35 +17,75 @@ class IntegrationCommandTestUninstall < IntegrationCommandTestCase CONTENT end + def f1 + Formulary.factory(@f1_path) + end + + def f2 + Formulary.factory(@f2_path) + end + def test_uninstall cmd("install", testball) assert_match "Uninstalling testball", cmd("uninstall", "--force", testball) + assert_empty Formulary.factory(testball).installed_kegs end def test_uninstall_leaving_dependents cmd("install", "testball_f2") - assert_match "Refusing to uninstall", cmd_fail("uninstall", "testball_f1") - assert_match "Uninstalling #{Formulary.factory(@f2_path).rack}", - cmd("uninstall", "testball_f2") + run_as_not_developer do + assert_match "Refusing to uninstall", + cmd_fail("uninstall", "testball_f1") + refute_empty f1.installed_kegs + assert_match "Uninstalling #{f2.rack}", + cmd("uninstall", "testball_f2") + assert_empty f2.installed_kegs + end end def test_uninstall_force_leaving_dependents cmd("install", "testball_f2") - assert_match "Refusing to uninstall", - cmd_fail("uninstall", "testball_f1", "--force") - assert_match "Uninstalling testball_f2", - cmd("uninstall", "testball_f2", "--force") + run_as_not_developer do + assert_match "Refusing to uninstall", + cmd_fail("uninstall", "testball_f1", "--force") + refute_empty f1.installed_kegs + assert_match "Uninstalling testball_f2", + cmd("uninstall", "testball_f2", "--force") + assert_empty f2.installed_kegs + end + end + + def test_uninstall_ignore_dependencies_leaving_dependents + cmd("install", "testball_f2") + run_as_not_developer do + assert_match "Uninstalling #{f1.rack}", + cmd("uninstall", "testball_f1", "--ignore-dependencies") + assert_empty f1.installed_kegs + end + end + + def test_uninstall_leaving_dependents_developer + cmd("install", "testball_f2") + assert_match "Uninstalling #{f1.rack}", + cmd("uninstall", "testball_f1") + assert_empty f1.installed_kegs end def test_uninstall_dependent_first cmd("install", "testball_f2") - assert_match "Uninstalling #{Formulary.factory(@f1_path).rack}", - cmd("uninstall", "testball_f2", "testball_f1") + run_as_not_developer do + assert_match "Uninstalling #{f1.rack}", + cmd("uninstall", "testball_f2", "testball_f1") + assert_empty f1.installed_kegs + end end def test_uninstall_dependent_last cmd("install", "testball_f2") - assert_match "Uninstalling #{Formulary.factory(@f2_path).rack}", - cmd("uninstall", "testball_f1", "testball_f2") + run_as_not_developer do + assert_match "Uninstalling #{f2.rack}", + cmd("uninstall", "testball_f1", "testball_f2") + assert_empty f2.installed_kegs + end end end From ed0fffd93117a5d2d0297c5965d661e2e7e250de Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Thu, 29 Sep 2016 11:24:16 +0100 Subject: [PATCH 11/32] uninstall: style fixes --- Library/Homebrew/cmd/uninstall.rb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index b9e50db234..e00109bac0 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -15,18 +15,18 @@ module Homebrew raise KegUnspecifiedError if ARGV.named.empty? kegs_by_rack = if ARGV.force? - Hash[ARGV.named.map do |name| + Hash[ARGV.named.map { |name| rack = Formulary.to_rack(name) [rack, rack.subdirs.map { |d| Keg.new(d) }] - end] + }] else ARGV.kegs.group_by(&:rack) end # --ignore-dependencies, to be consistent with install unless ARGV.include?("--ignore-dependencies") || ARGV.homebrew_developer? - kegs = kegs_by_rack.values.flatten(1) - return if check_for_dependents kegs + all_kegs = kegs_by_rack.values.flatten(1) + return if check_for_dependents all_kegs end kegs_by_rack.each do |rack, kegs| @@ -77,13 +77,13 @@ module Homebrew def check_for_dependents(kegs) kegs.each do |keg| dependents = keg.installed_dependents - kegs - if dependents.any? - dependents_output = dependents.map { |k| "#{k.name} #{k.version}" }.join(", ") - conjugation = dependents.count == 1 ? "is" : "are" - ofail "Refusing to uninstall #{keg} because it is required by #{dependents_output}, which #{conjugation} currently installed." - puts "You can override this and force removal with `brew uninstall --ignore-dependencies #{keg.name}`." - return true - end + next if dependents.empty? + + dependents_output = dependents.map { |k| "#{k.name} #{k.version}" }.join(", ") + conjugation = dependents.count == 1 ? "is" : "are" + ofail "Refusing to uninstall #{keg} because it is required by #{dependents_output}, which #{conjugation} currently installed." + puts "You can override this and force removal with `brew uninstall --ignore-dependencies #{keg.name}`." + return true end false end From 7fa4ffe3dc3dd577fa04c374cc965c1b6f303632 Mon Sep 17 00:00:00 2001 From: ilovezfs Date: Thu, 29 Sep 2016 11:28:01 +0100 Subject: [PATCH 12/32] missing: allow hiding specified formulae --- Library/Homebrew/cmd/missing.rb | 2 +- Library/Homebrew/diagnostic.rb | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Library/Homebrew/cmd/missing.rb b/Library/Homebrew/cmd/missing.rb index 148fe5bef0..4a9e0b7b32 100644 --- a/Library/Homebrew/cmd/missing.rb +++ b/Library/Homebrew/cmd/missing.rb @@ -18,7 +18,7 @@ module Homebrew ARGV.resolved_formulae end - Diagnostic.missing_deps(ff) do |name, missing| + Diagnostic.missing_deps(ff, ARGV.value("hide")) do |name, missing| print "#{name}: " if ff.size > 1 puts missing.join(" ") end diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index 6f53bf0e10..0031811c58 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -7,7 +7,7 @@ require "utils/shell" module Homebrew module Diagnostic - def self.missing_deps(ff) + def self.missing_deps(ff, hide = nil) missing = {} ff.each do |f| missing_deps = f.recursive_dependencies do |dependent, dep| @@ -20,7 +20,13 @@ module Homebrew end missing_deps.map!(&:to_formula) - missing_deps.reject! { |d| d.installed_prefixes.any? } + if hide + missing_deps.reject! do |d| + !hide.include?(d.name) && d.installed_prefixes.any? + end + else + missing_deps.reject! { |d| d.installed_prefixes.any? } + end unless missing_deps.empty? yield f.full_name, missing_deps if block_given? From 8e3e8e31c23e5ec6e204ee5462cb9d22119891ee Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Fri, 30 Sep 2016 14:10:40 +0100 Subject: [PATCH 13/32] missing: add tests for not missing and hide --- Library/Homebrew/test/test_missing.rb | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Library/Homebrew/test/test_missing.rb b/Library/Homebrew/test/test_missing.rb index 3a5fd3df09..565f413daf 100644 --- a/Library/Homebrew/test/test_missing.rb +++ b/Library/Homebrew/test/test_missing.rb @@ -1,11 +1,34 @@ require "helper/integration_command_test_case" class IntegrationCommandTestMissing < IntegrationCommandTestCase - def test_missing + def setup + super + setup_test_formula "foo" setup_test_formula "bar" + end + + def make_prefix(name) + (HOMEBREW_CELLAR/name/"1.0").mkpath + end + + def test_missing_missing + make_prefix "bar" - (HOMEBREW_CELLAR/"bar/1.0").mkpath assert_match "foo", cmd("missing") end + + def test_missing_not_missing + make_prefix "foo" + make_prefix "bar" + + assert_empty cmd("missing") + end + + def test_missing_hide + make_prefix "foo" + make_prefix "bar" + + assert_match "foo", cmd("missing", "--hide=foo") + end end From c88b67f3a8f7e90ad974eaf82d00fd88827a1e4c Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Fri, 30 Sep 2016 14:14:14 +0100 Subject: [PATCH 14/32] missing: simplify code a bit --- Library/Homebrew/diagnostic.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index 0031811c58..a5b1302bde 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -8,6 +8,8 @@ require "utils/shell" module Homebrew module Diagnostic def self.missing_deps(ff, hide = nil) + hide ||= [] + missing = {} ff.each do |f| missing_deps = f.recursive_dependencies do |dependent, dep| @@ -20,12 +22,8 @@ module Homebrew end missing_deps.map!(&:to_formula) - if hide - missing_deps.reject! do |d| - !hide.include?(d.name) && d.installed_prefixes.any? - end - else - missing_deps.reject! { |d| d.installed_prefixes.any? } + missing_deps.reject! do |d| + !hide.include?(d.name) && d.installed_prefixes.any? end unless missing_deps.empty? From ae3f53a1ecb19ea70e5d461a3205e6434bbe0c56 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Fri, 30 Sep 2016 14:16:00 +0100 Subject: [PATCH 15/32] keg: fallback to missing_deps if deps not in tab --- Library/Homebrew/keg.rb | 24 ++++++++++++++++++++++-- Library/Homebrew/test/test_keg.rb | 21 +++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index 257594671a..dfc27b55a9 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -1,6 +1,7 @@ require "extend/pathname" require "keg_relocate" require "formula_lock" +require "diagnostic" require "ostruct" class Keg @@ -297,14 +298,33 @@ class Keg end def installed_dependents - Formula.installed.flat_map(&:installed_kegs).select do |keg| - Tab.for_keg(keg).runtime_dependencies.any? do |dep| + installed_kegs = Formula.installed.flat_map(&:installed_kegs) + installed_kegs_and_tabs = installed_kegs.map { |k| [k, Tab.for_keg(k)] } + kegs_by_tab_deps_presence = installed_kegs_and_tabs.group_by do |_, tab| + !tab.runtime_dependencies.nil? + end + [true, false].each { |v| kegs_by_tab_deps_presence[v] ||= [] } + + kegs_by_tab_deps_presence[true].select! do |_, tab| + tab.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 == to_formula && dep["version"] == version.to_s end end + + remaining_kegs_and_tabs = kegs_by_tab_deps_presence[false] + remaining_formulae = remaining_kegs_and_tabs.map { |k, _| k.to_formula } + + # Expensive if installed_dependents of multiple kegs are being checked + deps = Homebrew::Diagnostic.missing_deps(remaining_formulae, [name]) + remaining_kegs_and_tabs.select! do |keg, _| + keg_deps = deps[keg.to_formula.full_name] + keg_deps && keg_deps.any? + end + + kegs_by_tab_deps_presence.values.flatten(1).map { |k, _| k } end def find(*args, &block) diff --git a/Library/Homebrew/test/test_keg.rb b/Library/Homebrew/test/test_keg.rb index ac5831b092..9c1c9118c9 100644 --- a/Library/Homebrew/test/test_keg.rb +++ b/Library/Homebrew/test/test_keg.rb @@ -339,8 +339,29 @@ class InstalledDependantsTests < LinkTests tab.write end + def test_unknown_dependencies + dependencies nil + + bar = formula "bar" do + url "bar-1.0" + depends_on "foo" + end + stub_formula_loader bar + + assert_equal [@dependent], @keg.installed_dependents + end + def test_no_dependencies dependencies [] + + # Make sure formula dependencies aren't checked when dependencies are + # recorded in the tab. + bar = formula "bar" do + url "bar-1.0" + depends_on "foo" + end + stub_formula_loader bar + assert_empty @keg.installed_dependents end From 452691528d4c5c67175963ddb33f5cc3d51dce41 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Fri, 30 Sep 2016 18:13:12 +0100 Subject: [PATCH 16/32] Revert "keg: fallback to missing_deps if deps not in tab" This reverts commit da1caba17c624f03fa8e6fbe59683f57fb7ac17a. --- Library/Homebrew/keg.rb | 24 ++---------------------- Library/Homebrew/test/test_keg.rb | 21 --------------------- 2 files changed, 2 insertions(+), 43 deletions(-) diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index dfc27b55a9..257594671a 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -1,7 +1,6 @@ require "extend/pathname" require "keg_relocate" require "formula_lock" -require "diagnostic" require "ostruct" class Keg @@ -298,33 +297,14 @@ class Keg end def installed_dependents - installed_kegs = Formula.installed.flat_map(&:installed_kegs) - installed_kegs_and_tabs = installed_kegs.map { |k| [k, Tab.for_keg(k)] } - kegs_by_tab_deps_presence = installed_kegs_and_tabs.group_by do |_, tab| - !tab.runtime_dependencies.nil? - end - [true, false].each { |v| kegs_by_tab_deps_presence[v] ||= [] } - - kegs_by_tab_deps_presence[true].select! do |_, tab| - tab.runtime_dependencies.any? do |dep| + 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 == to_formula && dep["version"] == version.to_s end end - - remaining_kegs_and_tabs = kegs_by_tab_deps_presence[false] - remaining_formulae = remaining_kegs_and_tabs.map { |k, _| k.to_formula } - - # Expensive if installed_dependents of multiple kegs are being checked - deps = Homebrew::Diagnostic.missing_deps(remaining_formulae, [name]) - remaining_kegs_and_tabs.select! do |keg, _| - keg_deps = deps[keg.to_formula.full_name] - keg_deps && keg_deps.any? - end - - kegs_by_tab_deps_presence.values.flatten(1).map { |k, _| k } end def find(*args, &block) diff --git a/Library/Homebrew/test/test_keg.rb b/Library/Homebrew/test/test_keg.rb index 9c1c9118c9..ac5831b092 100644 --- a/Library/Homebrew/test/test_keg.rb +++ b/Library/Homebrew/test/test_keg.rb @@ -339,29 +339,8 @@ class InstalledDependantsTests < LinkTests tab.write end - def test_unknown_dependencies - dependencies nil - - bar = formula "bar" do - url "bar-1.0" - depends_on "foo" - end - stub_formula_loader bar - - assert_equal [@dependent], @keg.installed_dependents - end - def test_no_dependencies dependencies [] - - # Make sure formula dependencies aren't checked when dependencies are - # recorded in the tab. - bar = formula "bar" do - url "bar-1.0" - depends_on "foo" - end - stub_formula_loader bar - assert_empty @keg.installed_dependents end From ef13f8eacaa62be6d2e4ee0af5041a6563fd0c71 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Fri, 30 Sep 2016 19:34:14 +0100 Subject: [PATCH 17/32] uninstall: only <=1 Diagnostic.missing_deps call --- Library/Homebrew/cmd/uninstall.rb | 45 ++++++++++++++++++++----- Library/Homebrew/keg.rb | 4 ++- Library/Homebrew/test/test_uninstall.rb | 18 ++++++++++ 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index e00109bac0..92b3beff47 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -6,6 +6,7 @@ require "keg" require "formula" +require "diagnostic" require "migrator" module Homebrew @@ -75,17 +76,45 @@ module Homebrew end def check_for_dependents(kegs) + return false unless result = find_some_installed_dependents(kegs) + + requireds, dependents = result + + msg = "Refusing to uninstall #{requireds.join(", ")} because " + msg << (requireds.count == 1 ? "it is" : "they are") + msg << " required by #{dependents.join(", ")}, which " + msg << (dependents.count == 1 ? "is" : "are") + msg << " currently installed." + ofail msg + print "You can override this and force removal with " + puts "`brew uninstall --ignore-dependencies #{requireds.map(&:name).join(" ")}`." + + true + end + + # Will return some kegs, and some dependencies, if they're present. + # For efficiency, we don't bother trying to get complete data. + def find_some_installed_dependents(kegs) kegs.each do |keg| dependents = keg.installed_dependents - kegs - next if dependents.empty? - - dependents_output = dependents.map { |k| "#{k.name} #{k.version}" }.join(", ") - conjugation = dependents.count == 1 ? "is" : "are" - ofail "Refusing to uninstall #{keg} because it is required by #{dependents_output}, which #{conjugation} currently installed." - puts "You can override this and force removal with `brew uninstall --ignore-dependencies #{keg.name}`." - return true + dependents.map! { |d| "#{d.name} #{d.version}" } + return [keg], dependents if dependents.any? end - false + + # Find formulae that didn't have dependencies saved in all of their kegs, + # so need them to be calculated now. + # + # This happens after the initial dependency check because it's sloooow. + remaining_formulae = Formula.installed.select { |f| + f.installed_kegs.any? { |k| Tab.for_keg(k).runtime_dependencies.nil? } + } + Diagnostic.missing_deps(remaining_formulae, kegs.map(&:name)) do |dependent, required| + kegs_by_name = kegs.group_by(&:to_formula) + required_kegs = required.map { |f| kegs_by_name[f].sort_by(&:version).last } + return required_kegs, [dependent] + end + + nil end def rm_pin(rack) diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index 257594671a..16717ea4ed 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -298,7 +298,9 @@ class Keg def installed_dependents Formula.installed.flat_map(&:installed_kegs).select do |keg| - Tab.for_keg(keg).runtime_dependencies.any? do |dep| + tab = Tab.for_keg(keg) + next if tab.runtime_dependencies.nil? # no dependency information saved. + tab.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"]) diff --git a/Library/Homebrew/test/test_uninstall.rb b/Library/Homebrew/test/test_uninstall.rb index ede1e321b3..c36a14477c 100644 --- a/Library/Homebrew/test/test_uninstall.rb +++ b/Library/Homebrew/test/test_uninstall.rb @@ -43,6 +43,24 @@ class IntegrationCommandTestUninstall < IntegrationCommandTestCase end end + def test_uninstall_leaving_dependents_no_runtime_dependencies_in_tab + cmd("install", "testball_f2") + + f2_keg = f2.installed_kegs.first + f2_tab = Tab.for_keg(f2_keg) + f2_tab.runtime_dependencies = nil + f2_tab.write + + run_as_not_developer do + assert_match "Refusing to uninstall", + cmd_fail("uninstall", "testball_f1") + refute_empty f1.installed_kegs + assert_match "Uninstalling #{f2.rack}", + cmd("uninstall", "testball_f2") + assert_empty f2.installed_kegs + end + end + def test_uninstall_force_leaving_dependents cmd("install", "testball_f2") run_as_not_developer do From 99a7fb8cb4c68fa3d7821fb2d164fbd1211437c1 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Sun, 2 Oct 2016 10:45:06 +0100 Subject: [PATCH 18/32] uninstall: don't use unless || --- Library/Homebrew/cmd/uninstall.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index 92b3beff47..f9cf56f444 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -25,7 +25,7 @@ module Homebrew end # --ignore-dependencies, to be consistent with install - unless ARGV.include?("--ignore-dependencies") || ARGV.homebrew_developer? + if !ARGV.include?("--ignore-dependencies") && !ARGV.homebrew_developer? all_kegs = kegs_by_rack.values.flatten(1) return if check_for_dependents all_kegs end From 0cd983487c97ff373ccd0a73631ff57ae1b3baa0 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Wed, 5 Oct 2016 20:56:07 +0100 Subject: [PATCH 19/32] missing_deps: extract formula instance method --- Library/Homebrew/diagnostic.rb | 22 ++++------------------ Library/Homebrew/formula.rb | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index a5b1302bde..b434c394bd 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -8,27 +8,13 @@ require "utils/shell" module Homebrew module Diagnostic def self.missing_deps(ff, hide = nil) - hide ||= [] - missing = {} ff.each do |f| - missing_deps = f.recursive_dependencies do |dependent, dep| - if dep.optional? || dep.recommended? - tab = Tab.for_formula(dependent) - Dependency.prune unless tab.with?(dep) - elsif dep.build? - Dependency.prune - end - end + missing_dependencies = f.missing_dependencies(hide: hide) - missing_deps.map!(&:to_formula) - missing_deps.reject! do |d| - !hide.include?(d.name) && d.installed_prefixes.any? - end - - unless missing_deps.empty? - yield f.full_name, missing_deps if block_given? - missing[f.full_name] = missing_deps + unless missing_dependencies.empty? + yield f.full_name, missing_dependencies if block_given? + missing[f.full_name] = missing_dependencies end end missing diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index c2467b7bce..9473130474 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -1466,6 +1466,26 @@ class Formula recursive_dependencies.reject(&:build?) end + # Returns a list of formulae depended on by this formula that aren't + # installed + def missing_dependencies(hide: nil) + hide ||= [] + missing_dependencies = recursive_dependencies do |dependent, dep| + if dep.optional? || dep.recommended? + tab = Tab.for_formula(dependent) + Dependency.prune unless tab.with?(dep) + elsif dep.build? + Dependency.prune + end + end + + missing_dependencies.map!(&:to_formula) + missing_dependencies.select! do |d| + hide.include?(d.name) || d.installed_prefixes.empty? + end + missing_dependencies + end + # @private def to_hash hsh = { From 422f38b945ac9f13ea5f9290b022a18c811445e4 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Wed, 5 Oct 2016 21:14:43 +0100 Subject: [PATCH 20/32] missing: call Formula#missing_dependencies directly --- Library/Homebrew/cmd/missing.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Library/Homebrew/cmd/missing.rb b/Library/Homebrew/cmd/missing.rb index 4a9e0b7b32..44d2aa8aef 100644 --- a/Library/Homebrew/cmd/missing.rb +++ b/Library/Homebrew/cmd/missing.rb @@ -18,8 +18,13 @@ module Homebrew ARGV.resolved_formulae end - Diagnostic.missing_deps(ff, ARGV.value("hide")) do |name, missing| - print "#{name}: " if ff.size > 1 + hide = (ARGV.value("hide") || "").split(",") + + ff.each do |f| + missing = f.missing_dependencies(hide: hide) + next if missing.empty? + + print "#{f}: " if ff.size > 1 puts missing.join(" ") end end From a4dc835ba06e7d1cd2a6b6b4ffacf4416b35ffea Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Wed, 5 Oct 2016 22:22:32 +0100 Subject: [PATCH 21/32] uninstall: call Formula#missing_dependencies directly --- Library/Homebrew/cmd/uninstall.rb | 14 +++++++--- Library/Homebrew/keg.rb | 35 +++++++++++++++++++++++++ Library/Homebrew/test/test_uninstall.rb | 26 ++++++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index f9cf56f444..2c7be04be5 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -76,7 +76,7 @@ module Homebrew end def check_for_dependents(kegs) - return false unless result = find_some_installed_dependents(kegs) + return false unless result = Keg.find_some_installed_dependents(kegs) requireds, dependents = result @@ -108,8 +108,16 @@ module Homebrew remaining_formulae = Formula.installed.select { |f| f.installed_kegs.any? { |k| Tab.for_keg(k).runtime_dependencies.nil? } } - Diagnostic.missing_deps(remaining_formulae, kegs.map(&:name)) do |dependent, required| - kegs_by_name = kegs.group_by(&:to_formula) + + keg_names = kegs.map(&:name) + kegs_by_name = kegs.group_by(&:to_formula) + remaining_formulae.each do |dependent| + required = dependent.missing_dependencies(hide: keg_names) + required.select! do |f| + kegs_by_name.key?(f) + end + next unless required.any? + required_kegs = required.map { |f| kegs_by_name[f].sort_by(&:version).last } return required_kegs, [dependent] end diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index 16717ea4ed..d86236037b 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -87,6 +87,41 @@ class Keg mime-info pixmaps sounds postgresql ].freeze + # Will return some kegs, and some dependencies, if they're present. + # For efficiency, we don't bother trying to get complete data. + def self.find_some_installed_dependents(kegs) + # First, check in the tabs of installed Formulae. + kegs.each do |keg| + dependents = keg.installed_dependents - kegs + dependents.map! { |d| "#{d.name} #{d.version}" } + return [keg], dependents if dependents.any? + end + + # Some kegs won't have modern Tabs with the dependencies listed. + # In this case, fall back to Formula#missing_dependencies. + + # Find formulae that didn't have dependencies saved in all of their kegs, + # so need them to be calculated now. + # + # This happens after the initial dependency check because it's sloooow. + remaining_formulae = Formula.installed.select { |f| + f.installed_kegs.any? { |k| Tab.for_keg(k).runtime_dependencies.nil? } + } + + keg_names = kegs.map(&:name) + kegs_by_name = kegs.group_by(&:to_formula) + remaining_formulae.each do |dependent| + required = dependent.missing_dependencies(hide: keg_names) + required.select! { |f| kegs_by_name.key?(f) } + next unless required.any? + + required_kegs = required.map { |f| kegs_by_name[f].sort_by(&:version).last } + return required_kegs, [dependent] + end + + nil + end + # if path is a file in a keg then this will return the containing Keg object def self.for(path) path = path.realpath diff --git a/Library/Homebrew/test/test_uninstall.rb b/Library/Homebrew/test/test_uninstall.rb index c36a14477c..a41e5e9d1c 100644 --- a/Library/Homebrew/test/test_uninstall.rb +++ b/Library/Homebrew/test/test_uninstall.rb @@ -31,6 +31,32 @@ class IntegrationCommandTestUninstall < IntegrationCommandTestCase assert_empty Formulary.factory(testball).installed_kegs end + def test_uninstall_with_unrelated_missing_deps_in_tab + setup_test_formula "testball" + run_as_not_developer do + cmd("install", testball) + cmd("install", "testball_f2") + cmd("uninstall", "--ignore-dependencies", "testball_f1") + cmd("uninstall", testball) + end + end + + def test_uninstall_with_unrelated_missing_deps_not_in_tab + setup_test_formula "testball" + run_as_not_developer do + cmd("install", testball) + cmd("install", "testball_f2") + + f2_keg = f2.installed_kegs.first + f2_tab = Tab.for_keg(f2_keg) + f2_tab.runtime_dependencies = nil + f2_tab.write + + cmd("uninstall", "--ignore-dependencies", "testball_f1") + cmd("uninstall", testball) + end + end + def test_uninstall_leaving_dependents cmd("install", "testball_f2") run_as_not_developer do From c4c855b9fc8695664a66dd7822a483b91e3e26e3 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Mon, 10 Oct 2016 13:26:09 +0100 Subject: [PATCH 22/32] ARGV: extract #values from missing --- Library/Homebrew/cmd/missing.rb | 4 +--- Library/Homebrew/extend/ARGV.rb | 7 +++++++ Library/Homebrew/test/test_ARGV.rb | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Library/Homebrew/cmd/missing.rb b/Library/Homebrew/cmd/missing.rb index 44d2aa8aef..d4c07a8c08 100644 --- a/Library/Homebrew/cmd/missing.rb +++ b/Library/Homebrew/cmd/missing.rb @@ -18,10 +18,8 @@ module Homebrew ARGV.resolved_formulae end - hide = (ARGV.value("hide") || "").split(",") - ff.each do |f| - missing = f.missing_dependencies(hide: hide) + missing = f.missing_dependencies(hide: ARGV.values("hide")) next if missing.empty? print "#{f}: " if ff.size > 1 diff --git a/Library/Homebrew/extend/ARGV.rb b/Library/Homebrew/extend/ARGV.rb index 0adf8d548b..bd60cbeccb 100644 --- a/Library/Homebrew/extend/ARGV.rb +++ b/Library/Homebrew/extend/ARGV.rb @@ -121,6 +121,13 @@ module HomebrewArgvExtension flag_with_value.strip_prefix(arg_prefix) if flag_with_value end + # Returns an array of values that were given as a comma-seperated list. + # @see value + def values(name) + return unless val = value(name) + val.split(",") + end + def force? flag? "--force" end diff --git a/Library/Homebrew/test/test_ARGV.rb b/Library/Homebrew/test/test_ARGV.rb index 39f32f4523..6805e0c620 100644 --- a/Library/Homebrew/test/test_ARGV.rb +++ b/Library/Homebrew/test/test_ARGV.rb @@ -62,4 +62,19 @@ class ArgvExtensionTests < Homebrew::TestCase assert !@argv.flag?("--frotz") assert !@argv.flag?("--debug") end + + def test_value + @argv << "--foo=" << "--bar=ab" + assert_equal "", @argv.value("foo") + assert_equal "ab", @argv.value("bar") + assert_nil @argv.value("baz") + end + + def test_values + @argv << "--foo=" << "--bar=a" << "--baz=b,c" + assert_equal [], @argv.values("foo") + assert_equal ["a"], @argv.values("bar") + assert_equal ["b", "c"], @argv.values("baz") + assert_nil @argv.values("qux") + end end From aff5f42c58f735a90f50f4992ce037e4de413f8c Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Mon, 10 Oct 2016 13:41:27 +0100 Subject: [PATCH 23/32] missing: document --hide --- Library/Homebrew/cmd/missing.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Library/Homebrew/cmd/missing.rb b/Library/Homebrew/cmd/missing.rb index d4c07a8c08..891846cb8d 100644 --- a/Library/Homebrew/cmd/missing.rb +++ b/Library/Homebrew/cmd/missing.rb @@ -1,6 +1,9 @@ -#: * `missing` []: +#: * `missing` [`--hide=`] []: #: Check the given for missing dependencies. If no are #: given, check all installed brews. +#: +#: If `--hide=` is passed, act as if none of are installed. +#: should be a comma-seperated list of formulae. require "formula" require "tab" From d0ad09708218bfbffed4857387b259bceba177c2 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 25 Oct 2016 23:48:00 +0100 Subject: [PATCH 24/32] uninstall: remove duplicated method This was moved to Keg, but looks like I forgot to get rid of it here. --- Library/Homebrew/cmd/uninstall.rb | 33 ------------------------------- 1 file changed, 33 deletions(-) diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index 2c7be04be5..e13ca83853 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -92,39 +92,6 @@ module Homebrew true end - # Will return some kegs, and some dependencies, if they're present. - # For efficiency, we don't bother trying to get complete data. - def find_some_installed_dependents(kegs) - kegs.each do |keg| - dependents = keg.installed_dependents - kegs - dependents.map! { |d| "#{d.name} #{d.version}" } - return [keg], dependents if dependents.any? - end - - # Find formulae that didn't have dependencies saved in all of their kegs, - # so need them to be calculated now. - # - # This happens after the initial dependency check because it's sloooow. - remaining_formulae = Formula.installed.select { |f| - f.installed_kegs.any? { |k| Tab.for_keg(k).runtime_dependencies.nil? } - } - - keg_names = kegs.map(&:name) - kegs_by_name = kegs.group_by(&:to_formula) - remaining_formulae.each do |dependent| - required = dependent.missing_dependencies(hide: keg_names) - required.select! do |f| - kegs_by_name.key?(f) - end - next unless required.any? - - required_kegs = required.map { |f| kegs_by_name[f].sort_by(&:version).last } - return required_kegs, [dependent] - end - - nil - end - def rm_pin(rack) Formulary.from_rack(rack).unpin rescue From 5a3d6c4c8f8254c8234157c16dbb80c23ffd49b1 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 25 Oct 2016 23:48:34 +0100 Subject: [PATCH 25/32] uninstall, keg: update style --- Library/Homebrew/cmd/uninstall.rb | 4 ++-- Library/Homebrew/keg.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index e13ca83853..a05adece69 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -16,10 +16,10 @@ module Homebrew raise KegUnspecifiedError if ARGV.named.empty? kegs_by_rack = if ARGV.force? - Hash[ARGV.named.map { |name| + Hash[ARGV.named.map do |name| rack = Formulary.to_rack(name) [rack, rack.subdirs.map { |d| Keg.new(d) }] - }] + end] else ARGV.kegs.group_by(&:rack) end diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index d86236037b..ccd3cc2c15 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -104,9 +104,9 @@ class Keg # so need them to be calculated now. # # This happens after the initial dependency check because it's sloooow. - remaining_formulae = Formula.installed.select { |f| + remaining_formulae = Formula.installed.select do |f| f.installed_kegs.any? { |k| Tab.for_keg(k).runtime_dependencies.nil? } - } + end keg_names = kegs.map(&:name) kegs_by_name = kegs.group_by(&:to_formula) From 3702e561d6e5a5d63a63da1da62bbcfc73545f96 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 25 Oct 2016 23:53:10 +0100 Subject: [PATCH 26/32] uninstall: test should_check_for_dependents? --- Library/Homebrew/cmd/uninstall.rb | 10 ++++++++-- Library/Homebrew/test/test_uninstall.rb | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index a05adece69..504ee99835 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -24,8 +24,7 @@ module Homebrew ARGV.kegs.group_by(&:rack) end - # --ignore-dependencies, to be consistent with install - if !ARGV.include?("--ignore-dependencies") && !ARGV.homebrew_developer? + if should_check_for_dependents? all_kegs = kegs_by_rack.values.flatten(1) return if check_for_dependents all_kegs end @@ -75,6 +74,13 @@ module Homebrew end end + def should_check_for_dependents? + # --ignore-dependencies, to be consistent with install + return false if ARGV.include?("--ignore-dependencies") + return false if ARGV.homebrew_developer? + true + end + def check_for_dependents(kegs) return false unless result = Keg.find_some_installed_dependents(kegs) diff --git a/Library/Homebrew/test/test_uninstall.rb b/Library/Homebrew/test/test_uninstall.rb index a41e5e9d1c..9999b2bb2e 100644 --- a/Library/Homebrew/test/test_uninstall.rb +++ b/Library/Homebrew/test/test_uninstall.rb @@ -1,4 +1,26 @@ require "helper/integration_command_test_case" +require "cmd/uninstall" + +class UninstallTests < Homebrew::TestCase + def test_check_for_testball_f2s_when_developer + refute_predicate Homebrew, :should_check_for_dependents? + end + + def test_check_for_dependents_when_not_developer + run_as_not_developer do + assert_predicate Homebrew, :should_check_for_dependents? + end + end + + def test_check_for_dependents_when_ignore_dependencies + ARGV << "--ignore-dependencies" + run_as_not_developer do + refute_predicate Homebrew, :should_check_for_dependents? + end + ensure + ARGV.delete("--ignore-dependencies") + end +end class IntegrationCommandTestUninstall < IntegrationCommandTestCase def setup From bb30b01d5dad465c1cded9e5e0a47fc6cb9e20c6 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 25 Oct 2016 23:53:59 +0100 Subject: [PATCH 27/32] uninstall: go easier on the integration tests --- Library/Homebrew/keg.rb | 2 +- Library/Homebrew/test/test_keg.rb | 39 +++++++--- Library/Homebrew/test/test_uninstall.rb | 95 +------------------------ 3 files changed, 35 insertions(+), 101 deletions(-) diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index ccd3cc2c15..e2719582d6 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -116,7 +116,7 @@ class Keg next unless required.any? required_kegs = required.map { |f| kegs_by_name[f].sort_by(&:version).last } - return required_kegs, [dependent] + return required_kegs, [dependent.to_s] end nil diff --git a/Library/Homebrew/test/test_keg.rb b/Library/Homebrew/test/test_keg.rb index ac5831b092..7450d9c0f1 100644 --- a/Library/Homebrew/test/test_keg.rb +++ b/Library/Homebrew/test/test_keg.rb @@ -332,31 +332,54 @@ class InstalledDependantsTests < LinkTests @dependent = setup_test_keg("bar", "1.0") end - def dependencies(deps) - tab = Tab.for_keg(@dependent) - tab.tabfile = @dependent.join("INSTALL_RECEIPT.json") - tab.runtime_dependencies = deps + def alter_tab(keg = @dependent) + tab = Tab.for_keg(keg) + yield tab tab.write end - def test_no_dependencies + def dependencies(deps) + alter_tab do |tab| + tab.tabfile = @dependent.join("INSTALL_RECEIPT.json") + tab.runtime_dependencies = deps + end + end + + def test_no_dependencies_anywhere + dependencies nil + assert_empty @keg.installed_dependents + assert_nil Keg.find_some_installed_dependents([@keg]) + end + + def test_missing_formula_dependency + dependencies nil + Formula["bar"].class.depends_on "foo" + assert_empty @keg.installed_dependents + assert_equal [[@keg], ["bar"]], Keg.find_some_installed_dependents([@keg]) + end + + def test_empty_dependencies_in_tab dependencies [] assert_empty @keg.installed_dependents + assert_nil Keg.find_some_installed_dependents([@keg]) end - def test_same_name_different_version + def test_same_name_different_version_in_tab dependencies [{ "full_name" => "foo", "version" => "1.1" }] assert_empty @keg.installed_dependents + assert_nil Keg.find_some_installed_dependents([@keg]) end - def test_different_name_same_version + def test_different_name_same_version_in_tab stub_formula_name("baz") dependencies [{ "full_name" => "baz", "version" => @keg.version.to_s }] assert_empty @keg.installed_dependents + assert_nil Keg.find_some_installed_dependents([@keg]) end - def test_same_name_and_version + def test_same_name_and_version_in_tab dependencies [{ "full_name" => "foo", "version" => "1.0" }] assert_equal [@dependent], @keg.installed_dependents + assert_equal [[@keg], ["bar 1.0"]], Keg.find_some_installed_dependents([@keg]) end end diff --git a/Library/Homebrew/test/test_uninstall.rb b/Library/Homebrew/test/test_uninstall.rb index 9999b2bb2e..037596bfc5 100644 --- a/Library/Homebrew/test/test_uninstall.rb +++ b/Library/Homebrew/test/test_uninstall.rb @@ -48,110 +48,21 @@ class IntegrationCommandTestUninstall < IntegrationCommandTestCase end def test_uninstall - cmd("install", testball) - assert_match "Uninstalling testball", cmd("uninstall", "--force", testball) - assert_empty Formulary.factory(testball).installed_kegs - end - - def test_uninstall_with_unrelated_missing_deps_in_tab - setup_test_formula "testball" - run_as_not_developer do - cmd("install", testball) - cmd("install", "testball_f2") - cmd("uninstall", "--ignore-dependencies", "testball_f1") - cmd("uninstall", testball) - end - end - - def test_uninstall_with_unrelated_missing_deps_not_in_tab - setup_test_formula "testball" - run_as_not_developer do - cmd("install", testball) - cmd("install", "testball_f2") - - f2_keg = f2.installed_kegs.first - f2_tab = Tab.for_keg(f2_keg) - f2_tab.runtime_dependencies = nil - f2_tab.write - - cmd("uninstall", "--ignore-dependencies", "testball_f1") - cmd("uninstall", testball) - end - end - - def test_uninstall_leaving_dependents cmd("install", "testball_f2") run_as_not_developer do + assert_match "Refusing to uninstall", cmd_fail("uninstall", "testball_f1") refute_empty f1.installed_kegs + assert_match "Uninstalling #{f2.rack}", cmd("uninstall", "testball_f2") assert_empty f2.installed_kegs - end - end - def test_uninstall_leaving_dependents_no_runtime_dependencies_in_tab - cmd("install", "testball_f2") - - f2_keg = f2.installed_kegs.first - f2_tab = Tab.for_keg(f2_keg) - f2_tab.runtime_dependencies = nil - f2_tab.write - - run_as_not_developer do - assert_match "Refusing to uninstall", - cmd_fail("uninstall", "testball_f1") - refute_empty f1.installed_kegs - assert_match "Uninstalling #{f2.rack}", - cmd("uninstall", "testball_f2") - assert_empty f2.installed_kegs - end - end - - def test_uninstall_force_leaving_dependents - cmd("install", "testball_f2") - run_as_not_developer do - assert_match "Refusing to uninstall", - cmd_fail("uninstall", "testball_f1", "--force") - refute_empty f1.installed_kegs - assert_match "Uninstalling testball_f2", - cmd("uninstall", "testball_f2", "--force") - assert_empty f2.installed_kegs - end - end - - def test_uninstall_ignore_dependencies_leaving_dependents - cmd("install", "testball_f2") - run_as_not_developer do assert_match "Uninstalling #{f1.rack}", - cmd("uninstall", "testball_f1", "--ignore-dependencies") + cmd("uninstall", "testball_f1") assert_empty f1.installed_kegs - end - end - def test_uninstall_leaving_dependents_developer - cmd("install", "testball_f2") - assert_match "Uninstalling #{f1.rack}", - cmd("uninstall", "testball_f1") - assert_empty f1.installed_kegs - end - - def test_uninstall_dependent_first - cmd("install", "testball_f2") - run_as_not_developer do - assert_match "Uninstalling #{f1.rack}", - cmd("uninstall", "testball_f2", "testball_f1") - assert_empty f1.installed_kegs - end - end - - def test_uninstall_dependent_last - cmd("install", "testball_f2") - run_as_not_developer do - assert_match "Uninstalling #{f2.rack}", - cmd("uninstall", "testball_f1", "testball_f2") - assert_empty f2.installed_kegs end end end From 481a0976436f657ecabee8d04502423516db9864 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 25 Oct 2016 23:59:55 +0100 Subject: [PATCH 28/32] uninstall: document --ignore-dependencies --- Library/Homebrew/cmd/uninstall.rb | 5 ++++- docs/brew.1.html | 14 ++++++++++---- manpages/brew.1 | 10 ++++++++-- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index 504ee99835..d4a64c5055 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -1,8 +1,11 @@ -#: * `uninstall`, `rm`, `remove` [`--force`] : +#: * `uninstall`, `rm`, `remove` [`--force`] [`--ignore-dependencies`] : #: Uninstall . #: #: If `--force` is passed, and there are multiple versions of #: installed, delete all installed versions. +#: +#: If `--ignore-dependencies` is passed, uninstalling won't fail, even if +#: formulae depending on would still be installed. require "keg" require "formula" diff --git a/docs/brew.1.html b/docs/brew.1.html index bb8775cebe..0f5bf034a2 100644 --- a/docs/brew.1.html +++ b/docs/brew.1.html @@ -245,8 +245,11 @@ packages.

If --force is passed, then treat installed formulae and passed formulae like if they are from same taps and migrate them anyway.

-
missing [formulae]

Check the given formulae for missing dependencies. If no formulae are -given, check all installed brews.

+
missing [--hide=hidden] [formulae]

Check the given formulae for missing dependencies. If no formulae are +given, check all installed brews.

+ +

If --hide=hidden is passed, act as if none of hidden are installed. +hidden should be a comma-seperated list of formulae.

options [--compact] (--all|--installed|formulae)

Display install options specific to formulae.

If --compact is passed, show all options on a single line separated by @@ -348,10 +351,13 @@ for version is v1.

tap-pin tap

Pin tap, prioritizing its formulae over core when formula names are supplied by the user. See also tap-unpin.

tap-unpin tap

Unpin tap so its formulae are no longer prioritized. See also tap-pin.

-
uninstall, rm, remove [--force] formula

Uninstall formula.

+
uninstall, rm, remove [--force] [--ignore-dependencies] formula

Uninstall formula.

If --force is passed, and there are multiple versions of formula -installed, delete all installed versions.

+installed, delete all installed versions.

+ +

If --ignore-dependencies is passed, uninstalling won't fail, even if +formulae depending on formula would still be installed.

unlink [--dry-run] formula

Remove symlinks for formula from the Homebrew prefix. This can be useful for temporarily disabling a formula: brew unlink foo && commands && brew link foo.

diff --git a/manpages/brew.1 b/manpages/brew.1 index c74969aee7..0b547aac22 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -329,9 +329,12 @@ Migrate renamed packages to new name, where \fIformulae\fR are old names of pack If \fB\-\-force\fR is passed, then treat installed \fIformulae\fR and passed \fIformulae\fR like if they are from same taps and migrate them anyway\. . .TP -\fBmissing\fR [\fIformulae\fR] +\fBmissing\fR [\fB\-\-hide=\fR\fIhidden\fR] [\fIformulae\fR] Check the given \fIformulae\fR for missing dependencies\. If no \fIformulae\fR are given, check all installed brews\. . +.IP +If \fB\-\-hide=\fR\fIhidden\fR is passed, act as if none of \fIhidden\fR are installed\. \fIhidden\fR should be a comma\-seperated list of formulae\. +. .TP \fBoptions\fR [\fB\-\-compact\fR] (\fB\-\-all\fR|\fB\-\-installed\fR|\fIformulae\fR) Display install options specific to \fIformulae\fR\. @@ -484,12 +487,15 @@ Pin \fItap\fR, prioritizing its formulae over core when formula names are suppli Unpin \fItap\fR so its formulae are no longer prioritized\. See also \fBtap\-pin\fR\. . .TP -\fBuninstall\fR, \fBrm\fR, \fBremove\fR [\fB\-\-force\fR] \fIformula\fR +\fBuninstall\fR, \fBrm\fR, \fBremove\fR [\fB\-\-force\fR] [\fB\-\-ignore\-dependencies\fR] \fIformula\fR Uninstall \fIformula\fR\. . .IP If \fB\-\-force\fR is passed, and there are multiple versions of \fIformula\fR installed, delete all installed versions\. . +.IP +If \fB\-\-ignore\-dependencies\fR is passed, uninstalling won\'t fail, even if formulae depending on \fIformula\fR would still be installed\. +. .TP \fBunlink\fR [\fB\-\-dry\-run\fR] \fIformula\fR Remove symlinks for \fIformula\fR from the Homebrew prefix\. This can be useful for temporarily disabling a formula: \fBbrew unlink foo && commands && brew link foo\fR\. From b71ce88fa1a1424cccbc43a5b602ecbb9875fe77 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Wed, 26 Oct 2016 00:04:12 +0100 Subject: [PATCH 29/32] test_uninstall: style fixes --- Library/Homebrew/test/test_uninstall.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/Library/Homebrew/test/test_uninstall.rb b/Library/Homebrew/test/test_uninstall.rb index 037596bfc5..f56f206d07 100644 --- a/Library/Homebrew/test/test_uninstall.rb +++ b/Library/Homebrew/test/test_uninstall.rb @@ -50,7 +50,6 @@ class IntegrationCommandTestUninstall < IntegrationCommandTestCase def test_uninstall cmd("install", "testball_f2") run_as_not_developer do - assert_match "Refusing to uninstall", cmd_fail("uninstall", "testball_f1") refute_empty f1.installed_kegs @@ -62,7 +61,6 @@ class IntegrationCommandTestUninstall < IntegrationCommandTestCase assert_match "Uninstalling #{f1.rack}", cmd("uninstall", "testball_f1") assert_empty f1.installed_kegs - end end end From ec83d1decb7cf7166ccf0021f998503975a845f3 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Wed, 26 Oct 2016 15:07:06 +0100 Subject: [PATCH 30/32] uninstall: remove more integration tests --- Library/Homebrew/test/test_uninstall.rb | 40 ++----------------------- 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/Library/Homebrew/test/test_uninstall.rb b/Library/Homebrew/test/test_uninstall.rb index f56f206d07..a7859b7ad9 100644 --- a/Library/Homebrew/test/test_uninstall.rb +++ b/Library/Homebrew/test/test_uninstall.rb @@ -23,44 +23,8 @@ class UninstallTests < Homebrew::TestCase end class IntegrationCommandTestUninstall < IntegrationCommandTestCase - def setup - super - @f1_path = setup_test_formula "testball_f1", <<-CONTENT - def install - FileUtils.touch prefix/touch("hello") - end - CONTENT - @f2_path = setup_test_formula "testball_f2", <<-CONTENT - depends_on "testball_f1" - - def install - FileUtils.touch prefix/touch("hello") - end - CONTENT - end - - def f1 - Formulary.factory(@f1_path) - end - - def f2 - Formulary.factory(@f2_path) - end - def test_uninstall - cmd("install", "testball_f2") - run_as_not_developer do - assert_match "Refusing to uninstall", - cmd_fail("uninstall", "testball_f1") - refute_empty f1.installed_kegs - - assert_match "Uninstalling #{f2.rack}", - cmd("uninstall", "testball_f2") - assert_empty f2.installed_kegs - - assert_match "Uninstalling #{f1.rack}", - cmd("uninstall", "testball_f1") - assert_empty f1.installed_kegs - end + cmd("install", testball) + assert_match "Uninstalling testball", cmd("uninstall", "--force", testball) end end From 702345ee67db78d8f2a97f68f4b4bd2803ad8538 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Sun, 30 Oct 2016 16:30:40 +0000 Subject: [PATCH 31/32] docs: fix typo for brew missing --- Library/Homebrew/cmd/missing.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Library/Homebrew/cmd/missing.rb b/Library/Homebrew/cmd/missing.rb index 891846cb8d..8a1dc506df 100644 --- a/Library/Homebrew/cmd/missing.rb +++ b/Library/Homebrew/cmd/missing.rb @@ -3,7 +3,7 @@ #: given, check all installed brews. #: #: If `--hide=` is passed, act as if none of are installed. -#: should be a comma-seperated list of formulae. +#: should be a comma-separated list of formulae. require "formula" require "tab" From f3526381c329cb2f274d74d8ff0b149916d29608 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Sun, 30 Oct 2016 19:20:36 +0000 Subject: [PATCH 32/32] Update man --- docs/brew.1.html | 2 +- manpages/brew.1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/brew.1.html b/docs/brew.1.html index 0f5bf034a2..d0b0f418cf 100644 --- a/docs/brew.1.html +++ b/docs/brew.1.html @@ -249,7 +249,7 @@ like if they are from same taps and migrate them anyway.

given, check all installed brews.

If --hide=hidden is passed, act as if none of hidden are installed. -hidden should be a comma-seperated list of formulae.

+hidden should be a comma-separated list of formulae.

options [--compact] (--all|--installed|formulae)

Display install options specific to formulae.

If --compact is passed, show all options on a single line separated by diff --git a/manpages/brew.1 b/manpages/brew.1 index 0b547aac22..517948f279 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -333,7 +333,7 @@ If \fB\-\-force\fR is passed, then treat installed \fIformulae\fR and passed \fI Check the given \fIformulae\fR for missing dependencies\. If no \fIformulae\fR are given, check all installed brews\. . .IP -If \fB\-\-hide=\fR\fIhidden\fR is passed, act as if none of \fIhidden\fR are installed\. \fIhidden\fR should be a comma\-seperated list of formulae\. +If \fB\-\-hide=\fR\fIhidden\fR is passed, act as if none of \fIhidden\fR are installed\. \fIhidden\fR should be a comma\-separated list of formulae\. . .TP \fBoptions\fR [\fB\-\-compact\fR] (\fB\-\-all\fR|\fB\-\-installed\fR|\fIformulae\fR)