diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index b1a788ba37..0c2e7b2935 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "keg" @@ -16,7 +16,12 @@ require "system_command" module Homebrew # Module containing diagnostic checks. module Diagnostic - def self.missing_deps(formulae, hide = []) + sig { + params(formulae: T::Array[Formula], hide: T::Array[String], _block: T.nilable( + T.proc.params(formula_name: String, missing_dependencies: T::Array[Formula]).void, + )).returns(T::Hash[String, T::Array[String]]) + } + def self.missing_deps(formulae, hide = [], &_block) missing = {} formulae.each do |f| missing_dependencies = f.missing_dependencies(hide: hide) @@ -28,8 +33,9 @@ module Homebrew missing end + sig { params(type: Symbol, fatal: T::Boolean).void } def self.checks(type, fatal: true) - @checks ||= Checks.new + @checks ||= T.let(Checks.new, T.nilable(Checks)) failed = T.let(false, T::Boolean) @checks.public_send(type).each do |check| out = @checks.public_send(check) @@ -49,14 +55,21 @@ module Homebrew class Checks include SystemCommand::Mixin + sig { params(verbose: T::Boolean).void } def initialize(verbose: true) - @verbose = verbose + @verbose = T.let(verbose, T::Boolean) + @found = T.let([], T::Array[String]) + @seen_prefix_bin = T.let(false, T::Boolean) + @seen_prefix_sbin = T.let(false, T::Boolean) + @user_path_1_done = T.let(false, T::Boolean) + @non_core_taps = T.let([], T.nilable(T::Array[Tap])) end ############# @!group HELPERS # Finds files in `HOMEBREW_PREFIX` *and* /usr/local. # Specify paths relative to a prefix, e.g. "include/foo.h". # Sets @found for your convenience. + sig { params(relative_paths: T.any(String, T::Array[String])).void } def find_relative_paths(*relative_paths) @found = [HOMEBREW_PREFIX, "/usr/local"].uniq.reduce([]) do |found, prefix| found + relative_paths.map { |f| File.join(prefix, f) }.select { |f| File.exist? f } @@ -69,44 +82,52 @@ module Homebrew .freeze end + sig { params(path: String).returns(String) } def user_tilde(path) path.gsub(Dir.home, "~") end - sig { returns(String) } + sig { returns(T.nilable(String)) } def none_string "" end + sig { params(args: T.anything).returns(T.nilable(String)) } def add_info(*args) ohai(*args) if @verbose end ############# @!endgroup END HELPERS + sig { returns(T::Array[String]) } def fatal_preinstall_checks %w[ check_access_directories ].freeze end + sig { returns(T::Array[String]) } def fatal_build_from_source_checks %w[ check_for_installed_developer_tools ].freeze end + sig { returns(T::Array[String]) } def fatal_setup_build_environment_checks [].freeze end + sig { returns(T::Array[String]) } def supported_configuration_checks [].freeze end + sig { returns(T::Array[String]) } def build_from_source_checks [].freeze end + sig { returns(T::Array[String]) } def build_error_checks supported_configuration_checks + build_from_source_checks end @@ -156,6 +177,7 @@ module Homebrew end end + sig { params(tap: Tap).returns(T.nilable(String)) } def broken_tap(tap) return unless Utils::Git.available? @@ -177,6 +199,7 @@ module Homebrew message end + sig { returns(T.nilable(String)) } def check_for_installed_developer_tools return if DevelopmentTools.installed? @@ -186,6 +209,7 @@ module Homebrew EOS end + sig { params(dir: String, pattern: String, allow_list: T::Array[String], message: String).returns(T.nilable(String)) } def __check_stray_files(dir, pattern, allow_list, message) return unless File.directory?(dir) @@ -203,6 +227,7 @@ module Homebrew inject_file_list(files, message) end + sig { returns(T.nilable(String)) } def check_for_stray_dylibs # Dylibs which are generally OK should be added to this list, # with a short description of the software they come with. @@ -236,6 +261,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_for_stray_static_libs # Static libs which are generally OK should be added to this list, # with a short description of the software they come with. @@ -261,6 +287,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_for_stray_pcs # Package-config files which are generally OK should be added to this list, # with a short description of the software they come with. @@ -282,6 +309,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_for_stray_las allow_list = [ "libfuse.la", # MacFuse @@ -303,6 +331,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_for_stray_headers allow_list = [ "fuse.h", # MacFuse @@ -323,6 +352,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_for_broken_symlinks broken_symlinks = [] @@ -340,6 +370,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_tmpdir_sticky_bit world_writable = HOMEBREW_TEMP.stat.mode & 0777 == 0777 return if !world_writable || HOMEBREW_TEMP.sticky? @@ -351,6 +382,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_exist_directories return if HOMEBREW_PREFIX.writable? @@ -367,6 +399,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_access_directories not_writable_dirs = Keg.must_be_writable_directories.select(&:exist?) @@ -385,6 +418,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_multiple_cellars return if HOMEBREW_PREFIX.to_s == HOMEBREW_REPOSITORY.to_s return unless (HOMEBREW_REPOSITORY/"Cellar").exist? @@ -397,6 +431,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_user_path_1 @seen_prefix_bin = false @seen_prefix_sbin = false @@ -436,8 +471,9 @@ module Homebrew message unless message.empty? end + sig { returns(T.nilable(String)) } def check_user_path_2 - check_user_path_1 unless defined?(@user_path_1_done) + check_user_path_1 unless @user_path_1_done return if @seen_prefix_bin <<~EOS @@ -447,8 +483,9 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_user_path_3 - check_user_path_1 unless defined?(@user_path_1_done) + check_user_path_1 unless @user_path_1_done return if @seen_prefix_sbin # Don't complain about sbin not being in the path if it doesn't exist @@ -465,6 +502,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_for_symlinked_cellar return unless HOMEBREW_CELLAR.exist? return unless HOMEBREW_CELLAR.symlink? @@ -484,6 +522,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_git_version minimum_version = ENV.fetch("HOMEBREW_MINIMUM_GIT_VERSION") return unless Utils::Git.available? @@ -499,6 +538,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_for_git return if Utils::Git.available? @@ -510,6 +550,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_git_newline_settings return unless Utils::Git.available? @@ -528,11 +569,13 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_brew_git_origin repo = GitRepository.new(HOMEBREW_REPOSITORY) examine_git_origin(repo, Homebrew::EnvConfig.brew_git_remote) end + sig { returns(T.nilable(String)) } def check_coretap_integrity core_tap = CoreTap.instance unless core_tap.installed? @@ -544,6 +587,7 @@ module Homebrew broken_tap(core_tap) || examine_git_origin(core_tap.git_repository, Homebrew::EnvConfig.core_git_remote) end + sig { returns(T.nilable(String)) } def check_casktap_integrity core_cask_tap = CoreCaskTap.instance return unless core_cask_tap.installed? @@ -571,6 +615,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_deprecated_official_taps tapped_deprecated_taps = Tap.select(&:official?).map(&:repository) & DEPRECATED_OFFICIAL_TAPS @@ -587,6 +632,7 @@ module Homebrew EOS end + sig { params(formula: Formula).returns(T::Boolean) } def __check_linked_brew!(formula) formula.installed_prefixes.each do |prefix| prefix.find do |src| @@ -600,6 +646,7 @@ module Homebrew false end + sig { returns(T.nilable(String)) } def check_for_other_frameworks # Other frameworks that are known to cause problems when present frameworks_to_check = %w[ @@ -619,6 +666,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_tmpdir tmpdir = ENV.fetch("TMPDIR", nil) return if tmpdir.nil? || File.directory?(tmpdir) @@ -628,6 +676,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_missing_deps return unless HOMEBREW_CELLAR.exist? @@ -646,6 +695,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_deprecated_disabled return unless HOMEBREW_CELLAR.exist? @@ -660,6 +710,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_cask_deprecated_disabled deprecated_or_disabled = Cask::Caskroom.casks.select(&:deprecated?) deprecated_or_disabled += Cask::Caskroom.casks.select(&:disabled?) @@ -716,6 +767,7 @@ module Homebrew message end + sig { returns(T.nilable(String)) } def check_for_non_prefixed_coreutils coreutils = Formula["coreutils"] return unless coreutils.any_version_installed? @@ -730,6 +782,7 @@ module Homebrew nil end + sig { returns(T.nilable(String)) } def check_for_pydistutils_cfg_in_home return unless File.exist? "#{Dir.home}/.pydistutils.cfg" @@ -741,6 +794,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_for_unreadable_installed_formula formula_unavailable_exceptions = [] Formula.racks.each do |rack| @@ -759,6 +813,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_for_unlinked_but_not_keg_only unlinked = Formula.racks.reject do |rack| next true if (HOMEBREW_LINKED_KEGS/rack.basename).directory? @@ -778,6 +833,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_for_external_cmd_name_conflict cmds = Commands.tap_cmd_directories.flat_map { |p| Dir["#{p}/brew-*"] }.uniq cmds = cmds.select { |cmd| File.file?(cmd) && File.executable?(cmd) } @@ -805,6 +861,7 @@ module Homebrew message end + sig { returns(T.nilable(String)) } def check_for_tap_ruby_files_locations bad_tap_files = {} Tap.installed.each do |tap| @@ -831,6 +888,7 @@ module Homebrew end.join("\n") end + sig { returns(T.nilable(String)) } def check_homebrew_prefix return if Homebrew.default_prefix? @@ -844,6 +902,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_deleted_formula kegs = Keg.all @@ -878,6 +937,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_for_unnecessary_core_tap return if Homebrew::EnvConfig.developer? return if Homebrew::EnvConfig.no_install_from_api? @@ -892,6 +952,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_for_unnecessary_cask_tap return if Homebrew::EnvConfig.developer? return if Homebrew::EnvConfig.no_install_from_api? @@ -908,6 +969,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_deprecated_cask_taps tapped_caskroom_taps = ::Tap.select { |t| t.user == "caskroom" || t.name == "phinze/cask" } .map(&:name) @@ -921,12 +983,14 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_cask_software_versions add_info "Homebrew Version", HOMEBREW_VERSION nil end + sig { returns(T.nilable(String)) } def check_cask_install_location locations = Dir.glob(HOMEBREW_CELLAR.join("brew-cask", "*")).reverse return if locations.empty? @@ -936,6 +1000,7 @@ module Homebrew end.join "\n" end + sig { returns(T.nilable(String)) } def check_cask_staging_location # Skip this check when running CI since the staging path is not writable for security reasons return if GitHub::Actions.env_set? @@ -953,6 +1018,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_cask_taps error_tap_paths = [] @@ -975,6 +1041,7 @@ module Homebrew "Unable to read from cask #{taps_string}: #{error_tap_paths.to_sentence}" if error_tap_paths.present? end + sig { returns(T.nilable(String)) } def check_cask_load_path paths = $LOAD_PATH.map { user_tilde(_1) } @@ -983,6 +1050,7 @@ module Homebrew "$LOAD_PATH is empty" if paths.blank? end + sig { returns(T.nilable(String)) } def check_cask_environment_variables environment_variables = %w[ RUBYLIB @@ -1011,6 +1079,7 @@ module Homebrew nil end + sig { returns(T.nilable(String)) } def check_cask_xattr # If quarantine is not available, a warning is already shown by check_cask_quarantine_support so just return return unless Cask::Quarantine.available? @@ -1043,10 +1112,12 @@ module Homebrew end end + sig { returns(T::Array[Tap]) } def non_core_taps @non_core_taps ||= Tap.installed.reject(&:core_tap?).reject(&:core_cask_tap?) end + sig { returns(T.nilable(String)) } def check_for_duplicate_formulae return if ENV["HOMEBREW_TEST_BOT"].present? @@ -1074,6 +1145,7 @@ module Homebrew EOS end + sig { returns(T.nilable(String)) } def check_for_duplicate_casks return if ENV["HOMEBREW_TEST_BOT"].present? @@ -1101,14 +1173,17 @@ module Homebrew EOS end + sig { returns(T::Array[String]) } def all methods.map(&:to_s).grep(/^check_/).sort end + sig { returns(T::Array[String]) } def cask_checks all.grep(/^check_cask_/) end + sig { returns(String) } def current_user ENV.fetch("USER", "$(whoami)") end diff --git a/Library/Homebrew/extend/os/linux/diagnostic.rb b/Library/Homebrew/extend/os/linux/diagnostic.rb index d9e2073996..92e8f03a86 100644 --- a/Library/Homebrew/extend/os/linux/diagnostic.rb +++ b/Library/Homebrew/extend/os/linux/diagnostic.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:disable Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "tempfile" @@ -16,6 +16,7 @@ module OS requires_ancestor { Homebrew::Diagnostic::Checks } + sig { returns(T::Array[String]) } def fatal_preinstall_checks %w[ check_access_directories @@ -24,6 +25,7 @@ module OS ].freeze end + sig { returns(T::Array[String]) } def supported_configuration_checks %w[ check_glibc_minimum_version @@ -32,6 +34,7 @@ module OS ].freeze end + sig { returns(T.nilable(String)) } def check_tmpdir_sticky_bit message = super return if message.nil? @@ -45,6 +48,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_tmpdir_executable f = Tempfile.new(%w[homebrew_check_tmpdir_executable .sh], HOMEBREW_TEMP) f.write "#!/bin/sh\n" @@ -63,6 +67,7 @@ module OS f&.unlink end + sig { returns(T.nilable(String)) } def check_umask_not_zero return unless File.umask.zero? @@ -74,6 +79,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_supported_architecture return if ::Hardware::CPU.intel? return if Homebrew::EnvConfig.developer? && ENV["HOMEBREW_ARM64_TESTING"].present? && ::Hardware::CPU.arm? @@ -86,6 +92,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_glibc_minimum_version return unless OS::Linux::Glibc.below_minimum_version? @@ -101,6 +108,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_glibc_version return unless OS::Linux::Glibc.below_ci_version? @@ -119,6 +127,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_kernel_minimum_version return unless OS::Linux::Kernel.below_minimum_version? @@ -135,6 +144,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_linuxbrew_core return unless Homebrew::EnvConfig.no_install_from_api? return unless CoreTap.instance.linuxbrew_core? @@ -145,6 +155,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_linuxbrew_bottle_domain return unless Homebrew::EnvConfig.bottle_domain.include?("linuxbrew") @@ -155,6 +166,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_for_symlinked_home return unless File.symlink?("/home") @@ -176,6 +188,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_gcc_dependent_linkage gcc_dependents = ::Formula.installed.select do |formula| next false unless formula.tap&.core_tap? @@ -212,6 +225,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_cask_software_versions super add_info "Linux", OS::Linux.os_version diff --git a/Library/Homebrew/extend/os/mac/diagnostic.rb b/Library/Homebrew/extend/os/mac/diagnostic.rb index 1d563b7694..ced2ce3a8f 100644 --- a/Library/Homebrew/extend/os/mac/diagnostic.rb +++ b/Library/Homebrew/extend/os/mac/diagnostic.rb @@ -1,14 +1,16 @@ -# typed: true # rubocop:disable Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true module OS module Mac module Diagnostic class Volumes + sig { void } def initialize - @volumes = get_mounts + @volumes = T.let(get_mounts, T::Array[String]) end + sig { params(path: T.nilable(Pathname)).returns(Integer) } def which(path) vols = get_mounts path @@ -22,12 +24,13 @@ module OS vol_index end + sig { params(path: T.nilable(Pathname)).returns(T::Array[String]) } def get_mounts(path = nil) vols = [] # get the volume of path, if path is nil returns all volumes args = %w[/bin/df -P] - args << path if path + args << path.to_s if path Utils.popen_read(*args) do |io| io.each_line do |line| @@ -47,6 +50,13 @@ module OS requires_ancestor { Homebrew::Diagnostic::Checks } + sig { params(verbose: T::Boolean).void } + def initialize(verbose: true) + super + @found = T.let([], T::Array[String]) + end + + sig { returns(T::Array[String]) } def fatal_preinstall_checks checks = %w[ check_access_directories @@ -58,6 +68,7 @@ module OS checks.freeze end + sig { returns(T::Array[String]) } def fatal_build_from_source_checks %w[ check_xcode_license_approved @@ -69,6 +80,7 @@ module OS ].freeze end + sig { returns(T::Array[String]) } def fatal_setup_build_environment_checks %w[ check_xcode_minimum_version @@ -77,12 +89,14 @@ module OS ].freeze end + sig { returns(T::Array[String]) } def supported_configuration_checks %w[ check_for_unsupported_macos ].freeze end + sig { returns(T::Array[String]) } def build_from_source_checks %w[ check_for_installed_developer_tools @@ -91,6 +105,7 @@ module OS ].freeze end + sig { returns(T.nilable(String)) } def check_for_non_prefixed_findutils findutils = ::Formula["findutils"] return unless findutils.any_version_installed? @@ -106,6 +121,7 @@ module OS nil end + sig { returns(T.nilable(String)) } def check_for_unsupported_macos return if Homebrew::EnvConfig.developer? return if ENV["HOMEBREW_INTEGRATION_TEST"] @@ -131,6 +147,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_for_opencore return if ::Hardware::CPU.physical_cpu_arm64? @@ -155,6 +172,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_xcode_up_to_date return unless MacOS::Xcode.outdated? @@ -191,6 +209,7 @@ module OS message end + sig { returns(T.nilable(String)) } def check_clt_up_to_date return unless MacOS::CLT.outdated? @@ -211,6 +230,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_xcode_minimum_version return unless MacOS::Xcode.below_minimum_version? @@ -224,6 +244,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_clt_minimum_version return unless MacOS::CLT.below_minimum_version? @@ -233,6 +254,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_if_xcode_needs_clt_installed return unless MacOS::Xcode.needs_clt_installed? @@ -242,6 +264,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_xcode_prefix prefix = MacOS::Xcode.prefix return if prefix.nil? @@ -253,6 +276,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_xcode_prefix_exists prefix = MacOS::Xcode.prefix return if prefix.nil? || prefix.exist? @@ -264,6 +288,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_xcode_select_path return if MacOS::CLT.installed? return unless MacOS::Xcode.installed? @@ -278,6 +303,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_xcode_license_approved # If the user installs Xcode-only, they have to approve the # license or no "xc*" tool will work. @@ -291,6 +317,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_filesystem_case_sensitive dirs_to_check = [ HOMEBREW_PREFIX, @@ -324,6 +351,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_for_gettext find_relative_paths("lib/libgettextlib.dylib", "lib/libintl.dylib", @@ -359,6 +387,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_for_iconv find_relative_paths("lib/libiconv.dylib", "include/iconv.h") return if @found.empty? @@ -389,6 +418,7 @@ module OS end end + sig { returns(T.nilable(String)) } def check_for_multiple_volumes return unless HOMEBREW_CELLAR.exist? @@ -424,6 +454,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_if_supported_sdk_available return unless ::DevelopmentTools.installed? return unless MacOS.sdk_root_needed? @@ -454,6 +485,7 @@ module OS # The CLT 10.x -> 11.x upgrade process on 10.14 contained a bug which broke the SDKs. # Notably, MacOSX10.14.sdk would indirectly symlink to MacOSX10.15.sdk. # This diagnostic was introduced to check for this and recommend a full reinstall. + sig { returns(T.nilable(String)) } def check_broken_sdks locator = MacOS.sdk_locator @@ -485,6 +517,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_cask_software_versions super add_info "macOS", MacOS.full_version @@ -507,6 +540,7 @@ module OS nil end + sig { returns(T.nilable(String)) } def check_pkgconf_macos_sdk_mismatch # We don't provide suitable bottles for these versions. return if OS::Mac.version.prerelease? || OS::Mac.version.outdated_release? @@ -543,6 +577,7 @@ module OS EOS end + sig { returns(T.nilable(String)) } def check_cask_quarantine_support status, check_output = ::Cask::Quarantine.check_quarantine_support