cleanup: move code away from cmd/

Closes Homebrew/homebrew#47484.

Signed-off-by: Baptiste Fontaine <batifon@yahoo.fr>
This commit is contained in:
Baptiste Fontaine 2015-12-29 12:57:48 +01:00
parent d7b6230aed
commit 9bdd6619e2
7 changed files with 307 additions and 216 deletions

156
Library/Homebrew/cleanup.rb Normal file
View 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

View File

@ -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

View File

@ -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

View File

@ -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)

View 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

View File

@ -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

View File

@ -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