2023-03-25 13:16:11 -07:00
|
|
|
# typed: true
|
2019-04-19 15:38:03 +09:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-04-25 17:57:51 +01:00
|
|
|
require "utils/bottles"
|
2020-12-09 10:36:02 +00:00
|
|
|
|
2023-12-28 11:45:18 -08:00
|
|
|
require "attrable"
|
2015-12-29 12:57:48 +01:00
|
|
|
require "formula"
|
2018-09-03 19:39:07 +01:00
|
|
|
require "cask/cask_loader"
|
2018-08-10 00:54:03 +02:00
|
|
|
require "set"
|
2015-12-29 12:57:48 +01:00
|
|
|
|
2020-08-17 03:52:17 +02:00
|
|
|
module Homebrew
|
|
|
|
# Helper class for cleaning up the Homebrew cache.
|
|
|
|
class Cleanup
|
2020-12-09 10:36:02 +00:00
|
|
|
CLEANUP_DEFAULT_DAYS = Homebrew::EnvConfig.cleanup_periodic_full_days.to_i.freeze
|
2020-08-17 03:52:17 +02:00
|
|
|
private_constant :CLEANUP_DEFAULT_DAYS
|
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
class << self
|
|
|
|
sig { params(pathname: Pathname).returns(T::Boolean) }
|
|
|
|
def incomplete?(pathname)
|
|
|
|
pathname.extname.end_with?(".incomplete")
|
|
|
|
end
|
2018-08-08 22:23:55 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
sig { params(pathname: Pathname).returns(T::Boolean) }
|
|
|
|
def nested_cache?(pathname)
|
|
|
|
pathname.directory? && %w[
|
|
|
|
cargo_cache
|
|
|
|
go_cache
|
|
|
|
go_mod_cache
|
|
|
|
glide_home
|
|
|
|
java_cache
|
|
|
|
npm_cache
|
2023-11-22 13:11:53 -08:00
|
|
|
pip_cache
|
2023-03-27 09:28:27 -07:00
|
|
|
gclient_cache
|
|
|
|
].include?(pathname.basename.to_s)
|
|
|
|
end
|
2018-08-08 09:43:38 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
sig { params(pathname: Pathname).returns(T::Boolean) }
|
|
|
|
def go_cache_directory?(pathname)
|
|
|
|
# Go makes its cache contents read-only to ensure cache integrity,
|
|
|
|
# which makes sense but is something we need to undo for cleanup.
|
|
|
|
pathname.directory? && %w[go_cache go_mod_cache].include?(pathname.basename.to_s)
|
|
|
|
end
|
2018-08-08 09:43:38 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
sig { params(pathname: Pathname, days: T.nilable(Integer)).returns(T::Boolean) }
|
|
|
|
def prune?(pathname, days)
|
|
|
|
return false unless days
|
|
|
|
return true if days.zero?
|
|
|
|
return true if pathname.symlink? && !pathname.exist?
|
2018-08-08 09:43:38 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
days_ago = (DateTime.now - days).to_time
|
|
|
|
pathname.mtime < days_ago && pathname.ctime < days_ago
|
|
|
|
end
|
2018-09-01 06:42:47 +02:00
|
|
|
|
2023-04-18 00:22:13 +01:00
|
|
|
sig { params(entry: { path: Pathname, type: T.nilable(Symbol) }, scrub: T::Boolean).returns(T::Boolean) }
|
|
|
|
def stale?(entry, scrub: false)
|
|
|
|
pathname = entry[:path]
|
2023-03-27 09:28:27 -07:00
|
|
|
return false unless pathname.resolved_path.file?
|
2018-08-08 09:43:38 +02:00
|
|
|
|
2023-04-18 00:22:13 +01:00
|
|
|
case entry[:type]
|
|
|
|
when :api_source
|
|
|
|
stale_api_source?(pathname, scrub)
|
|
|
|
when :cask
|
2023-03-27 09:28:27 -07:00
|
|
|
stale_cask?(pathname, scrub)
|
2023-05-17 13:27:10 +08:00
|
|
|
when :gh_actions_artifact
|
|
|
|
stale_gh_actions_artifact?(pathname, scrub)
|
2023-03-27 09:28:27 -07:00
|
|
|
else
|
|
|
|
stale_formula?(pathname, scrub)
|
2020-08-17 03:52:17 +02:00
|
|
|
end
|
2023-03-27 09:28:27 -07:00
|
|
|
end
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
private
|
2018-08-08 09:43:38 +02:00
|
|
|
|
2023-05-17 13:27:10 +08:00
|
|
|
GH_ACTIONS_ARTIFACT_CLEANUP_DAYS = 3
|
|
|
|
|
|
|
|
sig { params(pathname: Pathname, scrub: T::Boolean).returns(T::Boolean) }
|
|
|
|
def stale_gh_actions_artifact?(pathname, scrub)
|
|
|
|
scrub || prune?(pathname, GH_ACTIONS_ARTIFACT_CLEANUP_DAYS)
|
|
|
|
end
|
|
|
|
|
2023-04-18 00:22:13 +01:00
|
|
|
sig { params(pathname: Pathname, scrub: T::Boolean).returns(T::Boolean) }
|
|
|
|
def stale_api_source?(pathname, scrub)
|
|
|
|
return true if scrub
|
|
|
|
|
|
|
|
org, repo, git_head, type, basename = pathname.each_filename.to_a.last(5)
|
|
|
|
|
|
|
|
name = "#{org}/#{repo}/#{File.basename(T.must(basename), ".rb")}"
|
|
|
|
package = if type == "Cask"
|
|
|
|
begin
|
|
|
|
Cask::CaskLoader.load(name)
|
|
|
|
rescue Cask::CaskError
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
Formulary.factory(name)
|
|
|
|
rescue FormulaUnavailableError
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return true if package.nil?
|
|
|
|
|
|
|
|
package.tap_git_head != git_head
|
|
|
|
end
|
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
sig { params(pathname: Pathname, scrub: T::Boolean).returns(T::Boolean) }
|
|
|
|
def stale_formula?(pathname, scrub)
|
|
|
|
return false unless HOMEBREW_CELLAR.directory?
|
2018-08-08 09:43:38 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
version = if HOMEBREW_BOTTLES_EXTNAME_REGEX.match?(to_s)
|
|
|
|
begin
|
|
|
|
Utils::Bottles.resolve_version(pathname)
|
|
|
|
rescue
|
|
|
|
nil
|
2020-08-17 03:52:17 +02:00
|
|
|
end
|
2023-03-27 09:28:27 -07:00
|
|
|
end
|
|
|
|
basename_str = pathname.basename.to_s
|
2018-08-08 22:23:55 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
version ||= basename_str[/\A.*(?:--.*?)*--(.*?)#{Regexp.escape(pathname.extname)}\Z/, 1]
|
|
|
|
version ||= basename_str[/\A.*--?(.*?)#{Regexp.escape(pathname.extname)}\Z/, 1]
|
2018-08-08 22:23:55 +02:00
|
|
|
|
2023-05-11 13:44:26 +01:00
|
|
|
return false if version.blank?
|
2018-08-08 22:23:55 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
version = Version.new(version)
|
2018-08-08 09:43:38 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
unless (formula_name = basename_str[/\A(.*?)(?:--.*?)*--?(?:#{Regexp.escape(version.to_s)})/, 1])
|
|
|
|
return false
|
|
|
|
end
|
2020-11-10 00:11:22 +11:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
formula = begin
|
|
|
|
Formulary.from_rack(HOMEBREW_CELLAR/formula_name)
|
2024-02-16 21:27:02 +01:00
|
|
|
rescue FormulaUnavailableError, TapFormulaAmbiguityError
|
2023-03-27 09:28:27 -07:00
|
|
|
nil
|
|
|
|
end
|
2018-08-08 09:43:38 +02:00
|
|
|
|
2024-03-30 03:30:58 +00:00
|
|
|
if formula.blank? && formula_name.delete_suffix!("_bottle_manifest")
|
|
|
|
formula = begin
|
|
|
|
Formulary.from_rack(HOMEBREW_CELLAR/formula_name)
|
|
|
|
rescue FormulaUnavailableError, TapFormulaAmbiguityError
|
|
|
|
nil
|
|
|
|
end
|
2024-04-01 14:36:39 +01:00
|
|
|
|
|
|
|
return false if formula.blank?
|
|
|
|
|
|
|
|
# We can't determine an installed rebuild and parsing manifest version cannot be reliably done.
|
|
|
|
return false unless formula.latest_version_installed?
|
|
|
|
|
|
|
|
return true if (bottle = formula.bottle).blank?
|
|
|
|
|
|
|
|
return version != GitHubPackages.version_rebuild(bottle.resource.version, bottle.rebuild)
|
2024-03-30 03:30:58 +00:00
|
|
|
end
|
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
return false if formula.blank?
|
2018-08-08 09:43:38 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
resource_name = basename_str[/\A.*?--(.*?)--?(?:#{Regexp.escape(version.to_s)})/, 1]
|
2018-08-08 09:43:38 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
if resource_name == "patch"
|
|
|
|
patch_hashes = formula.stable&.patches&.select(&:external?)&.map(&:resource)&.map(&:version)
|
|
|
|
return true unless patch_hashes&.include?(Checksum.new(version.to_s))
|
|
|
|
elsif resource_name && (resource_version = formula.stable&.resources&.dig(resource_name)&.version)
|
|
|
|
return true if resource_version != version
|
2024-04-01 14:36:39 +01:00
|
|
|
elsif (formula.latest_version_installed? && formula.pkg_version.to_s != version) ||
|
|
|
|
formula.pkg_version.to_s > version
|
2023-03-27 09:28:27 -07:00
|
|
|
return true
|
2020-08-17 03:52:17 +02:00
|
|
|
end
|
2018-08-08 22:23:55 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
return true if scrub && !formula.latest_version_installed?
|
|
|
|
return true if Utils::Bottles.file_outdated?(formula, pathname)
|
2018-08-08 22:23:55 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
false
|
|
|
|
end
|
2018-08-08 22:23:55 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
sig { params(pathname: Pathname, scrub: T::Boolean).returns(T::Boolean) }
|
|
|
|
def stale_cask?(pathname, scrub)
|
|
|
|
basename = pathname.basename
|
|
|
|
return false unless (name = basename.to_s[/\A(.*?)--/, 1])
|
2018-08-08 22:23:55 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
cask = begin
|
|
|
|
Cask::CaskLoader.load(name)
|
|
|
|
rescue Cask::CaskError
|
|
|
|
nil
|
|
|
|
end
|
2018-08-08 22:23:55 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
return false if cask.blank?
|
|
|
|
return true unless basename.to_s.match?(/\A#{Regexp.escape(name)}--#{Regexp.escape(cask.version)}\b/)
|
2023-04-08 14:10:58 +02:00
|
|
|
return true if scrub && cask.installed_version != cask.version
|
2023-03-27 09:28:27 -07:00
|
|
|
|
|
|
|
if cask.version.latest?
|
|
|
|
cleanup_threshold = (DateTime.now - CLEANUP_DEFAULT_DAYS).to_time
|
|
|
|
return pathname.mtime < cleanup_threshold && pathname.ctime < cleanup_threshold
|
2020-08-17 03:52:17 +02:00
|
|
|
end
|
2023-03-27 09:28:27 -07:00
|
|
|
|
|
|
|
false
|
2020-08-17 03:52:17 +02:00
|
|
|
end
|
2018-08-08 22:23:55 +02:00
|
|
|
end
|
2018-08-08 09:43:38 +02:00
|
|
|
|
2023-12-28 11:45:18 -08:00
|
|
|
extend Attrable
|
2018-08-08 11:20:53 +02:00
|
|
|
|
2019-04-19 21:46:20 +09:00
|
|
|
PERIODIC_CLEAN_FILE = (HOMEBREW_CACHE/".cleaned").freeze
|
2019-01-03 16:23:44 +00:00
|
|
|
|
2020-12-31 13:40:49 +00:00
|
|
|
attr_predicate :dry_run?, :scrub?, :prune?
|
2020-07-07 11:29:33 +01:00
|
|
|
attr_reader :args, :days, :cache, :disk_cleanup_size
|
2018-08-08 11:20:53 +02:00
|
|
|
|
|
|
|
def initialize(*args, dry_run: false, scrub: false, days: nil, cache: HOMEBREW_CACHE)
|
|
|
|
@disk_cleanup_size = 0
|
|
|
|
@args = args
|
|
|
|
@dry_run = dry_run
|
|
|
|
@scrub = scrub
|
2020-12-31 13:40:49 +00:00
|
|
|
@prune = days.present?
|
2020-04-20 10:33:15 +01:00
|
|
|
@days = days || Homebrew::EnvConfig.cleanup_max_age_days.to_i
|
2018-08-08 11:20:53 +02:00
|
|
|
@cache = cache
|
2018-08-10 00:54:03 +02:00
|
|
|
@cleaned_up_paths = Set.new
|
2017-08-13 04:21:07 +05:30
|
|
|
end
|
|
|
|
|
2023-03-10 23:46:07 +00:00
|
|
|
def self.install_formula_clean!(formula, dry_run: false)
|
2020-04-05 15:44:50 +01:00
|
|
|
return if Homebrew::EnvConfig.no_install_cleanup?
|
2023-03-10 23:46:07 +00:00
|
|
|
return unless formula.latest_version_installed?
|
|
|
|
return if skip_clean_formula?(formula)
|
2019-01-03 16:23:44 +00:00
|
|
|
|
2022-07-20 08:24:55 -07:00
|
|
|
if dry_run
|
2023-03-10 23:46:07 +00:00
|
|
|
ohai "Would run `brew cleanup #{formula}`"
|
2022-07-20 08:24:55 -07:00
|
|
|
else
|
2023-03-10 23:46:07 +00:00
|
|
|
ohai "Running `brew cleanup #{formula}`..."
|
2019-01-03 16:23:44 +00:00
|
|
|
end
|
2022-07-20 08:24:55 -07:00
|
|
|
|
|
|
|
puts_no_install_cleanup_disable_message_if_not_already!
|
|
|
|
return if dry_run
|
|
|
|
|
2023-03-10 23:46:07 +00:00
|
|
|
Cleanup.new.cleanup_formula(formula)
|
2019-01-03 16:23:44 +00:00
|
|
|
end
|
|
|
|
|
2022-07-20 08:52:37 -07:00
|
|
|
def self.puts_no_install_cleanup_disable_message
|
2021-11-26 13:14:10 +00:00
|
|
|
return if Homebrew::EnvConfig.no_env_hints?
|
|
|
|
return if Homebrew::EnvConfig.no_install_cleanup?
|
|
|
|
|
|
|
|
puts "Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP."
|
|
|
|
puts "Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`)."
|
2022-07-20 08:52:37 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.puts_no_install_cleanup_disable_message_if_not_already!
|
|
|
|
return if @puts_no_install_cleanup_disable_message_if_not_already
|
|
|
|
|
|
|
|
puts_no_install_cleanup_disable_message
|
2021-11-26 13:14:10 +00:00
|
|
|
@puts_no_install_cleanup_disable_message_if_not_already = true
|
|
|
|
end
|
|
|
|
|
2023-03-10 23:46:07 +00:00
|
|
|
def self.skip_clean_formula?(formula)
|
2023-03-27 09:28:27 -07:00
|
|
|
no_cleanup_formula = Homebrew::EnvConfig.no_cleanup_formulae
|
|
|
|
return false if no_cleanup_formula.blank?
|
2021-08-27 18:00:43 +08:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
@skip_clean_formulae ||= no_cleanup_formula.split(",")
|
2024-01-18 14:11:43 +00:00
|
|
|
@skip_clean_formulae.include?(formula.name) || @skip_clean_formulae.intersect?(formula.aliases)
|
2021-08-27 18:00:43 +08:00
|
|
|
end
|
|
|
|
|
2022-07-20 08:24:55 -07:00
|
|
|
def self.periodic_clean_due?
|
2020-04-05 15:44:50 +01:00
|
|
|
return false if Homebrew::EnvConfig.no_install_cleanup?
|
2020-05-07 09:58:27 +01:00
|
|
|
|
|
|
|
unless PERIODIC_CLEAN_FILE.exist?
|
2020-11-14 10:48:10 -05:00
|
|
|
HOMEBREW_CACHE.mkpath
|
2020-05-07 09:58:27 +01:00
|
|
|
FileUtils.touch PERIODIC_CLEAN_FILE
|
|
|
|
return false
|
|
|
|
end
|
2019-01-03 16:23:44 +00:00
|
|
|
|
2023-02-04 13:51:35 -08:00
|
|
|
PERIODIC_CLEAN_FILE.mtime < (DateTime.now - CLEANUP_DEFAULT_DAYS).to_time
|
2019-01-03 16:23:44 +00:00
|
|
|
end
|
|
|
|
|
2022-07-20 08:24:55 -07:00
|
|
|
def self.periodic_clean!(dry_run: false)
|
|
|
|
return if Homebrew::EnvConfig.no_install_cleanup?
|
|
|
|
return unless periodic_clean_due?
|
2019-01-03 16:23:44 +00:00
|
|
|
|
2022-07-20 08:24:55 -07:00
|
|
|
if dry_run
|
|
|
|
oh1 "Would run `brew cleanup` which has not been run in the last #{CLEANUP_DEFAULT_DAYS} days"
|
2021-08-31 12:27:14 -04:00
|
|
|
else
|
2022-07-20 08:24:55 -07:00
|
|
|
oh1 "`brew cleanup` has not been run in the last #{CLEANUP_DEFAULT_DAYS} days, running now..."
|
2021-08-31 12:27:14 -04:00
|
|
|
end
|
2021-11-25 09:10:59 +00:00
|
|
|
|
2022-07-20 08:52:37 -07:00
|
|
|
puts_no_install_cleanup_disable_message
|
2022-07-20 08:24:55 -07:00
|
|
|
return if dry_run
|
2021-11-25 09:10:59 +00:00
|
|
|
|
2022-07-20 08:24:55 -07:00
|
|
|
Cleanup.new.clean!(quiet: true, periodic: true)
|
2019-01-03 16:23:44 +00:00
|
|
|
end
|
|
|
|
|
2019-02-13 12:56:36 +00:00
|
|
|
def clean!(quiet: false, periodic: false)
|
2018-08-08 11:20:53 +02:00
|
|
|
if args.empty?
|
2021-08-27 18:00:43 +08:00
|
|
|
Formula.installed
|
|
|
|
.sort_by(&:name)
|
2022-07-15 16:27:22 -07:00
|
|
|
.reject { |f| Cleanup.skip_clean_formula?(f) }
|
2021-08-27 18:00:43 +08:00
|
|
|
.each do |formula|
|
2024-03-07 16:20:20 +00:00
|
|
|
cleanup_formula(formula, quiet:, ds_store: false, cache_db: false)
|
2018-08-08 11:20:53 +02:00
|
|
|
end
|
2022-07-14 13:16:26 -07:00
|
|
|
|
|
|
|
Cleanup.autoremove(dry_run: dry_run?) if Homebrew::EnvConfig.autoremove?
|
|
|
|
|
2018-08-08 11:20:53 +02:00
|
|
|
cleanup_cache
|
2023-04-18 00:22:13 +01:00
|
|
|
cleanup_empty_api_source_directories
|
2018-08-08 11:20:53 +02:00
|
|
|
cleanup_logs
|
2018-10-14 00:13:04 +02:00
|
|
|
cleanup_lockfiles
|
2022-08-18 21:09:12 +08:00
|
|
|
cleanup_python_site_packages
|
2019-01-02 13:21:34 +00:00
|
|
|
prune_prefix_symlinks_and_directories
|
2019-02-13 12:56:36 +00:00
|
|
|
|
|
|
|
unless dry_run?
|
2020-09-11 12:56:15 +01:00
|
|
|
cleanup_cache_db
|
2019-02-13 12:56:36 +00:00
|
|
|
rm_ds_store
|
|
|
|
HOMEBREW_CACHE.mkpath
|
|
|
|
FileUtils.touch PERIODIC_CLEAN_FILE
|
|
|
|
end
|
|
|
|
|
|
|
|
# Cleaning up Ruby needs to be done last to avoid requiring additional
|
|
|
|
# files afterwards. Additionally, don't allow it on periodic cleans to
|
|
|
|
# avoid having to try to do a `brew install` when we've just deleted
|
|
|
|
# the running Ruby process...
|
|
|
|
return if periodic
|
2019-02-19 13:12:52 +00:00
|
|
|
|
2019-02-13 12:56:36 +00:00
|
|
|
cleanup_portable_ruby
|
2021-02-02 11:51:08 +00:00
|
|
|
cleanup_bootsnap
|
2018-08-08 11:20:53 +02:00
|
|
|
else
|
|
|
|
args.each do |arg|
|
|
|
|
formula = begin
|
2018-09-11 17:44:18 +02:00
|
|
|
Formulary.resolve(arg)
|
2024-02-16 21:27:02 +01:00
|
|
|
rescue FormulaUnavailableError, TapFormulaAmbiguityError
|
2018-08-08 11:20:53 +02:00
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
|
|
|
cask = begin
|
2018-09-06 08:29:14 +02:00
|
|
|
Cask::CaskLoader.load(arg)
|
2019-11-06 13:33:33 +00:00
|
|
|
rescue Cask::CaskError
|
2018-08-08 11:20:53 +02:00
|
|
|
nil
|
|
|
|
end
|
2017-03-21 04:13:13 -05:00
|
|
|
|
2022-07-15 16:27:22 -07:00
|
|
|
if formula && Cleanup.skip_clean_formula?(formula)
|
2021-08-27 18:00:43 +08:00
|
|
|
onoe "Refusing to clean #{formula} because it is listed in " \
|
|
|
|
"#{Tty.bold}HOMEBREW_NO_CLEANUP_FORMULAE#{Tty.reset}!"
|
|
|
|
elsif formula
|
|
|
|
cleanup_formula(formula)
|
|
|
|
end
|
2018-08-08 11:20:53 +02:00
|
|
|
cleanup_cask(cask) if cask
|
|
|
|
end
|
|
|
|
end
|
2015-12-29 12:57:48 +01:00
|
|
|
end
|
|
|
|
|
2017-03-21 04:13:13 -05:00
|
|
|
def unremovable_kegs
|
|
|
|
@unremovable_kegs ||= []
|
|
|
|
end
|
|
|
|
|
2020-09-11 12:56:15 +01:00
|
|
|
def cleanup_formula(formula, quiet: false, ds_store: true, cache_db: true)
|
2024-03-07 16:20:20 +00:00
|
|
|
formula.eligible_kegs_for_cleanup(quiet:)
|
2024-04-08 09:47:06 -07:00
|
|
|
.each { cleanup_keg(_1) }
|
2024-03-30 03:30:58 +00:00
|
|
|
cleanup_cache(Pathname.glob(cache/"#{formula.name}{_bottle_manifest,}--*").map { |path| { path:, type: nil } })
|
2019-09-18 11:39:40 +01:00
|
|
|
rm_ds_store([formula.rack]) if ds_store
|
2020-09-11 12:56:15 +01:00
|
|
|
cleanup_cache_db(formula.rack) if cache_db
|
2018-08-09 16:46:39 +02:00
|
|
|
cleanup_lockfiles(FormulaLock.new(formula.name).path)
|
2015-12-29 12:57:48 +01:00
|
|
|
end
|
|
|
|
|
2019-09-18 11:39:40 +01:00
|
|
|
def cleanup_cask(cask, ds_store: true)
|
2024-03-07 16:20:20 +00:00
|
|
|
cleanup_cache(Pathname.glob(cache/"Cask/#{cask.token}--*").map { |path| { path:, type: :cask } })
|
2019-09-18 11:39:40 +01:00
|
|
|
rm_ds_store([cask.caskroom_path]) if ds_store
|
2018-08-09 16:46:39 +02:00
|
|
|
cleanup_lockfiles(CaskLock.new(cask.token).path)
|
2018-08-08 22:23:55 +02:00
|
|
|
end
|
2018-08-08 11:20:53 +02:00
|
|
|
|
2017-03-21 04:13:13 -05:00
|
|
|
def cleanup_keg(keg)
|
2021-01-21 13:03:52 +00:00
|
|
|
cleanup_path(keg) { keg.uninstall(raise_failures: true) }
|
2021-10-19 23:39:58 +09:00
|
|
|
rescue Errno::EACCES, Errno::ENOTEMPTY => e
|
2017-03-21 04:13:13 -05:00
|
|
|
opoo e.message
|
|
|
|
unremovable_kegs << keg
|
|
|
|
end
|
|
|
|
|
|
|
|
def cleanup_logs
|
2015-12-29 12:57:48 +01:00
|
|
|
return unless HOMEBREW_LOGS.directory?
|
2019-02-19 13:12:52 +00:00
|
|
|
|
2023-01-02 19:18:51 +00:00
|
|
|
logs_days = [days, CLEANUP_DEFAULT_DAYS].min
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2015-12-29 12:57:48 +01:00
|
|
|
HOMEBREW_LOGS.subdirs.each do |dir|
|
2023-03-27 09:28:27 -07:00
|
|
|
cleanup_path(dir) { dir.rmtree } if self.class.prune?(dir, logs_days)
|
2015-12-29 12:57:48 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-04-18 00:22:13 +01:00
|
|
|
def cache_files
|
|
|
|
files = cache.directory? ? cache.children : []
|
|
|
|
cask_files = (cache/"Cask").directory? ? (cache/"Cask").children : []
|
|
|
|
api_source_files = (cache/"api-source").glob("*/*/*/*/*") # org/repo/git_head/type/file.rb
|
2023-05-17 13:27:10 +08:00
|
|
|
gh_actions_artifacts = (cache/"gh-actions-artifact").directory? ? (cache/"gh-actions-artifact").children : []
|
2023-04-18 00:22:13 +01:00
|
|
|
|
2024-03-07 16:20:20 +00:00
|
|
|
files.map { |path| { path:, type: nil } } +
|
|
|
|
cask_files.map { |path| { path:, type: :cask } } +
|
|
|
|
api_source_files.map { |path| { path:, type: :api_source } } +
|
|
|
|
gh_actions_artifacts.map { |path| { path:, type: :gh_actions_artifact } }
|
2023-04-18 00:22:13 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def cleanup_empty_api_source_directories(directory = cache/"api-source")
|
|
|
|
return if dry_run?
|
|
|
|
return unless directory.directory?
|
|
|
|
|
|
|
|
directory.each_child do |child|
|
|
|
|
next unless child.directory?
|
|
|
|
|
|
|
|
cleanup_empty_api_source_directories(child)
|
|
|
|
child.rmdir if child.empty?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-08-10 04:11:54 +02:00
|
|
|
def cleanup_unreferenced_downloads
|
|
|
|
return if dry_run?
|
|
|
|
return unless (cache/"downloads").directory?
|
|
|
|
|
2018-10-14 00:13:04 +02:00
|
|
|
downloads = (cache/"downloads").children
|
|
|
|
|
2023-04-18 00:22:13 +01:00
|
|
|
referenced_downloads = cache_files.map { |file| file[:path] }.select(&:symlink?).map(&:resolved_path)
|
2018-08-10 04:11:54 +02:00
|
|
|
|
2018-10-14 00:13:04 +02:00
|
|
|
(downloads - referenced_downloads).each do |download|
|
2023-03-27 09:28:27 -07:00
|
|
|
if self.class.incomplete?(download)
|
2018-10-14 00:13:04 +02:00
|
|
|
begin
|
|
|
|
LockFile.new(download.basename).with_lock do
|
|
|
|
download.unlink
|
|
|
|
end
|
|
|
|
rescue OperationInProgressError
|
|
|
|
# Skip incomplete downloads which are still in progress.
|
|
|
|
next
|
|
|
|
end
|
2019-01-28 16:08:23 +00:00
|
|
|
elsif download.directory?
|
|
|
|
FileUtils.rm_rf download
|
2018-10-14 00:13:04 +02:00
|
|
|
else
|
|
|
|
download.unlink
|
|
|
|
end
|
|
|
|
end
|
2018-08-10 04:11:54 +02:00
|
|
|
end
|
|
|
|
|
2018-08-08 22:23:55 +02:00
|
|
|
def cleanup_cache(entries = nil)
|
2023-04-18 00:22:13 +01:00
|
|
|
entries ||= cache_files
|
2018-08-08 22:23:55 +02:00
|
|
|
|
2023-04-18 00:22:13 +01:00
|
|
|
entries.each do |entry|
|
|
|
|
path = entry[:path]
|
2019-04-17 21:06:47 +09:00
|
|
|
next if path == PERIODIC_CLEAN_FILE
|
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
FileUtils.chmod_R 0755, path if self.class.go_cache_directory?(path) && !dry_run?
|
|
|
|
next cleanup_path(path) { path.unlink } if self.class.incomplete?(path)
|
|
|
|
next cleanup_path(path) { FileUtils.rm_rf path } if self.class.nested_cache?(path)
|
2018-08-08 09:43:38 +02:00
|
|
|
|
2023-03-27 09:28:27 -07:00
|
|
|
if self.class.prune?(path, days)
|
2018-08-25 22:06:24 +02:00
|
|
|
if path.file? || path.symlink?
|
2015-12-29 12:57:48 +01:00
|
|
|
cleanup_path(path) { path.unlink }
|
|
|
|
elsif path.directory? && path.to_s.include?("--")
|
|
|
|
cleanup_path(path) { FileUtils.rm_rf path }
|
|
|
|
end
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
2021-05-08 11:20:01 +10:00
|
|
|
# If we've specified --prune don't do the (expensive) .stale? check.
|
2023-04-18 00:22:13 +01:00
|
|
|
cleanup_path(path) { path.unlink } if !prune? && self.class.stale?(entry, scrub: scrub?)
|
2015-12-29 12:57:48 +01:00
|
|
|
end
|
2018-08-10 04:11:54 +02:00
|
|
|
|
|
|
|
cleanup_unreferenced_downloads
|
2015-12-29 12:57:48 +01:00
|
|
|
end
|
|
|
|
|
2017-03-21 04:13:13 -05:00
|
|
|
def cleanup_path(path)
|
2020-07-10 09:32:27 +01:00
|
|
|
return unless path.exist?
|
2018-08-10 00:54:03 +02:00
|
|
|
return unless @cleaned_up_paths.add?(path)
|
|
|
|
|
2022-03-07 12:25:27 -05:00
|
|
|
@disk_cleanup_size += path.disk_usage
|
2018-08-08 09:43:38 +02:00
|
|
|
|
2018-08-08 11:20:53 +02:00
|
|
|
if dry_run?
|
2015-12-29 12:57:48 +01:00
|
|
|
puts "Would remove: #{path} (#{path.abv})"
|
|
|
|
else
|
|
|
|
puts "Removing: #{path}... (#{path.abv})"
|
|
|
|
yield
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-08-09 16:46:39 +02:00
|
|
|
def cleanup_lockfiles(*lockfiles)
|
2018-08-16 05:55:17 +02:00
|
|
|
return if dry_run?
|
2018-08-09 16:46:39 +02:00
|
|
|
|
2019-02-19 13:11:32 +00:00
|
|
|
lockfiles = HOMEBREW_LOCKS.children.select(&:file?) if lockfiles.empty? && HOMEBREW_LOCKS.directory?
|
2018-08-09 16:46:39 +02:00
|
|
|
|
2015-12-29 12:57:48 +01:00
|
|
|
lockfiles.each do |file|
|
|
|
|
next unless file.readable?
|
2018-08-09 16:46:39 +02:00
|
|
|
next unless file.open(File::RDWR).flock(File::LOCK_EX | File::LOCK_NB)
|
2018-08-11 17:23:35 +02:00
|
|
|
|
|
|
|
begin
|
2018-08-16 05:55:17 +02:00
|
|
|
file.unlink
|
2018-08-11 17:23:35 +02:00
|
|
|
ensure
|
|
|
|
file.open(File::RDWR).flock(File::LOCK_UN) if file.exist?
|
|
|
|
end
|
2015-12-29 12:57:48 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-10-03 19:26:49 +01:00
|
|
|
def cleanup_portable_ruby
|
2020-09-03 09:43:41 +01:00
|
|
|
vendor_dir = HOMEBREW_LIBRARY/"Homebrew/vendor"
|
2023-11-29 15:27:50 +00:00
|
|
|
portable_ruby_latest_version = (vendor_dir/"portable-ruby-version").read.chomp
|
2020-09-03 09:43:41 +01:00
|
|
|
|
2020-01-16 16:04:24 +00:00
|
|
|
portable_rubies_to_remove = []
|
2020-09-03 09:43:41 +01:00
|
|
|
Pathname.glob(vendor_dir/"portable-ruby/*.*").select(&:directory?).each do |path|
|
2022-11-20 10:49:53 -08:00
|
|
|
next if !use_system_ruby? && portable_ruby_latest_version == path.basename.to_s
|
2019-02-19 13:12:52 +00:00
|
|
|
|
2020-01-16 16:04:24 +00:00
|
|
|
portable_rubies_to_remove << path
|
2018-10-03 19:26:49 +01:00
|
|
|
end
|
|
|
|
|
2020-01-16 16:04:24 +00:00
|
|
|
return if portable_rubies_to_remove.empty?
|
2018-10-03 19:26:49 +01:00
|
|
|
|
2023-11-07 22:28:03 +00:00
|
|
|
bundler_paths = (vendor_dir/"bundle/ruby").children.select do |child|
|
|
|
|
basename = child.basename.to_s
|
|
|
|
|
|
|
|
next false if basename == ".homebrew_gem_groups"
|
|
|
|
next true unless child.directory?
|
|
|
|
|
|
|
|
[
|
|
|
|
"#{Version.new(portable_ruby_latest_version).major_minor}.0",
|
|
|
|
RbConfig::CONFIG["ruby_version"],
|
|
|
|
].uniq.exclude?(basename)
|
|
|
|
end
|
|
|
|
|
|
|
|
bundler_paths.each do |bundler_path|
|
|
|
|
if dry_run?
|
|
|
|
puts Utils.popen_read("git", "-C", HOMEBREW_REPOSITORY, "clean", "-nx", bundler_path).chomp
|
|
|
|
else
|
|
|
|
puts Utils.popen_read("git", "-C", HOMEBREW_REPOSITORY, "clean", "-ffqx", bundler_path).chomp
|
|
|
|
end
|
2018-10-03 19:26:49 +01:00
|
|
|
end
|
2020-01-16 16:04:24 +00:00
|
|
|
|
2022-08-18 21:25:17 +08:00
|
|
|
portable_rubies_to_remove.each do |portable_ruby|
|
|
|
|
cleanup_path(portable_ruby) { portable_ruby.rmtree }
|
|
|
|
end
|
2018-10-03 19:26:49 +01:00
|
|
|
end
|
|
|
|
|
2023-10-26 22:03:54 +01:00
|
|
|
def use_system_ruby?
|
|
|
|
false
|
|
|
|
end
|
2022-11-20 10:49:53 -08:00
|
|
|
|
2021-02-02 11:51:08 +00:00
|
|
|
def cleanup_bootsnap
|
|
|
|
bootsnap = cache/"bootsnap"
|
|
|
|
return unless bootsnap.exist?
|
|
|
|
|
2022-08-18 21:25:17 +08:00
|
|
|
cleanup_path(bootsnap) { bootsnap.rmtree }
|
2021-02-02 11:51:08 +00:00
|
|
|
end
|
|
|
|
|
2020-09-11 12:56:15 +01:00
|
|
|
def cleanup_cache_db(rack = nil)
|
2018-10-13 08:22:51 -07:00
|
|
|
FileUtils.rm_rf [
|
|
|
|
cache/"desc_cache.json",
|
|
|
|
cache/"linkage.db",
|
|
|
|
cache/"linkage.db.db",
|
|
|
|
]
|
2020-09-11 12:56:15 +01:00
|
|
|
|
|
|
|
CacheStoreDatabase.use(:linkage) do |db|
|
|
|
|
break unless db.created?
|
|
|
|
|
|
|
|
db.each_key do |keg|
|
|
|
|
next if rack.present? && !keg.start_with?("#{rack}/")
|
|
|
|
next if File.directory?(keg)
|
|
|
|
|
|
|
|
LinkageCacheStore.new(keg, db).delete!
|
|
|
|
end
|
|
|
|
end
|
2018-10-03 19:26:49 +01:00
|
|
|
end
|
|
|
|
|
2018-08-09 01:55:43 +02:00
|
|
|
def rm_ds_store(dirs = nil)
|
2021-03-26 14:11:03 +00:00
|
|
|
dirs ||= Keg::MUST_EXIST_DIRECTORIES + [
|
|
|
|
HOMEBREW_PREFIX/"Caskroom",
|
|
|
|
]
|
2019-09-18 11:39:40 +01:00
|
|
|
dirs.select(&:directory?)
|
|
|
|
.flat_map { |d| Pathname.glob("#{d}/**/.DS_Store") }
|
2021-04-23 14:49:12 +01:00
|
|
|
.each do |dir|
|
|
|
|
dir.unlink
|
|
|
|
rescue Errno::EACCES
|
|
|
|
# don't care if we can't delete a .DS_Store
|
|
|
|
nil
|
|
|
|
end
|
2015-12-29 12:57:48 +01:00
|
|
|
end
|
2019-01-02 13:21:34 +00:00
|
|
|
|
2022-08-18 21:09:12 +08:00
|
|
|
def cleanup_python_site_packages
|
|
|
|
pyc_files = Hash.new { |h, k| h[k] = [] }
|
|
|
|
seen_non_pyc_file = Hash.new { |h, k| h[k] = false }
|
|
|
|
unused_pyc_files = []
|
|
|
|
|
|
|
|
HOMEBREW_PREFIX.glob("lib/python*/site-packages").each do |site_packages|
|
|
|
|
site_packages.each_child do |child|
|
|
|
|
next unless child.directory?
|
|
|
|
# TODO: Work out a sensible way to clean up pip's, setuptools', and wheel's
|
|
|
|
# {dist,site}-info directories. Alternatively, consider always removing
|
|
|
|
# all `-info` directories, because we may not be making use of them.
|
|
|
|
next if child.basename.to_s.end_with?("-info")
|
|
|
|
|
|
|
|
# Clean up old *.pyc files in the top-level __pycache__.
|
|
|
|
if child.basename.to_s == "__pycache__"
|
|
|
|
child.find do |path|
|
2023-04-18 15:06:50 -07:00
|
|
|
next if path.extname != ".pyc"
|
2023-03-27 09:28:27 -07:00
|
|
|
next unless self.class.prune?(path, days)
|
2022-08-18 21:09:12 +08:00
|
|
|
|
|
|
|
unused_pyc_files << path
|
|
|
|
end
|
|
|
|
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
# Look for directories that contain only *.pyc files.
|
|
|
|
child.find do |path|
|
|
|
|
next if path.directory?
|
|
|
|
|
|
|
|
if path.extname == ".pyc"
|
|
|
|
pyc_files[child] << path
|
|
|
|
else
|
|
|
|
seen_non_pyc_file[child] = true
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
unused_pyc_files += pyc_files.reject { |k,| seen_non_pyc_file[k] }
|
|
|
|
.values
|
2022-08-31 16:04:21 +08:00
|
|
|
.flatten
|
2022-08-18 21:09:12 +08:00
|
|
|
return if unused_pyc_files.blank?
|
|
|
|
|
|
|
|
unused_pyc_files.each do |pyc|
|
|
|
|
cleanup_path(pyc) { pyc.unlink }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-01-02 13:21:34 +00:00
|
|
|
def prune_prefix_symlinks_and_directories
|
|
|
|
ObserverPathnameExtension.reset_counts!
|
|
|
|
|
|
|
|
dirs = []
|
2024-03-10 15:27:30 -04:00
|
|
|
children_count = {}
|
2019-01-02 13:21:34 +00:00
|
|
|
|
|
|
|
Keg::MUST_EXIST_SUBDIRECTORIES.each do |dir|
|
|
|
|
next unless dir.directory?
|
|
|
|
|
|
|
|
dir.find do |path|
|
|
|
|
path.extend(ObserverPathnameExtension)
|
|
|
|
if path.symlink?
|
2022-01-25 12:10:50 +00:00
|
|
|
unless path.resolved_path_exists?
|
2020-09-01 14:05:52 +01:00
|
|
|
path.uninstall_info if path.to_s.match?(Keg::INFOFILE_RX) && !dry_run?
|
2019-01-02 13:21:34 +00:00
|
|
|
|
|
|
|
if dry_run?
|
|
|
|
puts "Would remove (broken link): #{path}"
|
2024-03-10 15:27:30 -04:00
|
|
|
children_count[path.dirname] -= 1 if children_count.key?(path.dirname)
|
2019-01-02 13:21:34 +00:00
|
|
|
else
|
|
|
|
path.unlink
|
|
|
|
end
|
|
|
|
end
|
2020-12-01 17:04:59 +00:00
|
|
|
elsif path.directory? && Keg::MUST_EXIST_SUBDIRECTORIES.exclude?(path)
|
2019-01-02 13:21:34 +00:00
|
|
|
dirs << path
|
2024-03-10 15:27:30 -04:00
|
|
|
children_count[path] = path.children.length if dry_run?
|
2019-01-02 13:21:34 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
dirs.reverse_each do |d|
|
2024-03-10 15:27:30 -04:00
|
|
|
if !dry_run?
|
2019-01-02 13:21:34 +00:00
|
|
|
d.rmdir_if_possible
|
2024-03-10 15:27:30 -04:00
|
|
|
elsif children_count[d].zero?
|
|
|
|
puts "Would remove (empty directory): #{d}"
|
|
|
|
children_count[d.dirname] -= 1 if children_count.key?(d.dirname)
|
2019-01-02 13:21:34 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-03-10 19:25:00 -04:00
|
|
|
require "cask/caskroom"
|
|
|
|
if Cask::Caskroom.path.directory?
|
|
|
|
Cask::Caskroom.path.each_child do |path|
|
|
|
|
path.extend(ObserverPathnameExtension)
|
|
|
|
next if !path.symlink? || path.resolved_path_exists?
|
|
|
|
|
|
|
|
if dry_run?
|
|
|
|
puts "Would remove (broken link): #{path}"
|
|
|
|
else
|
|
|
|
path.unlink
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-01-02 13:21:34 +00:00
|
|
|
return if dry_run?
|
|
|
|
|
|
|
|
return if ObserverPathnameExtension.total.zero?
|
|
|
|
|
|
|
|
n, d = ObserverPathnameExtension.counts
|
|
|
|
print "Pruned #{n} symbolic links "
|
|
|
|
print "and #{d} directories " if d.positive?
|
|
|
|
puts "from #{HOMEBREW_PREFIX}"
|
|
|
|
end
|
2022-07-14 13:16:26 -07:00
|
|
|
|
|
|
|
def self.autoremove(dry_run: false)
|
2022-09-13 23:23:48 -07:00
|
|
|
require "utils/autoremove"
|
2022-07-15 16:27:22 -07:00
|
|
|
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
|
|
|
|
|
2022-08-21 12:37:38 -07:00
|
|
|
formulae = Formula.installed
|
|
|
|
# Remove formulae listed in HOMEBREW_NO_CLEANUP_FORMULAE and their dependencies.
|
|
|
|
if Homebrew::EnvConfig.no_cleanup_formulae.present?
|
2024-04-08 09:47:06 -07:00
|
|
|
formulae -= formulae.select { skip_clean_formula?(_1) }
|
2022-08-21 12:37:38 -07:00
|
|
|
.flat_map { |f| [f, *f.runtime_formula_dependencies] }
|
|
|
|
end
|
2022-07-15 16:27:22 -07:00
|
|
|
casks = Cask::Caskroom.casks
|
|
|
|
|
2022-09-13 23:23:48 -07:00
|
|
|
removable_formulae = Utils::Autoremove.removable_formulae(formulae, casks)
|
2022-07-14 13:16:26 -07:00
|
|
|
|
|
|
|
return if removable_formulae.blank?
|
|
|
|
|
|
|
|
formulae_names = removable_formulae.map(&:full_name).sort
|
|
|
|
|
|
|
|
verb = dry_run ? "Would autoremove" : "Autoremoving"
|
2023-02-27 20:49:02 -08:00
|
|
|
oh1 "#{verb} #{formulae_names.count} unneeded #{Utils.pluralize("formula", formulae_names.count, plural: "e")}:"
|
2022-07-14 13:16:26 -07:00
|
|
|
puts formulae_names.join("\n")
|
|
|
|
return if dry_run
|
|
|
|
|
|
|
|
require "uninstall"
|
|
|
|
|
2024-04-28 03:23:21 +02:00
|
|
|
kegs_by_rack = removable_formulae.filter_map(&:any_installed_keg).group_by(&:rack)
|
2022-07-14 13:16:26 -07:00
|
|
|
Uninstall.uninstall_kegs(kegs_by_rack)
|
2022-07-15 16:27:22 -07:00
|
|
|
|
|
|
|
# The installed formula cache will be invalid after uninstalling.
|
|
|
|
Formula.clear_cache
|
2022-07-14 13:16:26 -07:00
|
|
|
end
|
2015-12-29 12:57:48 +01:00
|
|
|
end
|
|
|
|
end
|
2022-11-20 10:49:53 -08:00
|
|
|
|
|
|
|
require "extend/os/cleanup"
|