Merge pull request #20474 from Homebrew/more_types

Add more Sorbet types
This commit is contained in:
Mike McQuaid 2025-08-15 18:40:12 +00:00 committed by GitHub
commit 670c5e0d07
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 116 additions and 33 deletions

View File

@ -4,18 +4,27 @@
class BottleSpecification class BottleSpecification
RELOCATABLE_CELLARS = [:any, :any_skip_relocation].freeze RELOCATABLE_CELLARS = [:any, :any_skip_relocation].freeze
sig { returns(T.nilable(Tap)) }
attr_accessor :tap attr_accessor :tap
attr_reader :collector, :root_url_specs, :repository
attr_reader :collector
sig { returns(T::Hash[String, T.untyped]) }
attr_reader :root_url_specs
sig { returns(String) }
attr_reader :repository
sig { void } sig { void }
def initialize def initialize
@rebuild = 0 @rebuild = T.let(0, Integer)
@repository = Homebrew::DEFAULT_REPOSITORY @repository = T.let(Homebrew::DEFAULT_REPOSITORY, String)
@collector = Utils::Bottles::Collector.new @collector = T.let(Utils::Bottles::Collector.new, Utils::Bottles::Collector)
@root_url_specs = {} @root_url_specs = T.let({}, T::Hash[String, T.untyped])
@root_url = T.let(nil, T.nilable(String))
end end
sig { params(val: Integer).returns(T.nilable(Integer)) } sig { params(val: Integer).returns(Integer) }
def rebuild(val = T.unsafe(nil)) def rebuild(val = T.unsafe(nil))
val.nil? ? @rebuild : @rebuild = val val.nil? ? @rebuild : @rebuild = val
end end

View File

@ -171,7 +171,7 @@ module Homebrew
stable = formula.stable stable = formula.stable
if resource_name == "patch" if resource_name == "patch"
patch_hashes = stable&.patches&.filter_map { _1.resource.version if _1.external? } patch_hashes = stable&.patches&.filter_map { T.cast(_1, ExternalPatch).resource.version if _1.external? }
return true unless patch_hashes&.include?(Checksum.new(version.to_s)) return true unless patch_hashes&.include?(Checksum.new(version.to_s))
elsif resource_name && stable && (resource_version = stable.resources[resource_name]&.version) elsif resource_name && stable && (resource_version = stable.resources[resource_name]&.version)
return true if resource_version != version return true if resource_version != version

View File

@ -5,12 +5,19 @@ require "options"
# Shared functions for classes which can be depended upon. # Shared functions for classes which can be depended upon.
module Dependable module Dependable
extend T::Helpers
# `:run` and `:linked` are no longer used but keep them here to avoid their # `:run` and `:linked` are no longer used but keep them here to avoid their
# misuse in future. # misuse in future.
RESERVED_TAGS = [:build, :optional, :recommended, :run, :test, :linked, :implicit].freeze RESERVED_TAGS = [:build, :optional, :recommended, :run, :test, :linked, :implicit].freeze
attr_reader :tags attr_reader :tags
abstract!
sig { abstract.returns(T::Array[String]) }
def option_names; end
def build? def build?
tags.include? :build tags.include? :build
end end

View File

@ -80,10 +80,10 @@ class DependencyCollector
parse_spec(spec, Array(tags)) parse_spec(spec, Array(tags))
end end
sig { params(related_formula_names: T::Array[String]).returns(T.nilable(Dependency)) } sig { params(related_formula_names: T::Set[String]).returns(T.nilable(Dependency)) }
def gcc_dep_if_needed(related_formula_names); end def gcc_dep_if_needed(related_formula_names); end
sig { params(related_formula_names: T::Array[String]).returns(T.nilable(Dependency)) } sig { params(related_formula_names: T::Set[String]).returns(T.nilable(Dependency)) }
def glibc_dep_if_needed(related_formula_names); end def glibc_dep_if_needed(related_formula_names); end
def git_dep_if_needed(tags) def git_dep_if_needed(tags)

View File

@ -32,7 +32,7 @@ module Downloadable
@download_name = T.let(nil, T.nilable(String)) @download_name = T.let(nil, T.nilable(String))
end end
sig { params(other: Object).void } sig { overridable.params(other: Downloadable).void }
def initialize_dup(other) def initialize_dup(other)
super super
@checksum = @checksum.dup @checksum = @checksum.dup

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:disable Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module OS module OS
@ -30,6 +30,7 @@ module OS
def add_global_deps_to_spec(spec) def add_global_deps_to_spec(spec)
return unless ::DevelopmentTools.needs_build_formulae? return unless ::DevelopmentTools.needs_build_formulae?
@global_deps ||= T.let(nil, T.nilable(T::Array[Dependency]))
@global_deps ||= begin @global_deps ||= begin
dependency_collector = spec.dependency_collector dependency_collector = spec.dependency_collector
related_formula_names = Set.new([ related_formula_names = Set.new([

View File

@ -37,7 +37,7 @@ class Resource
instance_eval(&block) if block instance_eval(&block) if block
end end
sig { params(other: Object).void } sig { override.params(other: T.any(Resource, Downloadable)).void }
def initialize_dup(other) def initialize_dup(other)
super super
@name = @name.dup @name = @name.dup

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "resource" require "resource"
@ -20,13 +20,46 @@ class SoftwareSpec
extend Forwardable extend Forwardable
include OnSystem::MacOSAndLinux include OnSystem::MacOSAndLinux
PREDEFINED_OPTIONS = { PREDEFINED_OPTIONS = T.let({
universal: Option.new("universal", "Build a universal binary"), universal: Option.new("universal", "Build a universal binary"),
cxx11: Option.new("c++11", "Build using C++11 mode"), cxx11: Option.new("c++11", "Build using C++11 mode"),
}.freeze }.freeze, T::Hash[T.any(Symbol, String), Option])
attr_reader :name, :full_name, :owner, :build, :resources, :patches, :options, :deprecated_flags, sig { returns(T.nilable(String)) }
:deprecated_options, :dependency_collector, :bottle_specification, :compiler_failures attr_reader :name
sig { returns(T.nilable(String)) }
attr_reader :full_name
sig { returns(T.nilable(T.any(Formula, Cask::Cask))) }
attr_reader :owner
sig { returns(BuildOptions) }
attr_reader :build
sig { returns(T::Hash[String, Resource]) }
attr_reader :resources
sig { returns(T::Array[T.any(EmbeddedPatch, ExternalPatch)]) }
attr_reader :patches
sig { returns(Options) }
attr_reader :options
sig { returns(T::Array[DeprecatedOption]) }
attr_reader :deprecated_flags
sig { returns(T::Array[DeprecatedOption]) }
attr_reader :deprecated_options
sig { returns(DependencyCollector) }
attr_reader :dependency_collector
sig { returns(BottleSpecification) }
attr_reader :bottle_specification
sig { returns(T::Array[CompilerFailure]) }
attr_reader :compiler_failures
def_delegators :@resource, :stage, :fetch, :verify_download_integrity, :source_modified_time, def_delegators :@resource, :stage, :fetch, :verify_download_integrity, :source_modified_time,
:cached_download, :clear_cache, :checksum, :mirrors, :specs, :using, :version, :mirror, :cached_download, :clear_cache, :checksum, :mirrors, :specs, :using, :version, :mirror,
@ -34,23 +67,29 @@ class SoftwareSpec
def_delegators :@resource, :sha256 def_delegators :@resource, :sha256
sig { params(flags: T::Array[String]).void }
def initialize(flags: []) def initialize(flags: [])
super() super()
@name = T.let(nil, T.nilable(String))
@full_name = T.let(nil, T.nilable(String))
@owner = T.let(nil, T.nilable(T.any(Formula, Cask::Cask)))
# Ensure this is synced with `initialize_dup` and `freeze` (excluding simple objects like integers and booleans) # Ensure this is synced with `initialize_dup` and `freeze` (excluding simple objects like integers and booleans)
@resource = T.let(Resource::Formula.new, Resource::Formula) @resource = T.let(Resource::Formula.new, Resource::Formula)
@resources = {} @resources = T.let({}, T::Hash[String, Resource])
@dependency_collector = DependencyCollector.new @dependency_collector = T.let(DependencyCollector.new, DependencyCollector)
@bottle_specification = BottleSpecification.new @bottle_specification = T.let(BottleSpecification.new, BottleSpecification)
@patches = [] @patches = T.let([], T::Array[T.any(EmbeddedPatch, ExternalPatch)])
@options = Options.new @options = T.let(Options.new, Options)
@flags = flags @flags = T.let(flags, T::Array[String])
@deprecated_flags = [] @deprecated_flags = T.let([], T::Array[DeprecatedOption])
@deprecated_options = [] @deprecated_options = T.let([], T::Array[DeprecatedOption])
@build = BuildOptions.new(Options.create(@flags), options) @build = T.let(BuildOptions.new(Options.create(@flags), options), BuildOptions)
@compiler_failures = [] @compiler_failures = T.let([], T::Array[CompilerFailure])
end end
sig { override.params(other: T.any(SoftwareSpec, Downloadable)).void }
def initialize_dup(other) def initialize_dup(other)
super super
@resource = @resource.dup @resource = @resource.dup
@ -66,6 +105,7 @@ class SoftwareSpec
@compiler_failures = @compiler_failures.dup @compiler_failures = @compiler_failures.dup
end end
sig { override.returns(T.self_type) }
def freeze def freeze
@resource.freeze @resource.freeze
@resources.freeze @resources.freeze
@ -81,6 +121,7 @@ class SoftwareSpec
super super
end end
sig { params(owner: T.any(Formula, Cask::Cask)).void }
def owner=(owner) def owner=(owner)
@name = owner.name @name = owner.name
@full_name = owner.full_name @full_name = owner.full_name
@ -106,23 +147,35 @@ class SoftwareSpec
@resource.url @resource.url
end end
sig { returns(T::Boolean) }
def bottle_defined? def bottle_defined?
!bottle_specification.collector.tags.empty? !bottle_specification.collector.tags.empty?
end end
sig { params(tag: T.nilable(Utils::Bottles::Tag)).returns(T::Boolean) }
def bottle_tag?(tag = nil) def bottle_tag?(tag = nil)
bottle_specification.tag?(Utils::Bottles.tag(tag)) bottle_specification.tag?(Utils::Bottles.tag(tag))
end end
sig { params(tag: T.nilable(Utils::Bottles::Tag)).returns(T::Boolean) }
def bottled?(tag = nil) def bottled?(tag = nil)
bottle_tag?(tag) && return false unless bottle_tag?(tag)
(tag.present? || bottle_specification.compatible_locations? || owner.force_bottle)
return true if tag.present?
return true if bottle_specification.compatible_locations?
owner = self.owner
return false unless owner.is_a?(Formula)
owner.force_bottle
end end
sig { params(block: T.proc.bind(BottleSpecification).void).void }
def bottle(&block) def bottle(&block)
bottle_specification.instance_eval(&block) bottle_specification.instance_eval(&block)
end end
sig { params(name: String).returns(T::Boolean) }
def resource_defined?(name) def resource_defined?(name)
resources.key?(name) resources.key?(name)
end end
@ -149,15 +202,14 @@ class SoftwareSpec
end end
end end
sig { params(name: String).returns(T::Boolean) }
def option_defined?(name) def option_defined?(name)
options.include?(name) options.include?(name)
end end
sig { params(name: T.any(Symbol, String), description: String).void }
def option(name, description = "") def option(name, description = "")
opt = PREDEFINED_OPTIONS.fetch(name) do opt = PREDEFINED_OPTIONS.fetch(name) do
unless name.is_a?(String)
raise ArgumentError, "option name must be string or symbol; got a #{name.class}: #{name}"
end
raise ArgumentError, "option name is required" if name.empty? raise ArgumentError, "option name is required" if name.empty?
raise ArgumentError, "option name must be longer than one character: #{name}" if name.length <= 1 raise ArgumentError, "option name must be longer than one character: #{name}" if name.length <= 1
raise ArgumentError, "option name must not start with dashes: #{name}" if name.start_with?("-") raise ArgumentError, "option name must not start with dashes: #{name}" if name.start_with?("-")
@ -167,6 +219,7 @@ class SoftwareSpec
options << opt options << opt
end end
sig { params(hash: T::Hash[T.any(String, Symbol), T.any(String, Symbol)]).void }
def deprecated_option(hash) def deprecated_option(hash)
raise ArgumentError, "deprecated_option hash must not be empty" if hash.empty? raise ArgumentError, "deprecated_option hash must not be empty" if hash.empty?
@ -189,6 +242,7 @@ class SoftwareSpec
@build = BuildOptions.new(Options.create(@flags), options) @build = BuildOptions.new(Options.create(@flags), options)
end end
sig { params(spec: T.any(String, Symbol, T::Hash[String, T.untyped], T::Class[Requirement], Dependable)).void }
def depends_on(spec) def depends_on(spec)
dep = dependency_collector.add(spec) dep = dependency_collector.add(spec)
add_dep_option(dep) if dep add_dep_option(dep) if dep
@ -214,14 +268,17 @@ class SoftwareSpec
depends_on UsesFromMacOSDependency.new(dep, tags, bounds:) depends_on UsesFromMacOSDependency.new(dep, tags, bounds:)
end end
sig { returns(Dependencies) }
def deps def deps
dependency_collector.deps.dup_without_system_deps dependency_collector.deps.dup_without_system_deps
end end
sig { returns(Dependencies) }
def declared_deps def declared_deps
dependency_collector.deps dependency_collector.deps
end end
sig { returns(T::Array[Dependable]) }
def recursive_dependencies def recursive_dependencies
deps_f = [] deps_f = []
recursive_dependencies = deps.filter_map do |dep| recursive_dependencies = deps.filter_map do |dep|
@ -239,15 +296,21 @@ class SoftwareSpec
recursive_dependencies recursive_dependencies
end end
sig { returns(Requirements) }
def requirements def requirements
dependency_collector.requirements dependency_collector.requirements
end end
sig { returns(Requirements) }
def recursive_requirements def recursive_requirements
Requirement.expand(self) Requirement.expand(self)
end end
def patch(strip = :p1, src = nil, &block) sig {
params(strip: T.any(Symbol, String), src: T.nilable(T.any(String, Symbol)),
block: T.nilable(T.proc.bind(Patch).void)).void
}
def patch(strip = :p1, src = T.unsafe(nil), &block)
p = Patch.create(strip, src, &block) p = Patch.create(strip, src, &block)
return if p.is_a?(ExternalPatch) && p.url.blank? return if p.is_a?(ExternalPatch) && p.url.blank?
@ -255,16 +318,19 @@ class SoftwareSpec
patches << p patches << p
end end
sig { params(compiler: T.any(T::Hash[Symbol, String], Symbol), block: T.nilable(T.proc.bind(CompilerFailure).void)).void }
def fails_with(compiler, &block) def fails_with(compiler, &block)
compiler_failures << CompilerFailure.create(compiler, &block) compiler_failures << CompilerFailure.create(compiler, &block)
end end
sig { params(standards: T::Array[String]).void }
def needs(*standards) def needs(*standards)
standards.each do |standard| standards.each do |standard|
compiler_failures.concat CompilerFailure.for_standard(standard) compiler_failures.concat CompilerFailure.for_standard(standard)
end end
end end
sig { params(dep: Dependable).void }
def add_dep_option(dep) def add_dep_option(dep)
dep.option_names.each do |name| dep.option_names.each do |name|
if dep.optional? && !option_defined?("with-#{name}") if dep.optional? && !option_defined?("with-#{name}")