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};