Commit 92deebcd66

Jakub Konka <kubkon@jakubkonka.com>
2024-02-02 14:05:51
cli+build: handle -ObjC flag and route it to MachO linker
1 parent 9eda6cc
lib/std/Build/Step/Compile.zig
@@ -149,6 +149,9 @@ headerpad_max_install_names: bool = false,
 /// (Darwin) Remove dylibs that are unreachable by the entry point or exported symbols.
 dead_strip_dylibs: bool = false,
 
+/// (Darwin) Force load all members of static archives that implement an Objective-C class or category
+force_load_objc: bool = false,
+
 /// Position Independent Executable
 pie: ?bool = null,
 
@@ -1433,6 +1436,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
     if (self.dead_strip_dylibs) {
         try zig_args.append("-dead_strip_dylibs");
     }
+    if (self.force_load_objc) {
+        try zig_args.append("-ObjC");
+    }
 
     try addFlag(&zig_args, "compiler-rt", self.bundle_compiler_rt);
     try addFlag(&zig_args, "dll-export-fns", self.dll_export_fns);
src/link/Coff/lld.zig
@@ -70,7 +70,7 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, prog_node: *std.Progress.Node)
         man = comp.cache_parent.obtain();
         self.base.releaseLock();
 
-        comptime assert(Compilation.link_hash_implementation_version == 11);
+        comptime assert(Compilation.link_hash_implementation_version == 12);
 
         for (comp.objects) |obj| {
             _ = try man.addFile(obj.path, null);
src/link/Elf.zig
@@ -2380,7 +2380,7 @@ fn linkWithLLD(self: *Elf, arena: Allocator, prog_node: *std.Progress.Node) !voi
         // We are about to obtain this lock, so here we give other processes a chance first.
         self.base.releaseLock();
 
-        comptime assert(Compilation.link_hash_implementation_version == 11);
+        comptime assert(Compilation.link_hash_implementation_version == 12);
 
         try man.addOptionalFile(self.linker_script);
         try man.addOptionalFile(self.version_script);
src/link/MachO.zig
@@ -217,6 +217,7 @@ pub fn createEmpty(
         .undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error",
         .lib_dirs = options.lib_dirs,
         .framework_dirs = options.framework_dirs,
+        .force_load_objc = options.force_load_objc,
     };
     if (use_llvm and comp.config.have_zcu) {
         self.llvm_object = try LlvmObject.create(arena, comp);
@@ -807,6 +808,10 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
             try argv.append("-dead_strip_dylibs");
         }
 
+        if (self.force_load_objc) {
+            try argv.append("-ObjC");
+        }
+
         if (self.entry_name) |entry_name| {
             try argv.appendSlice(&.{ "-e", entry_name });
         }
@@ -1247,7 +1252,7 @@ fn parseDependentDylibs(self: *MachO) !void {
                         try checked_paths.append(rel_path);
                         var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
                         const full_path = std.fs.realpath(rel_path, &buffer) catch continue;
-                        break :full_path full_path;
+                        break :full_path try arena.dupe(u8, full_path);
                     }
                 } else if (eatPrefix(id.name, "@loader_path/")) |_| {
                     try self.reportParseError2(dylib_index, "TODO handle install_name '{s}'", .{id.name});
@@ -1260,7 +1265,7 @@ fn parseDependentDylibs(self: *MachO) !void {
                 try checked_paths.append(try arena.dupe(u8, id.name));
                 var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
                 if (std.fs.realpath(id.name, &buffer)) |full_path| {
-                    break :full_path full_path;
+                    break :full_path try arena.dupe(u8, full_path);
                 } else |_| {
                     try self.reportMissingDependencyError(
                         self.getFile(dylib_index).?.dylib.getUmbrella(self).index,
src/link/Wasm.zig
@@ -3511,7 +3511,7 @@ fn linkWithZld(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) lin
         // We are about to obtain this lock, so here we give other processes a chance first.
         wasm.base.releaseLock();
 
-        comptime assert(Compilation.link_hash_implementation_version == 11);
+        comptime assert(Compilation.link_hash_implementation_version == 12);
 
         for (objects) |obj| {
             _ = try man.addFile(obj.path, null);
@@ -4580,7 +4580,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) !vo
         // We are about to obtain this lock, so here we give other processes a chance first.
         wasm.base.releaseLock();
 
-        comptime assert(Compilation.link_hash_implementation_version == 11);
+        comptime assert(Compilation.link_hash_implementation_version == 12);
 
         for (comp.objects) |obj| {
             _ = try man.addFile(obj.path, null);
src/clang_options_data.zig
@@ -115,7 +115,14 @@ flagpd1("Mach"),
     .pd2 = false,
     .psl = false,
 },
-flagpd1("ObjC"),
+.{
+    .name = "ObjC",
+    .syntax = .flag,
+    .zig_equivalent = .force_load_objc,
+    .pd1 = true,
+    .pd2 = false,
+    .psl = false,
+},
 flagpd1("ObjC++"),
 flagpd1("P"),
 flagpd1("Q"),
src/Compilation.zig
@@ -1113,6 +1113,8 @@ pub const CreateOptions = struct {
     headerpad_max_install_names: bool = false,
     /// (Darwin) remove dylibs that are unreachable by the entry point or exported symbols
     dead_strip_dylibs: bool = false,
+    /// (Darwin) Force load all members of static archives that implement an Objective-C class or category
+    force_load_objc: bool = false,
     libcxx_abi_version: libcxx.AbiVersion = libcxx.AbiVersion.default,
     /// (Windows) PDB source path prefix to instruct the linker how to resolve relative
     /// paths when consolidating CodeView streams into a single PDB file.
@@ -1591,6 +1593,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
             .headerpad_size = options.headerpad_size,
             .headerpad_max_install_names = options.headerpad_max_install_names,
             .dead_strip_dylibs = options.dead_strip_dylibs,
+            .force_load_objc = options.force_load_objc,
             .pdb_source_path = options.pdb_source_path,
             .pdb_out_path = options.pdb_out_path,
             .entry_addr = null, // CLI does not expose this option (yet?)
@@ -2456,7 +2459,7 @@ fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemo
 /// to remind the programmer to update multiple related pieces of code that
 /// are in different locations. Bump this number when adding or deleting
 /// anything from the link cache manifest.
-pub const link_hash_implementation_version = 11;
+pub const link_hash_implementation_version = 12;
 
 fn addNonIncrementalStuffToCacheManifest(
     comp: *Compilation,
@@ -2465,7 +2468,7 @@ fn addNonIncrementalStuffToCacheManifest(
 ) !void {
     const gpa = comp.gpa;
 
-    comptime assert(link_hash_implementation_version == 11);
+    comptime assert(link_hash_implementation_version == 12);
 
     if (comp.module) |mod| {
         try addModuleTableToCacheHash(gpa, arena, &man.hash, mod.root_mod, mod.main_mod, .{ .files = man });
@@ -2589,6 +2592,7 @@ fn addNonIncrementalStuffToCacheManifest(
     man.hash.addOptional(opts.headerpad_size);
     man.hash.add(opts.headerpad_max_install_names);
     man.hash.add(opts.dead_strip_dylibs);
+    man.hash.add(opts.force_load_objc);
 
     // COFF specific stuff
     man.hash.addOptional(opts.subsystem);
src/link.zig
@@ -136,30 +136,36 @@ pub const File = struct {
         framework_dirs: []const []const u8,
         rpath_list: []const []const u8,
 
-        /// (Zig compiler development) Enable dumping of linker's state as JSON.
+        /// Zig compiler development linker flags.
+        /// Enable dumping of linker's state as JSON.
         enable_link_snapshots: bool,
 
-        /// (Darwin) Install name for the dylib
+        /// Darwin-specific linker flags:
+        /// Install name for the dylib
         install_name: ?[]const u8,
-        /// (Darwin) Path to entitlements file
+        /// Path to entitlements file
         entitlements: ?[]const u8,
-        /// (Darwin) size of the __PAGEZERO segment
+        /// size of the __PAGEZERO segment
         pagezero_size: ?u64,
-        /// (Darwin) set minimum space for future expansion of the load commands
+        /// Set minimum space for future expansion of the load commands
         headerpad_size: ?u32,
-        /// (Darwin) set enough space as if all paths were MATPATHLEN
+        /// Set enough space as if all paths were MATPATHLEN
         headerpad_max_install_names: bool,
-        /// (Darwin) remove dylibs that are unreachable by the entry point or exported symbols
+        /// Remove dylibs that are unreachable by the entry point or exported symbols
         dead_strip_dylibs: bool,
         frameworks: []const MachO.Framework,
         darwin_sdk_layout: ?MachO.SdkLayout,
+        /// Force load all members of static archives that implement an
+        /// Objective-C class or category
+        force_load_objc: bool,
 
-        /// (Windows) PDB source path prefix to instruct the linker how to resolve relative
+        /// Windows-specific linker flags:
+        /// PDB source path prefix to instruct the linker how to resolve relative
         /// paths when consolidating CodeView streams into a single PDB file.
         pdb_source_path: ?[]const u8,
-        /// (Windows) PDB output path
+        /// PDB output path
         pdb_out_path: ?[]const u8,
-        /// (Windows) .def file to specify when linking
+        /// .def file to specify when linking
         module_definition_file: ?[]const u8,
 
         pub const Entry = union(enum) {
src/main.zig
@@ -559,6 +559,7 @@ const usage_build_generic =
     \\  -headerpad_max_install_names   (Darwin) set enough space as if all paths were MAXPATHLEN
     \\  -dead_strip                    (Darwin) remove functions and data that are unreachable by the entry point or exported symbols
     \\  -dead_strip_dylibs             (Darwin) remove dylibs that are unreachable by the entry point or exported symbols
+    \\  -ObjC                          (Darwin) force load all members of static archives that implement an Objective-C class or category
     \\  --import-memory                (WebAssembly) import memory from the environment
     \\  --export-memory                (WebAssembly) export memory to the host (Default unless --import-memory used)
     \\  --import-symbols               (WebAssembly) import missing symbols from the host environment
@@ -589,7 +590,7 @@ const usage_build_generic =
     \\  -rpath [path]                  Add directory to the runtime library search path
     \\  -framework [name]              (Darwin) link against framework
     \\  -needed_framework [name]       (Darwin) link against framework (even if unused)
-    \\  -needed_library [lib]          link against system library (even if unused)
+    \\  -needed_library [lib]          (Darwin) link against system library (even if unused)
     \\  -weak_framework [name]         (Darwin) link against framework and mark it and all referenced symbols as weak
     \\  -F[dir]                        (Darwin) add search path for frameworks
     \\  --export=[value]               (WebAssembly) Force a symbol to be exported
@@ -889,6 +890,7 @@ fn buildOutputType(
     var headerpad_size: ?u32 = null;
     var headerpad_max_install_names: bool = false;
     var dead_strip_dylibs: bool = false;
+    var force_load_objc: bool = false;
     var contains_res_file: bool = false;
     var reference_trace: ?u32 = null;
     var pdb_out_path: ?[]const u8 = null;
@@ -1182,6 +1184,8 @@ fn buildOutputType(
                         linker_gc_sections = true;
                     } else if (mem.eql(u8, arg, "-dead_strip_dylibs")) {
                         dead_strip_dylibs = true;
+                    } else if (mem.eql(u8, arg, "-ObjC")) {
+                        force_load_objc = true;
                     } else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) {
                         linker_script = args_iter.nextOrFatal();
                     } else if (mem.eql(u8, arg, "-version-script") or mem.eql(u8, arg, "--version-script")) {
@@ -2061,6 +2065,7 @@ fn buildOutputType(
                     .force_undefined_symbol => {
                         try force_undefined_symbols.put(arena, it.only_arg, {});
                     },
+                    .force_load_objc => force_load_objc = true,
                     .weak_library => try create_module.system_libs.put(arena, it.only_arg, .{
                         .needed = false,
                         .weak = true,
@@ -2167,6 +2172,8 @@ fn buildOutputType(
                     linker_gc_sections = true;
                 } else if (mem.eql(u8, arg, "-dead_strip_dylibs")) {
                     dead_strip_dylibs = true;
+                } else if (mem.eql(u8, arg, "-ObjC")) {
+                    force_load_objc = true;
                 } else if (mem.eql(u8, arg, "--no-undefined")) {
                     linker_z_defs = true;
                 } else if (mem.eql(u8, arg, "--gc-sections")) {
@@ -3246,6 +3253,7 @@ fn buildOutputType(
         .headerpad_size = headerpad_size,
         .headerpad_max_install_names = headerpad_max_install_names,
         .dead_strip_dylibs = dead_strip_dylibs,
+        .force_load_objc = force_load_objc,
         .reference_trace = reference_trace,
         .pdb_out_path = pdb_out_path,
         .error_limit = error_limit,
@@ -6266,6 +6274,7 @@ pub const ClangArgIterator = struct {
         compress_debug_sections,
         install_name,
         undefined,
+        force_load_objc,
     };
 
     const Args = struct {
test/link/macho.zig
@@ -969,19 +969,38 @@ fn testObjc(b: *Build, opts: Options) *Step {
     \\@end
     });
 
-    const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
-    exe.root_module.linkSystemLibrary("a", .{});
-    exe.root_module.linkFramework("Foundation", .{});
-    exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
+    {
+        const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
+        exe.root_module.linkSystemLibrary("a", .{});
+        exe.root_module.linkFramework("Foundation", .{});
+        exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
 
-    const check = exe.checkObject();
-    check.checkInSymtab();
-    check.checkContains("_OBJC_");
-    test_step.dependOn(&check.step);
+        const check = exe.checkObject();
+        check.checkInSymtab();
+        check.checkNotPresent("_OBJC_");
+        test_step.dependOn(&check.step);
 
-    const run = addRunArtifact(exe);
-    run.expectExitCode(0);
-    test_step.dependOn(&run.step);
+        const run = addRunArtifact(exe);
+        run.expectExitCode(0);
+        test_step.dependOn(&run.step);
+    }
+
+    {
+        const exe = addExecutable(b, opts, .{ .name = "main2", .c_source_bytes = "int main() { return 0; }" });
+        exe.root_module.linkSystemLibrary("a", .{});
+        exe.root_module.linkFramework("Foundation", .{});
+        exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
+        exe.force_load_objc = true;
+
+        const check = exe.checkObject();
+        check.checkInSymtab();
+        check.checkContains("_OBJC_");
+        test_step.dependOn(&check.step);
+
+        const run = addRunArtifact(exe);
+        run.expectExitCode(0);
+        test_step.dependOn(&run.step);
+    }
 
     return test_step;
 }
tools/update_clang_options.zig
@@ -528,6 +528,10 @@ const known_options = [_]KnownOpt{
         .name = "x",
         .ident = "x",
     },
+    .{
+        .name = "ObjC",
+        .ident = "force_load_objc",
+    },
 };
 
 const blacklisted_options = [_][]const u8{};