From f95e1729a2d95d8dc80dadc8c05851d534a72f74 Mon Sep 17 00:00:00 2001 From: Tie Date: Sun, 1 Nov 2020 01:57:02 -0400 Subject: [PATCH 01/10] move logic from leaves to formula --- Library/Homebrew/cmd/leaves.rb | 5 +- Library/Homebrew/formula.rb | 12 ++++ Library/Homebrew/test/cmd/leaves_spec.rb | 44 +++++++++++--- Library/Homebrew/test/formula_spec.rb | 74 ++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 12 deletions(-) diff --git a/Library/Homebrew/cmd/leaves.rb b/Library/Homebrew/cmd/leaves.rb index d84468f5fd..dceb3c0e42 100644 --- a/Library/Homebrew/cmd/leaves.rb +++ b/Library/Homebrew/cmd/leaves.rb @@ -2,7 +2,6 @@ # frozen_string_literal: true require "formula" -require "tab" require "cli/parser" module Homebrew @@ -23,9 +22,7 @@ module Homebrew def leaves leaves_args.parse - installed = Formula.installed.sort - deps_of_installed = installed.flat_map(&:runtime_formula_dependencies) - leaves = installed.map(&:full_name) - deps_of_installed.map(&:full_name) + leaves = Formula.installed_non_deps.map(&:full_name).sort leaves.each(&method(:puts)) end end diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 9fd75a3197..808c73b3f4 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -1515,6 +1515,18 @@ class Formula end.uniq(&:name) end + # An array of installed {Formula} that are dependencies of other installed {Formula} + # @private + def self.installed_deps(formulae=installed) + formulae.flat_map(&:runtime_formula_dependencies).uniq(&:name) + end + + # An array of all installed {Formula} that are not dependencies of other installed {Formula} + # @private + def self.installed_non_deps(formulae=installed) + formulae - installed_deps(formulae) + end + def self.installed_with_alias_path(alias_path) return [] if alias_path.nil? diff --git a/Library/Homebrew/test/cmd/leaves_spec.rb b/Library/Homebrew/test/cmd/leaves_spec.rb index 4962a75603..16239d1cb3 100644 --- a/Library/Homebrew/test/cmd/leaves_spec.rb +++ b/Library/Homebrew/test/cmd/leaves_spec.rb @@ -8,14 +8,42 @@ describe "Homebrew.leaves_args" do end describe "brew leaves", :integration_test do - it "prints all Formulae that are not dependencies of other Formulae" do - setup_test_formula "foo" - setup_test_formula "bar" - (HOMEBREW_CELLAR/"foo/0.1/somedir").mkpath + context "when there are no installed Formulae" do + it "prints nothing" do + setup_test_formula "foo" + setup_test_formula "bar" + + expect { brew "leaves" } + .to not_to_output.to_stdout + .and not_to_output.to_stderr + .and be_a_success + end + end - expect { brew "leaves" } - .to output("foo\n").to_stdout - .and not_to_output.to_stderr - .and be_a_success + context "when there are only installed Formulae without dependencies" do + it "prints all installed Formulae" do + setup_test_formula "foo" + setup_test_formula "bar" + (HOMEBREW_CELLAR/"foo/0.1/somedir").mkpath + + expect { brew "leaves" } + .to output("foo\n").to_stdout + .and not_to_output.to_stderr + .and be_a_success + end + end + + context "when there are installed Formulae" do + it "prints all installed Formulae that are not dependencies of another installed Formula" do + setup_test_formula "foo" + setup_test_formula "bar" + (HOMEBREW_CELLAR/"foo/0.1/somedir").mkpath + (HOMEBREW_CELLAR/"bar/0.1/somedir").mkpath + + expect { brew "leaves" } + .to output("bar\n").to_stdout + .and not_to_output.to_stderr + .and be_a_success + end end end diff --git a/Library/Homebrew/test/formula_spec.rb b/Library/Homebrew/test/formula_spec.rb index 9d9997dbd2..ad803f05f9 100644 --- a/Library/Homebrew/test/formula_spec.rb +++ b/Library/Homebrew/test/formula_spec.rb @@ -440,6 +440,80 @@ describe Formula do end end + describe "::installed_deps" do + let(:formula_is_dep) do + formula "foo" do + url "foo-1.1" + end + end + + let(:formula_with_deps) do + formula "bar" do + url "bar-1.0" + end + end + + let(:formulae) do + [ + formula_with_deps, + formula_is_dep + ] + end + + before do + allow(formula_with_deps).to receive(:runtime_formula_dependencies).and_return([ formula_is_dep ]) + end + + specify "without formulae parameter" do + allow(described_class).to receive(:installed).and_return(formulae) + + expect(described_class.installed_deps) + .to eq([formula_is_dep]) + end + + specify "with formulae parameter" do + expect(described_class.installed_deps(formulae)) + .to eq([formula_is_dep]) + end + end + + describe "::installed_non_deps" do + let(:formula_is_dep) do + formula "foo" do + url "foo-1.1" + end + end + + let(:formula_with_deps) do + formula "bar" do + url "bar-1.0" + end + end + + let(:formulae) do + [ + formula_with_deps, + formula_is_dep + ] + end + + before do + allow(formula_with_deps).to receive(:runtime_formula_dependencies).and_return([ formula_is_dep ]) + end + + specify "without formulae parameter" do + allow(described_class).to receive(:installed).and_return(formulae) + + expect(described_class.installed_non_deps) + .to eq([formula_with_deps]) + end + + specify "with formulae parameter" do + expect(described_class.installed_non_deps(formulae)) + .to eq([formula_with_deps]) + end + end + describe "::installed_with_alias_path" do specify "with alias path with nil" do expect(described_class.installed_with_alias_path(nil)).to be_empty From 30a35614484f422c78a6328bcae2504b8ec35fb2 Mon Sep 17 00:00:00 2001 From: Tie Date: Sun, 1 Nov 2020 14:45:11 -0500 Subject: [PATCH 02/10] move uninstall logic to new file --- Library/Homebrew/cmd/uninstall.rb | 147 +------------------- Library/Homebrew/uninstall.rb | 215 ++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+), 140 deletions(-) create mode 100644 Library/Homebrew/uninstall.rb diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index 41a0117c6e..e541425b62 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -8,8 +8,11 @@ require "migrator" require "cli/parser" require "cask/cmd" require "cask/cask_loader" +require "uninstall" module Homebrew + extend Uninstall + module_function def uninstall_args @@ -54,76 +57,10 @@ module Homebrew kegs_by_rack = all_kegs.group_by(&:rack) end - handle_unsatisfied_dependents(kegs_by_rack, - ignore_dependencies: args.ignore_dependencies?, - named_args: args.named) - return if Homebrew.failed? - - kegs_by_rack.each do |rack, kegs| - if args.force? - name = rack.basename - - if rack.directory? - puts "Uninstalling #{name}... (#{rack.abv})" - kegs.each do |keg| - keg.unlink - keg.uninstall - end - end - - rm_pin rack - else - kegs.each do |keg| - begin - f = Formulary.from_rack(rack) - if f.pinned? - onoe "#{f.full_name} is pinned. You must unpin it to uninstall." - next - end - rescue - nil - 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) - puts "#{keg.name} #{versions.to_sentence} #{"is".pluralize(versions.count)} still installed." - puts "Run `brew uninstall --force #{keg.name}` to remove all versions." - end - - next unless f - - paths = f.pkgetc.find.map(&:to_s) if f.pkgetc.exist? - if paths.present? - puts - opoo <<~EOS - The following #{f.name} configuration files have not been removed! - If desired, remove them manually with `rm -rf`: - #{paths.sort.uniq.join("\n ")} - EOS - end - - unversioned_name = f.name.gsub(/@.+$/, "") - maybe_paths = Dir.glob("#{f.etc}/*#{unversioned_name}*") - maybe_paths -= paths if paths.present? - if maybe_paths.present? - puts - opoo <<~EOS - The following may be #{f.name} configuration files and have not been removed! - If desired, remove them manually with `rm -rf`: - #{maybe_paths.sort.uniq.join("\n ")} - EOS - end - end - end - end - end + uninstall_kegs(kegs_by_rack, + force: args.force?, + ignore_dependencies: args.ignore_dependencies?, + named_args: args.named) return if casks.blank? @@ -144,74 +81,4 @@ module Homebrew end end end - - def handle_unsatisfied_dependents(kegs_by_rack, ignore_dependencies: false, named_args: []) - return if ignore_dependencies - - all_kegs = kegs_by_rack.values.flatten(1) - check_for_dependents(all_kegs, named_args: named_args) - rescue MethodDeprecatedError - # Silently ignore deprecations when uninstalling. - nil - end - - def check_for_dependents(kegs, named_args: []) - return false unless result = Keg.find_some_installed_dependents(kegs) - - if Homebrew::EnvConfig.developer? - DeveloperDependentsMessage.new(*result, named_args: named_args).output - else - NondeveloperDependentsMessage.new(*result, named_args: named_args).output - end - - true - end - - class DependentsMessage - attr_reader :reqs, :deps, :named_args - - def initialize(requireds, dependents, named_args: []) - @reqs = requireds - @deps = dependents - @named_args = named_args - end - - protected - - def sample_command - "brew uninstall --ignore-dependencies #{named_args.join(" ")}" - end - - def are_required_by_deps - "#{"is".pluralize(reqs.count)} required by #{deps.to_sentence}, " \ - "which #{"is".pluralize(deps.count)} currently installed" - end - end - - class DeveloperDependentsMessage < DependentsMessage - def output - opoo <<~EOS - #{reqs.to_sentence} #{are_required_by_deps}. - You can silence this warning with: - #{sample_command} - EOS - end - end - - class NondeveloperDependentsMessage < DependentsMessage - def output - ofail <<~EOS - Refusing to uninstall #{reqs.to_sentence} - because #{"it".pluralize(reqs.count)} #{are_required_by_deps}. - You can override this and force removal with: - #{sample_command} - EOS - end - end - - def rm_pin(rack) - Formulary.from_rack(rack).unpin - rescue - nil - end end diff --git a/Library/Homebrew/uninstall.rb b/Library/Homebrew/uninstall.rb new file mode 100644 index 0000000000..6c201a3e26 --- /dev/null +++ b/Library/Homebrew/uninstall.rb @@ -0,0 +1,215 @@ +# typed: true +# frozen_string_literal: true + +require "keg" +require "formula" + +module Homebrew + # Helper module for uninstalling kegs. + # + # @api private + module Uninstall + module_function + + def uninstall_kegs(kegs_by_rack, force: false, ignore_dependencies: false, named_args: []) + handle_unsatisfied_dependents(kegs_by_rack, + ignore_dependencies: ignore_dependencies, + named_args: named_args) + return if Homebrew.failed? + + kegs_by_rack.each do |rack, kegs| + if force + name = rack.basename + + if rack.directory? + puts "Uninstalling #{name}... (#{rack.abv})" + kegs.each do |keg| + keg.unlink + keg.uninstall + end + end + + rm_pin rack + else + kegs.each do |keg| + begin + f = Formulary.from_rack(rack) + if f.pinned? + onoe "#{f.full_name} is pinned. You must unpin it to uninstall." + next + end + rescue + nil + 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) + puts "#{keg.name} #{versions.to_sentence} #{"is".pluralize(versions.count)} still installed." + puts "Run `brew uninstall --force #{keg.name}` to remove all versions." + end + + next unless f + + paths = f.pkgetc.find.map(&:to_s) if f.pkgetc.exist? + if paths.present? + puts + opoo <<~EOS + The following #{f.name} configuration files have not been removed! + If desired, remove them manually with `rm -rf`: + #{paths.sort.uniq.join("\n ")} + EOS + end + + unversioned_name = f.name.gsub(/@.+$/, "") + maybe_paths = Dir.glob("#{f.etc}/*#{unversioned_name}*") + maybe_paths -= paths if paths.present? + if maybe_paths.present? + puts + opoo <<~EOS + The following may be #{f.name} configuration files and have not been removed! + If desired, remove them manually with `rm -rf`: + #{maybe_paths.sort.uniq.join("\n ")} + EOS + end + end + end + end + end + end + + def handle_unsatisfied_dependents(kegs_by_rack, ignore_dependencies: false, named_args: []) + return if ignore_dependencies + + all_kegs = kegs_by_rack.values.flatten(1) + check_for_dependents(all_kegs, named_args: named_args) + rescue MethodDeprecatedError + # Silently ignore deprecations when uninstalling. + nil + end + + def check_for_dependents(kegs, named_args: []) + return false unless result = Keg.find_some_installed_dependents(kegs) + + if Homebrew::EnvConfig.developer? + DeveloperDependentsMessage.new(*result, named_args: named_args).output + else + NondeveloperDependentsMessage.new(*result, named_args: named_args).output + end + + true + end + + class DependentsMessage + attr_reader :reqs, :deps, :named_args + + def initialize(requireds, dependents, named_args: []) + @reqs = requireds + @deps = dependents + @named_args = named_args + end + + protected + + def sample_command + "brew uninstall --ignore-dependencies #{named_args.join(" ")}" + end + + def are_required_by_deps + "#{"is".pluralize(reqs.count)} required by #{deps.to_sentence}, " \ + "which #{"is".pluralize(deps.count)} currently installed" + end + end + + class DeveloperDependentsMessage < DependentsMessage + def output + opoo <<~EOS + #{reqs.to_sentence} #{are_required_by_deps}. + You can silence this warning with: + #{sample_command} + EOS + end + end + + class NondeveloperDependentsMessage < DependentsMessage + def output + ofail <<~EOS + Refusing to uninstall #{reqs.to_sentence} + because #{"it".pluralize(reqs.count)} #{are_required_by_deps}. + You can override this and force removal with: + #{sample_command} + EOS + end + end + + def rm_pin(rack) + Formulary.from_rack(rack).unpin + rescue + nil + end + + # def perform_preinstall_checks(all_fatal: false, cc: nil) + # check_cpu + # attempt_directory_creation + # check_cc_argv(cc) + # Diagnostic.checks(:supported_configuration_checks, fatal: all_fatal) + # Diagnostic.checks(:fatal_preinstall_checks) + # end + # alias generic_perform_preinstall_checks perform_preinstall_checks + # module_function :generic_perform_preinstall_checks + + # def perform_build_from_source_checks(all_fatal: false) + # Diagnostic.checks(:fatal_build_from_source_checks) + # Diagnostic.checks(:build_from_source_checks, fatal: all_fatal) + # end + + # def check_cpu + # return if Hardware::CPU.intel? && Hardware::CPU.is_64_bit? + + # message = "Sorry, Homebrew does not support your computer's CPU architecture!" + # if Hardware::CPU.arm? + # opoo message + # return + # elsif Hardware::CPU.ppc? + # message += <<~EOS + # For PowerPC Mac (PPC32/PPC64BE) support, see: + # #{Formatter.url("https://github.com/mistydemeo/tigerbrew")} + # EOS + # end + # abort message + # end + # private_class_method :check_cpu + + # def attempt_directory_creation + # Keg::MUST_EXIST_DIRECTORIES.each do |dir| + # FileUtils.mkdir_p(dir) unless dir.exist? + + # # Create these files to ensure that these directories aren't removed + # # by the Catalina installer. + # # (https://github.com/Homebrew/brew/issues/6263) + # keep_file = dir/".keepme" + # FileUtils.touch(keep_file) unless keep_file.exist? + # rescue + # nil + # end + # end + # private_class_method :attempt_directory_creation + + # def check_cc_argv(cc) + # return unless cc + + # @checks ||= Diagnostic::Checks.new + # opoo <<~EOS + # You passed `--cc=#{cc}`. + # #{@checks.please_create_pull_requests} + # EOS + # end + # private_class_method :check_cc_argv + end +end From 4127a7b62422ed0b9c38dbe022ee958640b7c614 Mon Sep 17 00:00:00 2001 From: Tie Date: Wed, 4 Nov 2020 17:14:43 -0500 Subject: [PATCH 03/10] move some uninstall tests to new file --- Library/Homebrew/test/cmd/uninstall_spec.rb | 58 ------------------- Library/Homebrew/test/uninstall_spec.rb | 62 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 58 deletions(-) create mode 100644 Library/Homebrew/test/uninstall_spec.rb diff --git a/Library/Homebrew/test/cmd/uninstall_spec.rb b/Library/Homebrew/test/cmd/uninstall_spec.rb index 9d79d3e7da..d05f4025d0 100644 --- a/Library/Homebrew/test/cmd/uninstall_spec.rb +++ b/Library/Homebrew/test/cmd/uninstall_spec.rb @@ -19,61 +19,3 @@ describe "brew uninstall", :integration_test do .and be_a_success end end - -describe Homebrew do - let(:dependency) { formula("dependency") { url "f-1" } } - let(:dependent) do - formula("dependent") do - url "f-1" - depends_on "dependency" - end - end - - let(:kegs_by_rack) { { dependency.rack => [Keg.new(dependency.latest_installed_prefix)] } } - - before do - [dependency, dependent].each do |f| - f.latest_installed_prefix.mkpath - Keg.new(f.latest_installed_prefix).optlink - end - - tab = Tab.empty - tab.homebrew_version = "1.1.6" - tab.tabfile = dependent.latest_installed_prefix/Tab::FILENAME - tab.runtime_dependencies = [ - { "full_name" => "dependency", "version" => "1" }, - ] - tab.write - - stub_formula_loader dependency - stub_formula_loader dependent - end - - describe "::handle_unsatisfied_dependents" do - specify "when developer" do - ENV["HOMEBREW_DEVELOPER"] = "1" - - expect { - described_class.handle_unsatisfied_dependents(kegs_by_rack) - }.to output(/Warning/).to_stderr - - expect(described_class).not_to have_failed - end - - specify "when not developer" do - expect { - described_class.handle_unsatisfied_dependents(kegs_by_rack) - }.to output(/Error/).to_stderr - - expect(described_class).to have_failed - end - - specify "when not developer and `ignore_dependencies` is true" do - expect { - described_class.handle_unsatisfied_dependents(kegs_by_rack, ignore_dependencies: true) - }.not_to output.to_stderr - - expect(described_class).not_to have_failed - end - end -end diff --git a/Library/Homebrew/test/uninstall_spec.rb b/Library/Homebrew/test/uninstall_spec.rb new file mode 100644 index 0000000000..d5f54077f2 --- /dev/null +++ b/Library/Homebrew/test/uninstall_spec.rb @@ -0,0 +1,62 @@ +# typed: false +# frozen_string_literal: true + +require "uninstall" + +describe Homebrew::Uninstall do + let(:dependency) { formula("dependency") { url "f-1" } } + let(:dependent) do + formula("dependent") do + url "f-1" + depends_on "dependency" + end + end + + let(:kegs_by_rack) { { dependency.rack => [Keg.new(dependency.latest_installed_prefix)] } } + + before do + [dependency, dependent].each do |f| + f.latest_installed_prefix.mkpath + Keg.new(f.latest_installed_prefix).optlink + end + + tab = Tab.empty + tab.homebrew_version = "1.1.6" + tab.tabfile = dependent.latest_installed_prefix/Tab::FILENAME + tab.runtime_dependencies = [ + { "full_name" => "dependency", "version" => "1" }, + ] + tab.write + + stub_formula_loader dependency + stub_formula_loader dependent + end + + describe "::handle_unsatisfied_dependents" do + specify "when developer" do + ENV["HOMEBREW_DEVELOPER"] = "1" + + expect { + described_class.handle_unsatisfied_dependents(kegs_by_rack) + }.to output(/Warning/).to_stderr + + expect(Homebrew).not_to have_failed + end + + specify "when not developer" do + expect { + described_class.handle_unsatisfied_dependents(kegs_by_rack) + }.to output(/Error/).to_stderr + + expect(Homebrew).to have_failed + end + + specify "when not developer and `ignore_dependencies` is true" do + expect { + described_class.handle_unsatisfied_dependents(kegs_by_rack, ignore_dependencies: true) + }.not_to output.to_stderr + + expect(Homebrew).not_to have_failed + end + end +end From 0fa417706a2143dbec1e6fd2f29f3e3e32663252 Mon Sep 17 00:00:00 2001 From: Dawid Dziurla Date: Thu, 10 Sep 2020 19:45:02 +0200 Subject: [PATCH 04/10] cmd: add autoremove command --- Library/Homebrew/cmd/autoremove.rb | 54 ++++++++++++++++++++ Library/Homebrew/test/.rubocop_todo.yml | 1 + Library/Homebrew/test/cmd/autoremove_spec.rb | 7 +++ completions/internal_commands_list.txt | 1 + docs/Manpage.md | 7 +++ manpages/brew.1 | 7 +++ 6 files changed, 77 insertions(+) create mode 100644 Library/Homebrew/cmd/autoremove.rb create mode 100644 Library/Homebrew/test/cmd/autoremove_spec.rb diff --git a/Library/Homebrew/cmd/autoremove.rb b/Library/Homebrew/cmd/autoremove.rb new file mode 100644 index 0000000000..f02712e94b --- /dev/null +++ b/Library/Homebrew/cmd/autoremove.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "formula" +require "cli/parser" + +module Homebrew + module_function + + def autoremove_args + Homebrew::CLI::Parser.new do + usage_banner <<~EOS + `autoremove` [] + + Remove packages that weren't installed on request and are no longer needed. + EOS + switch "-n", "--dry-run", + description: "Just print what would be removed." + named 0 + end + end + + def get_removable_formulae(installed_formulae) + removable_formulae = [] + + installed_formulae.each do |formula| + # Reject formulae installed on request. + next if Tab.for_keg(formula.any_installed_keg).installed_on_request + # Reject formulae which are needed at runtime by other formulae. + next if installed_formulae.flat_map(&:runtime_formula_dependencies).include?(formula) + + removable_formulae << installed_formulae.delete(formula) + removable_formulae += get_removable_formulae(installed_formulae) + end + + removable_formulae + end + + def autoremove + args = autoremove_args.parse + + removable_formulae = get_removable_formulae(Formula.installed.sort) + + return if removable_formulae.blank? + + formulae_names = removable_formulae.map(&:full_name) + + oh1 "Formulae that could be removed" + puts formulae_names + + return if args.dry_run? + + system HOMEBREW_BREW_FILE, "rm", *formulae_names + end +end diff --git a/Library/Homebrew/test/.rubocop_todo.yml b/Library/Homebrew/test/.rubocop_todo.yml index 3474b71ed0..e3f0383a8a 100644 --- a/Library/Homebrew/test/.rubocop_todo.yml +++ b/Library/Homebrew/test/.rubocop_todo.yml @@ -33,6 +33,7 @@ RSpec/MultipleDescribes: - 'cmd/--repository_spec.rb' - 'cmd/--version_spec.rb' - 'cmd/analytics_spec.rb' + - 'cmd/autoremove_spec.rb' - 'cmd/cleanup_spec.rb' - 'cmd/commands_spec.rb' - 'cmd/config_spec.rb' diff --git a/Library/Homebrew/test/cmd/autoremove_spec.rb b/Library/Homebrew/test/cmd/autoremove_spec.rb new file mode 100644 index 0000000000..efbe93ecfb --- /dev/null +++ b/Library/Homebrew/test/cmd/autoremove_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "cmd/shared_examples/args_parse" + +describe "Homebrew.autoremove_args" do + it_behaves_like "parseable arguments" +end diff --git a/completions/internal_commands_list.txt b/completions/internal_commands_list.txt index cd9c60d4b0..0d277a1200 100644 --- a/completions/internal_commands_list.txt +++ b/completions/internal_commands_list.txt @@ -12,6 +12,7 @@ abv analytics audit +autoremove bottle bump bump-cask-pr diff --git a/docs/Manpage.md b/docs/Manpage.md index 318806b29d..9fff49ae0d 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -56,6 +56,13 @@ Turn Homebrew's analytics on or off respectively. `brew analytics regenerate-uuid`: Regenerate the UUID used for Homebrew's analytics. +### `autoremove` [*`options`*] + +Remove packages that weren't installed on request and are no longer needed. + +* `-n`, `--dry-run`: + Just print what would be removed. + ### `cask` *`command`* [*`options`*] [*`cask`*] Homebrew Cask provides a friendly CLI workflow for the administration of macOS applications distributed as binaries. diff --git a/manpages/brew.1 b/manpages/brew.1 index 3ea3804113..537ebab945 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -53,6 +53,13 @@ Control Homebrew\'s anonymous aggregate user behaviour analytics\. Read more at \fBbrew analytics regenerate\-uuid\fR Regenerate the UUID used for Homebrew\'s analytics\. . +.SS "\fBautoremove\fR [\fIoptions\fR]" +Remove packages that weren\'t installed on request and are no longer needed\. +. +.TP +\fB\-n\fR, \fB\-\-dry\-run\fR +Just print what would be removed\. +. .SS "\fBcask\fR \fIcommand\fR [\fIoptions\fR] [\fIcask\fR]" Homebrew Cask provides a friendly CLI workflow for the administration of macOS applications distributed as binaries\. . From 10ae9bcde728148ffed445f976cb06d3cf08e65d Mon Sep 17 00:00:00 2001 From: Tie Date: Wed, 4 Nov 2020 15:59:50 -0500 Subject: [PATCH 05/10] modify autoremove to use uninstall --- Library/Homebrew/cmd/autoremove.rb | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Library/Homebrew/cmd/autoremove.rb b/Library/Homebrew/cmd/autoremove.rb index f02712e94b..de6c704e43 100644 --- a/Library/Homebrew/cmd/autoremove.rb +++ b/Library/Homebrew/cmd/autoremove.rb @@ -2,8 +2,11 @@ require "formula" require "cli/parser" +require "uninstall" module Homebrew + extend Uninstall + module_function def autoremove_args @@ -19,17 +22,13 @@ module Homebrew end end - def get_removable_formulae(installed_formulae) - removable_formulae = [] + def get_removable_formulae(formulae) + removable_formulae = Formula.installed_non_deps(formulae).reject { + |f| Tab.for_keg(f.any_installed_keg).installed_on_request + } - installed_formulae.each do |formula| - # Reject formulae installed on request. - next if Tab.for_keg(formula.any_installed_keg).installed_on_request - # Reject formulae which are needed at runtime by other formulae. - next if installed_formulae.flat_map(&:runtime_formula_dependencies).include?(formula) - - removable_formulae << installed_formulae.delete(formula) - removable_formulae += get_removable_formulae(installed_formulae) + if removable_formulae.any? + removable_formulae += get_removable_formulae(formulae - removable_formulae) end removable_formulae @@ -38,17 +37,18 @@ module Homebrew def autoremove args = autoremove_args.parse - removable_formulae = get_removable_formulae(Formula.installed.sort) + removable_formulae = get_removable_formulae(Formula.installed) return if removable_formulae.blank? - formulae_names = removable_formulae.map(&:full_name) + formulae_names = removable_formulae.map(&:full_name).sort oh1 "Formulae that could be removed" puts formulae_names - + return if args.dry_run? - system HOMEBREW_BREW_FILE, "rm", *formulae_names + kegs_by_rack = removable_formulae.map(&:any_installed_keg).group_by(&:rack) + uninstall_kegs(kegs_by_rack) end end From b5427e5095c922acbbee0e7e6052c83c2798060b Mon Sep 17 00:00:00 2001 From: Tie Date: Wed, 4 Nov 2020 18:53:03 -0500 Subject: [PATCH 06/10] fix style issues --- Library/Homebrew/cmd/autoremove.rb | 13 ++- Library/Homebrew/formula.rb | 4 +- Library/Homebrew/test/cmd/autoremove_spec.rb | 1 + Library/Homebrew/test/cmd/leaves_spec.rb | 6 +- Library/Homebrew/test/formula_spec.rb | 10 +-- Library/Homebrew/uninstall.rb | 91 ++++---------------- 6 files changed, 35 insertions(+), 90 deletions(-) diff --git a/Library/Homebrew/cmd/autoremove.rb b/Library/Homebrew/cmd/autoremove.rb index de6c704e43..384c7ea3de 100644 --- a/Library/Homebrew/cmd/autoremove.rb +++ b/Library/Homebrew/cmd/autoremove.rb @@ -1,3 +1,4 @@ +# typed: false # frozen_string_literal: true require "formula" @@ -23,14 +24,12 @@ module Homebrew end def get_removable_formulae(formulae) - removable_formulae = Formula.installed_non_deps(formulae).reject { - |f| Tab.for_keg(f.any_installed_keg).installed_on_request - } - - if removable_formulae.any? - removable_formulae += get_removable_formulae(formulae - removable_formulae) + removable_formulae = Formula.installed_non_deps(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.any? + removable_formulae end @@ -45,7 +44,7 @@ module Homebrew oh1 "Formulae that could be removed" puts formulae_names - + return if args.dry_run? kegs_by_rack = removable_formulae.map(&:any_installed_keg).group_by(&:rack) diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 808c73b3f4..e10ca82e6f 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -1517,13 +1517,13 @@ class Formula # An array of installed {Formula} that are dependencies of other installed {Formula} # @private - def self.installed_deps(formulae=installed) + def self.installed_deps(formulae = installed) formulae.flat_map(&:runtime_formula_dependencies).uniq(&:name) end # An array of all installed {Formula} that are not dependencies of other installed {Formula} # @private - def self.installed_non_deps(formulae=installed) + def self.installed_non_deps(formulae = installed) formulae - installed_deps(formulae) end diff --git a/Library/Homebrew/test/cmd/autoremove_spec.rb b/Library/Homebrew/test/cmd/autoremove_spec.rb index efbe93ecfb..4cd9c99f51 100644 --- a/Library/Homebrew/test/cmd/autoremove_spec.rb +++ b/Library/Homebrew/test/cmd/autoremove_spec.rb @@ -1,3 +1,4 @@ +# typed: false # frozen_string_literal: true require "cmd/shared_examples/args_parse" diff --git a/Library/Homebrew/test/cmd/leaves_spec.rb b/Library/Homebrew/test/cmd/leaves_spec.rb index 16239d1cb3..705a85c7d3 100644 --- a/Library/Homebrew/test/cmd/leaves_spec.rb +++ b/Library/Homebrew/test/cmd/leaves_spec.rb @@ -12,7 +12,7 @@ describe "brew leaves", :integration_test do it "prints nothing" do setup_test_formula "foo" setup_test_formula "bar" - + expect { brew "leaves" } .to not_to_output.to_stdout .and not_to_output.to_stderr @@ -25,7 +25,7 @@ describe "brew leaves", :integration_test do setup_test_formula "foo" setup_test_formula "bar" (HOMEBREW_CELLAR/"foo/0.1/somedir").mkpath - + expect { brew "leaves" } .to output("foo\n").to_stdout .and not_to_output.to_stderr @@ -39,7 +39,7 @@ describe "brew leaves", :integration_test do setup_test_formula "bar" (HOMEBREW_CELLAR/"foo/0.1/somedir").mkpath (HOMEBREW_CELLAR/"bar/0.1/somedir").mkpath - + expect { brew "leaves" } .to output("bar\n").to_stdout .and not_to_output.to_stderr diff --git a/Library/Homebrew/test/formula_spec.rb b/Library/Homebrew/test/formula_spec.rb index ad803f05f9..f5d785da82 100644 --- a/Library/Homebrew/test/formula_spec.rb +++ b/Library/Homebrew/test/formula_spec.rb @@ -456,12 +456,12 @@ describe Formula do let(:formulae) do [ formula_with_deps, - formula_is_dep + formula_is_dep, ] - end + 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_dep]) end specify "without formulae parameter" do @@ -493,12 +493,12 @@ describe Formula do let(:formulae) do [ formula_with_deps, - formula_is_dep + formula_is_dep, ] 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_dep]) end specify "without formulae parameter" do diff --git a/Library/Homebrew/uninstall.rb b/Library/Homebrew/uninstall.rb index 6c201a3e26..214f33a662 100644 --- a/Library/Homebrew/uninstall.rb +++ b/Library/Homebrew/uninstall.rb @@ -13,8 +13,8 @@ module Homebrew def uninstall_kegs(kegs_by_rack, force: false, ignore_dependencies: false, named_args: []) handle_unsatisfied_dependents(kegs_by_rack, - ignore_dependencies: ignore_dependencies, - named_args: named_args) + ignore_dependencies: ignore_dependencies, + named_args: named_args) return if Homebrew.failed? kegs_by_rack.each do |rack, kegs| @@ -50,9 +50,9 @@ module Homebrew rm_pin rack if rack.directory? - versions = rack.subdirs.map(&:basename) - puts "#{keg.name} #{versions.to_sentence} #{"is".pluralize(versions.count)} still installed." - puts "Run `brew uninstall --force #{keg.name}` to remove all versions." + versions = rack.subdirs.map(&:basename) + puts "#{keg.name} #{versions.to_sentence} #{"is".pluralize(versions.count)} still installed." + puts "Run `brew uninstall --force #{keg.name}` to remove all versions." end next unless f @@ -86,7 +86,7 @@ module Homebrew def handle_unsatisfied_dependents(kegs_by_rack, ignore_dependencies: false, named_args: []) return if ignore_dependencies - + all_kegs = kegs_by_rack.values.flatten(1) check_for_dependents(all_kegs, named_args: named_args) rescue MethodDeprecatedError @@ -96,37 +96,39 @@ module Homebrew def check_for_dependents(kegs, named_args: []) return false unless result = Keg.find_some_installed_dependents(kegs) - + if Homebrew::EnvConfig.developer? DeveloperDependentsMessage.new(*result, named_args: named_args).output else NondeveloperDependentsMessage.new(*result, named_args: named_args).output end - + true end + # @api private class DependentsMessage attr_reader :reqs, :deps, :named_args - + def initialize(requireds, dependents, named_args: []) @reqs = requireds @deps = dependents @named_args = named_args end - + protected - + def sample_command "brew uninstall --ignore-dependencies #{named_args.join(" ")}" end - + def are_required_by_deps "#{"is".pluralize(reqs.count)} required by #{deps.to_sentence}, " \ "which #{"is".pluralize(deps.count)} currently installed" end end - + + # @api private class DeveloperDependentsMessage < DependentsMessage def output opoo <<~EOS @@ -136,7 +138,8 @@ module Homebrew EOS end end - + + # @api private class NondeveloperDependentsMessage < DependentsMessage def output ofail <<~EOS @@ -147,69 +150,11 @@ module Homebrew EOS end end - + def rm_pin(rack) Formulary.from_rack(rack).unpin rescue nil end - - # def perform_preinstall_checks(all_fatal: false, cc: nil) - # check_cpu - # attempt_directory_creation - # check_cc_argv(cc) - # Diagnostic.checks(:supported_configuration_checks, fatal: all_fatal) - # Diagnostic.checks(:fatal_preinstall_checks) - # end - # alias generic_perform_preinstall_checks perform_preinstall_checks - # module_function :generic_perform_preinstall_checks - - # def perform_build_from_source_checks(all_fatal: false) - # Diagnostic.checks(:fatal_build_from_source_checks) - # Diagnostic.checks(:build_from_source_checks, fatal: all_fatal) - # end - - # def check_cpu - # return if Hardware::CPU.intel? && Hardware::CPU.is_64_bit? - - # message = "Sorry, Homebrew does not support your computer's CPU architecture!" - # if Hardware::CPU.arm? - # opoo message - # return - # elsif Hardware::CPU.ppc? - # message += <<~EOS - # For PowerPC Mac (PPC32/PPC64BE) support, see: - # #{Formatter.url("https://github.com/mistydemeo/tigerbrew")} - # EOS - # end - # abort message - # end - # private_class_method :check_cpu - - # def attempt_directory_creation - # Keg::MUST_EXIST_DIRECTORIES.each do |dir| - # FileUtils.mkdir_p(dir) unless dir.exist? - - # # Create these files to ensure that these directories aren't removed - # # by the Catalina installer. - # # (https://github.com/Homebrew/brew/issues/6263) - # keep_file = dir/".keepme" - # FileUtils.touch(keep_file) unless keep_file.exist? - # rescue - # nil - # end - # end - # private_class_method :attempt_directory_creation - - # def check_cc_argv(cc) - # return unless cc - - # @checks ||= Diagnostic::Checks.new - # opoo <<~EOS - # You passed `--cc=#{cc}`. - # #{@checks.please_create_pull_requests} - # EOS - # end - # private_class_method :check_cc_argv end end From b5d5be3474cbe30195823ebdfea923e3a8dbea6a Mon Sep 17 00:00:00 2001 From: Tie Date: Thu, 5 Nov 2020 00:53:12 -0500 Subject: [PATCH 07/10] fix typecheck --- Library/Homebrew/uninstall.rbi | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Library/Homebrew/uninstall.rbi diff --git a/Library/Homebrew/uninstall.rbi b/Library/Homebrew/uninstall.rbi new file mode 100644 index 0000000000..f801c23a87 --- /dev/null +++ b/Library/Homebrew/uninstall.rbi @@ -0,0 +1,7 @@ +# typed: strict + +module Homebrew + module Uninstall + include Kernel + end +end From afd7bb88898ab48fa8f4225c0c96858c39f64317 Mon Sep 17 00:00:00 2001 From: Tie Date: Thu, 5 Nov 2020 12:27:22 -0500 Subject: [PATCH 08/10] make suggestions --- Library/Homebrew/cmd/autoremove.rb | 10 +++---- Library/Homebrew/cmd/leaves.rb | 3 +- Library/Homebrew/cmd/uninstall.rb | 10 +++---- Library/Homebrew/formula.rb | 12 +++----- Library/Homebrew/test/formula_spec.rb | 43 ++------------------------- 5 files changed, 17 insertions(+), 61 deletions(-) diff --git a/Library/Homebrew/cmd/autoremove.rb b/Library/Homebrew/cmd/autoremove.rb index 384c7ea3de..771fdad0f0 100644 --- a/Library/Homebrew/cmd/autoremove.rb +++ b/Library/Homebrew/cmd/autoremove.rb @@ -6,8 +6,6 @@ require "cli/parser" require "uninstall" module Homebrew - extend Uninstall - module_function def autoremove_args @@ -24,7 +22,7 @@ module Homebrew end def get_removable_formulae(formulae) - removable_formulae = Formula.installed_non_deps(formulae).reject do |f| + removable_formulae = Formula.installed_formulae_with_no_dependents(formulae).reject do |f| Tab.for_keg(f.any_installed_keg).installed_on_request end @@ -42,12 +40,14 @@ module Homebrew formulae_names = removable_formulae.map(&:full_name).sort - oh1 "Formulae that could be removed" + intent = args.dry_run? ? "could" : "will" + oh1 "Formulae that #{intent} be removed" puts formulae_names return if args.dry_run? + puts kegs_by_rack = removable_formulae.map(&:any_installed_keg).group_by(&:rack) - uninstall_kegs(kegs_by_rack) + Uninstall.uninstall_kegs(kegs_by_rack) end end diff --git a/Library/Homebrew/cmd/leaves.rb b/Library/Homebrew/cmd/leaves.rb index dceb3c0e42..7cb1497367 100644 --- a/Library/Homebrew/cmd/leaves.rb +++ b/Library/Homebrew/cmd/leaves.rb @@ -22,7 +22,6 @@ module Homebrew def leaves leaves_args.parse - leaves = Formula.installed_non_deps.map(&:full_name).sort - leaves.each(&method(:puts)) + Formula.installed_formulae_with_no_dependents.map(&:full_name).sort.each(&method(:puts)) end end diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index e541425b62..2fadf91c50 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -11,8 +11,6 @@ require "cask/cask_loader" require "uninstall" module Homebrew - extend Uninstall - module_function def uninstall_args @@ -57,10 +55,10 @@ module Homebrew kegs_by_rack = all_kegs.group_by(&:rack) end - uninstall_kegs(kegs_by_rack, - force: args.force?, - ignore_dependencies: args.ignore_dependencies?, - named_args: args.named) + Uninstall.uninstall_kegs(kegs_by_rack, + force: args.force?, + ignore_dependencies: args.ignore_dependencies?, + named_args: args.named) return if casks.blank? diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index e10ca82e6f..5d2dc27735 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -1515,16 +1515,12 @@ class Formula end.uniq(&:name) end - # An array of installed {Formula} that are dependencies of other installed {Formula} + # An array of all installed {Formula} without dependents # @private - def self.installed_deps(formulae = installed) - formulae.flat_map(&:runtime_formula_dependencies).uniq(&:name) - end + def self.installed_formulae_with_no_dependents(formulae = installed) + return [] if formulae.blank? - # An array of all installed {Formula} that are not dependencies of other installed {Formula} - # @private - def self.installed_non_deps(formulae = installed) - formulae - installed_deps(formulae) + formulae - formulae.flat_map(&:runtime_formula_dependencies) 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 f5d785da82..c91b27d04f 100644 --- a/Library/Homebrew/test/formula_spec.rb +++ b/Library/Homebrew/test/formula_spec.rb @@ -440,7 +440,7 @@ describe Formula do end end - describe "::installed_deps" do + describe "::installed_formulae_with_no_dependents" do let(:formula_is_dep) do formula "foo" do url "foo-1.1" @@ -467,49 +467,12 @@ describe Formula do specify "without formulae parameter" do allow(described_class).to receive(:installed).and_return(formulae) - expect(described_class.installed_deps) - .to eq([formula_is_dep]) - end - - specify "with formulae parameter" do - expect(described_class.installed_deps(formulae)) - .to eq([formula_is_dep]) - end - end - - describe "::installed_non_deps" do - let(:formula_is_dep) do - formula "foo" do - url "foo-1.1" - end - end - - let(:formula_with_deps) do - formula "bar" do - url "bar-1.0" - end - end - - let(:formulae) do - [ - formula_with_deps, - formula_is_dep, - ] - end - - before do - allow(formula_with_deps).to receive(:runtime_formula_dependencies).and_return([formula_is_dep]) - end - - specify "without formulae parameter" do - allow(described_class).to receive(:installed).and_return(formulae) - - expect(described_class.installed_non_deps) + expect(described_class.installed_formulae_with_no_dependents) .to eq([formula_with_deps]) end specify "with formulae parameter" do - expect(described_class.installed_non_deps(formulae)) + expect(described_class.installed_formulae_with_no_dependents(formulae)) .to eq([formula_with_deps]) end end From 8b4197dd10203c22f3ef2b4f334b208c15eff7ac Mon Sep 17 00:00:00 2001 From: Tie Date: Fri, 6 Nov 2020 10:36:21 -0500 Subject: [PATCH 09/10] make suggestions round 2 --- Library/Homebrew/cmd/autoremove.rb | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Library/Homebrew/cmd/autoremove.rb b/Library/Homebrew/cmd/autoremove.rb index 771fdad0f0..fd5c786f5b 100644 --- a/Library/Homebrew/cmd/autoremove.rb +++ b/Library/Homebrew/cmd/autoremove.rb @@ -13,10 +13,10 @@ module Homebrew usage_banner <<~EOS `autoremove` [] - Remove packages that weren't installed on request and are no longer needed. + Uninstall formulae that were only installed as a dependency of another formula and are now no longer needed. EOS switch "-n", "--dry-run", - description: "Just print what would be removed." + description: "List what would be uninstalled, but do not actually uninstall anything." named 0 end end @@ -26,7 +26,7 @@ module Homebrew Tab.for_keg(f.any_installed_keg).installed_on_request end - removable_formulae += get_removable_formulae(formulae - removable_formulae) if removable_formulae.any? + removable_formulae += get_removable_formulae(formulae - removable_formulae) if removable_formulae.present? removable_formulae end @@ -35,18 +35,15 @@ module Homebrew args = autoremove_args.parse removable_formulae = get_removable_formulae(Formula.installed) - return if removable_formulae.blank? formulae_names = removable_formulae.map(&:full_name).sort - intent = args.dry_run? ? "could" : "will" - oh1 "Formulae that #{intent} be removed" - puts formulae_names - + 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? - puts kegs_by_rack = removable_formulae.map(&:any_installed_keg).group_by(&:rack) Uninstall.uninstall_kegs(kegs_by_rack) end From 45a4fa85ccb29b98561ca2dddc66db5e91c4fac1 Mon Sep 17 00:00:00 2001 From: Tie Date: Fri, 6 Nov 2020 11:02:59 -0500 Subject: [PATCH 10/10] update manpages --- docs/Manpage.md | 4 ++-- manpages/brew.1 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Manpage.md b/docs/Manpage.md index 9fff49ae0d..4fb7f93ab7 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -58,10 +58,10 @@ Regenerate the UUID used for Homebrew's analytics. ### `autoremove` [*`options`*] -Remove packages that weren't installed on request and are no longer needed. +Uninstall formulae that were only installed as a dependency of another formula and are now no longer needed. * `-n`, `--dry-run`: - Just print what would be removed. + List what would be uninstalled, but do not actually uninstall anything. ### `cask` *`command`* [*`options`*] [*`cask`*] diff --git a/manpages/brew.1 b/manpages/brew.1 index 537ebab945..d62b6a8664 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -54,11 +54,11 @@ Control Homebrew\'s anonymous aggregate user behaviour analytics\. Read more at Regenerate the UUID used for Homebrew\'s analytics\. . .SS "\fBautoremove\fR [\fIoptions\fR]" -Remove packages that weren\'t installed on request and are no longer needed\. +Uninstall formulae that were only installed as a dependency of another formula and are now no longer needed\. . .TP \fB\-n\fR, \fB\-\-dry\-run\fR -Just print what would be removed\. +List what would be uninstalled, but do not actually uninstall anything\. . .SS "\fBcask\fR \fIcommand\fR [\fIoptions\fR] [\fIcask\fR]" Homebrew Cask provides a friendly CLI workflow for the administration of macOS applications distributed as binaries\.