# typed: true # rubocop:todo Sorbet/StrictSigil # frozen_string_literal: true require "requirement" # A requirement on macOS. class MacOSRequirement < Requirement fatal true sig { returns(String) } attr_reader :comparator attr_reader :version # TODO: when Yosemite is removed here, keep these around as empty arrays so we # can keep the deprecation/disabling code the same. DISABLED_MACOS_VERSIONS = T.let([ :yosemite, ].freeze, T::Array[Symbol]) DEPRECATED_MACOS_VERSIONS = T.let([].freeze, T::Array[Symbol]) def initialize(tags = [], comparator: ">=") @version = begin if comparator == "==" && tags.first.respond_to?(:map) tags.first.map { |s| MacOSVersion.from_symbol(s) } else MacOSVersion.from_symbol(tags.first) unless tags.empty? end rescue MacOSVersion::Error => e if DISABLED_MACOS_VERSIONS.include?(e.version) # This odisabled should stick around indefinitely. odisabled "`depends_on macos: :#{e.version}`" elsif DEPRECATED_MACOS_VERSIONS.include?(e.version) # This odeprecated should stick around indefinitely. odeprecated "`depends_on macos: :#{e.version}`" else raise end # Array of versions: remove the bad ones and try again. if tags.first.respond_to?(:reject) tags = [tags.first.reject { |s| s == e.version }, tags[1..]] retry end # Otherwise fallback to the oldest allowed if comparator is >=. MacOSVersion.new(HOMEBREW_MACOS_OLDEST_ALLOWED) if comparator == ">=" end @comparator = T.let(comparator, String) super(tags.drop(1)) end sig { returns(T::Boolean) } def version_specified? @version.present? end satisfy(build_env: false) do T.bind(self, MacOSRequirement) next Array(@version).any? { |v| OS::Mac.version.compare(@comparator, v) } if OS.mac? && version_specified? next true if OS.mac? next true if @version false end sig { returns(MacOSVersion) } def minimum_version return MacOSVersion.new(HOMEBREW_MACOS_OLDEST_ALLOWED) if @comparator == "<=" || !version_specified? return @version.min if @version.respond_to?(:to_ary) @version end sig { returns(MacOSVersion) } def maximum_version return MacOSVersion.new(HOMEBREW_MACOS_NEWEST_UNSUPPORTED) if @comparator == ">=" || !version_specified? return @version.max if @version.respond_to?(:to_ary) @version end sig { params(other: MacOSVersion).returns(T::Boolean) } def allows?(other) return true unless version_specified? case @comparator when ">=" then other >= @version when "<=" then other <= @version else return @version.include?(other) if @version.respond_to?(:to_ary) @version == other end end # Finds the highest supported {MacOSVersion} that meets the requirement, if # any. sig { returns(T.nilable(MacOSVersion)) } def highest_allowed MacOSVersion::SYMBOLS.each_key do |sym| candidate_version = MacOSVersion.from_symbol(sym) return candidate_version if allows?(candidate_version) end end sig { params(type: Symbol).returns(String) } def message(type: :formula) return "macOS is required for this software." unless version_specified? case @comparator when ">=" "This software does not run on macOS versions older than #{@version.pretty_name}." when "<=" case type when :formula <<~EOS This formula either does not compile or function as expected on macOS versions newer than #{@version.pretty_name} due to an upstream incompatibility. EOS when :cask "This cask does not run on macOS versions newer than #{@version.pretty_name}." else "This does not run on macOS versions newer than #{@version.pretty_name}." end else if @version.respond_to?(:to_ary) *versions, last = @version.map(&:pretty_name) return "This software does not run on macOS versions other than #{versions.join(", ")} and #{last}." end "This software does not run on macOS versions other than #{@version.pretty_name}." end end def ==(other) super && comparator == other.comparator && version == other.version end alias eql? == sig { returns(Integer) } def hash [super, comparator, version].hash end sig { returns(String) } def inspect "#<#{self.class.name}: version#{@comparator}#{@version.to_s.inspect} #{tags.inspect}>" end sig { returns(String) } def display_s if version_specified? if @version.respond_to?(:to_ary) "macOS #{@comparator} #{version.join(" / ")} (or Linux)" else "macOS #{@comparator} #{@version} (or Linux)" end else "macOS" end end sig { params(options: T.untyped).returns(String) } def to_json(options) comp = @comparator.to_s return { comp => @version.map(&:to_s) }.to_json(options) if @version.is_a?(Array) { comp => [@version.to_s] }.to_json(options) end end