master
1const std = @import("std");
2const fs = std.fs;
3const mem = std.mem;
4const log = std.log.scoped(.tapi);
5const yaml = @import("tapi/yaml.zig");
6
7const Allocator = mem.Allocator;
8const Yaml = yaml.Yaml;
9
10const VersionField = union(enum) {
11 string: []const u8,
12 float: f64,
13 int: u64,
14};
15
16pub const TbdV3 = struct {
17 archs: []const []const u8,
18 uuids: []const []const u8,
19 platform: []const u8,
20 install_name: []const u8,
21 current_version: ?VersionField,
22 compatibility_version: ?VersionField,
23 objc_constraint: ?[]const u8,
24 parent_umbrella: ?[]const u8,
25 exports: ?[]const struct {
26 archs: []const []const u8,
27 allowable_clients: ?[]const []const u8,
28 re_exports: ?[]const []const u8,
29 symbols: ?[]const []const u8,
30 weak_symbols: ?[]const []const u8,
31 objc_classes: ?[]const []const u8,
32 objc_ivars: ?[]const []const u8,
33 objc_eh_types: ?[]const []const u8,
34 },
35};
36
37pub const TbdV4 = struct {
38 tbd_version: u3,
39 targets: []const []const u8,
40 uuids: ?[]const struct {
41 target: []const u8,
42 value: []const u8,
43 },
44 install_name: []const u8,
45 current_version: ?VersionField,
46 compatibility_version: ?VersionField,
47 reexported_libraries: ?[]const struct {
48 targets: []const []const u8,
49 libraries: []const []const u8,
50 },
51 parent_umbrella: ?[]const struct {
52 targets: []const []const u8,
53 umbrella: []const u8,
54 },
55 exports: ?[]const struct {
56 targets: []const []const u8,
57 symbols: ?[]const []const u8,
58 weak_symbols: ?[]const []const u8,
59 objc_classes: ?[]const []const u8,
60 objc_ivars: ?[]const []const u8,
61 objc_eh_types: ?[]const []const u8,
62 },
63 reexports: ?[]const struct {
64 targets: []const []const u8,
65 symbols: ?[]const []const u8,
66 weak_symbols: ?[]const []const u8,
67 objc_classes: ?[]const []const u8,
68 objc_ivars: ?[]const []const u8,
69 objc_eh_types: ?[]const []const u8,
70 },
71 allowable_clients: ?[]const struct {
72 targets: []const []const u8,
73 clients: []const []const u8,
74 },
75 objc_classes: ?[]const []const u8,
76 objc_ivars: ?[]const []const u8,
77 objc_eh_types: ?[]const []const u8,
78};
79
80pub const Tbd = union(enum) {
81 v3: TbdV3,
82 v4: TbdV4,
83
84 /// Caller owns memory.
85 pub fn targets(self: Tbd, gpa: Allocator) error{OutOfMemory}![]const []const u8 {
86 var out = std.array_list.Managed([]const u8).init(gpa);
87 defer out.deinit();
88
89 switch (self) {
90 .v3 => |v3| {
91 try out.ensureTotalCapacityPrecise(v3.archs.len);
92 for (v3.archs) |arch| {
93 const target = try std.fmt.allocPrint(gpa, "{s}-{s}", .{ arch, v3.platform });
94 out.appendAssumeCapacity(target);
95 }
96 },
97 .v4 => |v4| {
98 try out.ensureTotalCapacityPrecise(v4.targets.len);
99 for (v4.targets) |t| {
100 out.appendAssumeCapacity(try gpa.dupe(u8, t));
101 }
102 },
103 }
104
105 return out.toOwnedSlice();
106 }
107
108 pub fn currentVersion(self: Tbd) ?VersionField {
109 return switch (self) {
110 .v3 => |v3| v3.current_version,
111 .v4 => |v4| v4.current_version,
112 };
113 }
114
115 pub fn compatibilityVersion(self: Tbd) ?VersionField {
116 return switch (self) {
117 .v3 => |v3| v3.compatibility_version,
118 .v4 => |v4| v4.compatibility_version,
119 };
120 }
121
122 pub fn installName(self: Tbd) []const u8 {
123 return switch (self) {
124 .v3 => |v3| v3.install_name,
125 .v4 => |v4| v4.install_name,
126 };
127 }
128};
129
130pub const TapiError = error{
131 NotLibStub,
132 InputOutput,
133} || yaml.YamlError || std.fs.File.PReadError;
134
135pub const LibStub = struct {
136 /// Underlying memory for stub's contents.
137 yaml: Yaml,
138
139 /// Typed contents of the tbd file.
140 inner: []Tbd,
141
142 pub fn loadFromFile(allocator: Allocator, file: fs.File) TapiError!LibStub {
143 const filesize = blk: {
144 const stat = file.stat() catch break :blk std.math.maxInt(u32);
145 break :blk @min(stat.size, std.math.maxInt(u32));
146 };
147 const source = try allocator.alloc(u8, filesize);
148 defer allocator.free(source);
149 const amt = try file.preadAll(source, 0);
150 if (amt != filesize) return error.InputOutput;
151
152 var lib_stub = LibStub{
153 .yaml = try Yaml.load(allocator, source),
154 .inner = undefined,
155 };
156
157 // TODO revisit this logic in the hope of simplifying it.
158 lib_stub.inner = blk: {
159 err: {
160 log.debug("trying to parse as []TbdV4", .{});
161 const inner = lib_stub.yaml.parse([]TbdV4) catch break :err;
162 var out = try lib_stub.yaml.arena.allocator().alloc(Tbd, inner.len);
163 for (inner, 0..) |doc, i| {
164 out[i] = .{ .v4 = doc };
165 }
166 break :blk out;
167 }
168
169 err: {
170 log.debug("trying to parse as TbdV4", .{});
171 const inner = lib_stub.yaml.parse(TbdV4) catch break :err;
172 var out = try lib_stub.yaml.arena.allocator().alloc(Tbd, 1);
173 out[0] = .{ .v4 = inner };
174 break :blk out;
175 }
176
177 err: {
178 log.debug("trying to parse as []TbdV3", .{});
179 const inner = lib_stub.yaml.parse([]TbdV3) catch break :err;
180 var out = try lib_stub.yaml.arena.allocator().alloc(Tbd, inner.len);
181 for (inner, 0..) |doc, i| {
182 out[i] = .{ .v3 = doc };
183 }
184 break :blk out;
185 }
186
187 err: {
188 log.debug("trying to parse as TbdV3", .{});
189 const inner = lib_stub.yaml.parse(TbdV3) catch break :err;
190 var out = try lib_stub.yaml.arena.allocator().alloc(Tbd, 1);
191 out[0] = .{ .v3 = inner };
192 break :blk out;
193 }
194
195 return error.NotLibStub;
196 };
197
198 return lib_stub;
199 }
200
201 pub fn deinit(self: *LibStub) void {
202 self.yaml.deinit();
203 }
204};