authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-08-11 22:48:03+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-08-11 22:48:03+02:00
log0686954802d17e87114542878615fd0a9a245e49
tree0b00c0add254a980d2b7ed6676769ac0f20ef71a
parent60a5552d414ffedf84117df57963fd5bf099c2ea
parent5d548cc65125cc33ebf4840fb73a97030b1d0505
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #9549 from ziglang/tapi-v3

macho: handle TAPI v3 and simplify handling of dependent dynamic libraries

6 files changed, 393 insertions(+), 318 deletions(-)

src/link/MachO.zig+137-37
......@@ -31,6 +31,7 @@ const DebugSymbols = @import("MachO/DebugSymbols.zig");
3131const Dylib = @import("MachO/Dylib.zig");
3232const File = link.File;
3333const Object = @import("MachO/Object.zig");
34const LibStub = @import("tapi.zig").LibStub;
3435const Liveness = @import("../Liveness.zig");
3536const LlvmObject = @import("../codegen/llvm.zig").Object;
3637const LoadCommand = commands.LoadCommand;
......@@ -65,6 +66,7 @@ objects: std.ArrayListUnmanaged(Object) = .{},
6566archives: std.ArrayListUnmanaged(Archive) = .{},
6667
6768dylibs: std.ArrayListUnmanaged(Dylib) = .{},
69dylibs_map: std.StringHashMapUnmanaged(u16) = .{},
6870referenced_dylibs: std.AutoArrayHashMapUnmanaged(u16, void) = .{},
6971
7072load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
......@@ -994,6 +996,133 @@ fn linkWithZld(self: *MachO, comp: *Compilation) !void {
994996 }
995997}
996998
999fn parseObject(self: *MachO, path: []const u8) !bool {
1000 const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
1001 error.FileNotFound => return false,
1002 else => |e| return e,
1003 };
1004 errdefer file.close();
1005
1006 const name = try self.base.allocator.dupe(u8, path);
1007 errdefer self.base.allocator.free(name);
1008
1009 var object = Object{
1010 .name = name,
1011 .file = file,
1012 };
1013
1014 object.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) {
1015 error.EndOfStream, error.NotObject => {
1016 object.deinit(self.base.allocator);
1017 return false;
1018 },
1019 else => |e| return e,
1020 };
1021
1022 try self.objects.append(self.base.allocator, object);
1023
1024 return true;
1025}
1026
1027fn parseArchive(self: *MachO, path: []const u8) !bool {
1028 const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
1029 error.FileNotFound => return false,
1030 else => |e| return e,
1031 };
1032 errdefer file.close();
1033
1034 const name = try self.base.allocator.dupe(u8, path);
1035 errdefer self.base.allocator.free(name);
1036
1037 var archive = Archive{
1038 .name = name,
1039 .file = file,
1040 };
1041
1042 archive.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) {
1043 error.EndOfStream, error.NotArchive => {
1044 archive.deinit(self.base.allocator);
1045 return false;
1046 },
1047 else => |e| return e,
1048 };
1049
1050 try self.archives.append(self.base.allocator, archive);
1051
1052 return true;
1053}
1054
1055const ParseDylibError = error{
1056 OutOfMemory,
1057 EmptyStubFile,
1058 MismatchedCpuArchitecture,
1059 UnsupportedCpuArchitecture,
1060} || fs.File.OpenError || std.os.PReadError || Dylib.Id.ParseError;
1061
1062const DylibCreateOpts = struct {
1063 syslibroot: ?[]const u8 = null,
1064 id: ?Dylib.Id = null,
1065 is_dependent: bool = false,
1066};
1067
1068pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDylibError!bool {
1069 const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
1070 error.FileNotFound => return false,
1071 else => |e| return e,
1072 };
1073 errdefer file.close();
1074
1075 const name = try self.base.allocator.dupe(u8, path);
1076 errdefer self.base.allocator.free(name);
1077
1078 var dylib = Dylib{
1079 .name = name,
1080 .file = file,
1081 };
1082
1083 dylib.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) {
1084 error.EndOfStream, error.NotDylib => {
1085 try file.seekTo(0);
1086
1087 var lib_stub = LibStub.loadFromFile(self.base.allocator, file) catch {
1088 dylib.deinit(self.base.allocator);
1089 return false;
1090 };
1091 defer lib_stub.deinit();
1092
1093 try dylib.parseFromStub(self.base.allocator, self.base.options.target, lib_stub);
1094 },
1095 else => |e| return e,
1096 };
1097
1098 if (opts.id) |id| {
1099 if (dylib.id.?.current_version < id.compatibility_version) {
1100 log.warn("found dylib is incompatible with the required minimum version", .{});
1101 log.warn(" dylib: {s}", .{id.name});
1102 log.warn(" required minimum version: {}", .{id.compatibility_version});
1103 log.warn(" dylib version: {}", .{dylib.id.?.current_version});
1104
1105 // TODO maybe this should be an error and facilitate auto-cleanup?
1106 dylib.deinit(self.base.allocator);
1107 return false;
1108 }
1109 }
1110
1111 const dylib_id = @intCast(u16, self.dylibs.items.len);
1112 try self.dylibs.append(self.base.allocator, dylib);
1113 try self.dylibs_map.putNoClobber(self.base.allocator, dylib.id.?.name, dylib_id);
1114
1115 if (!(opts.is_dependent or self.referenced_dylibs.contains(dylib_id))) {
1116 try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {});
1117 }
1118
1119 // TODO this should not be performed if the user specifies `-flat_namespace` flag.
1120 // See ld64 manpages.
1121 try dylib.parseDependentLibs(self, opts.syslibroot);
1122
1123 return true;
1124}
1125
9971126fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const u8) !void {
9981127 for (files) |file_name| {
9991128 const full_path = full_path: {
......@@ -1003,28 +1132,11 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const
10031132 };
10041133 defer self.base.allocator.free(full_path);
10051134
1006 if (try Object.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path)) |object| {
1007 try self.objects.append(self.base.allocator, object);
1008 continue;
1009 }
1010
1011 if (try Archive.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path)) |archive| {
1012 try self.archives.append(self.base.allocator, archive);
1013 continue;
1014 }
1015
1016 if (try Dylib.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path, .{
1135 if (try self.parseObject(full_path)) continue;
1136 if (try self.parseArchive(full_path)) continue;
1137 if (try self.parseDylib(full_path, .{
10171138 .syslibroot = syslibroot,
1018 })) |dylibs| {
1019 defer self.base.allocator.free(dylibs);
1020 const dylib_id = @intCast(u16, self.dylibs.items.len);
1021 try self.dylibs.appendSlice(self.base.allocator, dylibs);
1022 // We always have to add the dylib that was on the linker line.
1023 if (!self.referenced_dylibs.contains(dylib_id)) {
1024 try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {});
1025 }
1026 continue;
1027 }
1139 })) continue;
10281140
10291141 log.warn("unknown filetype for positional input file: '{s}'", .{file_name});
10301142 }
......@@ -1032,23 +1144,10 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const
10321144
10331145fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8) !void {
10341146 for (libs) |lib| {
1035 if (try Dylib.createAndParseFromPath(self.base.allocator, self.base.options.target, lib, .{
1147 if (try self.parseDylib(lib, .{
10361148 .syslibroot = syslibroot,
1037 })) |dylibs| {
1038 defer self.base.allocator.free(dylibs);
1039 const dylib_id = @intCast(u16, self.dylibs.items.len);
1040 try self.dylibs.appendSlice(self.base.allocator, dylibs);
1041 // We always have to add the dylib that was on the linker line.
1042 if (!self.referenced_dylibs.contains(dylib_id)) {
1043 try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {});
1044 }
1045 continue;
1046 }
1047
1048 if (try Archive.createAndParseFromPath(self.base.allocator, self.base.options.target, lib)) |archive| {
1049 try self.archives.append(self.base.allocator, archive);
1050 continue;
1051 }
1149 })) continue;
1150 if (try self.parseArchive(lib)) continue;
10521151
10531152 log.warn("unknown filetype for a library: '{s}'", .{lib});
10541153 }
......@@ -3360,6 +3459,7 @@ pub fn deinit(self: *MachO) void {
33603459 dylib.deinit(self.base.allocator);
33613460 }
33623461 self.dylibs.deinit(self.base.allocator);
3462 self.dylibs_map.deinit(self.base.allocator);
33633463 self.referenced_dylibs.deinit(self.base.allocator);
33643464
33653465 for (self.load_commands.items) |*lc| {
src/link/MachO/Archive.zig-26
......@@ -103,32 +103,6 @@ pub fn deinit(self: *Archive, allocator: *Allocator) void {
103103 allocator.free(self.name);
104104}
105105
106pub fn createAndParseFromPath(allocator: *Allocator, target: std.Target, path: []const u8) !?Archive {
107 const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
108 error.FileNotFound => return null,
109 else => |e| return e,
110 };
111 errdefer file.close();
112
113 const name = try allocator.dupe(u8, path);
114 errdefer allocator.free(name);
115
116 var archive = Archive{
117 .name = name,
118 .file = file,
119 };
120
121 archive.parse(allocator, target) catch |err| switch (err) {
122 error.EndOfStream, error.NotArchive => {
123 archive.deinit(allocator);
124 return null;
125 },
126 else => |e| return e,
127 };
128
129 return archive;
130}
131
132106pub fn parse(self: *Archive, allocator: *Allocator, target: std.Target) !void {
133107 const reader = self.file.reader();
134108 self.library_offset = try fat.getLibraryOffset(reader, target);
src/link/MachO/Dylib.zig+144-176
......@@ -73,7 +73,7 @@ pub const Id = struct {
7373 allocator.free(id.name);
7474 }
7575
76 const ParseError = fmt.ParseIntError || fmt.BufPrintError;
76 pub const ParseError = fmt.ParseIntError || fmt.BufPrintError;
7777
7878 pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void {
7979 id.current_version = try parseVersion(version);
......@@ -109,7 +109,7 @@ pub const Id = struct {
109109 var count: u4 = 0;
110110 while (split.next()) |value| {
111111 if (count > 2) {
112 log.warn("malformed version field: {s}", .{string});
112 log.debug("malformed version field: {s}", .{string});
113113 return 0x10000;
114114 }
115115 values[count] = value;
......@@ -128,78 +128,6 @@ pub const Id = struct {
128128 }
129129};
130130
131pub const Error = error{
132 OutOfMemory,
133 EmptyStubFile,
134 MismatchedCpuArchitecture,
135 UnsupportedCpuArchitecture,
136} || fs.File.OpenError || std.os.PReadError || Id.ParseError;
137
138pub const CreateOpts = struct {
139 syslibroot: ?[]const u8 = null,
140 id: ?Id = null,
141 target: ?std.Target = null,
142};
143
144pub fn createAndParseFromPath(
145 allocator: *Allocator,
146 target: std.Target,
147 path: []const u8,
148 opts: CreateOpts,
149) Error!?[]Dylib {
150 const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
151 error.FileNotFound => return null,
152 else => |e| return e,
153 };
154 errdefer file.close();
155
156 const name = try allocator.dupe(u8, path);
157 errdefer allocator.free(name);
158
159 var dylib = Dylib{
160 .name = name,
161 .file = file,
162 };
163
164 dylib.parse(allocator, target) catch |err| switch (err) {
165 error.EndOfStream, error.NotDylib => {
166 try file.seekTo(0);
167
168 var lib_stub = LibStub.loadFromFile(allocator, file) catch {
169 dylib.deinit(allocator);
170 return null;
171 };
172 defer lib_stub.deinit();
173
174 try dylib.parseFromStub(allocator, target, lib_stub);
175 },
176 else => |e| return e,
177 };
178
179 if (opts.id) |id| {
180 if (dylib.id.?.current_version < id.compatibility_version) {
181 log.warn("found dylib is incompatible with the required minimum version", .{});
182 log.warn(" | dylib: {s}", .{id.name});
183 log.warn(" | required minimum version: {}", .{id.compatibility_version});
184 log.warn(" | dylib version: {}", .{dylib.id.?.current_version});
185
186 // TODO maybe this should be an error and facilitate auto-cleanup?
187 dylib.deinit(allocator);
188 return null;
189 }
190 }
191
192 var dylibs = std.ArrayList(Dylib).init(allocator);
193 defer dylibs.deinit();
194
195 try dylibs.append(dylib);
196 // TODO this should not be performed if the user specifies `-flat_namespace` flag.
197 // See ld64 manpages.
198 try dylib.parseDependentLibs(allocator, target, &dylibs, opts.syslibroot);
199
200 return dylibs.toOwnedSlice();
201}
202
203131pub fn deinit(self: *Dylib, allocator: *Allocator) void {
204132 for (self.load_commands.items) |*lc| {
205133 lc.deinit(allocator);
......@@ -315,14 +243,7 @@ fn parseSymbols(self: *Dylib, allocator: *Allocator) !void {
315243 }
316244}
317245
318fn hasTarget(targets: []const []const u8, target: []const u8) bool {
319 for (targets) |t| {
320 if (mem.eql(u8, t, target)) return true;
321 }
322 return false;
323}
324
325fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void {
246fn addObjCClassSymbol(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void {
326247 const expanded = &[_][]const u8{
327248 try std.fmt.allocPrint(allocator, "_OBJC_CLASS_$_{s}", .{sym_name}),
328249 try std.fmt.allocPrint(allocator, "_OBJC_METACLASS_$_{s}", .{sym_name}),
......@@ -334,30 +255,21 @@ fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8
334255 }
335256}
336257
337fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 {
338 const arch = switch (target.cpu.arch) {
339 .aarch64 => "arm64",
340 .x86_64 => "x86_64",
341 else => unreachable,
342 };
343 const os = @tagName(target.os.tag);
344 const abi: ?[]const u8 = switch (target.abi) {
345 .gnu => null,
346 .simulator => "simulator",
347 else => unreachable,
348 };
349 if (abi) |x| {
350 return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ arch, os, x });
351 }
352 return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, os });
258fn addSymbol(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void {
259 if (self.symbols.contains(sym_name)) return;
260 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {});
353261}
354262
355263const TargetMatcher = struct {
356264 allocator: *Allocator,
265 target: std.Target,
357266 target_strings: std.ArrayListUnmanaged([]const u8) = .{},
358267
359268 fn init(allocator: *Allocator, target: std.Target) !TargetMatcher {
360 var self = TargetMatcher{ .allocator = allocator };
269 var self = TargetMatcher{
270 .allocator = allocator,
271 .target = target,
272 };
361273 try self.target_strings.append(allocator, try targetToAppleString(allocator, target));
362274
363275 if (target.abi == .simulator) {
......@@ -380,12 +292,41 @@ const TargetMatcher = struct {
380292 self.target_strings.deinit(self.allocator);
381293 }
382294
383 fn matches(self: TargetMatcher, targets: []const []const u8) bool {
295 fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 {
296 const arch = switch (target.cpu.arch) {
297 .aarch64 => "arm64",
298 .x86_64 => "x86_64",
299 else => unreachable,
300 };
301 const os = @tagName(target.os.tag);
302 const abi: ?[]const u8 = switch (target.abi) {
303 .gnu => null,
304 .simulator => "simulator",
305 else => unreachable,
306 };
307 if (abi) |x| {
308 return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ arch, os, x });
309 }
310 return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, os });
311 }
312
313 fn hasValue(stack: []const []const u8, needle: []const u8) bool {
314 for (stack) |v| {
315 if (mem.eql(u8, v, needle)) return true;
316 }
317 return false;
318 }
319
320 fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool {
384321 for (self.target_strings.items) |t| {
385 if (hasTarget(targets, t)) return true;
322 if (hasValue(targets, t)) return true;
386323 }
387324 return false;
388325 }
326
327 fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool {
328 return hasValue(archs, @tagName(self.target.cpu.arch));
329 }
389330};
390331
391332pub 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
395336
396337 const umbrella_lib = lib_stub.inner[0];
397338
398 var id = try Id.default(allocator, umbrella_lib.install_name);
399 if (umbrella_lib.current_version) |version| {
339 var id = try Id.default(allocator, umbrella_lib.installName());
340 if (umbrella_lib.currentVersion()) |version| {
400341 try id.parseCurrentVersion(version);
401342 }
402 if (umbrella_lib.compatibility_version) |version| {
343 if (umbrella_lib.compatibilityVersion()) |version| {
403344 try id.parseCompatibilityVersion(version);
404345 }
405346 self.id = id;
406347
407 var matcher = try TargetMatcher.init(allocator, target);
408 defer matcher.deinit();
409
410348 var umbrella_libs = std.StringHashMap(void).init(allocator);
411349 defer umbrella_libs.deinit();
412350
413 for (lib_stub.inner) |stub, stub_index| {
414 if (!matcher.matches(stub.targets)) continue;
351 log.debug(" (install_name '{s}')", .{umbrella_lib.installName()});
352
353 var matcher = try TargetMatcher.init(allocator, target);
354 defer matcher.deinit();
355
356 for (lib_stub.inner) |elem, stub_index| {
357 const is_match = switch (elem) {
358 .v3 => |stub| matcher.matchesArch(stub.archs),
359 .v4 => |stub| matcher.matchesTarget(stub.targets),
360 };
361 if (!is_match) continue;
415362
416363 if (stub_index > 0) {
417364 // TODO I thought that we could switch on presence of `parent-umbrella` map;
418365 // however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib`
419366 // BUT does not feature a `parent-umbrella` map as the only sublib. Apple's bug perhaps?
420 try umbrella_libs.put(stub.install_name, .{});
367 try umbrella_libs.put(elem.installName(), .{});
421368 }
422369
423 if (stub.exports) |exports| {
424 for (exports) |exp| {
425 if (!matcher.matches(exp.targets)) continue;
426
427 if (exp.symbols) |symbols| {
428 for (symbols) |sym_name| {
429 if (self.symbols.contains(sym_name)) continue;
430 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {});
370 switch (elem) {
371 .v3 => |stub| {
372 if (stub.exports) |exports| {
373 for (exports) |exp| {
374 if (!matcher.matchesArch(exp.archs)) continue;
375
376 if (exp.symbols) |symbols| {
377 for (symbols) |sym_name| {
378 try self.addSymbol(allocator, sym_name);
379 }
380 }
381
382 if (exp.objc_classes) |objc_classes| {
383 for (objc_classes) |class_name| {
384 try self.addObjCClassSymbol(allocator, class_name);
385 }
386 }
387
388 // TODO track which libs were already parsed in different steps
389 if (exp.re_exports) |re_exports| {
390 for (re_exports) |lib| {
391 if (umbrella_libs.contains(lib)) continue;
392
393 log.debug(" (found re-export '{s}')", .{lib});
394
395 const dep_id = try Id.default(allocator, lib);
396 try self.dependent_libs.append(allocator, dep_id);
397 }
398 }
431399 }
432400 }
433
434 if (exp.objc_classes) |classes| {
435 for (classes) |sym_name| {
436 try self.addObjCClassSymbols(allocator, sym_name);
401 },
402 .v4 => |stub| {
403 if (stub.exports) |exports| {
404 for (exports) |exp| {
405 if (!matcher.matchesTarget(exp.targets)) continue;
406
407 if (exp.symbols) |symbols| {
408 for (symbols) |sym_name| {
409 try self.addSymbol(allocator, sym_name);
410 }
411 }
412
413 if (exp.objc_classes) |classes| {
414 for (classes) |sym_name| {
415 try self.addObjCClassSymbol(allocator, sym_name);
416 }
417 }
437418 }
438419 }
439 }
440 }
441
442 if (stub.reexports) |reexports| {
443 for (reexports) |reexp| {
444 if (!matcher.matches(reexp.targets)) continue;
445420
446 if (reexp.symbols) |symbols| {
447 for (symbols) |sym_name| {
448 if (self.symbols.contains(sym_name)) continue;
449 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {});
421 if (stub.reexports) |reexports| {
422 for (reexports) |reexp| {
423 if (!matcher.matchesTarget(reexp.targets)) continue;
424
425 if (reexp.symbols) |symbols| {
426 for (symbols) |sym_name| {
427 try self.addSymbol(allocator, sym_name);
428 }
429 }
430
431 if (reexp.objc_classes) |classes| {
432 for (classes) |sym_name| {
433 try self.addObjCClassSymbol(allocator, sym_name);
434 }
435 }
450436 }
451437 }
452438
453 if (reexp.objc_classes) |classes| {
439 if (stub.objc_classes) |classes| {
454440 for (classes) |sym_name| {
455 try self.addObjCClassSymbols(allocator, sym_name);
441 try self.addObjCClassSymbol(allocator, sym_name);
456442 }
457443 }
458 }
459 }
460
461 if (stub.objc_classes) |classes| {
462 for (classes) |sym_name| {
463 try self.addObjCClassSymbols(allocator, sym_name);
464 }
444 },
465445 }
466446 }
467447
468 log.debug("{s}", .{umbrella_lib.install_name});
469
470 // TODO track which libs were already parsed in different steps
471 for (lib_stub.inner) |stub| {
472 if (!matcher.matches(stub.targets)) continue;
448 // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include
449 // re-exports directly in the stub file.
450 for (lib_stub.inner) |elem| {
451 if (elem == .v3) break;
452 const stub = elem.v4;
473453
454 // TODO track which libs were already parsed in different steps
474455 if (stub.reexported_libraries) |reexports| {
475456 for (reexports) |reexp| {
476 if (!matcher.matches(reexp.targets)) continue;
457 if (!matcher.matchesTarget(reexp.targets)) continue;
477458
478459 for (reexp.libraries) |lib| {
479 if (umbrella_libs.contains(lib)) {
480 log.debug(" | {s} <= {s}", .{ lib, umbrella_lib.install_name });
481 continue;
482 }
460 if (umbrella_libs.contains(lib)) continue;
483461
484 log.debug(" | {s}", .{lib});
462 log.debug(" (found re-export '{s}')", .{lib});
485463
486464 const dep_id = try Id.default(allocator, lib);
487465 try self.dependent_libs.append(allocator, dep_id);
......@@ -493,12 +471,12 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
493471
494472pub fn parseDependentLibs(
495473 self: *Dylib,
496 allocator: *Allocator,
497 target: std.Target,
498 out: *std.ArrayList(Dylib),
474 macho_file: *MachO,
499475 syslibroot: ?[]const u8,
500476) !void {
501477 outer: for (self.dependent_libs.items) |id| {
478 if (macho_file.dylibs_map.contains(id.name)) continue :outer;
479
502480 const has_ext = blk: {
503481 const basename = fs.path.basename(id.name);
504482 break :blk mem.lastIndexOfScalar(u8, basename, '.') != null;
......@@ -510,38 +488,28 @@ pub fn parseDependentLibs(
510488 } else id.name;
511489
512490 for (&[_][]const u8{ extension, ".tbd" }) |ext| {
513 const with_ext = try std.fmt.allocPrint(allocator, "{s}{s}", .{
491 const with_ext = try std.fmt.allocPrint(macho_file.base.allocator, "{s}{s}", .{
514492 without_ext,
515493 ext,
516494 });
517 defer allocator.free(with_ext);
495 defer macho_file.base.allocator.free(with_ext);
518496
519497 const full_path = if (syslibroot) |root|
520 try fs.path.join(allocator, &.{ root, with_ext })
498 try fs.path.join(macho_file.base.allocator, &.{ root, with_ext })
521499 else
522500 with_ext;
523 defer if (syslibroot) |_| allocator.free(full_path);
501 defer if (syslibroot) |_| macho_file.base.allocator.free(full_path);
524502
525503 log.debug("trying dependency at fully resolved path {s}", .{full_path});
526504
527 const dylibs = (try createAndParseFromPath(
528 allocator,
529 target,
530 full_path,
531 .{
532 .id = id,
533 .syslibroot = syslibroot,
534 },
535 )) orelse {
536 continue;
537 };
538 defer allocator.free(dylibs);
539
540 try out.appendSlice(dylibs);
541
542 continue :outer;
505 const did_parse_successfully = try macho_file.parseDylib(full_path, .{
506 .id = id,
507 .syslibroot = syslibroot,
508 .is_dependent = true,
509 });
510 if (!did_parse_successfully) continue;
543511 } else {
544 log.warn("unable to resolve dependency {s}", .{id.name});
512 log.debug("unable to resolve dependency {s}", .{id.name});
545513 }
546514 }
547515}
src/link/MachO/Object.zig-26
......@@ -153,32 +153,6 @@ pub fn deinit(self: *Object, allocator: *Allocator) void {
153153 }
154154}
155155
156pub fn createAndParseFromPath(allocator: *Allocator, target: std.Target, path: []const u8) !?Object {
157 const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
158 error.FileNotFound => return null,
159 else => |e| return e,
160 };
161 errdefer file.close();
162
163 const name = try allocator.dupe(u8, path);
164 errdefer allocator.free(name);
165
166 var object = Object{
167 .name = name,
168 .file = file,
169 };
170
171 object.parse(allocator, target) catch |err| switch (err) {
172 error.EndOfStream, error.NotObject => {
173 object.deinit(allocator);
174 return null;
175 },
176 else => |e| return e,
177 };
178
179 return object;
180}
181
182156pub fn parse(self: *Object, allocator: *Allocator, target: std.Target) !void {
183157 const reader = self.file.reader();
184158 if (self.file_offset) |offset| {
src/link/tapi.zig+111-52
......@@ -6,6 +6,89 @@ const log = std.log.scoped(.tapi);
66const Allocator = mem.Allocator;
77const Yaml = @import("tapi/yaml.zig").Yaml;
88
9const VersionField = union(enum) {
10 string: []const u8,
11 float: f64,
12 int: u64,
13};
14
15pub const TbdV3 = struct {
16 archs: []const []const u8,
17 uuids: []const []const u8,
18 platform: []const u8,
19 install_name: []const u8,
20 current_version: ?VersionField,
21 compatibility_version: ?VersionField,
22 objc_constraint: ?[]const u8,
23 exports: ?[]const struct {
24 archs: []const []const u8,
25 re_exports: ?[]const []const u8,
26 symbols: ?[]const []const u8,
27 objc_classes: ?[]const []const u8,
28 },
29};
30
31pub const TbdV4 = struct {
32 tbd_version: u3,
33 targets: []const []const u8,
34 uuids: []const struct {
35 target: []const u8,
36 value: []const u8,
37 },
38 install_name: []const u8,
39 current_version: ?VersionField,
40 compatibility_version: ?VersionField,
41 reexported_libraries: ?[]const struct {
42 targets: []const []const u8,
43 libraries: []const []const u8,
44 },
45 parent_umbrella: ?[]const struct {
46 targets: []const []const u8,
47 umbrella: []const u8,
48 },
49 exports: ?[]const struct {
50 targets: []const []const u8,
51 symbols: ?[]const []const u8,
52 objc_classes: ?[]const []const u8,
53 },
54 reexports: ?[]const struct {
55 targets: []const []const u8,
56 symbols: ?[]const []const u8,
57 objc_classes: ?[]const []const u8,
58 },
59 allowable_clients: ?[]const struct {
60 targets: []const []const u8,
61 clients: []const []const u8,
62 },
63 objc_classes: ?[]const []const u8,
64};
65
66pub const Tbd = union(enum) {
67 v3: TbdV3,
68 v4: TbdV4,
69
70 pub fn currentVersion(self: Tbd) ?VersionField {
71 return switch (self) {
72 .v3 => |v3| v3.current_version,
73 .v4 => |v4| v4.current_version,
74 };
75 }
76
77 pub fn compatibilityVersion(self: Tbd) ?VersionField {
78 return switch (self) {
79 .v3 => |v3| v3.compatibility_version,
80 .v4 => |v4| v4.compatibility_version,
81 };
82 }
83
84 pub fn installName(self: Tbd) []const u8 {
85 return switch (self) {
86 .v3 => |v3| v3.install_name,
87 .v4 => |v4| v4.install_name,
88 };
89 }
90};
91
992pub const LibStub = struct {
1093 /// Underlying memory for stub's contents.
1194 yaml: Yaml,
......@@ -13,49 +96,6 @@ pub const LibStub = struct {
1396 /// Typed contents of the tbd file.
1497 inner: []Tbd,
1598
16 const Tbd = struct {
17 tbd_version: u3,
18 targets: []const []const u8,
19 uuids: []const struct {
20 target: []const u8,
21 value: []const u8,
22 },
23 install_name: []const u8,
24 current_version: ?union(enum) {
25 string: []const u8,
26 float: f64,
27 int: u64,
28 },
29 compatibility_version: ?union(enum) {
30 string: []const u8,
31 float: f64,
32 int: u64,
33 },
34 reexported_libraries: ?[]const struct {
35 targets: []const []const u8,
36 libraries: []const []const u8,
37 },
38 parent_umbrella: ?[]const struct {
39 targets: []const []const u8,
40 umbrella: []const u8,
41 },
42 exports: ?[]const struct {
43 targets: []const []const u8,
44 symbols: ?[]const []const u8,
45 objc_classes: ?[]const []const u8,
46 },
47 reexports: ?[]const struct {
48 targets: []const []const u8,
49 symbols: ?[]const []const u8,
50 objc_classes: ?[]const []const u8,
51 },
52 allowable_clients: ?[]const struct {
53 targets: []const []const u8,
54 clients: []const []const u8,
55 },
56 objc_classes: ?[]const []const u8,
57 };
58
5999 pub fn loadFromFile(allocator: *Allocator, file: fs.File) !LibStub {
60100 const source = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
61101 defer allocator.free(source);
......@@ -65,16 +105,35 @@ pub const LibStub = struct {
65105 .inner = undefined,
66106 };
67107
68 lib_stub.inner = lib_stub.yaml.parse([]Tbd) catch |err| blk: {
69 switch (err) {
70 error.TypeMismatch => {
71 // TODO clean this up.
72 var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1);
73 out[0] = try lib_stub.yaml.parse(Tbd);
74 break :blk out;
75 },
76 else => |e| return e,
108 // TODO revisit this logic in the hope of simplifying it.
109 lib_stub.inner = blk: {
110 err: {
111 log.debug("trying to parse as []TbdV4", .{});
112 const inner = lib_stub.yaml.parse([]TbdV4) catch break :err;
113 var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, inner.len);
114 for (inner) |doc, i| {
115 out[i] = .{ .v4 = doc };
116 }
117 break :blk out;
118 }
119
120 err: {
121 log.debug("trying to parse as TbdV4", .{});
122 const inner = lib_stub.yaml.parse(TbdV4) catch break :err;
123 var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1);
124 out[0] = .{ .v4 = inner };
125 break :blk out;
126 }
127
128 err: {
129 log.debug("trying to parse as TbdV3", .{});
130 const inner = lib_stub.yaml.parse(TbdV3) catch break :err;
131 var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1);
132 out[0] = .{ .v3 = inner };
133 break :blk out;
77134 }
135
136 return error.NotLibStub;
78137 };
79138
80139 return lib_stub;
src/link/tapi/yaml.zig+1-1
......@@ -371,7 +371,7 @@ pub const Yaml = struct {
371371 }
372372
373373 const unwrapped = value orelse {
374 log.err("missing struct field: {s}: {s}", .{ field.name, @typeName(field.field_type) });
374 log.debug("missing struct field: {s}: {s}", .{ field.name, @typeName(field.field_type) });
375375 return error.StructFieldMissing;
376376 };
377377 @field(parsed, field.name) = try self.parseValue(field.field_type, unwrapped);