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
 | 
