authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-01-12 16:45:50-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-01-12 16:45:50-08:00
loge564d2ca3c7a7b2bdb18649e9bdd24f06478f2df
treec0d6a271aea50584ca5062343d71b590bfebb817
parent1e2be14b6b77c0d13480fad49aa9445eb18213d7
parent4c3de99253487620d3897df29eac7f14553a2632
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #7714 from mikdusan/target-macos

macos: reimplement OS version detection

3 files changed, 376 insertions(+), 456 deletions(-)

lib/std/target.zig+1-1
......@@ -260,7 +260,7 @@ pub const Target = struct {
260260 .macos => return .{
261261 .semver = .{
262262 .min = .{ .major = 10, .minor = 13 },
263 .max = .{ .major = 10, .minor = 15, .patch = 7 },
263 .max = .{ .major = 11, .minor = 1 },
264264 },
265265 },
266266 .ios => return .{
lib/std/zig/system.zig+2-32
......@@ -222,6 +222,7 @@ pub const NativeTargetInfo = struct {
222222 ProcessFdQuotaExceeded,
223223 SystemFdQuotaExceeded,
224224 DeviceBusy,
225 OSVersionDetectionFail,
225226 };
226227
227228 /// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected
......@@ -254,38 +255,7 @@ pub const NativeTargetInfo = struct {
254255 os.version_range.windows.min = detected_version;
255256 os.version_range.windows.max = detected_version;
256257 },
257 .macos => {
258 var scbuf: [32]u8 = undefined;
259 var size: usize = undefined;
260
261 // The osproductversion sysctl was introduced first with 10.13.4 High Sierra.
262 const key_osproductversion = "kern.osproductversion"; // eg. "10.15.4"
263 size = scbuf.len;
264 if (std.os.sysctlbynameZ(key_osproductversion, &scbuf, &size, null, 0)) |_| {
265 const string_version = scbuf[0 .. size - 1];
266 if (std.builtin.Version.parse(string_version)) |ver| {
267 os.version_range.semver.min = ver;
268 os.version_range.semver.max = ver;
269 } else |err| switch (err) {
270 error.Overflow => {},
271 error.InvalidCharacter => {},
272 error.InvalidVersion => {},
273 }
274 } else |err| switch (err) {
275 error.UnknownName => {
276 const key_osversion = "kern.osversion"; // eg. "19E287"
277 size = scbuf.len;
278 std.os.sysctlbynameZ(key_osversion, &scbuf, &size, null, 0) catch {
279 @panic("unable to detect macOS version: " ++ key_osversion);
280 };
281 if (macos.version_from_build(scbuf[0 .. size - 1])) |ver| {
282 os.version_range.semver.min = ver;
283 os.version_range.semver.max = ver;
284 } else |_| {}
285 },
286 else => @panic("unable to detect macOS version: " ++ key_osproductversion),
287 }
288 },
258 .macos => try macos.detect(&os),
289259 .freebsd => {
290260 var osreldate: u32 = undefined;
291261 var len: usize = undefined;
lib/std/zig/system/macos.zig+373-423
......@@ -6,453 +6,403 @@
66const std = @import("std");
77const assert = std.debug.assert;
88const mem = std.mem;
9const testing = std.testing;
910
10pub fn version_from_build(build: []const u8) !std.builtin.Version {
11 // build format:
12 // 19E287 (example)
13 // xxyzzz
11/// Detect macOS version.
12/// `os` is not modified in case of error.
13pub 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
1417 //
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:
1919 //
20 // note: patch is typical but with some older releases (before 10.8) zzz is considered
21
22 // never return anything below 10.0.0
23 var result = std.builtin.Version{ .major = 10, .minor = 0, .patch = 0 };
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 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;
2458
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;
59 if (std.fs.cwd().readFile(path, &buf)) |bytes| {
60 if (parseSystemVersion(bytes)) |ver| {
61 // never return non-canonical `10.(16+)`
62 if (!(ver.major == 10 and ver.minor >= 16)) {
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;
3073 }
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;
3574 }
75 return error.OSVersionDetectionFail;
76}
3677
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 };
78fn parseSystemVersion(buf: []const u8) !std.builtin.Version {
79 var svt = SystemVersionTokenizer{ .bytes = buf };
80 try svt.skipUntilTag(.start, "dict");
81 while (true) {
82 try svt.skipUntilTag(.start, "key");
83 const content = try svt.expectContent();
84 try svt.skipUntilTag(.end, "key");
85 if (std.mem.eql(u8, content, "ProductVersion")) break;
16486 }
165 return result;
166}
87 try svt.skipUntilTag(.start, "string");
88 const ver = try svt.expectContent();
89 try svt.skipUntilTag(.end, "string");
16790
168test "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" },
91 return std.builtin.Version.parse(ver);
92}
17693
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" },
94const SystemVersionTokenizer = struct {
95 bytes: []const u8,
96 index: usize = 0,
97 state: State = .begin,
18398
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" },
99 fn next(self: *@This()) !?Token {
100 var mark: usize = self.index;
101 var tag = Tag{};
102 var content: []const u8 = "";
197103
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" },
104 while (self.index < self.bytes.len) {
105 const char = self.bytes[self.index];
106 switch (self.state) {
107 .begin => switch (char) {
108 '<' => {
109 self.state = .tag0;
110 self.index += 1;
111 tag = Tag{};
112 mark = self.index;
113 },
114 '>' => {
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 }
210230
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" },
231 return null;
232 }
240233
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" },
234 fn expectContent(self: *@This()) ![]const u8 {
235 if (try self.next()) |tok| {
236 switch (tok) {
237 .content => |content| {
238 return content;
239 },
240 else => {},
241 }
242 }
243 return error.UnexpectedToken;
244 }
252245
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" },
246 fn skipUntilTag(self: *@This(), kind: Tag.Kind, name: []const u8) !void {
247 while (try self.next()) |tok| {
248 switch (tok) {
249 .tag => |tag| {
250 if (tag.kind == kind and std.mem.eql(u8, tag.name, name)) return;
251 },
252 else => {},
253 }
254 }
255 return error.TagNotFound;
256 }
268257
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" },
258 const State = enum {
259 begin,
260 tag0,
261 tag0_end_or_empty,
262 tagN,
263 tagN_end,
264 tag_string,
265 content,
266 };
280267
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" },
268 const Token = union(enum) {
269 tag: Tag,
270 content: []const u8,
271 };
297272
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" },
273 const Tag = struct {
274 kind: Kind = .unknown,
275 name: []const u8 = "",
315276
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" },
277 const Kind = enum { unknown, start, end, empty };
278 };
279};
340280
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" },
281test "detect" {
282 const cases = .{
283 .{
284 \\<?xml version="1.0" encoding="UTF-8"?>
285 \\<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
286 \\<plist version="1.0">
287 \\<dict>
288 \\ <key>ProductBuildVersion</key>
289 \\ <string>7B85</string>
290 \\ <key>ProductCopyright</key>
291 \\ <string>Apple Computer, Inc. 1983-2003</string>
292 \\ <key>ProductName</key>
293 \\ <string>Mac OS X</string>
294 \\ <key>ProductUserVisibleVersion</key>
295 \\ <string>10.3</string>
296 \\ <key>ProductVersion</key>
297 \\ <string>10.3</string>
298 \\</dict>
299 \\</plist>
300 ,
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 };
362390
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" },
391 inline for (cases) |case| {
392 const ver0 = try parseSystemVersion(case[0]);
393 const ver1: std.builtin.Version = case[1];
394 try testVersionEquality(ver1, ver0);
395 }
396}
388397
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" },
398fn testVersionEquality(expected: std.builtin.Version, got: std.builtin.Version) !void {
399 var b_expected: [64]u8 = undefined;
400 const s_expected: []const u8 = try std.fmt.bufPrint(b_expected[0..], "{}", .{expected});
420401
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" },
402 var b_got: [64]u8 = undefined;
403 const s_got: []const u8 = try std.fmt.bufPrint(b_got[0..], "{}", .{got});
440404
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 }
405 testing.expectEqualStrings(s_expected, s_got);
456406}
457407
458408/// Detect SDK path on Darwin.