diff --git a/Library/Homebrew/autoremove.rb b/Library/Homebrew/autoremove.rb new file mode 100644 index 0000000000..cebf54a1ab --- /dev/null +++ b/Library/Homebrew/autoremove.rb @@ -0,0 +1,64 @@ +# typed: false +# frozen_string_literal: true + +require "cask/caskroom" +require "formula" +require "uninstall" + +module Homebrew + # Helpers for removing unused formulae. + # + # @api private + module Autoremove + module_function + + def remove_unused_formulae(dry_run: false) + removable_formulae = unused_formulae_with_no_dependents + + return if removable_formulae.blank? + + formulae_names = removable_formulae.map(&:full_name).sort + + verb = dry_run ? "Would autoremove" : "Autoremoving" + oh1 "#{verb} #{formulae_names.count} unneeded #{"formula".pluralize(formulae_names.count)}:" + puts formulae_names.join("\n") + return if dry_run + + kegs_by_rack = removable_formulae.map(&:any_installed_keg).group_by(&:rack) + Uninstall.uninstall_kegs(kegs_by_rack) + end + + # An array of installed {Formula} without {Formula} or {Cask} + # dependents that weren't installed on request. + # @private + def unused_formulae_with_no_dependents + unused_formulae = unused_formulae_with_no_formula_dependents(Formula.installed) + unused_formulae - installed_formulae_with_cask_dependents + end + + # Recursive function that returns an array of installed {Formula} without + # {Formula} dependents that weren't installed on request. + # @private + def unused_formulae_with_no_formula_dependents(formulae) + unused_formulae = Formula.installed_formulae_with_no_dependents(formulae).reject do |f| + Tab.for_keg(f.any_installed_keg).installed_on_request + end + + if unused_formulae.present? + unused_formulae += unused_formulae_with_no_formula_dependents(formulae - unused_formulae) + end + + unused_formulae + end + + # An array of all installed {Formula} with {Cask} dependents. + # @private + def installed_formulae_with_cask_dependents + Cask::Caskroom.casks + .flat_map { |cask| cask.depends_on[:formula] } + .compact + .map { |f| Formula[f] } + .flat_map { |f| [f, *f.runtime_formula_dependencies].compact } + end + end +end diff --git a/Library/Homebrew/cmd/autoremove.rb b/Library/Homebrew/cmd/autoremove.rb index c1a9b65cec..9cd02cb40a 100644 --- a/Library/Homebrew/cmd/autoremove.rb +++ b/Library/Homebrew/cmd/autoremove.rb @@ -1,9 +1,8 @@ # typed: true # frozen_string_literal: true -require "formula" +require "autoremove" require "cli/parser" -require "uninstall" module Homebrew module_function @@ -23,6 +22,6 @@ module Homebrew def autoremove args = autoremove_args.parse - Uninstall.autoremove_kegs(Formula.removable_formulae, dry_run: args.dry_run?) + Autoremove.remove_unused_formulae(dry_run: args.dry_run?) end end diff --git a/Library/Homebrew/cmd/install.rb b/Library/Homebrew/cmd/install.rb index cff794979e..8c555b92ee 100644 --- a/Library/Homebrew/cmd/install.rb +++ b/Library/Homebrew/cmd/install.rb @@ -1,6 +1,7 @@ # typed: false # frozen_string_literal: true +require "autoremove" require "cask/config" require "cask/cmd" require "cask/cmd/install" @@ -254,6 +255,8 @@ module Homebrew ) Homebrew.messages.display_messages(display_times: args.display_times?) + + Autoremove.remove_unused_formulae if Homebrew::EnvConfig.autoremove? rescue FormulaUnreadableError, FormulaClassUnavailableError, TapFormulaUnreadableError, TapFormulaClassUnavailableError => e # Need to rescue before `FormulaUnavailableError` (superclass of this) diff --git a/Library/Homebrew/cmd/reinstall.rb b/Library/Homebrew/cmd/reinstall.rb index bc6bca5053..e24f30ce74 100644 --- a/Library/Homebrew/cmd/reinstall.rb +++ b/Library/Homebrew/cmd/reinstall.rb @@ -1,6 +1,7 @@ # typed: false # frozen_string_literal: true +require "autoremove" require "formula_installer" require "development_tools" require "messages" @@ -152,5 +153,7 @@ module Homebrew end Homebrew.messages.display_messages(display_times: args.display_times?) + + Autoremove.remove_unused_formulae if Homebrew::EnvConfig.autoremove? end end diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index fffa54a69f..0ba3018709 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -79,8 +79,6 @@ module Homebrew ) end - return unless Homebrew::EnvConfig.uninstall_autoremove? - - Uninstall.autoremove_kegs(Formula.removable_formulae) + Autoremove.remove_unused_formulae if Homebrew::EnvConfig.autoremove? end end diff --git a/Library/Homebrew/cmd/upgrade.rb b/Library/Homebrew/cmd/upgrade.rb index ec7520a7c3..62b5e7cbc1 100644 --- a/Library/Homebrew/cmd/upgrade.rb +++ b/Library/Homebrew/cmd/upgrade.rb @@ -1,6 +1,7 @@ # typed: false # frozen_string_literal: true +require "autoremove" require "cli/parser" require "formula_installer" require "install" @@ -111,6 +112,8 @@ module Homebrew upgrade_outdated_casks(casks, args: args) unless only_upgrade_formulae Homebrew.messages.display_messages(display_times: args.display_times?) + + Autoremove.remove_unused_formulae(dry_run: args.dry_run?) if Homebrew::EnvConfig.autoremove? end sig { params(formulae: T::Array[Formula], args: CLI::Args).returns(T::Boolean) } diff --git a/Library/Homebrew/env_config.rb b/Library/Homebrew/env_config.rb index b2b27fda69..f56c3a0ddb 100644 --- a/Library/Homebrew/env_config.rb +++ b/Library/Homebrew/env_config.rb @@ -36,7 +36,7 @@ module Homebrew "disable auto-update entirely with HOMEBREW_NO_AUTO_UPDATE.", default: 300, }, - HOMEBREW_AUTOREMOVE: { + HOMEBREW_AUTOREMOVE: { description: "If set, calls to `brew install`, `brew upgrade`, `brew reinstall` and `brew uninstall` " \ "will automatically remove unused formula dependents.", boolean: true, diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index d367e157dd..fc496d4e8d 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -1720,37 +1720,6 @@ class Formula installed.select { |f| f.installed_alias_path == alias_path } end - # An array of all installed {Formula} with {Cask} dependents. - # @private - def self.installed_formulae_with_cask_dependents - Cask::Caskroom.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} or {Cask} dependents - # that weren't installed on request. - # @private - def self.removable_formulae - formulae = installed - all_removable_formulae = T.let([], T::Array[Formula]) - - loop do - removable_formulae = installed_formulae_with_no_dependents(formulae).reject do |f| - Tab.for_keg(f.any_installed_keg).installed_on_request - end - - break if removable_formulae.blank? - - all_removable_formulae += removable_formulae - formulae -= removable_formulae - end - - all_removable_formulae - installed_formulae_with_cask_dependents - end - # an array of all alias files of core {Formula} # @private def self.core_alias_files diff --git a/Library/Homebrew/uninstall.rb b/Library/Homebrew/uninstall.rb index ee00fb849c..1d9a1585e4 100644 --- a/Library/Homebrew/uninstall.rb +++ b/Library/Homebrew/uninstall.rb @@ -97,20 +97,6 @@ module Homebrew end end - def autoremove_kegs(removable_formulae, dry_run: false) - return if removable_formulae.blank? - - formulae_names = removable_formulae.map(&:full_name).sort - - verb = dry_run ? "Would uninstall" : "Uninstalling" - oh1 "#{verb} #{formulae_names.count} unneeded #{"formula".pluralize(formulae_names.count)}:" - puts formulae_names.join("\n") - return if dry_run - - kegs_by_rack = removable_formulae.map(&:any_installed_keg).group_by(&:rack) - Uninstall.uninstall_kegs(kegs_by_rack) - end - def handle_unsatisfied_dependents(kegs_by_rack, casks: [], ignore_dependencies: false, named_args: []) return if ignore_dependencies