development_tools: add type signatures

This commit is contained in:
fn ⌃ ⌥ 2021-09-29 15:12:53 -07:00
parent 4e6919b734
commit 61a7ffb999
8 changed files with 97 additions and 16 deletions

View File

@ -1,9 +1,14 @@
# typed: false # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "version"
# @private # @private
class DevelopmentTools class DevelopmentTools
class << self class << self
extend T::Sig
sig { params(tool: String).returns(T.nilable(Pathname)) }
def locate(tool) def locate(tool)
# Don't call tools (cc, make, strip, etc.) directly! # Don't call tools (cc, make, strip, etc.) directly!
# Give the name of the binary you look for as a string to this method # Give the name of the binary you look for as a string to this method
@ -18,17 +23,27 @@ class DevelopmentTools
end end
end end
sig { returns(T::Boolean) }
def installed? def installed?
locate("clang") || locate("gcc") locate("clang").present? || locate("gcc").present?
end end
sig { returns(String) }
def installation_instructions def installation_instructions
"Install Clang or run `brew install gcc`." "Install Clang or run `brew install gcc`."
end end
alias custom_installation_instructions installation_instructions
sig { returns(String) }
def custom_installation_instructions
installation_instructions
end
# TODO: This method appears to be unused. Can it be deleted?
sig { returns(T.nilable(String)) }
def default_cc def default_cc
cc = DevelopmentTools.locate "cc" cc = DevelopmentTools.locate "cc"
return if cc.nil?
begin begin
cc.realpath.basename.to_s cc.realpath.basename.to_s
rescue rescue
@ -36,10 +51,12 @@ class DevelopmentTools
end end
end end
sig { returns(Symbol) }
def default_compiler def default_compiler
:clang :clang
end end
sig { returns(Version) }
def clang_version def clang_version
@clang_version ||= if (path = locate("clang")) && @clang_version ||= if (path = locate("clang")) &&
(build_version = `#{path} --version`[/(?:clang|LLVM) version (\d+\.\d(?:\.\d)?)/, 1]) (build_version = `#{path} --version`[/(?:clang|LLVM) version (\d+\.\d(?:\.\d)?)/, 1])
@ -49,6 +66,7 @@ class DevelopmentTools
end end
end end
sig { returns(Version) }
def clang_build_version def clang_build_version
@clang_build_version ||= if (path = locate("clang")) && @clang_build_version ||= if (path = locate("clang")) &&
(build_version = `#{path} --version`[ (build_version = `#{path} --version`[
@ -59,6 +77,7 @@ class DevelopmentTools
end end
end end
sig { returns(Version) }
def llvm_clang_build_version def llvm_clang_build_version
@llvm_clang_build_version ||= begin @llvm_clang_build_version ||= begin
path = Formulary.factory("llvm").opt_prefix/"bin/clang" path = Formulary.factory("llvm").opt_prefix/"bin/clang"
@ -71,6 +90,7 @@ class DevelopmentTools
end end
end end
sig { params(cc: String).returns(Version) }
def non_apple_gcc_version(cc) def non_apple_gcc_version(cc)
(@non_apple_gcc_version ||= {}).fetch(cc) do (@non_apple_gcc_version ||= {}).fetch(cc) do
path = HOMEBREW_PREFIX/"opt/#{CompilerSelector.preferred_gcc}/bin"/cc path = HOMEBREW_PREFIX/"opt/#{CompilerSelector.preferred_gcc}/bin"/cc
@ -85,19 +105,23 @@ class DevelopmentTools
end end
end end
sig { void }
def clear_version_cache def clear_version_cache
@clang_version = @clang_build_version = nil @clang_version = @clang_build_version = nil
@non_apple_gcc_version = {} @non_apple_gcc_version = {}
end end
sig { returns(T::Boolean) }
def curl_handles_most_https_certificates? def curl_handles_most_https_certificates?
true true
end end
sig { returns(T::Boolean) }
def subversion_handles_most_https_certificates? def subversion_handles_most_https_certificates?
true true
end end
sig { returns(T::Hash[String, T.untyped]) }
def build_system_info def build_system_info
{ {
"os" => ENV["HOMEBREW_SYSTEM"], "os" => ENV["HOMEBREW_SYSTEM"],

View File

@ -5,6 +5,7 @@ class DevelopmentTools
class << self class << self
extend T::Sig extend T::Sig
sig { params(tool: String).returns(T.nilable(Pathname)) }
def locate(tool) def locate(tool)
(@locate ||= {}).fetch(tool) do |key| (@locate ||= {}).fetch(tool) do |key|
@locate[key] = if (path = HOMEBREW_PREFIX/"bin/#{tool}").executable? @locate[key] = if (path = HOMEBREW_PREFIX/"bin/#{tool}").executable?
@ -20,6 +21,7 @@ class DevelopmentTools
:gcc :gcc
end end
sig { returns(T::Hash[String, T.untyped]) }
def build_system_info def build_system_info
generic_build_system_info.merge({ generic_build_system_info.merge({
"glibc_version" => OS::Linux::Glibc.version, "glibc_version" => OS::Linux::Glibc.version,

View File

@ -12,6 +12,7 @@ class DevelopmentTools
undef installed?, default_compiler, curl_handles_most_https_certificates?, undef installed?, default_compiler, curl_handles_most_https_certificates?,
subversion_handles_most_https_certificates? subversion_handles_most_https_certificates?
sig { params(tool: String).returns(T.nilable(Pathname)) }
def locate(tool) def locate(tool)
(@locate ||= {}).fetch(tool) do |key| (@locate ||= {}).fetch(tool) do |key|
@locate[key] = if (located_tool = generic_locate(tool)) @locate[key] = if (located_tool = generic_locate(tool))
@ -26,6 +27,7 @@ class DevelopmentTools
# Checks if the user has any developer tools installed, either via Xcode # Checks if the user has any developer tools installed, either via Xcode
# or the CLT. Convenient for guarding against formula builds when building # or the CLT. Convenient for guarding against formula builds when building
# is impossible. # is impossible.
sig { returns(T::Boolean) }
def installed? def installed?
MacOS::Xcode.installed? || MacOS::CLT.installed? MacOS::Xcode.installed? || MacOS::CLT.installed?
end end
@ -62,6 +64,7 @@ class DevelopmentTools
EOS EOS
end end
sig { returns(T::Hash[String, T.untyped]) }
def build_system_info def build_system_info
build_info = { build_info = {
"xcode" => MacOS::Xcode.version.to_s.presence, "xcode" => MacOS::Xcode.version.to_s.presence,

View File

@ -18,8 +18,8 @@ module SharedEnvExtension
def no_weak_imports_support? def no_weak_imports_support?
return false unless compiler == :clang return false unless compiler == :clang
return false if MacOS::Xcode.version && MacOS::Xcode.version < "8.0" return false if !MacOS::Xcode.version.null? && MacOS::Xcode.version < "8.0"
return false if MacOS::CLT.version && MacOS::CLT.version < "8.0" return false if !MacOS::CLT.version.null? && MacOS::CLT.version < "8.0"
true true
end end

View File

@ -178,6 +178,7 @@ module OS
paths.uniq paths.uniq
end end
sig { params(ids: String).returns(T.nilable(Pathname)) }
def app_with_bundle_id(*ids) def app_with_bundle_id(*ids)
path = mdfind(*ids) path = mdfind(*ids)
.reject { |p| p.include?("/Backups.backupdb/") } .reject { |p| p.include?("/Backups.backupdb/") }
@ -185,6 +186,7 @@ module OS
Pathname.new(path) if path.present? Pathname.new(path) if path.present?
end end
sig { params(ids: String).returns(T::Array[String]) }
def mdfind(*ids) def mdfind(*ids)
(@mdfind ||= {}).fetch(ids) do (@mdfind ||= {}).fetch(ids) do
@mdfind[ids] = Utils.popen_read("/usr/bin/mdfind", mdfind_query(*ids)).split("\n") @mdfind[ids] = Utils.popen_read("/usr/bin/mdfind", mdfind_query(*ids)).split("\n")
@ -197,6 +199,7 @@ module OS
end end
end end
sig { params(ids: String).returns(String) }
def mdfind_query(*ids) def mdfind_query(*ids)
ids.map! { |id| "kMDItemCFBundleIdentifier == #{id}" }.join(" || ") ids.map! { |id| "kMDItemCFBundleIdentifier == #{id}" }.join(" || ")
end end

View File

@ -9,11 +9,21 @@ module OS
# #
# @api private # @api private
class SDK class SDK
extend T::Sig
# 11.x SDKs are explicitly excluded - we want the MacOSX11.sdk symlink instead. # 11.x SDKs are explicitly excluded - we want the MacOSX11.sdk symlink instead.
VERSIONED_SDK_REGEX = /MacOSX(10\.\d+|\d+)\.sdk$/.freeze VERSIONED_SDK_REGEX = /MacOSX(10\.\d+|\d+)\.sdk$/.freeze
attr_reader :version, :path, :source sig { returns(OS::Mac::Version) }
attr_reader :version
sig { returns(Pathname) }
attr_reader :path
sig { returns(Symbol) }
attr_reader :source
sig { params(version: OS::Mac::Version, path: T.any(String, Pathname), source: Symbol).void }
def initialize(version, path, source) def initialize(version, path, source)
@version = version @version = version
@path = Pathname.new(path) @path = Pathname.new(path)
@ -25,8 +35,14 @@ module OS
# #
# @api private # @api private
class BaseSDKLocator class BaseSDKLocator
extend T::Sig
extend T::Helpers
abstract!
class NoSDKError < StandardError; end class NoSDKError < StandardError; end
sig { params(v: OS::Mac::Version).returns(SDK) }
def sdk_for(v) def sdk_for(v)
sdk = all_sdks.find { |s| s.version == v } sdk = all_sdks.find { |s| s.version == v }
raise NoSDKError if sdk.nil? raise NoSDKError if sdk.nil?
@ -34,6 +50,7 @@ module OS
sdk sdk
end end
sig { returns(T::Array[SDK]) }
def all_sdks def all_sdks
return @all_sdks if @all_sdks return @all_sdks if @all_sdks
@ -62,6 +79,7 @@ module OS
@all_sdks @all_sdks
end end
sig { params(v: T.nilable(OS::Mac::Version)).returns(T.nilable(SDK)) }
def sdk_if_applicable(v = nil) def sdk_if_applicable(v = nil)
sdk = begin sdk = begin
if v.blank? if v.blank?
@ -81,20 +99,20 @@ module OS
sdk sdk
end end
def source sig { abstract.returns(Symbol) }
nil def source; end
end
private private
def sdk_prefix sig { abstract.returns(String) }
"" def sdk_prefix; end
end
sig { returns(T.nilable(SDK)) }
def latest_sdk def latest_sdk
all_sdks.max_by(&:version) all_sdks.max_by(&:version)
end end
sig { params(sdk_path: Pathname).returns(T.nilable(OS::Mac::Version)) }
def read_sdk_version(sdk_path) def read_sdk_version(sdk_path)
sdk_settings = sdk_path/"SDKSettings.json" sdk_settings = sdk_path/"SDKSettings.json"
sdk_settings_string = sdk_settings.read if sdk_settings.exist? sdk_settings_string = sdk_settings.read if sdk_settings.exist?
@ -129,13 +147,14 @@ module OS
class XcodeSDKLocator < BaseSDKLocator class XcodeSDKLocator < BaseSDKLocator
extend T::Sig extend T::Sig
sig { returns(Symbol) } sig { override.returns(Symbol) }
def source def source
:xcode :xcode
end end
private private
sig { override.returns(String) }
def sdk_prefix def sdk_prefix
@sdk_prefix ||= begin @sdk_prefix ||= begin
# Xcode.prefix is pretty smart, so let's look inside to find the sdk # Xcode.prefix is pretty smart, so let's look inside to find the sdk
@ -155,7 +174,7 @@ module OS
class CLTSDKLocator < BaseSDKLocator class CLTSDKLocator < BaseSDKLocator
extend T::Sig extend T::Sig
sig { returns(Symbol) } sig { override.returns(Symbol) }
def source def source
:clt :clt
end end
@ -169,6 +188,7 @@ module OS
# separate package, so we can't rely on their being present. # separate package, so we can't rely on their being present.
# This will only look up SDKs on Xcode 10 or newer, and still # This will only look up SDKs on Xcode 10 or newer, and still
# return nil SDKs for Xcode 9 and older. # return nil SDKs for Xcode 9 and older.
sig { override.returns(String) }
def sdk_prefix def sdk_prefix
@sdk_prefix ||= if CLT.provides_sdk? @sdk_prefix ||= if CLT.provides_sdk?
"#{CLT::PKG_PATH}/SDKs" "#{CLT::PKG_PATH}/SDKs"

View File

@ -1,4 +1,4 @@
# typed: false # typed: true
# frozen_string_literal: true # frozen_string_literal: true
module OS module OS
@ -89,6 +89,7 @@ module OS
# Returns a Pathname object corresponding to Xcode.app's Developer # Returns a Pathname object corresponding to Xcode.app's Developer
# directory or nil if Xcode.app is not installed. # directory or nil if Xcode.app is not installed.
sig { returns(Pathname) }
def prefix def prefix
@prefix ||= @prefix ||=
begin begin
@ -109,6 +110,7 @@ module OS
Pathname("#{prefix}/Toolchains/XcodeDefault.xctoolchain") Pathname("#{prefix}/Toolchains/XcodeDefault.xctoolchain")
end end
sig { returns(T.nilable(Pathname)) }
def bundle_path def bundle_path
# Use the default location if it exists. # Use the default location if it exists.
return DEFAULT_BUNDLE_PATH if DEFAULT_BUNDLE_PATH.exist? return DEFAULT_BUNDLE_PATH if DEFAULT_BUNDLE_PATH.exist?
@ -124,18 +126,22 @@ module OS
!prefix.nil? !prefix.nil?
end end
sig { returns(XcodeSDKLocator) }
def sdk_locator def sdk_locator
@sdk_locator ||= XcodeSDKLocator.new @sdk_locator ||= XcodeSDKLocator.new
end end
sig { params(v: T.nilable(MacOS::Version)).returns(T.nilable(SDK)) }
def sdk(v = nil) def sdk(v = nil)
sdk_locator.sdk_if_applicable(v) sdk_locator.sdk_if_applicable(v)
end end
sig { params(v: T.nilable(MacOS::Version)).returns(T.nilable(Pathname)) }
def sdk_path(v = nil) def sdk_path(v = nil)
sdk(v)&.path sdk(v)&.path
end end
sig { returns(String) }
def installation_instructions def installation_instructions
if OS::Mac.version.prerelease? if OS::Mac.version.prerelease?
<<~EOS <<~EOS
@ -163,6 +169,7 @@ module OS
end end
end end
sig { returns(::Version) }
def version def version
# may return a version string # may return a version string
# that is guessed based on the compiler, so do not # that is guessed based on the compiler, so do not
@ -174,6 +181,7 @@ module OS
end end
end end
sig { returns(T.nilable(String)) }
def detect_version def detect_version
# This is a separate function as you can't cache the value out of a block # This is a separate function as you can't cache the value out of a block
# if return is used in the middle, which we do many times in here. # if return is used in the middle, which we do many times in here.
@ -231,6 +239,7 @@ module OS
end end
end end
sig { returns(T::Boolean) }
def default_prefix? def default_prefix?
prefix.to_s == "/Applications/Xcode.app/Contents/Developer" prefix.to_s == "/Applications/Xcode.app/Contents/Developer"
end end
@ -255,26 +264,32 @@ module OS
!version.null? !version.null?
end end
sig { returns(T::Boolean) }
def separate_header_package? def separate_header_package?
version >= "10" && MacOS.version >= "10.14" version >= "10" && MacOS.version >= "10.14"
end end
sig { returns(T::Boolean) }
def provides_sdk? def provides_sdk?
version >= "8" version >= "8"
end end
sig { returns(CLTSDKLocator) }
def sdk_locator def sdk_locator
@sdk_locator ||= CLTSDKLocator.new @sdk_locator ||= CLTSDKLocator.new
end end
sig { params(v: T.nilable(MacOS::Version)).returns(T.nilable(SDK)) }
def sdk(v = nil) def sdk(v = nil)
sdk_locator.sdk_if_applicable(v) sdk_locator.sdk_if_applicable(v)
end end
sig { params(v: T.nilable(MacOS::Version)).returns(T.nilable(Pathname)) }
def sdk_path(v = nil) def sdk_path(v = nil)
sdk(v)&.path sdk(v)&.path
end end
sig { returns(String) }
def installation_instructions def installation_instructions
if MacOS.version == "10.14" if MacOS.version == "10.14"
# This is not available from `xcode-select` # This is not available from `xcode-select`
@ -344,6 +359,7 @@ module OS
end end
end end
sig { returns(T::Boolean) }
def below_minimum_version? def below_minimum_version?
return false unless installed? return false unless installed?
@ -358,11 +374,13 @@ module OS
::Version.new(clang_version) < latest_clang_version ::Version.new(clang_version) < latest_clang_version
end end
sig { returns(T.nilable(String)) }
def detect_clang_version def detect_clang_version
version_output = Utils.popen_read("#{PKG_PATH}/usr/bin/clang", "--version") version_output = Utils.popen_read("#{PKG_PATH}/usr/bin/clang", "--version")
version_output[/clang-(\d+\.\d+\.\d+(\.\d+)?)/, 1] version_output[/clang-(\d+\.\d+\.\d+(\.\d+)?)/, 1]
end end
sig { returns(T.nilable(String)) }
def detect_version_from_clang_version def detect_version_from_clang_version
detect_clang_version&.sub(/^(\d+)0(\d)\./, "\\1.\\2.") detect_clang_version&.sub(/^(\d+)0(\d)\./, "\\1.\\2.")
end end
@ -370,6 +388,7 @@ module OS
# Version string (a pretty long one) of the CLT package. # Version string (a pretty long one) of the CLT package.
# Note that the different ways of installing the CLTs lead to different # Note that the different ways of installing the CLTs lead to different
# version numbers. # version numbers.
sig { returns(::Version) }
def version def version
if @version ||= detect_version if @version ||= detect_version
::Version.new @version ::Version.new @version
@ -378,8 +397,9 @@ module OS
end end
end end
sig { returns(T.nilable(String)) }
def detect_version def detect_version
version = nil version = T.let(nil, T.nilable(String))
[EXECUTABLE_PKG_ID, MAVERICKS_NEW_PKG_ID].each do |id| [EXECUTABLE_PKG_ID, MAVERICKS_NEW_PKG_ID].each do |id|
next unless File.exist?("#{PKG_PATH}/usr/bin/clang") next unless File.exist?("#{PKG_PATH}/usr/bin/clang")

View File

@ -0,0 +1,9 @@
# typed: strict
module OS
module Mac
module Xcode
include Kernel
end
end
end