Commit 3249e5d952

Andrew Kelley <andrew@ziglang.org>
2020-09-30 10:00:06
MachO: add the same workaround for no -r LLD flag support
This is the MachO equivalent for the code added to COFF for doing the file copy when the input and output are both just one object file.
1 parent 2a893ef
Changed files (2)
src/link/MachO.zig
@@ -319,7 +319,6 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
         break :blk full_obj_path;
     } else null;
 
-    const is_obj = self.base.options.output_mode == .Obj;
     const is_lib = self.base.options.output_mode == .Lib;
     const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib;
     const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe;
@@ -391,215 +390,238 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
         };
     }
 
-    // Create an LLD command line and invoke it.
-    var argv = std.ArrayList([]const u8).init(self.base.allocator);
-    defer argv.deinit();
-    // Even though we're calling LLD as a library it thinks the first argument is its own exe name.
-    try argv.append("lld");
-    if (is_obj) {
-        try argv.append("-r");
-    }
+    const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
 
-    try argv.append("-error-limit");
-    try argv.append("0");
+    if (self.base.options.output_mode == .Obj) {
+        // LLD's MachO driver does not support the equvialent of `-r` so we do a simple file copy
+        // here. TODO: think carefully about how we can avoid this redundant operation when doing
+        // build-obj. See also the corresponding TODO in linkAsArchive.
+        const the_object_path = blk: {
+            if (self.base.options.objects.len != 0)
+                break :blk self.base.options.objects[0];
 
-    try argv.append("-demangle");
+            if (comp.c_object_table.count() != 0)
+                break :blk comp.c_object_table.items()[0].key.status.success.object_path;
 
-    if (self.base.options.rdynamic) {
-        try argv.append("--export-dynamic");
-    }
+            if (module_obj_path) |p|
+                break :blk p;
 
-    try argv.appendSlice(self.base.options.extra_lld_args);
+            // TODO I think this is unreachable. Audit this situation when solving the above TODO
+            // regarding eliding redundant object -> object transformations.
+            return error.NoObjectsToLink;
+        };
+        // This can happen when using --enable-cache and using the stage1 backend. In this case
+        // we can skip the file copy.
+        if (!mem.eql(u8, the_object_path, full_out_path)) {
+            try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{});
+        }
+    } else {
+        // Create an LLD command line and invoke it.
+        var argv = std.ArrayList([]const u8).init(self.base.allocator);
+        defer argv.deinit();
+        // Even though we're calling LLD as a library it thinks the first argument is its own exe name.
+        try argv.append("lld");
 
-    if (self.base.options.z_nodelete) {
-        try argv.append("-z");
-        try argv.append("nodelete");
-    }
-    if (self.base.options.z_defs) {
-        try argv.append("-z");
-        try argv.append("defs");
-    }
+        try argv.append("-error-limit");
+        try argv.append("0");
 
-    if (is_dyn_lib) {
-        try argv.append("-static");
-    } else {
-        try argv.append("-dynamic");
-    }
+        try argv.append("-demangle");
 
-    if (is_dyn_lib) {
-        try argv.append("-dylib");
+        if (self.base.options.rdynamic) {
+            try argv.append("--export-dynamic");
+        }
 
-        if (self.base.options.version) |ver| {
-            const compat_vers = try std.fmt.allocPrint(arena, "{d}.0.0", .{ver.major});
-            try argv.append("-compatibility_version");
-            try argv.append(compat_vers);
+        try argv.appendSlice(self.base.options.extra_lld_args);
 
-            const cur_vers = try std.fmt.allocPrint(arena, "{d}.{d}.{d}", .{ ver.major, ver.minor, ver.patch });
-            try argv.append("-current_version");
-            try argv.append(cur_vers);
+        if (self.base.options.z_nodelete) {
+            try argv.append("-z");
+            try argv.append("nodelete");
+        }
+        if (self.base.options.z_defs) {
+            try argv.append("-z");
+            try argv.append("defs");
         }
 
-        // TODO getting an error when running an executable when doing this rpath thing
-        //Buf *dylib_install_name = buf_sprintf("@rpath/lib%s.%" ZIG_PRI_usize ".dylib",
-        //    buf_ptr(g->root_out_name), g->version_major);
-        //try argv.append("-install_name");
-        //try argv.append(buf_ptr(dylib_install_name));
-    }
+        if (is_dyn_lib) {
+            try argv.append("-static");
+        } else {
+            try argv.append("-dynamic");
+        }
 
-    try argv.append("-arch");
-    try argv.append(darwinArchString(target.cpu.arch));
+        if (is_dyn_lib) {
+            try argv.append("-dylib");
 
-    switch (target.os.tag) {
-        .macosx => {
-            try argv.append("-macosx_version_min");
-        },
-        .ios, .tvos, .watchos => switch (target.cpu.arch) {
-            .i386, .x86_64 => {
-                try argv.append("-ios_simulator_version_min");
+            if (self.base.options.version) |ver| {
+                const compat_vers = try std.fmt.allocPrint(arena, "{d}.0.0", .{ver.major});
+                try argv.append("-compatibility_version");
+                try argv.append(compat_vers);
+
+                const cur_vers = try std.fmt.allocPrint(arena, "{d}.{d}.{d}", .{ ver.major, ver.minor, ver.patch });
+                try argv.append("-current_version");
+                try argv.append(cur_vers);
+            }
+
+            // TODO getting an error when running an executable when doing this rpath thing
+            //Buf *dylib_install_name = buf_sprintf("@rpath/lib%s.%" ZIG_PRI_usize ".dylib",
+            //    buf_ptr(g->root_out_name), g->version_major);
+            //try argv.append("-install_name");
+            //try argv.append(buf_ptr(dylib_install_name));
+        }
+
+        try argv.append("-arch");
+        try argv.append(darwinArchString(target.cpu.arch));
+
+        switch (target.os.tag) {
+            .macosx => {
+                try argv.append("-macosx_version_min");
             },
-            else => {
-                try argv.append("-iphoneos_version_min");
+            .ios, .tvos, .watchos => switch (target.cpu.arch) {
+                .i386, .x86_64 => {
+                    try argv.append("-ios_simulator_version_min");
+                },
+                else => {
+                    try argv.append("-iphoneos_version_min");
+                },
             },
-        },
-        else => unreachable,
-    }
-    const ver = target.os.version_range.semver.min;
-    const version_string = try std.fmt.allocPrint(arena, "{d}.{d}.{d}", .{ ver.major, ver.minor, ver.patch });
-    try argv.append(version_string);
+            else => unreachable,
+        }
+        const ver = target.os.version_range.semver.min;
+        const version_string = try std.fmt.allocPrint(arena, "{d}.{d}.{d}", .{ ver.major, ver.minor, ver.patch });
+        try argv.append(version_string);
 
-    try argv.append("-sdk_version");
-    try argv.append(version_string);
+        try argv.append("-sdk_version");
+        try argv.append(version_string);
 
-    if (target_util.requiresPIE(target) and self.base.options.output_mode == .Exe) {
-        try argv.append("-pie");
-    }
+        if (target_util.requiresPIE(target) and self.base.options.output_mode == .Exe) {
+            try argv.append("-pie");
+        }
 
-    const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
-    try argv.append("-o");
-    try argv.append(full_out_path);
-
-    // rpaths
-    var rpath_table = std.StringHashMap(void).init(self.base.allocator);
-    defer rpath_table.deinit();
-    for (self.base.options.rpath_list) |rpath| {
-        if ((try rpath_table.fetchPut(rpath, {})) == null) {
-            try argv.append("-rpath");
-            try argv.append(rpath);
+        try argv.append("-o");
+        try argv.append(full_out_path);
+
+        // rpaths
+        var rpath_table = std.StringHashMap(void).init(self.base.allocator);
+        defer rpath_table.deinit();
+        for (self.base.options.rpath_list) |rpath| {
+            if ((try rpath_table.fetchPut(rpath, {})) == null) {
+                try argv.append("-rpath");
+                try argv.append(rpath);
+            }
         }
-    }
-    if (is_dyn_lib) {
-        if ((try rpath_table.fetchPut(full_out_path, {})) == null) {
-            try argv.append("-rpath");
-            try argv.append(full_out_path);
+        if (is_dyn_lib) {
+            if ((try rpath_table.fetchPut(full_out_path, {})) == null) {
+                try argv.append("-rpath");
+                try argv.append(full_out_path);
+            }
         }
-    }
 
-    for (self.base.options.lib_dirs) |lib_dir| {
-        try argv.append("-L");
-        try argv.append(lib_dir);
-    }
+        for (self.base.options.lib_dirs) |lib_dir| {
+            try argv.append("-L");
+            try argv.append(lib_dir);
+        }
 
-    // Positional arguments to the linker such as object files.
-    try argv.appendSlice(self.base.options.objects);
+        // Positional arguments to the linker such as object files.
+        try argv.appendSlice(self.base.options.objects);
 
-    for (comp.c_object_table.items()) |entry| {
-        try argv.append(entry.key.status.success.object_path);
-    }
-    if (module_obj_path) |p| {
-        try argv.append(p);
-    }
+        for (comp.c_object_table.items()) |entry| {
+            try argv.append(entry.key.status.success.object_path);
+        }
+        if (module_obj_path) |p| {
+            try argv.append(p);
+        }
 
-    // compiler_rt on darwin is missing some stuff, so we still build it and rely on LinkOnce
-    if (is_exe_or_dyn_lib and !self.base.options.is_compiler_rt_or_libc) {
-        try argv.append(comp.compiler_rt_static_lib.?.full_object_path);
-    }
+        // compiler_rt on darwin is missing some stuff, so we still build it and rely on LinkOnce
+        if (is_exe_or_dyn_lib and !self.base.options.is_compiler_rt_or_libc) {
+            try argv.append(comp.compiler_rt_static_lib.?.full_object_path);
+        }
 
-    // Shared libraries.
-    const system_libs = self.base.options.system_libs.items();
-    try argv.ensureCapacity(argv.items.len + system_libs.len);
-    for (system_libs) |entry| {
-        const link_lib = entry.key;
-        // By this time, we depend on these libs being dynamically linked libraries and not static libraries
-        // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which
-        // case we want to avoid prepending "-l".
-        const ext = Compilation.classifyFileExt(link_lib);
-        const arg = if (ext == .shared_library) link_lib else try std.fmt.allocPrint(arena, "-l{}", .{link_lib});
-        argv.appendAssumeCapacity(arg);
-    }
+        // Shared libraries.
+        const system_libs = self.base.options.system_libs.items();
+        try argv.ensureCapacity(argv.items.len + system_libs.len);
+        for (system_libs) |entry| {
+            const link_lib = entry.key;
+            // By this time, we depend on these libs being dynamically linked libraries and not static libraries
+            // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which
+            // case we want to avoid prepending "-l".
+            const ext = Compilation.classifyFileExt(link_lib);
+            const arg = if (ext == .shared_library) link_lib else try std.fmt.allocPrint(arena, "-l{}", .{link_lib});
+            argv.appendAssumeCapacity(arg);
+        }
 
-    // libc++ dep
-    if (!is_obj and self.base.options.link_libcpp) {
-        try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
-        try argv.append(comp.libcxx_static_lib.?.full_object_path);
-    }
+        // libc++ dep
+        if (self.base.options.link_libcpp) {
+            try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
+            try argv.append(comp.libcxx_static_lib.?.full_object_path);
+        }
 
-    // On Darwin, libSystem has libc in it, but also you have to use it
-    // to make syscalls because the syscall numbers are not documented
-    // and change between versions. So we always link against libSystem.
-    // LLD craps out if you do -lSystem cross compiling, so until that
-    // codebase gets some love from the new maintainers we're left with
-    // this dirty hack.
-    if (self.base.options.is_native_os) {
-        try argv.append("-lSystem");
-    }
+        // On Darwin, libSystem has libc in it, but also you have to use it
+        // to make syscalls because the syscall numbers are not documented
+        // and change between versions. So we always link against libSystem.
+        // LLD craps out if you do -lSystem cross compiling, so until that
+        // codebase gets some love from the new maintainers we're left with
+        // this dirty hack.
+        if (self.base.options.is_native_os) {
+            try argv.append("-lSystem");
+        }
 
-    for (self.base.options.framework_dirs) |framework_dir| {
-        try argv.append("-F");
-        try argv.append(framework_dir);
-    }
-    for (self.base.options.frameworks) |framework| {
-        try argv.append("-framework");
-        try argv.append(framework);
-    }
+        for (self.base.options.framework_dirs) |framework_dir| {
+            try argv.append("-F");
+            try argv.append(framework_dir);
+        }
+        for (self.base.options.frameworks) |framework| {
+            try argv.append("-framework");
+            try argv.append(framework);
+        }
 
-    if (allow_shlib_undefined) {
-        try argv.append("-undefined");
-        try argv.append("dynamic_lookup");
-    }
-    if (self.base.options.bind_global_refs_locally) {
-        try argv.append("-Bsymbolic");
-    }
+        if (allow_shlib_undefined) {
+            try argv.append("-undefined");
+            try argv.append("dynamic_lookup");
+        }
+        if (self.base.options.bind_global_refs_locally) {
+            try argv.append("-Bsymbolic");
+        }
 
-    if (self.base.options.verbose_link) {
-        Compilation.dump_argv(argv.items);
-    }
+        if (self.base.options.verbose_link) {
+            Compilation.dump_argv(argv.items);
+        }
 
-    const new_argv = try arena.allocSentinel(?[*:0]const u8, argv.items.len, null);
-    for (argv.items) |arg, i| {
-        new_argv[i] = try arena.dupeZ(u8, arg);
-    }
+        const new_argv = try arena.allocSentinel(?[*:0]const u8, argv.items.len, null);
+        for (argv.items) |arg, i| {
+            new_argv[i] = try arena.dupeZ(u8, arg);
+        }
 
-    var stderr_context: LLDContext = .{
-        .macho = self,
-        .data = std.ArrayList(u8).init(self.base.allocator),
-    };
-    defer stderr_context.data.deinit();
-    var stdout_context: LLDContext = .{
-        .macho = self,
-        .data = std.ArrayList(u8).init(self.base.allocator),
-    };
-    defer stdout_context.data.deinit();
-    const llvm = @import("../llvm.zig");
-    const ok = llvm.Link(
-        .MachO,
-        new_argv.ptr,
-        new_argv.len,
-        append_diagnostic,
-        @ptrToInt(&stdout_context),
-        @ptrToInt(&stderr_context),
-    );
-    if (stderr_context.oom or stdout_context.oom) return error.OutOfMemory;
-    if (stdout_context.data.items.len != 0) {
-        std.log.warn("unexpected LLD stdout: {}", .{stdout_context.data.items});
-    }
-    if (!ok) {
-        // TODO parse this output and surface with the Compilation API rather than
-        // directly outputting to stderr here.
-        std.debug.print("{}", .{stderr_context.data.items});
-        return error.LLDReportedFailure;
-    }
-    if (stderr_context.data.items.len != 0) {
-        std.log.warn("unexpected LLD stderr: {}", .{stderr_context.data.items});
+        var stderr_context: LLDContext = .{
+            .macho = self,
+            .data = std.ArrayList(u8).init(self.base.allocator),
+        };
+        defer stderr_context.data.deinit();
+        var stdout_context: LLDContext = .{
+            .macho = self,
+            .data = std.ArrayList(u8).init(self.base.allocator),
+        };
+        defer stdout_context.data.deinit();
+        const llvm = @import("../llvm.zig");
+        const ok = llvm.Link(
+            .MachO,
+            new_argv.ptr,
+            new_argv.len,
+            append_diagnostic,
+            @ptrToInt(&stdout_context),
+            @ptrToInt(&stderr_context),
+        );
+        if (stderr_context.oom or stdout_context.oom) return error.OutOfMemory;
+        if (stdout_context.data.items.len != 0) {
+            std.log.warn("unexpected LLD stdout: {}", .{stdout_context.data.items});
+        }
+        if (!ok) {
+            // TODO parse this output and surface with the Compilation API rather than
+            // directly outputting to stderr here.
+            std.debug.print("{}", .{stderr_context.data.items});
+            return error.LLDReportedFailure;
+        }
+        if (stderr_context.data.items.len != 0) {
+            std.log.warn("unexpected LLD stderr: {}", .{stderr_context.data.items});
+        }
     }
 
     if (!self.base.options.disable_lld_caching) {
src/main.zig
@@ -1337,12 +1337,12 @@ fn buildOutputType(
         }
     };
 
-    if (output_mode == .Obj and object_format == .coff) {
+    if (output_mode == .Obj and (object_format == .coff or object_format == .macho)) {
         const total_obj_count = c_source_files.items.len +
             @boolToInt(root_src_file != null) +
             link_objects.items.len;
         if (total_obj_count > 1) {
-            fatal("COFF does not support linking multiple objects into one", .{});
+            fatal("{s} does not support linking multiple objects into one", .{@tagName(object_format)});
         }
     }