aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/cleanup.rb
blob: 960bcb8b7bf12cfc1bdd29dabdc871e8549cc00c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
require "utils/bottles"
require "formula"

module Homebrew
  module Cleanup
    @disk_cleanup_size = 0

    class << self
      attr_reader :disk_cleanup_size
    end

    module_function

    def cleanup
      cleanup_cellar
      cleanup_cache
      cleanup_logs
      return if ARGV.dry_run?
      cleanup_lockfiles
      rm_ds_store
    end

    def update_disk_cleanup_size(path_size)
      @disk_cleanup_size += path_size
    end

    def unremovable_kegs
      @unremovable_kegs ||= []
    end

    def cleanup_cellar(formulae = Formula.installed)
      formulae.each(&method(:cleanup_formula))
    end

    def cleanup_formula(formula)
      formula.eligible_kegs_for_cleanup.each(&method(:cleanup_keg))
    end

    def cleanup_keg(keg)
      cleanup_path(keg) { keg.uninstall }
    rescue Errno::EACCES => e
      opoo e.message
      unremovable_kegs << keg
    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_cache(cache = HOMEBREW_CACHE)
      return unless cache.directory?
      cache.children.each do |path|
        if path.to_s.end_with? ".incomplete"
          cleanup_path(path) { path.unlink }
          next
        end
        if %w[glide_home java_cache npm_cache].include?(path.basename.to_s) && 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 file.to_s =~ Pathname::BOTTLE_EXTNAME_RX
          version = begin
                      Utils::Bottles.resolve_version(file)
                    rescue
                      file.version
                    end
        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 version.is_a?(PkgVersion)
          f.pkg_version > version
        else
          f.version > version
        end

        if file_is_stale || ARGV.switch?("s") && !f.installed? || Utils::Bottles.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_LOCK_DIR.directory?
      candidates = HOMEBREW_LOCK_DIR.children
      lockfiles  = candidates.select(&:file?)
      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
          Kernel.loop do
            begin
              quiet_system "find", paths.deq(true), "-name", ".DS_Store", "-delete"
            rescue ThreadError
              break # if queue is empty
            end
          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
  end
end