mirror of
https://github.com/ziglang/zig.git
synced 2025-12-30 18:13:19 +00:00
Merge pull request #11941 from ziglang/macho-stripping-dylibs
macho: handle `-dead_strip_dylibs`, `-needed-lx` and `-needed_framework x` flags
This commit is contained in:
commit
d4623a8a07
@ -11,7 +11,6 @@ const ArrayList = std.ArrayList;
|
||||
const StringHashMap = std.StringHashMap;
|
||||
const Allocator = mem.Allocator;
|
||||
const process = std.process;
|
||||
const BufSet = std.BufSet;
|
||||
const EnvMap = std.process.EnvMap;
|
||||
const fmt_lib = std.fmt;
|
||||
const File = std.fs.File;
|
||||
@ -1484,7 +1483,7 @@ pub const LibExeObjStep = struct {
|
||||
lib_paths: ArrayList([]const u8),
|
||||
rpaths: ArrayList([]const u8),
|
||||
framework_dirs: ArrayList([]const u8),
|
||||
frameworks: BufSet,
|
||||
frameworks: StringHashMap(bool),
|
||||
verbose_link: bool,
|
||||
verbose_cc: bool,
|
||||
emit_analysis: EmitOption = .default,
|
||||
@ -1601,6 +1600,9 @@ pub const LibExeObjStep = struct {
|
||||
/// and start of `__TEXT,__text` section to a value fitting all paths expanded to MAXPATHLEN.
|
||||
headerpad_max_install_names: bool = false,
|
||||
|
||||
/// (Darwin) Remove dylibs that are unreachable by the entry point or exported symbols.
|
||||
dead_strip_dylibs: bool = false,
|
||||
|
||||
/// Position Independent Code
|
||||
force_pic: ?bool = null,
|
||||
|
||||
@ -1640,6 +1642,7 @@ pub const LibExeObjStep = struct {
|
||||
|
||||
pub const SystemLib = struct {
|
||||
name: []const u8,
|
||||
needed: bool,
|
||||
use_pkg_config: enum {
|
||||
/// Don't use pkg-config, just pass -lfoo where foo is name.
|
||||
no,
|
||||
@ -1741,7 +1744,7 @@ pub const LibExeObjStep = struct {
|
||||
.kind = kind,
|
||||
.root_src = root_src,
|
||||
.name = name,
|
||||
.frameworks = BufSet.init(builder.allocator),
|
||||
.frameworks = StringHashMap(bool).init(builder.allocator),
|
||||
.step = Step.init(base_id, name, builder.allocator, make),
|
||||
.version = ver,
|
||||
.out_filename = undefined,
|
||||
@ -1890,8 +1893,11 @@ pub const LibExeObjStep = struct {
|
||||
}
|
||||
|
||||
pub fn linkFramework(self: *LibExeObjStep, framework_name: []const u8) void {
|
||||
// Note: No need to dupe because frameworks dupes internally.
|
||||
self.frameworks.insert(framework_name) catch unreachable;
|
||||
self.frameworks.put(self.builder.dupe(framework_name), false) catch unreachable;
|
||||
}
|
||||
|
||||
pub fn linkFrameworkNeeded(self: *LibExeObjStep, framework_name: []const u8) void {
|
||||
self.frameworks.put(self.builder.dupe(framework_name), true) catch unreachable;
|
||||
}
|
||||
|
||||
/// Returns whether the library, executable, or object depends on a particular system library.
|
||||
@ -1932,6 +1938,7 @@ pub const LibExeObjStep = struct {
|
||||
self.link_objects.append(.{
|
||||
.system_lib = .{
|
||||
.name = "c",
|
||||
.needed = false,
|
||||
.use_pkg_config = .no,
|
||||
},
|
||||
}) catch unreachable;
|
||||
@ -1944,6 +1951,7 @@ pub const LibExeObjStep = struct {
|
||||
self.link_objects.append(.{
|
||||
.system_lib = .{
|
||||
.name = "c++",
|
||||
.needed = false,
|
||||
.use_pkg_config = .no,
|
||||
},
|
||||
}) catch unreachable;
|
||||
@ -1968,6 +1976,19 @@ pub const LibExeObjStep = struct {
|
||||
self.link_objects.append(.{
|
||||
.system_lib = .{
|
||||
.name = self.builder.dupe(name),
|
||||
.needed = false,
|
||||
.use_pkg_config = .no,
|
||||
},
|
||||
}) catch unreachable;
|
||||
}
|
||||
|
||||
/// This one has no integration with anything, it just puts -needed-lname on the command line.
|
||||
/// Prefer to use `linkSystemLibraryNeeded` instead.
|
||||
pub fn linkSystemLibraryNeededName(self: *LibExeObjStep, name: []const u8) void {
|
||||
self.link_objects.append(.{
|
||||
.system_lib = .{
|
||||
.name = self.builder.dupe(name),
|
||||
.needed = true,
|
||||
.use_pkg_config = .no,
|
||||
},
|
||||
}) catch unreachable;
|
||||
@ -1979,6 +2000,19 @@ pub const LibExeObjStep = struct {
|
||||
self.link_objects.append(.{
|
||||
.system_lib = .{
|
||||
.name = self.builder.dupe(lib_name),
|
||||
.needed = false,
|
||||
.use_pkg_config = .force,
|
||||
},
|
||||
}) catch unreachable;
|
||||
}
|
||||
|
||||
/// This links against a system library, exclusively using pkg-config to find the library.
|
||||
/// Prefer to use `linkSystemLibraryNeeded` instead.
|
||||
pub fn linkSystemLibraryNeededPkgConfigOnly(self: *LibExeObjStep, lib_name: []const u8) void {
|
||||
self.link_objects.append(.{
|
||||
.system_lib = .{
|
||||
.name = self.builder.dupe(lib_name),
|
||||
.needed = true,
|
||||
.use_pkg_config = .force,
|
||||
},
|
||||
}) catch unreachable;
|
||||
@ -2081,6 +2115,14 @@ pub const LibExeObjStep = struct {
|
||||
}
|
||||
|
||||
pub fn linkSystemLibrary(self: *LibExeObjStep, name: []const u8) void {
|
||||
self.linkSystemLibraryInner(name, false);
|
||||
}
|
||||
|
||||
pub fn linkSystemLibraryNeeded(self: *LibExeObjStep, name: []const u8) void {
|
||||
self.linkSystemLibraryInner(name, true);
|
||||
}
|
||||
|
||||
fn linkSystemLibraryInner(self: *LibExeObjStep, name: []const u8, needed: bool) void {
|
||||
if (isLibCLibrary(name)) {
|
||||
self.linkLibC();
|
||||
return;
|
||||
@ -2093,6 +2135,7 @@ pub const LibExeObjStep = struct {
|
||||
self.link_objects.append(.{
|
||||
.system_lib = .{
|
||||
.name = self.builder.dupe(name),
|
||||
.needed = needed,
|
||||
.use_pkg_config = .yes,
|
||||
},
|
||||
}) catch unreachable;
|
||||
@ -2434,7 +2477,7 @@ pub const LibExeObjStep = struct {
|
||||
if (!other.isDynamicLibrary()) {
|
||||
var it = other.frameworks.iterator();
|
||||
while (it.next()) |framework| {
|
||||
self.frameworks.insert(framework.*) catch unreachable;
|
||||
self.frameworks.put(framework.key_ptr.*, framework.value_ptr.*) catch unreachable;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -2470,8 +2513,9 @@ pub const LibExeObjStep = struct {
|
||||
},
|
||||
|
||||
.system_lib => |system_lib| {
|
||||
const prefix: []const u8 = if (system_lib.needed) "-needed-l" else "-l";
|
||||
switch (system_lib.use_pkg_config) {
|
||||
.no => try zig_args.append(builder.fmt("-l{s}", .{system_lib.name})),
|
||||
.no => try zig_args.append(builder.fmt("{s}{s}", .{ prefix, system_lib.name })),
|
||||
.yes, .force => {
|
||||
if (self.runPkgConfig(system_lib.name)) |args| {
|
||||
try zig_args.appendSlice(args);
|
||||
@ -2485,7 +2529,10 @@ pub const LibExeObjStep = struct {
|
||||
.yes => {
|
||||
// pkg-config failed, so fall back to linking the library
|
||||
// by name directly.
|
||||
try zig_args.append(builder.fmt("-l{s}", .{system_lib.name}));
|
||||
try zig_args.append(builder.fmt("{s}{s}", .{
|
||||
prefix,
|
||||
system_lib.name,
|
||||
}));
|
||||
},
|
||||
.force => {
|
||||
panic("pkg-config failed for library {s}", .{system_lib.name});
|
||||
@ -2676,6 +2723,9 @@ pub const LibExeObjStep = struct {
|
||||
if (self.headerpad_max_install_names) {
|
||||
try zig_args.append("-headerpad_max_install_names");
|
||||
}
|
||||
if (self.dead_strip_dylibs) {
|
||||
try zig_args.append("-dead_strip_dylibs");
|
||||
}
|
||||
|
||||
if (self.bundle_compiler_rt) |x| {
|
||||
if (x) {
|
||||
@ -2966,9 +3016,15 @@ pub const LibExeObjStep = struct {
|
||||
}
|
||||
|
||||
var it = self.frameworks.iterator();
|
||||
while (it.next()) |framework| {
|
||||
zig_args.append("-framework") catch unreachable;
|
||||
zig_args.append(framework.*) catch unreachable;
|
||||
while (it.next()) |entry| {
|
||||
const name = entry.key_ptr.*;
|
||||
const needed = entry.value_ptr.*;
|
||||
if (needed) {
|
||||
zig_args.append("-needed_framework") catch unreachable;
|
||||
} else {
|
||||
zig_args.append("-framework") catch unreachable;
|
||||
}
|
||||
zig_args.append(name) catch unreachable;
|
||||
}
|
||||
} else {
|
||||
if (self.framework_dirs.items.len > 0) {
|
||||
|
||||
@ -791,7 +791,7 @@ pub const InitOptions = struct {
|
||||
c_source_files: []const CSourceFile = &[0]CSourceFile{},
|
||||
link_objects: []LinkObject = &[0]LinkObject{},
|
||||
framework_dirs: []const []const u8 = &[0][]const u8{},
|
||||
frameworks: []const []const u8 = &[0][]const u8{},
|
||||
frameworks: std.StringArrayHashMapUnmanaged(SystemLib) = .{},
|
||||
system_lib_names: []const []const u8 = &.{},
|
||||
system_lib_infos: []const SystemLib = &.{},
|
||||
/// These correspond to the WASI libc emulated subcomponents including:
|
||||
@ -911,6 +911,8 @@ pub const InitOptions = struct {
|
||||
headerpad_size: ?u32 = null,
|
||||
/// (Darwin) set enough space as if all paths were MATPATHLEN
|
||||
headerpad_max_install_names: bool = false,
|
||||
/// (Darwin) remove dylibs that are unreachable by the entry point or exported symbols
|
||||
dead_strip_dylibs: bool = false,
|
||||
};
|
||||
|
||||
fn addPackageTableToCacheHash(
|
||||
@ -1095,7 +1097,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
// Our linker can't handle objects or most advanced options yet.
|
||||
if (options.link_objects.len != 0 or
|
||||
options.c_source_files.len != 0 or
|
||||
options.frameworks.len != 0 or
|
||||
options.frameworks.count() != 0 or
|
||||
options.system_lib_names.len != 0 or
|
||||
options.link_libc or options.link_libcpp or
|
||||
link_eh_frame_hdr or
|
||||
@ -1213,7 +1215,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
options.target,
|
||||
options.is_native_abi,
|
||||
link_libc,
|
||||
options.system_lib_names.len != 0 or options.frameworks.len != 0,
|
||||
options.system_lib_names.len != 0 or options.frameworks.count() != 0,
|
||||
options.libc_installation,
|
||||
options.native_darwin_sdk != null,
|
||||
);
|
||||
@ -1754,6 +1756,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
.search_strategy = options.search_strategy,
|
||||
.headerpad_size = options.headerpad_size,
|
||||
.headerpad_max_install_names = options.headerpad_max_install_names,
|
||||
.dead_strip_dylibs = options.dead_strip_dylibs,
|
||||
});
|
||||
errdefer bin_file.destroy();
|
||||
comp.* = .{
|
||||
@ -2369,7 +2372,7 @@ fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemo
|
||||
/// to remind the programmer to update multiple related pieces of code that
|
||||
/// are in different locations. Bump this number when adding or deleting
|
||||
/// anything from the link cache manifest.
|
||||
pub const link_hash_implementation_version = 6;
|
||||
pub const link_hash_implementation_version = 7;
|
||||
|
||||
fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void {
|
||||
const gpa = comp.gpa;
|
||||
@ -2379,7 +2382,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
|
||||
defer arena_allocator.deinit();
|
||||
const arena = arena_allocator.allocator();
|
||||
|
||||
comptime assert(link_hash_implementation_version == 6);
|
||||
comptime assert(link_hash_implementation_version == 7);
|
||||
|
||||
if (comp.bin_file.options.module) |mod| {
|
||||
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
|
||||
|
||||
// Mach-O specific stuff
|
||||
man.hash.addListOfBytes(comp.bin_file.options.framework_dirs);
|
||||
man.hash.addListOfBytes(comp.bin_file.options.frameworks);
|
||||
link.hashAddSystemLibs(&man.hash, comp.bin_file.options.frameworks);
|
||||
try man.addOptionalFile(comp.bin_file.options.entitlements);
|
||||
man.hash.addOptional(comp.bin_file.options.pagezero_size);
|
||||
man.hash.addOptional(comp.bin_file.options.search_strategy);
|
||||
man.hash.addOptional(comp.bin_file.options.headerpad_size);
|
||||
man.hash.add(comp.bin_file.options.headerpad_max_install_names);
|
||||
man.hash.add(comp.bin_file.options.dead_strip_dylibs);
|
||||
|
||||
// COFF specific stuff
|
||||
man.hash.addOptional(comp.bin_file.options.subsystem);
|
||||
|
||||
@ -162,7 +162,7 @@ pub const Options = struct {
|
||||
|
||||
objects: []Compilation.LinkObject,
|
||||
framework_dirs: []const []const u8,
|
||||
frameworks: []const []const u8,
|
||||
frameworks: std.StringArrayHashMapUnmanaged(SystemLib),
|
||||
system_libs: std.StringArrayHashMapUnmanaged(SystemLib),
|
||||
wasi_emulated_libs: []const wasi_libc.CRTFile,
|
||||
lib_dirs: []const []const u8,
|
||||
@ -199,6 +199,9 @@ pub const Options = struct {
|
||||
/// (Darwin) set enough space as if all paths were MATPATHLEN
|
||||
headerpad_max_install_names: bool = false,
|
||||
|
||||
/// (Darwin) remove dylibs that are unreachable by the entry point or exported symbols
|
||||
dead_strip_dylibs: bool = false,
|
||||
|
||||
pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
|
||||
return if (options.use_lld) .Obj else options.output_mode;
|
||||
}
|
||||
|
||||
@ -969,7 +969,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !
|
||||
man = comp.cache_parent.obtain();
|
||||
self.base.releaseLock();
|
||||
|
||||
comptime assert(Compilation.link_hash_implementation_version == 6);
|
||||
comptime assert(Compilation.link_hash_implementation_version == 7);
|
||||
|
||||
for (self.base.options.objects) |obj| {
|
||||
_ = try man.addFile(obj.path, null);
|
||||
|
||||
@ -1298,7 +1298,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
|
||||
// We are about to obtain this lock, so here we give other processes a chance first.
|
||||
self.base.releaseLock();
|
||||
|
||||
comptime assert(Compilation.link_hash_implementation_version == 6);
|
||||
comptime assert(Compilation.link_hash_implementation_version == 7);
|
||||
|
||||
try man.addOptionalFile(self.base.options.linker_script);
|
||||
try man.addOptionalFile(self.base.options.version_script);
|
||||
|
||||
@ -541,7 +541,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
|
||||
// We are about to obtain this lock, so here we give other processes a chance first.
|
||||
self.base.releaseLock();
|
||||
|
||||
comptime assert(Compilation.link_hash_implementation_version == 6);
|
||||
comptime assert(Compilation.link_hash_implementation_version == 7);
|
||||
|
||||
for (self.base.options.objects) |obj| {
|
||||
_ = try man.addFile(obj.path, null);
|
||||
@ -558,9 +558,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
|
||||
man.hash.addOptional(self.base.options.search_strategy);
|
||||
man.hash.addOptional(self.base.options.headerpad_size);
|
||||
man.hash.add(self.base.options.headerpad_max_install_names);
|
||||
man.hash.add(self.base.options.dead_strip_dylibs);
|
||||
man.hash.addListOfBytes(self.base.options.lib_dirs);
|
||||
man.hash.addListOfBytes(self.base.options.framework_dirs);
|
||||
man.hash.addListOfBytes(self.base.options.frameworks);
|
||||
link.hashAddSystemLibs(&man.hash, self.base.options.frameworks);
|
||||
man.hash.addListOfBytes(self.base.options.rpath_list);
|
||||
if (is_dyn_lib) {
|
||||
man.hash.addOptionalBytes(self.base.options.install_name);
|
||||
@ -767,19 +768,20 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
|
||||
}
|
||||
|
||||
// Shared and static libraries passed via `-l` flag.
|
||||
var search_lib_names = std.ArrayList([]const u8).init(arena);
|
||||
var candidate_libs = std.StringArrayHashMap(Compilation.SystemLib).init(arena);
|
||||
|
||||
const system_libs = self.base.options.system_libs.keys();
|
||||
for (system_libs) |link_lib| {
|
||||
const system_lib_names = self.base.options.system_libs.keys();
|
||||
for (system_lib_names) |system_lib_name| {
|
||||
// By this time, we depend on these libs being dynamically linked libraries and not static libraries
|
||||
// (the check for that needs to be earlier), but they could be full paths to .dylib files, in which
|
||||
// case we want to avoid prepending "-l".
|
||||
if (Compilation.classifyFileExt(link_lib) == .shared_library) {
|
||||
try positionals.append(link_lib);
|
||||
if (Compilation.classifyFileExt(system_lib_name) == .shared_library) {
|
||||
try positionals.append(system_lib_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
try search_lib_names.append(link_lib);
|
||||
const system_lib_info = self.base.options.system_libs.get(system_lib_name).?;
|
||||
try candidate_libs.put(system_lib_name, system_lib_info);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
var libs = std.ArrayList([]const u8).init(arena);
|
||||
var libs = std.StringArrayHashMap(Compilation.SystemLib).init(arena);
|
||||
|
||||
// Assume ld64 default -search_paths_first if no strategy specified.
|
||||
const search_strategy = self.base.options.search_strategy orelse .paths_first;
|
||||
outer: for (search_lib_names.items) |lib_name| {
|
||||
outer: for (candidate_libs.keys()) |lib_name| {
|
||||
switch (search_strategy) {
|
||||
.paths_first => {
|
||||
// Look in each directory for a dylib (stub first), and then for archive
|
||||
for (lib_dirs.items) |dir| {
|
||||
for (&[_][]const u8{ ".tbd", ".dylib", ".a" }) |ext| {
|
||||
if (try resolveLib(arena, dir, lib_name, ext)) |full_path| {
|
||||
try libs.append(full_path);
|
||||
try libs.put(full_path, candidate_libs.get(lib_name).?);
|
||||
continue :outer;
|
||||
}
|
||||
}
|
||||
@ -816,13 +818,13 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
|
||||
for (lib_dirs.items) |dir| {
|
||||
for (&[_][]const u8{ ".tbd", ".dylib" }) |ext| {
|
||||
if (try resolveLib(arena, dir, lib_name, ext)) |full_path| {
|
||||
try libs.append(full_path);
|
||||
try libs.put(full_path, candidate_libs.get(lib_name).?);
|
||||
continue :outer;
|
||||
}
|
||||
}
|
||||
} else for (lib_dirs.items) |dir| {
|
||||
if (try resolveLib(arena, dir, lib_name, ".a")) |full_path| {
|
||||
try libs.append(full_path);
|
||||
try libs.put(full_path, candidate_libs.get(lib_name).?);
|
||||
} else {
|
||||
log.warn("library not found for '-l{s}'", .{lib_name});
|
||||
lib_not_found = true;
|
||||
@ -846,7 +848,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
|
||||
// re-exports every single symbol definition.
|
||||
for (lib_dirs.items) |dir| {
|
||||
if (try resolveLib(arena, dir, "System", ".tbd")) |full_path| {
|
||||
try libs.append(full_path);
|
||||
try libs.put(full_path, .{ .needed = false });
|
||||
libsystem_available = true;
|
||||
break :blk;
|
||||
}
|
||||
@ -856,8 +858,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
|
||||
for (lib_dirs.items) |dir| {
|
||||
if (try resolveLib(arena, dir, "System", ".dylib")) |libsystem_path| {
|
||||
if (try resolveLib(arena, dir, "c", ".dylib")) |libc_path| {
|
||||
try libs.append(libsystem_path);
|
||||
try libs.append(libc_path);
|
||||
try libs.put(libsystem_path, .{ .needed = false });
|
||||
try libs.put(libc_path, .{ .needed = false });
|
||||
libsystem_available = true;
|
||||
break :blk;
|
||||
}
|
||||
@ -871,7 +873,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
|
||||
const full_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
|
||||
"libc", "darwin", libsystem_name,
|
||||
});
|
||||
try libs.append(full_path);
|
||||
try libs.put(full_path, .{ .needed = false });
|
||||
}
|
||||
|
||||
// frameworks
|
||||
@ -884,16 +886,16 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
|
||||
}
|
||||
}
|
||||
|
||||
outer: for (self.base.options.frameworks) |framework| {
|
||||
outer: for (self.base.options.frameworks.keys()) |f_name| {
|
||||
for (framework_dirs.items) |dir| {
|
||||
for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
|
||||
if (try resolveFramework(arena, dir, framework, ext)) |full_path| {
|
||||
try libs.append(full_path);
|
||||
if (try resolveFramework(arena, dir, f_name, ext)) |full_path| {
|
||||
try libs.put(full_path, self.base.options.frameworks.get(f_name).?);
|
||||
continue :outer;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn("framework not found for '-framework {s}'", .{framework});
|
||||
log.warn("framework not found for '-framework {s}'", .{f_name});
|
||||
framework_not_found = true;
|
||||
}
|
||||
}
|
||||
@ -987,6 +989,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
|
||||
try argv.append("-headerpad_max_install_names");
|
||||
}
|
||||
|
||||
if (self.base.options.dead_strip_dylibs) {
|
||||
try argv.append("-dead_strip_dylibs");
|
||||
}
|
||||
|
||||
if (self.base.options.entry) |entry| {
|
||||
try argv.append("-e");
|
||||
try argv.append(entry);
|
||||
@ -1020,15 +1026,25 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
|
||||
try argv.append("-lc");
|
||||
|
||||
for (self.base.options.system_libs.keys()) |l_name| {
|
||||
try argv.append(try std.fmt.allocPrint(arena, "-l{s}", .{l_name}));
|
||||
const needed = self.base.options.system_libs.get(l_name).?.needed;
|
||||
const arg = if (needed)
|
||||
try std.fmt.allocPrint(arena, "-needed-l{s}", .{l_name})
|
||||
else
|
||||
try std.fmt.allocPrint(arena, "-l{s}", .{l_name});
|
||||
try argv.append(arg);
|
||||
}
|
||||
|
||||
for (self.base.options.lib_dirs) |lib_dir| {
|
||||
try argv.append(try std.fmt.allocPrint(arena, "-L{s}", .{lib_dir}));
|
||||
}
|
||||
|
||||
for (self.base.options.frameworks) |framework| {
|
||||
try argv.append(try std.fmt.allocPrint(arena, "-framework {s}", .{framework}));
|
||||
for (self.base.options.frameworks.keys()) |framework| {
|
||||
const needed = self.base.options.frameworks.get(framework).?.needed;
|
||||
const arg = if (needed)
|
||||
try std.fmt.allocPrint(arena, "-needed_framework {s}", .{framework})
|
||||
else
|
||||
try std.fmt.allocPrint(arena, "-framework {s}", .{framework});
|
||||
try argv.append(arg);
|
||||
}
|
||||
|
||||
for (self.base.options.framework_dirs) |framework_dir| {
|
||||
@ -1051,7 +1067,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
|
||||
defer dependent_libs.deinit();
|
||||
try self.parseInputFiles(positionals.items, self.base.options.sysroot, &dependent_libs);
|
||||
try self.parseAndForceLoadStaticArchives(must_link_archives.keys());
|
||||
try self.parseLibs(libs.items, self.base.options.sysroot, &dependent_libs);
|
||||
try self.parseLibs(libs.keys(), libs.values(), self.base.options.sysroot, &dependent_libs);
|
||||
try self.parseDependentLibs(self.base.options.sysroot, &dependent_libs);
|
||||
}
|
||||
|
||||
@ -1376,6 +1392,7 @@ const DylibCreateOpts = struct {
|
||||
dependent_libs: *std.fifo.LinearFifo(Dylib.Id, .Dynamic),
|
||||
id: ?Dylib.Id = null,
|
||||
is_dependent: bool = false,
|
||||
is_needed: bool = false,
|
||||
};
|
||||
|
||||
pub 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
|
||||
try self.dylibs.append(self.base.allocator, dylib);
|
||||
try self.dylibs_map.putNoClobber(self.base.allocator, dylib.id.?.name, dylib_id);
|
||||
|
||||
if (!(opts.is_dependent or self.referenced_dylibs.contains(dylib_id))) {
|
||||
const should_link_dylib_even_if_unreachable = blk: {
|
||||
if (self.base.options.dead_strip_dylibs and !opts.is_needed) break :blk false;
|
||||
break :blk !(opts.is_dependent or self.referenced_dylibs.contains(dylib_id));
|
||||
};
|
||||
|
||||
if (should_link_dylib_even_if_unreachable) {
|
||||
try self.addLoadDylibLC(dylib_id);
|
||||
try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {});
|
||||
}
|
||||
@ -1469,12 +1491,20 @@ fn parseAndForceLoadStaticArchives(self: *MachO, files: []const []const u8) !voi
|
||||
}
|
||||
}
|
||||
|
||||
fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8, dependent_libs: anytype) !void {
|
||||
for (libs) |lib| {
|
||||
fn parseLibs(
|
||||
self: *MachO,
|
||||
lib_names: []const []const u8,
|
||||
lib_infos: []const Compilation.SystemLib,
|
||||
syslibroot: ?[]const u8,
|
||||
dependent_libs: anytype,
|
||||
) !void {
|
||||
for (lib_names) |lib, i| {
|
||||
const lib_info = lib_infos[i];
|
||||
log.debug("parsing lib path '{s}'", .{lib});
|
||||
if (try self.parseDylib(lib, .{
|
||||
.syslibroot = syslibroot,
|
||||
.dependent_libs = dependent_libs,
|
||||
.is_needed = lib_info.needed,
|
||||
})) continue;
|
||||
if (try self.parseArchive(lib, false)) continue;
|
||||
|
||||
|
||||
@ -2546,7 +2546,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
|
||||
// We are about to obtain this lock, so here we give other processes a chance first.
|
||||
self.base.releaseLock();
|
||||
|
||||
comptime assert(Compilation.link_hash_implementation_version == 6);
|
||||
comptime assert(Compilation.link_hash_implementation_version == 7);
|
||||
|
||||
for (self.base.options.objects) |obj| {
|
||||
_ = try man.addFile(obj.path, null);
|
||||
|
||||
47
src/main.zig
47
src/main.zig
@ -444,6 +444,8 @@ const usage_build_generic =
|
||||
\\ --stack [size] Override default stack size
|
||||
\\ --image-base [addr] Set base address for executable image
|
||||
\\ -framework [name] (Darwin) link against framework
|
||||
\\ -needed_framework [name] (Darwin) link against framework (even if unused)
|
||||
\\ -needed_library [lib] (Darwin) link against system library (even if unused)
|
||||
\\ -F[dir] (Darwin) add search path for frameworks
|
||||
\\ -install_name=[value] (Darwin) add dylib's install name
|
||||
\\ --entitlements [path] (Darwin) add path to entitlements file for embedding in code signature
|
||||
@ -452,6 +454,7 @@ const usage_build_generic =
|
||||
\\ -search_dylibs_first (Darwin) search `libx.dylib` in each dir in library search paths, then `libx.a`
|
||||
\\ -headerpad [value] (Darwin) set minimum space for future expansion of the load commands in hexadecimal notation
|
||||
\\ -headerpad_max_install_names (Darwin) set enough space as if all paths were MAXPATHLEN
|
||||
\\ -dead_strip_dylibs (Darwin) remove dylibs that are unreachable by the entry point or exported symbols
|
||||
\\ --import-memory (WebAssembly) import memory from the environment
|
||||
\\ --import-table (WebAssembly) import function table from the host environment
|
||||
\\ --export-table (WebAssembly) export function table to the host environment
|
||||
@ -703,6 +706,7 @@ fn buildOutputType(
|
||||
var search_strategy: ?link.File.MachO.SearchStrategy = null;
|
||||
var headerpad_size: ?u32 = null;
|
||||
var headerpad_max_install_names: bool = false;
|
||||
var dead_strip_dylibs: bool = false;
|
||||
|
||||
// e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.
|
||||
// This array is populated by zig cc frontend and then has to be converted to zig-style
|
||||
@ -748,8 +752,7 @@ fn buildOutputType(
|
||||
var framework_dirs = std.ArrayList([]const u8).init(gpa);
|
||||
defer framework_dirs.deinit();
|
||||
|
||||
var frameworks = std.ArrayList([]const u8).init(gpa);
|
||||
defer frameworks.deinit();
|
||||
var frameworks: std.StringArrayHashMapUnmanaged(Compilation.SystemLib) = .{};
|
||||
|
||||
// null means replace with the test executable binary
|
||||
var test_exec_args = std.ArrayList(?[]const u8).init(gpa);
|
||||
@ -910,9 +913,15 @@ fn buildOutputType(
|
||||
fatal("expected parameter after {s}", .{arg});
|
||||
});
|
||||
} else if (mem.eql(u8, arg, "-framework")) {
|
||||
try frameworks.append(args_iter.next() orelse {
|
||||
const path = args_iter.next() orelse {
|
||||
fatal("expected parameter after {s}", .{arg});
|
||||
});
|
||||
};
|
||||
try frameworks.put(gpa, path, .{ .needed = false });
|
||||
} else if (mem.eql(u8, arg, "-needed_framework")) {
|
||||
const path = args_iter.next() orelse {
|
||||
fatal("expected parameter after {s}", .{arg});
|
||||
};
|
||||
try frameworks.put(gpa, path, .{ .needed = true });
|
||||
} else if (mem.eql(u8, arg, "-install_name")) {
|
||||
install_name = args_iter.next() orelse {
|
||||
fatal("expected parameter after {s}", .{arg});
|
||||
@ -937,6 +946,8 @@ fn buildOutputType(
|
||||
};
|
||||
} else if (mem.eql(u8, arg, "-headerpad_max_install_names")) {
|
||||
headerpad_max_install_names = true;
|
||||
} else if (mem.eql(u8, arg, "-dead_strip_dylibs")) {
|
||||
dead_strip_dylibs = true;
|
||||
} else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) {
|
||||
linker_script = args_iter.next() orelse {
|
||||
fatal("expected parameter after {s}", .{arg});
|
||||
@ -952,7 +963,10 @@ fn buildOutputType(
|
||||
// We don't know whether this library is part of libc or libc++ until
|
||||
// we resolve the target, so we simply append to the list for now.
|
||||
try system_libs.put(next_arg, .{ .needed = false });
|
||||
} else if (mem.eql(u8, arg, "--needed-library") or mem.eql(u8, arg, "-needed-l")) {
|
||||
} else if (mem.eql(u8, arg, "--needed-library") or
|
||||
mem.eql(u8, arg, "-needed-l") or
|
||||
mem.eql(u8, arg, "--needed_library"))
|
||||
{
|
||||
const next_arg = args_iter.next() orelse {
|
||||
fatal("expected parameter after {s}", .{arg});
|
||||
};
|
||||
@ -1582,7 +1596,7 @@ fn buildOutputType(
|
||||
try clang_argv.appendSlice(it.other_args);
|
||||
},
|
||||
.framework_dir => try framework_dirs.append(it.only_arg),
|
||||
.framework => try frameworks.append(it.only_arg),
|
||||
.framework => try frameworks.put(gpa, it.only_arg, .{ .needed = false }),
|
||||
.nostdlibinc => want_native_include_dirs = false,
|
||||
.strip => strip = true,
|
||||
.exec_model => {
|
||||
@ -1700,6 +1714,8 @@ fn buildOutputType(
|
||||
};
|
||||
} else if (mem.eql(u8, arg, "-headerpad_max_install_names")) {
|
||||
headerpad_max_install_names = true;
|
||||
} else if (mem.eql(u8, arg, "-dead_strip_dylibs")) {
|
||||
dead_strip_dylibs = true;
|
||||
} else if (mem.eql(u8, arg, "--gc-sections")) {
|
||||
linker_gc_sections = true;
|
||||
} else if (mem.eql(u8, arg, "--no-gc-sections")) {
|
||||
@ -1868,7 +1884,19 @@ fn buildOutputType(
|
||||
if (i >= linker_args.items.len) {
|
||||
fatal("expected linker arg after '{s}'", .{arg});
|
||||
}
|
||||
try frameworks.append(linker_args.items[i]);
|
||||
try frameworks.put(gpa, linker_args.items[i], .{ .needed = false });
|
||||
} else if (mem.eql(u8, arg, "-needed_framework")) {
|
||||
i += 1;
|
||||
if (i >= linker_args.items.len) {
|
||||
fatal("expected linker arg after '{s}'", .{arg});
|
||||
}
|
||||
try frameworks.put(gpa, linker_args.items[i], .{ .needed = true });
|
||||
} else if (mem.eql(u8, arg, "-needed_library")) {
|
||||
i += 1;
|
||||
if (i >= linker_args.items.len) {
|
||||
fatal("expected linker arg after '{s}'", .{arg});
|
||||
}
|
||||
try system_libs.put(linker_args.items[i], .{ .needed = true });
|
||||
} else if (mem.eql(u8, arg, "-compatibility_version")) {
|
||||
i += 1;
|
||||
if (i >= linker_args.items.len) {
|
||||
@ -2238,7 +2266,7 @@ fn buildOutputType(
|
||||
|
||||
if (comptime builtin.target.isDarwin()) {
|
||||
// If we want to link against frameworks, we need system headers.
|
||||
if (framework_dirs.items.len > 0 or frameworks.items.len > 0)
|
||||
if (framework_dirs.items.len > 0 or frameworks.count() > 0)
|
||||
want_native_include_dirs = true;
|
||||
}
|
||||
|
||||
@ -2728,7 +2756,7 @@ fn buildOutputType(
|
||||
.c_source_files = c_source_files.items,
|
||||
.link_objects = link_objects.items,
|
||||
.framework_dirs = framework_dirs.items,
|
||||
.frameworks = frameworks.items,
|
||||
.frameworks = frameworks,
|
||||
.system_lib_names = system_libs.keys(),
|
||||
.system_lib_infos = system_libs.values(),
|
||||
.wasi_emulated_libs = wasi_emulated_libs.items,
|
||||
@ -2821,6 +2849,7 @@ fn buildOutputType(
|
||||
.search_strategy = search_strategy,
|
||||
.headerpad_size = headerpad_size,
|
||||
.headerpad_max_install_names = headerpad_max_install_names,
|
||||
.dead_strip_dylibs = dead_strip_dylibs,
|
||||
}) catch |err| switch (err) {
|
||||
error.LibCUnavailable => {
|
||||
const target = target_info.target;
|
||||
|
||||
@ -40,7 +40,16 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
|
||||
.build_modes = true,
|
||||
});
|
||||
|
||||
cases.addBuildFile("test/link/macho/frameworks/build.zig", .{
|
||||
cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{
|
||||
.build_modes = true,
|
||||
.requires_macos_sdk = true,
|
||||
});
|
||||
|
||||
cases.addBuildFile("test/link/macho/needed_l/build.zig", .{
|
||||
.build_modes = true,
|
||||
});
|
||||
|
||||
cases.addBuildFile("test/link/macho/needed_framework/build.zig", .{
|
||||
.build_modes = true,
|
||||
.requires_macos_sdk = true,
|
||||
});
|
||||
|
||||
46
test/link/macho/dead_strip_dylibs/build.zig
Normal file
46
test/link/macho/dead_strip_dylibs/build.zig
Normal file
@ -0,0 +1,46 @@
|
||||
const std = @import("std");
|
||||
const Builder = std.build.Builder;
|
||||
const LibExeObjectStep = std.build.LibExeObjStep;
|
||||
|
||||
pub fn build(b: *Builder) void {
|
||||
const mode = b.standardReleaseOptions();
|
||||
|
||||
const test_step = b.step("test", "Test the program");
|
||||
test_step.dependOn(b.getInstallStep());
|
||||
|
||||
{
|
||||
// Without -dead_strip_dylibs we expect `-la` to include liba.dylib in the final executable
|
||||
const exe = createScenario(b, mode);
|
||||
|
||||
const check = exe.checkObject(.macho);
|
||||
check.checkStart("cmd LOAD_DYLIB");
|
||||
check.checkNext("name {*}Cocoa");
|
||||
|
||||
check.checkStart("cmd LOAD_DYLIB");
|
||||
check.checkNext("name {*}libobjc{*}.dylib");
|
||||
|
||||
test_step.dependOn(&check.step);
|
||||
|
||||
const run_cmd = exe.run();
|
||||
test_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
|
||||
{
|
||||
// With -dead_strip_dylibs, we should include liba.dylib as it's unreachable
|
||||
const exe = createScenario(b, mode);
|
||||
exe.dead_strip_dylibs = true;
|
||||
|
||||
const run_cmd = exe.run();
|
||||
run_cmd.expected_exit_code = @bitCast(u8, @as(i8, -2)); // should fail
|
||||
test_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
}
|
||||
|
||||
fn createScenario(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep {
|
||||
const exe = b.addExecutable("test", null);
|
||||
exe.addCSourceFile("main.c", &[0][]const u8{});
|
||||
exe.setBuildMode(mode);
|
||||
exe.linkLibC();
|
||||
exe.linkFramework("Cocoa");
|
||||
return exe;
|
||||
}
|
||||
11
test/link/macho/dead_strip_dylibs/main.c
Normal file
11
test/link/macho/dead_strip_dylibs/main.c
Normal file
@ -0,0 +1,11 @@
|
||||
#include <objc/runtime.h>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (objc_getClass("NSObject") == 0) {
|
||||
return -1;
|
||||
}
|
||||
if (objc_getClass("NSApplication") == 0) {
|
||||
return -2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
char* hello();
|
||||
extern char world[];
|
||||
|
||||
int main() {
|
||||
int main(int argc, char* argv[]) {
|
||||
printf("%s %s", hello(), world);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
#include <assert.h>
|
||||
#include <objc/runtime.h>
|
||||
|
||||
int main() {
|
||||
assert(objc_getClass("NSObject") > 0);
|
||||
assert(objc_getClass("NSApplication") > 0);
|
||||
}
|
||||
@ -1,30 +1,25 @@
|
||||
const std = @import("std");
|
||||
const Builder = std.build.Builder;
|
||||
const LibExeObjectStep = std.build.LibExeObjStep;
|
||||
|
||||
pub fn build(b: *Builder) void {
|
||||
const mode = b.standardReleaseOptions();
|
||||
|
||||
const test_step = b.step("test", "Test the program");
|
||||
test_step.dependOn(b.getInstallStep());
|
||||
|
||||
// -dead_strip_dylibs
|
||||
// -needed_framework Cocoa
|
||||
const exe = b.addExecutable("test", null);
|
||||
b.default_step.dependOn(&exe.step);
|
||||
exe.addCSourceFile("main.c", &[0][]const u8{});
|
||||
exe.setBuildMode(mode);
|
||||
exe.linkLibC();
|
||||
exe.linkFramework("Cocoa");
|
||||
exe.linkFrameworkNeeded("Cocoa");
|
||||
exe.dead_strip_dylibs = true;
|
||||
|
||||
const check = exe.checkObject(.macho);
|
||||
check.checkStart("cmd LOAD_DYLIB");
|
||||
check.checkNext("name {*}Cocoa");
|
||||
|
||||
switch (mode) {
|
||||
.Debug, .ReleaseSafe => {
|
||||
check.checkStart("cmd LOAD_DYLIB");
|
||||
check.checkNext("name {*}libobjc{*}.dylib");
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
test_step.dependOn(&check.step);
|
||||
|
||||
const run_cmd = exe.run();
|
||||
3
test/link/macho/needed_framework/main.c
Normal file
3
test/link/macho/needed_framework/main.c
Normal file
@ -0,0 +1,3 @@
|
||||
int main(int argc, char* argv[]) {
|
||||
return 0;
|
||||
}
|
||||
1
test/link/macho/needed_l/a.c
Normal file
1
test/link/macho/needed_l/a.c
Normal file
@ -0,0 +1 @@
|
||||
int a = 42;
|
||||
35
test/link/macho/needed_l/build.zig
Normal file
35
test/link/macho/needed_l/build.zig
Normal file
@ -0,0 +1,35 @@
|
||||
const std = @import("std");
|
||||
const Builder = std.build.Builder;
|
||||
const LibExeObjectStep = std.build.LibExeObjStep;
|
||||
|
||||
pub fn build(b: *Builder) void {
|
||||
const mode = b.standardReleaseOptions();
|
||||
|
||||
const test_step = b.step("test", "Test the program");
|
||||
test_step.dependOn(b.getInstallStep());
|
||||
|
||||
const dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0));
|
||||
dylib.setBuildMode(mode);
|
||||
dylib.addCSourceFile("a.c", &.{});
|
||||
dylib.linkLibC();
|
||||
dylib.install();
|
||||
|
||||
// -dead_strip_dylibs
|
||||
// -needed-la
|
||||
const exe = b.addExecutable("test", null);
|
||||
exe.addCSourceFile("main.c", &[0][]const u8{});
|
||||
exe.setBuildMode(mode);
|
||||
exe.linkLibC();
|
||||
exe.linkSystemLibraryNeeded("a");
|
||||
exe.addLibraryPath(b.pathFromRoot("zig-out/lib"));
|
||||
exe.addRPath(b.pathFromRoot("zig-out/lib"));
|
||||
exe.dead_strip_dylibs = true;
|
||||
|
||||
const check = exe.checkObject(.macho);
|
||||
check.checkStart("cmd LOAD_DYLIB");
|
||||
check.checkNext("name @rpath/liba.dylib");
|
||||
test_step.dependOn(&check.step);
|
||||
|
||||
const run_cmd = exe.run();
|
||||
test_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
3
test/link/macho/needed_l/main.c
Normal file
3
test/link/macho/needed_l/main.c
Normal file
@ -0,0 +1,3 @@
|
||||
int main(int argc, char* argv[]) {
|
||||
return 0;
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
char* hello();
|
||||
extern char world[];
|
||||
|
||||
int main() {
|
||||
int main(int argc, char* argv[]) {
|
||||
printf("%s %s", hello(), world);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user