Commit abeb0e3ea4

Jakub Konka <kubkon@jakubkonka.com>
2024-01-15 10:58:40
test/link/macho: test force-loading objects containing ObjC from archives
1 parent 7c65f0b
Changed files (3)
src
test
src/link/MachO.zig
@@ -116,6 +116,9 @@ platform: Platform,
 sdk_version: ?std.SemanticVersion,
 /// When set to true, the linker will hoist all dylibs including system dependent dylibs.
 no_implicit_dylibs: bool = false,
+/// Whether the linker should parse and always force load objects containing ObjC in archives.
+// TODO: in Zig we currently take -ObjC as always on
+force_load_objc: bool = true,
 
 /// Hot-code swapping state.
 hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},
@@ -998,7 +1001,7 @@ fn parseArchive(self: *MachO, lib: SystemLib, must_link: bool, fat_arch: ?fat.Ar
 
         // Finally, we do a post-parse check for -ObjC to see if we need to force load this member
         // anyhow.
-        // TODO: object.alive = object.alive or (self.options.force_load_objc and object.hasObjc());
+        object.alive = object.alive or (self.force_load_objc and object.hasObjc());
     }
     if (has_parse_error) return error.MalformedArchive;
 }
test/link/link.zig
@@ -46,121 +46,66 @@ const OverlayOptions = struct {
     c_source_flags: []const []const u8 = &.{},
     cpp_source_bytes: ?[]const u8 = null,
     cpp_source_flags: []const []const u8 = &.{},
+    objc_source_bytes: ?[]const u8 = null,
+    objc_source_flags: []const []const u8 = &.{},
     zig_source_bytes: ?[]const u8 = null,
     pic: ?bool = null,
     strip: ?bool = null,
 };
 
-pub fn addExecutable(b: *std.Build, base: Options, overlay: OverlayOptions) *Step.Compile {
-    const compile_step = b.addExecutable(.{
-        .name = overlay.name,
-        .root_source_file = rsf: {
-            const bytes = overlay.zig_source_bytes orelse break :rsf null;
-            break :rsf b.addWriteFiles().add("a.zig", bytes);
-        },
-        .target = base.target,
-        .optimize = base.optimize,
-        .use_llvm = base.use_llvm,
-        .use_lld = base.use_lld,
-        .pic = overlay.pic,
-        .strip = overlay.strip,
-    });
-    if (overlay.cpp_source_bytes) |bytes| {
-        compile_step.addCSourceFile(.{
-            .file = b.addWriteFiles().add("a.cpp", bytes),
-            .flags = overlay.cpp_source_flags,
-        });
-    }
-    if (overlay.c_source_bytes) |bytes| {
-        compile_step.addCSourceFile(.{
-            .file = b.addWriteFiles().add("a.c", bytes),
-            .flags = overlay.c_source_flags,
-        });
-    }
-    if (overlay.asm_source_bytes) |bytes| {
-        compile_step.addAssemblyFile(b.addWriteFiles().add("a.s", bytes));
-    }
-    return compile_step;
+pub fn addExecutable(b: *std.Build, base: Options, overlay: OverlayOptions) *Compile {
+    return addCompileStep(b, base, overlay, .exe);
 }
 
-pub fn addObject(b: *Build, base: Options, overlay: OverlayOptions) *Step.Compile {
-    const compile_step = b.addObject(.{
-        .name = overlay.name,
-        .root_source_file = rsf: {
-            const bytes = overlay.zig_source_bytes orelse break :rsf null;
-            break :rsf b.addWriteFiles().add("a.zig", bytes);
-        },
-        .target = base.target,
-        .optimize = base.optimize,
-        .use_llvm = base.use_llvm,
-        .use_lld = base.use_lld,
-        .pic = overlay.pic,
-        .strip = overlay.strip,
-    });
-    if (overlay.cpp_source_bytes) |bytes| {
-        compile_step.addCSourceFile(.{
-            .file = b.addWriteFiles().add("a.cpp", bytes),
-            .flags = overlay.cpp_source_flags,
-        });
-    }
-    if (overlay.c_source_bytes) |bytes| {
-        compile_step.addCSourceFile(.{
-            .file = b.addWriteFiles().add("a.c", bytes),
-            .flags = overlay.c_source_flags,
-        });
-    }
-    if (overlay.asm_source_bytes) |bytes| {
-        compile_step.addAssemblyFile(b.addWriteFiles().add("a.s", bytes));
-    }
-    return compile_step;
+pub fn addObject(b: *Build, base: Options, overlay: OverlayOptions) *Compile {
+    return addCompileStep(b, base, overlay, .obj);
 }
 
 pub fn addStaticLibrary(b: *Build, base: Options, overlay: OverlayOptions) *Compile {
-    const compile_step = b.addStaticLibrary(.{
-        .name = overlay.name,
-        .root_source_file = rsf: {
-            const bytes = overlay.zig_source_bytes orelse break :rsf null;
-            break :rsf b.addWriteFiles().add("a.zig", bytes);
-        },
-        .target = base.target,
-        .optimize = base.optimize,
-        .use_llvm = base.use_llvm,
-        .use_lld = base.use_lld,
-        .pic = overlay.pic,
-        .strip = overlay.strip,
-    });
-    if (overlay.cpp_source_bytes) |bytes| {
-        compile_step.addCSourceFile(.{
-            .file = b.addWriteFiles().add("a.cpp", bytes),
-            .flags = overlay.cpp_source_flags,
-        });
-    }
-    if (overlay.c_source_bytes) |bytes| {
-        compile_step.addCSourceFile(.{
-            .file = b.addWriteFiles().add("a.c", bytes),
-            .flags = overlay.c_source_flags,
-        });
-    }
-    if (overlay.asm_source_bytes) |bytes| {
-        compile_step.addAssemblyFile(b.addWriteFiles().add("a.s", bytes));
-    }
-    return compile_step;
+    return addCompileStep(b, base, overlay, .static_lib);
 }
 
 pub fn addSharedLibrary(b: *Build, base: Options, overlay: OverlayOptions) *Compile {
-    const compile_step = b.addSharedLibrary(.{
+    return addCompileStep(b, base, overlay, .shared_lib);
+}
+
+fn addCompileStep(
+    b: *Build,
+    base: Options,
+    overlay: OverlayOptions,
+    kind: enum { exe, obj, shared_lib, static_lib },
+) *Compile {
+    const compile_step = Compile.create(b, .{
         .name = overlay.name,
-        .root_source_file = rsf: {
-            const bytes = overlay.zig_source_bytes orelse break :rsf null;
-            break :rsf b.addWriteFiles().add("a.zig", bytes);
+        .root_module = .{
+            .target = base.target,
+            .optimize = base.optimize,
+            .root_source_file = rsf: {
+                const bytes = overlay.zig_source_bytes orelse break :rsf null;
+                break :rsf b.addWriteFiles().add("a.zig", bytes);
+            },
+            .pic = overlay.pic,
+            .strip = overlay.strip,
         },
-        .target = base.target,
-        .optimize = base.optimize,
         .use_llvm = base.use_llvm,
         .use_lld = base.use_lld,
-        .pic = overlay.pic,
-        .strip = overlay.strip,
+        .kind = switch (kind) {
+            .exe => .exe,
+            .obj => .obj,
+            .shared_lib, .static_lib => .lib,
+        },
+        .linkage = switch (kind) {
+            .exe, .obj => null,
+            .shared_lib => .dynamic,
+            .static_lib => .static,
+        },
     });
+    if (overlay.objc_source_bytes) |bytes| {
+        compile_step.addCSourceFile(.{
+            .file = b.addWriteFiles().add("a.m", bytes),
+            .flags = overlay.objc_source_flags,
+        });
+    }
     if (overlay.cpp_source_bytes) |bytes| {
         compile_step.addCSourceFile(.{
             .file = b.addWriteFiles().add("a.cpp", bytes),
test/link/macho.zig
@@ -52,6 +52,7 @@ pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
             macho_step.dependOn(testDeadStripDylibs(b, .{ .target = b.host }));
             macho_step.dependOn(testHeaderpad(b, .{ .target = b.host }));
             macho_step.dependOn(testNeededFramework(b, .{ .target = b.host }));
+            macho_step.dependOn(testObjc(b, .{ .target = b.host }));
             macho_step.dependOn(testWeakFramework(b, .{ .target = b.host }));
         }
     }
@@ -830,6 +831,34 @@ fn testNeededLibrary(b: *Build, opts: Options) *Step {
     return test_step;
 }
 
+fn testObjc(b: *Build, opts: Options) *Step {
+    const test_step = addTestStep(b, "macho-objc", opts);
+
+    const lib = addStaticLibrary(b, opts, .{ .name = "a", .objc_source_bytes = 
+    \\#import <Foundation/Foundation.h>
+    \\@interface Foo : NSObject
+    \\@end
+    \\@implementation Foo
+    \\@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.addLibraryPath(lib.getEmittedBinDirectory());
+
+    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;
+}
+
 fn testRelocatable(b: *Build, opts: Options) *Step {
     const test_step = addTestStep(b, "macho-relocatable", opts);