diff --git a/src/link/MachO.zig b/src/link/MachO.zig index d23b696cb8..6fbe3bc150 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -31,6 +31,7 @@ const DebugSymbols = @import("MachO/DebugSymbols.zig"); const Dylib = @import("MachO/Dylib.zig"); const File = link.File; const Object = @import("MachO/Object.zig"); +const LibStub = @import("tapi.zig").LibStub; const Liveness = @import("../Liveness.zig"); const LlvmObject = @import("../codegen/llvm.zig").Object; const LoadCommand = commands.LoadCommand; @@ -65,6 +66,7 @@ objects: std.ArrayListUnmanaged(Object) = .{}, archives: std.ArrayListUnmanaged(Archive) = .{}, dylibs: std.ArrayListUnmanaged(Dylib) = .{}, +dylibs_map: std.StringHashMapUnmanaged(u16) = .{}, referenced_dylibs: std.AutoArrayHashMapUnmanaged(u16, void) = .{}, load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, @@ -994,6 +996,133 @@ fn linkWithZld(self: *MachO, comp: *Compilation) !void { } } +fn parseObject(self: *MachO, path: []const u8) !bool { + const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { + error.FileNotFound => return false, + else => |e| return e, + }; + errdefer file.close(); + + const name = try self.base.allocator.dupe(u8, path); + errdefer self.base.allocator.free(name); + + var object = Object{ + .name = name, + .file = file, + }; + + object.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) { + error.EndOfStream, error.NotObject => { + object.deinit(self.base.allocator); + return false; + }, + else => |e| return e, + }; + + try self.objects.append(self.base.allocator, object); + + return true; +} + +fn parseArchive(self: *MachO, path: []const u8) !bool { + const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { + error.FileNotFound => return false, + else => |e| return e, + }; + errdefer file.close(); + + const name = try self.base.allocator.dupe(u8, path); + errdefer self.base.allocator.free(name); + + var archive = Archive{ + .name = name, + .file = file, + }; + + archive.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) { + error.EndOfStream, error.NotArchive => { + archive.deinit(self.base.allocator); + return false; + }, + else => |e| return e, + }; + + try self.archives.append(self.base.allocator, archive); + + return true; +} + +const ParseDylibError = error{ + OutOfMemory, + EmptyStubFile, + MismatchedCpuArchitecture, + UnsupportedCpuArchitecture, +} || fs.File.OpenError || std.os.PReadError || Dylib.Id.ParseError; + +const DylibCreateOpts = struct { + syslibroot: ?[]const u8 = null, + id: ?Dylib.Id = null, + is_dependent: bool = false, +}; + +pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDylibError!bool { + const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { + error.FileNotFound => return false, + else => |e| return e, + }; + errdefer file.close(); + + const name = try self.base.allocator.dupe(u8, path); + errdefer self.base.allocator.free(name); + + var dylib = Dylib{ + .name = name, + .file = file, + }; + + dylib.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) { + error.EndOfStream, error.NotDylib => { + try file.seekTo(0); + + var lib_stub = LibStub.loadFromFile(self.base.allocator, file) catch { + dylib.deinit(self.base.allocator); + return false; + }; + defer lib_stub.deinit(); + + try dylib.parseFromStub(self.base.allocator, self.base.options.target, lib_stub); + }, + else => |e| return e, + }; + + if (opts.id) |id| { + if (dylib.id.?.current_version < id.compatibility_version) { + log.warn("found dylib is incompatible with the required minimum version", .{}); + log.warn(" dylib: {s}", .{id.name}); + log.warn(" required minimum version: {}", .{id.compatibility_version}); + log.warn(" dylib version: {}", .{dylib.id.?.current_version}); + + // TODO maybe this should be an error and facilitate auto-cleanup? + dylib.deinit(self.base.allocator); + return false; + } + } + + const dylib_id = @intCast(u16, self.dylibs.items.len); + try self.dylibs.append(self.base.allocator, dylib); + try self.dylibs_map.putNoClobber(self.base.allocator, dylib.id.?.name, dylib_id); + + if (!(opts.is_dependent or self.referenced_dylibs.contains(dylib_id))) { + try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {}); + } + + // TODO this should not be performed if the user specifies `-flat_namespace` flag. + // See ld64 manpages. + try dylib.parseDependentLibs(self, opts.syslibroot); + + return true; +} + fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const u8) !void { for (files) |file_name| { const full_path = full_path: { @@ -1003,28 +1132,11 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const }; defer self.base.allocator.free(full_path); - if (try Object.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path)) |object| { - try self.objects.append(self.base.allocator, object); - continue; - } - - if (try Archive.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path)) |archive| { - try self.archives.append(self.base.allocator, archive); - continue; - } - - if (try Dylib.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path, .{ + if (try self.parseObject(full_path)) continue; + if (try self.parseArchive(full_path)) continue; + if (try self.parseDylib(full_path, .{ .syslibroot = syslibroot, - })) |dylibs| { - defer self.base.allocator.free(dylibs); - const dylib_id = @intCast(u16, self.dylibs.items.len); - try self.dylibs.appendSlice(self.base.allocator, dylibs); - // We always have to add the dylib that was on the linker line. - if (!self.referenced_dylibs.contains(dylib_id)) { - try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {}); - } - continue; - } + })) continue; log.warn("unknown filetype for positional input file: '{s}'", .{file_name}); } @@ -1032,23 +1144,10 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8) !void { for (libs) |lib| { - if (try Dylib.createAndParseFromPath(self.base.allocator, self.base.options.target, lib, .{ + if (try self.parseDylib(lib, .{ .syslibroot = syslibroot, - })) |dylibs| { - defer self.base.allocator.free(dylibs); - const dylib_id = @intCast(u16, self.dylibs.items.len); - try self.dylibs.appendSlice(self.base.allocator, dylibs); - // We always have to add the dylib that was on the linker line. - if (!self.referenced_dylibs.contains(dylib_id)) { - try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {}); - } - continue; - } - - if (try Archive.createAndParseFromPath(self.base.allocator, self.base.options.target, lib)) |archive| { - try self.archives.append(self.base.allocator, archive); - continue; - } + })) continue; + if (try self.parseArchive(lib)) continue; log.warn("unknown filetype for a library: '{s}'", .{lib}); } @@ -3360,6 +3459,7 @@ pub fn deinit(self: *MachO) void { dylib.deinit(self.base.allocator); } self.dylibs.deinit(self.base.allocator); + self.dylibs_map.deinit(self.base.allocator); self.referenced_dylibs.deinit(self.base.allocator); for (self.load_commands.items) |*lc| { diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig index 3b01233e1f..1f370088f0 100644 --- a/src/link/MachO/Archive.zig +++ b/src/link/MachO/Archive.zig @@ -103,32 +103,6 @@ pub fn deinit(self: *Archive, allocator: *Allocator) void { allocator.free(self.name); } -pub fn createAndParseFromPath(allocator: *Allocator, target: std.Target, path: []const u8) !?Archive { - const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { - error.FileNotFound => return null, - else => |e| return e, - }; - errdefer file.close(); - - const name = try allocator.dupe(u8, path); - errdefer allocator.free(name); - - var archive = Archive{ - .name = name, - .file = file, - }; - - archive.parse(allocator, target) catch |err| switch (err) { - error.EndOfStream, error.NotArchive => { - archive.deinit(allocator); - return null; - }, - else => |e| return e, - }; - - return archive; -} - pub fn parse(self: *Archive, allocator: *Allocator, target: std.Target) !void { const reader = self.file.reader(); self.library_offset = try fat.getLibraryOffset(reader, target); diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index 64be1fd2fa..05d44559ce 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -73,7 +73,7 @@ pub const Id = struct { allocator.free(id.name); } - const ParseError = fmt.ParseIntError || fmt.BufPrintError; + pub const ParseError = fmt.ParseIntError || fmt.BufPrintError; pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void { id.current_version = try parseVersion(version); @@ -109,7 +109,7 @@ pub const Id = struct { var count: u4 = 0; while (split.next()) |value| { if (count > 2) { - log.warn("malformed version field: {s}", .{string}); + log.debug("malformed version field: {s}", .{string}); return 0x10000; } values[count] = value; @@ -128,78 +128,6 @@ pub const Id = struct { } }; -pub const Error = error{ - OutOfMemory, - EmptyStubFile, - MismatchedCpuArchitecture, - UnsupportedCpuArchitecture, -} || fs.File.OpenError || std.os.PReadError || Id.ParseError; - -pub const CreateOpts = struct { - syslibroot: ?[]const u8 = null, - id: ?Id = null, - target: ?std.Target = null, -}; - -pub fn createAndParseFromPath( - allocator: *Allocator, - target: std.Target, - path: []const u8, - opts: CreateOpts, -) Error!?[]Dylib { - const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { - error.FileNotFound => return null, - else => |e| return e, - }; - errdefer file.close(); - - const name = try allocator.dupe(u8, path); - errdefer allocator.free(name); - - var dylib = Dylib{ - .name = name, - .file = file, - }; - - dylib.parse(allocator, target) catch |err| switch (err) { - error.EndOfStream, error.NotDylib => { - try file.seekTo(0); - - var lib_stub = LibStub.loadFromFile(allocator, file) catch { - dylib.deinit(allocator); - return null; - }; - defer lib_stub.deinit(); - - try dylib.parseFromStub(allocator, target, lib_stub); - }, - else => |e| return e, - }; - - if (opts.id) |id| { - if (dylib.id.?.current_version < id.compatibility_version) { - log.warn("found dylib is incompatible with the required minimum version", .{}); - log.warn(" | dylib: {s}", .{id.name}); - log.warn(" | required minimum version: {}", .{id.compatibility_version}); - log.warn(" | dylib version: {}", .{dylib.id.?.current_version}); - - // TODO maybe this should be an error and facilitate auto-cleanup? - dylib.deinit(allocator); - return null; - } - } - - var dylibs = std.ArrayList(Dylib).init(allocator); - defer dylibs.deinit(); - - try dylibs.append(dylib); - // TODO this should not be performed if the user specifies `-flat_namespace` flag. - // See ld64 manpages. - try dylib.parseDependentLibs(allocator, target, &dylibs, opts.syslibroot); - - return dylibs.toOwnedSlice(); -} - pub fn deinit(self: *Dylib, allocator: *Allocator) void { for (self.load_commands.items) |*lc| { lc.deinit(allocator); @@ -315,14 +243,7 @@ fn parseSymbols(self: *Dylib, allocator: *Allocator) !void { } } -fn hasTarget(targets: []const []const u8, target: []const u8) bool { - for (targets) |t| { - if (mem.eql(u8, t, target)) return true; - } - return false; -} - -fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void { +fn addObjCClassSymbol(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void { const expanded = &[_][]const u8{ try std.fmt.allocPrint(allocator, "_OBJC_CLASS_$_{s}", .{sym_name}), try std.fmt.allocPrint(allocator, "_OBJC_METACLASS_$_{s}", .{sym_name}), @@ -334,30 +255,21 @@ fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8 } } -fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 { - const arch = switch (target.cpu.arch) { - .aarch64 => "arm64", - .x86_64 => "x86_64", - else => unreachable, - }; - const os = @tagName(target.os.tag); - const abi: ?[]const u8 = switch (target.abi) { - .gnu => null, - .simulator => "simulator", - else => unreachable, - }; - if (abi) |x| { - return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ arch, os, x }); - } - return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, os }); +fn addSymbol(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void { + if (self.symbols.contains(sym_name)) return; + try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {}); } const TargetMatcher = struct { allocator: *Allocator, + target: std.Target, target_strings: std.ArrayListUnmanaged([]const u8) = .{}, fn init(allocator: *Allocator, target: std.Target) !TargetMatcher { - var self = TargetMatcher{ .allocator = allocator }; + var self = TargetMatcher{ + .allocator = allocator, + .target = target, + }; try self.target_strings.append(allocator, try targetToAppleString(allocator, target)); if (target.abi == .simulator) { @@ -380,12 +292,41 @@ const TargetMatcher = struct { self.target_strings.deinit(self.allocator); } - fn matches(self: TargetMatcher, targets: []const []const u8) bool { - for (self.target_strings.items) |t| { - if (hasTarget(targets, t)) return true; + fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 { + const arch = switch (target.cpu.arch) { + .aarch64 => "arm64", + .x86_64 => "x86_64", + else => unreachable, + }; + const os = @tagName(target.os.tag); + const abi: ?[]const u8 = switch (target.abi) { + .gnu => null, + .simulator => "simulator", + else => unreachable, + }; + if (abi) |x| { + return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ arch, os, x }); + } + return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, os }); + } + + fn hasValue(stack: []const []const u8, needle: []const u8) bool { + for (stack) |v| { + if (mem.eql(u8, v, needle)) return true; } return false; } + + fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool { + for (self.target_strings.items) |t| { + if (hasValue(targets, t)) return true; + } + return false; + } + + fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool { + return hasValue(archs, @tagName(self.target.cpu.arch)); + } }; pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void { @@ -395,93 +336,130 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li const umbrella_lib = lib_stub.inner[0]; - var id = try Id.default(allocator, umbrella_lib.install_name); - if (umbrella_lib.current_version) |version| { + var id = try Id.default(allocator, umbrella_lib.installName()); + if (umbrella_lib.currentVersion()) |version| { try id.parseCurrentVersion(version); } - if (umbrella_lib.compatibility_version) |version| { + if (umbrella_lib.compatibilityVersion()) |version| { try id.parseCompatibilityVersion(version); } self.id = id; - var matcher = try TargetMatcher.init(allocator, target); - defer matcher.deinit(); - var umbrella_libs = std.StringHashMap(void).init(allocator); defer umbrella_libs.deinit(); - for (lib_stub.inner) |stub, stub_index| { - if (!matcher.matches(stub.targets)) continue; + log.debug(" (install_name '{s}')", .{umbrella_lib.installName()}); + + var matcher = try TargetMatcher.init(allocator, target); + defer matcher.deinit(); + + for (lib_stub.inner) |elem, stub_index| { + const is_match = switch (elem) { + .v3 => |stub| matcher.matchesArch(stub.archs), + .v4 => |stub| matcher.matchesTarget(stub.targets), + }; + if (!is_match) continue; if (stub_index > 0) { // TODO I thought that we could switch on presence of `parent-umbrella` map; // however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib` // BUT does not feature a `parent-umbrella` map as the only sublib. Apple's bug perhaps? - try umbrella_libs.put(stub.install_name, .{}); + try umbrella_libs.put(elem.installName(), .{}); } - if (stub.exports) |exports| { - for (exports) |exp| { - if (!matcher.matches(exp.targets)) continue; + switch (elem) { + .v3 => |stub| { + if (stub.exports) |exports| { + for (exports) |exp| { + if (!matcher.matchesArch(exp.archs)) continue; - if (exp.symbols) |symbols| { - for (symbols) |sym_name| { - if (self.symbols.contains(sym_name)) continue; - try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {}); + if (exp.symbols) |symbols| { + for (symbols) |sym_name| { + try self.addSymbol(allocator, sym_name); + } + } + + if (exp.objc_classes) |objc_classes| { + for (objc_classes) |class_name| { + try self.addObjCClassSymbol(allocator, class_name); + } + } + + // TODO track which libs were already parsed in different steps + if (exp.re_exports) |re_exports| { + for (re_exports) |lib| { + if (umbrella_libs.contains(lib)) continue; + + log.debug(" (found re-export '{s}')", .{lib}); + + const dep_id = try Id.default(allocator, lib); + try self.dependent_libs.append(allocator, dep_id); + } + } + } + } + }, + .v4 => |stub| { + if (stub.exports) |exports| { + for (exports) |exp| { + if (!matcher.matchesTarget(exp.targets)) continue; + + if (exp.symbols) |symbols| { + for (symbols) |sym_name| { + try self.addSymbol(allocator, sym_name); + } + } + + if (exp.objc_classes) |classes| { + for (classes) |sym_name| { + try self.addObjCClassSymbol(allocator, sym_name); + } + } } } - if (exp.objc_classes) |classes| { + if (stub.reexports) |reexports| { + for (reexports) |reexp| { + if (!matcher.matchesTarget(reexp.targets)) continue; + + if (reexp.symbols) |symbols| { + for (symbols) |sym_name| { + try self.addSymbol(allocator, sym_name); + } + } + + if (reexp.objc_classes) |classes| { + for (classes) |sym_name| { + try self.addObjCClassSymbol(allocator, sym_name); + } + } + } + } + + if (stub.objc_classes) |classes| { for (classes) |sym_name| { - try self.addObjCClassSymbols(allocator, sym_name); + try self.addObjCClassSymbol(allocator, sym_name); } } - } - } - - if (stub.reexports) |reexports| { - for (reexports) |reexp| { - if (!matcher.matches(reexp.targets)) continue; - - if (reexp.symbols) |symbols| { - for (symbols) |sym_name| { - if (self.symbols.contains(sym_name)) continue; - try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {}); - } - } - - if (reexp.objc_classes) |classes| { - for (classes) |sym_name| { - try self.addObjCClassSymbols(allocator, sym_name); - } - } - } - } - - if (stub.objc_classes) |classes| { - for (classes) |sym_name| { - try self.addObjCClassSymbols(allocator, sym_name); - } + }, } } - log.debug("{s}", .{umbrella_lib.install_name}); - - // TODO track which libs were already parsed in different steps - for (lib_stub.inner) |stub| { - if (!matcher.matches(stub.targets)) continue; + // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include + // re-exports directly in the stub file. + for (lib_stub.inner) |elem| { + if (elem == .v3) break; + const stub = elem.v4; + // TODO track which libs were already parsed in different steps if (stub.reexported_libraries) |reexports| { for (reexports) |reexp| { - if (!matcher.matches(reexp.targets)) continue; + if (!matcher.matchesTarget(reexp.targets)) continue; for (reexp.libraries) |lib| { - if (umbrella_libs.contains(lib)) { - log.debug(" | {s} <= {s}", .{ lib, umbrella_lib.install_name }); - continue; - } + if (umbrella_libs.contains(lib)) continue; - log.debug(" | {s}", .{lib}); + log.debug(" (found re-export '{s}')", .{lib}); const dep_id = try Id.default(allocator, lib); try self.dependent_libs.append(allocator, dep_id); @@ -493,12 +471,12 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li pub fn parseDependentLibs( self: *Dylib, - allocator: *Allocator, - target: std.Target, - out: *std.ArrayList(Dylib), + macho_file: *MachO, syslibroot: ?[]const u8, ) !void { outer: for (self.dependent_libs.items) |id| { + if (macho_file.dylibs_map.contains(id.name)) continue :outer; + const has_ext = blk: { const basename = fs.path.basename(id.name); break :blk mem.lastIndexOfScalar(u8, basename, '.') != null; @@ -510,38 +488,28 @@ pub fn parseDependentLibs( } else id.name; for (&[_][]const u8{ extension, ".tbd" }) |ext| { - const with_ext = try std.fmt.allocPrint(allocator, "{s}{s}", .{ + const with_ext = try std.fmt.allocPrint(macho_file.base.allocator, "{s}{s}", .{ without_ext, ext, }); - defer allocator.free(with_ext); + defer macho_file.base.allocator.free(with_ext); const full_path = if (syslibroot) |root| - try fs.path.join(allocator, &.{ root, with_ext }) + try fs.path.join(macho_file.base.allocator, &.{ root, with_ext }) else with_ext; - defer if (syslibroot) |_| allocator.free(full_path); + defer if (syslibroot) |_| macho_file.base.allocator.free(full_path); log.debug("trying dependency at fully resolved path {s}", .{full_path}); - const dylibs = (try createAndParseFromPath( - allocator, - target, - full_path, - .{ - .id = id, - .syslibroot = syslibroot, - }, - )) orelse { - continue; - }; - defer allocator.free(dylibs); - - try out.appendSlice(dylibs); - - continue :outer; + const did_parse_successfully = try macho_file.parseDylib(full_path, .{ + .id = id, + .syslibroot = syslibroot, + .is_dependent = true, + }); + if (!did_parse_successfully) continue; } else { - log.warn("unable to resolve dependency {s}", .{id.name}); + log.debug("unable to resolve dependency {s}", .{id.name}); } } } diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index c6aa2fb631..06c76b259d 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -153,32 +153,6 @@ pub fn deinit(self: *Object, allocator: *Allocator) void { } } -pub fn createAndParseFromPath(allocator: *Allocator, target: std.Target, path: []const u8) !?Object { - const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { - error.FileNotFound => return null, - else => |e| return e, - }; - errdefer file.close(); - - const name = try allocator.dupe(u8, path); - errdefer allocator.free(name); - - var object = Object{ - .name = name, - .file = file, - }; - - object.parse(allocator, target) catch |err| switch (err) { - error.EndOfStream, error.NotObject => { - object.deinit(allocator); - return null; - }, - else => |e| return e, - }; - - return object; -} - pub fn parse(self: *Object, allocator: *Allocator, target: std.Target) !void { const reader = self.file.reader(); if (self.file_offset) |offset| { diff --git a/src/link/tapi.zig b/src/link/tapi.zig index 35193b0eec..d10942ea02 100644 --- a/src/link/tapi.zig +++ b/src/link/tapi.zig @@ -6,6 +6,89 @@ const log = std.log.scoped(.tapi); const Allocator = mem.Allocator; const Yaml = @import("tapi/yaml.zig").Yaml; +const VersionField = union(enum) { + string: []const u8, + float: f64, + int: u64, +}; + +pub const TbdV3 = struct { + archs: []const []const u8, + uuids: []const []const u8, + platform: []const u8, + install_name: []const u8, + current_version: ?VersionField, + compatibility_version: ?VersionField, + objc_constraint: ?[]const u8, + exports: ?[]const struct { + archs: []const []const u8, + re_exports: ?[]const []const u8, + symbols: ?[]const []const u8, + objc_classes: ?[]const []const u8, + }, +}; + +pub const TbdV4 = struct { + tbd_version: u3, + targets: []const []const u8, + uuids: []const struct { + target: []const u8, + value: []const u8, + }, + install_name: []const u8, + current_version: ?VersionField, + compatibility_version: ?VersionField, + reexported_libraries: ?[]const struct { + targets: []const []const u8, + libraries: []const []const u8, + }, + parent_umbrella: ?[]const struct { + targets: []const []const u8, + umbrella: []const u8, + }, + exports: ?[]const struct { + targets: []const []const u8, + symbols: ?[]const []const u8, + objc_classes: ?[]const []const u8, + }, + reexports: ?[]const struct { + targets: []const []const u8, + symbols: ?[]const []const u8, + objc_classes: ?[]const []const u8, + }, + allowable_clients: ?[]const struct { + targets: []const []const u8, + clients: []const []const u8, + }, + objc_classes: ?[]const []const u8, +}; + +pub const Tbd = union(enum) { + v3: TbdV3, + v4: TbdV4, + + pub fn currentVersion(self: Tbd) ?VersionField { + return switch (self) { + .v3 => |v3| v3.current_version, + .v4 => |v4| v4.current_version, + }; + } + + pub fn compatibilityVersion(self: Tbd) ?VersionField { + return switch (self) { + .v3 => |v3| v3.compatibility_version, + .v4 => |v4| v4.compatibility_version, + }; + } + + pub fn installName(self: Tbd) []const u8 { + return switch (self) { + .v3 => |v3| v3.install_name, + .v4 => |v4| v4.install_name, + }; + } +}; + pub const LibStub = struct { /// Underlying memory for stub's contents. yaml: Yaml, @@ -13,49 +96,6 @@ pub const LibStub = struct { /// Typed contents of the tbd file. inner: []Tbd, - const Tbd = struct { - tbd_version: u3, - targets: []const []const u8, - uuids: []const struct { - target: []const u8, - value: []const u8, - }, - install_name: []const u8, - current_version: ?union(enum) { - string: []const u8, - float: f64, - int: u64, - }, - compatibility_version: ?union(enum) { - string: []const u8, - float: f64, - int: u64, - }, - reexported_libraries: ?[]const struct { - targets: []const []const u8, - libraries: []const []const u8, - }, - parent_umbrella: ?[]const struct { - targets: []const []const u8, - umbrella: []const u8, - }, - exports: ?[]const struct { - targets: []const []const u8, - symbols: ?[]const []const u8, - objc_classes: ?[]const []const u8, - }, - reexports: ?[]const struct { - targets: []const []const u8, - symbols: ?[]const []const u8, - objc_classes: ?[]const []const u8, - }, - allowable_clients: ?[]const struct { - targets: []const []const u8, - clients: []const []const u8, - }, - objc_classes: ?[]const []const u8, - }; - pub fn loadFromFile(allocator: *Allocator, file: fs.File) !LibStub { const source = try file.readToEndAlloc(allocator, std.math.maxInt(u32)); defer allocator.free(source); @@ -65,16 +105,35 @@ pub const LibStub = struct { .inner = undefined, }; - lib_stub.inner = lib_stub.yaml.parse([]Tbd) catch |err| blk: { - switch (err) { - error.TypeMismatch => { - // TODO clean this up. - var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1); - out[0] = try lib_stub.yaml.parse(Tbd); - break :blk out; - }, - else => |e| return e, + // TODO revisit this logic in the hope of simplifying it. + lib_stub.inner = blk: { + err: { + log.debug("trying to parse as []TbdV4", .{}); + const inner = lib_stub.yaml.parse([]TbdV4) catch break :err; + var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, inner.len); + for (inner) |doc, i| { + out[i] = .{ .v4 = doc }; + } + break :blk out; } + + err: { + log.debug("trying to parse as TbdV4", .{}); + const inner = lib_stub.yaml.parse(TbdV4) catch break :err; + var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1); + out[0] = .{ .v4 = inner }; + break :blk out; + } + + err: { + log.debug("trying to parse as TbdV3", .{}); + const inner = lib_stub.yaml.parse(TbdV3) catch break :err; + var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1); + out[0] = .{ .v3 = inner }; + break :blk out; + } + + return error.NotLibStub; }; return lib_stub; diff --git a/src/link/tapi/yaml.zig b/src/link/tapi/yaml.zig index b58df7609f..25d2c73e82 100644 --- a/src/link/tapi/yaml.zig +++ b/src/link/tapi/yaml.zig @@ -371,7 +371,7 @@ pub const Yaml = struct { } const unwrapped = value orelse { - log.err("missing struct field: {s}: {s}", .{ field.name, @typeName(field.field_type) }); + log.debug("missing struct field: {s}: {s}", .{ field.name, @typeName(field.field_type) }); return error.StructFieldMissing; }; @field(parsed, field.name) = try self.parseValue(field.field_type, unwrapped);