diff --git a/Library/Homebrew/cleanup.rb b/Library/Homebrew/cleanup.rb index ece51108a2..cc690902b6 100644 --- a/Library/Homebrew/cleanup.rb +++ b/Library/Homebrew/cleanup.rb @@ -1,4 +1,4 @@ -# typed: false +# typed: true # frozen_string_literal: true require "utils/bottles" @@ -15,127 +15,129 @@ module Homebrew CLEANUP_DEFAULT_DAYS = Homebrew::EnvConfig.cleanup_periodic_full_days.to_i.freeze private_constant :CLEANUP_DEFAULT_DAYS - # {Pathname} refinement with helper functions for cleaning up files. - module CleanupRefinement - refine Pathname do - def incomplete? - extname.end_with?(".incomplete") - end + class << self + extend T::Sig - def nested_cache? - directory? && %w[ - cargo_cache - go_cache - go_mod_cache - glide_home - java_cache - npm_cache - gclient_cache - ].include?(basename.to_s) - end + sig { params(pathname: Pathname).returns(T::Boolean) } + def incomplete?(pathname) + pathname.extname.end_with?(".incomplete") + end - def go_cache_directory? - # Go makes its cache contents read-only to ensure cache integrity, - # which makes sense but is something we need to undo for cleanup. - directory? && %w[go_cache go_mod_cache].include?(basename.to_s) - end + 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 + gclient_cache + ].include?(pathname.basename.to_s) + end - def prune?(days) - return false unless days - return true if days.zero? + 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 - return true if symlink? && !exist? + 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? - days_ago = (DateTime.now - days).to_time - mtime < days_ago && ctime < days_ago - end + days_ago = (DateTime.now - days).to_time + pathname.mtime < days_ago && pathname.ctime < days_ago + end - def stale?(scrub: false) - return false unless resolved_path.file? + sig { params(pathname: Pathname, scrub: T::Boolean).returns(T::Boolean) } + def stale?(pathname, scrub: false) + return false unless pathname.resolved_path.file? - if dirname.basename.to_s == "Cask" - stale_cask?(scrub) - else - stale_formula?(scrub) - end - end - - private - - def stale_formula?(scrub) - return false unless HOMEBREW_CELLAR.directory? - - version = if HOMEBREW_BOTTLES_EXTNAME_REGEX.match?(to_s) - begin - Utils::Bottles.resolve_version(self) - rescue - nil - end - end - - version ||= basename.to_s[/\A.*(?:--.*?)*--(.*?)#{Regexp.escape(extname)}\Z/, 1] - version ||= basename.to_s[/\A.*--?(.*?)#{Regexp.escape(extname)}\Z/, 1] - - return false unless version - - version = Version.new(version) - - return false unless (formula_name = basename.to_s[/\A(.*?)(?:--.*?)*--?(?:#{Regexp.escape(version)})/, 1]) - - formula = begin - Formulary.from_rack(HOMEBREW_CELLAR/formula_name) - rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError - nil - end - - return false if formula.blank? - - resource_name = basename.to_s[/\A.*?--(.*?)--?(?:#{Regexp.escape(version)})/, 1] - - 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 - elsif version.is_a?(PkgVersion) - return true if formula.pkg_version > version - elsif formula.version > version - return true - end - - return true if scrub && !formula.latest_version_installed? - - return true if Utils::Bottles.file_outdated?(formula, self) - - false - end - - def stale_cask?(scrub) - return false unless (name = basename.to_s[/\A(.*?)--/, 1]) - - cask = begin - Cask::CaskLoader.load(name) - rescue Cask::CaskError - nil - end - - return false if cask.blank? - - return true unless basename.to_s.match?(/\A#{Regexp.escape(name)}--#{Regexp.escape(cask.version)}\b/) - - return true if scrub && cask.versions.exclude?(cask.version) - - if cask.version.latest? - cleanup_threshold = (DateTime.now - CLEANUP_DEFAULT_DAYS).to_time - return mtime < cleanup_threshold && ctime < cleanup_threshold - end - - false + if pathname.dirname.basename.to_s == "Cask" + stale_cask?(pathname, scrub) + else + stale_formula?(pathname, scrub) end end - end - using CleanupRefinement + private + + sig { params(pathname: Pathname, scrub: T::Boolean).returns(T::Boolean) } + def stale_formula?(pathname, scrub) + return false unless HOMEBREW_CELLAR.directory? + + version = if HOMEBREW_BOTTLES_EXTNAME_REGEX.match?(to_s) + begin + Utils::Bottles.resolve_version(pathname) + rescue + nil + end + end + basename_str = pathname.basename.to_s + + version ||= basename_str[/\A.*(?:--.*?)*--(.*?)#{Regexp.escape(pathname.extname)}\Z/, 1] + version ||= basename_str[/\A.*--?(.*?)#{Regexp.escape(pathname.extname)}\Z/, 1] + + return false unless version + + version = Version.new(version) + + unless (formula_name = basename_str[/\A(.*?)(?:--.*?)*--?(?:#{Regexp.escape(version.to_s)})/, 1]) + return false + end + + formula = begin + Formulary.from_rack(HOMEBREW_CELLAR/formula_name) + rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError + nil + end + + return false if formula.blank? + + resource_name = basename_str[/\A.*?--(.*?)--?(?:#{Regexp.escape(version.to_s)})/, 1] + + 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 + elsif formula.version > version + return true + end + + return true if scrub && !formula.latest_version_installed? + return true if Utils::Bottles.file_outdated?(formula, pathname) + + false + end + + 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]) + + cask = begin + Cask::CaskLoader.load(name) + rescue Cask::CaskError + nil + end + + return false if cask.blank? + return true unless basename.to_s.match?(/\A#{Regexp.escape(name)}--#{Regexp.escape(cask.version)}\b/) + return true if scrub && cask.versions.exclude?(cask.version) + + if cask.version.latest? + cleanup_threshold = (DateTime.now - CLEANUP_DEFAULT_DAYS).to_time + return pathname.mtime < cleanup_threshold && pathname.ctime < cleanup_threshold + end + + false + end + end extend Predicable @@ -188,9 +190,10 @@ module Homebrew end def self.skip_clean_formula?(formula) - return false if Homebrew::EnvConfig.no_cleanup_formulae.blank? + no_cleanup_formula = Homebrew::EnvConfig.no_cleanup_formulae + return false if no_cleanup_formula.blank? - @skip_clean_formulae ||= Homebrew::EnvConfig.no_cleanup_formulae.split(",") + @skip_clean_formulae ||= no_cleanup_formula.split(",") @skip_clean_formulae.include?(formula.name) || (@skip_clean_formulae & formula.aliases).present? end @@ -311,7 +314,7 @@ module Homebrew logs_days = [days, CLEANUP_DEFAULT_DAYS].min HOMEBREW_LOGS.subdirs.each do |dir| - cleanup_path(dir) { dir.rmtree } if dir.prune?(logs_days) + cleanup_path(dir) { dir.rmtree } if self.class.prune?(dir, logs_days) end end @@ -327,7 +330,7 @@ module Homebrew .map(&:resolved_path) (downloads - referenced_downloads).each do |download| - if download.incomplete? + if self.class.incomplete?(download) begin LockFile.new(download.basename).with_lock do download.unlink @@ -350,11 +353,11 @@ module Homebrew entries.each do |path| next if path == PERIODIC_CLEAN_FILE - FileUtils.chmod_R 0755, path if path.go_cache_directory? && !dry_run? - next cleanup_path(path) { path.unlink } if path.incomplete? - next cleanup_path(path) { FileUtils.rm_rf path } if path.nested_cache? + 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) - if path.prune?(days) + if self.class.prune?(path, days) if path.file? || path.symlink? cleanup_path(path) { path.unlink } elsif path.directory? && path.to_s.include?("--") @@ -364,7 +367,7 @@ module Homebrew end # If we've specified --prune don't do the (expensive) .stale? check. - cleanup_path(path) { path.unlink } if !prune? && path.stale?(scrub: scrub?) + cleanup_path(path) { path.unlink } if !prune? && self.class.stale?(path, scrub: scrub?) end cleanup_unreferenced_downloads @@ -485,7 +488,7 @@ module Homebrew if child.basename.to_s == "__pycache__" child.find do |path| next unless path.extname == ".pyc" - next unless path.prune?(days) + next unless self.class.prune?(path, days) unused_pyc_files << path end diff --git a/Library/Homebrew/test/cleanup_spec.rb b/Library/Homebrew/test/cleanup_spec.rb index 13a6bde07c..4fee43e3f2 100644 --- a/Library/Homebrew/test/cleanup_spec.rb +++ b/Library/Homebrew/test/cleanup_spec.rb @@ -6,8 +6,6 @@ require "cleanup" require "cask/cache" require "fileutils" -using Homebrew::Cleanup::CleanupRefinement - describe Homebrew::Cleanup do subject(:cleanup) { described_class.new } @@ -27,9 +25,7 @@ describe Homebrew::Cleanup do FileUtils.rm_rf HOMEBREW_LIBRARY/"Homebrew" end - describe "::CleanupRefinement::prune?" do - alias_matcher :be_pruned, :be_prune - + describe "::prune?" do subject(:path) { HOMEBREW_CACHE/"foo" } before do @@ -39,11 +35,11 @@ describe Homebrew::Cleanup do it "returns true when ctime and mtime < days_default" do allow_any_instance_of(Pathname).to receive(:ctime).and_return((DateTime.now - 2).to_time) allow_any_instance_of(Pathname).to receive(:mtime).and_return((DateTime.now - 2).to_time) - expect(path.prune?(1)).to be true + expect(described_class.prune?(path, 1)).to be true end it "returns false when ctime and mtime >= days_default" do - expect(path.prune?(2)).to be false + expect(described_class.prune?(path, 2)).to be false end end