Merge pull request #10226 from ziglang/fix-10217
macos: improved SDK detection and linker integration with _mh_execute_header
This commit is contained in:
@@ -2,14 +2,34 @@ const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
const Target = std.Target;
|
||||
const Version = std.builtin.Version;
|
||||
|
||||
pub const macos = @import("darwin/macos.zig");
|
||||
|
||||
/// Detect SDK path on Darwin.
|
||||
/// Calls `xcrun --sdk <target_sdk> --show-sdk-path` which result can be used to specify
|
||||
/// `--sysroot` of the compiler.
|
||||
/// The caller needs to free the resulting path slice.
|
||||
pub fn getSDKPath(allocator: *Allocator, target: Target) !?[]u8 {
|
||||
/// Check if SDK is installed on Darwin without triggering CLT installation popup window.
|
||||
/// Note: simply invoking `xcrun` will inevitably trigger the CLT installation popup.
|
||||
/// Therefore, we resort to the same tool used by Homebrew, namely, invoking `xcode-select --print-path`
|
||||
/// and checking if the status is nonzero or the returned string in nonempty.
|
||||
/// https://github.com/Homebrew/brew/blob/e119bdc571dcb000305411bc1e26678b132afb98/Library/Homebrew/brew.sh#L630
|
||||
pub fn isDarwinSDKInstalled(allocator: *Allocator) bool {
|
||||
const argv = &[_][]const u8{ "/usr/bin/xcode-select", "--print-path" };
|
||||
const result = std.ChildProcess.exec(.{ .allocator = allocator, .argv = argv }) catch return false;
|
||||
defer {
|
||||
allocator.free(result.stderr);
|
||||
allocator.free(result.stdout);
|
||||
}
|
||||
if (result.stderr.len != 0 or result.term.Exited != 0) {
|
||||
// We don't actually care if there were errors as this is best-effort check anyhow.
|
||||
return false;
|
||||
}
|
||||
return result.stdout.len > 0;
|
||||
}
|
||||
|
||||
/// Detect SDK on Darwin.
|
||||
/// Calls `xcrun --sdk <target_sdk> --show-sdk-path` which fetches the path to the SDK sysroot (if any).
|
||||
/// Subsequently calls `xcrun --sdk <target_sdk> --show-sdk-version` which fetches version of the SDK.
|
||||
/// The caller needs to deinit the resulting struct.
|
||||
pub fn getDarwinSDK(allocator: *Allocator, target: Target) ?DarwinSDK {
|
||||
const is_simulator_abi = target.abi == .simulator;
|
||||
const sdk = switch (target.os.tag) {
|
||||
.macos => "macosx",
|
||||
@@ -18,22 +38,55 @@ pub fn getSDKPath(allocator: *Allocator, target: Target) !?[]u8 {
|
||||
.tvos => if (is_simulator_abi) "appletvsimulator" else "appletvos",
|
||||
else => return null,
|
||||
};
|
||||
|
||||
const argv = &[_][]const u8{ "xcrun", "--sdk", sdk, "--show-sdk-path" };
|
||||
const result = try std.ChildProcess.exec(.{ .allocator = allocator, .argv = argv });
|
||||
defer {
|
||||
allocator.free(result.stderr);
|
||||
allocator.free(result.stdout);
|
||||
}
|
||||
if (result.stderr.len != 0 or result.term.Exited != 0) {
|
||||
// We don't actually care if there were errors as this is best-effort check anyhow
|
||||
// and in the worst case the user can specify the sysroot manually.
|
||||
return null;
|
||||
}
|
||||
const sysroot = try allocator.dupe(u8, mem.trimRight(u8, result.stdout, "\r\n"));
|
||||
return sysroot;
|
||||
const path = path: {
|
||||
const argv = &[_][]const u8{ "/usr/bin/xcrun", "--sdk", sdk, "--show-sdk-path" };
|
||||
const result = std.ChildProcess.exec(.{ .allocator = allocator, .argv = argv }) catch return null;
|
||||
defer {
|
||||
allocator.free(result.stderr);
|
||||
allocator.free(result.stdout);
|
||||
}
|
||||
if (result.stderr.len != 0 or result.term.Exited != 0) {
|
||||
// We don't actually care if there were errors as this is best-effort check anyhow
|
||||
// and in the worst case the user can specify the sysroot manually.
|
||||
return null;
|
||||
}
|
||||
const path = allocator.dupe(u8, mem.trimRight(u8, result.stdout, "\r\n")) catch return null;
|
||||
break :path path;
|
||||
};
|
||||
const version = version: {
|
||||
const argv = &[_][]const u8{ "/usr/bin/xcrun", "--sdk", sdk, "--show-sdk-version" };
|
||||
const result = std.ChildProcess.exec(.{ .allocator = allocator, .argv = argv }) catch return null;
|
||||
defer {
|
||||
allocator.free(result.stderr);
|
||||
allocator.free(result.stdout);
|
||||
}
|
||||
if (result.stderr.len != 0 or result.term.Exited != 0) {
|
||||
// We don't actually care if there were errors as this is best-effort check anyhow
|
||||
// and in the worst case the user can specify the sysroot manually.
|
||||
return null;
|
||||
}
|
||||
const raw_version = mem.trimRight(u8, result.stdout, "\r\n");
|
||||
const version = Version.parse(raw_version) catch Version{
|
||||
.major = 0,
|
||||
.minor = 0,
|
||||
};
|
||||
break :version version;
|
||||
};
|
||||
return DarwinSDK{
|
||||
.path = path,
|
||||
.version = version,
|
||||
};
|
||||
}
|
||||
|
||||
pub const DarwinSDK = struct {
|
||||
path: []const u8,
|
||||
version: Version,
|
||||
|
||||
pub fn deinit(self: DarwinSDK, allocator: *Allocator) void {
|
||||
allocator.free(self.path);
|
||||
}
|
||||
};
|
||||
|
||||
test "" {
|
||||
_ = @import("darwin/macos.zig");
|
||||
}
|
||||
|
||||
@@ -773,6 +773,8 @@ pub const InitOptions = struct {
|
||||
wasi_exec_model: ?std.builtin.WasiExecModel = null,
|
||||
/// (Zig compiler development) Enable dumping linker's state as JSON.
|
||||
enable_link_snapshots: bool = false,
|
||||
/// (Darwin) Path and version of the native SDK if detected.
|
||||
native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null,
|
||||
};
|
||||
|
||||
fn addPackageTableToCacheHash(
|
||||
@@ -962,18 +964,11 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
|
||||
break :blk false;
|
||||
};
|
||||
|
||||
const darwin_use_system_sdk = blk: {
|
||||
if (comptime !builtin.target.isDarwin()) break :blk false;
|
||||
if (!options.is_native_os) break :blk false;
|
||||
if (builtin.os.tag != .macos or !options.target.isDarwin()) break :blk false;
|
||||
break :blk options.frameworks.len > 0 or options.framework_dirs.len > 0;
|
||||
};
|
||||
|
||||
const sysroot = blk: {
|
||||
if (options.sysroot) |sysroot| {
|
||||
break :blk sysroot;
|
||||
} else if (darwin_use_system_sdk) {
|
||||
break :blk try std.zig.system.darwin.getSDKPath(arena, options.target);
|
||||
} else if (options.native_darwin_sdk) |sdk| {
|
||||
break :blk sdk.path;
|
||||
} else {
|
||||
break :blk null;
|
||||
}
|
||||
@@ -1060,6 +1055,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
|
||||
link_libc,
|
||||
options.system_lib_names.len != 0 or options.frameworks.len != 0,
|
||||
options.libc_installation,
|
||||
options.native_darwin_sdk != null,
|
||||
);
|
||||
|
||||
const must_pie = target_util.requiresPIE(options.target);
|
||||
@@ -1496,6 +1492,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
|
||||
.wasi_exec_model = wasi_exec_model,
|
||||
.use_stage1 = use_stage1,
|
||||
.enable_link_snapshots = options.enable_link_snapshots,
|
||||
.native_darwin_sdk = options.native_darwin_sdk,
|
||||
});
|
||||
errdefer bin_file.destroy();
|
||||
comp.* = .{
|
||||
@@ -3776,6 +3773,37 @@ const LibCDirs = struct {
|
||||
libc_installation: ?*const LibCInstallation,
|
||||
};
|
||||
|
||||
fn getZigShippedLibCIncludeDirsDarwin(arena: *Allocator, zig_lib_dir: []const u8, target: Target) !LibCDirs {
|
||||
const arch_name = @tagName(target.cpu.arch);
|
||||
const os_name = try std.fmt.allocPrint(arena, "{s}.{d}", .{
|
||||
@tagName(target.os.tag),
|
||||
target.os.version_range.semver.min.major,
|
||||
});
|
||||
const s = std.fs.path.sep_str;
|
||||
const list = try arena.alloc([]const u8, 3);
|
||||
|
||||
list[0] = try std.fmt.allocPrint(
|
||||
arena,
|
||||
"{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-gnu",
|
||||
.{ zig_lib_dir, arch_name, os_name },
|
||||
);
|
||||
list[1] = try std.fmt.allocPrint(
|
||||
arena,
|
||||
"{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "any-{s}-any",
|
||||
.{ zig_lib_dir, os_name },
|
||||
);
|
||||
list[2] = try std.fmt.allocPrint(
|
||||
arena,
|
||||
"{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "any-macos-any",
|
||||
.{zig_lib_dir},
|
||||
);
|
||||
|
||||
return LibCDirs{
|
||||
.libc_include_dir_list = list,
|
||||
.libc_installation = null,
|
||||
};
|
||||
}
|
||||
|
||||
fn detectLibCIncludeDirs(
|
||||
arena: *Allocator,
|
||||
zig_lib_dir: []const u8,
|
||||
@@ -3784,6 +3812,7 @@ fn detectLibCIncludeDirs(
|
||||
link_libc: bool,
|
||||
link_system_libs: bool,
|
||||
libc_installation: ?*const LibCInstallation,
|
||||
has_macos_sdk: bool,
|
||||
) !LibCDirs {
|
||||
if (!link_libc) {
|
||||
return LibCDirs{
|
||||
@@ -3800,11 +3829,14 @@ fn detectLibCIncludeDirs(
|
||||
// using the system libc installation.
|
||||
if (link_system_libs and is_native_abi and !target.isMinGW()) {
|
||||
if (target.isDarwin()) {
|
||||
// For Darwin/macOS, we are all set with getSDKPath found earlier.
|
||||
return LibCDirs{
|
||||
.libc_include_dir_list = &[0][]u8{},
|
||||
.libc_installation = null,
|
||||
};
|
||||
return if (has_macos_sdk)
|
||||
// For Darwin/macOS, we are all set with getDarwinSDK found earlier.
|
||||
LibCDirs{
|
||||
.libc_include_dir_list = &[0][]u8{},
|
||||
.libc_installation = null,
|
||||
}
|
||||
else
|
||||
getZigShippedLibCIncludeDirsDarwin(arena, zig_lib_dir, target);
|
||||
}
|
||||
const libc = try arena.create(LibCInstallation);
|
||||
libc.* = try LibCInstallation.findNative(.{ .allocator = arena, .verbose = true });
|
||||
@@ -3815,36 +3847,14 @@ fn detectLibCIncludeDirs(
|
||||
// default if possible.
|
||||
if (target_util.canBuildLibC(target)) {
|
||||
switch (target.os.tag) {
|
||||
.macos => {
|
||||
const arch_name = @tagName(target.cpu.arch);
|
||||
const os_name = try std.fmt.allocPrint(arena, "{s}.{d}", .{
|
||||
@tagName(target.os.tag),
|
||||
target.os.version_range.semver.min.major,
|
||||
});
|
||||
const s = std.fs.path.sep_str;
|
||||
const list = try arena.alloc([]const u8, 3);
|
||||
|
||||
list[0] = try std.fmt.allocPrint(
|
||||
arena,
|
||||
"{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-gnu",
|
||||
.{ zig_lib_dir, arch_name, os_name },
|
||||
);
|
||||
list[1] = try std.fmt.allocPrint(
|
||||
arena,
|
||||
"{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "any-{s}-any",
|
||||
.{ zig_lib_dir, os_name },
|
||||
);
|
||||
list[2] = try std.fmt.allocPrint(
|
||||
arena,
|
||||
"{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "any-macos-any",
|
||||
.{zig_lib_dir},
|
||||
);
|
||||
|
||||
return LibCDirs{
|
||||
.libc_include_dir_list = list,
|
||||
.macos => return if (has_macos_sdk)
|
||||
// For Darwin/macOS, we are all set with getDarwinSDK found earlier.
|
||||
LibCDirs{
|
||||
.libc_include_dir_list = &[0][]u8{},
|
||||
.libc_installation = null,
|
||||
};
|
||||
},
|
||||
}
|
||||
else
|
||||
getZigShippedLibCIncludeDirsDarwin(arena, zig_lib_dir, target),
|
||||
else => {
|
||||
const generic_name = target_util.libCGenericName(target);
|
||||
// Some architectures are handled by the same set of headers.
|
||||
|
||||
@@ -153,6 +153,9 @@ pub const Options = struct {
|
||||
/// (Zig compiler development) Enable dumping of linker's state as JSON.
|
||||
enable_link_snapshots: bool = false,
|
||||
|
||||
/// (Darwin) Path and version of the native SDK if detected.
|
||||
native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null,
|
||||
|
||||
pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
|
||||
return if (options.use_lld) .Obj else options.output_mode;
|
||||
}
|
||||
|
||||
@@ -154,6 +154,7 @@ tentatives: std.AutoArrayHashMapUnmanaged(u32, void) = .{},
|
||||
locals_free_list: std.ArrayListUnmanaged(u32) = .{},
|
||||
globals_free_list: std.ArrayListUnmanaged(u32) = .{},
|
||||
|
||||
mh_execute_header_index: ?u32 = null,
|
||||
dyld_stub_binder_index: ?u32 = null,
|
||||
dyld_private_atom: ?*Atom = null,
|
||||
stub_helper_preamble_atom: ?*Atom = null,
|
||||
@@ -863,6 +864,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
|
||||
sect.offset = self.tlv_bss_file_offset;
|
||||
}
|
||||
|
||||
try self.createMhExecuteHeaderAtom();
|
||||
for (self.objects.items) |*object, object_id| {
|
||||
if (object.analyzed) continue;
|
||||
try self.resolveSymbolsInObject(@intCast(u16, object_id));
|
||||
@@ -2725,6 +2727,42 @@ fn resolveSymbolsInDylibs(self: *MachO) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn createMhExecuteHeaderAtom(self: *MachO) !void {
|
||||
if (self.mh_execute_header_index != null) return;
|
||||
|
||||
const match: MatchingSection = .{
|
||||
.seg = self.text_segment_cmd_index.?,
|
||||
.sect = self.text_section_index.?,
|
||||
};
|
||||
const n_strx = try self.makeString("__mh_execute_header");
|
||||
const local_sym_index = @intCast(u32, self.locals.items.len);
|
||||
var nlist = macho.nlist_64{
|
||||
.n_strx = n_strx,
|
||||
.n_type = macho.N_SECT,
|
||||
.n_sect = @intCast(u8, self.section_ordinals.getIndex(match).? + 1),
|
||||
.n_desc = 0,
|
||||
.n_value = 0,
|
||||
};
|
||||
try self.locals.append(self.base.allocator, nlist);
|
||||
|
||||
nlist.n_type |= macho.N_EXT;
|
||||
const global_sym_index = @intCast(u32, self.globals.items.len);
|
||||
try self.globals.append(self.base.allocator, nlist);
|
||||
try self.symbol_resolver.putNoClobber(self.base.allocator, n_strx, .{
|
||||
.where = .global,
|
||||
.where_index = global_sym_index,
|
||||
.local_sym_index = local_sym_index,
|
||||
.file = null,
|
||||
});
|
||||
|
||||
const atom = try self.createEmptyAtom(local_sym_index, 0, 0);
|
||||
const sym = &self.locals.items[local_sym_index];
|
||||
const vaddr = try self.allocateAtom(atom, 0, 1, match);
|
||||
sym.n_value = vaddr;
|
||||
atom.dirty = false;
|
||||
self.mh_execute_header_index = local_sym_index;
|
||||
}
|
||||
|
||||
fn resolveDyldStubBinder(self: *MachO) !void {
|
||||
if (self.dyld_stub_binder_index != null) return;
|
||||
|
||||
@@ -4077,8 +4115,16 @@ pub fn populateMissingMetadata(self: *MachO) !void {
|
||||
@sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version),
|
||||
@sizeOf(u64),
|
||||
));
|
||||
const ver = self.base.options.target.os.version_range.semver.min;
|
||||
const version = ver.major << 16 | ver.minor << 8 | ver.patch;
|
||||
const platform_version = blk: {
|
||||
const ver = self.base.options.target.os.version_range.semver.min;
|
||||
const platform_version = ver.major << 16 | ver.minor << 8;
|
||||
break :blk platform_version;
|
||||
};
|
||||
const sdk_version = if (self.base.options.native_darwin_sdk) |sdk| blk: {
|
||||
const ver = sdk.version;
|
||||
const sdk_version = ver.major << 16 | ver.minor << 8;
|
||||
break :blk sdk_version;
|
||||
} else platform_version;
|
||||
const is_simulator_abi = self.base.options.target.abi == .simulator;
|
||||
var cmd = commands.emptyGenericCommandWithData(macho.build_version_command{
|
||||
.cmd = macho.LC_BUILD_VERSION,
|
||||
@@ -4090,8 +4136,8 @@ pub fn populateMissingMetadata(self: *MachO) !void {
|
||||
.tvos => if (is_simulator_abi) macho.PLATFORM_TVOSSIMULATOR else macho.PLATFORM_TVOS,
|
||||
else => unreachable,
|
||||
},
|
||||
.minos = version,
|
||||
.sdk = version,
|
||||
.minos = platform_version,
|
||||
.sdk = sdk_version,
|
||||
.ntools = 1,
|
||||
});
|
||||
const ld_ver = macho.build_tool_version{
|
||||
|
||||
@@ -663,6 +663,7 @@ fn buildOutputType(
|
||||
var minor_subsystem_version: ?u32 = null;
|
||||
var wasi_exec_model: ?std.builtin.WasiExecModel = null;
|
||||
var enable_link_snapshots: bool = false;
|
||||
var native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null;
|
||||
|
||||
var system_libs = std.StringArrayHashMap(Compilation.SystemLib).init(gpa);
|
||||
defer system_libs.deinit();
|
||||
@@ -1857,10 +1858,13 @@ fn buildOutputType(
|
||||
}
|
||||
|
||||
const has_sysroot = if (comptime builtin.target.isDarwin()) outer: {
|
||||
if (try std.zig.system.darwin.getSDKPath(arena, target_info.target)) |sdk_path| {
|
||||
if (std.zig.system.darwin.isDarwinSDKInstalled(arena)) {
|
||||
const sdk = std.zig.system.darwin.getDarwinSDK(arena, target_info.target) orelse
|
||||
break :outer false;
|
||||
native_darwin_sdk = sdk;
|
||||
try clang_argv.ensureUnusedCapacity(2);
|
||||
clang_argv.appendAssumeCapacity("-isysroot");
|
||||
clang_argv.appendAssumeCapacity(sdk_path);
|
||||
clang_argv.appendAssumeCapacity(sdk.path);
|
||||
break :outer true;
|
||||
} else break :outer false;
|
||||
} else false;
|
||||
@@ -2340,6 +2344,7 @@ fn buildOutputType(
|
||||
.wasi_exec_model = wasi_exec_model,
|
||||
.debug_compile_errors = debug_compile_errors,
|
||||
.enable_link_snapshots = enable_link_snapshots,
|
||||
.native_darwin_sdk = native_darwin_sdk,
|
||||
}) catch |err| {
|
||||
fatal("unable to create compilation: {s}", .{@errorName(err)});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user