Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump some utils/ files to Sorbet typed: strict #19094

Merged
merged 6 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions Library/Homebrew/utils/analytics.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true

require "context"
Expand Down Expand Up @@ -60,7 +60,7 @@
puts Utils.popen_read(curl, *args, url)
else
pid = spawn curl, *args, url
Process.detach T.must(pid)
Process.detach(pid)

Check warning on line 63 in Library/Homebrew/utils/analytics.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/utils/analytics.rb#L63

Added line #L63 was not covered by tests
end
end

Expand Down Expand Up @@ -161,52 +161,63 @@
report_influx(:test_bot_test, tags, fields)
end

sig { returns(T::Boolean) }
def influx_message_displayed?
config_true?(:influxanalyticsmessage)
end

sig { returns(T::Boolean) }
def messages_displayed?
config_true?(:analyticsmessage) &&
config_true?(:caskanalyticsmessage) &&
influx_message_displayed?
return false unless config_true?(:analyticsmessage)
return false unless config_true?(:caskanalyticsmessage)

influx_message_displayed?

Check warning on line 174 in Library/Homebrew/utils/analytics.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/utils/analytics.rb#L174

Added line #L174 was not covered by tests
end

sig { returns(T::Boolean) }
def disabled?
return true if Homebrew::EnvConfig.no_analytics?

config_true?(:analyticsdisabled)
end

sig { returns(T::Boolean) }
def not_this_run?
ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"].present?
end

sig { returns(T::Boolean) }
def no_message_output?
# Used by Homebrew/install
ENV["HOMEBREW_NO_ANALYTICS_MESSAGE_OUTPUT"].present?
end

sig { void }
def messages_displayed!
Homebrew::Settings.write :analyticsmessage, true
Homebrew::Settings.write :caskanalyticsmessage, true
Homebrew::Settings.write :influxanalyticsmessage, true
end

sig { void }
def enable!
Homebrew::Settings.write :analyticsdisabled, false
delete_uuid!
messages_displayed!
end

sig { void }
def disable!
Homebrew::Settings.write :analyticsdisabled, true
delete_uuid!
end

sig { void }
def delete_uuid!
Homebrew::Settings.delete :analyticsuuid
end

sig { params(args: Homebrew::Cmd::Info::Args, filter: T.nilable(String)).void }
def output(args:, filter: nil)
require "api"

Expand Down Expand Up @@ -244,6 +255,7 @@
table_output(category, days, results, os_version:, cask_install:)
end

sig { params(json: T::Hash[String, T.untyped], args: Homebrew::Cmd::Info::Args).void }
def output_analytics(json, args:)
full_analytics = args.analytics? || verbose?

Expand Down Expand Up @@ -273,6 +285,7 @@
# It relies on screen scraping some GitHub HTML that's not available as an API.
# This seems very likely to break in the future.
# That said, it's the only way to get the data we want right now.
sig { params(formula: Formula, args: Homebrew::Cmd::Info::Args).void }
def output_github_packages_downloads(formula, args:)
return unless args.github_packages_downloads?
return unless formula.core_formula?
Expand Down Expand Up @@ -316,6 +329,7 @@
puts "#{number_readable(thirty_day_download_count)} (30 days)"
end

sig { params(formula: Formula, args: Homebrew::Cmd::Info::Args).void }
def formula_output(formula, args:)
return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api?

Expand All @@ -331,6 +345,7 @@
nil
end

sig { params(cask: Cask::Cask, args: Homebrew::Cmd::Info::Args).void }
def cask_output(cask, args:)
return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api?

Expand Down Expand Up @@ -388,6 +403,12 @@
end
end

sig {
params(
category: String, days: String, results: T::Hash[String, Integer], os_version: T::Boolean,
cask_install: T::Boolean
).void
}
def table_output(category, days, results, os_version: false, cask_install: false)
oh1 "#{category} (#{days} days)"
total_count = results.values.inject("+")
Expand Down Expand Up @@ -475,14 +496,17 @@
"#{formatted_total_count_footer} | #{formatted_total_percent_footer}%"
end

sig { params(key: Symbol).returns(T::Boolean) }
def config_true?(key)
Homebrew::Settings.read(key) == "true"
end

sig { params(count: Integer).returns(String) }
def format_count(count)
count.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
end

sig { params(percent: T.any(Integer, Float)).returns(String) }
def format_percent(percent)
format("%<percent>.2f", percent:)
end
Expand Down
11 changes: 7 additions & 4 deletions Library/Homebrew/utils/fork.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true

require "fcntl"
require "utils/socket"

module Utils
sig { params(child_error: T::Hash[String, T.untyped]).returns(Exception) }
def self.rewrite_child_error(child_error)
inner_class = Object.const_get(child_error["json_class"])
error = if child_error["cmd"] && inner_class == ErrorDuringExecution
Expand Down Expand Up @@ -33,7 +34,11 @@ def self.rewrite_child_error(child_error)
# When using this function, remember to call `exec` as soon as reasonably possible.
# This function does not protect against the pitfalls of what you can do pre-exec in a fork.
# See `man fork` for more information.
def self.safe_fork(directory: nil, yield_parent: false)
sig {
params(directory: T.nilable(String), yield_parent: T::Boolean,
_blk: T.proc.params(arg0: T.nilable(String)).void).void
}
def self.safe_fork(directory: nil, yield_parent: false, &_blk)
require "json/add/exception"

block = proc do |tmpdir|
Expand Down Expand Up @@ -80,8 +85,6 @@ def self.safe_fork(directory: nil, yield_parent: false)
exit!(true)
end

pid = T.must(pid)

begin
yield(nil) if yield_parent

Expand Down
57 changes: 38 additions & 19 deletions Library/Homebrew/utils/pypi.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true

require "utils/inreplace"
Expand All @@ -14,20 +14,20 @@
class Package
sig { params(package_string: String, is_url: T::Boolean, python_name: String).void }
def initialize(package_string, is_url: false, python_name: "python")
@pypi_info = nil
@pypi_info = T.let(nil, T.nilable(T::Array[String]))
@package_string = package_string
@is_url = is_url
@is_pypi_url = package_string.start_with? PYTHONHOSTED_URL_PREFIX
@is_pypi_url = T.let(package_string.start_with?(PYTHONHOSTED_URL_PREFIX), T::Boolean)
@python_name = python_name
end

sig { returns(String) }
sig { returns(T.nilable(String)) }
def name
basic_metadata if @name.blank?
@name
end

sig { returns(T::Array[T.nilable(String)]) }
sig { returns(T.nilable(T::Array[String])) }
def extras
basic_metadata if @extras.blank?
@extras
Expand All @@ -43,7 +43,7 @@
def version=(new_version)
raise ArgumentError, "can't update version for non-PyPI packages" unless valid_pypi_package?

@version = new_version
@version = T.let(new_version, T.nilable(String))
end

sig { returns(T::Boolean) }
Expand Down Expand Up @@ -97,8 +97,10 @@
sig { returns(String) }
def to_s
if valid_pypi_package?
out = name
out += "[#{extras.join(",")}]" if extras.present?
out = T.must(name)
if (pypi_extras = extras.presence)
out += "[#{pypi_extras.join(",")}]"
end
out += "==#{version}" if version.present?
out
else
Expand Down Expand Up @@ -132,14 +134,15 @@
private

# Returns [name, [extras], version] for this package.
sig { returns(T.nilable(T.any(String, T::Array[String]))) }
def basic_metadata
if @is_pypi_url
match = File.basename(@package_string).match(/^(.+)-([a-z\d.]+?)(?:.tar.gz|.zip)$/)
raise ArgumentError, "Package should be a valid PyPI URL" if match.blank?

@name ||= PyPI.normalize_python_package match[1]
@extras ||= []
@version ||= match[2]
@name ||= T.let(PyPI.normalize_python_package(T.must(match[1])), T.nilable(String))
@extras ||= T.let([], T.nilable(T::Array[String]))
@version ||= T.let(match[2], T.nilable(String))
elsif @is_url
ensure_formula_installed!(@python_name)

Expand All @@ -162,9 +165,9 @@

metadata = JSON.parse(pip_output)["install"].first["metadata"]

@name ||= PyPI.normalize_python_package metadata["name"]
@extras ||= []
@version ||= metadata["version"]
@name ||= T.let(PyPI.normalize_python_package(metadata["name"]), T.nilable(String))
@extras ||= T.let([], T.nilable(T::Array[String]))
@version ||= T.let(metadata["version"], T.nilable(String))

Check warning on line 170 in Library/Homebrew/utils/pypi.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/utils/pypi.rb#L168-L170

Added lines #L168 - L170 were not covered by tests
else
if @package_string.include? "=="
name, version = @package_string.split("==")
Expand All @@ -180,7 +183,7 @@
extras = []
end

@name ||= PyPI.normalize_python_package name
@name ||= T.let(PyPI.normalize_python_package(T.must(name)), T.nilable(String))
@extras ||= extras
@version ||= version
end
Expand Down Expand Up @@ -248,7 +251,7 @@
missing_msg = "formulae required to update \"#{formula.name}\" resources: #{missing_dependencies.join(", ")}"
odie "Missing #{missing_msg}" unless install_dependencies
ohai "Installing #{missing_msg}"
missing_dependencies.each(&method(:ensure_formula_installed!))
missing_dependencies.each(&:ensure_formula_installed!)

Check warning on line 254 in Library/Homebrew/utils/pypi.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/utils/pypi.rb#L254

Added line #L254 was not covered by tests
end

python_deps = formula.deps
Expand Down Expand Up @@ -327,12 +330,21 @@
# Resolve the dependency tree of all input packages
show_info = !print_only && !silent
ohai "Retrieving PyPI dependencies for \"#{input_packages.join(" ")}\"..." if show_info
found_packages = pip_report(input_packages, python_name:, print_stderr: verbose && show_info)

print_stderr = if verbose && show_info

Check warning on line 334 in Library/Homebrew/utils/pypi.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/utils/pypi.rb#L334

Added line #L334 was not covered by tests
true
else
false
end

found_packages = pip_report(input_packages, python_name:, print_stderr:)

Check warning on line 340 in Library/Homebrew/utils/pypi.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/utils/pypi.rb#L340

Added line #L340 was not covered by tests
# Resolve the dependency tree of excluded packages to prune the above
exclude_packages.delete_if { |package| found_packages.exclude? package }
ohai "Retrieving PyPI dependencies for excluded \"#{exclude_packages.join(" ")}\"..." if show_info
exclude_packages = pip_report(exclude_packages, python_name:, print_stderr: verbose && show_info)
exclude_packages += [Package.new(main_package.name)] unless main_package.nil?
exclude_packages = pip_report(exclude_packages, python_name:, print_stderr:)

Check warning on line 344 in Library/Homebrew/utils/pypi.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/utils/pypi.rb#L344

Added line #L344 was not covered by tests
if (main_package_name = main_package&.name)
exclude_packages += [Package.new(main_package_name)]
end

new_resource_blocks = ""
found_packages.sort.each do |package|
Expand Down Expand Up @@ -404,12 +416,18 @@
true
end

sig { params(name: String).returns(String) }
def self.normalize_python_package(name)
# This normalization is defined in the PyPA packaging specifications;
# https://packaging.python.org/en/latest/specifications/name-normalization/#name-normalization
name.gsub(/[-_.]+/, "-").downcase
end

sig {
params(

Check warning on line 427 in Library/Homebrew/utils/pypi.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/utils/pypi.rb#L427

Added line #L427 was not covered by tests
packages: T::Array[Package], python_name: String, print_stderr: T::Boolean,
).returns(T::Array[Package])
}
def self.pip_report(packages, python_name: "python", print_stderr: false)
return [] if packages.blank?

Expand All @@ -430,6 +448,7 @@
pip_report_to_packages(JSON.parse(pip_output)).uniq
end

sig { params(report: T::Hash[String, T.untyped]).returns(T::Array[Package]) }
def self.pip_report_to_packages(report)
return [] if report.blank?

Expand Down
32 changes: 19 additions & 13 deletions Library/Homebrew/utils/shell.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true

module Utils
Expand Down Expand Up @@ -67,7 +67,10 @@ def profile
return "#{ENV["HOMEBREW_ZDOTDIR"]}/.zshrc" if ENV["HOMEBREW_ZDOTDIR"].present?
end

SHELL_PROFILE_MAP.fetch(preferred, "~/.profile")
shell = preferred
return "~/.profile" if shell.nil?

SHELL_PROFILE_MAP.fetch(shell, "~/.profile")
end

sig { params(variable: String, value: String).returns(T.nilable(String)) }
Expand Down Expand Up @@ -98,17 +101,20 @@ def prepend_path_in_profile(path)
end
end

SHELL_PROFILE_MAP = {
bash: "~/.profile",
csh: "~/.cshrc",
fish: "~/.config/fish/config.fish",
ksh: "~/.kshrc",
mksh: "~/.kshrc",
rc: "~/.rcrc",
sh: "~/.profile",
tcsh: "~/.tcshrc",
zsh: "~/.zshrc",
}.freeze
SHELL_PROFILE_MAP = T.let(
{
bash: "~/.profile",
csh: "~/.cshrc",
fish: "~/.config/fish/config.fish",
ksh: "~/.kshrc",
mksh: "~/.kshrc",
rc: "~/.rcrc",
sh: "~/.profile",
tcsh: "~/.tcshrc",
zsh: "~/.zshrc",
}.freeze,
T::Hash[Symbol, String],
)

UNSAFE_SHELL_CHAR = %r{([^A-Za-z0-9_\-.,:/@~+\n])}

Expand Down
Loading
Loading