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 = Caskroom.path if path.exist? && !path.writable? add_error "The staging path #{user_tilde(path.to_s)} is not writable by the current user." end puts user_tilde(path.to_s) 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(Cache.path.to_s) msg << " (#{number_readable(count)} files, #{disk_usage_readable(size)})" unless count.zero? puts msg end def check_taps default_tap = Tap.default_cask_tap alt_taps = Tap.select { |t| t.cask_dir.exist? && t != default_tap } ohai "Homebrew-Cask Taps:" [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 HOMEBREW_CASK_OPTS ] 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 "" end def self.error_string(string = "Error") Formatter.error("(#{string})") end def self.alt_taps Tap.select { |t| t.cask_dir.exist? && t != Tap.default_cask_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_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 def self.help "checks for configuration issues" end end end end