Commit 0df7ed79d3

Jakub Konka <kubkon@jakubkonka.com>
2022-06-24 20:25:16
macho: implement -search_dylibs_first linker option
1 parent d589047
Changed files (5)
lib/std/build.zig
@@ -1586,6 +1586,13 @@ pub const LibExeObjStep = struct {
     /// (Darwin) Size of the pagezero segment.
     pagezero_size: ?u64 = null,
 
+    /// (Darwin) Search strategy for searching system libraries. Either `paths_first` or `dylibs_first`.
+    /// The former lowers to `-search_paths_first` linker option, while the latter to `-search_dylibs_first`
+    /// option.
+    /// By default, if no option is specified, the linker assumes `paths_first` as the default
+    /// search strategy.
+    search_strategy: ?enum { paths_first, dylibs_first } = null,
+
     /// Position Independent Code
     force_pic: ?bool = null,
 
@@ -2650,6 +2657,10 @@ pub const LibExeObjStep = struct {
             const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{pagezero_size});
             try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size });
         }
+        if (self.search_strategy) |strat| switch (strat) {
+            .paths_first => try zig_args.append("-search_paths_first"),
+            .dylibs_first => try zig_args.append("-search_dylibs_first"),
+        };
 
         if (self.bundle_compiler_rt) |x| {
             if (x) {
src/link/MachO.zig
@@ -47,6 +47,11 @@ pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
 
 pub const base_tag: File.Tag = File.Tag.macho;
 
+pub const SearchStrategy = enum {
+    paths_first,
+    dylibs_first,
+};
+
 base: File,
 
 /// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
@@ -784,18 +789,43 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
             }
 
             var libs = std.ArrayList([]const u8).init(arena);
-            for (search_lib_names.items) |lib_name| {
-                // Assume ld64 default: -search_paths_first
-                // Look in each directory for a dylib (stub first), and then for archive
-                // TODO implement alternative: -search_dylibs_first
-                for (&[_][]const u8{ ".tbd", ".dylib", ".a" }) |ext| {
-                    if (try resolveLib(arena, lib_dirs.items, lib_name, ext)) |full_path| {
-                        try libs.append(full_path);
-                        break;
-                    }
-                } else {
-                    log.warn("library not found for '-l{s}'", .{lib_name});
-                    lib_not_found = true;
+
+            // Assume ld64 default -search_paths_first if no strategy specified.
+            const search_strategy = self.base.options.search_strategy orelse .paths_first;
+            outer: for (search_lib_names.items) |lib_name| {
+                switch (search_strategy) {
+                    .paths_first => {
+                        // Look in each directory for a dylib (stub first), and then for archive
+                        for (lib_dirs.items) |dir| {
+                            for (&[_][]const u8{ ".tbd", ".dylib", ".a" }) |ext| {
+                                if (try resolveLib(arena, dir, lib_name, ext)) |full_path| {
+                                    try libs.append(full_path);
+                                    continue :outer;
+                                }
+                            }
+                        } else {
+                            log.warn("library not found for '-l{s}'", .{lib_name});
+                            lib_not_found = true;
+                        }
+                    },
+                    .dylibs_first => {
+                        // First, look for a dylib in each search dir
+                        for (lib_dirs.items) |dir| {
+                            for (&[_][]const u8{ ".tbd", ".dylib" }) |ext| {
+                                if (try resolveLib(arena, dir, lib_name, ext)) |full_path| {
+                                    try libs.append(full_path);
+                                    continue :outer;
+                                }
+                            }
+                        } else for (lib_dirs.items) |dir| {
+                            if (try resolveLib(arena, dir, lib_name, ".a")) |full_path| {
+                                try libs.append(full_path);
+                            } else {
+                                log.warn("library not found for '-l{s}'", .{lib_name});
+                                lib_not_found = true;
+                            }
+                        }
+                    },
                 }
             }
 
@@ -811,19 +841,23 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
             if (self.base.options.sysroot != null) blk: {
                 // Try stub file first. If we hit it, then we're done as the stub file
                 // re-exports every single symbol definition.
-                if (try resolveLib(arena, lib_dirs.items, "System", ".tbd")) |full_path| {
-                    try libs.append(full_path);
-                    libsystem_available = true;
-                    break :blk;
+                for (lib_dirs.items) |dir| {
+                    if (try resolveLib(arena, dir, "System", ".tbd")) |full_path| {
+                        try libs.append(full_path);
+                        libsystem_available = true;
+                        break :blk;
+                    }
                 }
                 // If we didn't hit the stub file, try .dylib next. However, libSystem.dylib
                 // doesn't export libc.dylib which we'll need to resolve subsequently also.
-                if (try resolveLib(arena, lib_dirs.items, "System", ".dylib")) |libsystem_path| {
-                    if (try resolveLib(arena, lib_dirs.items, "c", ".dylib")) |libc_path| {
-                        try libs.append(libsystem_path);
-                        try libs.append(libc_path);
-                        libsystem_available = true;
-                        break :blk;
+                for (lib_dirs.items) |dir| {
+                    if (try resolveLib(arena, dir, "System", ".dylib")) |libsystem_path| {
+                        if (try resolveLib(arena, dir, "c", ".dylib")) |libc_path| {
+                            try libs.append(libsystem_path);
+                            try libs.append(libc_path);
+                            libsystem_available = true;
+                            break :blk;
+                        }
                     }
                 }
             }
@@ -847,11 +881,13 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
                 }
             }
 
-            for (self.base.options.frameworks) |framework| {
-                for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
-                    if (try resolveFramework(arena, framework_dirs.items, framework, ext)) |full_path| {
-                        try libs.append(full_path);
-                        break;
+            outer: for (self.base.options.frameworks) |framework| {
+                for (framework_dirs.items) |dir| {
+                    for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
+                        if (try resolveFramework(arena, dir, framework, ext)) |full_path| {
+                            try libs.append(full_path);
+                            continue :outer;
+                        }
                     }
                 } else {
                     log.warn("framework not found for '-framework {s}'", .{framework});
@@ -934,6 +970,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
                     try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{pagezero_size}));
                 }
 
+                if (self.base.options.search_strategy) |strat| switch (strat) {
+                    .paths_first => try argv.append("-search_paths_first"),
+                    .dylibs_first => try argv.append("-search_dylibs_first"),
+                };
+
                 if (self.base.options.entry) |entry| {
                     try argv.append("-e");
                     try argv.append(entry);
@@ -1183,51 +1224,41 @@ fn resolveSearchDir(
 
 fn resolveLib(
     arena: Allocator,
-    search_dirs: []const []const u8,
+    search_dir: []const u8,
     name: []const u8,
     ext: []const u8,
 ) !?[]const u8 {
     const search_name = try std.fmt.allocPrint(arena, "lib{s}{s}", .{ name, ext });
+    const full_path = try fs.path.join(arena, &[_][]const u8{ search_dir, search_name });
 
-    for (search_dirs) |dir| {
-        const full_path = try fs.path.join(arena, &[_][]const u8{ dir, search_name });
-
-        // Check if the file exists.
-        const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
-            error.FileNotFound => continue,
-            else => |e| return e,
-        };
-        defer tmp.close();
-
-        return full_path;
-    }
+    // 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 null;
+    return full_path;
 }
 
 fn resolveFramework(
     arena: Allocator,
-    search_dirs: []const []const u8,
+    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 });
 
-    for (search_dirs) |dir| {
-        const full_path = try fs.path.join(arena, &[_][]const u8{ dir, prefix_path, search_name });
-
-        // Check if the file exists.
-        const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
-            error.FileNotFound => continue,
-            else => |e| return e,
-        };
-        defer tmp.close();
-
-        return full_path;
-    }
+    // 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 null;
+    return full_path;
 }
 
 fn parseObject(self: *MachO, path: []const u8) !bool {
src/Compilation.zig
@@ -905,6 +905,8 @@ pub const InitOptions = struct {
     entitlements: ?[]const u8 = null,
     /// (Darwin) size of the __PAGEZERO segment
     pagezero_size: ?u64 = null,
+    /// (Darwin) search strategy for system libraries
+    search_strategy: ?link.File.MachO.SearchStrategy = null,
 };
 
 fn addPackageTableToCacheHash(
@@ -1745,6 +1747,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
             .install_name = options.install_name,
             .entitlements = options.entitlements,
             .pagezero_size = options.pagezero_size,
+            .search_strategy = options.search_strategy,
         });
         errdefer bin_file.destroy();
         comp.* = .{
src/link.zig
@@ -190,6 +190,9 @@ pub const Options = struct {
     /// (Darwin) size of the __PAGEZERO segment
     pagezero_size: ?u64 = null,
 
+    /// (Darwin) search strategy for system libraries
+    search_strategy: ?File.MachO.SearchStrategy = null,
+
     pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
         return if (options.use_lld) .Obj else options.output_mode;
     }
src/main.zig
@@ -448,6 +448,8 @@ const usage_build_generic =
     \\  -install_name=[value]          (Darwin) add dylib's install name
     \\  --entitlements [path]          (Darwin) add path to entitlements file for embedding in code signature
     \\  -pagezero_size [value]         (Darwin) size of the __PAGEZERO segment in hexadecimal notation
+    \\  -search_paths_first            (Darwin) search each dir in library search paths for `libx.dylib` then `libx.a`
+    \\  -search_dylibs_first           (Darwin) search `libx.dylib` in each dir in library search paths, then `libx.a`
     \\  --import-memory                (WebAssembly) import memory from the environment
     \\  --import-table                 (WebAssembly) import function table from the host environment
     \\  --export-table                 (WebAssembly) export function table to the host environment
@@ -696,6 +698,7 @@ fn buildOutputType(
     var hash_style: link.HashStyle = .both;
     var entitlements: ?[]const u8 = null;
     var pagezero_size: ?u64 = null;
+    var search_strategy: ?link.File.MachO.SearchStrategy = null;
 
     // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.
     // This array is populated by zig cc frontend and then has to be converted to zig-style
@@ -917,6 +920,10 @@ fn buildOutputType(
                         pagezero_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| {
                             fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) });
                         };
+                    } else if (mem.eql(u8, arg, "-search_paths_first")) {
+                        search_strategy = .paths_first;
+                    } else if (mem.eql(u8, arg, "-search_dylibs_first")) {
+                        search_strategy = .dylibs_first;
                     } else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) {
                         linker_script = args_iter.next() orelse {
                             fatal("expected parameter after {s}", .{arg});
@@ -1476,7 +1483,9 @@ fn buildOutputType(
                             {
                                 force_static_libs = true;
                             } else if (mem.eql(u8, linker_arg, "-search_paths_first")) {
-                                // ignore, since it's the default behavior in both ld64 and zld
+                                search_strategy = .paths_first;
+                            } else if (mem.eql(u8, linker_arg, "-search_dylibs_first")) {
+                                search_strategy = .dylibs_first;
                             } else {
                                 try linker_args.append(linker_arg);
                             }
@@ -2784,6 +2793,7 @@ fn buildOutputType(
         .install_name = install_name,
         .entitlements = entitlements,
         .pagezero_size = pagezero_size,
+        .search_strategy = search_strategy,
     }) catch |err| switch (err) {
         error.LibCUnavailable => {
             const target = target_info.target;