
- Previously I thought that comments were fine to discourage people from wasting their time trying to bump things that used `undef` that Sorbet didn't support. But RuboCop is better at this since it'll complain if the comments are unnecessary. - Suggested in https://github.com/Homebrew/brew/pull/18018#issuecomment-2283369501. - I've gone for a mixture of `rubocop:disable` for the files that can't be `typed: strict` (use of undef, required before everything else, etc) and `rubocop:todo` for everything else that should be tried to make strictly typed. There's no functional difference between the two as `rubocop:todo` is `rubocop:disable` with a different name. - And I entirely disabled the cop for the docs/ directory since `typed: strict` isn't going to gain us anything for some Markdown linting config files. - This means that now it's easier to track what needs to be done rather than relying on checklists of files in our big Sorbet issue: ```shell $ git grep 'typed: true # rubocop:todo Sorbet/StrictSigil' | wc -l 268 ``` - And this is confirmed working for new files: ```shell $ git status On branch use-rubocop-for-sorbet-strict-sigils Untracked files: (use "git add <file>..." to include in what will be committed) Library/Homebrew/bad.rb Library/Homebrew/good.rb nothing added to commit but untracked files present (use "git add" to track) $ brew style Offenses: bad.rb:1:1: C: Sorbet/StrictSigil: Sorbet sigil should be at least strict got true. ^^^^^^^^^^^^^ 1340 files inspected, 1 offense detected ```
189 lines
5.4 KiB
Ruby
189 lines
5.4 KiB
Ruby
# typed: true # rubocop:todo Sorbet/StrictSigil
|
|
# frozen_string_literal: true
|
|
|
|
require "system_command"
|
|
|
|
module OS
|
|
module Mac
|
|
# Class representing a macOS SDK.
|
|
class SDK
|
|
# 11.x SDKs are explicitly excluded - we want the MacOSX11.sdk symlink instead.
|
|
VERSIONED_SDK_REGEX = /MacOSX(10\.\d+|\d+)\.sdk$/
|
|
|
|
sig { returns(MacOSVersion) }
|
|
attr_reader :version
|
|
|
|
sig { returns(Pathname) }
|
|
attr_reader :path
|
|
|
|
sig { returns(Symbol) }
|
|
attr_reader :source
|
|
|
|
sig { params(version: MacOSVersion, path: T.any(String, Pathname), source: Symbol).void }
|
|
def initialize(version, path, source)
|
|
@version = version
|
|
@path = Pathname.new(path)
|
|
@source = source
|
|
end
|
|
end
|
|
|
|
# Base class for SDK locators.
|
|
class BaseSDKLocator
|
|
extend T::Helpers
|
|
include SystemCommand::Mixin
|
|
|
|
abstract!
|
|
|
|
class NoSDKError < StandardError; end
|
|
|
|
sig { params(version: MacOSVersion).returns(SDK) }
|
|
def sdk_for(version)
|
|
sdk = all_sdks.find { |s| s.version == version }
|
|
raise NoSDKError if sdk.nil?
|
|
|
|
sdk
|
|
end
|
|
|
|
sig { returns(T::Array[SDK]) }
|
|
def all_sdks
|
|
return @all_sdks if @all_sdks
|
|
|
|
@all_sdks = []
|
|
|
|
# Bail out if there is no SDK prefix at all
|
|
return @all_sdks unless File.directory? sdk_prefix
|
|
|
|
found_versions = Set.new
|
|
|
|
Dir["#{sdk_prefix}/MacOSX*.sdk"].each do |sdk_path|
|
|
next unless sdk_path.match?(SDK::VERSIONED_SDK_REGEX)
|
|
|
|
version = read_sdk_version(Pathname.new(sdk_path))
|
|
next if version.nil?
|
|
|
|
@all_sdks << SDK.new(version, sdk_path, source)
|
|
found_versions << version
|
|
end
|
|
|
|
# Use unversioned SDK only if we don't have one matching that version.
|
|
sdk_path = Pathname.new("#{sdk_prefix}/MacOSX.sdk")
|
|
if (version = read_sdk_version(sdk_path)) && found_versions.exclude?(version)
|
|
@all_sdks << SDK.new(version, sdk_path, source)
|
|
end
|
|
|
|
@all_sdks
|
|
end
|
|
|
|
sig { params(version: T.nilable(MacOSVersion)).returns(T.nilable(SDK)) }
|
|
def sdk_if_applicable(version = nil)
|
|
sdk = begin
|
|
if version.blank?
|
|
sdk_for OS::Mac.version
|
|
else
|
|
sdk_for version
|
|
end
|
|
rescue NoSDKError
|
|
latest_sdk
|
|
end
|
|
return if sdk.blank?
|
|
|
|
# On OSs lower than 11, whenever the major versions don't match,
|
|
# only return an SDK older than the OS version if it was specifically requested
|
|
return if version.blank? && sdk.version < OS::Mac.version
|
|
|
|
sdk
|
|
end
|
|
|
|
sig { abstract.returns(Symbol) }
|
|
def source; end
|
|
|
|
private
|
|
|
|
sig { abstract.returns(String) }
|
|
def sdk_prefix; end
|
|
|
|
sig { returns(T.nilable(SDK)) }
|
|
def latest_sdk
|
|
all_sdks.max_by(&:version)
|
|
end
|
|
|
|
sig { params(sdk_path: Pathname).returns(T.nilable(MacOSVersion)) }
|
|
def read_sdk_version(sdk_path)
|
|
sdk_settings = sdk_path/"SDKSettings.json"
|
|
sdk_settings_string = sdk_settings.read if sdk_settings.exist?
|
|
|
|
# Pre-10.14 SDKs
|
|
sdk_settings = sdk_path/"SDKSettings.plist"
|
|
if sdk_settings_string.blank? && sdk_settings.exist?
|
|
result = system_command("plutil", args: ["-convert", "json", "-o", "-", sdk_settings])
|
|
sdk_settings_string = result.stdout if result.success?
|
|
end
|
|
|
|
return if sdk_settings_string.blank?
|
|
|
|
sdk_settings_json = JSON.parse(sdk_settings_string)
|
|
return if sdk_settings_json.blank?
|
|
|
|
version_string = sdk_settings_json.fetch("Version", nil)
|
|
return if version_string.blank?
|
|
|
|
begin
|
|
MacOSVersion.new(version_string).strip_patch
|
|
rescue MacOSVersion::Error
|
|
nil
|
|
end
|
|
end
|
|
end
|
|
private_constant :BaseSDKLocator
|
|
|
|
# Helper class for locating the Xcode SDK.
|
|
class XcodeSDKLocator < BaseSDKLocator
|
|
sig { override.returns(Symbol) }
|
|
def source
|
|
:xcode
|
|
end
|
|
|
|
private
|
|
|
|
sig { override.returns(String) }
|
|
def sdk_prefix
|
|
@sdk_prefix ||= begin
|
|
# Xcode.prefix is pretty smart, so let's look inside to find the sdk
|
|
sdk_prefix = "#{Xcode.prefix}/Platforms/MacOSX.platform/Developer/SDKs"
|
|
# Finally query Xcode itself (this is slow, so check it last)
|
|
sdk_platform_path = Utils.popen_read(DevelopmentTools.locate("xcrun"), "--show-sdk-platform-path").chomp
|
|
sdk_prefix = File.join(sdk_platform_path, "Developer", "SDKs") unless File.directory? sdk_prefix
|
|
|
|
sdk_prefix
|
|
end
|
|
end
|
|
end
|
|
|
|
# Helper class for locating the macOS Command Line Tools SDK.
|
|
class CLTSDKLocator < BaseSDKLocator
|
|
sig { override.returns(Symbol) }
|
|
def source
|
|
:clt
|
|
end
|
|
|
|
private
|
|
|
|
# While CLT SDKs existed prior to Xcode 10, those packages also
|
|
# installed a traditional Unix-style header layout and we prefer
|
|
# using that.
|
|
# As of Xcode 10, the Unix-style headers are installed via a
|
|
# separate package, so we can't rely on their being present.
|
|
# This will only look up SDKs on Xcode 10 or newer and still
|
|
# return `nil` SDKs for Xcode 9 and older.
|
|
sig { override.returns(String) }
|
|
def sdk_prefix
|
|
@sdk_prefix ||= if CLT.provides_sdk?
|
|
"#{CLT::PKG_PATH}/SDKs"
|
|
else
|
|
""
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|