Skip to content

Commit

Permalink
Merge pull request #17636 from Homebrew/sorbet-strict-public-apis
Browse files Browse the repository at this point in the history
  • Loading branch information
issyl0 authored Jul 12, 2024
2 parents d9da0ca + edb8055 commit 1479013
Show file tree
Hide file tree
Showing 40 changed files with 260 additions and 151 deletions.
50 changes: 26 additions & 24 deletions Library/Homebrew/development_tools.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "version"
Expand All @@ -16,7 +16,7 @@ def locate(tool)
# Don't call tools (cc, make, strip, etc.) directly!
# Give the name of the binary you look for as a string to this method
# in order to get the full path back as a Pathname.
(@locate ||= {}).fetch(tool) do |key|
(@locate ||= T.let({}, T.nilable(T::Hash[T.any(String, Symbol), T.untyped]))).fetch(tool) do |key|
@locate[key] = if File.executable?((path = "/usr/bin/#{tool}"))
Pathname.new path
# Homebrew GCCs most frequently; much faster to check this before xcrun
Expand Down Expand Up @@ -62,55 +62,57 @@ def default_compiler
# @api public
sig { returns(Version) }
def clang_version
@clang_version ||= if (path = locate("clang")) &&
(build_version = `#{path} --version`[/(?:clang|LLVM) version (\d+\.\d(?:\.\d)?)/, 1])
Version.new build_version
else
Version::NULL
end
@clang_version ||= T.let(
if (path = locate("clang")) &&
(build_version = `#{path} --version`[/(?:clang|LLVM) version (\d+\.\d(?:\.\d)?)/, 1])
Version.new(build_version)
else
Version::NULL
end, T.nilable(Version)
)
end

# Get the Clang build version.
#
# @api public
sig { returns(Version) }
def clang_build_version
@clang_build_version ||= if (path = locate("clang")) &&
(build_version = `#{path} --version`[
%r{clang(-| version [^ ]+ \(tags/RELEASE_)(\d{2,})}, 2])
Version.new build_version
else
Version::NULL
end
@clang_build_version ||= T.let(
if (path = locate("clang")) &&
(build_version = `#{path} --version`[%r{clang(-| version [^ ]+ \(tags/RELEASE_)(\d{2,})}, 2])
Version.new(build_version)
else
Version::NULL
end, T.nilable(Version)
)
end

# Get the LLVM Clang build version.
#
# @api public
sig { returns(Version) }
def llvm_clang_build_version
@llvm_clang_build_version ||= begin
@llvm_clang_build_version ||= T.let(begin
path = Formulary.factory("llvm").opt_prefix/"bin/clang"
if path.executable? &&
(build_version = `#{path} --version`[/clang version (\d+\.\d\.\d)/, 1])
Version.new build_version
if path.executable? && (build_version = `#{path} --version`[/clang version (\d+\.\d\.\d)/, 1])
Version.new(build_version)
else
Version::NULL
end
end
end, T.nilable(Version))
end

# Get the GCC version.
#
# @api internal
sig { params(cc: String).returns(Version) }
def gcc_version(cc)
(@gcc_version ||= {}).fetch(cc) do
(@gcc_version ||= T.let({}, T.nilable(T::Hash[String, Version]))).fetch(cc) do
path = HOMEBREW_PREFIX/"opt/#{CompilerSelector.preferred_gcc}/bin"/cc
path = locate(cc) unless path.exist?
version = if path &&
(build_version = `#{path} --version`[/gcc(?:(?:-\d+(?:\.\d)?)? \(.+\))? (\d+\.\d\.\d)/, 1])
Version.new build_version
Version.new(build_version)
else
Version::NULL
end
Expand All @@ -120,8 +122,8 @@ def gcc_version(cc)

sig { void }
def clear_version_cache
@clang_version = @clang_build_version = nil
@gcc_version = {}
@clang_version = @clang_build_version = T.let(nil, T.nilable(Version))
@gcc_version = T.let({}, T.nilable(T::Hash[String, Version]))
end

sig { returns(T::Boolean) }
Expand Down
2 changes: 1 addition & 1 deletion Library/Homebrew/extend/os/mac/unpack_strategy/zip.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Zip
module MacOSZipExtension
private

sig { params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).returns(T.untyped) }
sig { params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).void }
def extract_to_dir(unpack_dir, basename:, verbose:)
with_env(TZ: "UTC") do
if merge_xattrs && contains_extended_attributes?(path)
Expand Down
8 changes: 6 additions & 2 deletions Library/Homebrew/formula_assertions.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true
# typed: strict
# frozen_string_literal: true

module Homebrew
Expand All @@ -10,15 +10,18 @@ module Assertions
require "minitest/assertions"
include ::Minitest::Assertions

sig { params(assertions: Integer).returns(Integer) }
attr_writer :assertions

sig { returns(Integer) }
def assertions
@assertions ||= 0
@assertions ||= T.let(0, T.nilable(Integer))
end

# Returns the output of running cmd and asserts the exit status.
#
# @api public
sig { params(cmd: String, result: Integer).returns(String) }
def shell_output(cmd, result = 0)
ohai cmd
output = `#{cmd}`
Expand All @@ -33,6 +36,7 @@ def shell_output(cmd, result = 0)
# optionally asserts the exit status.
#
# @api public
sig { params(cmd: String, input: T.nilable(String), result: T.nilable(Integer)).returns(String) }
def pipe_output(cmd, input = nil, result = nil)
ohai cmd
output = IO.popen(cmd, "w+") do |pipe|
Expand Down
81 changes: 60 additions & 21 deletions Library/Homebrew/unpack_strategy.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "system_command"

# Module containing all available strategies for unpacking archives.
module UnpackStrategy
extend T::Helpers

include SystemCommand::Mixin
abstract!

# FIXME: Enable cop again when https://github.com/sorbet/sorbet/issues/3532 is fixed.
# rubocop:disable Style/MutableConstant
UnpackStrategyType = T.type_alias { T.all(T::Class[UnpackStrategy], UnpackStrategy::ClassMethods) }
# rubocop:enable Style/MutableConstant

module ClassMethods
extend T::Helpers
abstract!

sig { abstract.returns(T::Array[String]) }
def extensions; end

sig { abstract.params(path: Pathname).returns(T::Boolean) }
def can_extract?(path); end
end

mixes_in_class_methods(ClassMethods)

sig { returns(T.nilable(T::Array[UnpackStrategyType])) }
def self.strategies
@strategies ||= [
@strategies ||= T.let([
Tar, # Needs to be before Bzip2/Gzip/Xz/Lzma/Zstd.
Pax,
Gzip,
Expand Down Expand Up @@ -43,10 +62,11 @@ def self.strategies
Sit,
Rar,
Lha,
].freeze
].freeze, T.nilable(T::Array[UnpackStrategyType]))
end
private_class_method :strategies

sig { params(type: Symbol).returns(T.nilable(UnpackStrategyType)) }
def self.from_type(type)
type = {
naked: :uncompressed,
Expand All @@ -61,23 +81,31 @@ def self.from_type(type)
end
end

sig { params(extension: String).returns(T.nilable(UnpackStrategyType)) }
def self.from_extension(extension)
strategies.sort_by { |s| s.extensions.map(&:length).max || 0 }
.reverse
.find { |s| s.extensions.any? { |ext| extension.end_with?(ext) } }
return unless strategies

strategies&.sort_by { |s| s.extensions.map(&:length).max || 0 }
&.reverse
&.find { |s| s.extensions.any? { |ext| extension.end_with?(ext) } }
end

sig { params(path: Pathname).returns(T.nilable(UnpackStrategyType)) }
def self.from_magic(path)
strategies.find { |s| s.can_extract?(path) }
strategies&.find { |s| s.can_extract?(path) }
end

def self.detect(path, prioritize_extension: false, type: nil, ref_type: nil, ref: nil, merge_xattrs: nil)
sig {
params(path: Pathname, prioritize_extension: T::Boolean, type: T.nilable(Symbol), ref_type: T.nilable(Symbol),
ref: T.nilable(String), merge_xattrs: T::Boolean).returns(T.untyped)
}
def self.detect(path, prioritize_extension: false, type: nil, ref_type: nil, ref: nil, merge_xattrs: false)
strategy = from_type(type) if type

if prioritize_extension && path.extname.present?
strategy ||= from_extension(path.extname)
strategy ||= strategies.select { |s| s < Directory || s == Fossil }
.find { |s| s.can_extract?(path) }

strategy ||= strategies&.find { |s| (s < Directory || s == Fossil) && s.can_extract?(path) }
else
strategy ||= from_magic(path)
strategy ||= from_extension(path.extname)
Expand All @@ -88,24 +116,31 @@ def self.detect(path, prioritize_extension: false, type: nil, ref_type: nil, ref
strategy.new(path, ref_type:, ref:, merge_xattrs:)
end

attr_reader :path, :merge_xattrs
sig { returns(Pathname) }
attr_reader :path

def initialize(path, ref_type: nil, ref: nil, merge_xattrs: nil)
@path = Pathname(path).expand_path
@ref_type = ref_type
@ref = ref
@merge_xattrs = merge_xattrs
sig { returns(T::Boolean) }
attr_reader :merge_xattrs

sig {
params(path: T.any(String, Pathname), ref_type: T.nilable(Symbol), ref: T.nilable(String),
merge_xattrs: T::Boolean).void
}
def initialize(path, ref_type: nil, ref: nil, merge_xattrs: false)
@path = T.let(Pathname(path).expand_path, Pathname)
@ref_type = T.let(ref_type, T.nilable(Symbol))
@ref = T.let(ref, T.nilable(String))
@merge_xattrs = T.let(merge_xattrs, T::Boolean)
end

abstract!
sig { abstract.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).returns(T.untyped) }
sig { abstract.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).void }
def extract_to_dir(unpack_dir, basename:, verbose:); end
private :extract_to_dir

sig {
params(
to: T.nilable(Pathname), basename: T.nilable(T.any(String, Pathname)), verbose: T::Boolean,
).returns(T.untyped)
).void
}
def extract(to: nil, basename: nil, verbose: false)
basename ||= path.basename
Expand All @@ -131,7 +166,10 @@ def extract_nestedly(to: nil, basename: nil, verbose: false, prioritize_extensio
children = tmp_unpack_dir.children

if children.size == 1 && !children.fetch(0).directory?
s = UnpackStrategy.detect(children.first, prioritize_extension:)
first_child = children.first
next if first_child.nil?

s = UnpackStrategy.detect(first_child, prioritize_extension:)

s.extract_nestedly(to:, verbose:, prioritize_extension:)

Expand All @@ -149,6 +187,7 @@ def extract_nestedly(to: nil, basename: nil, verbose: false, prioritize_extensio
end
end

sig { returns(T::Array[String]) }
def dependencies
[]
end
Expand Down
10 changes: 6 additions & 4 deletions Library/Homebrew/unpack_strategy/air.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
# typed: true
# typed: strict
# frozen_string_literal: true

module UnpackStrategy
# Strategy for unpacking Adobe Air archives.
class Air
include UnpackStrategy

sig { returns(T::Array[String]) }
sig { override.returns(T::Array[String]) }
def self.extensions
[".air"]
end

sig { override.params(path: Pathname).returns(T::Boolean) }
def self.can_extract?(path)
mime_type = "application/vnd.adobe.air-application-installer-package+zip"
path.magic_number.match?(/.{59}#{Regexp.escape(mime_type)}/)
end

sig { returns(T.nilable(T::Array[Cask::Cask])) }
def dependencies
@dependencies ||= [Cask::CaskLoader.load("adobe-air")]
@dependencies ||= T.let([Cask::CaskLoader.load("adobe-air")], T.nilable(T::Array[Cask::Cask]))
end

AIR_APPLICATION_INSTALLER =
Expand All @@ -27,7 +29,7 @@ def dependencies

private

sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).returns(T.untyped) }
sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).void }
def extract_to_dir(unpack_dir, basename:, verbose:)
system_command! AIR_APPLICATION_INSTALLER,
args: ["-silent", "-location", unpack_dir, path],
Expand Down
7 changes: 4 additions & 3 deletions Library/Homebrew/unpack_strategy/bazaar.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require_relative "directory"

module UnpackStrategy
# Strategy for unpacking Bazaar archives.
class Bazaar < Directory
sig { override.params(path: Pathname).returns(T::Boolean) }
def self.can_extract?(path)
super && (path/".bzr").directory?
!!(super && (path/".bzr").directory?)
end

private

sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).returns(T.untyped) }
sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).void }
def extract_to_dir(unpack_dir, basename:, verbose:)
super

Expand Down
7 changes: 4 additions & 3 deletions Library/Homebrew/unpack_strategy/bzip2.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
# typed: true
# typed: strict
# frozen_string_literal: true

module UnpackStrategy
# Strategy for unpacking bzip2 archives.
class Bzip2
include UnpackStrategy

sig { returns(T::Array[String]) }
sig { override.returns(T::Array[String]) }
def self.extensions
[".bz2"]
end

sig { override.params(path: Pathname).returns(T::Boolean) }
def self.can_extract?(path)
path.magic_number.match?(/\ABZh/n)
end

private

sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).returns(T.untyped) }
sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).void }
def extract_to_dir(unpack_dir, basename:, verbose:)
FileUtils.cp path, unpack_dir/basename, preserve: true
quiet_flags = verbose ? [] : ["-q"]
Expand Down
Loading

0 comments on commit 1479013

Please sign in to comment.