Skip to content

Commit fa40d2f

Browse files
authored
Merge pull request #19094 from Homebrew/utils-sorbet-strict
Bump some `utils/` files to Sorbet `typed: strict`
2 parents 55475cb + 66e5084 commit fa40d2f

File tree

6 files changed

+215
-88
lines changed

6 files changed

+215
-88
lines changed

Library/Homebrew/utils/analytics.rb

+29-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# typed: true # rubocop:todo Sorbet/StrictSigil
1+
# typed: strict
22
# frozen_string_literal: true
33

44
require "context"
@@ -60,7 +60,7 @@ def deferred_curl(url, args)
6060
puts Utils.popen_read(curl, *args, url)
6161
else
6262
pid = spawn curl, *args, url
63-
Process.detach T.must(pid)
63+
Process.detach(pid)
6464
end
6565
end
6666

@@ -161,52 +161,63 @@ def report_test_bot_test(step_command_short, passed)
161161
report_influx(:test_bot_test, tags, fields)
162162
end
163163

164+
sig { returns(T::Boolean) }
164165
def influx_message_displayed?
165166
config_true?(:influxanalyticsmessage)
166167
end
167168

169+
sig { returns(T::Boolean) }
168170
def messages_displayed?
169-
config_true?(:analyticsmessage) &&
170-
config_true?(:caskanalyticsmessage) &&
171-
influx_message_displayed?
171+
return false unless config_true?(:analyticsmessage)
172+
return false unless config_true?(:caskanalyticsmessage)
173+
174+
influx_message_displayed?
172175
end
173176

177+
sig { returns(T::Boolean) }
174178
def disabled?
175179
return true if Homebrew::EnvConfig.no_analytics?
176180

177181
config_true?(:analyticsdisabled)
178182
end
179183

184+
sig { returns(T::Boolean) }
180185
def not_this_run?
181186
ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"].present?
182187
end
183188

189+
sig { returns(T::Boolean) }
184190
def no_message_output?
185191
# Used by Homebrew/install
186192
ENV["HOMEBREW_NO_ANALYTICS_MESSAGE_OUTPUT"].present?
187193
end
188194

195+
sig { void }
189196
def messages_displayed!
190197
Homebrew::Settings.write :analyticsmessage, true
191198
Homebrew::Settings.write :caskanalyticsmessage, true
192199
Homebrew::Settings.write :influxanalyticsmessage, true
193200
end
194201

202+
sig { void }
195203
def enable!
196204
Homebrew::Settings.write :analyticsdisabled, false
197205
delete_uuid!
198206
messages_displayed!
199207
end
200208

209+
sig { void }
201210
def disable!
202211
Homebrew::Settings.write :analyticsdisabled, true
203212
delete_uuid!
204213
end
205214

215+
sig { void }
206216
def delete_uuid!
207217
Homebrew::Settings.delete :analyticsuuid
208218
end
209219

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

@@ -244,6 +255,7 @@ def output(args:, filter: nil)
244255
table_output(category, days, results, os_version:, cask_install:)
245256
end
246257

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

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

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

@@ -331,6 +345,7 @@ def formula_output(formula, args:)
331345
nil
332346
end
333347

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

@@ -388,6 +403,12 @@ def default_package_fields
388403
end
389404
end
390405

406+
sig {
407+
params(
408+
category: String, days: String, results: T::Hash[String, Integer], os_version: T::Boolean,
409+
cask_install: T::Boolean
410+
).void
411+
}
391412
def table_output(category, days, results, os_version: false, cask_install: false)
392413
oh1 "#{category} (#{days} days)"
393414
total_count = results.values.inject("+")
@@ -475,14 +496,17 @@ def table_output(category, days, results, os_version: false, cask_install: false
475496
"#{formatted_total_count_footer} | #{formatted_total_percent_footer}%"
476497
end
477498

499+
sig { params(key: Symbol).returns(T::Boolean) }
478500
def config_true?(key)
479501
Homebrew::Settings.read(key) == "true"
480502
end
481503

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

509+
sig { params(percent: T.any(Integer, Float)).returns(String) }
486510
def format_percent(percent)
487511
format("%<percent>.2f", percent:)
488512
end

Library/Homebrew/utils/fork.rb

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
# typed: true # rubocop:todo Sorbet/StrictSigil
1+
# typed: strict
22
# frozen_string_literal: true
33

44
require "fcntl"
55
require "utils/socket"
66

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

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

83-
pid = T.must(pid)
84-
8588
begin
8689
yield(nil) if yield_parent
8790

Library/Homebrew/utils/pypi.rb

+38-19
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# typed: true # rubocop:todo Sorbet/StrictSigil
1+
# typed: strict
22
# frozen_string_literal: true
33

44
require "utils/inreplace"
@@ -14,20 +14,20 @@ module PyPI
1414
class Package
1515
sig { params(package_string: String, is_url: T::Boolean, python_name: String).void }
1616
def initialize(package_string, is_url: false, python_name: "python")
17-
@pypi_info = nil
17+
@pypi_info = T.let(nil, T.nilable(T::Array[String]))
1818
@package_string = package_string
1919
@is_url = is_url
20-
@is_pypi_url = package_string.start_with? PYTHONHOSTED_URL_PREFIX
20+
@is_pypi_url = T.let(package_string.start_with?(PYTHONHOSTED_URL_PREFIX), T::Boolean)
2121
@python_name = python_name
2222
end
2323

24-
sig { returns(String) }
24+
sig { returns(T.nilable(String)) }
2525
def name
2626
basic_metadata if @name.blank?
2727
@name
2828
end
2929

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

46-
@version = new_version
46+
@version = T.let(new_version, T.nilable(String))
4747
end
4848

4949
sig { returns(T::Boolean) }
@@ -97,8 +97,10 @@ def pypi_info(new_version: nil)
9797
sig { returns(String) }
9898
def to_s
9999
if valid_pypi_package?
100-
out = name
101-
out += "[#{extras.join(",")}]" if extras.present?
100+
out = T.must(name)
101+
if (pypi_extras = extras.presence)
102+
out += "[#{pypi_extras.join(",")}]"
103+
end
102104
out += "==#{version}" if version.present?
103105
out
104106
else
@@ -132,14 +134,15 @@ def <=>(other)
132134
private
133135

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

140-
@name ||= PyPI.normalize_python_package match[1]
141-
@extras ||= []
142-
@version ||= match[2]
143+
@name ||= T.let(PyPI.normalize_python_package(T.must(match[1])), T.nilable(String))
144+
@extras ||= T.let([], T.nilable(T::Array[String]))
145+
@version ||= T.let(match[2], T.nilable(String))
143146
elsif @is_url
144147
ensure_formula_installed!(@python_name)
145148

@@ -162,9 +165,9 @@ def basic_metadata
162165

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

165-
@name ||= PyPI.normalize_python_package metadata["name"]
166-
@extras ||= []
167-
@version ||= metadata["version"]
168+
@name ||= T.let(PyPI.normalize_python_package(metadata["name"]), T.nilable(String))
169+
@extras ||= T.let([], T.nilable(T::Array[String]))
170+
@version ||= T.let(metadata["version"], T.nilable(String))
168171
else
169172
if @package_string.include? "=="
170173
name, version = @package_string.split("==")
@@ -180,7 +183,7 @@ def basic_metadata
180183
extras = []
181184
end
182185

183-
@name ||= PyPI.normalize_python_package name
186+
@name ||= T.let(PyPI.normalize_python_package(T.must(name)), T.nilable(String))
184187
@extras ||= extras
185188
@version ||= version
186189
end
@@ -248,7 +251,7 @@ def self.update_python_resources!(formula, version: nil, package_name: nil, extr
248251
missing_msg = "formulae required to update \"#{formula.name}\" resources: #{missing_dependencies.join(", ")}"
249252
odie "Missing #{missing_msg}" unless install_dependencies
250253
ohai "Installing #{missing_msg}"
251-
missing_dependencies.each(&method(:ensure_formula_installed!))
254+
missing_dependencies.each(&:ensure_formula_installed!)
252255
end
253256

254257
python_deps = formula.deps
@@ -327,12 +330,21 @@ def self.update_python_resources!(formula, version: nil, package_name: nil, extr
327330
# Resolve the dependency tree of all input packages
328331
show_info = !print_only && !silent
329332
ohai "Retrieving PyPI dependencies for \"#{input_packages.join(" ")}\"..." if show_info
330-
found_packages = pip_report(input_packages, python_name:, print_stderr: verbose && show_info)
333+
334+
print_stderr = if verbose && show_info
335+
true
336+
else
337+
false
338+
end
339+
340+
found_packages = pip_report(input_packages, python_name:, print_stderr:)
331341
# Resolve the dependency tree of excluded packages to prune the above
332342
exclude_packages.delete_if { |package| found_packages.exclude? package }
333343
ohai "Retrieving PyPI dependencies for excluded \"#{exclude_packages.join(" ")}\"..." if show_info
334-
exclude_packages = pip_report(exclude_packages, python_name:, print_stderr: verbose && show_info)
335-
exclude_packages += [Package.new(main_package.name)] unless main_package.nil?
344+
exclude_packages = pip_report(exclude_packages, python_name:, print_stderr:)
345+
if (main_package_name = main_package&.name)
346+
exclude_packages += [Package.new(main_package_name)]
347+
end
336348

337349
new_resource_blocks = ""
338350
found_packages.sort.each do |package|
@@ -404,12 +416,18 @@ def self.update_python_resources!(formula, version: nil, package_name: nil, extr
404416
true
405417
end
406418

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

426+
sig {
427+
params(
428+
packages: T::Array[Package], python_name: String, print_stderr: T::Boolean,
429+
).returns(T::Array[Package])
430+
}
413431
def self.pip_report(packages, python_name: "python", print_stderr: false)
414432
return [] if packages.blank?
415433

@@ -430,6 +448,7 @@ def self.pip_report(packages, python_name: "python", print_stderr: false)
430448
pip_report_to_packages(JSON.parse(pip_output)).uniq
431449
end
432450

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

Library/Homebrew/utils/shell.rb

+19-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# typed: true # rubocop:todo Sorbet/StrictSigil
1+
# typed: strict
22
# frozen_string_literal: true
33

44
module Utils
@@ -67,7 +67,10 @@ def profile
6767
return "#{ENV["HOMEBREW_ZDOTDIR"]}/.zshrc" if ENV["HOMEBREW_ZDOTDIR"].present?
6868
end
6969

70-
SHELL_PROFILE_MAP.fetch(preferred, "~/.profile")
70+
shell = preferred
71+
return "~/.profile" if shell.nil?
72+
73+
SHELL_PROFILE_MAP.fetch(shell, "~/.profile")
7174
end
7275

7376
sig { params(variable: String, value: String).returns(T.nilable(String)) }
@@ -98,17 +101,20 @@ def prepend_path_in_profile(path)
98101
end
99102
end
100103

101-
SHELL_PROFILE_MAP = {
102-
bash: "~/.profile",
103-
csh: "~/.cshrc",
104-
fish: "~/.config/fish/config.fish",
105-
ksh: "~/.kshrc",
106-
mksh: "~/.kshrc",
107-
rc: "~/.rcrc",
108-
sh: "~/.profile",
109-
tcsh: "~/.tcshrc",
110-
zsh: "~/.zshrc",
111-
}.freeze
104+
SHELL_PROFILE_MAP = T.let(
105+
{
106+
bash: "~/.profile",
107+
csh: "~/.cshrc",
108+
fish: "~/.config/fish/config.fish",
109+
ksh: "~/.kshrc",
110+
mksh: "~/.kshrc",
111+
rc: "~/.rcrc",
112+
sh: "~/.profile",
113+
tcsh: "~/.tcshrc",
114+
zsh: "~/.zshrc",
115+
}.freeze,
116+
T::Hash[Symbol, String],
117+
)
112118

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

0 commit comments

Comments
 (0)