Merge pull request #12149 from FnControlOption/development_tools

development_tools: add type signatures
This commit is contained in:
Bo Anderson 2021-09-30 12:59:51 +01:00 committed by GitHub
commit 24543c63e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 121 additions and 27 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,28 +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
def default_cc sig { returns(String) }
cc = DevelopmentTools.locate "cc" def custom_installation_instructions
begin installation_instructions
cc.realpath.basename.to_s
rescue
nil
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 +53,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 +64,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 +77,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,24 +92,28 @@ 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.nilable(String)]) }
def build_system_info def build_system_info
{ {
"os" => ENV["HOMEBREW_SYSTEM"], "os" => ENV["HOMEBREW_SYSTEM"],
"os_version" => OS_VERSION, "os_version" => OS_VERSION,
"cpu_family" => Hardware::CPU.family, "cpu_family" => Hardware::CPU.family.to_s,
} }
end end
alias generic_build_system_info build_system_info alias generic_build_system_info build_system_info

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,10 +21,11 @@ class DevelopmentTools
:gcc :gcc
end end
sig { returns(T::Hash[String, T.nilable(String)]) }
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.to_s.presence,
"oldest_cpu_family" => Hardware.oldest_cpu, "oldest_cpu_family" => Hardware.oldest_cpu.to_s,
}) })
end end
end end

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.nilable(String)]) }
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

@ -4,7 +4,12 @@
module Hardware module Hardware
extend T::Sig extend T::Sig
sig { params(version: T.nilable(Version)).returns(Symbol) } sig { params(version: T.nilable(Version)).returns(Symbol) }
def self.oldest_cpu(version = MacOS.version) def self.oldest_cpu(version = nil)
version = if version
MacOS::Version.new(version.to_s)
else
MacOS.version
end
if CPU.arch == :arm64 if CPU.arch == :arm64
:arm_vortex_tempest :arm_vortex_tempest
# TODO: this cannot be re-enabled until either Rosetta 2 supports AVX # TODO: this cannot be re-enabled until either Rosetta 2 supports AVX

View File

@ -11,6 +11,7 @@ module OS
module_function module_function
sig { returns(Version) }
def system_version def system_version
@system_version ||= begin @system_version ||= begin
version = Utils.popen_read("/usr/bin/ldd", "--version")[/ (\d+\.\d+)/, 1] version = Utils.popen_read("/usr/bin/ldd", "--version")[/ (\d+\.\d+)/, 1]
@ -22,6 +23,7 @@ module OS
end end
end end
sig { returns(Version) }
def version def version
@version ||= begin @version ||= begin
version = Utils.popen_read(HOMEBREW_PREFIX/"opt/glibc/bin/ldd", "--version")[/ (\d+\.\d+)/, 1] version = Utils.popen_read(HOMEBREW_PREFIX/"opt/glibc/bin/ldd", "--version")[/ (\d+\.\d+)/, 1]
@ -38,6 +40,7 @@ module OS
Version.new(ENV.fetch("HOMEBREW_LINUX_MINIMUM_GLIBC_VERSION")) Version.new(ENV.fetch("HOMEBREW_LINUX_MINIMUM_GLIBC_VERSION"))
end end
sig { returns(T::Boolean) }
def below_minimum_version? def below_minimum_version?
system_version < minimum_version system_version < minimum_version
end end

View File

@ -24,16 +24,19 @@ module OS
# This can be compared to numerics, strings, or symbols # This can be compared to numerics, strings, or symbols
# using the standard Ruby Comparable methods. # using the standard Ruby Comparable methods.
sig { returns(Version) }
def version def version
@version ||= full_version.strip_patch @version ||= full_version.strip_patch
end end
# This can be compared to numerics, strings, or symbols # This can be compared to numerics, strings, or symbols
# using the standard Ruby Comparable methods. # using the standard Ruby Comparable methods.
sig { returns(Version) }
def full_version def full_version
@full_version ||= Version.new((ENV["HOMEBREW_MACOS_VERSION"]).chomp) @full_version ||= Version.new((ENV["HOMEBREW_MACOS_VERSION"]).chomp)
end end
sig { params(version: Version).void }
def full_version=(version) def full_version=(version)
@full_version = Version.new(version.chomp) @full_version = Version.new(version.chomp)
@version = nil @version = nil
@ -72,6 +75,7 @@ module OS
languages.first languages.first
end end
sig { returns(String) }
def active_developer_dir def active_developer_dir
@active_developer_dir ||= Utils.popen_read("/usr/bin/xcode-select", "-print-path").strip @active_developer_dir ||= Utils.popen_read("/usr/bin/xcode-select", "-print-path").strip
end end
@ -178,6 +182,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 +190,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 +203,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,7 +89,11 @@ 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(T.nilable(Pathname)) }
def prefix def prefix
return @prefix if defined?(@prefix)
@prefix = T.let(@prefix, T.nilable(Pathname))
@prefix ||= @prefix ||=
begin begin
dir = MacOS.active_developer_dir dir = MacOS.active_developer_dir
@ -109,6 +113,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 +129,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 +172,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 +184,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 +242,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 +267,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 +362,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 +377,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 +391,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 +400,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

View File

@ -21,3 +21,14 @@ class Module
end end
def define_method(arg0, arg1=T.unsafe(nil), &blk); end def define_method(arg0, arg1=T.unsafe(nil), &blk); end
end end
class Pathname
# https://github.com/sorbet/sorbet/pull/4660
sig do
params(
consider_symlink: T::Boolean,
)
.returns(Pathname)
end
def cleanpath(consider_symlink=T.unsafe(nil)); end
end