| 
									
										
										
										
											2025-04-05 15:03:41 -04:00
										 |  |  | # typed: strong | 
					
						
							| 
									
										
										
										
											2023-05-09 02:15:28 +02:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | require "version" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # A macOS version. | 
					
						
							|  |  |  | class MacOSVersion < Version | 
					
						
							|  |  |  |   # Raised when a macOS version is unsupported. | 
					
						
							|  |  |  |   class Error < RuntimeError | 
					
						
							|  |  |  |     sig { returns(T.nilable(T.any(String, Symbol))) } | 
					
						
							|  |  |  |     attr_reader :version | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-05 15:03:41 -04:00
										 |  |  |     sig { params(version: T.nilable(T.any(String, Symbol))).void } | 
					
						
							| 
									
										
										
										
											2023-05-09 02:15:28 +02:00
										 |  |  |     def initialize(version) | 
					
						
							|  |  |  |       @version = version | 
					
						
							|  |  |  |       super "unknown or unsupported macOS version: #{version.inspect}" | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # NOTE: When removing symbols here, ensure that they are added | 
					
						
							|  |  |  |   #       to `DEPRECATED_MACOS_VERSIONS` in `MacOSRequirement`. | 
					
						
							| 
									
										
										
										
											2025-04-05 15:03:41 -04:00
										 |  |  |   SYMBOLS = T.let({ | 
					
						
							| 
									
										
										
										
											2025-06-09 19:02:37 +01:00
										 |  |  |     tahoe:       "26", | 
					
						
							| 
									
										
										
										
											2024-06-10 18:56:50 +01:00
										 |  |  |     sequoia:     "15", | 
					
						
							| 
									
										
										
										
											2023-06-05 18:36:03 +01:00
										 |  |  |     sonoma:      "14", | 
					
						
							| 
									
										
										
										
											2023-05-09 02:15:28 +02:00
										 |  |  |     ventura:     "13", | 
					
						
							|  |  |  |     monterey:    "12", | 
					
						
							|  |  |  |     big_sur:     "11", | 
					
						
							|  |  |  |     catalina:    "10.15", | 
					
						
							|  |  |  |     mojave:      "10.14", | 
					
						
							|  |  |  |     high_sierra: "10.13", | 
					
						
							|  |  |  |     sierra:      "10.12", | 
					
						
							|  |  |  |     el_capitan:  "10.11", | 
					
						
							| 
									
										
										
										
											2025-04-05 15:03:41 -04:00
										 |  |  |   }.freeze, T::Hash[Symbol, String]) | 
					
						
							| 
									
										
										
										
											2023-05-09 02:15:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-18 16:57:20 +01:00
										 |  |  |   # TODO: can be replaced with a call to `#pretty_name` once we remove support | 
					
						
							|  |  |  |   # for El Capitan. | 
					
						
							|  |  |  |   VERSIONS_TO_ANALYTICS_PRETTY_NAMES = T.let({ | 
					
						
							|  |  |  |     "26"    => "macOS Tahoe (26)", | 
					
						
							|  |  |  |     "15"    => "macOS Sequoia (15)", | 
					
						
							|  |  |  |     "14"    => "macOS Sonoma (14)", | 
					
						
							|  |  |  |     "13"    => "macOS Ventura (13)", | 
					
						
							|  |  |  |     "12"    => "macOS Monterey (12)", | 
					
						
							|  |  |  |     "11"    => "macOS Big Sur (11)", | 
					
						
							|  |  |  |     "10.16" => "macOS Big Sur (11)", | 
					
						
							|  |  |  |     "10.15" => "macOS Catalina (10.15)", | 
					
						
							|  |  |  |     "10.14" => "macOS Mojave (10.14)", | 
					
						
							|  |  |  |     "10.13" => "macOS High Sierra (10.13)", | 
					
						
							|  |  |  |     "10.12" => "macOS Sierra (10.12)", | 
					
						
							|  |  |  |     "10.11" => "OS X El Capitan (10.11)", | 
					
						
							|  |  |  |   }.freeze, T::Hash[String, String]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { params(version: String).returns(T.nilable(String)) } | 
					
						
							|  |  |  |   def self.analytics_pretty_name(version) | 
					
						
							|  |  |  |     VERSIONS_TO_ANALYTICS_PRETTY_NAMES.fetch(version) do | 
					
						
							|  |  |  |       VERSIONS_TO_ANALYTICS_PRETTY_NAMES.find do |v, _| | 
					
						
							|  |  |  |         version.start_with?(v) | 
					
						
							|  |  |  |       end&.last | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-31 14:57:02 +08:00
										 |  |  |   sig { params(macos_version: MacOSVersion).returns(Version) } | 
					
						
							|  |  |  |   def self.kernel_major_version(macos_version) | 
					
						
							|  |  |  |     version_major = macos_version.major.to_i | 
					
						
							| 
									
										
										
										
											2025-06-09 19:02:37 +01:00
										 |  |  |     if version_major >= 26
 | 
					
						
							|  |  |  |       Version.new((version_major - 1).to_s) | 
					
						
							|  |  |  |     elsif version_major > 10
 | 
					
						
							| 
									
										
										
										
											2024-10-31 14:57:02 +08:00
										 |  |  |       Version.new((version_major + 9).to_s) | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       version_minor = macos_version.minor.to_i | 
					
						
							|  |  |  |       Version.new((version_minor + 4).to_s) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2024-10-31 02:39:38 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-09 02:15:28 +02:00
										 |  |  |   sig { params(version: Symbol).returns(T.attached_class) } | 
					
						
							|  |  |  |   def self.from_symbol(version) | 
					
						
							|  |  |  |     str = SYMBOLS.fetch(version) { raise MacOSVersion::Error, version } | 
					
						
							|  |  |  |     new(str) | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { params(version: T.nilable(String)).void } | 
					
						
							|  |  |  |   def initialize(version) | 
					
						
							| 
									
										
										
										
											2025-06-09 19:02:37 +01:00
										 |  |  |     raise MacOSVersion::Error, version unless /\A\d{2,}(?:\.\d+){0,2}\z/.match?(version) | 
					
						
							| 
									
										
										
										
											2023-05-09 02:15:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-05 08:55:58 -08:00
										 |  |  |     super(T.must(version)) | 
					
						
							| 
									
										
										
										
											2023-05-09 02:15:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-05 15:03:41 -04:00
										 |  |  |     @comparison_cache = T.let({}, T::Hash[T.untyped, T.nilable(Integer)]) | 
					
						
							|  |  |  |     @pretty_name = T.let(nil, T.nilable(String)) | 
					
						
							|  |  |  |     @sym = T.let(nil, T.nilable(Symbol)) | 
					
						
							| 
									
										
										
										
											2023-05-09 02:15:28 +02:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { override.params(other: T.untyped).returns(T.nilable(Integer)) } | 
					
						
							|  |  |  |   def <=>(other) | 
					
						
							|  |  |  |     return @comparison_cache[other] if @comparison_cache.key?(other) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     result = case other | 
					
						
							|  |  |  |     when Symbol | 
					
						
							|  |  |  |       if SYMBOLS.key?(other) && to_sym == other | 
					
						
							|  |  |  |         0
 | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         v = SYMBOLS.fetch(other) { other.to_s } | 
					
						
							|  |  |  |         super(v) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       super | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @comparison_cache[other] = result unless frozen? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     result | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { returns(T.self_type) } | 
					
						
							|  |  |  |   def strip_patch | 
					
						
							|  |  |  |     return self if null? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Big Sur is 11.x but Catalina is 10.15.x. | 
					
						
							|  |  |  |     if T.must(major) >= 11
 | 
					
						
							|  |  |  |       self.class.new(major.to_s) | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       major_minor | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { returns(Symbol) } | 
					
						
							|  |  |  |   def to_sym | 
					
						
							| 
									
										
										
										
											2025-04-05 15:03:41 -04:00
										 |  |  |     return @sym if @sym | 
					
						
							| 
									
										
										
										
											2023-05-09 02:15:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     sym = SYMBOLS.invert.fetch(strip_patch.to_s, :dunno) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @sym = sym unless frozen? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sym | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { returns(String) } | 
					
						
							|  |  |  |   def pretty_name | 
					
						
							| 
									
										
										
										
											2025-04-05 15:03:41 -04:00
										 |  |  |     return @pretty_name if @pretty_name | 
					
						
							| 
									
										
										
										
											2023-05-09 02:15:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     pretty_name = to_sym.to_s.split("_").map(&:capitalize).join(" ").freeze | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @pretty_name = pretty_name unless frozen? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     pretty_name | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-03 12:33:00 -04:00
										 |  |  |   sig { returns(String) } | 
					
						
							|  |  |  |   def inspect | 
					
						
							|  |  |  |     "#<#{self.class.name}: #{to_s.inspect}>" | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-09 02:15:28 +02:00
										 |  |  |   sig { returns(T::Boolean) } | 
					
						
							|  |  |  |   def outdated_release? | 
					
						
							|  |  |  |     self < HOMEBREW_MACOS_OLDEST_SUPPORTED | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { returns(T::Boolean) } | 
					
						
							|  |  |  |   def prerelease? | 
					
						
							|  |  |  |     self >= HOMEBREW_MACOS_NEWEST_UNSUPPORTED | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { returns(T::Boolean) } | 
					
						
							|  |  |  |   def unsupported_release? | 
					
						
							|  |  |  |     outdated_release? || prerelease? | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { returns(T::Boolean) } | 
					
						
							|  |  |  |   def requires_nehalem_cpu? | 
					
						
							|  |  |  |     return false if null? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     require "hardware" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return Hardware.oldest_cpu(self) == :nehalem if Hardware::CPU.intel? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     raise ArgumentError, "Unexpected architecture: #{Hardware::CPU.arch}. This only works with Intel architecture." | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  |   # https://en.wikipedia.org/wiki/Nehalem_(microarchitecture) | 
					
						
							|  |  |  |   alias requires_sse4? requires_nehalem_cpu? | 
					
						
							|  |  |  |   alias requires_sse41? requires_nehalem_cpu? | 
					
						
							|  |  |  |   alias requires_sse42? requires_nehalem_cpu? | 
					
						
							|  |  |  |   alias requires_popcnt? requires_nehalem_cpu? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Represents the absence of a version. | 
					
						
							| 
									
										
										
										
											2024-04-26 20:55:51 +02:00
										 |  |  |   # | 
					
						
							| 
									
										
										
										
											2023-05-09 02:15:28 +02:00
										 |  |  |   # NOTE: Constructor needs to called with an arbitrary macOS-like version which is then set to `nil`. | 
					
						
							| 
									
										
										
										
											2025-04-05 15:03:41 -04:00
										 |  |  |   NULL = T.let(MacOSVersion.new("10.0").tap do |v| | 
					
						
							|  |  |  |     T.let(v, MacOSVersion).instance_variable_set(:@version, nil) | 
					
						
							|  |  |  |   end.freeze, MacOSVersion) | 
					
						
							| 
									
										
										
										
											2023-05-09 02:15:28 +02:00
										 |  |  | end |