diff --git a/Library/Homebrew/cleanup.rb b/Library/Homebrew/cleanup.rb index 716a23dfbb..deadc6bd53 100644 --- a/Library/Homebrew/cleanup.rb +++ b/Library/Homebrew/cleanup.rb @@ -235,6 +235,7 @@ module Homebrew cleanup_cache cleanup_logs cleanup_lockfiles + cleanup_python_site_packages prune_prefix_symlinks_and_directories unless dry_run? @@ -484,6 +485,55 @@ module Homebrew end end + 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| + next unless path.extname == ".pyc" + next unless path.prune?(days) + + 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 + .flatten + return if unused_pyc_files.blank? + + unused_pyc_files.each do |pyc| + cleanup_path(pyc) { pyc.unlink } + end + end + def prune_prefix_symlinks_and_directories ObserverPathnameExtension.reset_counts! diff --git a/Library/Homebrew/test/cleanup_spec.rb b/Library/Homebrew/test/cleanup_spec.rb index 477de25fcd..007b64753e 100644 --- a/Library/Homebrew/test/cleanup_spec.rb +++ b/Library/Homebrew/test/cleanup_spec.rb @@ -364,4 +364,41 @@ describe Homebrew::Cleanup do end end end + + describe "::cleanup_python_site_packages" do + context "when cleaning up Python modules" do + let(:foo_module) { (HOMEBREW_PREFIX/"lib/python3.99/site-packages/foo") } + let(:foo_pycache) { (foo_module/"__pycache__") } + let(:foo_pyc) { (foo_pycache/"foo.cypthon-399.pyc") } + + before do + foo_pycache.mkpath + FileUtils.touch foo_pyc + end + + it "cleans up stray `*.pyc` files" do + cleanup.cleanup_python_site_packages + expect(foo_pyc).not_to exist + end + + it "retains `*.pyc` files of installed modules" do + FileUtils.touch foo_module/"__init__.py" + + cleanup.cleanup_python_site_packages + expect(foo_pyc).to exist + end + end + + it "cleans up stale `*.pyc` files in the top-level `__pycache__`" do + pycache = HOMEBREW_PREFIX/"lib/python3.99/site-packages/__pycache__" + foo_pyc = pycache/"foo.cypthon-3.99.pyc" + pycache.mkpath + FileUtils.touch foo_pyc + + allow_any_instance_of(Pathname).to receive(:ctime).and_return(Time.now - (2 * 60 * 60 * 24)) + allow_any_instance_of(Pathname).to receive(:mtime).and_return(Time.now - (2 * 60 * 60 * 24)) + described_class.new(days: 1).cleanup_python_site_packages + expect(foo_pyc).not_to exist + end + end end