authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-08-21 11:01:11+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-08-21 11:01:11+02:00
log8e96be0088d4ae007589651066d426f815aaab4f
tree398f48e05e365436a16f170b3075e596f135decb
parent283afb50b56fb8a2c288d2452bdf6e595a1bbb06
parentb73ef342895f00bdb31ea5f947ee83764f235d43
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #16888 from ziglang/macho-frameworks

compiler: resolve framework paths in the frontend

8 files changed, 125 insertions(+), 139 deletions(-)

src/Compilation.zig+5-5
......@@ -507,7 +507,7 @@ pub const InitOptions = struct {
507507 c_source_files: []const CSourceFile = &[0]CSourceFile{},
508508 link_objects: []LinkObject = &[0]LinkObject{},
509509 framework_dirs: []const []const u8 = &[0][]const u8{},
510 frameworks: std.StringArrayHashMapUnmanaged(Framework) = .{},
510 frameworks: []const Framework = &.{},
511511 system_lib_names: []const []const u8 = &.{},
512512 system_lib_infos: []const SystemLib = &.{},
513513 /// These correspond to the WASI libc emulated subcomponents including:
......@@ -830,7 +830,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
830830 // Our linker can't handle objects or most advanced options yet.
831831 if (options.link_objects.len != 0 or
832832 options.c_source_files.len != 0 or
833 options.frameworks.count() != 0 or
833 options.frameworks.len != 0 or
834834 options.system_lib_names.len != 0 or
835835 options.link_libc or options.link_libcpp or
836836 link_eh_frame_hdr or
......@@ -2267,7 +2267,7 @@ fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemo
22672267/// to remind the programmer to update multiple related pieces of code that
22682268/// are in different locations. Bump this number when adding or deleting
22692269/// anything from the link cache manifest.
2270pub const link_hash_implementation_version = 9;
2270pub const link_hash_implementation_version = 10;
22712271
22722272fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void {
22732273 const gpa = comp.gpa;
......@@ -2277,7 +2277,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
22772277 defer arena_allocator.deinit();
22782278 const arena = arena_allocator.allocator();
22792279
2280 comptime assert(link_hash_implementation_version == 9);
2280 comptime assert(link_hash_implementation_version == 10);
22812281
22822282 if (comp.bin_file.options.module) |mod| {
22832283 const main_zig_file = try mod.main_pkg.root_src_directory.join(arena, &[_][]const u8{
......@@ -2386,7 +2386,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
23862386
23872387 // Mach-O specific stuff
23882388 man.hash.addListOfBytes(comp.bin_file.options.framework_dirs);
2389 link.hashAddFrameworks(&man.hash, comp.bin_file.options.frameworks);
2389 try link.hashAddFrameworks(man, comp.bin_file.options.frameworks);
23902390 try man.addOptionalFile(comp.bin_file.options.entitlements);
23912391 man.hash.addOptional(comp.bin_file.options.pagezero_size);
23922392 man.hash.addOptional(comp.bin_file.options.headerpad_size);
src/link.zig+7-12
......@@ -37,6 +37,7 @@ pub const SystemLib = struct {
3737pub const Framework = struct {
3838 needed: bool = false,
3939 weak: bool = false,
40 path: []const u8,
4041};
4142
4243pub const SortSection = enum { name, alignment };
......@@ -56,15 +57,11 @@ pub fn hashAddSystemLibs(
5657 }
5758}
5859
59pub fn hashAddFrameworks(
60 hh: *Cache.HashHelper,
61 hm: std.StringArrayHashMapUnmanaged(Framework),
62) void {
63 const keys = hm.keys();
64 hh.addListOfBytes(keys);
65 for (hm.values()) |value| {
66 hh.add(value.needed);
67 hh.add(value.weak);
60pub fn hashAddFrameworks(man: *Cache.Manifest, hm: []const Framework) !void {
61 for (hm) |value| {
62 man.hash.add(value.needed);
63 man.hash.add(value.weak);
64 _ = try man.addFile(value.path, null);
6865 }
6966}
7067
......@@ -208,7 +205,7 @@ pub const Options = struct {
208205
209206 objects: []Compilation.LinkObject,
210207 framework_dirs: []const []const u8,
211 frameworks: std.StringArrayHashMapUnmanaged(Framework),
208 frameworks: []const Framework,
212209 /// These are *always* dynamically linked. Static libraries will be
213210 /// provided as positional arguments.
214211 system_libs: std.StringArrayHashMapUnmanaged(SystemLib),
......@@ -276,7 +273,6 @@ pub const Options = struct {
276273
277274 pub fn move(self: *Options) Options {
278275 const copied_state = self.*;
279 self.frameworks = .{};
280276 self.system_libs = .{};
281277 self.force_undefined_symbols = .{};
282278 return copied_state;
......@@ -642,7 +638,6 @@ pub const File = struct {
642638 base.releaseLock();
643639 if (base.file) |f| f.close();
644640 if (base.intermediary_basename) |sub_path| base.allocator.free(sub_path);
645 base.options.frameworks.deinit(base.allocator);
646641 base.options.system_libs.deinit(base.allocator);
647642 base.options.force_undefined_symbols.deinit(base.allocator);
648643 switch (base.tag) {
src/link/Coff/lld.zig+1-1
......@@ -63,7 +63,7 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
6363 man = comp.cache_parent.obtain();
6464 self.base.releaseLock();
6565
66 comptime assert(Compilation.link_hash_implementation_version == 9);
66 comptime assert(Compilation.link_hash_implementation_version == 10);
6767
6868 for (self.base.options.objects) |obj| {
6969 _ = try man.addFile(obj.path, null);
src/link/Elf.zig+1-1
......@@ -1367,7 +1367,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
13671367 // We are about to obtain this lock, so here we give other processes a chance first.
13681368 self.base.releaseLock();
13691369
1370 comptime assert(Compilation.link_hash_implementation_version == 9);
1370 comptime assert(Compilation.link_hash_implementation_version == 10);
13711371
13721372 try man.addOptionalFile(self.base.options.linker_script);
13731373 try man.addOptionalFile(self.base.options.version_script);
src/link/MachO.zig+1-63
......@@ -870,49 +870,7 @@ fn resolveLibSystemInDirs(arena: Allocator, dirs: []const []const u8, out_libs:
870870 return false;
871871}
872872
873pub fn resolveSearchDir(
874 arena: Allocator,
875 dir: []const u8,
876 syslibroot: ?[]const u8,
877) !?[]const u8 {
878 var candidates = std.ArrayList([]const u8).init(arena);
879
880 if (fs.path.isAbsolute(dir)) {
881 if (syslibroot) |root| {
882 const common_dir = if (builtin.os.tag == .windows) blk: {
883 // We need to check for disk designator and strip it out from dir path so
884 // that we can concat dir with syslibroot.
885 // TODO we should backport this mechanism to 'MachO.Dylib.parseDependentLibs()'
886 const disk_designator = fs.path.diskDesignatorWindows(dir);
887
888 if (mem.indexOf(u8, dir, disk_designator)) |where| {
889 break :blk dir[where + disk_designator.len ..];
890 }
891
892 break :blk dir;
893 } else dir;
894 const full_path = try fs.path.join(arena, &[_][]const u8{ root, common_dir });
895 try candidates.append(full_path);
896 }
897 }
898
899 try candidates.append(dir);
900
901 for (candidates.items) |candidate| {
902 // Verify that search path actually exists
903 var tmp = fs.cwd().openDir(candidate, .{}) catch |err| switch (err) {
904 error.FileNotFound => continue,
905 else => |e| return e,
906 };
907 defer tmp.close();
908
909 return candidate;
910 }
911
912 return null;
913}
914
915pub fn resolveLib(
873fn resolveLib(
916874 arena: Allocator,
917875 search_dir: []const u8,
918876 name: []const u8,
......@@ -931,26 +889,6 @@ pub fn resolveLib(
931889 return full_path;
932890}
933891
934pub fn resolveFramework(
935 arena: Allocator,
936 search_dir: []const u8,
937 name: []const u8,
938 ext: []const u8,
939) !?[]const u8 {
940 const search_name = try std.fmt.allocPrint(arena, "{s}{s}", .{ name, ext });
941 const prefix_path = try std.fmt.allocPrint(arena, "{s}.framework", .{name});
942 const full_path = try fs.path.join(arena, &[_][]const u8{ search_dir, prefix_path, search_name });
943
944 // Check if the file exists.
945 const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
946 error.FileNotFound => return null,
947 else => |e| return e,
948 };
949 defer tmp.close();
950
951 return full_path;
952}
953
954892const ParseDylibError = error{
955893 OutOfMemory,
956894 EmptyStubFile,
src/link/MachO/zld.zig+17-53
......@@ -3396,7 +3396,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
33963396 // We are about to obtain this lock, so here we give other processes a chance first.
33973397 macho_file.base.releaseLock();
33983398
3399 comptime assert(Compilation.link_hash_implementation_version == 9);
3399 comptime assert(Compilation.link_hash_implementation_version == 10);
34003400
34013401 for (options.objects) |obj| {
34023402 _ = try man.addFile(obj.path, null);
......@@ -3417,7 +3417,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
34173417 man.hash.add(options.strip);
34183418 man.hash.addListOfBytes(options.lib_dirs);
34193419 man.hash.addListOfBytes(options.framework_dirs);
3420 link.hashAddFrameworks(&man.hash, options.frameworks);
3420 try link.hashAddFrameworks(&man, options.frameworks);
34213421 man.hash.addListOfBytes(options.rpath_list);
34223422 if (is_dyn_lib) {
34233423 man.hash.addOptionalBytes(options.install_name);
......@@ -3512,9 +3512,6 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
35123512 try zld.atoms.append(gpa, Atom.empty); // AtomIndex at 0 is reserved as null atom
35133513 try zld.strtab.buffer.append(gpa, 0);
35143514
3515 var lib_not_found = false;
3516 var framework_not_found = false;
3517
35183515 // Positional arguments to the linker such as object files and static archives.
35193516 var positionals = std.ArrayList([]const u8).init(arena);
35203517 try positionals.ensureUnusedCapacity(options.objects.len);
......@@ -3557,43 +3554,16 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
35573554 for (vals) |v| libs.putAssumeCapacity(v.path.?, v);
35583555 }
35593556
3560 try MachO.resolveLibSystem(arena, comp, options.sysroot, target, options.lib_dirs, &libs);
3561
3562 // frameworks
3563 var framework_dirs = std.ArrayList([]const u8).init(arena);
3564 for (options.framework_dirs) |dir| {
3565 if (try MachO.resolveSearchDir(arena, dir, options.sysroot)) |search_dir| {
3566 try framework_dirs.append(search_dir);
3567 } else {
3568 log.warn("directory not found for '-F{s}'", .{dir});
3569 }
3570 }
3571
3572 outer: for (options.frameworks.keys()) |f_name| {
3573 for (framework_dirs.items) |dir| {
3574 for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
3575 if (try MachO.resolveFramework(arena, dir, f_name, ext)) |full_path| {
3576 const info = options.frameworks.get(f_name).?;
3577 try libs.put(full_path, .{
3578 .needed = info.needed,
3579 .weak = info.weak,
3580 .path = full_path,
3581 });
3582 continue :outer;
3583 }
3584 }
3585 } else {
3586 log.warn("framework not found for '-framework {s}'", .{f_name});
3587 framework_not_found = true;
3588 }
3557 {
3558 try libs.ensureUnusedCapacity(options.frameworks.len);
3559 for (options.frameworks) |v| libs.putAssumeCapacity(v.path, .{
3560 .needed = v.needed,
3561 .weak = v.weak,
3562 .path = v.path,
3563 });
35893564 }
35903565
3591 if (framework_not_found) {
3592 log.warn("Framework search paths:", .{});
3593 for (framework_dirs.items) |dir| {
3594 log.warn(" {s}", .{dir});
3595 }
3596 }
3566 try MachO.resolveLibSystem(arena, comp, options.sysroot, target, options.lib_dirs, &libs);
35973567
35983568 if (options.verbose_link) {
35993569 var argv = std.ArrayList([]const u8).init(arena);
......@@ -3693,14 +3663,14 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
36933663 try argv.append(try std.fmt.allocPrint(arena, "-L{s}", .{lib_dir}));
36943664 }
36953665
3696 for (options.frameworks.keys()) |framework| {
3697 const info = options.frameworks.get(framework).?;
3698 const arg = if (info.needed)
3699 try std.fmt.allocPrint(arena, "-needed_framework {s}", .{framework})
3700 else if (info.weak)
3701 try std.fmt.allocPrint(arena, "-weak_framework {s}", .{framework})
3666 for (options.frameworks) |framework| {
3667 const name = std.fs.path.stem(framework.path);
3668 const arg = if (framework.needed)
3669 try std.fmt.allocPrint(arena, "-needed_framework {s}", .{name})
3670 else if (framework.weak)
3671 try std.fmt.allocPrint(arena, "-weak_framework {s}", .{name})
37023672 else
3703 try std.fmt.allocPrint(arena, "-framework {s}", .{framework});
3673 try std.fmt.allocPrint(arena, "-framework {s}", .{name});
37043674 try argv.append(arg);
37053675 }
37063676
......@@ -3740,12 +3710,6 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
37403710 if (resolver.unresolved.count() > 0) {
37413711 return error.UndefinedSymbolReference;
37423712 }
3743 if (lib_not_found) {
3744 return error.LibraryNotFound;
3745 }
3746 if (framework_not_found) {
3747 return error.FrameworkNotFound;
3748 }
37493713
37503714 if (options.output_mode == .Exe) {
37513715 const entry_name = options.entry orelse load_commands.default_entry_point;
src/link/Wasm.zig+2-2
......@@ -3193,7 +3193,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
31933193 // We are about to obtain this lock, so here we give other processes a chance first.
31943194 wasm.base.releaseLock();
31953195
3196 comptime assert(Compilation.link_hash_implementation_version == 9);
3196 comptime assert(Compilation.link_hash_implementation_version == 10);
31973197
31983198 for (options.objects) |obj| {
31993199 _ = try man.addFile(obj.path, null);
......@@ -4254,7 +4254,7 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
42544254 // We are about to obtain this lock, so here we give other processes a chance first.
42554255 wasm.base.releaseLock();
42564256
4257 comptime assert(Compilation.link_hash_implementation_version == 9);
4257 comptime assert(Compilation.link_hash_implementation_version == 10);
42584258
42594259 for (wasm.base.options.objects) |obj| {
42604260 _ = try man.addFile(obj.path, null);
src/main.zig+91-2
......@@ -746,6 +746,13 @@ const SystemLib = struct {
746746 }
747747};
748748
749/// Similar to `link.Framework` except it doesn't store yet unresolved
750/// path to the framework.
751const Framework = struct {
752 needed: bool = false,
753 weak: bool = false,
754};
755
749756const CliModule = struct {
750757 mod: *Package,
751758 /// still in CLI arg format
......@@ -919,7 +926,7 @@ fn buildOutputType(
919926 var c_source_files = std.ArrayList(Compilation.CSourceFile).init(arena);
920927 var link_objects = std.ArrayList(Compilation.LinkObject).init(arena);
921928 var framework_dirs = std.ArrayList([]const u8).init(arena);
922 var frameworks: std.StringArrayHashMapUnmanaged(Compilation.Framework) = .{};
929 var frameworks: std.StringArrayHashMapUnmanaged(Framework) = .{};
923930 // null means replace with the test executable binary
924931 var test_exec_args = std.ArrayList(?[]const u8).init(arena);
925932 var linker_export_symbol_names = std.ArrayList([]const u8).init(arena);
......@@ -2868,6 +2875,59 @@ fn buildOutputType(
28682875 }
28692876 // After this point, resolved_system_libs is used instead of external_system_libs.
28702877
2878 // We now repeat part of the process for frameworks.
2879 var resolved_frameworks = std.ArrayList(Compilation.Framework).init(arena);
2880
2881 if (frameworks.keys().len > 0) {
2882 var test_path = std.ArrayList(u8).init(gpa);
2883 defer test_path.deinit();
2884
2885 var checked_paths = std.ArrayList(u8).init(gpa);
2886 defer checked_paths.deinit();
2887
2888 var failed_frameworks = std.ArrayList(struct {
2889 name: []const u8,
2890 checked_paths: []const u8,
2891 }).init(arena);
2892
2893 framework: for (frameworks.keys(), frameworks.values()) |framework_name, info| {
2894 checked_paths.clearRetainingCapacity();
2895
2896 for (framework_dirs.items) |framework_dir_path| {
2897 if (try accessFrameworkPath(
2898 &test_path,
2899 &checked_paths,
2900 framework_dir_path,
2901 framework_name,
2902 )) {
2903 const path = try arena.dupe(u8, test_path.items);
2904 try resolved_frameworks.append(.{
2905 .needed = info.needed,
2906 .weak = info.weak,
2907 .path = path,
2908 });
2909 continue :framework;
2910 }
2911 }
2912
2913 try failed_frameworks.append(.{
2914 .name = framework_name,
2915 .checked_paths = try arena.dupe(u8, checked_paths.items),
2916 });
2917 }
2918
2919 if (failed_frameworks.items.len > 0) {
2920 for (failed_frameworks.items) |f| {
2921 const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths;
2922 std.log.err("unable to find framework '{s}'. searched paths: {s}", .{
2923 f.name, searched_paths,
2924 });
2925 }
2926 process.exit(1);
2927 }
2928 }
2929 // After this point, resolved_frameworks is used instead of frameworks.
2930
28712931 const object_format = target_info.target.ofmt;
28722932
28732933 if (output_mode == .Obj and (object_format == .coff or object_format == .macho)) {
......@@ -3261,7 +3321,7 @@ fn buildOutputType(
32613321 .c_source_files = c_source_files.items,
32623322 .link_objects = link_objects.items,
32633323 .framework_dirs = framework_dirs.items,
3264 .frameworks = frameworks,
3324 .frameworks = resolved_frameworks.items,
32653325 .system_lib_names = resolved_system_libs.items(.name),
32663326 .system_lib_infos = resolved_system_libs.items(.lib),
32673327 .wasi_emulated_libs = wasi_emulated_libs.items,
......@@ -6336,3 +6396,32 @@ fn accessLibPath(
63366396
63376397 return false;
63386398}
6399
6400fn accessFrameworkPath(
6401 test_path: *std.ArrayList(u8),
6402 checked_paths: *std.ArrayList(u8),
6403 framework_dir_path: []const u8,
6404 framework_name: []const u8,
6405) !bool {
6406 const sep = fs.path.sep_str;
6407
6408 for (&[_][]const u8{ "tbd", "dylib" }) |ext| {
6409 test_path.clearRetainingCapacity();
6410 try test_path.writer().print("{s}" ++ sep ++ "{s}.framework" ++ sep ++ "{s}.{s}", .{
6411 framework_dir_path,
6412 framework_name,
6413 framework_name,
6414 ext,
6415 });
6416 try checked_paths.writer().print("\n {s}", .{test_path.items});
6417 fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
6418 error.FileNotFound => continue,
6419 else => |e| fatal("unable to search for {s} framework '{s}': {s}", .{
6420 ext, test_path.items, @errorName(e),
6421 }),
6422 };
6423 return true;
6424 }
6425
6426 return false;
6427}