| ... | @@ -6,453 +6,403 @@ | ... | @@ -6,453 +6,403 @@ |
| 6 | const std = @import("std"); | 6 | const std = @import("std"); |
| 7 | const assert = std.debug.assert; | 7 | const assert = std.debug.assert; |
| 8 | const mem = std.mem; | 8 | const mem = std.mem; |
| | 9 | const testing = std.testing; |
| 9 | | 10 | |
| 10 | pub fn version_from_build(build: []const u8) !std.builtin.Version { | 11 | /// Detect macOS version. |
| 11 | // build format: | 12 | /// `os` is not modified in case of error. |
| 12 | // 19E287 (example) | 13 | pub fn detect(os: *std.Target.Os) !void { |
| 13 | // xxyzzz | 14 | // Drop use of osproductversion sysctl because: |
| | 15 | // 1. only available 10.13.4 High Sierra and later |
| | 16 | // 2. when used from a binary built against < SDK 11.0 it returns 10.16 and masks Big Sur 11.x version |
| 14 | // | 17 | // |
| 15 | // major = 10 | 18 | // NEW APPROACH, STEP 1, parse file: |
| 16 | // minor = x - 4 = 19 - 4 = 15 | | |
| 17 | // patch = ascii(y) - 'A' = 'E' - 'A' = 69 - 65 = 4 | | |
| 18 | // answer: 10.15.4 | | |
| 19 | // | 19 | // |
| 20 | // note: patch is typical but with some older releases (before 10.8) zzz is considered | 20 | // /System/Library/CoreServices/SystemVersion.plist |
| 21 | | 21 | // |
| 22 | // never return anything below 10.0.0 | 22 | // NOTE: Historically `SystemVersion.plist` first appeared circa '2003 |
| 23 | var result = std.builtin.Version{ .major = 10, .minor = 0, .patch = 0 }; | 23 | // with the release of Mac OS X 10.3.0 Panther. |
| | 24 | // |
| | 25 | // and if it contains a `10.16` value where the `16` is `>= 16` then it is non-canonical, |
| | 26 | // discarded, and we move on to next step. Otherwise we accept the version. |
| | 27 | // |
| | 28 | // BACKGROUND: `10.(16+)` is not a proper version and does not have enough fidelity to |
| | 29 | // indicate minor/point version of Big Sur and later. It is a context-sensitive result |
| | 30 | // issued by the kernel for backwards compatibility purposes. Likely the kernel checks |
| | 31 | // if the executable was linked against an SDK older than Big Sur. |
| | 32 | // |
| | 33 | // STEP 2, parse next file: |
| | 34 | // |
| | 35 | // /System/Library/CoreServices/.SystemVersionPlatform.plist |
| | 36 | // |
| | 37 | // NOTE: Historically `SystemVersionPlatform.plist` first appeared circa '2020 |
| | 38 | // with the release of macOS 11.0 Big Sur. |
| | 39 | // |
| | 40 | // Accessing the content via this path circumvents a context-sensitive result and |
| | 41 | // yields a canonical Big Sur version. |
| | 42 | // |
| | 43 | // At this time there is no other known way for a < SDK 11.0 executable to obtain a |
| | 44 | // canonical Big Sur version. |
| | 45 | // |
| | 46 | // This implementation uses a reasonably simplified approach to parse .plist file |
| | 47 | // that while it is an xml document, we have good history on the file and its format |
| | 48 | // such that I am comfortable with implementing a minimalistic parser. |
| | 49 | // Things like string and general escapes are not supported. |
| | 50 | const prefixSlash = "/System/Library/CoreServices/"; |
| | 51 | const paths = [_][]const u8{ |
| | 52 | prefixSlash ++ "SystemVersion.plist", |
| | 53 | prefixSlash ++ ".SystemVersionPlatform.plist", |
| | 54 | }; |
| | 55 | for (paths) |path| { |
| | 56 | // approx. 4 times historical file size |
| | 57 | var buf: [2048]u8 = undefined; |
| 24 | | 58 | |
| 25 | // parse format-x | 59 | if (std.fs.cwd().readFile(path, &buf)) |bytes| { |
| 26 | var yindex: usize = 0; | 60 | if (parseSystemVersion(bytes)) |ver| { |
| 27 | { | 61 | // never return non-canonical `10.(16+)` |
| 28 | while (yindex < build.len) : (yindex += 1) { | 62 | if (!(ver.major == 10 and ver.minor >= 16)) { |
| 29 | if (build[yindex] < '0' or build[yindex] > '9') break; | 63 | os.version_range.semver.min = ver; |
| | 64 | os.version_range.semver.max = ver; |
| | 65 | return; |
| | 66 | } |
| | 67 | continue; |
| | 68 | } else |err| { |
| | 69 | return error.OSVersionDetectionFail; |
| | 70 | } |
| | 71 | } else |err| { |
| | 72 | return error.OSVersionDetectionFail; |
| 30 | } | 73 | } |
| 31 | if (yindex == 0) return result; | | |
| 32 | const x = std.fmt.parseUnsigned(u16, build[0..yindex], 10) catch return error.InvalidVersion; | | |
| 33 | if (x < 4) return result; | | |
| 34 | result.minor = x - 4; | | |
| 35 | } | 74 | } |
| | 75 | return error.OSVersionDetectionFail; |
| | 76 | } |
| 36 | | 77 | |
| 37 | // parse format-y, format-z | 78 | fn parseSystemVersion(buf: []const u8) !std.builtin.Version { |
| 38 | { | 79 | var svt = SystemVersionTokenizer{ .bytes = buf }; |
| 39 | // expect two more | 80 | try svt.skipUntilTag(.start, "dict"); |
| 40 | if (build.len < yindex + 2) return error.InvalidVersion; | 81 | while (true) { |
| 41 | const y = build[yindex]; | 82 | try svt.skipUntilTag(.start, "key"); |
| 42 | if (y < 'A') return error.InvalidVersion; | 83 | const content = try svt.expectContent(); |
| 43 | var zend = yindex + 1; | 84 | try svt.skipUntilTag(.end, "key"); |
| 44 | while (zend < build.len) { | 85 | if (std.mem.eql(u8, content, "ProductVersion")) break; |
| 45 | if (build[zend] < '0' or build[zend] > '9') break; | | |
| 46 | zend += 1; | | |
| 47 | } | | |
| 48 | if (zend == yindex + 1) return error.InvalidVersion; | | |
| 49 | const z = std.fmt.parseUnsigned(u16, build[yindex + 1 .. zend], 10) catch return error.InvalidVersion; | | |
| 50 | | | |
| 51 | result.patch = switch (result.minor) { | | |
| 52 | // TODO: compiler complains without explicit @as() coercion | | |
| 53 | 0 => @as(u32, switch (y) { // Cheetah: 10.0 | | |
| 54 | 'K' => 0, | | |
| 55 | 'L' => 1, | | |
| 56 | 'P' => @as(u32, block: { | | |
| 57 | if (z < 13) break :block 2; | | |
| 58 | break :block 3; | | |
| 59 | }), | | |
| 60 | 'Q' => 4, | | |
| 61 | else => return error.InvalidVersion, | | |
| 62 | }), | | |
| 63 | 1 => @as(u32, switch (y) { // Puma: 10.1 | | |
| 64 | 'G' => 0, | | |
| 65 | 'M' => 1, | | |
| 66 | 'P' => 2, | | |
| 67 | 'Q' => @as(u32, block: { | | |
| 68 | if (z < 125) break :block 3; | | |
| 69 | break :block 4; | | |
| 70 | }), | | |
| 71 | 'S' => 5, | | |
| 72 | else => return error.InvalidVersion, | | |
| 73 | }), | | |
| 74 | 2 => @as(u32, switch (y) { // Jaguar: 10.2 | | |
| 75 | 'C' => 0, | | |
| 76 | 'D' => 1, | | |
| 77 | 'F' => 2, | | |
| 78 | 'G' => 3, | | |
| 79 | 'I' => 4, | | |
| 80 | 'L' => @as(u32, block: { | | |
| 81 | if (z < 60) break :block 5; | | |
| 82 | break :block 6; | | |
| 83 | }), | | |
| 84 | 'R' => @as(u32, block: { | | |
| 85 | if (z < 73) break :block 7; | | |
| 86 | break :block 8; | | |
| 87 | }), | | |
| 88 | 'S' => 8, | | |
| 89 | else => return error.InvalidVersion, | | |
| 90 | }), | | |
| 91 | 3 => @as(u32, switch (y) { // Panther: 10.3 | | |
| 92 | 'B' => 0, | | |
| 93 | 'C' => 1, | | |
| 94 | 'D' => 2, | | |
| 95 | 'F' => 3, | | |
| 96 | 'H' => 4, | | |
| 97 | 'M' => 5, | | |
| 98 | 'R' => 6, | | |
| 99 | 'S' => 7, | | |
| 100 | 'U' => 8, | | |
| 101 | 'W' => 9, | | |
| 102 | else => return error.InvalidVersion, | | |
| 103 | }), | | |
| 104 | 4 => @as(u32, switch (y) { // Tiger: 10.4 | | |
| 105 | 'A' => 0, | | |
| 106 | 'B' => 1, | | |
| 107 | 'C', | | |
| 108 | 'E', | | |
| 109 | => 2, | | |
| 110 | 'F' => 3, | | |
| 111 | 'G' => @as(u32, block: { | | |
| 112 | if (z >= 1454) break :block 5; | | |
| 113 | break :block 4; | | |
| 114 | }), | | |
| 115 | 'H' => 5, | | |
| 116 | 'I' => 6, | | |
| 117 | 'J', | | |
| 118 | 'K', | | |
| 119 | 'N', | | |
| 120 | => 7, | | |
| 121 | 'L' => 8, | | |
| 122 | 'P' => 9, | | |
| 123 | 'R' => 10, | | |
| 124 | 'S' => 11, | | |
| 125 | else => return error.InvalidVersion, | | |
| 126 | }), | | |
| 127 | 5 => @as(u32, switch (y) { // Leopard: 10.5 | | |
| 128 | 'A' => 0, | | |
| 129 | 'B' => 1, | | |
| 130 | 'C' => 2, | | |
| 131 | 'D' => 3, | | |
| 132 | 'E' => 4, | | |
| 133 | 'F' => 5, | | |
| 134 | 'G' => 6, | | |
| 135 | 'J' => 7, | | |
| 136 | 'L' => 8, | | |
| 137 | else => return error.InvalidVersion, | | |
| 138 | }), | | |
| 139 | 6 => @as(u32, switch (y) { // Snow Leopard: 10.6 | | |
| 140 | 'A' => 0, | | |
| 141 | 'B' => 1, | | |
| 142 | 'C' => 2, | | |
| 143 | 'D' => 3, | | |
| 144 | 'F' => 4, | | |
| 145 | 'H' => 5, | | |
| 146 | 'J' => @as(u32, block: { | | |
| 147 | if (z < 869) break :block 6; | | |
| 148 | break :block 7; | | |
| 149 | }), | | |
| 150 | 'K' => 8, | | |
| 151 | else => return error.InvalidVersion, | | |
| 152 | }), | | |
| 153 | 7 => @as(u32, switch (y) { // Snow Leopard: 10.6 | | |
| 154 | 'A' => 0, | | |
| 155 | 'B' => 1, | | |
| 156 | 'C' => 2, | | |
| 157 | 'D' => 3, | | |
| 158 | 'E' => 4, | | |
| 159 | 'G' => 5, | | |
| 160 | else => return error.InvalidVersion, | | |
| 161 | }), | | |
| 162 | else => y - 'A', | | |
| 163 | }; | | |
| 164 | } | 86 | } |
| 165 | return result; | 87 | try svt.skipUntilTag(.start, "string"); |
| 166 | } | 88 | const ver = try svt.expectContent(); |
| | 89 | try svt.skipUntilTag(.end, "string"); |
| 167 | | 90 | |
| 168 | test "version_from_build" { | 91 | return std.builtin.Version.parse(ver); |
| 169 | // see https://en.wikipedia.org/wiki/MacOS_version_history#Releases | 92 | } |
| 170 | const known = [_][2][]const u8{ | | |
| 171 | .{ "4K78", "10.0.0" }, | | |
| 172 | .{ "4L13", "10.0.1" }, | | |
| 173 | .{ "4P12", "10.0.2" }, | | |
| 174 | .{ "4P13", "10.0.3" }, | | |
| 175 | .{ "4Q12", "10.0.4" }, | | |
| 176 | | 93 | |
| 177 | .{ "5G64", "10.1.0" }, | 94 | const SystemVersionTokenizer = struct { |
| 178 | .{ "5M28", "10.1.1" }, | 95 | bytes: []const u8, |
| 179 | .{ "5P48", "10.1.2" }, | 96 | index: usize = 0, |
| 180 | .{ "5Q45", "10.1.3" }, | 97 | state: State = .begin, |
| 181 | .{ "5Q125", "10.1.4" }, | | |
| 182 | .{ "5S60", "10.1.5" }, | | |
| 183 | | 98 | |
| 184 | .{ "6C115", "10.2.0" }, | 99 | fn next(self: *@This()) !?Token { |
| 185 | .{ "6C115a", "10.2.0" }, | 100 | var mark: usize = self.index; |
| 186 | .{ "6D52", "10.2.1" }, | 101 | var tag = Tag{}; |
| 187 | .{ "6F21", "10.2.2" }, | 102 | var content: []const u8 = ""; |
| 188 | .{ "6G30", "10.2.3" }, | | |
| 189 | .{ "6G37", "10.2.3" }, | | |
| 190 | .{ "6G50", "10.2.3" }, | | |
| 191 | .{ "6I32", "10.2.4" }, | | |
| 192 | .{ "6L29", "10.2.5" }, | | |
| 193 | .{ "6L60", "10.2.6" }, | | |
| 194 | .{ "6R65", "10.2.7" }, | | |
| 195 | .{ "6R73", "10.2.8" }, | | |
| 196 | .{ "6S90", "10.2.8" }, | | |
| 197 | | 103 | |
| 198 | .{ "7B85", "10.3.0" }, | 104 | while (self.index < self.bytes.len) { |
| 199 | .{ "7B86", "10.3.0" }, | 105 | const char = self.bytes[self.index]; |
| 200 | .{ "7C107", "10.3.1" }, | 106 | switch (self.state) { |
| 201 | .{ "7D24", "10.3.2" }, | 107 | .begin => switch (char) { |
| 202 | .{ "7D28", "10.3.2" }, | 108 | '<' => { |
| 203 | .{ "7F44", "10.3.3" }, | 109 | self.state = .tag0; |
| 204 | .{ "7H63", "10.3.4" }, | 110 | self.index += 1; |
| 205 | .{ "7M34", "10.3.5" }, | 111 | tag = Tag{}; |
| 206 | .{ "7R28", "10.3.6" }, | 112 | mark = self.index; |
| 207 | .{ "7S215", "10.3.7" }, | 113 | }, |
| 208 | .{ "7U16", "10.3.8" }, | 114 | '>' => { |
| 209 | .{ "7W98", "10.3.9" }, | 115 | return error.BadToken; |
| | 116 | }, |
| | 117 | else => { |
| | 118 | self.state = .content; |
| | 119 | content = ""; |
| | 120 | mark = self.index; |
| | 121 | }, |
| | 122 | }, |
| | 123 | .tag0 => switch (char) { |
| | 124 | '<' => { |
| | 125 | return error.BadToken; |
| | 126 | }, |
| | 127 | '>' => { |
| | 128 | self.state = .begin; |
| | 129 | self.index += 1; |
| | 130 | tag.name = self.bytes[mark..self.index]; |
| | 131 | return Token{ .tag = tag }; |
| | 132 | }, |
| | 133 | '"' => { |
| | 134 | self.state = .tag_string; |
| | 135 | self.index += 1; |
| | 136 | }, |
| | 137 | '/' => { |
| | 138 | self.state = .tag0_end_or_empty; |
| | 139 | self.index += 1; |
| | 140 | }, |
| | 141 | 'A'...'Z', 'a'...'z' => { |
| | 142 | self.state = .tagN; |
| | 143 | tag.kind = .start; |
| | 144 | self.index += 1; |
| | 145 | }, |
| | 146 | else => { |
| | 147 | self.state = .tagN; |
| | 148 | self.index += 1; |
| | 149 | }, |
| | 150 | }, |
| | 151 | .tag0_end_or_empty => switch (char) { |
| | 152 | '<' => { |
| | 153 | return error.BadToken; |
| | 154 | }, |
| | 155 | '>' => { |
| | 156 | self.state = .begin; |
| | 157 | tag.kind = .empty; |
| | 158 | tag.name = self.bytes[self.index..self.index]; |
| | 159 | self.index += 1; |
| | 160 | return Token{ .tag = tag }; |
| | 161 | }, |
| | 162 | else => { |
| | 163 | self.state = .tagN; |
| | 164 | tag.kind = .end; |
| | 165 | mark = self.index; |
| | 166 | self.index += 1; |
| | 167 | }, |
| | 168 | }, |
| | 169 | .tagN => switch (char) { |
| | 170 | '<' => { |
| | 171 | return error.BadToken; |
| | 172 | }, |
| | 173 | '>' => { |
| | 174 | self.state = .begin; |
| | 175 | tag.name = self.bytes[mark..self.index]; |
| | 176 | self.index += 1; |
| | 177 | return Token{ .tag = tag }; |
| | 178 | }, |
| | 179 | '"' => { |
| | 180 | self.state = .tag_string; |
| | 181 | self.index += 1; |
| | 182 | }, |
| | 183 | '/' => { |
| | 184 | self.state = .tagN_end; |
| | 185 | tag.kind = .end; |
| | 186 | self.index += 1; |
| | 187 | }, |
| | 188 | else => { |
| | 189 | self.index += 1; |
| | 190 | }, |
| | 191 | }, |
| | 192 | .tagN_end => switch (char) { |
| | 193 | '>' => { |
| | 194 | self.state = .begin; |
| | 195 | tag.name = self.bytes[mark..self.index]; |
| | 196 | self.index += 1; |
| | 197 | return Token{ .tag = tag }; |
| | 198 | }, |
| | 199 | else => { |
| | 200 | return error.BadToken; |
| | 201 | }, |
| | 202 | }, |
| | 203 | .tag_string => switch (char) { |
| | 204 | '"' => { |
| | 205 | self.state = .tagN; |
| | 206 | self.index += 1; |
| | 207 | }, |
| | 208 | else => { |
| | 209 | self.index += 1; |
| | 210 | }, |
| | 211 | }, |
| | 212 | .content => switch (char) { |
| | 213 | '<' => { |
| | 214 | self.state = .tag0; |
| | 215 | content = self.bytes[mark..self.index]; |
| | 216 | self.index += 1; |
| | 217 | tag = Tag{}; |
| | 218 | mark = self.index; |
| | 219 | return Token{ .content = content }; |
| | 220 | }, |
| | 221 | '>' => { |
| | 222 | return error.BadToken; |
| | 223 | }, |
| | 224 | else => { |
| | 225 | self.index += 1; |
| | 226 | }, |
| | 227 | }, |
| | 228 | } |
| | 229 | } |
| 210 | | 230 | |
| 211 | .{ "8A428", "10.4.0" }, | 231 | return null; |
| 212 | .{ "8A432", "10.4.0" }, | 232 | } |
| 213 | .{ "8B15", "10.4.1" }, | | |
| 214 | .{ "8B17", "10.4.1" }, | | |
| 215 | .{ "8C46", "10.4.2" }, | | |
| 216 | .{ "8C47", "10.4.2" }, | | |
| 217 | .{ "8E102", "10.4.2" }, | | |
| 218 | .{ "8E45", "10.4.2" }, | | |
| 219 | .{ "8E90", "10.4.2" }, | | |
| 220 | .{ "8F46", "10.4.3" }, | | |
| 221 | .{ "8G32", "10.4.4" }, | | |
| 222 | .{ "8G1165", "10.4.4" }, | | |
| 223 | .{ "8H14", "10.4.5" }, | | |
| 224 | .{ "8G1454", "10.4.5" }, | | |
| 225 | .{ "8I127", "10.4.6" }, | | |
| 226 | .{ "8I1119", "10.4.6" }, | | |
| 227 | .{ "8J135", "10.4.7" }, | | |
| 228 | .{ "8J2135a", "10.4.7" }, | | |
| 229 | .{ "8K1079", "10.4.7" }, | | |
| 230 | .{ "8N5107", "10.4.7" }, | | |
| 231 | .{ "8L127", "10.4.8" }, | | |
| 232 | .{ "8L2127", "10.4.8" }, | | |
| 233 | .{ "8P135", "10.4.9" }, | | |
| 234 | .{ "8P2137", "10.4.9" }, | | |
| 235 | .{ "8R218", "10.4.10" }, | | |
| 236 | .{ "8R2218", "10.4.10" }, | | |
| 237 | .{ "8R2232", "10.4.10" }, | | |
| 238 | .{ "8S165", "10.4.11" }, | | |
| 239 | .{ "8S2167", "10.4.11" }, | | |
| 240 | | 233 | |
| 241 | .{ "9A581", "10.5.0" }, | 234 | fn expectContent(self: *@This()) ![]const u8 { |
| 242 | .{ "9B18", "10.5.1" }, | 235 | if (try self.next()) |tok| { |
| 243 | .{ "9C31", "10.5.2" }, | 236 | switch (tok) { |
| 244 | .{ "9C7010", "10.5.2" }, | 237 | .content => |content| { |
| 245 | .{ "9D34", "10.5.3" }, | 238 | return content; |
| 246 | .{ "9E17", "10.5.4" }, | 239 | }, |
| 247 | .{ "9F33", "10.5.5" }, | 240 | else => {}, |
| 248 | .{ "9G55", "10.5.6" }, | 241 | } |
| 249 | .{ "9G66", "10.5.6" }, | 242 | } |
| 250 | .{ "9J61", "10.5.7" }, | 243 | return error.UnexpectedToken; |
| 251 | .{ "9L30", "10.5.8" }, | 244 | } |
| 252 | | 245 | |
| 253 | .{ "10A432", "10.6.0" }, | 246 | fn skipUntilTag(self: *@This(), kind: Tag.Kind, name: []const u8) !void { |
| 254 | .{ "10A433", "10.6.0" }, | 247 | while (try self.next()) |tok| { |
| 255 | .{ "10B504", "10.6.1" }, | 248 | switch (tok) { |
| 256 | .{ "10C540", "10.6.2" }, | 249 | .tag => |tag| { |
| 257 | .{ "10D573", "10.6.3" }, | 250 | if (tag.kind == kind and std.mem.eql(u8, tag.name, name)) return; |
| 258 | .{ "10D575", "10.6.3" }, | 251 | }, |
| 259 | .{ "10D578", "10.6.3" }, | 252 | else => {}, |
| 260 | .{ "10F569", "10.6.4" }, | 253 | } |
| 261 | .{ "10H574", "10.6.5" }, | 254 | } |
| 262 | .{ "10J567", "10.6.6" }, | 255 | return error.TagNotFound; |
| 263 | .{ "10J869", "10.6.7" }, | 256 | } |
| 264 | .{ "10J3250", "10.6.7" }, | | |
| 265 | .{ "10J4138", "10.6.7" }, | | |
| 266 | .{ "10K540", "10.6.8" }, | | |
| 267 | .{ "10K549", "10.6.8" }, | | |
| 268 | | 257 | |
| 269 | .{ "11A511", "10.7.0" }, | 258 | const State = enum { |
| 270 | .{ "11A511s", "10.7.0" }, | 259 | begin, |
| 271 | .{ "11A2061", "10.7.0" }, | 260 | tag0, |
| 272 | .{ "11A2063", "10.7.0" }, | 261 | tag0_end_or_empty, |
| 273 | .{ "11B26", "10.7.1" }, | 262 | tagN, |
| 274 | .{ "11B2118", "10.7.1" }, | 263 | tagN_end, |
| 275 | .{ "11C74", "10.7.2" }, | 264 | tag_string, |
| 276 | .{ "11D50", "10.7.3" }, | 265 | content, |
| 277 | .{ "11E53", "10.7.4" }, | 266 | }; |
| 278 | .{ "11G56", "10.7.5" }, | | |
| 279 | .{ "11G63", "10.7.5" }, | | |
| 280 | | 267 | |
| 281 | .{ "12A269", "10.8.0" }, | 268 | const Token = union(enum) { |
| 282 | .{ "12B19", "10.8.1" }, | 269 | tag: Tag, |
| 283 | .{ "12C54", "10.8.2" }, | 270 | content: []const u8, |
| 284 | .{ "12C60", "10.8.2" }, | 271 | }; |
| 285 | .{ "12C2034", "10.8.2" }, | | |
| 286 | .{ "12C3104", "10.8.2" }, | | |
| 287 | .{ "12D78", "10.8.3" }, | | |
| 288 | .{ "12E55", "10.8.4" }, | | |
| 289 | .{ "12E3067", "10.8.4" }, | | |
| 290 | .{ "12E4022", "10.8.4" }, | | |
| 291 | .{ "12F37", "10.8.5" }, | | |
| 292 | .{ "12F45", "10.8.5" }, | | |
| 293 | .{ "12F2501", "10.8.5" }, | | |
| 294 | .{ "12F2518", "10.8.5" }, | | |
| 295 | .{ "12F2542", "10.8.5" }, | | |
| 296 | .{ "12F2560", "10.8.5" }, | | |
| 297 | | 272 | |
| 298 | .{ "13A603", "10.9.0" }, | 273 | const Tag = struct { |
| 299 | .{ "13B42", "10.9.1" }, | 274 | kind: Kind = .unknown, |
| 300 | .{ "13C64", "10.9.2" }, | 275 | name: []const u8 = "", |
| 301 | .{ "13C1021", "10.9.2" }, | | |
| 302 | .{ "13D65", "10.9.3" }, | | |
| 303 | .{ "13E28", "10.9.4" }, | | |
| 304 | .{ "13F34", "10.9.5" }, | | |
| 305 | .{ "13F1066", "10.9.5" }, | | |
| 306 | .{ "13F1077", "10.9.5" }, | | |
| 307 | .{ "13F1096", "10.9.5" }, | | |
| 308 | .{ "13F1112", "10.9.5" }, | | |
| 309 | .{ "13F1134", "10.9.5" }, | | |
| 310 | .{ "13F1507", "10.9.5" }, | | |
| 311 | .{ "13F1603", "10.9.5" }, | | |
| 312 | .{ "13F1712", "10.9.5" }, | | |
| 313 | .{ "13F1808", "10.9.5" }, | | |
| 314 | .{ "13F1911", "10.9.5" }, | | |
| 315 | | 276 | |
| 316 | .{ "14A389", "10.10.0" }, | 277 | const Kind = enum { unknown, start, end, empty }; |
| 317 | .{ "14B25", "10.10.1" }, | 278 | }; |
| 318 | .{ "14C109", "10.10.2" }, | 279 | }; |
| 319 | .{ "14C1510", "10.10.2" }, | | |
| 320 | .{ "14C1514", "10.10.2" }, | | |
| 321 | .{ "14C2043", "10.10.2" }, | | |
| 322 | .{ "14C2513", "10.10.2" }, | | |
| 323 | .{ "14D131", "10.10.3" }, | | |
| 324 | .{ "14D136", "10.10.3" }, | | |
| 325 | .{ "14E46", "10.10.4" }, | | |
| 326 | .{ "14F27", "10.10.5" }, | | |
| 327 | .{ "14F1021", "10.10.5" }, | | |
| 328 | .{ "14F1505", "10.10.5" }, | | |
| 329 | .{ "14F1509", "10.10.5" }, | | |
| 330 | .{ "14F1605", "10.10.5" }, | | |
| 331 | .{ "14F1713", "10.10.5" }, | | |
| 332 | .{ "14F1808", "10.10.5" }, | | |
| 333 | .{ "14F1909", "10.10.5" }, | | |
| 334 | .{ "14F1912", "10.10.5" }, | | |
| 335 | .{ "14F2009", "10.10.5" }, | | |
| 336 | .{ "14F2109", "10.10.5" }, | | |
| 337 | .{ "14F2315", "10.10.5" }, | | |
| 338 | .{ "14F2411", "10.10.5" }, | | |
| 339 | .{ "14F2511", "10.10.5" }, | | |
| 340 | | 280 | |
| 341 | .{ "15A284", "10.11.0" }, | 281 | test "detect" { |
| 342 | .{ "15B42", "10.11.1" }, | 282 | const cases = .{ |
| 343 | .{ "15C50", "10.11.2" }, | 283 | .{ |
| 344 | .{ "15D21", "10.11.3" }, | 284 | \\<?xml version="1.0" encoding="UTF-8"?> |
| 345 | .{ "15E65", "10.11.4" }, | 285 | \\<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| 346 | .{ "15F34", "10.11.5" }, | 286 | \\<plist version="1.0"> |
| 347 | .{ "15G31", "10.11.6" }, | 287 | \\<dict> |
| 348 | .{ "15G1004", "10.11.6" }, | 288 | \\ <key>ProductBuildVersion</key> |
| 349 | .{ "15G1011", "10.11.6" }, | 289 | \\ <string>7B85</string> |
| 350 | .{ "15G1108", "10.11.6" }, | 290 | \\ <key>ProductCopyright</key> |
| 351 | .{ "15G1212", "10.11.6" }, | 291 | \\ <string>Apple Computer, Inc. 1983-2003</string> |
| 352 | .{ "15G1217", "10.11.6" }, | 292 | \\ <key>ProductName</key> |
| 353 | .{ "15G1421", "10.11.6" }, | 293 | \\ <string>Mac OS X</string> |
| 354 | .{ "15G1510", "10.11.6" }, | 294 | \\ <key>ProductUserVisibleVersion</key> |
| 355 | .{ "15G1611", "10.11.6" }, | 295 | \\ <string>10.3</string> |
| 356 | .{ "15G17023", "10.11.6" }, | 296 | \\ <key>ProductVersion</key> |
| 357 | .{ "15G18013", "10.11.6" }, | 297 | \\ <string>10.3</string> |
| 358 | .{ "15G19009", "10.11.6" }, | 298 | \\</dict> |
| 359 | .{ "15G20015", "10.11.6" }, | 299 | \\</plist> |
| 360 | .{ "15G21013", "10.11.6" }, | 300 | , |
| 361 | .{ "15G22010", "10.11.6" }, | 301 | .{ .major = 10, .minor = 3 }, |
| | 302 | }, |
| | 303 | .{ |
| | 304 | \\<?xml version="1.0" encoding="UTF-8"?> |
| | 305 | \\<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| | 306 | \\<plist version="1.0"> |
| | 307 | \\<dict> |
| | 308 | \\	<key>ProductBuildVersion</key> |
| | 309 | \\	<string>7W98</string> |
| | 310 | \\	<key>ProductCopyright</key> |
| | 311 | \\	<string>Apple Computer, Inc. 1983-2004</string> |
| | 312 | \\	<key>ProductName</key> |
| | 313 | \\	<string>Mac OS X</string> |
| | 314 | \\	<key>ProductUserVisibleVersion</key> |
| | 315 | \\	<string>10.3.9</string> |
| | 316 | \\	<key>ProductVersion</key> |
| | 317 | \\	<string>10.3.9</string> |
| | 318 | \\</dict> |
| | 319 | \\</plist> |
| | 320 | , |
| | 321 | .{ .major = 10, .minor = 3, .patch = 9 }, |
| | 322 | }, |
| | 323 | .{ |
| | 324 | \\<?xml version="1.0" encoding="UTF-8"?> |
| | 325 | \\<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| | 326 | \\<plist version="1.0"> |
| | 327 | \\<dict> |
| | 328 | \\	<key>ProductBuildVersion</key> |
| | 329 | \\	<string>19G68</string> |
| | 330 | \\	<key>ProductCopyright</key> |
| | 331 | \\	<string>1983-2020 Apple Inc.</string> |
| | 332 | \\	<key>ProductName</key> |
| | 333 | \\	<string>Mac OS X</string> |
| | 334 | \\	<key>ProductUserVisibleVersion</key> |
| | 335 | \\	<string>10.15.6</string> |
| | 336 | \\	<key>ProductVersion</key> |
| | 337 | \\	<string>10.15.6</string> |
| | 338 | \\	<key>iOSSupportVersion</key> |
| | 339 | \\	<string>13.6</string> |
| | 340 | \\</dict> |
| | 341 | \\</plist> |
| | 342 | , |
| | 343 | .{ .major = 10, .minor = 15, .patch = 6 }, |
| | 344 | }, |
| | 345 | .{ |
| | 346 | \\<?xml version="1.0" encoding="UTF-8"?> |
| | 347 | \\<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| | 348 | \\<plist version="1.0"> |
| | 349 | \\<dict> |
| | 350 | \\	<key>ProductBuildVersion</key> |
| | 351 | \\	<string>20A2408</string> |
| | 352 | \\	<key>ProductCopyright</key> |
| | 353 | \\	<string>1983-2020 Apple Inc.</string> |
| | 354 | \\	<key>ProductName</key> |
| | 355 | \\	<string>macOS</string> |
| | 356 | \\	<key>ProductUserVisibleVersion</key> |
| | 357 | \\	<string>11.0</string> |
| | 358 | \\	<key>ProductVersion</key> |
| | 359 | \\	<string>11.0</string> |
| | 360 | \\	<key>iOSSupportVersion</key> |
| | 361 | \\	<string>14.2</string> |
| | 362 | \\</dict> |
| | 363 | \\</plist> |
| | 364 | , |
| | 365 | .{ .major = 11, .minor = 0 }, |
| | 366 | }, |
| | 367 | .{ |
| | 368 | \\<?xml version="1.0" encoding="UTF-8"?> |
| | 369 | \\<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| | 370 | \\<plist version="1.0"> |
| | 371 | \\<dict> |
| | 372 | \\	<key>ProductBuildVersion</key> |
| | 373 | \\	<string>20C63</string> |
| | 374 | \\	<key>ProductCopyright</key> |
| | 375 | \\	<string>1983-2020 Apple Inc.</string> |
| | 376 | \\	<key>ProductName</key> |
| | 377 | \\	<string>macOS</string> |
| | 378 | \\	<key>ProductUserVisibleVersion</key> |
| | 379 | \\	<string>11.1</string> |
| | 380 | \\	<key>ProductVersion</key> |
| | 381 | \\	<string>11.1</string> |
| | 382 | \\	<key>iOSSupportVersion</key> |
| | 383 | \\	<string>14.3</string> |
| | 384 | \\</dict> |
| | 385 | \\</plist> |
| | 386 | , |
| | 387 | .{ .major = 11, .minor = 1 }, |
| | 388 | }, |
| | 389 | }; |
| 362 | | 390 | |
| 363 | .{ "16A323", "10.12.0" }, | 391 | inline for (cases) |case| { |
| 364 | .{ "16B2555", "10.12.1" }, | 392 | const ver0 = try parseSystemVersion(case[0]); |
| 365 | .{ "16B2657", "10.12.1" }, | 393 | const ver1: std.builtin.Version = case[1]; |
| 366 | .{ "16C67", "10.12.2" }, | 394 | try testVersionEquality(ver1, ver0); |
| 367 | .{ "16C68", "10.12.2" }, | 395 | } |
| 368 | .{ "16D32", "10.12.3" }, | 396 | } |
| 369 | .{ "16E195", "10.12.4" }, | | |
| 370 | .{ "16F73", "10.12.5" }, | | |
| 371 | .{ "16F2073", "10.12.5" }, | | |
| 372 | .{ "16G29", "10.12.6" }, | | |
| 373 | .{ "16G1036", "10.12.6" }, | | |
| 374 | .{ "16G1114", "10.12.6" }, | | |
| 375 | .{ "16G1212", "10.12.6" }, | | |
| 376 | .{ "16G1314", "10.12.6" }, | | |
| 377 | .{ "16G1408", "10.12.6" }, | | |
| 378 | .{ "16G1510", "10.12.6" }, | | |
| 379 | .{ "16G1618", "10.12.6" }, | | |
| 380 | .{ "16G1710", "10.12.6" }, | | |
| 381 | .{ "16G1815", "10.12.6" }, | | |
| 382 | .{ "16G1917", "10.12.6" }, | | |
| 383 | .{ "16G1918", "10.12.6" }, | | |
| 384 | .{ "16G2016", "10.12.6" }, | | |
| 385 | .{ "16G2127", "10.12.6" }, | | |
| 386 | .{ "16G2128", "10.12.6" }, | | |
| 387 | .{ "16G2136", "10.12.6" }, | | |
| 388 | | 397 | |
| 389 | .{ "17A365", "10.13.0" }, | 398 | fn testVersionEquality(expected: std.builtin.Version, got: std.builtin.Version) !void { |
| 390 | .{ "17A405", "10.13.0" }, | 399 | var b_expected: [64]u8 = undefined; |
| 391 | .{ "17B48", "10.13.1" }, | 400 | const s_expected: []const u8 = try std.fmt.bufPrint(b_expected[0..], "{}", .{expected}); |
| 392 | .{ "17B1002", "10.13.1" }, | | |
| 393 | .{ "17B1003", "10.13.1" }, | | |
| 394 | .{ "17C88", "10.13.2" }, | | |
| 395 | .{ "17C89", "10.13.2" }, | | |
| 396 | .{ "17C205", "10.13.2" }, | | |
| 397 | .{ "17C2205", "10.13.2" }, | | |
| 398 | .{ "17D47", "10.13.3" }, | | |
| 399 | .{ "17D2047", "10.13.3" }, | | |
| 400 | .{ "17D102", "10.13.3" }, | | |
| 401 | .{ "17D2102", "10.13.3" }, | | |
| 402 | .{ "17E199", "10.13.4" }, | | |
| 403 | .{ "17E202", "10.13.4" }, | | |
| 404 | .{ "17F77", "10.13.5" }, | | |
| 405 | .{ "17G65", "10.13.6" }, | | |
| 406 | .{ "17G2208", "10.13.6" }, | | |
| 407 | .{ "17G3025", "10.13.6" }, | | |
| 408 | .{ "17G4015", "10.13.6" }, | | |
| 409 | .{ "17G5019", "10.13.6" }, | | |
| 410 | .{ "17G6029", "10.13.6" }, | | |
| 411 | .{ "17G6030", "10.13.6" }, | | |
| 412 | .{ "17G7024", "10.13.6" }, | | |
| 413 | .{ "17G8029", "10.13.6" }, | | |
| 414 | .{ "17G8030", "10.13.6" }, | | |
| 415 | .{ "17G8037", "10.13.6" }, | | |
| 416 | .{ "17G9016", "10.13.6" }, | | |
| 417 | .{ "17G10021", "10.13.6" }, | | |
| 418 | .{ "17G11023", "10.13.6" }, | | |
| 419 | .{ "17G12034", "10.13.6" }, | | |
| 420 | | 401 | |
| 421 | .{ "18A391", "10.14.0" }, | 402 | var b_got: [64]u8 = undefined; |
| 422 | .{ "18B75", "10.14.1" }, | 403 | const s_got: []const u8 = try std.fmt.bufPrint(b_got[0..], "{}", .{got}); |
| 423 | .{ "18B2107", "10.14.1" }, | | |
| 424 | .{ "18B3094", "10.14.1" }, | | |
| 425 | .{ "18C54", "10.14.2" }, | | |
| 426 | .{ "18D42", "10.14.3" }, | | |
| 427 | .{ "18D43", "10.14.3" }, | | |
| 428 | .{ "18D109", "10.14.3" }, | | |
| 429 | .{ "18E226", "10.14.4" }, | | |
| 430 | .{ "18E227", "10.14.4" }, | | |
| 431 | .{ "18F132", "10.14.5" }, | | |
| 432 | .{ "18G84", "10.14.6" }, | | |
| 433 | .{ "18G87", "10.14.6" }, | | |
| 434 | .{ "18G95", "10.14.6" }, | | |
| 435 | .{ "18G103", "10.14.6" }, | | |
| 436 | .{ "18G1012", "10.14.6" }, | | |
| 437 | .{ "18G2022", "10.14.6" }, | | |
| 438 | .{ "18G3020", "10.14.6" }, | | |
| 439 | .{ "18G4032", "10.14.6" }, | | |
| 440 | | 404 | |
| 441 | .{ "19A583", "10.15.0" }, | 405 | testing.expectEqualStrings(s_expected, s_got); |
| 442 | .{ "19A602", "10.15.0" }, | | |
| 443 | .{ "19A603", "10.15.0" }, | | |
| 444 | .{ "19B88", "10.15.1" }, | | |
| 445 | .{ "19C57", "10.15.2" }, | | |
| 446 | .{ "19D76", "10.15.3" }, | | |
| 447 | .{ "19E266", "10.15.4" }, | | |
| 448 | .{ "19E287", "10.15.4" }, | | |
| 449 | }; | | |
| 450 | for (known) |pair| { | | |
| 451 | var buf: [32]u8 = undefined; | | |
| 452 | const ver = try version_from_build(pair[0]); | | |
| 453 | const sver = try std.fmt.bufPrint(buf[0..], "{d}.{d}.{d}", .{ ver.major, ver.minor, ver.patch }); | | |
| 454 | std.testing.expect(std.mem.eql(u8, sver, pair[1])); | | |
| 455 | } | | |
| 456 | } | 406 | } |
| 457 | | 407 | |
| 458 | /// Detect SDK path on Darwin. | 408 | /// Detect SDK path on Darwin. |