Merge pull request #9549 from ziglang/tapi-v3
macho: handle TAPI v3 and simplify handling of dependent dynamic libraries
This commit is contained in:
@@ -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| {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user