# frozen_string_literal: true require "system_config" require "cask/checkable" module Cask class Cmd 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_xattr check_quarantine_support check_install_location check_staging_location 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 Version", HOMEBREW_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 `brew 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.map 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." add_error "To fix, run \'sudo chown -R $(whoami):staff #{user_tilde(path.to_s)}'" end puts user_tilde(path.to_s) 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.blank? 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 check_xattr ohai "xattr issues" result = system_command "/usr/bin/xattr" if result.status.success? puts none_string elsif result.stderr.include? "ImportError: No module named pkg_resources" result = system_command "/usr/bin/python", "--version" if result.stdout.include? "Python 2.7" add_error "Your Python installation has a broken version of setuptools." add_error "To fix, reinstall macOS or run 'sudo /usr/bin/python -m pip install -I setuptools'." else add_error "The system Python version is wrong." add_error "To fix, run 'defaults write com.apple.versioner.python Version 2.7'." end elsif result.stderr.include? "pkg_resources.DistributionNotFound" add_error "Your Python installation is unable to find xattr." else add_error "unknown xattr error: #{result.stderr.split("\n").last}" end end def check_quarantine_support ohai "Gatekeeper support" case Quarantine.check_quarantine_support when :quarantine_available puts "Enabled" when :xattr_broken add_error "There's not a working version of xattr." when :no_swift add_error "Swift is not available on this system." when :no_quarantine add_error "This feature requires the macOS 10.10 SDK or higher." else onoe "Unknown support status" end 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") .first .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) cask_count = begin tap.cask_files.count rescue add_error "Unable to read from Tap: #{tap.path}" 0 end "#{cask_count} #{"cask".pluralize(cask_count)}" 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