| ... | ... | @@ -6,453 +6,408 @@ |
| 6 | 6 | const std = @import("std"); |
| 7 | 7 | const assert = std.debug.assert; |
| 8 | 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 | | // build format: |
| 12 | | // 19E287 (example) |
| 13 | | // xxyzzz |
| 11 | /// Detect macOS version. |
| 12 | /// On error `os` will not be modified. |
| 13 | pub fn detect(os: *std.Target.Os) !void { |
| 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 |
| 16 | | // minor = x - 4 = 19 - 4 = 15 |
| 17 | | // patch = ascii(y) - 'A' = 'E' - 'A' = 69 - 65 = 4 |
| 18 | | // answer: 10.15.4 |
| 18 | // NEW APPROACH, STEP 1, parse file: |
| 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 | // |
| 22 | // NOTE: Historically `SystemVersion.plist` first appeared circa '2003 |
| 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 a red herring |
| 26 | // and is discarded, and move on to next step. Otherwise we accept this is the |
| 27 | // canonical file for versioning. |
| 28 | // |
| 29 | // BACKGROUND: `10.(16+)` is not a proper version and does not have enough fidelity to |
| 30 | // indicate minor/point version of Big Sur and later. It is a context-sensitive result |
| 31 | // issued by the kernel for backwards compatibility purposes. Likely the kernel checks |
| 32 | // if the executable was linked against an SDK older than Big Sur. |
| 33 | // |
| 34 | // STEP 2, parse next file: |
| 35 | // |
| 36 | // /System/Library/CoreServices/.SystemVersionPlatform.plist |
| 37 | // |
| 38 | // NOTE: Historically `SystemVersionPlatform.plist` first appeared circa '2020 |
| 39 | // with the release of macOS 11.0 Big Sur. |
| 40 | // |
| 41 | // Accessing the content via this path circumvents a context-sensitive result and |
| 42 | // yields a canonical Big Sur version. |
| 43 | // |
| 44 | // At this time there is no other known way for a < SDK 11.0 executable to obtain a |
| 45 | // canonical Big Sur version. |
| 46 | // |
| 47 | // This implementation uses a reasonably simplified approach to parse .plist file |
| 48 | // that while it is an xml document, we have good history on the file and its format |
| 49 | // such that I am comfortable with implementing a minimalistic parser. |
| 50 | // Things like string and general escapes are not supported. |
| 51 | const prefixSlash = "/System/Library/CoreServices/"; |
| 52 | const format_failure = "macOS detect: failed to {s} '{s}': {}"; |
| 21 | 53 | |
| 22 | | // never return anything below 10.0.0 |
| 23 | | var result = std.builtin.Version{ .major = 10, .minor = 0, .patch = 0 }; |
| 54 | const paths = [_][]const u8{ |
| 55 | prefixSlash ++ "SystemVersion.plist", |
| 56 | prefixSlash ++ ".SystemVersionPlatform.plist", |
| 57 | }; |
| 58 | for (paths) |path| { |
| 59 | // approx. 4 times historical file size |
| 60 | var buf: [2048]u8 = undefined; |
| 24 | 61 | |
| 25 | | // parse format-x |
| 26 | | var yindex: usize = 0; |
| 27 | | { |
| 28 | | while (yindex < build.len) : (yindex += 1) { |
| 29 | | if (build[yindex] < '0' or build[yindex] > '9') break; |
| 62 | if (std.fs.cwd().readFile(path, &buf)) |bytes| { |
| 63 | if (parseSystemVersion(bytes)) |ver| { |
| 64 | // never return red herring |
| 65 | if (!(ver.major == 10 and ver.minor >= 16)) { |
| 66 | os.version_range.semver.min = ver; |
| 67 | os.version_range.semver.max = ver; |
| 68 | return; |
| 69 | } |
| 70 | continue; |
| 71 | } else |err| { |
| 72 | std.log.err(format_failure, .{ "parse", path, err }); |
| 73 | return error.OSVersionDetectionFail; |
| 74 | } |
| 75 | } else |err| { |
| 76 | std.log.err(format_failure, .{ "read", path, err }); |
| 77 | return error.OSVersionDetectionFail; |
| 30 | 78 | } |
| 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 | 79 | } |
| 80 | return error.OSVersionDetectionFail; |
| 81 | } |
| 36 | 82 | |
| 37 | | // parse format-y, format-z |
| 38 | | { |
| 39 | | // expect two more |
| 40 | | if (build.len < yindex + 2) return error.InvalidVersion; |
| 41 | | const y = build[yindex]; |
| 42 | | if (y < 'A') return error.InvalidVersion; |
| 43 | | var zend = yindex + 1; |
| 44 | | while (zend < build.len) { |
| 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 | | }; |
| 83 | fn parseSystemVersion(buf: []const u8) !std.builtin.Version { |
| 84 | var svt = SystemVersionTokenizer{ .bytes = buf }; |
| 85 | try svt.skipUntilTag(.start, "dict"); |
| 86 | while (true) { |
| 87 | try svt.skipUntilTag(.start, "key"); |
| 88 | const content = try svt.expectContent(); |
| 89 | try svt.skipUntilTag(.end, "key"); |
| 90 | if (std.mem.eql(u8, content, "ProductVersion")) break; |
| 164 | 91 | } |
| 165 | | return result; |
| 166 | | } |
| 92 | try svt.skipUntilTag(.start, "string"); |
| 93 | const ver = try svt.expectContent(); |
| 94 | try svt.skipUntilTag(.end, "string"); |
| 167 | 95 | |
| 168 | | test "version_from_build" { |
| 169 | | // see https://en.wikipedia.org/wiki/MacOS_version_history#Releases |
| 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" }, |
| 96 | return std.builtin.Version.parse(ver); |
| 97 | } |
| 176 | 98 | |
| 177 | | .{ "5G64", "10.1.0" }, |
| 178 | | .{ "5M28", "10.1.1" }, |
| 179 | | .{ "5P48", "10.1.2" }, |
| 180 | | .{ "5Q45", "10.1.3" }, |
| 181 | | .{ "5Q125", "10.1.4" }, |
| 182 | | .{ "5S60", "10.1.5" }, |
| 99 | const SystemVersionTokenizer = struct { |
| 100 | bytes: []const u8, |
| 101 | index: usize = 0, |
| 102 | state: State = .begin, |
| 183 | 103 | |
| 184 | | .{ "6C115", "10.2.0" }, |
| 185 | | .{ "6C115a", "10.2.0" }, |
| 186 | | .{ "6D52", "10.2.1" }, |
| 187 | | .{ "6F21", "10.2.2" }, |
| 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" }, |
| 104 | fn next(self: *@This()) !?Token { |
| 105 | var mark: usize = self.index; |
| 106 | var tag = Tag{}; |
| 107 | var content: []const u8 = ""; |
| 197 | 108 | |
| 198 | | .{ "7B85", "10.3.0" }, |
| 199 | | .{ "7B86", "10.3.0" }, |
| 200 | | .{ "7C107", "10.3.1" }, |
| 201 | | .{ "7D24", "10.3.2" }, |
| 202 | | .{ "7D28", "10.3.2" }, |
| 203 | | .{ "7F44", "10.3.3" }, |
| 204 | | .{ "7H63", "10.3.4" }, |
| 205 | | .{ "7M34", "10.3.5" }, |
| 206 | | .{ "7R28", "10.3.6" }, |
| 207 | | .{ "7S215", "10.3.7" }, |
| 208 | | .{ "7U16", "10.3.8" }, |
| 209 | | .{ "7W98", "10.3.9" }, |
| 109 | while (self.index < self.bytes.len) { |
| 110 | const char = self.bytes[self.index]; |
| 111 | switch (self.state) { |
| 112 | .begin => switch (char) { |
| 113 | '<' => { |
| 114 | self.state = .tag0; |
| 115 | self.index += 1; |
| 116 | tag = Tag{}; |
| 117 | mark = self.index; |
| 118 | }, |
| 119 | '>' => { |
| 120 | return error.BadToken; |
| 121 | }, |
| 122 | else => { |
| 123 | self.state = .content; |
| 124 | content = ""; |
| 125 | mark = self.index; |
| 126 | }, |
| 127 | }, |
| 128 | .tag0 => switch (char) { |
| 129 | '<' => { |
| 130 | return error.BadToken; |
| 131 | }, |
| 132 | '>' => { |
| 133 | self.state = .begin; |
| 134 | self.index += 1; |
| 135 | tag.name = self.bytes[mark..self.index]; |
| 136 | return Token{ .tag = tag }; |
| 137 | }, |
| 138 | '"' => { |
| 139 | self.state = .tag_string; |
| 140 | self.index += 1; |
| 141 | }, |
| 142 | '/' => { |
| 143 | self.state = .tag0_end_or_empty; |
| 144 | self.index += 1; |
| 145 | }, |
| 146 | 'A'...'Z', 'a'...'z' => { |
| 147 | self.state = .tagN; |
| 148 | tag.kind = .start; |
| 149 | self.index += 1; |
| 150 | }, |
| 151 | else => { |
| 152 | self.state = .tagN; |
| 153 | self.index += 1; |
| 154 | }, |
| 155 | }, |
| 156 | .tag0_end_or_empty => switch (char) { |
| 157 | '<' => { |
| 158 | return error.BadToken; |
| 159 | }, |
| 160 | '>' => { |
| 161 | self.state = .begin; |
| 162 | tag.kind = .empty; |
| 163 | tag.name = self.bytes[self.index..self.index]; |
| 164 | self.index += 1; |
| 165 | return Token{ .tag = tag }; |
| 166 | }, |
| 167 | else => { |
| 168 | self.state = .tagN; |
| 169 | tag.kind = .end; |
| 170 | mark = self.index; |
| 171 | self.index += 1; |
| 172 | }, |
| 173 | }, |
| 174 | .tagN => switch (char) { |
| 175 | '<' => { |
| 176 | return error.BadToken; |
| 177 | }, |
| 178 | '>' => { |
| 179 | self.state = .begin; |
| 180 | tag.name = self.bytes[mark..self.index]; |
| 181 | self.index += 1; |
| 182 | return Token{ .tag = tag }; |
| 183 | }, |
| 184 | '"' => { |
| 185 | self.state = .tag_string; |
| 186 | self.index += 1; |
| 187 | }, |
| 188 | '/' => { |
| 189 | self.state = .tagN_end; |
| 190 | tag.kind = .end; |
| 191 | self.index += 1; |
| 192 | }, |
| 193 | else => { |
| 194 | self.index += 1; |
| 195 | }, |
| 196 | }, |
| 197 | .tagN_end => switch (char) { |
| 198 | '>' => { |
| 199 | self.state = .begin; |
| 200 | tag.name = self.bytes[mark..self.index]; |
| 201 | self.index += 1; |
| 202 | return Token{ .tag = tag }; |
| 203 | }, |
| 204 | else => { |
| 205 | return error.BadToken; |
| 206 | }, |
| 207 | }, |
| 208 | .tag_string => switch (char) { |
| 209 | '"' => { |
| 210 | self.state = .tagN; |
| 211 | self.index += 1; |
| 212 | }, |
| 213 | else => { |
| 214 | self.index += 1; |
| 215 | }, |
| 216 | }, |
| 217 | .content => switch (char) { |
| 218 | '<' => { |
| 219 | self.state = .tag0; |
| 220 | content = self.bytes[mark..self.index]; |
| 221 | self.index += 1; |
| 222 | tag = Tag{}; |
| 223 | mark = self.index; |
| 224 | return Token{ .content = content }; |
| 225 | }, |
| 226 | '>' => { |
| 227 | return error.BadToken; |
| 228 | }, |
| 229 | else => { |
| 230 | self.index += 1; |
| 231 | }, |
| 232 | }, |
| 233 | } |
| 234 | } |
| 210 | 235 | |
| 211 | | .{ "8A428", "10.4.0" }, |
| 212 | | .{ "8A432", "10.4.0" }, |
| 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" }, |
| 236 | return null; |
| 237 | } |
| 240 | 238 | |
| 241 | | .{ "9A581", "10.5.0" }, |
| 242 | | .{ "9B18", "10.5.1" }, |
| 243 | | .{ "9C31", "10.5.2" }, |
| 244 | | .{ "9C7010", "10.5.2" }, |
| 245 | | .{ "9D34", "10.5.3" }, |
| 246 | | .{ "9E17", "10.5.4" }, |
| 247 | | .{ "9F33", "10.5.5" }, |
| 248 | | .{ "9G55", "10.5.6" }, |
| 249 | | .{ "9G66", "10.5.6" }, |
| 250 | | .{ "9J61", "10.5.7" }, |
| 251 | | .{ "9L30", "10.5.8" }, |
| 239 | fn expectContent(self: *@This()) ![]const u8 { |
| 240 | if (try self.next()) |tok| { |
| 241 | switch (tok) { |
| 242 | .content => |content| { |
| 243 | return content; |
| 244 | }, |
| 245 | else => {}, |
| 246 | } |
| 247 | } |
| 248 | return error.UnexpectedToken; |
| 249 | } |
| 252 | 250 | |
| 253 | | .{ "10A432", "10.6.0" }, |
| 254 | | .{ "10A433", "10.6.0" }, |
| 255 | | .{ "10B504", "10.6.1" }, |
| 256 | | .{ "10C540", "10.6.2" }, |
| 257 | | .{ "10D573", "10.6.3" }, |
| 258 | | .{ "10D575", "10.6.3" }, |
| 259 | | .{ "10D578", "10.6.3" }, |
| 260 | | .{ "10F569", "10.6.4" }, |
| 261 | | .{ "10H574", "10.6.5" }, |
| 262 | | .{ "10J567", "10.6.6" }, |
| 263 | | .{ "10J869", "10.6.7" }, |
| 264 | | .{ "10J3250", "10.6.7" }, |
| 265 | | .{ "10J4138", "10.6.7" }, |
| 266 | | .{ "10K540", "10.6.8" }, |
| 267 | | .{ "10K549", "10.6.8" }, |
| 251 | fn skipUntilTag(self: *@This(), kind: Tag.Kind, name: []const u8) !void { |
| 252 | while (try self.next()) |tok| { |
| 253 | switch (tok) { |
| 254 | .tag => |tag| { |
| 255 | if (tag.kind == kind and std.mem.eql(u8, tag.name, name)) return; |
| 256 | }, |
| 257 | else => {}, |
| 258 | } |
| 259 | } |
| 260 | return error.TagNotFound; |
| 261 | } |
| 268 | 262 | |
| 269 | | .{ "11A511", "10.7.0" }, |
| 270 | | .{ "11A511s", "10.7.0" }, |
| 271 | | .{ "11A2061", "10.7.0" }, |
| 272 | | .{ "11A2063", "10.7.0" }, |
| 273 | | .{ "11B26", "10.7.1" }, |
| 274 | | .{ "11B2118", "10.7.1" }, |
| 275 | | .{ "11C74", "10.7.2" }, |
| 276 | | .{ "11D50", "10.7.3" }, |
| 277 | | .{ "11E53", "10.7.4" }, |
| 278 | | .{ "11G56", "10.7.5" }, |
| 279 | | .{ "11G63", "10.7.5" }, |
| 263 | const State = enum { |
| 264 | begin, |
| 265 | tag0, |
| 266 | tag0_end_or_empty, |
| 267 | tagN, |
| 268 | tagN_end, |
| 269 | tag_string, |
| 270 | content, |
| 271 | }; |
| 280 | 272 | |
| 281 | | .{ "12A269", "10.8.0" }, |
| 282 | | .{ "12B19", "10.8.1" }, |
| 283 | | .{ "12C54", "10.8.2" }, |
| 284 | | .{ "12C60", "10.8.2" }, |
| 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" }, |
| 273 | const Token = union(enum) { |
| 274 | tag: Tag, |
| 275 | content: []const u8, |
| 276 | }; |
| 297 | 277 | |
| 298 | | .{ "13A603", "10.9.0" }, |
| 299 | | .{ "13B42", "10.9.1" }, |
| 300 | | .{ "13C64", "10.9.2" }, |
| 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" }, |
| 278 | const Tag = struct { |
| 279 | kind: Kind = .unknown, |
| 280 | name: []const u8 = "", |
| 315 | 281 | |
| 316 | | .{ "14A389", "10.10.0" }, |
| 317 | | .{ "14B25", "10.10.1" }, |
| 318 | | .{ "14C109", "10.10.2" }, |
| 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" }, |
| 282 | const Kind = enum { unknown, start, end, empty }; |
| 283 | }; |
| 284 | }; |
| 340 | 285 | |
| 341 | | .{ "15A284", "10.11.0" }, |
| 342 | | .{ "15B42", "10.11.1" }, |
| 343 | | .{ "15C50", "10.11.2" }, |
| 344 | | .{ "15D21", "10.11.3" }, |
| 345 | | .{ "15E65", "10.11.4" }, |
| 346 | | .{ "15F34", "10.11.5" }, |
| 347 | | .{ "15G31", "10.11.6" }, |
| 348 | | .{ "15G1004", "10.11.6" }, |
| 349 | | .{ "15G1011", "10.11.6" }, |
| 350 | | .{ "15G1108", "10.11.6" }, |
| 351 | | .{ "15G1212", "10.11.6" }, |
| 352 | | .{ "15G1217", "10.11.6" }, |
| 353 | | .{ "15G1421", "10.11.6" }, |
| 354 | | .{ "15G1510", "10.11.6" }, |
| 355 | | .{ "15G1611", "10.11.6" }, |
| 356 | | .{ "15G17023", "10.11.6" }, |
| 357 | | .{ "15G18013", "10.11.6" }, |
| 358 | | .{ "15G19009", "10.11.6" }, |
| 359 | | .{ "15G20015", "10.11.6" }, |
| 360 | | .{ "15G21013", "10.11.6" }, |
| 361 | | .{ "15G22010", "10.11.6" }, |
| 286 | test "detect" { |
| 287 | const cases = .{ |
| 288 | .{ |
| 289 | \\<?xml version="1.0" encoding="UTF-8"?> |
| 290 | \\<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| 291 | \\<plist version="1.0"> |
| 292 | \\<dict> |
| 293 | \\ <key>ProductBuildVersion</key> |
| 294 | \\ <string>7B85</string> |
| 295 | \\ <key>ProductCopyright</key> |
| 296 | \\ <string>Apple Computer, Inc. 1983-2003</string> |
| 297 | \\ <key>ProductName</key> |
| 298 | \\ <string>Mac OS X</string> |
| 299 | \\ <key>ProductUserVisibleVersion</key> |
| 300 | \\ <string>10.3</string> |
| 301 | \\ <key>ProductVersion</key> |
| 302 | \\ <string>10.3</string> |
| 303 | \\</dict> |
| 304 | \\</plist> |
| 305 | , |
| 306 | .{ .major = 10, .minor = 3 }, |
| 307 | }, |
| 308 | .{ |
| 309 | \\<?xml version="1.0" encoding="UTF-8"?> |
| 310 | \\<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| 311 | \\<plist version="1.0"> |
| 312 | \\<dict> |
| 313 | \\	<key>ProductBuildVersion</key> |
| 314 | \\	<string>7W98</string> |
| 315 | \\	<key>ProductCopyright</key> |
| 316 | \\	<string>Apple Computer, Inc. 1983-2004</string> |
| 317 | \\	<key>ProductName</key> |
| 318 | \\	<string>Mac OS X</string> |
| 319 | \\	<key>ProductUserVisibleVersion</key> |
| 320 | \\	<string>10.3.9</string> |
| 321 | \\	<key>ProductVersion</key> |
| 322 | \\	<string>10.3.9</string> |
| 323 | \\</dict> |
| 324 | \\</plist> |
| 325 | , |
| 326 | .{ .major = 10, .minor = 3, .patch = 9 }, |
| 327 | }, |
| 328 | .{ |
| 329 | \\<?xml version="1.0" encoding="UTF-8"?> |
| 330 | \\<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| 331 | \\<plist version="1.0"> |
| 332 | \\<dict> |
| 333 | \\	<key>ProductBuildVersion</key> |
| 334 | \\	<string>19G68</string> |
| 335 | \\	<key>ProductCopyright</key> |
| 336 | \\	<string>1983-2020 Apple Inc.</string> |
| 337 | \\	<key>ProductName</key> |
| 338 | \\	<string>Mac OS X</string> |
| 339 | \\	<key>ProductUserVisibleVersion</key> |
| 340 | \\	<string>10.15.6</string> |
| 341 | \\	<key>ProductVersion</key> |
| 342 | \\	<string>10.15.6</string> |
| 343 | \\	<key>iOSSupportVersion</key> |
| 344 | \\	<string>13.6</string> |
| 345 | \\</dict> |
| 346 | \\</plist> |
| 347 | , |
| 348 | .{ .major = 10, .minor = 15, .patch = 6 }, |
| 349 | }, |
| 350 | .{ |
| 351 | \\<?xml version="1.0" encoding="UTF-8"?> |
| 352 | \\<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| 353 | \\<plist version="1.0"> |
| 354 | \\<dict> |
| 355 | \\	<key>ProductBuildVersion</key> |
| 356 | \\	<string>20A2408</string> |
| 357 | \\	<key>ProductCopyright</key> |
| 358 | \\	<string>1983-2020 Apple Inc.</string> |
| 359 | \\	<key>ProductName</key> |
| 360 | \\	<string>macOS</string> |
| 361 | \\	<key>ProductUserVisibleVersion</key> |
| 362 | \\	<string>11.0</string> |
| 363 | \\	<key>ProductVersion</key> |
| 364 | \\	<string>11.0</string> |
| 365 | \\	<key>iOSSupportVersion</key> |
| 366 | \\	<string>14.2</string> |
| 367 | \\</dict> |
| 368 | \\</plist> |
| 369 | , |
| 370 | .{ .major = 11, .minor = 0 }, |
| 371 | }, |
| 372 | .{ |
| 373 | \\<?xml version="1.0" encoding="UTF-8"?> |
| 374 | \\<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| 375 | \\<plist version="1.0"> |
| 376 | \\<dict> |
| 377 | \\	<key>ProductBuildVersion</key> |
| 378 | \\	<string>20C63</string> |
| 379 | \\	<key>ProductCopyright</key> |
| 380 | \\	<string>1983-2020 Apple Inc.</string> |
| 381 | \\	<key>ProductName</key> |
| 382 | \\	<string>macOS</string> |
| 383 | \\	<key>ProductUserVisibleVersion</key> |
| 384 | \\	<string>11.1</string> |
| 385 | \\	<key>ProductVersion</key> |
| 386 | \\	<string>11.1</string> |
| 387 | \\	<key>iOSSupportVersion</key> |
| 388 | \\	<string>14.3</string> |
| 389 | \\</dict> |
| 390 | \\</plist> |
| 391 | , |
| 392 | .{ .major = 11, .minor = 1 }, |
| 393 | }, |
| 394 | }; |
| 362 | 395 | |
| 363 | | .{ "16A323", "10.12.0" }, |
| 364 | | .{ "16B2555", "10.12.1" }, |
| 365 | | .{ "16B2657", "10.12.1" }, |
| 366 | | .{ "16C67", "10.12.2" }, |
| 367 | | .{ "16C68", "10.12.2" }, |
| 368 | | .{ "16D32", "10.12.3" }, |
| 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" }, |
| 396 | inline for (cases) |case| { |
| 397 | const ver0 = try parseSystemVersion(case[0]); |
| 398 | const ver1: std.builtin.Version = case[1]; |
| 399 | try testVersionEquality(ver1, ver0); |
| 400 | } |
| 401 | } |
| 388 | 402 | |
| 389 | | .{ "17A365", "10.13.0" }, |
| 390 | | .{ "17A405", "10.13.0" }, |
| 391 | | .{ "17B48", "10.13.1" }, |
| 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" }, |
| 403 | fn testVersionEquality(expected: std.builtin.Version, got: std.builtin.Version) !void { |
| 404 | var b_expected: [64]u8 = undefined; |
| 405 | const s_expected: []const u8 = try std.fmt.bufPrint(b_expected[0..], "{}", .{expected}); |
| 420 | 406 | |
| 421 | | .{ "18A391", "10.14.0" }, |
| 422 | | .{ "18B75", "10.14.1" }, |
| 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" }, |
| 407 | var b_got: [64]u8 = undefined; |
| 408 | const s_got: []const u8 = try std.fmt.bufPrint(b_got[0..], "{}", .{got}); |
| 440 | 409 | |
| 441 | | .{ "19A583", "10.15.0" }, |
| 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 | | } |
| 410 | testing.expectEqualStrings(s_expected, s_got); |
| 456 | 411 | } |
| 457 | 412 | |
| 458 | 413 | /// Detect SDK path on Darwin. |