commit
						2ac5cffd0d
					
				
							
								
								
									
										50
									
								
								Library/Homebrew/cmd/autoremove.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								Library/Homebrew/cmd/autoremove.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
			
		||||
# typed: false
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "formula"
 | 
			
		||||
require "cli/parser"
 | 
			
		||||
require "uninstall"
 | 
			
		||||
 | 
			
		||||
module Homebrew
 | 
			
		||||
  module_function
 | 
			
		||||
 | 
			
		||||
  def autoremove_args
 | 
			
		||||
    Homebrew::CLI::Parser.new do
 | 
			
		||||
      usage_banner <<~EOS
 | 
			
		||||
        `autoremove` [<options>]
 | 
			
		||||
 | 
			
		||||
        Uninstall formulae that were only installed as a dependency of another formula and are now no longer needed.
 | 
			
		||||
      EOS
 | 
			
		||||
      switch "-n", "--dry-run",
 | 
			
		||||
             description: "List what would be uninstalled, but do not actually uninstall anything."
 | 
			
		||||
      named 0
 | 
			
		||||
    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
 | 
			
		||||
    args = autoremove_args.parse
 | 
			
		||||
 | 
			
		||||
    removable_formulae = get_removable_formulae(Formula.installed)
 | 
			
		||||
    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
 | 
			
		||||
@ -2,7 +2,6 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "formula"
 | 
			
		||||
require "tab"
 | 
			
		||||
require "cli/parser"
 | 
			
		||||
 | 
			
		||||
module Homebrew
 | 
			
		||||
@ -23,9 +22,6 @@ 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.each(&method(:puts))
 | 
			
		||||
    Formula.installed_formulae_with_no_dependents.map(&:full_name).sort.each(&method(:puts))
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ require "migrator"
 | 
			
		||||
require "cli/parser"
 | 
			
		||||
require "cask/cmd"
 | 
			
		||||
require "cask/cask_loader"
 | 
			
		||||
require "uninstall"
 | 
			
		||||
 | 
			
		||||
module Homebrew
 | 
			
		||||
  module_function
 | 
			
		||||
@ -54,76 +55,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.uninstall_kegs(kegs_by_rack,
 | 
			
		||||
                             force:               args.force?,
 | 
			
		||||
                             ignore_dependencies: args.ignore_dependencies?,
 | 
			
		||||
                             named_args:          args.named)
 | 
			
		||||
 | 
			
		||||
    return if casks.blank?
 | 
			
		||||
 | 
			
		||||
@ -144,74 +79,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
 | 
			
		||||
 | 
			
		||||
@ -1518,6 +1518,14 @@ class Formula
 | 
			
		||||
    end.uniq(&:name)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # An array of all installed {Formula} without dependents
 | 
			
		||||
  # @private
 | 
			
		||||
  def self.installed_formulae_with_no_dependents(formulae = installed)
 | 
			
		||||
    return [] if formulae.blank?
 | 
			
		||||
 | 
			
		||||
    formulae - formulae.flat_map(&:runtime_formula_dependencies)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def self.installed_with_alias_path(alias_path)
 | 
			
		||||
    return [] if alias_path.nil?
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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'
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								Library/Homebrew/test/cmd/autoremove_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								Library/Homebrew/test/cmd/autoremove_spec.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
# typed: false
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "cmd/shared_examples/args_parse"
 | 
			
		||||
 | 
			
		||||
describe "Homebrew.autoremove_args" do
 | 
			
		||||
  it_behaves_like "parseable arguments"
 | 
			
		||||
end
 | 
			
		||||
@ -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 output("foo\n").to_stdout
 | 
			
		||||
      .and not_to_output.to_stderr
 | 
			
		||||
      .and be_a_success
 | 
			
		||||
      expect { brew "leaves" }
 | 
			
		||||
        .to not_to_output.to_stdout
 | 
			
		||||
        .and not_to_output.to_stderr
 | 
			
		||||
        .and be_a_success
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -440,6 +440,43 @@ describe Formula do
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "::installed_formulae_with_no_dependents" 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_formulae_with_no_dependents)
 | 
			
		||||
          .to eq([formula_with_deps])
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    specify "with formulae parameter" do
 | 
			
		||||
      expect(described_class.installed_formulae_with_no_dependents(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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										62
									
								
								Library/Homebrew/test/uninstall_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								Library/Homebrew/test/uninstall_spec.rb
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
			
		||||
							
								
								
									
										160
									
								
								Library/Homebrew/uninstall.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								Library/Homebrew/uninstall.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,160 @@
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
    # @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
 | 
			
		||||
          #{reqs.to_sentence} #{are_required_by_deps}.
 | 
			
		||||
          You can silence this warning with:
 | 
			
		||||
            #{sample_command}
 | 
			
		||||
        EOS
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # @api private
 | 
			
		||||
    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
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										7
									
								
								Library/Homebrew/uninstall.rbi
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								Library/Homebrew/uninstall.rbi
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
# typed: strict
 | 
			
		||||
 | 
			
		||||
module Homebrew
 | 
			
		||||
  module Uninstall
 | 
			
		||||
    include Kernel
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -12,6 +12,7 @@
 | 
			
		||||
abv
 | 
			
		||||
analytics
 | 
			
		||||
audit
 | 
			
		||||
autoremove
 | 
			
		||||
bottle
 | 
			
		||||
bump
 | 
			
		||||
bump-cask-pr
 | 
			
		||||
 | 
			
		||||
@ -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`*]
 | 
			
		||||
 | 
			
		||||
Uninstall formulae that were only installed as a dependency of another formula and are now no longer needed.
 | 
			
		||||
 | 
			
		||||
* `-n`, `--dry-run`:
 | 
			
		||||
  List what would be uninstalled, but do not actually uninstall anything.
 | 
			
		||||
 | 
			
		||||
### `cask` *`command`* [*`options`*] [*`cask`*]
 | 
			
		||||
 | 
			
		||||
Homebrew Cask provides a friendly CLI workflow for the administration of macOS applications distributed as binaries.
 | 
			
		||||
 | 
			
		||||
@ -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]"
 | 
			
		||||
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
 | 
			
		||||
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\.
 | 
			
		||||
.
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user