cleanup: move code away from cmd/
Closes Homebrew/homebrew#47484. Signed-off-by: Baptiste Fontaine <batifon@yahoo.fr>
This commit is contained in:
parent
d7b6230aed
commit
9bdd6619e2
156
Library/Homebrew/cleanup.rb
Normal file
156
Library/Homebrew/cleanup.rb
Normal file
@ -0,0 +1,156 @@
|
||||
require "bottles"
|
||||
require "formula"
|
||||
require "thread"
|
||||
|
||||
module Homebrew
|
||||
module Cleanup
|
||||
@@disk_cleanup_size = 0
|
||||
|
||||
def self.cleanup
|
||||
cleanup_cellar
|
||||
cleanup_cache
|
||||
cleanup_logs
|
||||
unless ARGV.dry_run?
|
||||
cleanup_lockfiles
|
||||
rm_DS_Store
|
||||
end
|
||||
end
|
||||
|
||||
def self.update_disk_cleanup_size(path_size)
|
||||
@@disk_cleanup_size += path_size
|
||||
end
|
||||
|
||||
def self.disk_cleanup_size
|
||||
@@disk_cleanup_size
|
||||
end
|
||||
|
||||
def self.cleanup_formula(formula)
|
||||
formula.eligible_kegs_for_cleanup.each do |keg|
|
||||
cleanup_path(keg) { keg.uninstall }
|
||||
end
|
||||
end
|
||||
|
||||
def self.cleanup_logs
|
||||
return unless HOMEBREW_LOGS.directory?
|
||||
HOMEBREW_LOGS.subdirs.each do |dir|
|
||||
cleanup_path(dir) { dir.rmtree } if prune?(dir, :days_default => 14)
|
||||
end
|
||||
end
|
||||
|
||||
def self.cleanup_cellar
|
||||
Formula.installed.each do |formula|
|
||||
cleanup_formula formula
|
||||
end
|
||||
end
|
||||
|
||||
def self.cleanup_cache
|
||||
return unless HOMEBREW_CACHE.directory?
|
||||
HOMEBREW_CACHE.children.each do |path|
|
||||
if path.to_s.end_with? ".incomplete"
|
||||
cleanup_path(path) { path.unlink }
|
||||
next
|
||||
end
|
||||
if path.basename.to_s == "java_cache" && path.directory?
|
||||
cleanup_path(path) { FileUtils.rm_rf path }
|
||||
next
|
||||
end
|
||||
if prune?(path)
|
||||
if path.file?
|
||||
cleanup_path(path) { path.unlink }
|
||||
elsif path.directory? && path.to_s.include?("--")
|
||||
cleanup_path(path) { FileUtils.rm_rf path }
|
||||
end
|
||||
next
|
||||
end
|
||||
|
||||
next unless path.file?
|
||||
file = path
|
||||
|
||||
if Pathname::BOTTLE_EXTNAME_RX === file.to_s
|
||||
version = bottle_resolve_version(file) rescue file.version
|
||||
else
|
||||
version = file.version
|
||||
end
|
||||
next unless version
|
||||
next unless (name = file.basename.to_s[/(.*)-(?:#{Regexp.escape(version)})/, 1])
|
||||
|
||||
next unless HOMEBREW_CELLAR.directory?
|
||||
|
||||
begin
|
||||
f = Formulary.from_rack(HOMEBREW_CELLAR/name)
|
||||
rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError
|
||||
next
|
||||
end
|
||||
|
||||
file_is_stale = if PkgVersion === version
|
||||
f.pkg_version > version
|
||||
else
|
||||
f.version > version
|
||||
end
|
||||
|
||||
if file_is_stale || ARGV.switch?("s") && !f.installed? || bottle_file_outdated?(f, file)
|
||||
cleanup_path(file) { file.unlink }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.cleanup_path(path)
|
||||
if ARGV.dry_run?
|
||||
puts "Would remove: #{path} (#{path.abv})"
|
||||
else
|
||||
puts "Removing: #{path}... (#{path.abv})"
|
||||
yield
|
||||
end
|
||||
|
||||
update_disk_cleanup_size(path.disk_usage)
|
||||
end
|
||||
|
||||
def self.cleanup_lockfiles
|
||||
return unless HOMEBREW_CACHE_FORMULA.directory?
|
||||
candidates = HOMEBREW_CACHE_FORMULA.children
|
||||
lockfiles = candidates.select { |f| f.file? && f.extname == ".brewing" }
|
||||
lockfiles.each do |file|
|
||||
next unless file.readable?
|
||||
file.open.flock(File::LOCK_EX | File::LOCK_NB) && file.unlink
|
||||
end
|
||||
end
|
||||
|
||||
def self.rm_DS_Store
|
||||
paths = Queue.new
|
||||
%w[Cellar Frameworks Library bin etc include lib opt sbin share var].
|
||||
map { |p| HOMEBREW_PREFIX/p }.each { |p| paths << p if p.exist? }
|
||||
workers = (0...Hardware::CPU.cores).map do
|
||||
Thread.new do
|
||||
begin
|
||||
while p = paths.pop(true)
|
||||
quiet_system "find", p, "-name", ".DS_Store", "-delete"
|
||||
end
|
||||
rescue ThreadError # ignore empty queue error
|
||||
end
|
||||
end
|
||||
end
|
||||
workers.map(&:join)
|
||||
end
|
||||
|
||||
def self.prune?(path, options = {})
|
||||
@time ||= Time.now
|
||||
|
||||
path_modified_time = path.mtime
|
||||
days_default = options[:days_default]
|
||||
|
||||
prune = ARGV.value "prune"
|
||||
|
||||
return true if prune == "all"
|
||||
|
||||
prune_time = if prune
|
||||
@time - 60 * 60 * 24 * prune.to_i
|
||||
elsif days_default
|
||||
@time - 60 * 60 * 24 * days_default.to_i
|
||||
end
|
||||
|
||||
return false unless prune_time
|
||||
|
||||
path_modified_time < prune_time
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,31 +1,16 @@
|
||||
require "formula"
|
||||
require "keg"
|
||||
require "bottles"
|
||||
require "thread"
|
||||
require "cleanup"
|
||||
require "utils"
|
||||
|
||||
module Homebrew
|
||||
@@disk_cleanup_size = 0
|
||||
|
||||
def update_disk_cleanup_size(path_size)
|
||||
@@disk_cleanup_size += path_size
|
||||
end
|
||||
|
||||
def cleanup
|
||||
if ARGV.named.empty?
|
||||
cleanup_cellar
|
||||
cleanup_cache
|
||||
cleanup_logs
|
||||
unless ARGV.dry_run?
|
||||
cleanup_lockfiles
|
||||
rm_DS_Store
|
||||
end
|
||||
Cleanup.cleanup
|
||||
else
|
||||
ARGV.resolved_formulae.each { |f| cleanup_formula(f) }
|
||||
ARGV.resolved_formulae.each { |f| Cleanup.cleanup_formula f }
|
||||
end
|
||||
|
||||
if @@disk_cleanup_size > 0
|
||||
disk_space = disk_usage_readable(@@disk_cleanup_size)
|
||||
if Cleanup.disk_cleanup_size > 0
|
||||
disk_space = disk_usage_readable(Cleanup.disk_cleanup_size)
|
||||
if ARGV.dry_run?
|
||||
ohai "This operation would free approximately #{disk_space} of disk space."
|
||||
else
|
||||
@ -33,168 +18,4 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup_logs
|
||||
return unless HOMEBREW_LOGS.directory?
|
||||
HOMEBREW_LOGS.subdirs.each do |dir|
|
||||
cleanup_path(dir) { dir.rmtree } if prune?(dir, :days_default => 14)
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup_cellar
|
||||
Formula.installed.each do |formula|
|
||||
cleanup_formula formula
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup_formula(f)
|
||||
if f.installed?
|
||||
eligible_kegs = f.installed_kegs.select { |k| f.pkg_version > k.version }
|
||||
if eligible_kegs.any? && eligible_for_cleanup?(f)
|
||||
eligible_kegs.each { |keg| cleanup_keg(keg) }
|
||||
else
|
||||
eligible_kegs.each { |keg| opoo "Skipping (old) keg-only: #{keg}" }
|
||||
end
|
||||
elsif f.installed_prefixes.any? && !f.pinned?
|
||||
# If the cellar only has one version installed, don't complain
|
||||
# that we can't tell which one to keep. Don't complain at all if the
|
||||
# only installed version is a pinned formula.
|
||||
opoo "Skipping #{f.full_name}: most recent version #{f.pkg_version} not installed"
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup_keg(keg)
|
||||
if keg.linked?
|
||||
opoo "Skipping (old) #{keg} due to it being linked"
|
||||
else
|
||||
cleanup_path(keg) { keg.uninstall }
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup_cache
|
||||
return unless HOMEBREW_CACHE.directory?
|
||||
HOMEBREW_CACHE.children.each do |path|
|
||||
if path.to_s.end_with? ".incomplete"
|
||||
cleanup_path(path) { path.unlink }
|
||||
next
|
||||
end
|
||||
if path.basename.to_s == "java_cache" && path.directory?
|
||||
cleanup_path(path) { FileUtils.rm_rf path }
|
||||
next
|
||||
end
|
||||
if prune?(path)
|
||||
if path.file?
|
||||
cleanup_path(path) { path.unlink }
|
||||
elsif path.directory? && path.to_s.include?("--")
|
||||
cleanup_path(path) { FileUtils.rm_rf path }
|
||||
end
|
||||
next
|
||||
end
|
||||
|
||||
next unless path.file?
|
||||
file = path
|
||||
|
||||
if Pathname::BOTTLE_EXTNAME_RX === file.to_s
|
||||
version = bottle_resolve_version(file) rescue file.version
|
||||
else
|
||||
version = file.version
|
||||
end
|
||||
next unless version
|
||||
next unless (name = file.basename.to_s[/(.*)-(?:#{Regexp.escape(version)})/, 1])
|
||||
|
||||
next unless HOMEBREW_CELLAR.directory?
|
||||
|
||||
begin
|
||||
f = Formulary.from_rack(HOMEBREW_CELLAR/name)
|
||||
rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError
|
||||
next
|
||||
end
|
||||
|
||||
file_is_stale = if PkgVersion === version
|
||||
f.pkg_version > version
|
||||
else
|
||||
f.version > version
|
||||
end
|
||||
|
||||
if file_is_stale || ARGV.switch?("s") && !f.installed? || bottle_file_outdated?(f, file)
|
||||
cleanup_path(file) { file.unlink }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup_path(path)
|
||||
if ARGV.dry_run?
|
||||
puts "Would remove: #{path} (#{path.abv})"
|
||||
else
|
||||
puts "Removing: #{path}... (#{path.abv})"
|
||||
yield
|
||||
end
|
||||
update_disk_cleanup_size(path.disk_usage)
|
||||
end
|
||||
|
||||
def cleanup_lockfiles
|
||||
return unless HOMEBREW_CACHE_FORMULA.directory?
|
||||
candidates = HOMEBREW_CACHE_FORMULA.children
|
||||
lockfiles = candidates.select { |f| f.file? && f.extname == ".brewing" }
|
||||
lockfiles.each do |file|
|
||||
next unless file.readable?
|
||||
file.open.flock(File::LOCK_EX | File::LOCK_NB) && file.unlink
|
||||
end
|
||||
end
|
||||
|
||||
def rm_DS_Store
|
||||
paths = Queue.new
|
||||
%w[Cellar Frameworks Library bin etc include lib opt sbin share var].
|
||||
map { |p| HOMEBREW_PREFIX/p }.each { |p| paths << p if p.exist? }
|
||||
workers = (0...Hardware::CPU.cores).map do
|
||||
Thread.new do
|
||||
begin
|
||||
while p = paths.pop(true)
|
||||
quiet_system "find", p, "-name", ".DS_Store", "-delete"
|
||||
end
|
||||
rescue ThreadError # ignore empty queue error
|
||||
end
|
||||
end
|
||||
end
|
||||
workers.map(&:join)
|
||||
end
|
||||
|
||||
def prune?(path, options = {})
|
||||
@time ||= Time.now
|
||||
|
||||
path_modified_time = path.mtime
|
||||
days_default = options[:days_default]
|
||||
|
||||
prune = ARGV.value "prune"
|
||||
|
||||
return true if prune == "all"
|
||||
|
||||
prune_time = if prune
|
||||
@time - 60 * 60 * 24 * prune.to_i
|
||||
elsif days_default
|
||||
@time - 60 * 60 * 24 * days_default.to_i
|
||||
end
|
||||
|
||||
return false unless prune_time
|
||||
|
||||
path_modified_time < prune_time
|
||||
end
|
||||
|
||||
def eligible_for_cleanup?(formula)
|
||||
# It used to be the case that keg-only kegs could not be cleaned up, because
|
||||
# older brews were built against the full path to the keg-only keg. Then we
|
||||
# introduced the opt symlink, and built against that instead. So provided
|
||||
# no brew exists that was built against an old-style keg-only keg, we can
|
||||
# remove it.
|
||||
if !formula.keg_only? || ARGV.force?
|
||||
true
|
||||
elsif formula.opt_prefix.directory?
|
||||
# SHA records were added to INSTALL_RECEIPTS the same day as opt symlinks
|
||||
Formula.installed.select do |f|
|
||||
f.deps.any? do |d|
|
||||
d.to_formula.full_name == formula.full_name rescue d.name == formula.name
|
||||
end
|
||||
end.all? { |f| f.installed_prefixes.all? { |keg| Tab.for_keg(keg).HEAD } }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
require "cmd/install"
|
||||
require "cmd/cleanup"
|
||||
require "cleanup"
|
||||
|
||||
module Homebrew
|
||||
def upgrade
|
||||
@ -44,7 +44,9 @@ module Homebrew
|
||||
|
||||
outdated.each do |f|
|
||||
upgrade_formula(f)
|
||||
cleanup_formula(f) if ARGV.include?("--cleanup") && f.installed?
|
||||
next unless ARGV.include?("--cleanup")
|
||||
next unless f.installed?
|
||||
Homebrew::Cleanup.cleanup_formula f
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -1475,6 +1475,50 @@ class Formula
|
||||
end
|
||||
end
|
||||
|
||||
# @private
|
||||
def eligible_kegs_for_cleanup
|
||||
eligible_for_cleanup = []
|
||||
if installed?
|
||||
eligible_kegs = installed_kegs.select { |k| pkg_version > k.version }
|
||||
if eligible_kegs.any? && eligible_for_cleanup?
|
||||
eligible_kegs.each do |keg|
|
||||
if keg.linked?
|
||||
opoo "Skipping (old) #{keg} due to it being linked"
|
||||
else
|
||||
eligible_for_cleanup << keg
|
||||
end
|
||||
end
|
||||
else
|
||||
eligible_kegs.each { |keg| opoo "Skipping (old) keg-only: #{keg}" }
|
||||
end
|
||||
elsif installed_prefixes.any? && !pinned?
|
||||
# If the cellar only has one version installed, don't complain
|
||||
# that we can't tell which one to keep. Don't complain at all if the
|
||||
# only installed version is a pinned formula.
|
||||
opoo "Skipping #{full_name}: most recent version #{f.pkg_version} not installed"
|
||||
end
|
||||
eligible_for_cleanup
|
||||
end
|
||||
|
||||
# @private
|
||||
def eligible_for_cleanup?
|
||||
# It used to be the case that keg-only kegs could not be cleaned up, because
|
||||
# older brews were built against the full path to the keg-only keg. Then we
|
||||
# introduced the opt symlink, and built against that instead. So provided
|
||||
# no brew exists that was built against an old-style keg-only keg, we can
|
||||
# remove it.
|
||||
if !keg_only? || ARGV.force?
|
||||
true
|
||||
elsif opt_prefix.directory?
|
||||
# SHA records were added to INSTALL_RECEIPTS the same day as opt symlinks
|
||||
Formula.installed.select do |f|
|
||||
f.deps.any? do |d|
|
||||
d.to_formula.full_name == full_name rescue d.name == name
|
||||
end
|
||||
end.all? { |f| f.installed_prefixes.all? { |keg| Tab.for_keg(keg).HEAD } }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def exec_cmd(cmd, args, out, logfn)
|
||||
|
||||
76
Library/Homebrew/test/test_cleanup.rb
Normal file
76
Library/Homebrew/test/test_cleanup.rb
Normal file
@ -0,0 +1,76 @@
|
||||
require "testing_env"
|
||||
require "testball"
|
||||
require "cleanup"
|
||||
require "fileutils"
|
||||
require "pathname"
|
||||
|
||||
class CleanupTests < Homebrew::TestCase
|
||||
def setup
|
||||
@ds_store = Pathname.new "#{HOMEBREW_PREFIX}/Library/.DS_Store"
|
||||
FileUtils.touch @ds_store
|
||||
end
|
||||
|
||||
def teardown
|
||||
FileUtils.rm_f @ds_store
|
||||
ARGV.delete "--dry-run"
|
||||
ARGV.delete "--prune=all"
|
||||
end
|
||||
|
||||
def test_cleanup
|
||||
shutup { Homebrew::Cleanup.cleanup }
|
||||
refute_predicate @ds_store, :exist?
|
||||
end
|
||||
|
||||
def test_cleanup_dry_run
|
||||
ARGV << "--dry-run"
|
||||
shutup { Homebrew::Cleanup.cleanup }
|
||||
assert_predicate @ds_store, :exist?
|
||||
end
|
||||
|
||||
def test_cleanup_formula
|
||||
f1 = Class.new(Testball) { version "0.1" }.new
|
||||
f2 = Class.new(Testball) { version "0.2" }.new
|
||||
f3 = Class.new(Testball) { version "0.3" }.new
|
||||
|
||||
shutup do
|
||||
f1.brew { f1.install }
|
||||
f2.brew { f2.install }
|
||||
f3.brew { f3.install }
|
||||
end
|
||||
|
||||
assert_predicate f1, :installed?
|
||||
assert_predicate f2, :installed?
|
||||
assert_predicate f3, :installed?
|
||||
|
||||
shutup { Homebrew::Cleanup.cleanup_formula f3 }
|
||||
|
||||
refute_predicate f1, :installed?
|
||||
refute_predicate f2, :installed?
|
||||
assert_predicate f3, :installed?
|
||||
ensure
|
||||
[f1, f2, f3].each(&:clear_cache)
|
||||
f3.rack.rmtree
|
||||
end
|
||||
|
||||
def test_cleanup_logs
|
||||
path = (HOMEBREW_LOGS/"delete_me")
|
||||
path.mkpath
|
||||
ARGV << "--prune=all"
|
||||
shutup { Homebrew::Cleanup.cleanup_logs }
|
||||
refute_predicate path, :exist?
|
||||
end
|
||||
|
||||
def test_cleanup_cache_incomplete_downloads
|
||||
incomplete = (HOMEBREW_CACHE/"something.incomplete")
|
||||
incomplete.mkpath
|
||||
shutup { Homebrew::Cleanup.cleanup_cache }
|
||||
refute_predicate incomplete, :exist?
|
||||
end
|
||||
|
||||
def test_cleanup_cache_java_cache
|
||||
java_cache = (HOMEBREW_CACHE/"java_cache")
|
||||
java_cache.mkpath
|
||||
shutup { Homebrew::Cleanup.cleanup_cache }
|
||||
refute_predicate java_cache, :exist?
|
||||
end
|
||||
end
|
||||
@ -1,30 +0,0 @@
|
||||
require "testing_env"
|
||||
require "testball"
|
||||
require "cmd/cleanup"
|
||||
|
||||
class CleanupTests < Homebrew::TestCase
|
||||
def test_cleanup
|
||||
f1 = Class.new(Testball) { version "0.1" }.new
|
||||
f2 = Class.new(Testball) { version "0.2" }.new
|
||||
f3 = Class.new(Testball) { version "0.3" }.new
|
||||
|
||||
shutup do
|
||||
f1.brew { f1.install }
|
||||
f2.brew { f2.install }
|
||||
f3.brew { f3.install }
|
||||
end
|
||||
|
||||
assert_predicate f1, :installed?
|
||||
assert_predicate f2, :installed?
|
||||
assert_predicate f3, :installed?
|
||||
|
||||
shutup { Homebrew.cleanup_formula(f3) }
|
||||
|
||||
refute_predicate f1, :installed?
|
||||
refute_predicate f2, :installed?
|
||||
assert_predicate f3, :installed?
|
||||
ensure
|
||||
[f1, f2, f3].each(&:clear_cache)
|
||||
f3.rack.rmtree
|
||||
end
|
||||
end
|
||||
@ -1,5 +1,6 @@
|
||||
require "testing_env"
|
||||
require "testball"
|
||||
require "formula"
|
||||
|
||||
class FormulaTests < Homebrew::TestCase
|
||||
def test_formula_instantiation
|
||||
@ -345,4 +346,25 @@ class FormulaTests < Homebrew::TestCase
|
||||
assert h.is_a?(Hash), "Formula#to_hash should return a Hash"
|
||||
assert h["versions"]["bottle"], "The hash should say the formula is bottled"
|
||||
end
|
||||
|
||||
def test_eligible_kegs_for_cleanup
|
||||
f1 = Class.new(Testball) { version "0.1" }.new
|
||||
f2 = Class.new(Testball) { version "0.2" }.new
|
||||
f3 = Class.new(Testball) { version "0.3" }.new
|
||||
|
||||
shutup do
|
||||
f1.brew { f1.install }
|
||||
f2.brew { f2.install }
|
||||
f3.brew { f3.install }
|
||||
end
|
||||
|
||||
assert_predicate f1, :installed?
|
||||
assert_predicate f2, :installed?
|
||||
assert_predicate f3, :installed?
|
||||
|
||||
assert_equal f3.installed_kegs[0..1], f3.eligible_kegs_for_cleanup
|
||||
ensure
|
||||
[f1, f2, f3].each(&:clear_cache)
|
||||
f3.rack.rmtree
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user