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)) }
private_constant :Element
Elements = T.type_alias { T.any(Element, T::Array[Element]) }
private_constant :Elements
sig { params(paths: Elements).void }
def initialize(*paths)
@paths = T.let(parse(paths), T::Array[String])

View File

@ -137,7 +137,7 @@ module Homebrew
def reset!
@mas_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))
@cask_installed = T.let(nil, T.nilable(T::Boolean))
@formula_versions_from_env = T.let(nil, T.nilable(T::Hash[String, String]))

View File

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

View File

@ -5,7 +5,7 @@ module SharedEnvExtension
sig {
type_parameters(:U).params(
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))
}
def []=(key, value); end

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "utils/output"
@ -32,32 +32,53 @@ module Kernel
raise $CHILD_STATUS.inspect
end
sig { type_parameters(:U).params(block: T.proc.returns(T.type_parameter(:U))).returns(T.type_parameter(:U)) }
def with_homebrew_path(&block)
with_env(PATH: PATH.new(ORIGINAL_PATHS), &block)
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)
with_env(LC_ALL: locale, &block)
end
# 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
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
# Run a system command without any output.
#
# @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
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
# will fail to execute if they can't write to an open stream.
$stdout.reopen(File::NULL)
@ -68,6 +89,7 @@ module Kernel
# Find a command.
#
# @api public
sig { params(cmd: String, path: PATH::Elements).returns(T.nilable(Pathname)) }
def which(cmd, path = ENV.fetch("PATH"))
PATH.new(path).each do |p|
begin
@ -82,6 +104,7 @@ module Kernel
nil
end
sig { params(silent: T::Boolean).returns(String) }
def which_editor(silent: false)
editor = Homebrew::EnvConfig.editor
return editor if editor
@ -124,7 +147,8 @@ module Kernel
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
interrupted = T.let(false, T::Boolean)
old_sigint_handler = trap(:INT) do
@ -144,7 +168,12 @@ module Kernel
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
$stdout.reopen(file)
yield
@ -196,9 +225,10 @@ module Kernel
end
end
sig { params(number: Integer).returns(String) }
def number_readable(number)
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
end
@ -251,7 +281,12 @@ module Kernel
# ```
#
# @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 = {}
begin
hash.each do |key, value|
@ -260,7 +295,7 @@ module Kernel
ENV[key] = value
end
yield if block_given?
yield
ensure
ENV.update(old_values)
end

View File

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

View File

@ -27,7 +27,7 @@ class GitRepository
end
# 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)
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
require "context"
@ -26,12 +26,25 @@ module Homebrew
# Need to keep this naming as-is for backwards compatibility.
# 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
yield if block_given?
args.map!(&:to_s)
begin
exec(cmd, *args, **options)
if argv0
exec(cmd, argv0, *args, **options)
else
exec(cmd, *args, **options)
end
rescue
nil
end
@ -44,13 +57,21 @@ module Homebrew
# private_class_method :_system
# 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?
out = (options[:out] == :err) ? $stderr : $stdout
out.puts "#{cmd} #{args * " "}".gsub(RUBY_PATH, "ruby")
.gsub($LOAD_PATH.join(File::PATH_SEPARATOR).to_s, "$LOAD_PATH")
end
_system(cmd, *args, **options)
_system(cmd, argv0, *args, **options)
end
# `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)
elsif systemctl?
quiet_system(systemctl, "is-active", "--quiet", formula.service_name)
else
false
end
end