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;...@@ -11,7 +11,6 @@ const ArrayList = std.ArrayList;
11const StringHashMap = std.StringHashMap;11const StringHashMap = std.StringHashMap;
12const Allocator = mem.Allocator;12const Allocator = mem.Allocator;
13const process = std.process;13const process = std.process;
14const BufSet = std.BufSet;
15const EnvMap = std.process.EnvMap;14const EnvMap = std.process.EnvMap;
16const fmt_lib = std.fmt;15const fmt_lib = std.fmt;
17const File = std.fs.File;16const File = std.fs.File;
...@@ -1484,7 +1483,7 @@ pub const LibExeObjStep = struct {...@@ -1484,7 +1483,7 @@ pub const LibExeObjStep = struct {
1484 lib_paths: ArrayList([]const u8),1483 lib_paths: ArrayList([]const u8),
1485 rpaths: ArrayList([]const u8),1484 rpaths: ArrayList([]const u8),
1486 framework_dirs: ArrayList([]const u8),1485 framework_dirs: ArrayList([]const u8),
1487 frameworks: BufSet,1486 frameworks: StringHashMap(bool),
1488 verbose_link: bool,1487 verbose_link: bool,
1489 verbose_cc: bool,1488 verbose_cc: bool,
1490 emit_analysis: EmitOption = .default,1489 emit_analysis: EmitOption = .default,
...@@ -1643,6 +1642,7 @@ pub const LibExeObjStep = struct {...@@ -1643,6 +1642,7 @@ pub const LibExeObjStep = struct {
16431642
1644 pub const SystemLib = struct {1643 pub const SystemLib = struct {
1645 name: []const u8,1644 name: []const u8,
1645 needed: bool,
1646 use_pkg_config: enum {1646 use_pkg_config: enum {
1647 /// Don't use pkg-config, just pass -lfoo where foo is name.1647 /// Don't use pkg-config, just pass -lfoo where foo is name.
1648 no,1648 no,
...@@ -1744,7 +1744,7 @@ pub const LibExeObjStep = struct {...@@ -1744,7 +1744,7 @@ pub const LibExeObjStep = struct {
1744 .kind = kind,1744 .kind = kind,
1745 .root_src = root_src,1745 .root_src = root_src,
1746 .name = name,1746 .name = name,
1747 .frameworks = BufSet.init(builder.allocator),1747 .frameworks = StringHashMap(bool).init(builder.allocator),
1748 .step = Step.init(base_id, name, builder.allocator, make),1748 .step = Step.init(base_id, name, builder.allocator, make),
1749 .version = ver,1749 .version = ver,
1750 .out_filename = undefined,1750 .out_filename = undefined,
...@@ -1893,8 +1893,11 @@ pub const LibExeObjStep = struct {...@@ -1893,8 +1893,11 @@ pub const LibExeObjStep = struct {
1893 }1893 }
18941894
1895 pub fn linkFramework(self: *LibExeObjStep, framework_name: []const u8) void {1895 pub fn linkFramework(self: *LibExeObjStep, framework_name: []const u8) void {
1896 // Note: No need to dupe because frameworks dupes internally.1896 self.frameworks.put(self.builder.dupe(framework_name), false) catch unreachable;
1897 self.frameworks.insert(framework_name) 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;
1898 }1901 }
18991902
1900 /// Returns whether the library, executable, or object depends on a particular system library.1903 /// Returns whether the library, executable, or object depends on a particular system library.
...@@ -1935,6 +1938,7 @@ pub const LibExeObjStep = struct {...@@ -1935,6 +1938,7 @@ pub const LibExeObjStep = struct {
1935 self.link_objects.append(.{1938 self.link_objects.append(.{
1936 .system_lib = .{1939 .system_lib = .{
1937 .name = "c",1940 .name = "c",
1941 .needed = false,
1938 .use_pkg_config = .no,1942 .use_pkg_config = .no,
1939 },1943 },
1940 }) catch unreachable;1944 }) catch unreachable;
...@@ -1947,6 +1951,7 @@ pub const LibExeObjStep = struct {...@@ -1947,6 +1951,7 @@ pub const LibExeObjStep = struct {
1947 self.link_objects.append(.{1951 self.link_objects.append(.{
1948 .system_lib = .{1952 .system_lib = .{
1949 .name = "c++",1953 .name = "c++",
1954 .needed = false,
1950 .use_pkg_config = .no,1955 .use_pkg_config = .no,
1951 },1956 },
1952 }) catch unreachable;1957 }) catch unreachable;
...@@ -1971,6 +1976,19 @@ pub const LibExeObjStep = struct {...@@ -1971,6 +1976,19 @@ pub const LibExeObjStep = struct {
1971 self.link_objects.append(.{1976 self.link_objects.append(.{
1972 .system_lib = .{1977 .system_lib = .{
1973 .name = self.builder.dupe(name),1978 .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,
1974 .use_pkg_config = .no,1992 .use_pkg_config = .no,
1975 },1993 },
1976 }) catch unreachable;1994 }) catch unreachable;
...@@ -1982,6 +2000,19 @@ pub const LibExeObjStep = struct {...@@ -1982,6 +2000,19 @@ pub const LibExeObjStep = struct {
1982 self.link_objects.append(.{2000 self.link_objects.append(.{
1983 .system_lib = .{2001 .system_lib = .{
1984 .name = self.builder.dupe(lib_name),2002 .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,
1985 .use_pkg_config = .force,2016 .use_pkg_config = .force,
1986 },2017 },
1987 }) catch unreachable;2018 }) catch unreachable;
...@@ -2084,6 +2115,14 @@ pub const LibExeObjStep = struct {...@@ -2084,6 +2115,14 @@ pub const LibExeObjStep = struct {
2084 }2115 }
20852116
2086 pub fn linkSystemLibrary(self: *LibExeObjStep, name: []const u8) void {2117 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 {
2087 if (isLibCLibrary(name)) {2126 if (isLibCLibrary(name)) {
2088 self.linkLibC();2127 self.linkLibC();
2089 return;2128 return;
...@@ -2096,6 +2135,7 @@ pub const LibExeObjStep = struct {...@@ -2096,6 +2135,7 @@ pub const LibExeObjStep = struct {
2096 self.link_objects.append(.{2135 self.link_objects.append(.{
2097 .system_lib = .{2136 .system_lib = .{
2098 .name = self.builder.dupe(name),2137 .name = self.builder.dupe(name),
2138 .needed = needed,
2099 .use_pkg_config = .yes,2139 .use_pkg_config = .yes,
2100 },2140 },
2101 }) catch unreachable;2141 }) catch unreachable;
...@@ -2437,7 +2477,7 @@ pub const LibExeObjStep = struct {...@@ -2437,7 +2477,7 @@ pub const LibExeObjStep = struct {
2437 if (!other.isDynamicLibrary()) {2477 if (!other.isDynamicLibrary()) {
2438 var it = other.frameworks.iterator();2478 var it = other.frameworks.iterator();
2439 while (it.next()) |framework| {2479 while (it.next()) |framework| {
2440 self.frameworks.insert(framework.*) catch unreachable;2480 self.frameworks.put(framework.key_ptr.*, framework.value_ptr.*) catch unreachable;
2441 }2481 }
2442 }2482 }
2443 },2483 },
...@@ -2473,8 +2513,9 @@ pub const LibExeObjStep = struct {...@@ -2473,8 +2513,9 @@ pub const LibExeObjStep = struct {
2473 },2513 },
24742514
2475 .system_lib => |system_lib| {2515 .system_lib => |system_lib| {
2516 const prefix: []const u8 = if (system_lib.needed) "-needed-l" else "-l";
2476 switch (system_lib.use_pkg_config) {2517 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 })),
2478 .yes, .force => {2519 .yes, .force => {
2479 if (self.runPkgConfig(system_lib.name)) |args| {2520 if (self.runPkgConfig(system_lib.name)) |args| {
2480 try zig_args.appendSlice(args);2521 try zig_args.appendSlice(args);
...@@ -2488,7 +2529,10 @@ pub const LibExeObjStep = struct {...@@ -2488,7 +2529,10 @@ pub const LibExeObjStep = struct {
2488 .yes => {2529 .yes => {
2489 // pkg-config failed, so fall back to linking the library2530 // pkg-config failed, so fall back to linking the library
2490 // by name directly.2531 // 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 }));
2492 },2536 },
2493 .force => {2537 .force => {
2494 panic("pkg-config failed for library {s}", .{system_lib.name});2538 panic("pkg-config failed for library {s}", .{system_lib.name});
...@@ -2972,9 +3016,15 @@ pub const LibExeObjStep = struct {...@@ -2972,9 +3016,15 @@ pub const LibExeObjStep = struct {
2972 }3016 }
29733017
2974 var it = self.frameworks.iterator();3018 var it = self.frameworks.iterator();
2975 while (it.next()) |framework| {3019 while (it.next()) |entry| {
2976 zig_args.append("-framework") catch unreachable;3020 const name = entry.key_ptr.*;
2977 zig_args.append(framework.*) catch unreachable;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;
2978 }3028 }
2979 } else {3029 } else {
2980 if (self.framework_dirs.items.len > 0) {3030 if (self.framework_dirs.items.len > 0) {
src/Compilation.zig+4-4
...@@ -791,7 +791,7 @@ pub const InitOptions = struct {...@@ -791,7 +791,7 @@ pub const InitOptions = struct {
791 c_source_files: []const CSourceFile = &[0]CSourceFile{},791 c_source_files: []const CSourceFile = &[0]CSourceFile{},
792 link_objects: []LinkObject = &[0]LinkObject{},792 link_objects: []LinkObject = &[0]LinkObject{},
793 framework_dirs: []const []const u8 = &[0][]const u8{},793 framework_dirs: []const []const u8 = &[0][]const u8{},
794 frameworks: []const []const u8 = &[0][]const u8{},794 frameworks: std.StringArrayHashMapUnmanaged(SystemLib) = .{},
795 system_lib_names: []const []const u8 = &.{},795 system_lib_names: []const []const u8 = &.{},
796 system_lib_infos: []const SystemLib = &.{},796 system_lib_infos: []const SystemLib = &.{},
797 /// These correspond to the WASI libc emulated subcomponents including:797 /// These correspond to the WASI libc emulated subcomponents including:
...@@ -1097,7 +1097,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {...@@ -1097,7 +1097,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
1097 // Our linker can't handle objects or most advanced options yet.1097 // Our linker can't handle objects or most advanced options yet.
1098 if (options.link_objects.len != 0 or1098 if (options.link_objects.len != 0 or
1099 options.c_source_files.len != 0 or1099 options.c_source_files.len != 0 or
1100 options.frameworks.len != 0 or1100 options.frameworks.count() != 0 or
1101 options.system_lib_names.len != 0 or1101 options.system_lib_names.len != 0 or
1102 options.link_libc or options.link_libcpp or1102 options.link_libc or options.link_libcpp or
1103 link_eh_frame_hdr or1103 link_eh_frame_hdr or
...@@ -1215,7 +1215,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {...@@ -1215,7 +1215,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
1215 options.target,1215 options.target,
1216 options.is_native_abi,1216 options.is_native_abi,
1217 link_libc,1217 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,
1219 options.libc_installation,1219 options.libc_installation,
1220 options.native_darwin_sdk != null,1220 options.native_darwin_sdk != null,
1221 );1221 );
...@@ -2485,7 +2485,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes...@@ -2485,7 +2485,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
24852485
2486 // Mach-O specific stuff2486 // Mach-O specific stuff
2487 man.hash.addListOfBytes(comp.bin_file.options.framework_dirs);2487 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);
2489 try man.addOptionalFile(comp.bin_file.options.entitlements);2489 try man.addOptionalFile(comp.bin_file.options.entitlements);
2490 man.hash.addOptional(comp.bin_file.options.pagezero_size);2490 man.hash.addOptional(comp.bin_file.options.pagezero_size);
2491 man.hash.addOptional(comp.bin_file.options.search_strategy);2491 man.hash.addOptional(comp.bin_file.options.search_strategy);
src/link.zig+1-1
...@@ -162,7 +162,7 @@ pub const Options = struct {...@@ -162,7 +162,7 @@ pub const Options = struct {
162162
163 objects: []Compilation.LinkObject,163 objects: []Compilation.LinkObject,
164 framework_dirs: []const []const u8,164 framework_dirs: []const []const u8,
165 frameworks: []const []const u8,165 frameworks: std.StringArrayHashMapUnmanaged(SystemLib),
166 system_libs: std.StringArrayHashMapUnmanaged(SystemLib),166 system_libs: std.StringArrayHashMapUnmanaged(SystemLib),
167 wasi_emulated_libs: []const wasi_libc.CRTFile,167 wasi_emulated_libs: []const wasi_libc.CRTFile,
168 lib_dirs: []const []const u8,168 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...@@ -561,7 +561,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
561 man.hash.add(self.base.options.dead_strip_dylibs);561 man.hash.add(self.base.options.dead_strip_dylibs);
562 man.hash.addListOfBytes(self.base.options.lib_dirs);562 man.hash.addListOfBytes(self.base.options.lib_dirs);
563 man.hash.addListOfBytes(self.base.options.framework_dirs);563 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);
565 man.hash.addListOfBytes(self.base.options.rpath_list);565 man.hash.addListOfBytes(self.base.options.rpath_list);
566 if (is_dyn_lib) {566 if (is_dyn_lib) {
567 man.hash.addOptionalBytes(self.base.options.install_name);567 man.hash.addOptionalBytes(self.base.options.install_name);
...@@ -768,19 +768,20 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No...@@ -768,19 +768,20 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
768 }768 }
769769
770 // Shared and static libraries passed via `-l` flag.770 // 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();773 const system_lib_names = self.base.options.system_libs.keys();
774 for (system_libs) |link_lib| {774 for (system_lib_names) |system_lib_name| {
775 // By this time, we depend on these libs being dynamically linked libraries and not static libraries775 // By this time, we depend on these libs being dynamically linked libraries and not static libraries
776 // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which776 // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which
777 // case we want to avoid prepending "-l".777 // case we want to avoid prepending "-l".
778 if (Compilation.classifyFileExt(link_lib) == .shared_library) {778 if (Compilation.classifyFileExt(system_lib_name) == .shared_library) {
779 try positionals.append(link_lib);779 try positionals.append(system_lib_name);
780 continue;780 continue;
781 }781 }
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);
784 }785 }
785786
786 var lib_dirs = std.ArrayList([]const u8).init(arena);787 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...@@ -792,18 +793,18 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
792 }793 }
793 }794 }
794795
795 var libs = std.ArrayList([]const u8).init(arena);796 var libs = std.StringArrayHashMap(Compilation.SystemLib).init(arena);
796797
797 // Assume ld64 default -search_paths_first if no strategy specified.798 // Assume ld64 default -search_paths_first if no strategy specified.
798 const search_strategy = self.base.options.search_strategy orelse .paths_first;799 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| {
800 switch (search_strategy) {801 switch (search_strategy) {
801 .paths_first => {802 .paths_first => {
802 // Look in each directory for a dylib (stub first), and then for archive803 // Look in each directory for a dylib (stub first), and then for archive
803 for (lib_dirs.items) |dir| {804 for (lib_dirs.items) |dir| {
804 for (&[_][]const u8{ ".tbd", ".dylib", ".a" }) |ext| {805 for (&[_][]const u8{ ".tbd", ".dylib", ".a" }) |ext| {
805 if (try resolveLib(arena, dir, lib_name, ext)) |full_path| {806 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).?);
807 continue :outer;808 continue :outer;
808 }809 }
809 }810 }
...@@ -817,13 +818,13 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No...@@ -817,13 +818,13 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
817 for (lib_dirs.items) |dir| {818 for (lib_dirs.items) |dir| {
818 for (&[_][]const u8{ ".tbd", ".dylib" }) |ext| {819 for (&[_][]const u8{ ".tbd", ".dylib" }) |ext| {
819 if (try resolveLib(arena, dir, lib_name, ext)) |full_path| {820 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).?);
821 continue :outer;822 continue :outer;
822 }823 }
823 }824 }
824 } else for (lib_dirs.items) |dir| {825 } else for (lib_dirs.items) |dir| {
825 if (try resolveLib(arena, dir, lib_name, ".a")) |full_path| {826 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).?);
827 } else {828 } else {
828 log.warn("library not found for '-l{s}'", .{lib_name});829 log.warn("library not found for '-l{s}'", .{lib_name});
829 lib_not_found = true;830 lib_not_found = true;
...@@ -847,7 +848,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No...@@ -847,7 +848,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
847 // re-exports every single symbol definition.848 // re-exports every single symbol definition.
848 for (lib_dirs.items) |dir| {849 for (lib_dirs.items) |dir| {
849 if (try resolveLib(arena, dir, "System", ".tbd")) |full_path| {850 if (try resolveLib(arena, dir, "System", ".tbd")) |full_path| {
850 try libs.append(full_path);851 try libs.put(full_path, .{ .needed = false });
851 libsystem_available = true;852 libsystem_available = true;
852 break :blk;853 break :blk;
853 }854 }
...@@ -857,8 +858,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No...@@ -857,8 +858,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
857 for (lib_dirs.items) |dir| {858 for (lib_dirs.items) |dir| {
858 if (try resolveLib(arena, dir, "System", ".dylib")) |libsystem_path| {859 if (try resolveLib(arena, dir, "System", ".dylib")) |libsystem_path| {
859 if (try resolveLib(arena, dir, "c", ".dylib")) |libc_path| {860 if (try resolveLib(arena, dir, "c", ".dylib")) |libc_path| {
860 try libs.append(libsystem_path);861 try libs.put(libsystem_path, .{ .needed = false });
861 try libs.append(libc_path);862 try libs.put(libc_path, .{ .needed = false });
862 libsystem_available = true;863 libsystem_available = true;
863 break :blk;864 break :blk;
864 }865 }
...@@ -872,7 +873,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No...@@ -872,7 +873,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
872 const full_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{873 const full_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
873 "libc", "darwin", libsystem_name,874 "libc", "darwin", libsystem_name,
874 });875 });
875 try libs.append(full_path);876 try libs.put(full_path, .{ .needed = false });
876 }877 }
877878
878 // frameworks879 // frameworks
...@@ -885,16 +886,16 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No...@@ -885,16 +886,16 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
885 }886 }
886 }887 }
887888
888 outer: for (self.base.options.frameworks) |framework| {889 outer: for (self.base.options.frameworks.keys()) |f_name| {
889 for (framework_dirs.items) |dir| {890 for (framework_dirs.items) |dir| {
890 for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {891 for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
891 if (try resolveFramework(arena, dir, framework, ext)) |full_path| {892 if (try resolveFramework(arena, dir, f_name, ext)) |full_path| {
892 try libs.append(full_path);893 try libs.put(full_path, self.base.options.frameworks.get(f_name).?);
893 continue :outer;894 continue :outer;
894 }895 }
895 }896 }
896 } else {897 } else {
897 log.warn("framework not found for '-framework {s}'", .{framework});898 log.warn("framework not found for '-framework {s}'", .{f_name});
898 framework_not_found = true;899 framework_not_found = true;
899 }900 }
900 }901 }
...@@ -1025,15 +1026,25 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No...@@ -1025,15 +1026,25 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
1025 try argv.append("-lc");1026 try argv.append("-lc");
10261027
1027 for (self.base.options.system_libs.keys()) |l_name| {1028 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);
1029 }1035 }
10301036
1031 for (self.base.options.lib_dirs) |lib_dir| {1037 for (self.base.options.lib_dirs) |lib_dir| {
1032 try argv.append(try std.fmt.allocPrint(arena, "-L{s}", .{lib_dir}));1038 try argv.append(try std.fmt.allocPrint(arena, "-L{s}", .{lib_dir}));
1033 }1039 }
10341040
1035 for (self.base.options.frameworks) |framework| {1041 for (self.base.options.frameworks.keys()) |framework| {
1036 try argv.append(try std.fmt.allocPrint(arena, "-framework {s}", .{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);
1037 }1048 }
10381049
1039 for (self.base.options.framework_dirs) |framework_dir| {1050 for (self.base.options.framework_dirs) |framework_dir| {
...@@ -1056,7 +1067,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No...@@ -1056,7 +1067,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
1056 defer dependent_libs.deinit();1067 defer dependent_libs.deinit();
1057 try self.parseInputFiles(positionals.items, self.base.options.sysroot, &dependent_libs);1068 try self.parseInputFiles(positionals.items, self.base.options.sysroot, &dependent_libs);
1058 try self.parseAndForceLoadStaticArchives(must_link_archives.keys());1069 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);
1060 try self.parseDependentLibs(self.base.options.sysroot, &dependent_libs);1071 try self.parseDependentLibs(self.base.options.sysroot, &dependent_libs);
1061 }1072 }
10621073
...@@ -1381,6 +1392,7 @@ const DylibCreateOpts = struct {...@@ -1381,6 +1392,7 @@ const DylibCreateOpts = struct {
1381 dependent_libs: *std.fifo.LinearFifo(Dylib.Id, .Dynamic),1392 dependent_libs: *std.fifo.LinearFifo(Dylib.Id, .Dynamic),
1382 id: ?Dylib.Id = null,1393 id: ?Dylib.Id = null,
1383 is_dependent: bool = false,1394 is_dependent: bool = false,
1395 is_needed: bool = false,
1384};1396};
13851397
1386pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDylibError!bool {1398pub 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...@@ -1431,7 +1443,7 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy
1431 try self.dylibs_map.putNoClobber(self.base.allocator, dylib.id.?.name, dylib_id);1443 try self.dylibs_map.putNoClobber(self.base.allocator, dylib.id.?.name, dylib_id);
14321444
1433 const should_link_dylib_even_if_unreachable = blk: {1445 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;
1435 break :blk !(opts.is_dependent or self.referenced_dylibs.contains(dylib_id));1447 break :blk !(opts.is_dependent or self.referenced_dylibs.contains(dylib_id));
1436 };1448 };
14371449
...@@ -1479,12 +1491,20 @@ fn parseAndForceLoadStaticArchives(self: *MachO, files: []const []const u8) !voi...@@ -1479,12 +1491,20 @@ fn parseAndForceLoadStaticArchives(self: *MachO, files: []const []const u8) !voi
1479 }1491 }
1480}1492}
14811493
1482fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8, dependent_libs: anytype) !void {1494fn parseLibs(
1483 for (libs) |lib| {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];
1484 log.debug("parsing lib path '{s}'", .{lib});1503 log.debug("parsing lib path '{s}'", .{lib});
1485 if (try self.parseDylib(lib, .{1504 if (try self.parseDylib(lib, .{
1486 .syslibroot = syslibroot,1505 .syslibroot = syslibroot,
1487 .dependent_libs = dependent_libs,1506 .dependent_libs = dependent_libs,
1507 .is_needed = lib_info.needed,
1488 })) continue;1508 })) continue;
1489 if (try self.parseArchive(lib, false)) continue;1509 if (try self.parseArchive(lib, false)) continue;
14901510
src/main.zig+31-9
...@@ -444,6 +444,8 @@ const usage_build_generic =...@@ -444,6 +444,8 @@ const usage_build_generic =
444 \\ --stack [size] Override default stack size444 \\ --stack [size] Override default stack size
445 \\ --image-base [addr] Set base address for executable image445 \\ --image-base [addr] Set base address for executable image
446 \\ -framework [name] (Darwin) link against framework446 \\ -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)
447 \\ -F[dir] (Darwin) add search path for frameworks449 \\ -F[dir] (Darwin) add search path for frameworks
448 \\ -install_name=[value] (Darwin) add dylib's install name450 \\ -install_name=[value] (Darwin) add dylib's install name
449 \\ --entitlements [path] (Darwin) add path to entitlements file for embedding in code signature451 \\ --entitlements [path] (Darwin) add path to entitlements file for embedding in code signature
...@@ -750,8 +752,7 @@ fn buildOutputType(...@@ -750,8 +752,7 @@ fn buildOutputType(
750 var framework_dirs = std.ArrayList([]const u8).init(gpa);752 var framework_dirs = std.ArrayList([]const u8).init(gpa);
751 defer framework_dirs.deinit();753 defer framework_dirs.deinit();
752754
753 var frameworks = std.ArrayList([]const u8).init(gpa);755 var frameworks: std.StringArrayHashMapUnmanaged(Compilation.SystemLib) = .{};
754 defer frameworks.deinit();
755756
756 // null means replace with the test executable binary757 // null means replace with the test executable binary
757 var test_exec_args = std.ArrayList(?[]const u8).init(gpa);758 var test_exec_args = std.ArrayList(?[]const u8).init(gpa);
...@@ -912,9 +913,15 @@ fn buildOutputType(...@@ -912,9 +913,15 @@ fn buildOutputType(
912 fatal("expected parameter after {s}", .{arg});913 fatal("expected parameter after {s}", .{arg});
913 });914 });
914 } else if (mem.eql(u8, arg, "-framework")) {915 } else if (mem.eql(u8, arg, "-framework")) {
915 try frameworks.append(args_iter.next() orelse {916 const path = args_iter.next() orelse {
916 fatal("expected parameter after {s}", .{arg});917 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 });
918 } else if (mem.eql(u8, arg, "-install_name")) {925 } else if (mem.eql(u8, arg, "-install_name")) {
919 install_name = args_iter.next() orelse {926 install_name = args_iter.next() orelse {
920 fatal("expected parameter after {s}", .{arg});927 fatal("expected parameter after {s}", .{arg});
...@@ -956,7 +963,10 @@ fn buildOutputType(...@@ -956,7 +963,10 @@ fn buildOutputType(
956 // We don't know whether this library is part of libc or libc++ until963 // We don't know whether this library is part of libc or libc++ until
957 // we resolve the target, so we simply append to the list for now.964 // we resolve the target, so we simply append to the list for now.
958 try system_libs.put(next_arg, .{ .needed = false });965 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 {
960 const next_arg = args_iter.next() orelse {970 const next_arg = args_iter.next() orelse {
961 fatal("expected parameter after {s}", .{arg});971 fatal("expected parameter after {s}", .{arg});
962 };972 };
...@@ -1586,7 +1596,7 @@ fn buildOutputType(...@@ -1586,7 +1596,7 @@ fn buildOutputType(
1586 try clang_argv.appendSlice(it.other_args);1596 try clang_argv.appendSlice(it.other_args);
1587 },1597 },
1588 .framework_dir => try framework_dirs.append(it.only_arg),1598 .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 }),
1590 .nostdlibinc => want_native_include_dirs = false,1600 .nostdlibinc => want_native_include_dirs = false,
1591 .strip => strip = true,1601 .strip => strip = true,
1592 .exec_model => {1602 .exec_model => {
...@@ -1874,7 +1884,19 @@ fn buildOutputType(...@@ -1874,7 +1884,19 @@ fn buildOutputType(
1874 if (i >= linker_args.items.len) {1884 if (i >= linker_args.items.len) {
1875 fatal("expected linker arg after '{s}'", .{arg});1885 fatal("expected linker arg after '{s}'", .{arg});
1876 }1886 }
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 });
1878 } else if (mem.eql(u8, arg, "-compatibility_version")) {1900 } else if (mem.eql(u8, arg, "-compatibility_version")) {
1879 i += 1;1901 i += 1;
1880 if (i >= linker_args.items.len) {1902 if (i >= linker_args.items.len) {
...@@ -2244,7 +2266,7 @@ fn buildOutputType(...@@ -2244,7 +2266,7 @@ fn buildOutputType(
22442266
2245 if (comptime builtin.target.isDarwin()) {2267 if (comptime builtin.target.isDarwin()) {
2246 // If we want to link against frameworks, we need system headers.2268 // 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)
2248 want_native_include_dirs = true;2270 want_native_include_dirs = true;
2249 }2271 }
22502272
...@@ -2734,7 +2756,7 @@ fn buildOutputType(...@@ -2734,7 +2756,7 @@ fn buildOutputType(
2734 .c_source_files = c_source_files.items,2756 .c_source_files = c_source_files.items,
2735 .link_objects = link_objects.items,2757 .link_objects = link_objects.items,
2736 .framework_dirs = framework_dirs.items,2758 .framework_dirs = framework_dirs.items,
2737 .frameworks = frameworks.items,2759 .frameworks = frameworks,
2738 .system_lib_names = system_libs.keys(),2760 .system_lib_names = system_libs.keys(),
2739 .system_lib_infos = system_libs.values(),2761 .system_lib_infos = system_libs.values(),
2740 .wasi_emulated_libs = wasi_emulated_libs.items,2762 .wasi_emulated_libs = wasi_emulated_libs.items,
test/link.zig+9
...@@ -45,6 +45,15 @@ pub fn addCases(cases: *tests.StandaloneContext) void {...@@ -45,6 +45,15 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
45 .requires_macos_sdk = true,45 .requires_macos_sdk = true,
46 });46 });
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
48 // Try to build and run an Objective-C executable.57 // Try to build and run an Objective-C executable.
49 cases.addBuildFile("test/link/macho/objc/build.zig", .{58 cases.addBuildFile("test/link/macho/objc/build.zig", .{
50 .build_modes = true,59 .build_modes = true,
test/link/macho/dead_strip_dylibs/build.zig+1-1
...@@ -6,6 +6,7 @@ pub fn build(b: *Builder) void {...@@ -6,6 +6,7 @@ pub fn build(b: *Builder) void {
6 const mode = b.standardReleaseOptions();6 const mode = b.standardReleaseOptions();
77
8 const test_step = b.step("test", "Test the program");8 const test_step = b.step("test", "Test the program");
9 test_step.dependOn(b.getInstallStep());
910
10 {11 {
11 // Without -dead_strip_dylibs we expect `-la` to include liba.dylib in the final executable12 // 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 {...@@ -37,7 +38,6 @@ pub fn build(b: *Builder) void {
3738
38fn createScenario(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep {39fn createScenario(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep {
39 const exe = b.addExecutable("test", null);40 const exe = b.addExecutable("test", null);
40 b.default_step.dependOn(&exe.step);
41 exe.addCSourceFile("main.c", &[0][]const u8{});41 exe.addCSourceFile("main.c", &[0][]const u8{});
42 exe.setBuildMode(mode);42 exe.setBuildMode(mode);
43 exe.linkLibC();43 exe.linkLibC();
test/link/macho/dead_strip_dylibs/main.c+2-1
...@@ -1,10 +1,11 @@...@@ -1,10 +1,11 @@
1#include <objc/runtime.h>1#include <objc/runtime.h>
22
3int main() {3int main(int argc, char* argv[]) {
4 if (objc_getClass("NSObject") == 0) {4 if (objc_getClass("NSObject") == 0) {
5 return -1;5 return -1;
6 }6 }
7 if (objc_getClass("NSApplication") == 0) {7 if (objc_getClass("NSApplication") == 0) {
8 return -2;8 return -2;
9 }9 }
10 return 0;
10}11}
test/link/macho/dylib/main.c+1-1
...@@ -3,7 +3,7 @@...@@ -3,7 +3,7 @@
3char* hello();3char* hello();
4extern char world[];4extern char world[];
55
6int main() {6int main(int argc, char* argv[]) {
7 printf("%s %s", hello(), world);7 printf("%s %s", hello(), world);
8 return 0;8 return 0;
9}9}
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 @@...@@ -3,7 +3,7 @@
3char* hello();3char* hello();
4extern char world[];4extern char world[];
55
6int main() {6int main(int argc, char* argv[]) {
7 printf("%s %s", hello(), world);7 printf("%s %s", hello(), world);
8 return 0;8 return 0;
9}9}