authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-09-01 08:00:12+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-09-01 08:00:12+02:00
log43b27d47c98e0ada00db828ddf410372c6ded66a
treee215b216133384bfed7dbcf386417be38c73d0d4
parentf4c9e19bc3213c2bc7e03d7b06d7129882f39f6c
parent2187744411047d3ce9ec199913cd0bcba323633b
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #17030 from ziglang/macho-libsystem

macho: improve logic for finding and reporting errors when searching for libSystem and dependencies

2 files changed, 121 insertions(+), 90 deletions(-)

src/link/MachO.zig+120-89
...@@ -329,14 +329,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No...@@ -329,14 +329,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
329 }329 }
330330
331 var libs = std.StringArrayHashMap(link.SystemLib).init(arena);331 var libs = std.StringArrayHashMap(link.SystemLib).init(arena);
332 try resolveLibSystem(332 try self.resolveLibSystem(arena, comp, &.{}, &libs);
333 arena,
334 comp,
335 self.base.options.sysroot,
336 self.base.options.target,
337 &.{},
338 &libs,
339 );
340333
341 const id_symlink_basename = "link.id";334 const id_symlink_basename = "link.id";
342335
...@@ -640,77 +633,104 @@ inline fn conformUuid(out: *[Md5.digest_length]u8) void {...@@ -640,77 +633,104 @@ inline fn conformUuid(out: *[Md5.digest_length]u8) void {
640}633}
641634
642pub fn resolveLibSystem(635pub fn resolveLibSystem(
636 self: *MachO,
643 arena: Allocator,637 arena: Allocator,
644 comp: *Compilation,638 comp: *Compilation,
645 syslibroot: ?[]const u8,
646 target: std.Target,
647 search_dirs: []const []const u8,639 search_dirs: []const []const u8,
648 out_libs: anytype,640 out_libs: anytype,
649) !void {641) !void {
650 // If we were given the sysroot, try to look there first for libSystem.B.{dylib, tbd}.642 var tmp_arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator);
651 if (syslibroot) |root| {643 defer tmp_arena_allocator.deinit();
652 const full_dir_path = try std.fs.path.join(arena, &.{ root, "usr", "lib" });644 const tmp_arena = tmp_arena_allocator.allocator();
653 if (try resolveLibSystemInDirs(arena, &.{full_dir_path}, out_libs)) return;
654 }
655645
656 // Next, try input search dirs if we are linking on a custom host such as Nix.646 var test_path = std.ArrayList(u8).init(tmp_arena);
657 if (try resolveLibSystemInDirs(arena, search_dirs, out_libs)) return;647 var checked_paths = std.ArrayList([]const u8).init(tmp_arena);
658648
659 // As a fallback, try linking against Zig shipped stub.649 success: {
660 const libsystem_name = try std.fmt.allocPrint(arena, "libSystem.{d}.tbd", .{650 if (self.base.options.sysroot) |root| {
661 target.os.version_range.semver.min.major,651 const dir = try fs.path.join(tmp_arena, &[_][]const u8{ root, "usr", "lib" });
662 });652 if (try accessLibPath(tmp_arena, &test_path, &checked_paths, dir, "libSystem")) break :success;
663 const full_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{653 }
664 "libc", "darwin", libsystem_name,654
665 });655 for (search_dirs) |dir| if (try accessLibPath(
666 try out_libs.put(full_path, .{656 tmp_arena,
657 &test_path,
658 &checked_paths,
659 dir,
660 "libSystem",
661 )) break :success;
662
663 const dir = try comp.zig_lib_directory.join(tmp_arena, &[_][]const u8{ "libc", "darwin" });
664 const lib_name = try std.fmt.allocPrint(tmp_arena, "libSystem.{d}", .{
665 self.base.options.target.os.version_range.semver.min.major,
666 });
667 if (try accessLibPath(tmp_arena, &test_path, &checked_paths, dir, lib_name)) break :success;
668
669 try self.reportMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{});
670 return;
671 }
672
673 const libsystem_path = try arena.dupe(u8, test_path.items);
674 try out_libs.put(libsystem_path, .{
667 .needed = true,675 .needed = true,
668 .weak = false,676 .weak = false,
669 .path = full_path,677 .path = libsystem_path,
670 });678 });
671}
672679
673fn resolveLibSystemInDirs(arena: Allocator, dirs: []const []const u8, out_libs: anytype) !bool {680 const ext = fs.path.extension(libsystem_path);
674 // Try stub file first. If we hit it, then we're done as the stub file681 if (mem.eql(u8, ext, ".dylib")) {
675 // re-exports every single symbol definition.682 // We found 'libSystem.dylib', so now we also need to look for 'libc.dylib'.
676 for (dirs) |dir| {683 success: {
677 if (try resolveLib(arena, dir, "libSystem", ".tbd")) |full_path| {684 if (self.base.options.sysroot) |root| {
678 try out_libs.put(full_path, .{ .needed = true, .weak = false, .path = full_path });685 const dir = try fs.path.join(tmp_arena, &[_][]const u8{ root, "usr", "lib" });
679 return true;686 if (try accessLibPath(tmp_arena, &test_path, &checked_paths, dir, "libc")) break :success;
680 }
681 }
682 // If we didn't hit the stub file, try .dylib next. However, libSystem.dylib
683 // doesn't export libc.dylib which we'll need to resolve subsequently also.
684 for (dirs) |dir| {
685 if (try resolveLib(arena, dir, "libSystem", ".dylib")) |libsystem_path| {
686 if (try resolveLib(arena, dir, "libc", ".dylib")) |libc_path| {
687 try out_libs.put(libsystem_path, .{ .needed = true, .weak = false, .path = libsystem_path });
688 try out_libs.put(libc_path, .{ .needed = true, .weak = false, .path = libc_path });
689 return true;
690 }687 }
688
689 for (search_dirs) |dir| if (try accessLibPath(
690 tmp_arena,
691 &test_path,
692 &checked_paths,
693 dir,
694 "libc",
695 )) break :success;
696
697 try self.reportMissingLibraryError(checked_paths.items, "unable to find libc system library", .{});
691 }698 }
692 }699 }
693
694 return false;
695}700}
696701
697fn resolveLib(702fn accessLibPath(
698 arena: Allocator,703 gpa: Allocator,
704 test_path: *std.ArrayList(u8),
705 checked_paths: *std.ArrayList([]const u8),
699 search_dir: []const u8,706 search_dir: []const u8,
700 name: []const u8,707 lib_name: []const u8,
701 ext: []const u8,708) !bool {
702) !?[]const u8 {709 const sep = fs.path.sep_str;
703 const search_name = try std.fmt.allocPrint(arena, "{s}{s}", .{ name, ext });710
704 const full_path = try fs.path.join(arena, &[_][]const u8{ search_dir, search_name });711 tbd: {
705712 test_path.clearRetainingCapacity();
706 // Check if the file exists.713 try test_path.writer().print("{s}" ++ sep ++ "{s}.tbd", .{ search_dir, lib_name });
707 const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {714 try checked_paths.append(try gpa.dupe(u8, test_path.items));
708 error.FileNotFound => return null,715 fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
709 else => |e| return e,716 error.FileNotFound => break :tbd,
710 };717 else => |e| return e,
711 defer tmp.close();718 };
719 return true;
720 }
721
722 dylib: {
723 test_path.clearRetainingCapacity();
724 try test_path.writer().print("{s}" ++ sep ++ "{s}.dylib", .{ search_dir, lib_name });
725 try checked_paths.append(try gpa.dupe(u8, test_path.items));
726 fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
727 error.FileNotFound => break :dylib,
728 else => |e| return e,
729 };
730 return true;
731 }
712732
713 return full_path;733 return false;
714}734}
715735
716const ParseError = error{736const ParseError = error{
...@@ -1084,9 +1104,6 @@ pub fn parseDependentLibs(self: *MachO, dependent_libs: anytype) !void {...@@ -1084,9 +1104,6 @@ pub fn parseDependentLibs(self: *MachO, dependent_libs: anytype) !void {
1084 // TODO this should not be performed if the user specifies `-flat_namespace` flag.1104 // TODO this should not be performed if the user specifies `-flat_namespace` flag.
1085 // See ld64 manpages.1105 // See ld64 manpages.
1086 const gpa = self.base.allocator;1106 const gpa = self.base.allocator;
1087 var arena_alloc = std.heap.ArenaAllocator.init(gpa);
1088 const arena = arena_alloc.allocator();
1089 defer arena_alloc.deinit();
10901107
1091 while (dependent_libs.readItem()) |dep_id| {1108 while (dependent_libs.readItem()) |dep_id| {
1092 defer dep_id.id.deinit(gpa);1109 defer dep_id.id.deinit(gpa);
...@@ -1095,38 +1112,33 @@ pub fn parseDependentLibs(self: *MachO, dependent_libs: anytype) !void {...@@ -1095,38 +1112,33 @@ pub fn parseDependentLibs(self: *MachO, dependent_libs: anytype) !void {
10951112
1096 const parent = &self.dylibs.items[dep_id.parent];1113 const parent = &self.dylibs.items[dep_id.parent];
1097 const weak = parent.weak;1114 const weak = parent.weak;
1098 const has_ext = blk: {1115 const dirname = fs.path.dirname(dep_id.id.name) orelse "";
1099 const basename = fs.path.basename(dep_id.id.name);1116 const stem = fs.path.stem(dep_id.id.name);
1100 break :blk mem.lastIndexOfScalar(u8, basename, '.') != null;
1101 };
1102 const extension = if (has_ext) fs.path.extension(dep_id.id.name) else "";
1103 const without_ext = if (has_ext) blk: {
1104 const index = mem.lastIndexOfScalar(u8, dep_id.id.name, '.') orelse unreachable;
1105 break :blk dep_id.id.name[0..index];
1106 } else dep_id.id.name;
11071117
1108 const maybe_full_path = full_path: {1118 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
1109 if (self.base.options.sysroot) |root| {1119 defer arena_allocator.deinit();
1110 for (&[_][]const u8{ extension, ".tbd" }) |ext| {1120 const arena = arena_allocator.allocator();
1111 if (try resolveLib(arena, root, without_ext, ext)) |full_path| break :full_path full_path;
1112 }
1113 }
11141121
1115 for (&[_][]const u8{ extension, ".tbd" }) |ext| {1122 var test_path = std.ArrayList(u8).init(arena);
1116 if (try resolveLib(arena, "", without_ext, ext)) |full_path| break :full_path full_path;1123 var checked_paths = std.ArrayList([]const u8).init(arena);
1124
1125 success: {
1126 if (self.base.options.sysroot) |root| {
1127 const dir = try fs.path.join(arena, &[_][]const u8{ root, dirname });
1128 if (try accessLibPath(gpa, &test_path, &checked_paths, dir, stem)) break :success;
1117 }1129 }
11181130
1119 break :full_path null;1131 if (try accessLibPath(gpa, &test_path, &checked_paths, dirname, stem)) break :success;
1120 };
11211132
1122 const full_path = maybe_full_path orelse {1133 try self.reportMissingLibraryError(
1123 const parent_name = if (parent.id) |id| id.name else parent.path;1134 checked_paths.items,
1124 try self.reportDependencyError(parent_name, null, "missing dynamic library dependency: '{s}'", .{1135 "missing dynamic library dependency: '{s}'",
1125 dep_id.id.name,1136 .{dep_id.id.name},
1126 });1137 );
1127 continue;1138 continue;
1128 };1139 }
11291140
1141 const full_path = test_path.items;
1130 const file = try std.fs.cwd().openFile(full_path, .{});1142 const file = try std.fs.cwd().openFile(full_path, .{});
1131 defer file.close();1143 defer file.close();
11321144
...@@ -4942,6 +4954,25 @@ pub fn handleAndReportParseError(...@@ -4942,6 +4954,25 @@ pub fn handleAndReportParseError(
4942 }4954 }
4943}4955}
49444956
4957fn reportMissingLibraryError(
4958 self: *MachO,
4959 checked_paths: []const []const u8,
4960 comptime format: []const u8,
4961 args: anytype,
4962) error{OutOfMemory}!void {
4963 const gpa = self.base.allocator;
4964 try self.misc_errors.ensureUnusedCapacity(gpa, 1);
4965 var notes = try gpa.alloc(File.ErrorMsg, checked_paths.len);
4966 errdefer gpa.free(notes);
4967 for (checked_paths, notes) |path, *note| {
4968 note.* = .{ .msg = try std.fmt.allocPrint(gpa, "tried {s}", .{path}) };
4969 }
4970 self.misc_errors.appendAssumeCapacity(.{
4971 .msg = try std.fmt.allocPrint(gpa, format, args),
4972 .notes = notes,
4973 });
4974}
4975
4945fn reportDependencyError(4976fn reportDependencyError(
4946 self: *MachO,4977 self: *MachO,
4947 parent: []const u8,4978 parent: []const u8,
src/link/MachO/zld.zig+1-1
...@@ -214,7 +214,7 @@ pub fn linkWithZld(...@@ -214,7 +214,7 @@ pub fn linkWithZld(
214 });214 });
215 }215 }
216216
217 try MachO.resolveLibSystem(arena, comp, options.sysroot, target, options.lib_dirs, &libs);217 try macho_file.resolveLibSystem(arena, comp, options.lib_dirs, &libs);
218218
219 if (options.verbose_link) {219 if (options.verbose_link) {
220 var argv = std.ArrayList([]const u8).init(arena);220 var argv = std.ArrayList([]const u8).init(arena);