Commit a08137330c

Jakub Konka <kubkon@jakubkonka.com>
2021-12-17 00:18:45
macho: handle -install_name option for dylibs/MachO
The status quo for the `build.zig` build system is preserved in the sense that, if the user does not explicitly override `dylib.setInstallName(...);` in their build script, the default of `@rpath/libname.dylib` applies. However, should they want to override the default behaviour, they can either: 1) unset it with ```dylib.setIntallName(null);``` 2) set it to an explicit string with ```dylib.setInstallName("somename.dylib");``` When it comes to the command line however, the default is not to use `@rpath` for the install name when creating a dylib. The user will now be required to explicitly specify the `@rpath` as part of the desired install name should they choose so like so: 1) with `build-lib` ``` zig build-lib -dynamic foo.zig -install_name @rpath/libfoo.dylib ``` 2) with `cc` ``` zig cc -shared foo.c -o libfoo.dylib -Wl,"-install_name=@rpath/libfoo.dylib" ```
1 parent 9a8fdbe
Changed files (5)
lib/std/build.zig
@@ -1537,6 +1537,9 @@ pub const LibExeObjStep = struct {
     /// Permit read-only relocations in read-only segments. Disallowed by default.
     link_z_notext: bool = false,
 
+    /// (Darwin) Install name for the dylib
+    install_name: ?[]const u8 = null,
+
     /// Position Independent Code
     force_pic: ?bool = null,
 
@@ -2451,6 +2454,16 @@ pub const LibExeObjStep = struct {
                 zig_args.append("--version") catch unreachable;
                 zig_args.append(builder.fmt("{}", .{version})) catch unreachable;
             }
+
+            if (self.target.isDarwin()) {
+                const install_name = self.install_name orelse builder.fmt("@rpath/{s}{s}{s}", .{
+                    self.target.libPrefix(),
+                    self.name,
+                    self.target.dynamicLibSuffix(),
+                });
+                try zig_args.append("-install_name");
+                try zig_args.append(install_name);
+            }
         }
 
         if (self.bundle_compiler_rt) |x| {
src/link/MachO.zig
@@ -479,6 +479,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
         man.hash.addListOfBytes(self.base.options.frameworks);
         man.hash.addListOfBytes(self.base.options.rpath_list);
         if (is_dyn_lib) {
+            man.hash.addOptionalBytes(self.base.options.install_name);
             man.hash.addOptional(self.base.options.version);
         }
         link.hashAddSystemLibs(&man.hash, self.base.options.system_libs);
@@ -811,11 +812,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
                 if (is_dyn_lib) {
                     try argv.append("-dylib");
 
-                    const install_name = try std.fmt.allocPrint(arena, "@rpath/{s}", .{
-                        self.base.options.emit.?.sub_path,
-                    });
-                    try argv.append("-install_name");
-                    try argv.append(install_name);
+                    if (self.base.options.install_name) |install_name| {
+                        try argv.append("-install_name");
+                        try argv.append(install_name);
+                    }
                 }
 
                 if (self.base.options.sysroot) |syslibroot| {
@@ -4336,10 +4336,7 @@ fn populateMissingMetadata(self: *MachO) !void {
 
     if (self.dylib_id_cmd_index == null and self.base.options.output_mode == .Lib) {
         self.dylib_id_cmd_index = @intCast(u16, self.load_commands.items.len);
-        const install_name = try std.fmt.allocPrint(self.base.allocator, "@rpath/{s}", .{
-            self.base.options.emit.?.sub_path,
-        });
-        defer self.base.allocator.free(install_name);
+        const install_name = self.base.options.install_name orelse self.base.options.emit.?.sub_path;
         const current_version = self.base.options.version orelse
             std.builtin.Version{ .major = 1, .minor = 0, .patch = 0 };
         const compat_version = self.base.options.compatibility_version orelse
src/Compilation.zig
@@ -780,6 +780,8 @@ pub const InitOptions = struct {
     enable_link_snapshots: bool = false,
     /// (Darwin) Path and version of the native SDK if detected.
     native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null,
+    /// (Darwin) Install name of the dylib
+    install_name: ?[]const u8 = null,
 };
 
 fn addPackageTableToCacheHash(
@@ -1509,6 +1511,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
             .use_stage1 = use_stage1,
             .enable_link_snapshots = options.enable_link_snapshots,
             .native_darwin_sdk = options.native_darwin_sdk,
+            .install_name = options.install_name,
         });
         errdefer bin_file.destroy();
         comp.* = .{
src/link.zig
@@ -157,6 +157,9 @@ pub const Options = struct {
     /// (Darwin) Path and version of the native SDK if detected.
     native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null,
 
+    /// (Darwin) Install name for the dylib
+    install_name: ?[]const u8 = null,
+
     pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
         return if (options.use_lld) .Obj else options.output_mode;
     }
src/main.zig
@@ -430,6 +430,7 @@ const usage_build_generic =
     \\  --image-base [addr]            Set base address for executable image
     \\  -framework [name]              (Darwin) link against framework
     \\  -F[dir]                        (Darwin) add search path for frameworks
+    \\  -install_name=[value]          (Darwin) add dylib's install name
     \\  --import-memory                (WebAssembly) import memory from the environment
     \\  --initial-memory=[bytes]       (WebAssembly) initial size of the linear memory
     \\  --max-memory=[bytes]           (WebAssembly) maximum size of the linear memory
@@ -668,6 +669,7 @@ fn buildOutputType(
     var wasi_exec_model: ?std.builtin.WasiExecModel = null;
     var enable_link_snapshots: bool = false;
     var native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null;
+    var install_name: ?[]const u8 = 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
@@ -873,6 +875,10 @@ fn buildOutputType(
                         if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg});
                         i += 1;
                         try frameworks.append(args[i]);
+                    } else if (mem.eql(u8, arg, "-install_name")) {
+                        if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg});
+                        i += 1;
+                        install_name = args[i];
                     } else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) {
                         if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg});
                         i += 1;
@@ -1721,6 +1727,12 @@ fn buildOutputType(
                     } else {
                         fatal("unsupported -undefined option '{s}'", .{linker_args.items[i]});
                     }
+                } else if (mem.eql(u8, arg, "-install_name")) {
+                    i += 1;
+                    if (i >= linker_args.items.len) {
+                        fatal("expected linker arg after '{s}'", .{arg});
+                    }
+                    install_name = linker_args.items[i];
                 } else {
                     warn("unsupported linker arg: {s}", .{arg});
                 }
@@ -2495,6 +2507,7 @@ fn buildOutputType(
         .debug_compile_errors = debug_compile_errors,
         .enable_link_snapshots = enable_link_snapshots,
         .native_darwin_sdk = native_darwin_sdk,
+        .install_name = install_name,
     }) catch |err| switch (err) {
         error.LibCUnavailable => {
             const target = target_info.target;