Enable strict typing in Utils::Bottles

This commit is contained in:
Douglas Eichelberger 2025-08-31 16:56:56 -07:00
parent f68908d492
commit b827a1337a
No known key found for this signature in database
GPG Key ID: F90193CBD547EB81
3 changed files with 73 additions and 30 deletions

View File

@ -182,7 +182,8 @@ module Homebrew
sig {
params(old_keys: T::Array[String], old_bottle_spec: BottleSpecification,
new_bottle_hash: T::Hash[String, T.untyped]).returns(T::Array[T::Array[String]])
new_bottle_hash: T::Hash[String, T.untyped])
.returns([T::Array[String], T::Array[T::Hash[Symbol, T.any(String, Symbol)]]])
}
def merge_bottle_spec(old_keys, old_bottle_spec, new_bottle_hash)
mismatches = []
@ -409,7 +410,7 @@ module Homebrew
bottle_tag, rebuild = if local_bottle_json
_, tag_string, rebuild_string = Utils::Bottles.extname_tag_rebuild(formula.local_bottle_path.to_s)
[tag_string.to_sym, rebuild_string.to_i]
[T.must(tag_string).to_sym, rebuild_string.to_i]
end
bottle_tag = if bottle_tag
@ -860,8 +861,8 @@ module Homebrew
end
sig {
params(formula: Formula, formula_ast: Utils::AST::FormulaAST,
bottle_hash: T::Hash[String, T.untyped]).returns(T.nilable(T::Array[String]))
params(formula: Formula, formula_ast: Utils::AST::FormulaAST, bottle_hash: T::Hash[String, T.untyped])
.returns(T.nilable(T::Array[T::Hash[Symbol, T.any(String, Symbol)]]))
}
def old_checksums(formula, formula_ast, bottle_hash)
bottle_node = T.cast(formula_ast.bottle_block, T.nilable(RuboCop::AST::BlockNode))

View File

@ -609,8 +609,8 @@ on_request: installed_on_request?, options:)
clean
# Store the formula used to build the keg in the keg.
formula_contents = if formula.local_bottle_path
Utils::Bottles.formula_contents formula.local_bottle_path, name: formula.name
formula_contents = if (local_bottle_path = formula.local_bottle_path)
Utils::Bottles.formula_contents local_bottle_path, name: formula.name
else
formula.path.read
end

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "tab"
@ -20,13 +20,14 @@ module Utils
when Tag
tag
else
@tag ||= Tag.new(
system: HOMEBREW_SYSTEM.downcase.to_sym,
arch: HOMEBREW_PROCESSOR.downcase.to_sym,
)
@tag ||= T.let(Tag.new(
system: HOMEBREW_SYSTEM.downcase.to_sym,
arch: HOMEBREW_PROCESSOR.downcase.to_sym,
), T.nilable(Tag))
end
end
sig { params(formula: Formula).returns(T::Boolean) }
def built_as?(formula)
return false unless formula.latest_version_installed?
@ -34,34 +35,40 @@ module Utils
tab.built_as_bottle
end
sig { params(formula: Formula, file: Pathname).returns(T::Boolean) }
def file_outdated?(formula, file)
file = file.resolved_path
filename = file.basename.to_s
return false if formula.bottle.blank?
bottle = formula.bottle
return false unless bottle
_, bottle_tag, bottle_rebuild = extname_tag_rebuild(filename)
return false if bottle_tag.blank?
bottle_tag != formula.bottle.tag.to_s || bottle_rebuild.to_i != formula.bottle.rebuild
bottle_tag != bottle.tag.to_s || bottle_rebuild.to_i != bottle.rebuild
end
sig { params(filename: String).returns(T::Array[String]) }
def extname_tag_rebuild(filename)
HOMEBREW_BOTTLES_EXTNAME_REGEX.match(filename).to_a
end
sig { params(bottle_file: Pathname).returns(T.nilable(String)) }
def receipt_path(bottle_file)
bottle_file_list(bottle_file).find do |line|
%r{.+/.+/INSTALL_RECEIPT.json}.match?(line)
end
end
sig { params(bottle_file: Pathname, file_path: String).returns(String) }
def file_from_bottle(bottle_file, file_path)
Utils.popen_read("tar", "--extract", "--to-stdout", "--file", bottle_file, file_path)
end
sig { params(bottle_file: Pathname).returns([String, String]) }
def resolve_formula_names(bottle_file)
name = bottle_file_list(bottle_file).first.to_s.split("/").first
name = bottle_file_list(bottle_file).first.to_s.split("/").fetch(0)
full_name = if (receipt_file_path = receipt_path(bottle_file))
receipt_file = file_from_bottle(bottle_file, receipt_file_path)
tap = Tab.from_file_content(receipt_file, "#{bottle_file}/#{receipt_file_path}").tap
@ -80,13 +87,14 @@ module Utils
[name, full_name]
end
sig { params(bottle_file: Pathname).returns(PkgVersion) }
def resolve_version(bottle_file)
version = bottle_file_list(bottle_file).first.to_s.split("/").second
version = bottle_file_list(bottle_file).first.to_s.split("/").fetch(1)
PkgVersion.parse(version)
end
def formula_contents(bottle_file,
name: resolve_formula_names(bottle_file)[0])
sig { params(bottle_file: Pathname, name: String).returns(String) }
def formula_contents(bottle_file, name: resolve_formula_names(bottle_file)[0])
bottle_version = resolve_version bottle_file
formula_path = "#{name}/#{bottle_version}/.brew/#{name}.rb"
contents = file_from_bottle(bottle_file, formula_path)
@ -95,6 +103,10 @@ module Utils
contents
end
sig {
params(root_url: String, name: String, checksum: T.any(Checksum, String),
filename: T.nilable(Bottle::Filename)).returns(T.any([String, T.nilable(String)], String))
}
def path_resolved_basename(root_url, name, checksum, filename)
if root_url.match?(GitHubPackages::URL_REGEX)
image_name = GitHubPackages.image_formula_name(name)
@ -104,6 +116,7 @@ module Utils
end
end
sig { params(formula: Formula).returns(Tab) }
def load_tab(formula)
keg = Keg.new(formula.prefix)
tabfile = keg/AbstractTab::FILENAME
@ -112,7 +125,7 @@ module Utils
if (tab_attributes = formula.bottle_tab_attributes.presence)
Tab.from_file_content(tab_attributes.to_json, tabfile)
elsif !tabfile.exist? && bottle_json_path&.exist?
_, tag, = Utils::Bottles.extname_tag_rebuild(formula.local_bottle_path)
_, tag, = Utils::Bottles.extname_tag_rebuild(formula.local_bottle_path.to_s)
bottle_hash = JSON.parse(File.read(bottle_json_path))
tab_json = bottle_hash[formula.full_name]["bottle"]["tags"][tag]["tab"].to_json
Tab.from_file_content(tab_json, tabfile)
@ -130,8 +143,9 @@ module Utils
private
sig { params(bottle_file: Pathname).returns(T::Array[String]) }
def bottle_file_list(bottle_file)
@bottle_file_list ||= {}
@bottle_file_list ||= T.let({}, T.nilable(T::Hash[Pathname, T::Array[String]]))
@bottle_file_list[bottle_file] ||= Utils.popen_read("tar", "--list", "--file", bottle_file)
.lines
.map(&:chomp)
@ -140,23 +154,24 @@ module Utils
# Denotes the arch and OS of a bottle.
class Tag
sig { returns(Symbol) }
attr_reader :system, :arch
sig { params(value: Symbol).returns(T.attached_class) }
def self.from_symbol(value)
return new(system: :all, arch: :all) if value == :all
@all_archs_regex ||= begin
@all_archs_regex ||= T.let(begin
all_archs = Hardware::CPU::ALL_ARCHS.map(&:to_s)
/
^((?<arch>#{Regexp.union(all_archs)})_)?
(?<system>[\w.]+)$
/x
end
end, T.nilable(Regexp))
match = @all_archs_regex.match(value.to_s)
raise ArgumentError, "Invalid bottle tag symbol" unless match
system = match[:system].to_sym
system = T.must(match[:system]).to_sym
arch = match[:arch]&.to_sym || :x86_64
new(system:, arch:)
end
@ -167,18 +182,27 @@ module Utils
@arch = arch
end
sig { override.params(other: BasicObject).returns(T::Boolean) }
def ==(other)
if other.is_a?(Symbol)
case other
when Symbol
to_sym == other
else
self.class == other.class && system == other.system && standardized_arch == other.standardized_arch
when self.class
system == other.system && standardized_arch == other.standardized_arch
else false
end
end
sig { override.params(other: BasicObject).returns(T::Boolean) }
def eql?(other)
self.class == other.class && self == other
case other
when self.class
self == other
else false
end
end
sig { override.returns(Integer) }
def hash
[system, standardized_arch].hash
end
@ -196,11 +220,12 @@ module Utils
arch_to_symbol(standardized_arch)
end
sig { returns(String) }
sig { override.returns(String) }
def to_s
to_sym.to_s
end
sig { returns(Symbol) }
def to_unstandardized_sym
# Never allow these generic names
return to_sym if [:intel, :arm].include? arch
@ -211,7 +236,7 @@ module Utils
sig { returns(MacOSVersion) }
def to_macos_version
@to_macos_version ||= MacOSVersion.from_symbol(system)
@to_macos_version ||= T.let(MacOSVersion.from_symbol(system), T.nilable(MacOSVersion))
end
sig { returns(T::Boolean) }
@ -280,14 +305,20 @@ module Utils
sig { returns(T.any(Symbol, String)) }
attr_reader :cellar
sig { params(tag: Utils::Bottles::Tag, checksum: Checksum, cellar: T.any(Symbol, String)).void }
def initialize(tag:, checksum:, cellar:)
@tag = tag
@checksum = checksum
@cellar = cellar
end
sig { override.params(other: BasicObject).returns(T::Boolean) }
def ==(other)
self.class == other.class && tag == other.tag && checksum == other.checksum && cellar == other.cellar
case other
when self.class
tag == other.tag && checksum == other.checksum && cellar == other.cellar
else false
end
end
alias eql? ==
end
@ -304,8 +335,13 @@ module Utils
@tag_specs.keys
end
sig { override.params(other: BasicObject).returns(T::Boolean) }
def ==(other)
self.class == other.class && @tag_specs == other.instance_variable_get(:@tag_specs)
case other
when self.class
@tag_specs == other.tag_specs
else false
end
end
alias eql? ==
@ -335,8 +371,14 @@ module Utils
@tag_specs[tag] if tag
end
protected
sig { returns(T::Hash[Utils::Bottles::Tag, Utils::Bottles::TagSpecification]) }
attr_reader :tag_specs
private
sig { params(tag: Utils::Bottles::Tag, no_older_versions: T::Boolean).returns(T.nilable(Utils::Bottles::Tag)) }
def find_matching_tag(tag, no_older_versions: false)
if @tag_specs.key?(tag)
tag