Merge pull request #13532 from apainintheneck/add-uninstall-autoremove

cmd/uninstall: Add env variable that runs autoremove after uninstalls
This commit is contained in:
Kevin 2022-08-03 18:10:32 -07:00 committed by GitHub
commit 93bf9e5ba2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 241 additions and 57 deletions

View File

@ -160,7 +160,7 @@ module Homebrew
cleanup = Cleanup.new(dry_run: dry_run) cleanup = Cleanup.new(dry_run: dry_run)
if cleanup.periodic_clean_due? if cleanup.periodic_clean_due?
cleanup.periodic_clean! 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}`..." ohai "Running `brew cleanup #{f}`..."
puts_no_install_cleanup_disable_message_if_not_already! puts_no_install_cleanup_disable_message_if_not_already!
cleanup.cleanup_formula(f) cleanup.cleanup_formula(f)
@ -177,7 +177,7 @@ module Homebrew
@puts_no_install_cleanup_disable_message_if_not_already = true @puts_no_install_cleanup_disable_message_if_not_already = true
end end
def skip_clean_formula?(f) def self.skip_clean_formula?(f)
return false if Homebrew::EnvConfig.no_cleanup_formulae.blank? return false if Homebrew::EnvConfig.no_cleanup_formulae.blank?
skip_clean_formulae = Homebrew::EnvConfig.no_cleanup_formulae.split(",") skip_clean_formulae = Homebrew::EnvConfig.no_cleanup_formulae.split(",")
@ -215,10 +215,13 @@ module Homebrew
if args.empty? if args.empty?
Formula.installed Formula.installed
.sort_by(&:name) .sort_by(&:name)
.reject { |f| skip_clean_formula?(f) } .reject { |f| Cleanup.skip_clean_formula?(f) }
.each do |formula| .each do |formula|
cleanup_formula(formula, quiet: quiet, ds_store: false, cache_db: false) cleanup_formula(formula, quiet: quiet, ds_store: false, cache_db: false)
end end
Cleanup.autoremove(dry_run: dry_run?) if Homebrew::EnvConfig.autoremove?
cleanup_cache cleanup_cache
cleanup_logs cleanup_logs
cleanup_lockfiles cleanup_lockfiles
@ -253,7 +256,7 @@ module Homebrew
nil nil
end end
if formula && skip_clean_formula?(formula) if formula && Cleanup.skip_clean_formula?(formula)
onoe "Refusing to clean #{formula} because it is listed in " \ onoe "Refusing to clean #{formula} because it is listed in " \
"#{Tty.bold}HOMEBREW_NO_CLEANUP_FORMULAE#{Tty.reset}!" "#{Tty.bold}HOMEBREW_NO_CLEANUP_FORMULAE#{Tty.reset}!"
elsif formula elsif formula
@ -519,5 +522,36 @@ module Homebrew
print "and #{d} directories " if d.positive? print "and #{d} directories " if d.positive?
puts "from #{HOMEBREW_PREFIX}" puts "from #{HOMEBREW_PREFIX}"
end end
def self.autoremove(dry_run: false)
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?
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
require "uninstall"
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
end end

View File

@ -1,9 +1,8 @@
# typed: true # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "formula" require "cleanup"
require "cli/parser" require "cli/parser"
require "uninstall"
module Homebrew module Homebrew
module_function module_function
@ -20,37 +19,9 @@ module Homebrew
end end
end end
def get_removable_formulae(formulae)
removable_formulae = Formula.installed_formulae_with_no_dependents(formulae).reject do |f|
Tab.for_keg(f.any_installed_keg).installed_on_request
end
removable_formulae += get_removable_formulae(formulae - removable_formulae) if removable_formulae.present?
removable_formulae
end
def autoremove def autoremove
args = autoremove_args.parse args = autoremove_args.parse
removable_formulae = get_removable_formulae(Formula.installed) Cleanup.autoremove(dry_run: args.dry_run?)
if (casks = Cask::Caskroom.casks.presence)
removable_formulae -= casks.flat_map { |cask| cask.depends_on[:formula] }
.compact
.map { |f| Formula[f] }
.flat_map { |f| [f, *f.runtime_formula_dependencies].compact }
end
return if removable_formulae.blank?
formulae_names = removable_formulae.map(&:full_name).sort
verb = args.dry_run? ? "Would uninstall" : "Uninstalling"
oh1 "#{verb} #{formulae_names.count} unneeded #{"formula".pluralize(formulae_names.count)}:"
puts formulae_names.join("\n")
return if args.dry_run?
kegs_by_rack = removable_formulae.map(&:any_installed_keg).group_by(&:rack)
Uninstall.uninstall_kegs(kegs_by_rack)
end end
end end

View File

@ -37,7 +37,7 @@ module Homebrew
def leaves def leaves
args = leaves_args.parse args = leaves_args.parse
leaves_list = Formula.installed_formulae_with_no_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_on_request?)) if args.installed_on_request?
leaves_list.select!(&method(:installed_as_dependency?)) if args.installed_as_dependency? leaves_list.select!(&method(:installed_as_dependency?)) if args.installed_as_dependency?

View File

@ -50,6 +50,11 @@ module Homebrew
all_kegs: args.force?, all_kegs: args.force?,
) )
# If ignore_unavailable is true and the named args
# are a series of invalid kegs and casks,
# #to_kegs_to_casks will return empty arrays.
return if all_kegs.blank? && casks.blank?
kegs_by_rack = all_kegs.group_by(&:rack) kegs_by_rack = all_kegs.group_by(&:rack)
Uninstall.uninstall_kegs( Uninstall.uninstall_kegs(
@ -73,5 +78,7 @@ module Homebrew
force: args.force?, force: args.force?,
) )
end end
Cleanup.autoremove if Homebrew::EnvConfig.autoremove?
end end
end end

View File

@ -36,6 +36,12 @@ module Homebrew
"disable auto-update entirely with HOMEBREW_NO_AUTO_UPDATE.", "disable auto-update entirely with HOMEBREW_NO_AUTO_UPDATE.",
default: 300, default: 300,
}, },
HOMEBREW_AUTOREMOVE: {
description: "If set, calls to `brew cleanup` and `brew uninstall` will automatically " \
"remove unused formula dependents and if HOMEBREW_NO_INSTALL_CLEANUP is not set, " \
"`brew cleanup` will start running `brew autoremove` periodically.",
boolean: true,
},
HOMEBREW_BAT: { HOMEBREW_BAT: {
description: "If set, use `bat` for the `brew cat` command.", description: "If set, use `bat` for the `brew cat` command.",
boolean: true, boolean: true,
@ -263,8 +269,8 @@ module Homebrew
boolean: true, boolean: true,
}, },
HOMEBREW_NO_CLEANUP_FORMULAE: { HOMEBREW_NO_CLEANUP_FORMULAE: {
description: "A comma-separated list of formulae. Homebrew will refuse to clean up a " \ description: "A comma-separated list of formulae. Homebrew will refuse to clean up " \
"formula if it appears on this list.", "or autoremove a formula if it appears on this list.",
}, },
HOMEBREW_NO_COLOR: { HOMEBREW_NO_COLOR: {
description: "If set, do not print text with colour added.", description: "If set, do not print text with colour added.",

View File

@ -1706,14 +1706,46 @@ class Formula
end.uniq(&:name) end.uniq(&:name)
end end
# An array of all installed {Formula} without dependents # An array of all installed {Formula} with {Cask} dependents.
# @private # @private
def self.installed_formulae_with_no_dependents(formulae = installed) 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.formulae_with_no_formula_dependents(formulae)
return [] if formulae.blank? return [] if formulae.blank?
formulae - formulae.flat_map(&:runtime_formula_dependencies) formulae - formulae.flat_map(&:runtime_formula_dependencies)
end end
# 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 = formulae_with_no_formula_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 {Formula} without {Formula} or {Cask}
# dependents that weren't installed on request.
# @private
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) def self.installed_with_alias_path(alias_path)
return [] if alias_path.nil? return [] if alias_path.nil?

View File

@ -2442,6 +2442,8 @@ module Homebrew::EnvConfig
def self.artifact_domain(); end def self.artifact_domain(); end
def self.autoremove?(); end
def self.auto_update_secs(); end def self.auto_update_secs(); end
def self.bat?(); end def self.bat?(); end

View File

@ -5,4 +5,34 @@ require "cmd/shared_examples/args_parse"
describe "brew autoremove" do describe "brew autoremove" do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
describe "integration test" do
let(:requested_formula) { Formula["testball1"] }
let(:unused_formula) { Formula["testball2"] }
before do
install_test_formula "testball1"
install_test_formula "testball2"
# Make testball2 an unused dependency
tab = Tab.for_name("testball2")
tab.installed_on_request = false
tab.installed_as_dependency = true
tab.write
end
it "only removes unused dependencies", :integration_test do
expect(requested_formula.any_version_installed?).to be true
expect(unused_formula.any_version_installed?).to be true
# When there are unused dependencies
expect { brew "autoremove" }
.to be_a_success
.and output(/Autoremoving/).to_stdout
.and not_to_output.to_stderr
expect(requested_formula.any_version_installed?).to be true
expect(unused_formula.any_version_installed?).to be false
end
end
end end

View File

@ -446,40 +446,133 @@ describe Formula do
end end
end end
describe "::installed_formulae_with_no_dependents" do shared_context "with formulae for dependency testing" do
let(:formula_is_dep) do let(:formula_with_deps) do
formula "foo" do formula "zero" do
url "foo-1.1" url "zero-1.0"
end end
end end
let(:formula_with_deps) do let(:formula_is_dep1) do
formula "bar" do formula "one" do
url "bar-1.0" url "one-1.1"
end
end
let(:formula_is_dep2) do
formula "two" do
url "two-1.1"
end end
end end
let(:formulae) do let(:formulae) do
[ [
formula_with_deps, formula_with_deps,
formula_is_dep, formula_is_dep1,
formula_is_dep2,
] ]
end end
before do 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 end
specify "without formulae parameter" do describe "::formulae_with_no_formula_dependents" do
allow(described_class).to receive(:installed).and_return(formulae) include_context "with formulae for dependency testing"
expect(described_class.installed_formulae_with_no_dependents) it "filters out dependencies" do
expect(described_class.formulae_with_no_formula_dependents(formulae))
.to eq([formula_with_deps]) .to eq([formula_with_deps])
end end
end
specify "with formulae parameter" do describe "::unused_formulae_with_no_formula_dependents" do
expect(described_class.installed_formulae_with_no_dependents(formulae)) include_context "with formulae for dependency testing"
.to eq([formula_with_deps])
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
end end

View File

@ -1960,6 +1960,9 @@ example, run `export HOMEBREW_NO_INSECURE_REDIRECT=1` rather than just
*Default:* `300`. *Default:* `300`.
- `HOMEBREW_AUTOREMOVE`
<br>If set, calls to `brew cleanup` and `brew uninstall` will automatically remove unused formula dependents and if HOMEBREW_NO_INSTALL_CLEANUP is not set, `brew cleanup` will start running `brew autoremove` periodically.
- `HOMEBREW_BAT` - `HOMEBREW_BAT`
<br>If set, use `bat` for the `brew cat` command. <br>If set, use `bat` for the `brew cat` command.
@ -2140,7 +2143,7 @@ example, run `export HOMEBREW_NO_INSECURE_REDIRECT=1` rather than just
<br>If set, do not check for broken linkage of dependents or outdated dependents after installing, upgrading or reinstalling formulae. This will result in fewer dependents (and their dependencies) being upgraded or reinstalled but may result in more breakage from running `brew install *`formula`*` or `brew upgrade *`formula`*`. <br>If set, do not check for broken linkage of dependents or outdated dependents after installing, upgrading or reinstalling formulae. This will result in fewer dependents (and their dependencies) being upgraded or reinstalled but may result in more breakage from running `brew install *`formula`*` or `brew upgrade *`formula`*`.
- `HOMEBREW_NO_CLEANUP_FORMULAE` - `HOMEBREW_NO_CLEANUP_FORMULAE`
<br>A comma-separated list of formulae. Homebrew will refuse to clean up a formula if it appears on this list. <br>A comma-separated list of formulae. Homebrew will refuse to clean up or autoremove a formula if it appears on this list.
- `HOMEBREW_NO_COLOR` - `HOMEBREW_NO_COLOR`
<br>If set, do not print text with colour added. <br>If set, do not print text with colour added.

View File

@ -2779,6 +2779,12 @@ Run \fBbrew update\fR once every \fBHOMEBREW_AUTO_UPDATE_SECS\fR seconds before
\fIDefault:\fR \fB300\fR\. \fIDefault:\fR \fB300\fR\.
. .
.TP .TP
\fBHOMEBREW_AUTOREMOVE\fR
.
.br
If set, calls to \fBbrew cleanup\fR and \fBbrew uninstall\fR will automatically remove unused formula dependents and if HOMEBREW_NO_INSTALL_CLEANUP is not set, \fBbrew cleanup\fR will start running \fBbrew autoremove\fR periodically\.
.
.TP
\fBHOMEBREW_BAT\fR \fBHOMEBREW_BAT\fR
. .
.br .br
@ -3118,7 +3124,7 @@ If set, do not check for broken linkage of dependents or outdated dependents aft
\fBHOMEBREW_NO_CLEANUP_FORMULAE\fR \fBHOMEBREW_NO_CLEANUP_FORMULAE\fR
. .
.br .br
A comma\-separated list of formulae\. Homebrew will refuse to clean up a formula if it appears on this list\. A comma\-separated list of formulae\. Homebrew will refuse to clean up or autoremove a formula if it appears on this list\.
. .
.TP .TP
\fBHOMEBREW_NO_COLOR\fR \fBHOMEBREW_NO_COLOR\fR