authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-27 19:48:10+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-27 19:53:38+02:00
log0dd28920daa5127ffe5a3691343fa519f7547cfd
tree949f6078a42f12415508667377b40ea0e84eac0b
parentefc5c97bff87d4c28ae9642fe69d9bc2c7e9eeb7

macho: implement and handle `-needed-*` and `-needed_*` family of flags

MachO linker now handles `-needed-l<name>`, `-needed_library=<name>` and `-needed_framework=<name>`. While on macOS `-l` is equivalent to `-needed-l`, and `-framework` to `-needed_framework`, it can be used to the same effect as on Linux if combined with `-dead_strip_dylibs`. This commit also adds handling for `-needed_library` which is macOS specific flag only (in addition to `-needed-l`). Finally, in order to leverage new linker testing harness, this commit added ability to specify lowering to those flags via `build.zig`: `linkSystemLibraryNeeded` (and related), and `linkFrameworkNeeded`.

15 files changed, 227 insertions(+), 56 deletions(-)

lib/std/build.zig+61-11
......@@ -11,7 +11,6 @@ const ArrayList = std.ArrayList;
1111const StringHashMap = std.StringHashMap;
1212const Allocator = mem.Allocator;
1313const process = std.process;
14const BufSet = std.BufSet;
1514const EnvMap = std.process.EnvMap;
1615const fmt_lib = std.fmt;
1716const File = std.fs.File;
......@@ -1484,7 +1483,7 @@ pub const LibExeObjStep = struct {
14841483 lib_paths: ArrayList([]const u8),
14851484 rpaths: ArrayList([]const u8),
14861485 framework_dirs: ArrayList([]const u8),
1487 frameworks: BufSet,
1486 frameworks: StringHashMap(bool),
14881487 verbose_link: bool,
14891488 verbose_cc: bool,
14901489 emit_analysis: EmitOption = .default,
......@@ -1643,6 +1642,7 @@ pub const LibExeObjStep = struct {
16431642
16441643 pub const SystemLib = struct {
16451644 name: []const u8,
1645 needed: bool,
16461646 use_pkg_config: enum {
16471647 /// Don't use pkg-config, just pass -lfoo where foo is name.
16481648 no,
......@@ -1744,7 +1744,7 @@ pub const LibExeObjStep = struct {
17441744 .kind = kind,
17451745 .root_src = root_src,
17461746 .name = name,
1747 .frameworks = BufSet.init(builder.allocator),
1747 .frameworks = StringHashMap(bool).init(builder.allocator),
17481748 .step = Step.init(base_id, name, builder.allocator, make),
17491749 .version = ver,
17501750 .out_filename = undefined,
......@@ -1893,8 +1893,11 @@ pub const LibExeObjStep = struct {
18931893 }
18941894
18951895 pub fn linkFramework(self: *LibExeObjStep, framework_name: []const u8) void {
1896 // Note: No need to dupe because frameworks dupes internally.
1897 self.frameworks.insert(framework_name) catch unreachable;
1896 self.frameworks.put(self.builder.dupe(framework_name), false) catch unreachable;
1897 }
1898
1899 pub fn linkFrameworkNeeded(self: *LibExeObjStep, framework_name: []const u8) void {
1900 self.frameworks.put(self.builder.dupe(framework_name), true) catch unreachable;
18981901 }
18991902
19001903 /// Returns whether the library, executable, or object depends on a particular system library.
......@@ -1935,6 +1938,7 @@ pub const LibExeObjStep = struct {
19351938 self.link_objects.append(.{
19361939 .system_lib = .{
19371940 .name = "c",
1941 .needed = false,
19381942 .use_pkg_config = .no,
19391943 },
19401944 }) catch unreachable;
......@@ -1947,6 +1951,7 @@ pub const LibExeObjStep = struct {
19471951 self.link_objects.append(.{
19481952 .system_lib = .{
19491953 .name = "c++",
1954 .needed = false,
19501955 .use_pkg_config = .no,
19511956 },
19521957 }) catch unreachable;
......@@ -1971,6 +1976,19 @@ pub const LibExeObjStep = struct {
19711976 self.link_objects.append(.{
19721977 .system_lib = .{
19731978 .name = self.builder.dupe(name),
1979 .needed = false,
1980 .use_pkg_config = .no,
1981 },
1982 }) catch unreachable;
1983 }
1984
1985 /// This one has no integration with anything, it just puts -needed-lname on the command line.
1986 /// Prefer to use `linkSystemLibraryNeeded` instead.
1987 pub fn linkSystemLibraryNeededName(self: *LibExeObjStep, name: []const u8) void {
1988 self.link_objects.append(.{
1989 .system_lib = .{
1990 .name = self.builder.dupe(name),
1991 .needed = true,
19741992 .use_pkg_config = .no,
19751993 },
19761994 }) catch unreachable;
......@@ -1982,6 +2000,19 @@ pub const LibExeObjStep = struct {
19822000 self.link_objects.append(.{
19832001 .system_lib = .{
19842002 .name = self.builder.dupe(lib_name),
2003 .needed = false,
2004 .use_pkg_config = .force,
2005 },
2006 }) catch unreachable;
2007 }
2008
2009 /// This links against a system library, exclusively using pkg-config to find the library.
2010 /// Prefer to use `linkSystemLibraryNeeded` instead.
2011 pub fn linkSystemLibraryNeededPkgConfigOnly(self: *LibExeObjStep, lib_name: []const u8) void {
2012 self.link_objects.append(.{
2013 .system_lib = .{
2014 .name = self.builder.dupe(lib_name),
2015 .needed = true,
19852016 .use_pkg_config = .force,
19862017 },
19872018 }) catch unreachable;
......@@ -2084,6 +2115,14 @@ pub const LibExeObjStep = struct {
20842115 }
20852116
20862117 pub fn linkSystemLibrary(self: *LibExeObjStep, name: []const u8) void {
2118 self.linkSystemLibraryInner(name, false);
2119 }
2120
2121 pub fn linkSystemLibraryNeeded(self: *LibExeObjStep, name: []const u8) void {
2122 self.linkSystemLibraryInner(name, true);
2123 }
2124
2125 fn linkSystemLibraryInner(self: *LibExeObjStep, name: []const u8, needed: bool) void {
20872126 if (isLibCLibrary(name)) {
20882127 self.linkLibC();
20892128 return;
......@@ -2096,6 +2135,7 @@ pub const LibExeObjStep = struct {
20962135 self.link_objects.append(.{
20972136 .system_lib = .{
20982137 .name = self.builder.dupe(name),
2138 .needed = needed,
20992139 .use_pkg_config = .yes,
21002140 },
21012141 }) catch unreachable;
......@@ -2437,7 +2477,7 @@ pub const LibExeObjStep = struct {
24372477 if (!other.isDynamicLibrary()) {
24382478 var it = other.frameworks.iterator();
24392479 while (it.next()) |framework| {
2440 self.frameworks.insert(framework.*) catch unreachable;
2480 self.frameworks.put(framework.key_ptr.*, framework.value_ptr.*) catch unreachable;
24412481 }
24422482 }
24432483 },
......@@ -2473,8 +2513,9 @@ pub const LibExeObjStep = struct {
24732513 },
24742514
24752515 .system_lib => |system_lib| {
2516 const prefix: []const u8 = if (system_lib.needed) "-needed-l" else "-l";
24762517 switch (system_lib.use_pkg_config) {
2477 .no => try zig_args.append(builder.fmt("-l{s}", .{system_lib.name})),
2518 .no => try zig_args.append(builder.fmt("{s}{s}", .{ prefix, system_lib.name })),
24782519 .yes, .force => {
24792520 if (self.runPkgConfig(system_lib.name)) |args| {
24802521 try zig_args.appendSlice(args);
......@@ -2488,7 +2529,10 @@ pub const LibExeObjStep = struct {
24882529 .yes => {
24892530 // pkg-config failed, so fall back to linking the library
24902531 // by name directly.
2491 try zig_args.append(builder.fmt("-l{s}", .{system_lib.name}));
2532 try zig_args.append(builder.fmt("{s}{s}", .{
2533 prefix,
2534 system_lib.name,
2535 }));
24922536 },
24932537 .force => {
24942538 panic("pkg-config failed for library {s}", .{system_lib.name});
......@@ -2972,9 +3016,15 @@ pub const LibExeObjStep = struct {
29723016 }
29733017
29743018 var it = self.frameworks.iterator();
2975 while (it.next()) |framework| {
2976 zig_args.append("-framework") catch unreachable;
2977 zig_args.append(framework.*) catch unreachable;
3019 while (it.next()) |entry| {
3020 const name = entry.key_ptr.*;
3021 const needed = entry.value_ptr.*;
3022 if (needed) {
3023 zig_args.append("-needed_framework") catch unreachable;
3024 } else {
3025 zig_args.append("-framework") catch unreachable;
3026 }
3027 zig_args.append(name) catch unreachable;
29783028 }
29793029 } else {
29803030 if (self.framework_dirs.items.len > 0) {
src/Compilation.zig+4-4
......@@ -791,7 +791,7 @@ pub const InitOptions = struct {
791791 c_source_files: []const CSourceFile = &[0]CSourceFile{},
792792 link_objects: []LinkObject = &[0]LinkObject{},
793793 framework_dirs: []const []const u8 = &[0][]const u8{},
794 frameworks: []const []const u8 = &[0][]const u8{},
794 frameworks: std.StringArrayHashMapUnmanaged(SystemLib) = .{},
795795 system_lib_names: []const []const u8 = &.{},
796796 system_lib_infos: []const SystemLib = &.{},
797797 /// These correspond to the WASI libc emulated subcomponents including:
......@@ -1097,7 +1097,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
10971097 // Our linker can't handle objects or most advanced options yet.
10981098 if (options.link_objects.len != 0 or
10991099 options.c_source_files.len != 0 or
1100 options.frameworks.len != 0 or
1100 options.frameworks.count() != 0 or
11011101 options.system_lib_names.len != 0 or
11021102 options.link_libc or options.link_libcpp or
11031103 link_eh_frame_hdr or
......@@ -1215,7 +1215,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
12151215 options.target,
12161216 options.is_native_abi,
12171217 link_libc,
1218 options.system_lib_names.len != 0 or options.frameworks.len != 0,
1218 options.system_lib_names.len != 0 or options.frameworks.count() != 0,
12191219 options.libc_installation,
12201220 options.native_darwin_sdk != null,
12211221 );
......@@ -2485,7 +2485,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
24852485
24862486 // Mach-O specific stuff
24872487 man.hash.addListOfBytes(comp.bin_file.options.framework_dirs);
2488 man.hash.addListOfBytes(comp.bin_file.options.frameworks);
2488 link.hashAddSystemLibs(&man.hash, comp.bin_file.options.frameworks);
24892489 try man.addOptionalFile(comp.bin_file.options.entitlements);
24902490 man.hash.addOptional(comp.bin_file.options.pagezero_size);
24912491 man.hash.addOptional(comp.bin_file.options.search_strategy);
src/link.zig+1-1
......@@ -162,7 +162,7 @@ pub const Options = struct {
162162
163163 objects: []Compilation.LinkObject,
164164 framework_dirs: []const []const u8,
165 frameworks: []const []const u8,
165 frameworks: std.StringArrayHashMapUnmanaged(SystemLib),
166166 system_libs: std.StringArrayHashMapUnmanaged(SystemLib),
167167 wasi_emulated_libs: []const wasi_libc.CRTFile,
168168 lib_dirs: []const []const u8,
src/link/MachO.zig+47-27
......@@ -561,7 +561,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
561561 man.hash.add(self.base.options.dead_strip_dylibs);
562562 man.hash.addListOfBytes(self.base.options.lib_dirs);
563563 man.hash.addListOfBytes(self.base.options.framework_dirs);
564 man.hash.addListOfBytes(self.base.options.frameworks);
564 link.hashAddSystemLibs(&man.hash, self.base.options.frameworks);
565565 man.hash.addListOfBytes(self.base.options.rpath_list);
566566 if (is_dyn_lib) {
567567 man.hash.addOptionalBytes(self.base.options.install_name);
......@@ -768,19 +768,20 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
768768 }
769769
770770 // Shared and static libraries passed via `-l` flag.
771 var search_lib_names = std.ArrayList([]const u8).init(arena);
771 var candidate_libs = std.StringArrayHashMap(Compilation.SystemLib).init(arena);
772772
773 const system_libs = self.base.options.system_libs.keys();
774 for (system_libs) |link_lib| {
773 const system_lib_names = self.base.options.system_libs.keys();
774 for (system_lib_names) |system_lib_name| {
775775 // By this time, we depend on these libs being dynamically linked libraries and not static libraries
776776 // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which
777777 // case we want to avoid prepending "-l".
778 if (Compilation.classifyFileExt(link_lib) == .shared_library) {
779 try positionals.append(link_lib);
778 if (Compilation.classifyFileExt(system_lib_name) == .shared_library) {
779 try positionals.append(system_lib_name);
780780 continue;
781781 }
782782
783 try search_lib_names.append(link_lib);
783 const system_lib_info = self.base.options.system_libs.get(system_lib_name).?;
784 try candidate_libs.put(system_lib_name, system_lib_info);
784785 }
785786
786787 var lib_dirs = std.ArrayList([]const u8).init(arena);
......@@ -792,18 +793,18 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
792793 }
793794 }
794795
795 var libs = std.ArrayList([]const u8).init(arena);
796 var libs = std.StringArrayHashMap(Compilation.SystemLib).init(arena);
796797
797798 // Assume ld64 default -search_paths_first if no strategy specified.
798799 const search_strategy = self.base.options.search_strategy orelse .paths_first;
799 outer: for (search_lib_names.items) |lib_name| {
800 outer: for (candidate_libs.keys()) |lib_name| {
800801 switch (search_strategy) {
801802 .paths_first => {
802803 // Look in each directory for a dylib (stub first), and then for archive
803804 for (lib_dirs.items) |dir| {
804805 for (&[_][]const u8{ ".tbd", ".dylib", ".a" }) |ext| {
805806 if (try resolveLib(arena, dir, lib_name, ext)) |full_path| {
806 try libs.append(full_path);
807 try libs.put(full_path, candidate_libs.get(lib_name).?);
807808 continue :outer;
808809 }
809810 }
......@@ -817,13 +818,13 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
817818 for (lib_dirs.items) |dir| {
818819 for (&[_][]const u8{ ".tbd", ".dylib" }) |ext| {
819820 if (try resolveLib(arena, dir, lib_name, ext)) |full_path| {
820 try libs.append(full_path);
821 try libs.put(full_path, candidate_libs.get(lib_name).?);
821822 continue :outer;
822823 }
823824 }
824825 } else for (lib_dirs.items) |dir| {
825826 if (try resolveLib(arena, dir, lib_name, ".a")) |full_path| {
826 try libs.append(full_path);
827 try libs.put(full_path, candidate_libs.get(lib_name).?);
827828 } else {
828829 log.warn("library not found for '-l{s}'", .{lib_name});
829830 lib_not_found = true;
......@@ -847,7 +848,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
847848 // re-exports every single symbol definition.
848849 for (lib_dirs.items) |dir| {
849850 if (try resolveLib(arena, dir, "System", ".tbd")) |full_path| {
850 try libs.append(full_path);
851 try libs.put(full_path, .{ .needed = false });
851852 libsystem_available = true;
852853 break :blk;
853854 }
......@@ -857,8 +858,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
857858 for (lib_dirs.items) |dir| {
858859 if (try resolveLib(arena, dir, "System", ".dylib")) |libsystem_path| {
859860 if (try resolveLib(arena, dir, "c", ".dylib")) |libc_path| {
860 try libs.append(libsystem_path);
861 try libs.append(libc_path);
861 try libs.put(libsystem_path, .{ .needed = false });
862 try libs.put(libc_path, .{ .needed = false });
862863 libsystem_available = true;
863864 break :blk;
864865 }
......@@ -872,7 +873,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
872873 const full_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
873874 "libc", "darwin", libsystem_name,
874875 });
875 try libs.append(full_path);
876 try libs.put(full_path, .{ .needed = false });
876877 }
877878
878879 // frameworks
......@@ -885,16 +886,16 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
885886 }
886887 }
887888
888 outer: for (self.base.options.frameworks) |framework| {
889 outer: for (self.base.options.frameworks.keys()) |f_name| {
889890 for (framework_dirs.items) |dir| {
890891 for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
891 if (try resolveFramework(arena, dir, framework, ext)) |full_path| {
892 try libs.append(full_path);
892 if (try resolveFramework(arena, dir, f_name, ext)) |full_path| {
893 try libs.put(full_path, self.base.options.frameworks.get(f_name).?);
893894 continue :outer;
894895 }
895896 }
896897 } else {
897 log.warn("framework not found for '-framework {s}'", .{framework});
898 log.warn("framework not found for '-framework {s}'", .{f_name});
898899 framework_not_found = true;
899900 }
900901 }
......@@ -1025,15 +1026,25 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
10251026 try argv.append("-lc");
10261027
10271028 for (self.base.options.system_libs.keys()) |l_name| {
1028 try argv.append(try std.fmt.allocPrint(arena, "-l{s}", .{l_name}));
1029 const needed = self.base.options.system_libs.get(l_name).?.needed;
1030 const arg = if (needed)
1031 try std.fmt.allocPrint(arena, "-needed-l{s}", .{l_name})
1032 else
1033 try std.fmt.allocPrint(arena, "-l{s}", .{l_name});
1034 try argv.append(arg);
10291035 }
10301036
10311037 for (self.base.options.lib_dirs) |lib_dir| {
10321038 try argv.append(try std.fmt.allocPrint(arena, "-L{s}", .{lib_dir}));
10331039 }
10341040
1035 for (self.base.options.frameworks) |framework| {
1036 try argv.append(try std.fmt.allocPrint(arena, "-framework {s}", .{framework}));
1041 for (self.base.options.frameworks.keys()) |framework| {
1042 const needed = self.base.options.frameworks.get(framework).?.needed;
1043 const arg = if (needed)
1044 try std.fmt.allocPrint(arena, "-needed_framework {s}", .{framework})
1045 else
1046 try std.fmt.allocPrint(arena, "-framework {s}", .{framework});
1047 try argv.append(arg);
10371048 }
10381049
10391050 for (self.base.options.framework_dirs) |framework_dir| {
......@@ -1056,7 +1067,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
10561067 defer dependent_libs.deinit();
10571068 try self.parseInputFiles(positionals.items, self.base.options.sysroot, &dependent_libs);
10581069 try self.parseAndForceLoadStaticArchives(must_link_archives.keys());
1059 try self.parseLibs(libs.items, self.base.options.sysroot, &dependent_libs);
1070 try self.parseLibs(libs.keys(), libs.values(), self.base.options.sysroot, &dependent_libs);
10601071 try self.parseDependentLibs(self.base.options.sysroot, &dependent_libs);
10611072 }
10621073
......@@ -1381,6 +1392,7 @@ const DylibCreateOpts = struct {
13811392 dependent_libs: *std.fifo.LinearFifo(Dylib.Id, .Dynamic),
13821393 id: ?Dylib.Id = null,
13831394 is_dependent: bool = false,
1395 is_needed: bool = false,
13841396};
13851397
13861398pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDylibError!bool {
......@@ -1431,7 +1443,7 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy
14311443 try self.dylibs_map.putNoClobber(self.base.allocator, dylib.id.?.name, dylib_id);
14321444
14331445 const should_link_dylib_even_if_unreachable = blk: {
1434 if (self.base.options.dead_strip_dylibs) break :blk false;
1446 if (self.base.options.dead_strip_dylibs and !opts.is_needed) break :blk false;
14351447 break :blk !(opts.is_dependent or self.referenced_dylibs.contains(dylib_id));
14361448 };
14371449
......@@ -1479,12 +1491,20 @@ fn parseAndForceLoadStaticArchives(self: *MachO, files: []const []const u8) !voi
14791491 }
14801492}
14811493
1482fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8, dependent_libs: anytype) !void {
1483 for (libs) |lib| {
1494fn parseLibs(
1495 self: *MachO,
1496 lib_names: []const []const u8,
1497 lib_infos: []const Compilation.SystemLib,
1498 syslibroot: ?[]const u8,
1499 dependent_libs: anytype,
1500) !void {
1501 for (lib_names) |lib, i| {
1502 const lib_info = lib_infos[i];
14841503 log.debug("parsing lib path '{s}'", .{lib});
14851504 if (try self.parseDylib(lib, .{
14861505 .syslibroot = syslibroot,
14871506 .dependent_libs = dependent_libs,
1507 .is_needed = lib_info.needed,
14881508 })) continue;
14891509 if (try self.parseArchive(lib, false)) continue;
14901510
src/main.zig+31-9
......@@ -444,6 +444,8 @@ const usage_build_generic =
444444 \\ --stack [size] Override default stack size
445445 \\ --image-base [addr] Set base address for executable image
446446 \\ -framework [name] (Darwin) link against framework
447 \\ -needed_framework [name] (Darwin) link against framework (even if unused)
448 \\ -needed_library [lib] (Darwin) link against system library (even if unused)
447449 \\ -F[dir] (Darwin) add search path for frameworks
448450 \\ -install_name=[value] (Darwin) add dylib's install name
449451 \\ --entitlements [path] (Darwin) add path to entitlements file for embedding in code signature
......@@ -750,8 +752,7 @@ fn buildOutputType(
750752 var framework_dirs = std.ArrayList([]const u8).init(gpa);
751753 defer framework_dirs.deinit();
752754
753 var frameworks = std.ArrayList([]const u8).init(gpa);
754 defer frameworks.deinit();
755 var frameworks: std.StringArrayHashMapUnmanaged(Compilation.SystemLib) = .{};
755756
756757 // null means replace with the test executable binary
757758 var test_exec_args = std.ArrayList(?[]const u8).init(gpa);
......@@ -912,9 +913,15 @@ fn buildOutputType(
912913 fatal("expected parameter after {s}", .{arg});
913914 });
914915 } else if (mem.eql(u8, arg, "-framework")) {
915 try frameworks.append(args_iter.next() orelse {
916 const path = args_iter.next() orelse {
916917 fatal("expected parameter after {s}", .{arg});
917 });
918 };
919 try frameworks.put(gpa, path, .{ .needed = false });
920 } else if (mem.eql(u8, arg, "-needed_framework")) {
921 const path = args_iter.next() orelse {
922 fatal("expected parameter after {s}", .{arg});
923 };
924 try frameworks.put(gpa, path, .{ .needed = true });
918925 } else if (mem.eql(u8, arg, "-install_name")) {
919926 install_name = args_iter.next() orelse {
920927 fatal("expected parameter after {s}", .{arg});
......@@ -956,7 +963,10 @@ fn buildOutputType(
956963 // We don't know whether this library is part of libc or libc++ until
957964 // we resolve the target, so we simply append to the list for now.
958965 try system_libs.put(next_arg, .{ .needed = false });
959 } else if (mem.eql(u8, arg, "--needed-library") or mem.eql(u8, arg, "-needed-l")) {
966 } else if (mem.eql(u8, arg, "--needed-library") or
967 mem.eql(u8, arg, "-needed-l") or
968 mem.eql(u8, arg, "--needed_library"))
969 {
960970 const next_arg = args_iter.next() orelse {
961971 fatal("expected parameter after {s}", .{arg});
962972 };
......@@ -1586,7 +1596,7 @@ fn buildOutputType(
15861596 try clang_argv.appendSlice(it.other_args);
15871597 },
15881598 .framework_dir => try framework_dirs.append(it.only_arg),
1589 .framework => try frameworks.append(it.only_arg),
1599 .framework => try frameworks.put(gpa, it.only_arg, .{ .needed = false }),
15901600 .nostdlibinc => want_native_include_dirs = false,
15911601 .strip => strip = true,
15921602 .exec_model => {
......@@ -1874,7 +1884,19 @@ fn buildOutputType(
18741884 if (i >= linker_args.items.len) {
18751885 fatal("expected linker arg after '{s}'", .{arg});
18761886 }
1877 try frameworks.append(linker_args.items[i]);
1887 try frameworks.put(gpa, linker_args.items[i], .{ .needed = false });
1888 } else if (mem.eql(u8, arg, "-needed_framework")) {
1889 i += 1;
1890 if (i >= linker_args.items.len) {
1891 fatal("expected linker arg after '{s}'", .{arg});
1892 }
1893 try frameworks.put(gpa, linker_args.items[i], .{ .needed = true });
1894 } else if (mem.eql(u8, arg, "-needed_library")) {
1895 i += 1;
1896 if (i >= linker_args.items.len) {
1897 fatal("expected linker arg after '{s}'", .{arg});
1898 }
1899 try system_libs.put(linker_args.items[i], .{ .needed = true });
18781900 } else if (mem.eql(u8, arg, "-compatibility_version")) {
18791901 i += 1;
18801902 if (i >= linker_args.items.len) {
......@@ -2244,7 +2266,7 @@ fn buildOutputType(
22442266
22452267 if (comptime builtin.target.isDarwin()) {
22462268 // If we want to link against frameworks, we need system headers.
2247 if (framework_dirs.items.len > 0 or frameworks.items.len > 0)
2269 if (framework_dirs.items.len > 0 or frameworks.count() > 0)
22482270 want_native_include_dirs = true;
22492271 }
22502272
......@@ -2734,7 +2756,7 @@ fn buildOutputType(
27342756 .c_source_files = c_source_files.items,
27352757 .link_objects = link_objects.items,
27362758 .framework_dirs = framework_dirs.items,
2737 .frameworks = frameworks.items,
2759 .frameworks = frameworks,
27382760 .system_lib_names = system_libs.keys(),
27392761 .system_lib_infos = system_libs.values(),
27402762 .wasi_emulated_libs = wasi_emulated_libs.items,
test/link.zig+9
......@@ -45,6 +45,15 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
4545 .requires_macos_sdk = true,
4646 });
4747
48 cases.addBuildFile("test/link/macho/needed_l/build.zig", .{
49 .build_modes = true,
50 });
51
52 cases.addBuildFile("test/link/macho/needed_framework/build.zig", .{
53 .build_modes = true,
54 .requires_macos_sdk = true,
55 });
56
4857 // Try to build and run an Objective-C executable.
4958 cases.addBuildFile("test/link/macho/objc/build.zig", .{
5059 .build_modes = true,
test/link/macho/dead_strip_dylibs/build.zig+1-1
......@@ -6,6 +6,7 @@ pub fn build(b: *Builder) void {
66 const mode = b.standardReleaseOptions();
77
88 const test_step = b.step("test", "Test the program");
9 test_step.dependOn(b.getInstallStep());
910
1011 {
1112 // Without -dead_strip_dylibs we expect `-la` to include liba.dylib in the final executable
......@@ -37,7 +38,6 @@ pub fn build(b: *Builder) void {
3738
3839fn createScenario(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep {
3940 const exe = b.addExecutable("test", null);
40 b.default_step.dependOn(&exe.step);
4141 exe.addCSourceFile("main.c", &[0][]const u8{});
4242 exe.setBuildMode(mode);
4343 exe.linkLibC();
test/link/macho/dead_strip_dylibs/main.c+2-1
......@@ -1,10 +1,11 @@
11#include <objc/runtime.h>
22
3int main() {
3int main(int argc, char* argv[]) {
44 if (objc_getClass("NSObject") == 0) {
55 return -1;
66 }
77 if (objc_getClass("NSApplication") == 0) {
88 return -2;
99 }
10 return 0;
1011}
test/link/macho/dylib/main.c+1-1
......@@ -3,7 +3,7 @@
33char* hello();
44extern char world[];
55
6int main() {
6int main(int argc, char* argv[]) {
77 printf("%s %s", hello(), world);
88 return 0;
99}
test/link/macho/needed_framework/build.zig created+27
......@@ -0,0 +1,27 @@
1const std = @import("std");
2const Builder = std.build.Builder;
3const LibExeObjectStep = std.build.LibExeObjStep;
4
5pub fn build(b: *Builder) void {
6 const mode = b.standardReleaseOptions();
7
8 const test_step = b.step("test", "Test the program");
9 test_step.dependOn(b.getInstallStep());
10
11 // -dead_strip_dylibs
12 // -needed_framework Cocoa
13 const exe = b.addExecutable("test", null);
14 exe.addCSourceFile("main.c", &[0][]const u8{});
15 exe.setBuildMode(mode);
16 exe.linkLibC();
17 exe.linkFrameworkNeeded("Cocoa");
18 exe.dead_strip_dylibs = true;
19
20 const check = exe.checkObject(.macho);
21 check.checkStart("cmd LOAD_DYLIB");
22 check.checkNext("name {*}Cocoa");
23 test_step.dependOn(&check.step);
24
25 const run_cmd = exe.run();
26 test_step.dependOn(&run_cmd.step);
27}
test/link/macho/needed_framework/main.c created+3
......@@ -0,0 +1,3 @@
1int main(int argc, char* argv[]) {
2 return 0;
3}
test/link/macho/needed_l/a.c created+1
......@@ -0,0 +1 @@
1int a = 42;
test/link/macho/needed_l/build.zig created+35
......@@ -0,0 +1,35 @@
1const std = @import("std");
2const Builder = std.build.Builder;
3const LibExeObjectStep = std.build.LibExeObjStep;
4
5pub fn build(b: *Builder) void {
6 const mode = b.standardReleaseOptions();
7
8 const test_step = b.step("test", "Test the program");
9 test_step.dependOn(b.getInstallStep());
10
11 const dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0));
12 dylib.setBuildMode(mode);
13 dylib.addCSourceFile("a.c", &.{});
14 dylib.linkLibC();
15 dylib.install();
16
17 // -dead_strip_dylibs
18 // -needed-la
19 const exe = b.addExecutable("test", null);
20 exe.addCSourceFile("main.c", &[0][]const u8{});
21 exe.setBuildMode(mode);
22 exe.linkLibC();
23 exe.linkSystemLibraryNeeded("a");
24 exe.addLibraryPath(b.pathFromRoot("zig-out/lib"));
25 exe.addRPath(b.pathFromRoot("zig-out/lib"));
26 exe.dead_strip_dylibs = true;
27
28 const check = exe.checkObject(.macho);
29 check.checkStart("cmd LOAD_DYLIB");
30 check.checkNext("name @rpath/liba.dylib");
31 test_step.dependOn(&check.step);
32
33 const run_cmd = exe.run();
34 test_step.dependOn(&run_cmd.step);
35}
test/link/macho/needed_l/main.c created+3
......@@ -0,0 +1,3 @@
1int main(int argc, char* argv[]) {
2 return 0;
3}
test/link/macho/search_strategy/main.c+1-1
......@@ -3,7 +3,7 @@
33char* hello();
44extern char world[];
55
6int main() {
6int main(int argc, char* argv[]) {
77 printf("%s %s", hello(), world);
88 return 0;
99}