diff --git a/Library/Homebrew/cleanup.rb b/Library/Homebrew/cleanup.rb index d31a51fca1..e381088a1d 100644 --- a/Library/Homebrew/cleanup.rb +++ b/Library/Homebrew/cleanup.rb @@ -160,7 +160,7 @@ module Homebrew cleanup = Cleanup.new(dry_run: dry_run) if cleanup.periodic_clean_due? cleanup.periodic_clean! - elsif f.latest_version_installed? && !cleanup.skip_clean_formula?(f) + elsif f.latest_version_installed? && !Cleanup.skip_clean_formula?(f) ohai "Running `brew cleanup #{f}`..." puts_no_install_cleanup_disable_message_if_not_already! cleanup.cleanup_formula(f) @@ -177,7 +177,7 @@ module Homebrew @puts_no_install_cleanup_disable_message_if_not_already = true end - def skip_clean_formula?(f) + def self.skip_clean_formula?(f) return false if Homebrew::EnvConfig.no_cleanup_formulae.blank? skip_clean_formulae = Homebrew::EnvConfig.no_cleanup_formulae.split(",") @@ -215,7 +215,7 @@ module Homebrew if args.empty? Formula.installed .sort_by(&:name) - .reject { |f| skip_clean_formula?(f) } + .reject { |f| Cleanup.skip_clean_formula?(f) } .each do |formula| cleanup_formula(formula, quiet: quiet, ds_store: false, cache_db: false) end @@ -256,7 +256,7 @@ module Homebrew nil end - if formula && skip_clean_formula?(formula) + if formula && Cleanup.skip_clean_formula?(formula) onoe "Refusing to clean #{formula} because it is listed in " \ "#{Tty.bold}HOMEBREW_NO_CLEANUP_FORMULAE#{Tty.reset}!" elsif formula @@ -264,8 +264,6 @@ module Homebrew end cleanup_cask(cask) if cask end - - Cleanup.autoremove(dry_run: dry_run?) if Homebrew::EnvConfig.autoremove? end end @@ -526,7 +524,17 @@ module Homebrew end def self.autoremove(dry_run: false) - removable_formulae = Formula.unused_formulae_with_no_dependents + require "cask/caskroom" + + # If this runs after install, uninstall, reinstall or upgrade, + # the cache of installed formulae may no longer be valid. + Formula.clear_cache unless dry_run + + # Remove formulae listed in HOMEBREW_NO_CLEANUP_FORMULAE. + formulae = Formula.installed.reject(&method(:skip_clean_formula?)) + casks = Cask::Caskroom.casks + + removable_formulae = Formula.unused_formulae_with_no_dependents(formulae, casks) return if removable_formulae.blank? @@ -541,6 +549,9 @@ module Homebrew kegs_by_rack = removable_formulae.map(&:any_installed_keg).group_by(&:rack) Uninstall.uninstall_kegs(kegs_by_rack) + + # The installed formula cache will be invalid after uninstalling. + Formula.clear_cache end end end diff --git a/Library/Homebrew/cmd/leaves.rb b/Library/Homebrew/cmd/leaves.rb index f14bed7dfc..ec134de4a1 100644 --- a/Library/Homebrew/cmd/leaves.rb +++ b/Library/Homebrew/cmd/leaves.rb @@ -37,7 +37,7 @@ module Homebrew def leaves args = leaves_args.parse - leaves_list = Formula.installed_formulae_with_no_formula_dependents + leaves_list = Formula.formulae_with_no_formula_dependents(Formula.installed) leaves_list.select!(&method(:installed_on_request?)) if args.installed_on_request? leaves_list.select!(&method(:installed_as_dependency?)) if args.installed_as_dependency? diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 5caa3876fd..4db86ebed3 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -1708,29 +1708,26 @@ class Formula # An array of all installed {Formula} with {Cask} dependents. # @private - def self.installed_formulae_with_cask_dependents - require "cask/caskroom" - - Cask::Caskroom.casks - .flat_map { |cask| cask.depends_on[:formula] } - .compact - .map { |f| Formula[f] } - .flat_map { |f| [f, *f.runtime_formula_dependencies].compact } + def self.formulae_with_cask_dependents(casks) + casks.flat_map { |cask| cask.depends_on[:formula] } + .compact + .map { |f| Formula[f] } + .flat_map { |f| [f, *f.runtime_formula_dependencies].compact } end # An array of all installed {Formula} without {Formula} dependents # @private - def self.installed_formulae_with_no_formula_dependents(formulae = installed) + def self.formulae_with_no_formula_dependents(formulae) return [] if formulae.blank? formulae - formulae.flat_map(&:runtime_formula_dependencies) end - # Recursive function that returns an array of installed {Formula} without + # Recursive function that returns an array of {Formula} without # {Formula} dependents that weren't installed on request. # @private def self.unused_formulae_with_no_formula_dependents(formulae) - unused_formulae = installed_formulae_with_no_formula_dependents(formulae).reject do |f| + unused_formulae = formulae_with_no_formula_dependents(formulae).reject do |f| Tab.for_keg(f.any_installed_keg).installed_on_request end @@ -1741,12 +1738,12 @@ class Formula unused_formulae end - # An array of installed {Formula} without {Formula} or {Cask} + # An array of {Formula} without {Formula} or {Cask} # dependents that weren't installed on request. # @private - def self.unused_formulae_with_no_dependents - unused_formulae = unused_formulae_with_no_formula_dependents(installed) - unused_formulae - installed_formulae_with_cask_dependents + def self.unused_formulae_with_no_dependents(formulae, casks) + unused_formulae = unused_formulae_with_no_formula_dependents(formulae) + unused_formulae - formulae_with_cask_dependents(casks) end def self.installed_with_alias_path(alias_path) diff --git a/Library/Homebrew/test/formula_spec.rb b/Library/Homebrew/test/formula_spec.rb index e8f3fa81d6..1ed7890557 100644 --- a/Library/Homebrew/test/formula_spec.rb +++ b/Library/Homebrew/test/formula_spec.rb @@ -446,40 +446,133 @@ describe Formula do end end - describe "::installed_formulae_with_no_formula_dependents" do - let(:formula_is_dep) do - formula "foo" do - url "foo-1.1" + shared_context "with formulae for dependency testing" do + let(:formula_with_deps) do + formula "zero" do + url "zero-1.0" end end - let(:formula_with_deps) do - formula "bar" do - url "bar-1.0" + let(:formula_is_dep1) do + formula "one" do + url "one-1.1" + end + end + + let(:formula_is_dep2) do + formula "two" do + url "two-1.1" end end let(:formulae) do [ formula_with_deps, - formula_is_dep, + formula_is_dep1, + formula_is_dep2, ] end before do - allow(formula_with_deps).to receive(:runtime_formula_dependencies).and_return([formula_is_dep]) + allow(formula_with_deps).to receive(:runtime_formula_dependencies).and_return([formula_is_dep1, + formula_is_dep2]) + allow(formula_is_dep1).to receive(:runtime_formula_dependencies).and_return([formula_is_dep2]) end + end - specify "without formulae parameter" do - allow(described_class).to receive(:installed).and_return(formulae) + describe "::formulae_with_no_formula_dependents" do + include_context "with formulae for dependency testing" - expect(described_class.installed_formulae_with_no_formula_dependents) + it "filters out dependencies" do + expect(described_class.formulae_with_no_formula_dependents(formulae)) .to eq([formula_with_deps]) end + end - specify "with formulae parameter" do - expect(described_class.installed_formulae_with_no_formula_dependents(formulae)) - .to eq([formula_with_deps]) + describe "::unused_formulae_with_no_formula_dependents" do + include_context "with formulae for dependency testing" + + let(:tab_from_keg) { double } + + before do + allow(Tab).to receive(:for_keg).and_return(tab_from_keg) + end + + specify "installed on request" do + allow(tab_from_keg).to receive(:installed_on_request).and_return(true) + expect(described_class.unused_formulae_with_no_formula_dependents(formulae)) + .to eq([]) + end + + specify "not installed on request" do + allow(tab_from_keg).to receive(:installed_on_request).and_return(false) + expect(described_class.unused_formulae_with_no_formula_dependents(formulae)) + .to eq(formulae) + end + end + + shared_context "with formulae and casks for dependency testing" do + include_context "with formulae for dependency testing" + + require "cask/cask_loader" + + let(:cask_one_dep) do + Cask::CaskLoader.load(+<<-RUBY) + cask "red" do + depends_on formula: "two" + end + RUBY + end + + let(:cask_multiple_deps) do + Cask::CaskLoader.load(+<<-RUBY) + cask "blue" do + depends_on formula: "zero" + end + RUBY + end + + let(:cask_no_deps1) do + Cask::CaskLoader.load(+<<-RUBY) + cask "green" do + end + RUBY + end + + let(:cask_no_deps2) do + Cask::CaskLoader.load(+<<-RUBY) + cask "purple" do + end + RUBY + end + + let(:casks_no_deps) { [cask_no_deps1, cask_no_deps2] } + let(:casks_one_dep) { [cask_no_deps1, cask_no_deps2, cask_one_dep] } + let(:casks_multiple_deps) { [cask_no_deps1, cask_no_deps2, cask_multiple_deps] } + + before do + allow(described_class).to receive("[]").with("zero").and_return(formula_with_deps) + allow(described_class).to receive("[]").with("one").and_return(formula_is_dep1) + allow(described_class).to receive("[]").with("two").and_return(formula_is_dep2) + end + end + + describe "::formulae_with_cask_dependents" do + include_context "with formulae and casks for dependency testing" + + specify "no dependents" do + expect(described_class.formulae_with_cask_dependents(casks_no_deps)) + .to eq([]) + end + + specify "one dependent" do + expect(described_class.formulae_with_cask_dependents(casks_one_dep)) + .to eq([formula_is_dep2]) + end + + specify "multiple dependents" do + expect(described_class.formulae_with_cask_dependents(casks_multiple_deps)) + .to eq(formulae) end end