diff --git a/lib/std/zig/system/darwin.zig b/lib/std/zig/system/darwin.zig index 30496e7d26..5ce769a792 100644 --- a/lib/std/zig/system/darwin.zig +++ b/lib/std/zig/system/darwin.zig @@ -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 --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 --show-sdk-path` which fetches the path to the SDK sysroot (if any). +/// Subsequently calls `xcrun --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"); } diff --git a/src/Compilation.zig b/src/Compilation.zig index 64e08c62be..ac25519190 100644 --- a/src/Compilation.zig +++ b/src/Compilation.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. diff --git a/src/link.zig b/src/link.zig index 38b91c9e39..77464737b0 100644 --- a/src/link.zig +++ b/src/link.zig @@ -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; } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 4d5133a959..37f905cdd4 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -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{ diff --git a/src/main.zig b/src/main.zig index 20ea5caded..5590e14d0e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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)}); };