struct macos [src]

Alias for std.zig.system.darwin.macos

Members

Source

const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; const mem = std.mem; const testing = std.testing; const Target = std.Target; /// Detect macOS version. /// `target_os` is not modified in case of error. pub fn detect(target_os: *Target.Os) !void { // Drop use of osproductversion sysctl because: // 1. only available 10.13.4 High Sierra and later // 2. when used from a binary built against < SDK 11.0 it returns 10.16 and masks Big Sur 11.x version // // NEW APPROACH, STEP 1, parse file: // // /System/Library/CoreServices/SystemVersion.plist // // NOTE: Historically `SystemVersion.plist` first appeared circa '2003 // with the release of Mac OS X 10.3.0 Panther. // // and if it contains a `10.16` value where the `16` is `>= 16` then it is non-canonical, // discarded, and we move on to next step. Otherwise we accept the version. // // BACKGROUND: `10.(16+)` is not a proper version and does not have enough fidelity to // indicate minor/point version of Big Sur and later. It is a context-sensitive result // issued by the kernel for backwards compatibility purposes. Likely the kernel checks // if the executable was linked against an SDK older than Big Sur. // // STEP 2, parse next file: // // /System/Library/CoreServices/.SystemVersionPlatform.plist // // NOTE: Historically `SystemVersionPlatform.plist` first appeared circa '2020 // with the release of macOS 11.0 Big Sur. // // Accessing the content via this path circumvents a context-sensitive result and // yields a canonical Big Sur version. // // At this time there is no other known way for a < SDK 11.0 executable to obtain a // canonical Big Sur version. // // This implementation uses a reasonably simplified approach to parse .plist file // that while it is an xml document, we have good history on the file and its format // such that I am comfortable with implementing a minimalistic parser. // Things like string and general escapes are not supported. const prefixSlash = "/System/Library/CoreServices/"; const paths = [_][]const u8{ prefixSlash ++ "SystemVersion.plist", prefixSlash ++ ".SystemVersionPlatform.plist", }; for (paths) |path| { // approx. 4 times historical file size var buf: [2048]u8 = undefined; if (std.fs.cwd().readFile(path, &buf)) |bytes| { if (parseSystemVersion(bytes)) |ver| { // never return non-canonical `10.(16+)` if (!(ver.major == 10 and ver.minor >= 16)) { target_os.version_range.semver.min = ver; target_os.version_range.semver.max = ver; return; } continue; } else |_| { return error.OSVersionDetectionFail; } } else |_| { return error.OSVersionDetectionFail; } } return error.OSVersionDetectionFail; } fn parseSystemVersion(buf: []const u8) !std.SemanticVersion { var svt = SystemVersionTokenizer{ .bytes = buf }; try svt.skipUntilTag(.start, "dict"); while (true) { try svt.skipUntilTag(.start, "key"); const content = try svt.expectContent(); try svt.skipUntilTag(.end, "key"); if (mem.eql(u8, content, "ProductVersion")) break; } try svt.skipUntilTag(.start, "string"); const ver = try svt.expectContent(); try svt.skipUntilTag(.end, "string"); return try std.Target.Query.parseVersion(ver); } const SystemVersionTokenizer = struct { bytes: []const u8, index: usize = 0, state: State = .begin, fn next(self: *@This()) !?Token { var mark: usize = self.index; var tag = Tag{}; var content: []const u8 = ""; while (self.index < self.bytes.len) { const char = self.bytes[self.index]; switch (self.state) { .begin => switch (char) { '<' => { self.state = .tag0; self.index += 1; tag = Tag{}; mark = self.index; }, '>' => { return error.BadToken; }, else => { self.state = .content; content = ""; mark = self.index; }, }, .tag0 => switch (char) { '<' => { return error.BadToken; }, '>' => { self.state = .begin; self.index += 1; tag.name = self.bytes[mark..self.index]; return Token{ .tag = tag }; }, '"' => { self.state = .tag_string; self.index += 1; }, '/' => { self.state = .tag0_end_or_empty; self.index += 1; }, 'A'...'Z', 'a'...'z' => { self.state = .tagN; tag.kind = .start; self.index += 1; }, else => { self.state = .tagN; self.index += 1; }, }, .tag0_end_or_empty => switch (char) { '<' => { return error.BadToken; }, '>' => { self.state = .begin; tag.kind = .empty; tag.name = self.bytes[self.index..self.index]; self.index += 1; return Token{ .tag = tag }; }, else => { self.state = .tagN; tag.kind = .end; mark = self.index; self.index += 1; }, }, .tagN => switch (char) { '<' => { return error.BadToken; }, '>' => { self.state = .begin; tag.name = self.bytes[mark..self.index]; self.index += 1; return Token{ .tag = tag }; }, '"' => { self.state = .tag_string; self.index += 1; }, '/' => { self.state = .tagN_end; tag.kind = .end; self.index += 1; }, else => { self.index += 1; }, }, .tagN_end => switch (char) { '>' => { self.state = .begin; tag.name = self.bytes[mark..self.index]; self.index += 1; return Token{ .tag = tag }; }, else => { return error.BadToken; }, }, .tag_string => switch (char) { '"' => { self.state = .tagN; self.index += 1; }, else => { self.index += 1; }, }, .content => switch (char) { '<' => { self.state = .tag0; content = self.bytes[mark..self.index]; self.index += 1; tag = Tag{}; mark = self.index; return Token{ .content = content }; }, '>' => { return error.BadToken; }, else => { self.index += 1; }, }, } } return null; } fn expectContent(self: *@This()) ![]const u8 { if (try self.next()) |tok| { switch (tok) { .content => |content| { return content; }, else => {}, } } return error.UnexpectedToken; } fn skipUntilTag(self: *@This(), kind: Tag.Kind, name: []const u8) !void { while (try self.next()) |tok| { switch (tok) { .tag => |tag| { if (tag.kind == kind and mem.eql(u8, tag.name, name)) return; }, else => {}, } } return error.TagNotFound; } const State = enum { begin, tag0, tag0_end_or_empty, tagN, tagN_end, tag_string, content, }; const Token = union(enum) { tag: Tag, content: []const u8, }; const Tag = struct { kind: Kind = .unknown, name: []const u8 = "", const Kind = enum { unknown, start, end, empty }; }; }; test "detect" { const cases: [5]struct { []const u8, std.SemanticVersion } = .{ .{ \\ \\ \\ \\ \\ ProductBuildVersion \\ 7B85 \\ ProductCopyright \\ Apple Computer, Inc. 1983-2003 \\ ProductName \\ Mac OS X \\ ProductUserVisibleVersion \\ 10.3 \\ ProductVersion \\ 10.3 \\ \\ , .{ .major = 10, .minor = 3, .patch = 0 }, }, .{ \\ \\ \\ \\ \\ ProductBuildVersion \\ 7W98 \\ ProductCopyright \\ Apple Computer, Inc. 1983-2004 \\ ProductName \\ Mac OS X \\ ProductUserVisibleVersion \\ 10.3.9 \\ ProductVersion \\ 10.3.9 \\ \\ , .{ .major = 10, .minor = 3, .patch = 9 }, }, .{ \\ \\ \\ \\ \\ ProductBuildVersion \\ 19G68 \\ ProductCopyright \\ 1983-2020 Apple Inc. \\ ProductName \\ Mac OS X \\ ProductUserVisibleVersion \\ 10.15.6 \\ ProductVersion \\ 10.15.6 \\ iOSSupportVersion \\ 13.6 \\ \\ , .{ .major = 10, .minor = 15, .patch = 6 }, }, .{ \\ \\ \\ \\ \\ ProductBuildVersion \\ 20A2408 \\ ProductCopyright \\ 1983-2020 Apple Inc. \\ ProductName \\ macOS \\ ProductUserVisibleVersion \\ 11.0 \\ ProductVersion \\ 11.0 \\ iOSSupportVersion \\ 14.2 \\ \\ , .{ .major = 11, .minor = 0, .patch = 0 }, }, .{ \\ \\ \\ \\ \\ ProductBuildVersion \\ 20C63 \\ ProductCopyright \\ 1983-2020 Apple Inc. \\ ProductName \\ macOS \\ ProductUserVisibleVersion \\ 11.1 \\ ProductVersion \\ 11.1 \\ iOSSupportVersion \\ 14.3 \\ \\ , .{ .major = 11, .minor = 1, .patch = 0 }, }, }; inline for (cases) |case| { const ver0 = try parseSystemVersion(case[0]); const ver1 = case[1]; try testing.expectEqual(std.math.Order.eq, ver0.order(ver1)); } } pub fn detectNativeCpuAndFeatures() ?Target.Cpu { var cpu_family: std.c.CPUFAMILY = undefined; var len: usize = @sizeOf(std.c.CPUFAMILY); std.posix.sysctlbynameZ("hw.cpufamily", &cpu_family, &len, null, 0) catch |err| switch (err) { error.NameTooLong => unreachable, // constant, known good value error.PermissionDenied => unreachable, // only when setting values, error.SystemResources => unreachable, // memory already on the stack error.UnknownName => unreachable, // constant, known good value error.Unexpected => unreachable, // EFAULT: stack should be safe, EISDIR/ENOTDIR: constant, known good value }; const current_arch = builtin.cpu.arch; switch (current_arch) { .aarch64, .aarch64_be => { const model = switch (cpu_family) { .ARM_EVEREST_SAWTOOTH => &Target.aarch64.cpu.apple_a16, .ARM_BLIZZARD_AVALANCHE => &Target.aarch64.cpu.apple_a15, .ARM_FIRESTORM_ICESTORM => &Target.aarch64.cpu.apple_a14, .ARM_LIGHTNING_THUNDER => &Target.aarch64.cpu.apple_a13, .ARM_VORTEX_TEMPEST => &Target.aarch64.cpu.apple_a12, .ARM_MONSOON_MISTRAL => &Target.aarch64.cpu.apple_a11, .ARM_HURRICANE => &Target.aarch64.cpu.apple_a10, .ARM_TWISTER => &Target.aarch64.cpu.apple_a9, .ARM_TYPHOON => &Target.aarch64.cpu.apple_a8, .ARM_CYCLONE => &Target.aarch64.cpu.cyclone, .ARM_COLL => &Target.aarch64.cpu.apple_a17, .ARM_IBIZA => &Target.aarch64.cpu.apple_m3, // base .ARM_LOBOS => &Target.aarch64.cpu.apple_m3, // pro .ARM_PALMA => &Target.aarch64.cpu.apple_m3, // max .ARM_DONAN => &Target.aarch64.cpu.apple_m4, // base .ARM_BRAVA => &Target.aarch64.cpu.apple_m4, // pro/max else => return null, }; return Target.Cpu{ .arch = current_arch, .model = model, .features = model.features, }; }, else => {}, } return null; }