aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/cask/lib/hbc/cli/doctor.rb
blob: 32b927218b6b893dcc250bf3b2626041f05e0005 (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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
require "system_config"
require "hbc/checkable"

module Hbc
  class CLI
    class Doctor < AbstractCommand
      include Checkable

      def initialize(*)
        super
        return if args.empty?
        raise ArgumentError, "#{self.class.command_name} does not take arguments."
      end

      def success?
        !(errors? || warnings?)
      end

      def summary_header
        "Cask's Doctor Checkup"
      end

      def run
        check_software_versions
        check_install_location
        check_staging_location
        check_cached_downloads
        check_taps
        check_load_path
        check_environment_variables

        puts summary unless success?
        raise CaskError, "There are some problems with your setup." unless success?
      end

      def check_software_versions
        ohai "Homebrew-Cask Version", Hbc.full_version
        ohai "macOS", MacOS.full_version
        ohai "SIP", self.class.check_sip
        ohai "Java", SystemConfig.describe_java
      end

      # This could be done by calling into Homebrew, but the situation
      # where "doctor" is needed is precisely the situation where such
      # things are less dependable.
      def check_install_location
        ohai "Homebrew-Cask Install Location"

        locations = Dir.glob(HOMEBREW_CELLAR.join("brew-cask", "*")).reverse
        if locations.empty?
          puts self.class.none_string
        else
          locations.collect do |l|
            add_error "Legacy install at #{l}. Run \"brew uninstall --force brew-cask\"."
            puts l
          end
        end
      end

      def check_staging_location
        ohai "Homebrew-Cask Staging Location"

        path = Pathname.new(user_tilde(Hbc.caskroom.to_s))

        if !path.exist?
          add_error "The staging path #{path} does not exist."
        elsif !path.writable?
          add_error "The staging path #{path} is not writable by the current user."
        end

        puts path
      end

      def check_cached_downloads
        ohai "Homebrew-Cask Cached Downloads"

        cleanup = CLI::Cleanup.new
        count = cleanup.cache_files.count
        size = cleanup.disk_cleanup_size
        msg = user_tilde(Hbc.cache.to_s)
        msg << " (#{number_readable(count)} files, #{disk_usage_readable(size)})" unless count.zero?
        puts msg
      end

      def check_taps
        ohai "Homebrew-Cask Taps:"

        default_tap = [Hbc.default_tap]

        alt_taps = Tap.select { |t| t.cask_dir.exist? && t != Hbc.default_tap }

        (default_tap + alt_taps).each do |tap|
          if tap.path.nil? || tap.path.to_s.empty?
            puts none_string
          else
            puts "#{tap.path} (#{cask_count_for_tap(tap)})"
          end
        end
      end

      def check_load_path
        ohai "Contents of $LOAD_PATH"
        paths = $LOAD_PATH.map(&method(:user_tilde))

        if paths.empty?
          puts none_string
          add_error "$LOAD_PATH is empty"
        else
          puts paths
        end
      end

      def check_environment_variables
        ohai "Environment Variables"

        environment_variables = %w[
          RUBYLIB
          RUBYOPT
          RUBYPATH
          RBENV_VERSION
          CHRUBY_VERSION
          GEM_HOME
          GEM_PATH
          BUNDLE_PATH
          PATH
          SHELL
        ]

        locale_variables = ENV.keys.grep(/^(?:LC_\S+|LANG|LANGUAGE)\Z/).sort

        (locale_variables + environment_variables).sort.each(&method(:render_env_var))
      end

      def user_tilde(path)
        self.class.user_tilde(path)
      end

      def cask_count_for_tap(tap)
        self.class.cask_count_for_tap(tap)
      end

      def none_string
        self.class.none_string
      end

      def render_env_var(var)
        self.class.render_env_var(var)
      end

      def self.check_sip
        csrutil = "/usr/bin/csrutil"
        return "N/A" unless File.executable?(csrutil)
        Open3.capture2(csrutil, "status")[0]
             .gsub("This is an unsupported configuration, likely to break in the future and leave your machine in an unknown state.", "")
             .gsub("System Integrity Protection status: ", "")
             .delete("\t\.").capitalize.strip
      end

      def self.locale_variables
        ENV.keys.grep(/^(?:LC_\S+|LANG|LANGUAGE)\Z/).sort
      end

      def self.none_string
        "<NONE>"
      end

      def self.error_string(string = "Error")
        Formatter.error("(#{string})")
      end

      def self.render_with_none(string)
        return string if !string.nil? && string.respond_to?(:to_s) && !string.to_s.empty?
        none_string
      end

      def self.alt_taps
        Tap.select { |t| t.cask_dir.exist? && t != Hbc.default_tap }
      end

      def self.cask_count_for_tap(tap)
        Formatter.pluralize(tap.cask_files.count, "cask")
      rescue StandardError
        add_error "Unable to read from Tap: #{tap.path}"
        "0"
      end

      def self.render_taps(*taps)
        taps.collect do |tap|
          if tap.path.nil? || tap.path.to_s.empty?
            none_string
          else
            "#{tap.path} (#{cask_count_for_tap(tap)})"
          end
        end
      end

      def self.render_env_var(var)
        return unless ENV.key?(var)
        var = %Q(#{var}="#{ENV[var]}")
        puts user_tilde(var)
      end

      def self.user_tilde(path)
        path.gsub(ENV["HOME"], "~")
      end

      # This could be done by calling into Homebrew, but the situation
      # where "doctor" is needed is precisely the situation where such
      # things are less dependable.
      def self.render_install_location
        locations = Dir.glob(HOMEBREW_CELLAR.join("brew-cask", "*")).reverse
        if locations.empty?
          none_string
        else
          locations.collect do |l|
            "#{l} #{error_string 'error: legacy install. Run "brew uninstall --force brew-cask".'}"
          end
        end
      end

      def self.render_staging_location(path)
        path = Pathname.new(user_tilde(path.to_s))
        if !path.exist?
          "#{path} #{error_string "error: path does not exist"}"
        elsif !path.writable?
          "#{path} #{error_string "error: not writable by current user"}"
        else
          path
        end
      end

      def self.render_load_path(paths)
        paths.map(&method(:user_tilde))
        return "#{none_string} #{error_string}" if [*paths].empty?
        paths
      end

      def self.render_cached_downloads
        cleanup = CLI::Cleanup.new
        count = cleanup.cache_files.count
        size = cleanup.disk_cleanup_size
        msg = user_tilde(Hbc.cache.to_s)
        msg << " (#{number_readable(count)} files, #{disk_usage_readable(size)})" unless count.zero?
        msg
      end

      def self.help
        "checks for configuration issues"
      end
    end
  end
end