mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00

This adds support for Cask old tokens used for renames of Casks. We'll now correctly check these at installation time to avoid repeatedly installing renamed Casks and dump them in the Brewfile. We also use this logic to avoid cleaning up renamed Casks.
222 lines
8.0 KiB
Ruby
222 lines
8.0 KiB
Ruby
# typed: true # rubocop:todo Sorbet/StrictSigil
|
|
# frozen_string_literal: true
|
|
|
|
require "utils/formatter"
|
|
|
|
module Homebrew
|
|
module Bundle
|
|
module Commands
|
|
# TODO: refactor into multiple modules
|
|
module Cleanup
|
|
def self.reset!
|
|
require "bundle/cask_dumper"
|
|
require "bundle/formula_dumper"
|
|
require "bundle/tap_dumper"
|
|
require "bundle/vscode_extension_dumper"
|
|
require "bundle/brew_services"
|
|
|
|
@dsl = nil
|
|
@kept_casks = nil
|
|
@kept_formulae = nil
|
|
Homebrew::Bundle::CaskDumper.reset!
|
|
Homebrew::Bundle::FormulaDumper.reset!
|
|
Homebrew::Bundle::TapDumper.reset!
|
|
Homebrew::Bundle::VscodeExtensionDumper.reset!
|
|
Homebrew::Bundle::BrewServices.reset!
|
|
end
|
|
|
|
def self.run(global: false, file: nil, force: false, zap: false, dsl: nil,
|
|
formulae: true, casks: true, taps: true, vscode: true)
|
|
@dsl ||= dsl
|
|
|
|
casks = casks ? casks_to_uninstall(global:, file:) : []
|
|
formulae = formulae ? formulae_to_uninstall(global:, file:) : []
|
|
taps = taps ? taps_to_untap(global:, file:) : []
|
|
vscode_extensions = vscode ? vscode_extensions_to_uninstall(global:, file:) : []
|
|
if force
|
|
if casks.any?
|
|
args = zap ? ["--zap"] : []
|
|
Kernel.system HOMEBREW_BREW_FILE, "uninstall", "--cask", *args, "--force", *casks
|
|
puts "Uninstalled #{casks.size} cask#{(casks.size == 1) ? "" : "s"}"
|
|
end
|
|
|
|
if formulae.any?
|
|
Kernel.system HOMEBREW_BREW_FILE, "uninstall", "--formula", "--force", *formulae
|
|
puts "Uninstalled #{formulae.size} formula#{(formulae.size == 1) ? "" : "e"}"
|
|
end
|
|
|
|
Kernel.system HOMEBREW_BREW_FILE, "untap", *taps if taps.any?
|
|
|
|
Bundle.exchange_uid_if_needed! do
|
|
vscode_extensions.each do |extension|
|
|
Kernel.system(T.must(Bundle.which_vscode).to_s, "--uninstall-extension", extension)
|
|
end
|
|
end
|
|
|
|
cleanup = system_output_no_stderr(HOMEBREW_BREW_FILE, "cleanup")
|
|
puts cleanup unless cleanup.empty?
|
|
else
|
|
would_uninstall = false
|
|
|
|
if casks.any?
|
|
puts "Would uninstall casks:"
|
|
puts Formatter.columns casks
|
|
would_uninstall = true
|
|
end
|
|
|
|
if formulae.any?
|
|
puts "Would uninstall formulae:"
|
|
puts Formatter.columns formulae
|
|
would_uninstall = true
|
|
end
|
|
|
|
if taps.any?
|
|
puts "Would untap:"
|
|
puts Formatter.columns taps
|
|
would_uninstall = true
|
|
end
|
|
|
|
if vscode_extensions.any?
|
|
puts "Would uninstall VSCode extensions:"
|
|
puts Formatter.columns vscode_extensions
|
|
would_uninstall = true
|
|
end
|
|
|
|
cleanup = system_output_no_stderr(HOMEBREW_BREW_FILE, "cleanup", "--dry-run")
|
|
unless cleanup.empty?
|
|
puts "Would `brew cleanup`:"
|
|
puts cleanup
|
|
end
|
|
|
|
puts "Run `brew bundle cleanup --force` to make these changes." if would_uninstall || !cleanup.empty?
|
|
exit 1 if would_uninstall
|
|
end
|
|
end
|
|
|
|
def self.casks_to_uninstall(global: false, file: nil)
|
|
require "bundle/cask_dumper"
|
|
Homebrew::Bundle::CaskDumper.cask_names - kept_casks(global:, file:)
|
|
end
|
|
|
|
def self.formulae_to_uninstall(global: false, file: nil)
|
|
kept_formulae = self.kept_formulae(global:, file:)
|
|
|
|
require "bundle/formula_dumper"
|
|
require "bundle/formula_installer"
|
|
current_formulae = Homebrew::Bundle::FormulaDumper.formulae
|
|
current_formulae.reject! do |f|
|
|
Homebrew::Bundle::FormulaInstaller.formula_in_array?(f[:full_name], kept_formulae)
|
|
end
|
|
|
|
# Don't try to uninstall formulae with keepme references
|
|
current_formulae.reject! do |f|
|
|
Formula[f[:full_name]].installed_kegs.any? do |keg|
|
|
keg.keepme_refs.present?
|
|
end
|
|
end
|
|
current_formulae.map { |f| f[:full_name] }
|
|
end
|
|
|
|
private_class_method def self.kept_formulae(global: false, file: nil)
|
|
require "bundle/brewfile"
|
|
require "bundle/formula_dumper"
|
|
require "bundle/cask_dumper"
|
|
|
|
@kept_formulae ||= begin
|
|
@dsl ||= Brewfile.read(global:, file:)
|
|
|
|
kept_formulae = @dsl.entries.select { |e| e.type == :brew }.map(&:name)
|
|
kept_formulae += Homebrew::Bundle::CaskDumper.formula_dependencies(kept_casks)
|
|
kept_formulae.map! do |f|
|
|
Homebrew::Bundle::FormulaDumper.formula_aliases.fetch(
|
|
f,
|
|
Homebrew::Bundle::FormulaDumper.formula_oldnames.fetch(f, f),
|
|
)
|
|
end
|
|
|
|
kept_formulae + recursive_dependencies(Homebrew::Bundle::FormulaDumper.formulae, kept_formulae)
|
|
end
|
|
end
|
|
|
|
private_class_method def self.kept_casks(global: false, file: nil)
|
|
require "bundle/brewfile"
|
|
return @kept_casks if @kept_casks
|
|
|
|
@dsl ||= Brewfile.read(global:, file:)
|
|
kept_casks = @dsl.entries.select { |e| e.type == :cask }.flat_map(&:name)
|
|
kept_casks.map! do |c|
|
|
Homebrew::Bundle::CaskDumper.cask_oldnames.fetch(c, c)
|
|
end
|
|
@kept_casks = kept_casks
|
|
end
|
|
|
|
private_class_method def self.recursive_dependencies(current_formulae, formulae_names, top_level: true)
|
|
@checked_formulae_names = [] if top_level
|
|
dependencies = T.let([], T::Array[Formula])
|
|
|
|
formulae_names.each do |name|
|
|
next if @checked_formulae_names.include?(name)
|
|
|
|
formula = current_formulae.find { |f| f[:full_name] == name }
|
|
next unless formula
|
|
|
|
f_deps = formula[:dependencies]
|
|
unless formula[:poured_from_bottle?]
|
|
f_deps += formula[:build_dependencies]
|
|
f_deps.uniq!
|
|
end
|
|
next unless f_deps
|
|
next if f_deps.empty?
|
|
|
|
@checked_formulae_names << name
|
|
f_deps += recursive_dependencies(current_formulae, f_deps, top_level: false)
|
|
dependencies += f_deps
|
|
end
|
|
|
|
dependencies.uniq
|
|
end
|
|
|
|
IGNORED_TAPS = %w[homebrew/core].freeze
|
|
|
|
def self.taps_to_untap(global: false, file: nil)
|
|
require "bundle/brewfile"
|
|
require "bundle/tap_dumper"
|
|
|
|
@dsl ||= Brewfile.read(global:, file:)
|
|
kept_formulae = self.kept_formulae(global:, file:).filter_map(&method(:lookup_formula))
|
|
kept_taps = @dsl.entries.select { |e| e.type == :tap }.map(&:name)
|
|
kept_taps += kept_formulae.filter_map(&:tap).map(&:name)
|
|
current_taps = Homebrew::Bundle::TapDumper.tap_names
|
|
current_taps - kept_taps - IGNORED_TAPS
|
|
end
|
|
|
|
def self.lookup_formula(formula)
|
|
Formulary.factory(formula)
|
|
rescue TapFormulaUnavailableError
|
|
# ignore these as an unavailable formula implies there is no tap to worry about
|
|
nil
|
|
end
|
|
|
|
def self.vscode_extensions_to_uninstall(global: false, file: nil)
|
|
require "bundle/brewfile"
|
|
@dsl ||= Brewfile.read(global:, file:)
|
|
kept_extensions = @dsl.entries.select { |e| e.type == :vscode }.map { |x| x.name.downcase }
|
|
|
|
# To provide a graceful migration from `Brewfile`s that don't yet or
|
|
# don't want to use `vscode`: don't remove any extensions if we don't
|
|
# find any in the `Brewfile`.
|
|
return [].freeze if kept_extensions.empty?
|
|
|
|
require "bundle/vscode_extension_dumper"
|
|
current_extensions = Homebrew::Bundle::VscodeExtensionDumper.extensions
|
|
current_extensions - kept_extensions
|
|
end
|
|
|
|
def self.system_output_no_stderr(cmd, *args)
|
|
IO.popen([cmd, *args], err: :close).read
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|