authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-28 07:22:19+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-06-28 07:22:19+02:00
logd4623a8a07bf8fe3bed3b4ee50339960d623958e
tree949f6078a42f12415508667377b40ea0e84eac0b
parenta76775b50a65fd0ea0fd17d6ef3c42058df13997
parent0dd28920daa5127ffe5a3691343fa519f7547cfd
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #11941 from ziglang/macho-stripping-dylibs

macho: handle `-dead_strip_dylibs`, `-needed-lx` and `-needed_framework x` flags

20 files changed, 318 insertions(+), 100 deletions(-)

lib/std/build.zig+67-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,
......@@ -1601,6 +1600,9 @@ pub const LibExeObjStep = struct {
16011600 /// and start of `__TEXT,__text` section to a value fitting all paths expanded to MAXPATHLEN.
16021601 headerpad_max_install_names: bool = false,
16031602
1603 /// (Darwin) Remove dylibs that are unreachable by the entry point or exported symbols.
1604 dead_strip_dylibs: bool = false,
1605
16041606 /// Position Independent Code
16051607 force_pic: ?bool = null,
16061608
......@@ -1640,6 +1642,7 @@ pub const LibExeObjStep = struct {
16401642
16411643 pub const SystemLib = struct {
16421644 name: []const u8,
1645 needed: bool,
16431646 use_pkg_config: enum {
16441647 /// Don't use pkg-config, just pass -lfoo where foo is name.
16451648 no,
......@@ -1741,7 +1744,7 @@ pub const LibExeObjStep = struct {
17411744 .kind = kind,
17421745 .root_src = root_src,
17431746 .name = name,
1744 .frameworks = BufSet.init(builder.allocator),
1747 .frameworks = StringHashMap(bool).init(builder.allocator),
17451748 .step = Step.init(base_id, name, builder.allocator, make),
17461749 .version = ver,
17471750 .out_filename = undefined,
......@@ -1890,8 +1893,11 @@ pub const LibExeObjStep = struct {
18901893 }
18911894
18921895 pub fn linkFramework(self: *LibExeObjStep, framework_name: []const u8) void {
1893 // Note: No need to dupe because frameworks dupes internally.
1894 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;
18951901 }
18961902
18971903 /// Returns whether the library, executable, or object depends on a particular system library.
......@@ -1932,6 +1938,7 @@ pub const LibExeObjStep = struct {
19321938 self.link_objects.append(.{
19331939 .system_lib = .{
19341940 .name = "c",
1941 .needed = false,
19351942 .use_pkg_config = .no,
19361943 },
19371944 }) catch unreachable;
......@@ -1944,6 +1951,7 @@ pub const LibExeObjStep = struct {
19441951 self.link_objects.append(.{
19451952 .system_lib = .{
19461953 .name = "c++",
1954 .needed = false,
19471955 .use_pkg_config = .no,
19481956 },
19491957 }) catch unreachable;
......@@ -1968,6 +1976,19 @@ pub const LibExeObjStep = struct {
19681976 self.link_objects.append(.{
19691977 .system_lib = .{
19701978 .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,
19711992 .use_pkg_config = .no,
19721993 },
19731994 }) catch unreachable;
......@@ -1979,6 +2000,19 @@ pub const LibExeObjStep = struct {
19792000 self.link_objects.append(.{
19802001 .system_lib = .{
19812002 .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,
19822016 .use_pkg_config = .force,
19832017 },
19842018 }) catch unreachable;
......@@ -2081,6 +2115,14 @@ pub const LibExeObjStep = struct {
20812115 }
20822116
20832117 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 {
20842126 if (isLibCLibrary(name)) {
20852127 self.linkLibC();
20862128 return;
......@@ -2093,6 +2135,7 @@ pub const LibExeObjStep = struct {
20932135 self.link_objects.append(.{
20942136 .system_lib = .{
20952137 .name = self.builder.dupe(name),
2138 .needed = needed,
20962139 .use_pkg_config = .yes,
20972140 },
20982141 }) catch unreachable;
......@@ -2434,7 +2477,7 @@ pub const LibExeObjStep = struct {
24342477 if (!other.isDynamicLibrary()) {
24352478 var it = other.frameworks.iterator();
24362479 while (it.next()) |framework| {
2437 self.frameworks.insert(framework.*) catch unreachable;
2480 self.frameworks.put(framework.key_ptr.*, framework.value_ptr.*) catch unreachable;
24382481 }
24392482 }
24402483 },
......@@ -2470,8 +2513,9 @@ pub const LibExeObjStep = struct {
24702513 },
24712514
24722515 .system_lib => |system_lib| {
2516 const prefix: []const u8 = if (system_lib.needed) "-needed-l" else "-l";
24732517 switch (system_lib.use_pkg_config) {
2474 .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 })),
24752519 .yes, .force => {
24762520 if (self.runPkgConfig(system_lib.name)) |args| {
24772521 try zig_args.appendSlice(args);
......@@ -2485,7 +2529,10 @@ pub const LibExeObjStep = struct {
24852529 .yes => {
24862530 // pkg-config failed, so fall back to linking the library
24872531 // by name directly.
2488 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 }));
24892536 },
24902537 .force => {
24912538 panic("pkg-config failed for library {s}", .{system_lib.name});
......@@ -2676,6 +2723,9 @@ pub const LibExeObjStep = struct {
26762723 if (self.headerpad_max_install_names) {
26772724 try zig_args.append("-headerpad_max_install_names");
26782725 }
2726 if (self.dead_strip_dylibs) {
2727 try zig_args.append("-dead_strip_dylibs");
2728 }
26792729
26802730 if (self.bundle_compiler_rt) |x| {
26812731 if (x) {
......@@ -2966,9 +3016,15 @@ pub const LibExeObjStep = struct {
29663016 }
29673017
29683018 var it = self.frameworks.iterator();
2969 while (it.next()) |framework| {
2970 zig_args.append("-framework") catch unreachable;
2971 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;
29723028 }
29733029 } else {
29743030 if (self.framework_dirs.items.len > 0) {
src/Compilation.zig+10-6
......@@ -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:
......@@ -911,6 +911,8 @@ pub const InitOptions = struct {
911911 headerpad_size: ?u32 = null,
912912 /// (Darwin) set enough space as if all paths were MATPATHLEN
913913 headerpad_max_install_names: bool = false,
914 /// (Darwin) remove dylibs that are unreachable by the entry point or exported symbols
915 dead_strip_dylibs: bool = false,
914916};
915917
916918fn addPackageTableToCacheHash(
......@@ -1095,7 +1097,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
10951097 // Our linker can't handle objects or most advanced options yet.
10961098 if (options.link_objects.len != 0 or
10971099 options.c_source_files.len != 0 or
1098 options.frameworks.len != 0 or
1100 options.frameworks.count() != 0 or
10991101 options.system_lib_names.len != 0 or
11001102 options.link_libc or options.link_libcpp or
11011103 link_eh_frame_hdr or
......@@ -1213,7 +1215,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
12131215 options.target,
12141216 options.is_native_abi,
12151217 link_libc,
1216 options.system_lib_names.len != 0 or options.frameworks.len != 0,
1218 options.system_lib_names.len != 0 or options.frameworks.count() != 0,
12171219 options.libc_installation,
12181220 options.native_darwin_sdk != null,
12191221 );
......@@ -1754,6 +1756,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
17541756 .search_strategy = options.search_strategy,
17551757 .headerpad_size = options.headerpad_size,
17561758 .headerpad_max_install_names = options.headerpad_max_install_names,
1759 .dead_strip_dylibs = options.dead_strip_dylibs,
17571760 });
17581761 errdefer bin_file.destroy();
17591762 comp.* = .{
......@@ -2369,7 +2372,7 @@ fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemo
23692372/// to remind the programmer to update multiple related pieces of code that
23702373/// are in different locations. Bump this number when adding or deleting
23712374/// anything from the link cache manifest.
2372pub const link_hash_implementation_version = 6;
2375pub const link_hash_implementation_version = 7;
23732376
23742377fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void {
23752378 const gpa = comp.gpa;
......@@ -2379,7 +2382,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
23792382 defer arena_allocator.deinit();
23802383 const arena = arena_allocator.allocator();
23812384
2382 comptime assert(link_hash_implementation_version == 6);
2385 comptime assert(link_hash_implementation_version == 7);
23832386
23842387 if (comp.bin_file.options.module) |mod| {
23852388 const main_zig_file = try mod.main_pkg.root_src_directory.join(arena, &[_][]const u8{
......@@ -2482,12 +2485,13 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
24822485
24832486 // Mach-O specific stuff
24842487 man.hash.addListOfBytes(comp.bin_file.options.framework_dirs);
2485 man.hash.addListOfBytes(comp.bin_file.options.frameworks);
2488 link.hashAddSystemLibs(&man.hash, comp.bin_file.options.frameworks);
24862489 try man.addOptionalFile(comp.bin_file.options.entitlements);
24872490 man.hash.addOptional(comp.bin_file.options.pagezero_size);
24882491 man.hash.addOptional(comp.bin_file.options.search_strategy);
24892492 man.hash.addOptional(comp.bin_file.options.headerpad_size);
24902493 man.hash.add(comp.bin_file.options.headerpad_max_install_names);
2494 man.hash.add(comp.bin_file.options.dead_strip_dylibs);
24912495
24922496 // COFF specific stuff
24932497 man.hash.addOptional(comp.bin_file.options.subsystem);
src/link.zig+4-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,
......@@ -199,6 +199,9 @@ pub const Options = struct {
199199 /// (Darwin) set enough space as if all paths were MATPATHLEN
200200 headerpad_max_install_names: bool = false,
201201
202 /// (Darwin) remove dylibs that are unreachable by the entry point or exported symbols
203 dead_strip_dylibs: bool = false,
204
202205 pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
203206 return if (options.use_lld) .Obj else options.output_mode;
204207 }
src/link/Coff.zig+1-1
......@@ -969,7 +969,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !
969969 man = comp.cache_parent.obtain();
970970 self.base.releaseLock();
971971
972 comptime assert(Compilation.link_hash_implementation_version == 6);
972 comptime assert(Compilation.link_hash_implementation_version == 7);
973973
974974 for (self.base.options.objects) |obj| {
975975 _ = try man.addFile(obj.path, null);
src/link/Elf.zig+1-1
......@@ -1298,7 +1298,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
12981298 // We are about to obtain this lock, so here we give other processes a chance first.
12991299 self.base.releaseLock();
13001300
1301 comptime assert(Compilation.link_hash_implementation_version == 6);
1301 comptime assert(Compilation.link_hash_implementation_version == 7);
13021302
13031303 try man.addOptionalFile(self.base.options.linker_script);
13041304 try man.addOptionalFile(self.base.options.version_script);
src/link/MachO.zig+58-28
......@@ -541,7 +541,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
541541 // We are about to obtain this lock, so here we give other processes a chance first.
542542 self.base.releaseLock();
543543
544 comptime assert(Compilation.link_hash_implementation_version == 6);
544 comptime assert(Compilation.link_hash_implementation_version == 7);
545545
546546 for (self.base.options.objects) |obj| {
547547 _ = try man.addFile(obj.path, null);
......@@ -558,9 +558,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
558558 man.hash.addOptional(self.base.options.search_strategy);
559559 man.hash.addOptional(self.base.options.headerpad_size);
560560 man.hash.add(self.base.options.headerpad_max_install_names);
561 man.hash.add(self.base.options.dead_strip_dylibs);
561562 man.hash.addListOfBytes(self.base.options.lib_dirs);
562563 man.hash.addListOfBytes(self.base.options.framework_dirs);
563 man.hash.addListOfBytes(self.base.options.frameworks);
564 link.hashAddSystemLibs(&man.hash, self.base.options.frameworks);
564565 man.hash.addListOfBytes(self.base.options.rpath_list);
565566 if (is_dyn_lib) {
566567 man.hash.addOptionalBytes(self.base.options.install_name);
......@@ -767,19 +768,20 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
767768 }
768769
769770 // Shared and static libraries passed via `-l` flag.
770 var search_lib_names = std.ArrayList([]const u8).init(arena);
771 var candidate_libs = std.StringArrayHashMap(Compilation.SystemLib).init(arena);
771772
772 const system_libs = self.base.options.system_libs.keys();
773 for (system_libs) |link_lib| {
773 const system_lib_names = self.base.options.system_libs.keys();
774 for (system_lib_names) |system_lib_name| {
774775 // By this time, we depend on these libs being dynamically linked libraries and not static libraries
775776 // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which
776777 // case we want to avoid prepending "-l".
777 if (Compilation.classifyFileExt(link_lib) == .shared_library) {
778 try positionals.append(link_lib);
778 if (Compilation.classifyFileExt(system_lib_name) == .shared_library) {
779 try positionals.append(system_lib_name);
779780 continue;
780781 }
781782
782 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);
783785 }
784786
785787 var lib_dirs = std.ArrayList([]const u8).init(arena);
......@@ -791,18 +793,18 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
791793 }
792794 }
793795
794 var libs = std.ArrayList([]const u8).init(arena);
796 var libs = std.StringArrayHashMap(Compilation.SystemLib).init(arena);
795797
796798 // Assume ld64 default -search_paths_first if no strategy specified.
797799 const search_strategy = self.base.options.search_strategy orelse .paths_first;
798 outer: for (search_lib_names.items) |lib_name| {
800 outer: for (candidate_libs.keys()) |lib_name| {
799801 switch (search_strategy) {
800802 .paths_first => {
801803 // Look in each directory for a dylib (stub first), and then for archive
802804 for (lib_dirs.items) |dir| {
803805 for (&[_][]const u8{ ".tbd", ".dylib", ".a" }) |ext| {
804806 if (try resolveLib(arena, dir, lib_name, ext)) |full_path| {
805 try libs.append(full_path);
807 try libs.put(full_path, candidate_libs.get(lib_name).?);
806808 continue :outer;
807809 }
808810 }
......@@ -816,13 +818,13 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
816818 for (lib_dirs.items) |dir| {
817819 for (&[_][]const u8{ ".tbd", ".dylib" }) |ext| {
818820 if (try resolveLib(arena, dir, lib_name, ext)) |full_path| {
819 try libs.append(full_path);
821 try libs.put(full_path, candidate_libs.get(lib_name).?);
820822 continue :outer;
821823 }
822824 }
823825 } else for (lib_dirs.items) |dir| {
824826 if (try resolveLib(arena, dir, lib_name, ".a")) |full_path| {
825 try libs.append(full_path);
827 try libs.put(full_path, candidate_libs.get(lib_name).?);
826828 } else {
827829 log.warn("library not found for '-l{s}'", .{lib_name});
828830 lib_not_found = true;
......@@ -846,7 +848,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
846848 // re-exports every single symbol definition.
847849 for (lib_dirs.items) |dir| {
848850 if (try resolveLib(arena, dir, "System", ".tbd")) |full_path| {
849 try libs.append(full_path);
851 try libs.put(full_path, .{ .needed = false });
850852 libsystem_available = true;
851853 break :blk;
852854 }
......@@ -856,8 +858,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
856858 for (lib_dirs.items) |dir| {
857859 if (try resolveLib(arena, dir, "System", ".dylib")) |libsystem_path| {
858860 if (try resolveLib(arena, dir, "c", ".dylib")) |libc_path| {
859 try libs.append(libsystem_path);
860 try libs.append(libc_path);
861 try libs.put(libsystem_path, .{ .needed = false });
862 try libs.put(libc_path, .{ .needed = false });
861863 libsystem_available = true;
862864 break :blk;
863865 }
......@@ -871,7 +873,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
871873 const full_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
872874 "libc", "darwin", libsystem_name,
873875 });
874 try libs.append(full_path);
876 try libs.put(full_path, .{ .needed = false });
875877 }
876878
877879 // frameworks
......@@ -884,16 +886,16 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
884886 }
885887 }
886888
887 outer: for (self.base.options.frameworks) |framework| {
889 outer: for (self.base.options.frameworks.keys()) |f_name| {
888890 for (framework_dirs.items) |dir| {
889891 for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
890 if (try resolveFramework(arena, dir, framework, ext)) |full_path| {
891 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).?);
892894 continue :outer;
893895 }
894896 }
895897 } else {
896 log.warn("framework not found for '-framework {s}'", .{framework});
898 log.warn("framework not found for '-framework {s}'", .{f_name});
897899 framework_not_found = true;
898900 }
899901 }
......@@ -987,6 +989,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
987989 try argv.append("-headerpad_max_install_names");
988990 }
989991
992 if (self.base.options.dead_strip_dylibs) {
993 try argv.append("-dead_strip_dylibs");
994 }
995
990996 if (self.base.options.entry) |entry| {
991997 try argv.append("-e");
992998 try argv.append(entry);
......@@ -1020,15 +1026,25 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
10201026 try argv.append("-lc");
10211027
10221028 for (self.base.options.system_libs.keys()) |l_name| {
1023 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);
10241035 }
10251036
10261037 for (self.base.options.lib_dirs) |lib_dir| {
10271038 try argv.append(try std.fmt.allocPrint(arena, "-L{s}", .{lib_dir}));
10281039 }
10291040
1030 for (self.base.options.frameworks) |framework| {
1031 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);
10321048 }
10331049
10341050 for (self.base.options.framework_dirs) |framework_dir| {
......@@ -1051,7 +1067,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
10511067 defer dependent_libs.deinit();
10521068 try self.parseInputFiles(positionals.items, self.base.options.sysroot, &dependent_libs);
10531069 try self.parseAndForceLoadStaticArchives(must_link_archives.keys());
1054 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);
10551071 try self.parseDependentLibs(self.base.options.sysroot, &dependent_libs);
10561072 }
10571073
......@@ -1376,6 +1392,7 @@ const DylibCreateOpts = struct {
13761392 dependent_libs: *std.fifo.LinearFifo(Dylib.Id, .Dynamic),
13771393 id: ?Dylib.Id = null,
13781394 is_dependent: bool = false,
1395 is_needed: bool = false,
13791396};
13801397
13811398pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDylibError!bool {
......@@ -1425,7 +1442,12 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy
14251442 try self.dylibs.append(self.base.allocator, dylib);
14261443 try self.dylibs_map.putNoClobber(self.base.allocator, dylib.id.?.name, dylib_id);
14271444
1428 if (!(opts.is_dependent or self.referenced_dylibs.contains(dylib_id))) {
1445 const should_link_dylib_even_if_unreachable = blk: {
1446 if (self.base.options.dead_strip_dylibs and !opts.is_needed) break :blk false;
1447 break :blk !(opts.is_dependent or self.referenced_dylibs.contains(dylib_id));
1448 };
1449
1450 if (should_link_dylib_even_if_unreachable) {
14291451 try self.addLoadDylibLC(dylib_id);
14301452 try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {});
14311453 }
......@@ -1469,12 +1491,20 @@ fn parseAndForceLoadStaticArchives(self: *MachO, files: []const []const u8) !voi
14691491 }
14701492}
14711493
1472fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8, dependent_libs: anytype) !void {
1473 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];
14741503 log.debug("parsing lib path '{s}'", .{lib});
14751504 if (try self.parseDylib(lib, .{
14761505 .syslibroot = syslibroot,
14771506 .dependent_libs = dependent_libs,
1507 .is_needed = lib_info.needed,
14781508 })) continue;
14791509 if (try self.parseArchive(lib, false)) continue;
14801510
src/link/Wasm.zig+1-1
......@@ -2546,7 +2546,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
25462546 // We are about to obtain this lock, so here we give other processes a chance first.
25472547 self.base.releaseLock();
25482548
2549 comptime assert(Compilation.link_hash_implementation_version == 6);
2549 comptime assert(Compilation.link_hash_implementation_version == 7);
25502550
25512551 for (self.base.options.objects) |obj| {
25522552 _ = try man.addFile(obj.path, null);
src/main.zig+38-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
......@@ -452,6 +454,7 @@ const usage_build_generic =
452454 \\ -search_dylibs_first (Darwin) search `libx.dylib` in each dir in library search paths, then `libx.a`
453455 \\ -headerpad [value] (Darwin) set minimum space for future expansion of the load commands in hexadecimal notation
454456 \\ -headerpad_max_install_names (Darwin) set enough space as if all paths were MAXPATHLEN
457 \\ -dead_strip_dylibs (Darwin) remove dylibs that are unreachable by the entry point or exported symbols
455458 \\ --import-memory (WebAssembly) import memory from the environment
456459 \\ --import-table (WebAssembly) import function table from the host environment
457460 \\ --export-table (WebAssembly) export function table to the host environment
......@@ -703,6 +706,7 @@ fn buildOutputType(
703706 var search_strategy: ?link.File.MachO.SearchStrategy = null;
704707 var headerpad_size: ?u32 = null;
705708 var headerpad_max_install_names: bool = false;
709 var dead_strip_dylibs: bool = false;
706710
707711 // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.
708712 // This array is populated by zig cc frontend and then has to be converted to zig-style
......@@ -748,8 +752,7 @@ fn buildOutputType(
748752 var framework_dirs = std.ArrayList([]const u8).init(gpa);
749753 defer framework_dirs.deinit();
750754
751 var frameworks = std.ArrayList([]const u8).init(gpa);
752 defer frameworks.deinit();
755 var frameworks: std.StringArrayHashMapUnmanaged(Compilation.SystemLib) = .{};
753756
754757 // null means replace with the test executable binary
755758 var test_exec_args = std.ArrayList(?[]const u8).init(gpa);
......@@ -910,9 +913,15 @@ fn buildOutputType(
910913 fatal("expected parameter after {s}", .{arg});
911914 });
912915 } else if (mem.eql(u8, arg, "-framework")) {
913 try frameworks.append(args_iter.next() orelse {
916 const path = args_iter.next() orelse {
914917 fatal("expected parameter after {s}", .{arg});
915 });
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 });
916925 } else if (mem.eql(u8, arg, "-install_name")) {
917926 install_name = args_iter.next() orelse {
918927 fatal("expected parameter after {s}", .{arg});
......@@ -937,6 +946,8 @@ fn buildOutputType(
937946 };
938947 } else if (mem.eql(u8, arg, "-headerpad_max_install_names")) {
939948 headerpad_max_install_names = true;
949 } else if (mem.eql(u8, arg, "-dead_strip_dylibs")) {
950 dead_strip_dylibs = true;
940951 } else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) {
941952 linker_script = args_iter.next() orelse {
942953 fatal("expected parameter after {s}", .{arg});
......@@ -952,7 +963,10 @@ fn buildOutputType(
952963 // We don't know whether this library is part of libc or libc++ until
953964 // we resolve the target, so we simply append to the list for now.
954965 try system_libs.put(next_arg, .{ .needed = false });
955 } 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 {
956970 const next_arg = args_iter.next() orelse {
957971 fatal("expected parameter after {s}", .{arg});
958972 };
......@@ -1582,7 +1596,7 @@ fn buildOutputType(
15821596 try clang_argv.appendSlice(it.other_args);
15831597 },
15841598 .framework_dir => try framework_dirs.append(it.only_arg),
1585 .framework => try frameworks.append(it.only_arg),
1599 .framework => try frameworks.put(gpa, it.only_arg, .{ .needed = false }),
15861600 .nostdlibinc => want_native_include_dirs = false,
15871601 .strip => strip = true,
15881602 .exec_model => {
......@@ -1700,6 +1714,8 @@ fn buildOutputType(
17001714 };
17011715 } else if (mem.eql(u8, arg, "-headerpad_max_install_names")) {
17021716 headerpad_max_install_names = true;
1717 } else if (mem.eql(u8, arg, "-dead_strip_dylibs")) {
1718 dead_strip_dylibs = true;
17031719 } else if (mem.eql(u8, arg, "--gc-sections")) {
17041720 linker_gc_sections = true;
17051721 } else if (mem.eql(u8, arg, "--no-gc-sections")) {
......@@ -1868,7 +1884,19 @@ fn buildOutputType(
18681884 if (i >= linker_args.items.len) {
18691885 fatal("expected linker arg after '{s}'", .{arg});
18701886 }
1871 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 });
18721900 } else if (mem.eql(u8, arg, "-compatibility_version")) {
18731901 i += 1;
18741902 if (i >= linker_args.items.len) {
......@@ -2238,7 +2266,7 @@ fn buildOutputType(
22382266
22392267 if (comptime builtin.target.isDarwin()) {
22402268 // If we want to link against frameworks, we need system headers.
2241 if (framework_dirs.items.len > 0 or frameworks.items.len > 0)
2269 if (framework_dirs.items.len > 0 or frameworks.count() > 0)
22422270 want_native_include_dirs = true;
22432271 }
22442272
......@@ -2728,7 +2756,7 @@ fn buildOutputType(
27282756 .c_source_files = c_source_files.items,
27292757 .link_objects = link_objects.items,
27302758 .framework_dirs = framework_dirs.items,
2731 .frameworks = frameworks.items,
2759 .frameworks = frameworks,
27322760 .system_lib_names = system_libs.keys(),
27332761 .system_lib_infos = system_libs.values(),
27342762 .wasi_emulated_libs = wasi_emulated_libs.items,
......@@ -2821,6 +2849,7 @@ fn buildOutputType(
28212849 .search_strategy = search_strategy,
28222850 .headerpad_size = headerpad_size,
28232851 .headerpad_max_install_names = headerpad_max_install_names,
2852 .dead_strip_dylibs = dead_strip_dylibs,
28242853 }) catch |err| switch (err) {
28252854 error.LibCUnavailable => {
28262855 const target = target_info.target;
test/link.zig+10-1
......@@ -40,7 +40,16 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
4040 .build_modes = true,
4141 });
4242
43 cases.addBuildFile("test/link/macho/frameworks/build.zig", .{
43 cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{
44 .build_modes = true,
45 .requires_macos_sdk = true,
46 });
47
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", .{
4453 .build_modes = true,
4554 .requires_macos_sdk = true,
4655 });
test/link/macho/dead_strip_dylibs/build.zig created+46
......@@ -0,0 +1,46 @@
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 {
12 // Without -dead_strip_dylibs we expect `-la` to include liba.dylib in the final executable
13 const exe = createScenario(b, mode);
14
15 const check = exe.checkObject(.macho);
16 check.checkStart("cmd LOAD_DYLIB");
17 check.checkNext("name {*}Cocoa");
18
19 check.checkStart("cmd LOAD_DYLIB");
20 check.checkNext("name {*}libobjc{*}.dylib");
21
22 test_step.dependOn(&check.step);
23
24 const run_cmd = exe.run();
25 test_step.dependOn(&run_cmd.step);
26 }
27
28 {
29 // With -dead_strip_dylibs, we should include liba.dylib as it's unreachable
30 const exe = createScenario(b, mode);
31 exe.dead_strip_dylibs = true;
32
33 const run_cmd = exe.run();
34 run_cmd.expected_exit_code = @bitCast(u8, @as(i8, -2)); // should fail
35 test_step.dependOn(&run_cmd.step);
36 }
37}
38
39fn createScenario(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep {
40 const exe = b.addExecutable("test", null);
41 exe.addCSourceFile("main.c", &[0][]const u8{});
42 exe.setBuildMode(mode);
43 exe.linkLibC();
44 exe.linkFramework("Cocoa");
45 return exe;
46}
test/link/macho/dead_strip_dylibs/main.c created+11
......@@ -0,0 +1,11 @@
1#include <objc/runtime.h>
2
3int main(int argc, char* argv[]) {
4 if (objc_getClass("NSObject") == 0) {
5 return -1;
6 }
7 if (objc_getClass("NSApplication") == 0) {
8 return -2;
9 }
10 return 0;
11}
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/frameworks/build.zig deleted-32
......@@ -1,32 +0,0 @@
1const std = @import("std");
2const Builder = std.build.Builder;
3
4pub fn build(b: *Builder) void {
5 const mode = b.standardReleaseOptions();
6
7 const test_step = b.step("test", "Test the program");
8
9 const exe = b.addExecutable("test", null);
10 b.default_step.dependOn(&exe.step);
11 exe.addCSourceFile("main.c", &[0][]const u8{});
12 exe.setBuildMode(mode);
13 exe.linkLibC();
14 exe.linkFramework("Cocoa");
15
16 const check = exe.checkObject(.macho);
17 check.checkStart("cmd LOAD_DYLIB");
18 check.checkNext("name {*}Cocoa");
19
20 switch (mode) {
21 .Debug, .ReleaseSafe => {
22 check.checkStart("cmd LOAD_DYLIB");
23 check.checkNext("name {*}libobjc{*}.dylib");
24 },
25 else => {},
26 }
27
28 test_step.dependOn(&check.step);
29
30 const run_cmd = exe.run();
31 test_step.dependOn(&run_cmd.step);
32}
test/link/macho/frameworks/main.c deleted-7
......@@ -1,7 +0,0 @@
1#include <assert.h>
2#include <objc/runtime.h>
3
4int main() {
5 assert(objc_getClass("NSObject") > 0);
6 assert(objc_getClass("NSApplication") > 0);
7}
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}