Commit 4793dafa04

Jakub Konka <kubkon@jakubkonka.com>
2023-08-20 10:43:20
frontend: move framework path resolution from the linker to frontend
1 parent e687c87
Changed files (5)
src/link/MachO/zld.zig
@@ -3512,9 +3512,6 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
         try zld.atoms.append(gpa, Atom.empty); // AtomIndex at 0 is reserved as null atom
         try zld.strtab.buffer.append(gpa, 0);
 
-        var lib_not_found = false;
-        var framework_not_found = false;
-
         // Positional arguments to the linker such as object files and static archives.
         var positionals = std.ArrayList([]const u8).init(arena);
         try positionals.ensureUnusedCapacity(options.objects.len);
@@ -3557,34 +3554,17 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
             for (vals) |v| libs.putAssumeCapacity(v.path.?, v);
         }
 
-        try MachO.resolveLibSystem(arena, comp, options.sysroot, target, options.lib_dirs, &libs);
-
-        // frameworks
-        outer: for (options.frameworks.keys()) |f_name| {
-            for (options.framework_dirs) |dir| {
-                for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
-                    if (try MachO.resolveFramework(arena, dir, f_name, ext)) |full_path| {
-                        const info = options.frameworks.get(f_name).?;
-                        try libs.put(full_path, .{
-                            .needed = info.needed,
-                            .weak = info.weak,
-                            .path = full_path,
-                        });
-                        continue :outer;
-                    }
-                }
-            } else {
-                log.warn("framework not found for '-framework {s}'", .{f_name});
-                framework_not_found = true;
-            }
+        {
+            const vals = options.frameworks.values();
+            try libs.ensureUnusedCapacity(vals.len);
+            for (vals) |v| libs.putAssumeCapacity(v.path, .{
+                .needed = v.needed,
+                .weak = v.weak,
+                .path = v.path,
+            });
         }
 
-        if (framework_not_found) {
-            log.warn("Framework search paths:", .{});
-            for (options.framework_dirs) |dir| {
-                log.warn("  {s}", .{dir});
-            }
-        }
+        try MachO.resolveLibSystem(arena, comp, options.sysroot, target, options.lib_dirs, &libs);
 
         if (options.verbose_link) {
             var argv = std.ArrayList([]const u8).init(arena);
@@ -3731,12 +3711,6 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
         if (resolver.unresolved.count() > 0) {
             return error.UndefinedSymbolReference;
         }
-        if (lib_not_found) {
-            return error.LibraryNotFound;
-        }
-        if (framework_not_found) {
-            return error.FrameworkNotFound;
-        }
 
         if (options.output_mode == .Exe) {
             const entry_name = options.entry orelse load_commands.default_entry_point;
src/link/MachO.zig
@@ -870,7 +870,7 @@ fn resolveLibSystemInDirs(arena: Allocator, dirs: []const []const u8, out_libs:
     return false;
 }
 
-pub fn resolveLib(
+fn resolveLib(
     arena: Allocator,
     search_dir: []const u8,
     name: []const u8,
@@ -889,26 +889,6 @@ pub fn resolveLib(
     return full_path;
 }
 
-pub fn resolveFramework(
-    arena: Allocator,
-    search_dir: []const u8,
-    name: []const u8,
-    ext: []const u8,
-) !?[]const u8 {
-    const search_name = try std.fmt.allocPrint(arena, "{s}{s}", .{ name, ext });
-    const prefix_path = try std.fmt.allocPrint(arena, "{s}.framework", .{name});
-    const full_path = try fs.path.join(arena, &[_][]const u8{ search_dir, prefix_path, search_name });
-
-    // Check if the file exists.
-    const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
-        error.FileNotFound => return null,
-        else => |e| return e,
-    };
-    defer tmp.close();
-
-    return full_path;
-}
-
 const ParseDylibError = error{
     OutOfMemory,
     EmptyStubFile,
src/Compilation.zig
@@ -507,7 +507,8 @@ pub const InitOptions = struct {
     c_source_files: []const CSourceFile = &[0]CSourceFile{},
     link_objects: []LinkObject = &[0]LinkObject{},
     framework_dirs: []const []const u8 = &[0][]const u8{},
-    frameworks: std.StringArrayHashMapUnmanaged(Framework) = .{},
+    framework_names: []const []const u8 = &.{},
+    framework_infos: []const Framework = &.{},
     system_lib_names: []const []const u8 = &.{},
     system_lib_infos: []const SystemLib = &.{},
     /// These correspond to the WASI libc emulated subcomponents including:
@@ -830,7 +831,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
             // Our linker can't handle objects or most advanced options yet.
             if (options.link_objects.len != 0 or
                 options.c_source_files.len != 0 or
-                options.frameworks.count() != 0 or
+                options.framework_names.len != 0 or
                 options.system_lib_names.len != 0 or
                 options.link_libc or options.link_libcpp or
                 link_eh_frame_hdr or
@@ -1446,6 +1447,13 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
             system_libs.putAssumeCapacity(lib_name, options.system_lib_infos[i]);
         }
 
+        var frameworks: std.StringArrayHashMapUnmanaged(Framework) = .{};
+        errdefer frameworks.deinit(gpa);
+        try frameworks.ensureTotalCapacity(gpa, options.framework_names.len);
+        for (options.framework_names, options.framework_infos) |framework_name, info| {
+            frameworks.putAssumeCapacity(framework_name, info);
+        }
+
         const bin_file = try link.File.openPath(gpa, .{
             .emit = bin_file_emit,
             .implib_emit = implib_emit,
@@ -1465,7 +1473,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
             .link_libcpp = link_libcpp,
             .link_libunwind = link_libunwind,
             .objects = options.link_objects,
-            .frameworks = options.frameworks,
+            .frameworks = frameworks,
             .framework_dirs = options.framework_dirs,
             .system_libs = system_libs,
             .wasi_emulated_libs = options.wasi_emulated_libs,
src/link.zig
@@ -37,6 +37,7 @@ pub const SystemLib = struct {
 pub const Framework = struct {
     needed: bool = false,
     weak: bool = false,
+    path: []const u8,
 };
 
 pub const SortSection = enum { name, alignment };
src/main.zig
@@ -746,6 +746,13 @@ const SystemLib = struct {
     }
 };
 
+/// Similar to `link.Framework` except it doesn't store yet unresolved
+/// path to the framework.
+const Framework = struct {
+    needed: bool = false,
+    weak: bool = false,
+};
+
 const CliModule = struct {
     mod: *Package,
     /// still in CLI arg format
@@ -919,7 +926,7 @@ fn buildOutputType(
     var c_source_files = std.ArrayList(Compilation.CSourceFile).init(arena);
     var link_objects = std.ArrayList(Compilation.LinkObject).init(arena);
     var framework_dirs = std.ArrayList([]const u8).init(arena);
-    var frameworks: std.StringArrayHashMapUnmanaged(Compilation.Framework) = .{};
+    var frameworks: std.StringArrayHashMapUnmanaged(Framework) = .{};
     // null means replace with the test executable binary
     var test_exec_args = std.ArrayList(?[]const u8).init(arena);
     var linker_export_symbol_names = std.ArrayList([]const u8).init(arena);
@@ -2566,7 +2573,7 @@ fn buildOutputType(
             want_native_include_dirs = true;
     }
 
-    // Resolve the library and framework path arguments with respect to sysroot.
+    // Resolve the library path arguments with respect to sysroot.
     var lib_dirs = std.ArrayList([]const u8).init(arena);
     if (sysroot) |root| {
         for (lib_dir_args.items) |dir| {
@@ -2868,6 +2875,65 @@ fn buildOutputType(
     }
     // After this point, resolved_system_libs is used instead of external_system_libs.
 
+    // We now repeat part of the process for frameworks.
+    var resolved_frameworks: std.MultiArrayList(struct {
+        name: []const u8,
+        framework: Compilation.Framework,
+    }) = .{};
+
+    if (frameworks.keys().len > 0) {
+        var test_path = std.ArrayList(u8).init(gpa);
+        defer test_path.deinit();
+
+        var checked_paths = std.ArrayList(u8).init(gpa);
+        defer checked_paths.deinit();
+
+        var failed_frameworks = std.ArrayList(struct {
+            name: []const u8,
+            checked_paths: []const u8,
+        }).init(arena);
+
+        framework: for (frameworks.keys(), frameworks.values()) |framework_name, info| {
+            checked_paths.clearRetainingCapacity();
+
+            for (framework_dirs.items) |framework_dir_path| {
+                if (try accessFrameworkPath(
+                    &test_path,
+                    &checked_paths,
+                    framework_dir_path,
+                    framework_name,
+                )) {
+                    const path = try arena.dupe(u8, test_path.items);
+                    try resolved_frameworks.append(arena, .{
+                        .name = framework_name,
+                        .framework = .{
+                            .needed = info.needed,
+                            .weak = info.weak,
+                            .path = path,
+                        },
+                    });
+                    continue :framework;
+                }
+            }
+
+            try failed_frameworks.append(.{
+                .name = framework_name,
+                .checked_paths = try arena.dupe(u8, checked_paths.items),
+            });
+        }
+
+        if (failed_frameworks.items.len > 0) {
+            for (failed_frameworks.items) |f| {
+                const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths;
+                std.log.err("unable to find framework '{s}'. searched paths: {s}", .{
+                    f.name, searched_paths,
+                });
+            }
+            process.exit(1);
+        }
+    }
+    // After this point, resolved_frameworks is used instead of frameworks.
+
     const object_format = target_info.target.ofmt;
 
     if (output_mode == .Obj and (object_format == .coff or object_format == .macho)) {
@@ -3261,7 +3327,8 @@ fn buildOutputType(
         .c_source_files = c_source_files.items,
         .link_objects = link_objects.items,
         .framework_dirs = framework_dirs.items,
-        .frameworks = frameworks,
+        .framework_names = resolved_frameworks.items(.name),
+        .framework_infos = resolved_frameworks.items(.framework),
         .system_lib_names = resolved_system_libs.items(.name),
         .system_lib_infos = resolved_system_libs.items(.lib),
         .wasi_emulated_libs = wasi_emulated_libs.items,
@@ -6336,3 +6403,32 @@ fn accessLibPath(
 
     return false;
 }
+
+fn accessFrameworkPath(
+    test_path: *std.ArrayList(u8),
+    checked_paths: *std.ArrayList(u8),
+    framework_dir_path: []const u8,
+    framework_name: []const u8,
+) !bool {
+    const sep = fs.path.sep_str;
+
+    for (&[_][]const u8{ "tbd", "dylib" }) |ext| {
+        test_path.clearRetainingCapacity();
+        try test_path.writer().print("{s}" ++ sep ++ "{s}.framework" ++ sep ++ "{s}.{s}", .{
+            framework_dir_path,
+            framework_name,
+            framework_name,
+            ext,
+        });
+        try checked_paths.writer().print("\n {s}", .{test_path.items});
+        fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
+            error.FileNotFound => continue,
+            else => |e| fatal("unable to search for {s} framework '{s}': {s}", .{
+                ext, test_path.items, @errorName(e),
+            }),
+        };
+        return true;
+    }
+
+    return false;
+}