Merge pull request #20560 from Homebrew/dug/typed-kernel

Enable strict typing in Kernel extensions + utils.rb
This commit is contained in:
Mike McQuaid 2025-08-25 07:31:58 +00:00 committed by GitHub
commit 82fabab8aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 81 additions and 24 deletions

View File

@ -15,7 +15,6 @@ class PATH
Element = T.type_alias { T.nilable(T.any(Pathname, String, PATH)) } Element = T.type_alias { T.nilable(T.any(Pathname, String, PATH)) }
private_constant :Element private_constant :Element
Elements = T.type_alias { T.any(Element, T::Array[Element]) } Elements = T.type_alias { T.any(Element, T::Array[Element]) }
private_constant :Elements
sig { params(paths: Elements).void } sig { params(paths: Elements).void }
def initialize(*paths) def initialize(*paths)
@paths = T.let(parse(paths), T::Array[String]) @paths = T.let(parse(paths), T::Array[String])

View File

@ -137,7 +137,7 @@ module Homebrew
def reset! def reset!
@mas_installed = T.let(nil, T.nilable(T::Boolean)) @mas_installed = T.let(nil, T.nilable(T::Boolean))
@vscode_installed = T.let(nil, T.nilable(T::Boolean)) @vscode_installed = T.let(nil, T.nilable(T::Boolean))
@which_vscode = T.let(nil, T.nilable(String)) @which_vscode = T.let(nil, T.nilable(Pathname))
@whalebrew_installed = T.let(nil, T.nilable(T::Boolean)) @whalebrew_installed = T.let(nil, T.nilable(T::Boolean))
@cask_installed = T.let(nil, T.nilable(T::Boolean)) @cask_installed = T.let(nil, T.nilable(T::Boolean))
@formula_versions_from_env = T.let(nil, T.nilable(T::Hash[String, String])) @formula_versions_from_env = T.let(nil, T.nilable(T::Hash[String, String]))

View File

@ -97,7 +97,7 @@ module Homebrew
if args.lsp? if args.lsp?
srb_exec << "--lsp" srb_exec << "--lsp"
if (watchman = which("watchman", ORIGINAL_PATHS)) if (watchman = which("watchman", ORIGINAL_PATHS))
srb_exec << "--watchman-path" << watchman srb_exec << "--watchman-path" << watchman.to_s
else else
srb_exec << "--disable-watchman" srb_exec << "--disable-watchman"
end end

View File

@ -5,7 +5,7 @@ module SharedEnvExtension
sig { sig {
type_parameters(:U).params( type_parameters(:U).params(
key: String, key: String,
value: T.all(T.type_parameter(:U), T.nilable(T.any(String, PATH))), value: T.all(T.type_parameter(:U), T.nilable(T.any(String, Pathname, PATH))),
).returns(T.type_parameter(:U)) ).returns(T.type_parameter(:U))
} }
def []=(key, value); end def []=(key, value); end

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "utils/output" require "utils/output"
@ -32,32 +32,53 @@ module Kernel
raise $CHILD_STATUS.inspect raise $CHILD_STATUS.inspect
end end
sig { type_parameters(:U).params(block: T.proc.returns(T.type_parameter(:U))).returns(T.type_parameter(:U)) }
def with_homebrew_path(&block) def with_homebrew_path(&block)
with_env(PATH: PATH.new(ORIGINAL_PATHS), &block) with_env(PATH: PATH.new(ORIGINAL_PATHS), &block)
end end
sig {
type_parameters(:U)
.params(locale: String, block: T.proc.returns(T.type_parameter(:U)))
.returns(T.type_parameter(:U))
}
def with_custom_locale(locale, &block) def with_custom_locale(locale, &block)
with_env(LC_ALL: locale, &block) with_env(LC_ALL: locale, &block)
end end
# Kernel.system but with exceptions. # Kernel.system but with exceptions.
def safe_system(cmd, *args, **options) sig {
params(
cmd: T.any(NilClass, Pathname, String, [String, String], T::Hash[String, T.nilable(String)]),
argv0: T.any(NilClass, Pathname, String, [String, String]),
args: T.any(NilClass, Pathname, String),
options: T.untyped,
).void
}
def safe_system(cmd, argv0 = nil, *args, **options)
# TODO: migrate to utils.rb Homebrew.safe_system # TODO: migrate to utils.rb Homebrew.safe_system
require "utils" require "utils"
return if Homebrew.system(cmd, *args, **options) return if Homebrew.system(cmd, argv0, *args, **options)
raise ErrorDuringExecution.new([cmd, *args], status: $CHILD_STATUS) raise ErrorDuringExecution.new([cmd, argv0, *args], status: $CHILD_STATUS)
end end
# Run a system command without any output. # Run a system command without any output.
# #
# @api internal # @api internal
def quiet_system(cmd, *args) sig {
params(
cmd: T.any(NilClass, Pathname, String, [String, String], T::Hash[String, T.nilable(String)]),
argv0: T.any(NilClass, String, [String, String]),
args: T.any(Pathname, String),
).returns(T::Boolean)
}
def quiet_system(cmd, argv0 = nil, *args)
# TODO: migrate to utils.rb Homebrew.quiet_system # TODO: migrate to utils.rb Homebrew.quiet_system
require "utils" require "utils"
Homebrew._system(cmd, *args) do Homebrew._system(cmd, argv0, *args) do
# Redirect output streams to `/dev/null` instead of closing as some programs # Redirect output streams to `/dev/null` instead of closing as some programs
# will fail to execute if they can't write to an open stream. # will fail to execute if they can't write to an open stream.
$stdout.reopen(File::NULL) $stdout.reopen(File::NULL)
@ -68,6 +89,7 @@ module Kernel
# Find a command. # Find a command.
# #
# @api public # @api public
sig { params(cmd: String, path: PATH::Elements).returns(T.nilable(Pathname)) }
def which(cmd, path = ENV.fetch("PATH")) def which(cmd, path = ENV.fetch("PATH"))
PATH.new(path).each do |p| PATH.new(path).each do |p|
begin begin
@ -82,6 +104,7 @@ module Kernel
nil nil
end end
sig { params(silent: T::Boolean).returns(String) }
def which_editor(silent: false) def which_editor(silent: false)
editor = Homebrew::EnvConfig.editor editor = Homebrew::EnvConfig.editor
return editor if editor return editor if editor
@ -124,7 +147,8 @@ module Kernel
IGNORE_INTERRUPTS_MUTEX = T.let(Thread::Mutex.new.freeze, Thread::Mutex) IGNORE_INTERRUPTS_MUTEX = T.let(Thread::Mutex.new.freeze, Thread::Mutex)
def ignore_interrupts sig { type_parameters(:U).params(_block: T.proc.returns(T.type_parameter(:U))).returns(T.type_parameter(:U)) }
def ignore_interrupts(&_block)
IGNORE_INTERRUPTS_MUTEX.synchronize do IGNORE_INTERRUPTS_MUTEX.synchronize do
interrupted = T.let(false, T::Boolean) interrupted = T.let(false, T::Boolean)
old_sigint_handler = trap(:INT) do old_sigint_handler = trap(:INT) do
@ -144,7 +168,12 @@ module Kernel
end end
end end
def redirect_stdout(file) sig {
type_parameters(:U)
.params(file: T.any(IO, Pathname, String), _block: T.proc.returns(T.type_parameter(:U)))
.returns(T.type_parameter(:U))
}
def redirect_stdout(file, &_block)
out = $stdout.dup out = $stdout.dup
$stdout.reopen(file) $stdout.reopen(file)
yield yield
@ -196,9 +225,10 @@ module Kernel
end end
end end
sig { params(number: Integer).returns(String) }
def number_readable(number) def number_readable(number)
numstr = number.to_i.to_s numstr = number.to_i.to_s
(numstr.size - 3).step(1, -3) { |i| numstr.insert(i, ",") } (numstr.size - 3).step(1, -3) { |i| numstr.insert(i.to_i, ",") }
numstr numstr
end end
@ -251,7 +281,12 @@ module Kernel
# ``` # ```
# #
# @api public # @api public
def with_env(hash) sig {
type_parameters(:U)
.params(hash: T::Hash[Object, String], _block: T.proc.returns(T.type_parameter(:U)))
.returns(T.type_parameter(:U))
}
def with_env(hash, &_block)
old_values = {} old_values = {}
begin begin
hash.each do |key, value| hash.each do |key, value|
@ -260,7 +295,7 @@ module Kernel
ENV[key] = value ENV[key] = value
end end
yield if block_given? yield
ensure ensure
ENV.update(old_values) ENV.update(old_values)
end end

View File

@ -11,7 +11,7 @@ module OS
end end
sig { params(path: T.nilable(Pathname)).returns(Integer) } sig { params(path: T.nilable(Pathname)).returns(Integer) }
def which(path) def index_of(path)
vols = get_mounts path vols = get_mounts path
# no volume found # no volume found
@ -426,13 +426,13 @@ module OS
# Find the volumes for the TMP folder & HOMEBREW_CELLAR # Find the volumes for the TMP folder & HOMEBREW_CELLAR
real_cellar = HOMEBREW_CELLAR.realpath real_cellar = HOMEBREW_CELLAR.realpath
where_cellar = volumes.which real_cellar where_cellar = volumes.index_of real_cellar
begin begin
tmp = Pathname.new(Dir.mktmpdir("doctor", HOMEBREW_TEMP)) tmp = Pathname.new(Dir.mktmpdir("doctor", HOMEBREW_TEMP))
begin begin
real_tmp = tmp.realpath.parent real_tmp = tmp.realpath.parent
where_tmp = volumes.which real_tmp where_tmp = volumes.index_of real_tmp
ensure ensure
Dir.delete tmp.to_s Dir.delete tmp.to_s
end end

View File

@ -27,7 +27,7 @@ class GitRepository
end end
# Sets the URL of the Git origin remote. # Sets the URL of the Git origin remote.
sig { params(origin: String).returns(T.nilable(T::Boolean)) } sig { params(origin: String).void }
def origin_url=(origin) def origin_url=(origin)
return if !git_repository? || !Utils::Git.available? return if !git_repository? || !Utils::Git.available?

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "context" require "context"
@ -26,12 +26,25 @@ module Homebrew
# Need to keep this naming as-is for backwards compatibility. # Need to keep this naming as-is for backwards compatibility.
# rubocop:disable Naming/PredicateMethod # rubocop:disable Naming/PredicateMethod
def self._system(cmd, *args, **options) sig {
params(
cmd: T.any(NilClass, Pathname, String, [String, String], T::Hash[String, T.nilable(String)]),
argv0: T.any(NilClass, Pathname, String, [String, String]),
args: T.any(Pathname, String),
options: T.untyped,
_block: T.nilable(T.proc.void),
).returns(T::Boolean)
}
def self._system(cmd, argv0 = nil, *args, **options, &_block)
pid = fork do pid = fork do
yield if block_given? yield if block_given?
args.map!(&:to_s) args.map!(&:to_s)
begin begin
if argv0
exec(cmd, argv0, *args, **options)
else
exec(cmd, *args, **options) exec(cmd, *args, **options)
end
rescue rescue
nil nil
end end
@ -44,13 +57,21 @@ module Homebrew
# private_class_method :_system # private_class_method :_system
# rubocop:enable Naming/PredicateMethod # rubocop:enable Naming/PredicateMethod
def self.system(cmd, *args, **options) sig {
params(
cmd: T.any(Pathname, String, [String, String], T::Hash[String, T.nilable(String)]),
argv0: T.any(NilClass, Pathname, String, [String, String]),
args: T.any(Pathname, String),
options: T.untyped,
).returns(T::Boolean)
}
def self.system(cmd, argv0 = nil, *args, **options)
if verbose? if verbose?
out = (options[:out] == :err) ? $stderr : $stdout out = (options[:out] == :err) ? $stderr : $stdout
out.puts "#{cmd} #{args * " "}".gsub(RUBY_PATH, "ruby") out.puts "#{cmd} #{args * " "}".gsub(RUBY_PATH, "ruby")
.gsub($LOAD_PATH.join(File::PATH_SEPARATOR).to_s, "$LOAD_PATH") .gsub($LOAD_PATH.join(File::PATH_SEPARATOR).to_s, "$LOAD_PATH")
end end
_system(cmd, *args, **options) _system(cmd, argv0, *args, **options)
end end
# `Module` and `Regexp` are global variables used as types here so they don't need to be imported # `Module` and `Regexp` are global variables used as types here so they don't need to be imported

View File

@ -11,6 +11,8 @@ module Utils
quiet_system(launchctl, "list", formula.plist_name) quiet_system(launchctl, "list", formula.plist_name)
elsif systemctl? elsif systemctl?
quiet_system(systemctl, "is-active", "--quiet", formula.service_name) quiet_system(systemctl, "is-active", "--quiet", formula.service_name)
else
false
end end
end end