authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-09-24 11:02:41+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-09-26 18:21:21+01:00
log5125399764909dcf19e861806e8a73688121461d
tree8b05147e9c661a3aa34576b89f2b5aa5389951b5
parentf8dd54d7d0e1f7ad71dcc7518659a9c30e09155d
signature Commit is signed but in an unrecognized format.

compiler: move Darwin SDK version resolution to the frontend

The SDK version is determined by parsing the `SDKSettings.json` file in the Darwin SDK (or in `lib/libc/darwin/` when using our vendored libs). (The old Mach-O linker also had a fallback strategy of determining the version from the SDK path, but that seems unnecessary nowadays.) Moving that parsing to `src/link.zig`, and calling into it at the same time as handling libc (libSystem), turns out to simplify this logic quite a bit!

7 files changed, 220 insertions(+), 160 deletions(-)

lib/std/zig/LibCDirs.zig+5-25
...@@ -11,15 +11,6 @@ libc_include_dir_list: []const []const u8,...@@ -11,15 +11,6 @@ libc_include_dir_list: []const []const u8,
11libc_installation: ?*const LibCInstallation,11libc_installation: ?*const LibCInstallation,
12libc_framework_dir_list: []const []const u8,12libc_framework_dir_list: []const []const u8,
13sysroot: ?[]const u8,13sysroot: ?[]const u8,
14darwin_sdk_layout: ?DarwinSdkLayout,
15
16/// The filesystem layout of darwin SDK elements.
17pub const DarwinSdkLayout = enum {
18 /// macOS SDK layout: TOP { /usr/include, /usr/lib, /System/Library/Frameworks }.
19 sdk,
20 /// Shipped libc layout: TOP { /lib/libc/include, /lib/libc/darwin, <NONE> }.
21 vendored,
22};
2314
24pub fn detect(15pub fn detect(
25 arena: Allocator,16 arena: Allocator,
...@@ -37,7 +28,6 @@ pub fn detect(...@@ -37,7 +28,6 @@ pub fn detect(
37 .libc_installation = null,28 .libc_installation = null,
38 .libc_framework_dir_list = &.{},29 .libc_framework_dir_list = &.{},
39 .sysroot = null,30 .sysroot = null,
40 .darwin_sdk_layout = null,
41 };31 };
42 }32 }
4333
...@@ -102,13 +92,11 @@ pub fn detect(...@@ -102,13 +92,11 @@ pub fn detect(
102 .libc_installation = null,92 .libc_installation = null,
103 .libc_framework_dir_list = &.{},93 .libc_framework_dir_list = &.{},
104 .sysroot = null,94 .sysroot = null,
105 .darwin_sdk_layout = null,
106 };95 };
107}96}
10897
109fn detectFromInstallation(arena: Allocator, target: *const std.Target, lci: *const LibCInstallation) !LibCDirs {98fn detectFromInstallation(arena: Allocator, target: *const std.Target, lci: *const LibCInstallation) !LibCDirs {
110 var list = try std.array_list.Managed([]const u8).initCapacity(arena, 5);99 var list = try std.array_list.Managed([]const u8).initCapacity(arena, 5);
111 var framework_list = std.array_list.Managed([]const u8).init(arena);
112100
113 list.appendAssumeCapacity(lci.include_dir.?);101 list.appendAssumeCapacity(lci.include_dir.?);
114102
...@@ -149,21 +137,15 @@ fn detectFromInstallation(arena: Allocator, target: *const std.Target, lci: *con...@@ -149,21 +137,15 @@ fn detectFromInstallation(arena: Allocator, target: *const std.Target, lci: *con
149 }137 }
150 }138 }
151139
152 var sysroot: ?[]const u8 = null;140 const frameworks: []const []const u8 = if (target.os.tag.isDarwin()) try arena.dupe([]const u8, &.{
153141 try std.fs.path.join(arena, &.{ lci.darwin_sdk_dir.?, "System", "Library", "Frameworks" }),
154 if (target.os.tag.isDarwin()) d: {142 }) else &.{};
155 const down1 = std.fs.path.dirname(lci.sys_include_dir.?) orelse break :d;
156 const down2 = std.fs.path.dirname(down1) orelse break :d;
157 try framework_list.append(try std.fs.path.join(arena, &.{ down2, "System", "Library", "Frameworks" }));
158 sysroot = down2;
159 }
160143
161 return .{144 return .{
162 .libc_include_dir_list = list.items,145 .libc_include_dir_list = list.items,
163 .libc_installation = lci,146 .libc_installation = lci,
164 .libc_framework_dir_list = framework_list.items,147 .libc_framework_dir_list = frameworks,
165 .sysroot = sysroot,148 .sysroot = if (target.os.tag.isDarwin()) lci.darwin_sdk_dir.? else null,
166 .darwin_sdk_layout = if (sysroot == null) null else .sdk,
167 };149 };
168}150}
169151
...@@ -178,7 +160,6 @@ pub fn detectFromBuilding(arena: Allocator, zig_lib_dir: Path, target: *const st...@@ -178,7 +160,6 @@ pub fn detectFromBuilding(arena: Allocator, zig_lib_dir: Path, target: *const st
178 .libc_installation = null,160 .libc_installation = null,
179 .libc_framework_dir_list = &.{},161 .libc_framework_dir_list = &.{},
180 .sysroot = null,162 .sysroot = null,
181 .darwin_sdk_layout = .vendored,
182 };163 };
183 }164 }
184165
...@@ -230,7 +211,6 @@ pub fn detectFromBuilding(arena: Allocator, zig_lib_dir: Path, target: *const st...@@ -230,7 +211,6 @@ pub fn detectFromBuilding(arena: Allocator, zig_lib_dir: Path, target: *const st
230 .libc_installation = null,211 .libc_installation = null,
231 .libc_framework_dir_list = &.{},212 .libc_framework_dir_list = &.{},
232 .sysroot = null,213 .sysroot = null,
233 .darwin_sdk_layout = .vendored,
234 };214 };
235}215}
236216
lib/std/zig/LibCInstallation.zig+18-15
...@@ -23,6 +23,7 @@ crt_dir: ?[]const u8 = null,...@@ -23,6 +23,7 @@ crt_dir: ?[]const u8 = null,
23msvc_lib_dir: ?[]const u8 = null,23msvc_lib_dir: ?[]const u8 = null,
24kernel32_lib_dir: ?[]const u8 = null,24kernel32_lib_dir: ?[]const u8 = null,
25cc_dir: ?[]const u8 = null,25cc_dir: ?[]const u8 = null,
26darwin_sdk_dir: ?[]const u8 = null,
2627
27pub const FindError = error{28pub const FindError = error{
28 OutOfMemory,29 OutOfMemory,
...@@ -121,19 +122,16 @@ pub fn parse(allocator: Allocator, io: Io, libc_file: []const u8, target: *const...@@ -121,19 +122,16 @@ pub fn parse(allocator: Allocator, io: Io, libc_file: []const u8, target: *const
121 });122 });
122 return error.ParseError;123 return error.ParseError;
123 }124 }
125 if (self.darwin_sdk_dir == null and os_tag.isDarwin()) {
126 log.err("darwin_sdk_dir may not be empty for {t}", .{os_tag});
127 return error.ParseError;
128 }
124129
125 return self;130 return self;
126}131}
127132
128pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void {133pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void {
129 @setEvalBranchQuota(4000);134 @setEvalBranchQuota(4000);
130 const include_dir = self.include_dir orelse "";
131 const sys_include_dir = self.sys_include_dir orelse "";
132 const crt_dir = self.crt_dir orelse "";
133 const msvc_lib_dir = self.msvc_lib_dir orelse "";
134 const kernel32_lib_dir = self.kernel32_lib_dir orelse "";
135 const cc_dir = self.cc_dir orelse "";
136
137 try out.print(135 try out.print(
138 \\# The directory that contains `stdlib.h`.136 \\# The directory that contains `stdlib.h`.
139 \\# On POSIX, can be found with: `cc -E -Wp,-v -xc /dev/null`137 \\# On POSIX, can be found with: `cc -E -Wp,-v -xc /dev/null`
...@@ -161,13 +159,18 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void {...@@ -161,13 +159,18 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void {
161 \\# Only needed when targeting MSVC on Windows.159 \\# Only needed when targeting MSVC on Windows.
162 \\kernel32_lib_dir={s}160 \\kernel32_lib_dir={s}
163 \\161 \\
162 \\# The directory that contains `SDKSettings.json`
163 \\# Only needed when targeting Darwin.
164 \\darwin_sdk_dir={s}
165 \\
164 , .{166 , .{
165 include_dir,167 self.include_dir orelse "",
166 sys_include_dir,168 self.sys_include_dir orelse "",
167 crt_dir,169 self.cc_dir orelse "",
168 msvc_lib_dir,170 self.crt_dir orelse "",
169 kernel32_lib_dir,171 self.msvc_lib_dir orelse "",
170 cc_dir,172 self.kernel32_lib_dir orelse "",
173 self.darwin_sdk_dir orelse "",
171 });174 });
172}175}
173176
...@@ -186,10 +189,10 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib...@@ -186,10 +189,10 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib
186 if (is_darwin and args.target.os.tag.isDarwin()) {189 if (is_darwin and args.target.os.tag.isDarwin()) {
187 if (!std.zig.system.darwin.isSdkInstalled(gpa, io))190 if (!std.zig.system.darwin.isSdkInstalled(gpa, io))
188 return error.DarwinSdkNotFound;191 return error.DarwinSdkNotFound;
189 const sdk = std.zig.system.darwin.getSdk(gpa, io, args.target) orelse192 self.darwin_sdk_dir = std.zig.system.darwin.getSdk(gpa, io, args.target) orelse
190 return error.DarwinSdkNotFound;193 return error.DarwinSdkNotFound;
191 defer gpa.free(sdk);
192194
195 const sdk = self.darwin_sdk_dir.?;
193 self.include_dir = try fs.path.join(gpa, &.{ sdk, "usr/include" });196 self.include_dir = try fs.path.join(gpa, &.{ sdk, "usr/include" });
194 self.sys_include_dir = try fs.path.join(gpa, &.{ sdk, "usr/include" });197 self.sys_include_dir = try fs.path.join(gpa, &.{ sdk, "usr/include" });
195 self.crt_dir = try fs.path.join(gpa, &.{ sdk, "usr/lib" });198 self.crt_dir = try fs.path.join(gpa, &.{ sdk, "usr/lib" });
src/Compilation.zig+10-6
...@@ -2208,7 +2208,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,...@@ -2208,7 +2208,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
2208 .z_relro = options.linker_z_relro,2208 .z_relro = options.linker_z_relro,
2209 .z_common_page_size = options.linker_z_common_page_size,2209 .z_common_page_size = options.linker_z_common_page_size,
2210 .z_max_page_size = options.linker_z_max_page_size,2210 .z_max_page_size = options.linker_z_max_page_size,
2211 .darwin_sdk_layout = libc_dirs.darwin_sdk_layout,
2212 .frameworks = options.frameworks,2211 .frameworks = options.frameworks,
2213 .lib_directories = options.lib_directories,2212 .lib_directories = options.lib_directories,
2214 .framework_dirs = options.framework_dirs,2213 .framework_dirs = options.framework_dirs,
...@@ -2453,10 +2452,16 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,...@@ -2453,10 +2452,16 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
2453 // Loads the libraries provided by `target_util.libcFullLinkFlags(target)`.2452 // Loads the libraries provided by `target_util.libcFullLinkFlags(target)`.
2454 comp.oneshot_prelink_tasks.appendAssumeCapacity(.load_host_libc);2453 comp.oneshot_prelink_tasks.appendAssumeCapacity(.load_host_libc);
2455 } else if (target.isDarwinLibC()) {2454 } else if (target.isDarwinLibC()) {
2456 try comp.oneshot_prelink_tasks.append(gpa, .{ .load_tbd = .{2455 try comp.oneshot_prelink_tasks.appendSlice(gpa, &.{
2457 .root_dir = comp.dirs.zig_lib,2456 .{ .load_tbd = .{
2458 .sub_path = "libc" ++ fs.path.sep_str ++ "darwin" ++ fs.path.sep_str ++ "libSystem.tbd",2457 .root_dir = comp.dirs.zig_lib,
2459 } });2458 .sub_path = "libc" ++ fs.path.sep_str ++ "darwin" ++ fs.path.sep_str ++ "libSystem.tbd",
2459 } },
2460 .{ .load_darwin_sdk_settings = .{
2461 .root_dir = comp.dirs.zig_lib,
2462 .sub_path = "libc" ++ fs.path.sep_str ++ "darwin" ++ fs.path.sep_str ++ "SDKSettings.json",
2463 } },
2464 });
2460 // TODO delete logic from MachO flush() and queue up tasks here instead.2465 // TODO delete logic from MachO flush() and queue up tasks here instead.
2461 } else if (target.isMuslLibC()) {2466 } else if (target.isMuslLibC()) {
2462 if (!std.zig.target.canBuildLibC(target)) return diag.fail(.cross_libc_unavailable);2467 if (!std.zig.target.canBuildLibC(target)) return diag.fail(.cross_libc_unavailable);
...@@ -3487,7 +3492,6 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes...@@ -3487,7 +3492,6 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
3487 man.hash.add(opts.discard_local_symbols);3492 man.hash.add(opts.discard_local_symbols);
3488 man.hash.addOptional(opts.compatibility_version);3493 man.hash.addOptional(opts.compatibility_version);
3489 man.hash.addOptionalBytes(opts.install_name);3494 man.hash.addOptionalBytes(opts.install_name);
3490 man.hash.addOptional(opts.darwin_sdk_layout);
34913495
3492 // COFF specific stuff3496 // COFF specific stuff
3493 man.hash.addOptional(opts.subsystem);3497 man.hash.addOptional(opts.subsystem);
src/link.zig+150-2
...@@ -502,7 +502,6 @@ pub const File = struct {...@@ -502,7 +502,6 @@ pub const File = struct {
502 /// Remove dylibs that are unreachable by the entry point or exported symbols502 /// Remove dylibs that are unreachable by the entry point or exported symbols
503 dead_strip_dylibs: bool,503 dead_strip_dylibs: bool,
504 frameworks: []const MachO.Framework,504 frameworks: []const MachO.Framework,
505 darwin_sdk_layout: ?MachO.SdkLayout,
506 /// Force load all members of static archives that implement an505 /// Force load all members of static archives that implement an
507 /// Objective-C class or category506 /// Objective-C class or category
508 force_load_objc: bool,507 force_load_objc: bool,
...@@ -1248,6 +1247,59 @@ pub const File = struct {...@@ -1248,6 +1247,59 @@ pub const File = struct {
1248 }1247 }
1249 }1248 }
12501249
1250 fn loadDarwinSdkSettings(base: *File, sdk_settings_path: Path) Error!void {
1251 const comp = base.comp;
1252 const io = comp.io;
1253 const arena = comp.arena;
1254 const diags = &comp.link_diags;
1255
1256 const contents = sdk_settings_path.root_dir.handle.readFileAlloc(
1257 io,
1258 sdk_settings_path.sub_path,
1259 arena,
1260 .limited(1024 * 1024 * 256),
1261 ) catch |err| switch (err) {
1262 error.OutOfMemory, error.Canceled => |e| return e,
1263 else => |e| return diags.failParse(sdk_settings_path, "failed to parse Darwin SDK settings: {t}", .{e}),
1264 };
1265
1266 const parsed = std.json.parseFromSlice(std.json.Value, arena, contents, .{}) catch |err| switch (err) {
1267 error.OutOfMemory => |e| return e,
1268 else => |e| return diags.failParse(sdk_settings_path, "failed to parse Darwin SDK settings: {t}", .{e}),
1269 };
1270 const parsed_object = switch (parsed.value) {
1271 .object => |obj| obj,
1272 else => return diags.failParse(sdk_settings_path, "failed to parse Darwin SDK settings: file is not a JSON object", .{}),
1273 };
1274
1275 const version_json = parsed_object.get("MinimalDisplayName") orelse return diags.failParse(
1276 sdk_settings_path,
1277 "failed to parse Darwin SDK settings: 'MinimalDisplayName' missing",
1278 .{},
1279 );
1280 const version_str: []const u8 = switch (version_json) {
1281 .string => |str| str,
1282 else => return diags.failParse(
1283 sdk_settings_path,
1284 "failed to parse Darwin SDK settings: 'MinimalDisplayName' not a string",
1285 .{},
1286 ),
1287 };
1288 const version: DarwinSdkVersion = try .parse(diags, sdk_settings_path, version_str);
1289 try base.setDarwinSdkVersion(version);
1290 }
1291
1292 fn setDarwinSdkVersion(base: *File, version: DarwinSdkVersion) Error!void {
1293 assert(!base.post_prelink);
1294 switch (base.tag) {
1295 inline .macho, .macho2 => |tag| {
1296 dev.check(tag.devFeature());
1297 return @as(*tag.Type(), @fieldParentPtr("base", base)).setDarwinSdkVersion(version);
1298 },
1299 else => unreachable,
1300 }
1301 }
1302
1251 /// Called when all linker inputs have been sent via `loadInput`. After1303 /// Called when all linker inputs have been sent via `loadInput`. After
1252 /// this, `loadInput` will not be called anymore.1304 /// this, `loadInput` will not be called anymore.
1253 pub fn prelink(base: *File) Error!void {1305 pub fn prelink(base: *File) Error!void {
...@@ -1451,6 +1503,7 @@ pub const PrelinkTask = union(enum) {...@@ -1451,6 +1503,7 @@ pub const PrelinkTask = union(enum) {
1451 /// GNU ld script.1503 /// GNU ld script.
1452 load_dso: Path,1504 load_dso: Path,
1453 load_tbd: Path,1505 load_tbd: Path,
1506 load_darwin_sdk_settings: Path,
1454};1507};
1455pub const ZcuTask = union(enum) {1508pub const ZcuTask = union(enum) {
1456 /// Sent once per update, as the very first `ZcuTask` in the update. Indicates that all per-file1509 /// Sent once per update, as the very first `ZcuTask` in the update. Indicates that all per-file
...@@ -1589,7 +1642,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {...@@ -1589,7 +1642,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
1589 error.FileNotFound => {},1642 error.FileNotFound => {},
1590 error.AlreadyReported => return,1643 error.AlreadyReported => return,
1591 error.Canceled => io.recancel(),1644 error.Canceled => io.recancel(),
1592 else => |e| diags.addParseError(archive_path, "failed to parse archive {f}: {s}", .{ archive_path, @errorName(e) }),1645 else => |e| diags.addParseError(archive_path, "failed to parse archive: {s}", .{@errorName(e)}),
1593 }1646 }
15941647
1595 diags.addError("failed to find library '{s}'", .{lib_name});1648 diags.addError("failed to find library '{s}'", .{lib_name});
...@@ -1612,6 +1665,19 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {...@@ -1612,6 +1665,19 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
1612 }1665 }
1613 }1666 }
16141667
1668 if (target.os.tag.isDarwin()) {
1669 const sdk_settings_path: Path = .initCwd(std.fmt.allocPrint(
1670 comp.arena,
1671 "{s}" ++ sep ++ "SDKSettings.json",
1672 .{libc_installation.darwin_sdk_dir.?},
1673 ) catch return diags.setAllocFailure());
1674 base.loadDarwinSdkSettings(sdk_settings_path) catch |err| switch (err) {
1675 error.OutOfMemory => return diags.setAllocFailure(),
1676 error.Canceled => return io.recancel(),
1677 error.AlreadyReported => return,
1678 };
1679 }
1680
1615 if (target.os.tag == .windows and target.abi == .msvc) {1681 if (target.os.tag == .windows and target.abi == .msvc) {
1616 const inputs: []const struct {1682 const inputs: []const struct {
1617 dir: enum { crt, msvc_lib, kernel32_lib },1683 dir: enum { crt, msvc_lib, kernel32_lib },
...@@ -1709,6 +1775,15 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {...@@ -1709,6 +1775,15 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
1709 else => |e| diags.addParseError(path, "failed to parse tbd: {t}", .{e}),1775 else => |e| diags.addParseError(path, "failed to parse tbd: {t}", .{e}),
1710 }1776 }
1711 },1777 },
1778 .load_darwin_sdk_settings => |path| {
1779 const prog_node = comp.link_prog_node.start("Parse SDK Settings", 0);
1780 defer prog_node.end();
1781 base.loadDarwinSdkSettings(path) catch |err| switch (err) {
1782 error.OutOfMemory => return diags.setAllocFailure(),
1783 error.Canceled => return io.recancel(),
1784 error.AlreadyReported => return,
1785 };
1786 },
1712 }1787 }
1713}1788}
1714pub fn doZcuTask(comp: *Compilation, tid: Zcu.PerThread.Id, task: ZcuTask) void {1789pub fn doZcuTask(comp: *Compilation, tid: Zcu.PerThread.Id, task: ZcuTask) void {
...@@ -2793,3 +2868,76 @@ pub fn firstObjectInput(inputs: []const Input) ?Input.Object {...@@ -2793,3 +2868,76 @@ pub fn firstObjectInput(inputs: []const Input) ?Input.Object {
2793 };2868 };
2794 return null;2869 return null;
2795}2870}
2871
2872pub const DarwinSdkVersion = packed struct(u32) {
2873 patch: u8,
2874 minor: u8,
2875 major: u16,
2876
2877 fn parse(
2878 diags: *Diags,
2879 /// Passed to `Diags.failParse` on error.
2880 sdk_settings_path: Path,
2881 version_str: []const u8,
2882 ) error{AlreadyReported}!DarwinSdkVersion {
2883 var it = std.mem.splitScalar(u8, version_str, '.');
2884
2885 const major = try parseComponent(
2886 .major,
2887 diags,
2888 sdk_settings_path,
2889 version_str,
2890 it.first(),
2891 );
2892 const minor = if (it.next()) |component_str| try parseComponent(
2893 .minor,
2894 diags,
2895 sdk_settings_path,
2896 version_str,
2897 component_str,
2898 ) else return diags.failParse(
2899 sdk_settings_path,
2900 "failed to parse Darwin SDK version {q}: missing minor version",
2901 .{version_str},
2902 );
2903 const patch = if (it.next()) |component_str| try parseComponent(
2904 .patch,
2905 diags,
2906 sdk_settings_path,
2907 version_str,
2908 component_str,
2909 ) else 0; // Apple sometimes omit the patch version
2910
2911 if (it.next() != null) return diags.failParse(
2912 sdk_settings_path,
2913 "failed to parse Darwin SDK version {q}: too many version components",
2914 .{version_str},
2915 );
2916
2917 return .{ .major = major, .minor = minor, .patch = patch };
2918 }
2919
2920 fn parseComponent(
2921 comptime component: enum { major, minor, patch },
2922 diags: *Diags,
2923 /// Passed to `Diags.failParse` on error.
2924 sdk_settings_path: Path,
2925 /// Only used for error messages.
2926 version_str: []const u8,
2927 component_str: []const u8,
2928 ) error{AlreadyReported}!@FieldType(DarwinSdkVersion, @tagName(component)) {
2929 const Int = @FieldType(DarwinSdkVersion, @tagName(component));
2930 return std.fmt.parseInt(Int, component_str, 10) catch |err| switch (err) {
2931 error.Overflow => return diags.failParse(
2932 sdk_settings_path,
2933 "failed to parse Darwin SDK version {q}: {t} version {q} too large",
2934 .{ version_str, component, component_str },
2935 ),
2936 error.InvalidCharacter => return diags.failParse(
2937 sdk_settings_path,
2938 "failed to parse Darwin SDK version {q}: invalid {t} version {q}",
2939 .{ version_str, component, component_str },
2940 ),
2941 };
2942 }
2943};
src/link/MachO.zig+26-100
...@@ -106,9 +106,7 @@ has_tlv: AtomicBool = AtomicBool.init(false),...@@ -106,9 +106,7 @@ has_tlv: AtomicBool = AtomicBool.init(false),
106binds_to_weak: AtomicBool = AtomicBool.init(false),106binds_to_weak: AtomicBool = AtomicBool.init(false),
107weak_defines: AtomicBool = AtomicBool.init(false),107weak_defines: AtomicBool = AtomicBool.init(false),
108108
109/// Options109// Options
110/// SDK layout
111sdk_layout: ?SdkLayout,
112/// Size of the __PAGEZERO segment.110/// Size of the __PAGEZERO segment.
113pagezero_size: ?u64,111pagezero_size: ?u64,
114/// Minimum space for future expansion of the load commands.112/// Minimum space for future expansion of the load commands.
...@@ -134,7 +132,7 @@ compatibility_version: ?std.SemanticVersion,...@@ -134,7 +132,7 @@ compatibility_version: ?std.SemanticVersion,
134/// Entry name132/// Entry name
135entry_name: ?[]const u8,133entry_name: ?[]const u8,
136platform: Platform,134platform: Platform,
137sdk_version: ?std.SemanticVersion,135sdk_version: ?link.DarwinSdkVersion,
138/// When set to true, the linker will hoist all dylibs including system dependent dylibs.136/// When set to true, the linker will hoist all dylibs including system dependent dylibs.
139no_implicit_dylibs: bool = false,137no_implicit_dylibs: bool = false,
140/// Whether the linker should parse and always force load objects containing ObjC in archives.138/// Whether the linker should parse and always force load objects containing ObjC in archives.
...@@ -198,7 +196,6 @@ pub fn createEmpty(...@@ -198,7 +196,6 @@ pub fn createEmpty(
198 .headerpad_size = options.headerpad_size,196 .headerpad_size = options.headerpad_size,
199 .headerpad_max_install_names = options.headerpad_max_install_names,197 .headerpad_max_install_names = options.headerpad_max_install_names,
200 .dead_strip_dylibs = options.dead_strip_dylibs,198 .dead_strip_dylibs = options.dead_strip_dylibs,
201 .sdk_layout = options.darwin_sdk_layout,
202 .frameworks = options.frameworks,199 .frameworks = options.frameworks,
203 .install_name = options.install_name,200 .install_name = options.install_name,
204 .entitlements = options.entitlements,201 .entitlements = options.entitlements,
...@@ -210,7 +207,7 @@ pub fn createEmpty(...@@ -210,7 +207,7 @@ pub fn createEmpty(
210 .named => |name| name,207 .named => |name| name,
211 },208 },
212 .platform = Platform.fromTarget(target),209 .platform = Platform.fromTarget(target),
213 .sdk_version = if (options.darwin_sdk_layout) |layout| inferSdkVersion(comp, layout) else null,210 .sdk_version = null,
214 .undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error",211 .undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error",
215 // TODO delete this, directories must instead be resolved by the frontend212 // TODO delete this, directories must instead be resolved by the frontend
216 .lib_directories = options.lib_directories,213 .lib_directories = options.lib_directories,
...@@ -604,12 +601,20 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {...@@ -604,12 +601,20 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
604601
605 try argv.append("-platform_version");602 try argv.append("-platform_version");
606 try argv.append(@tagName(self.platform.os_tag));603 try argv.append(@tagName(self.platform.os_tag));
607 try argv.append(try std.fmt.allocPrint(arena, "{f}", .{self.platform.version}));604 try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}.{d}", .{
605 self.platform.version.major,
606 self.platform.version.minor,
607 self.platform.version.patch,
608 }));
608609
609 if (self.sdk_version) |ver| {610 if (self.sdk_version) |ver| {
610 try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}", .{ ver.major, ver.minor }));611 try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}", .{ ver.major, ver.minor }));
611 } else {612 } else {
612 try argv.append(try std.fmt.allocPrint(arena, "{f}", .{self.platform.version}));613 try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}.{d}", .{
614 self.platform.version.major,
615 self.platform.version.minor,
616 self.platform.version.patch,
617 }));
613 }618 }
614619
615 if (comp.sysroot) |syslibroot| {620 if (comp.sysroot) |syslibroot| {
...@@ -4101,7 +4106,7 @@ pub const null_sym = macho.nlist_64{...@@ -4101,7 +4106,7 @@ pub const null_sym = macho.nlist_64{
4101pub const Platform = struct {4106pub const Platform = struct {
4102 os_tag: std.Target.Os.Tag,4107 os_tag: std.Target.Os.Tag,
4103 abi: std.Target.Abi,4108 abi: std.Target.Abi,
4104 version: std.SemanticVersion,4109 version: link.DarwinSdkVersion,
41054110
4106 /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to4111 /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to
4107 /// the extracted minimum platform version.4112 /// the extracted minimum platform version.
...@@ -4128,7 +4133,7 @@ pub const Platform = struct {...@@ -4128,7 +4133,7 @@ pub const Platform = struct {
4128 => .simulator,4133 => .simulator,
4129 else => .none,4134 else => .none,
4130 },4135 },
4131 .version = appleVersionToSemanticVersion(cmd.minos),4136 .version = @fromBackingInt(cmd.minos),
4132 };4137 };
4133 },4138 },
4134 .VERSION_MIN_IPHONEOS,4139 .VERSION_MIN_IPHONEOS,
...@@ -4147,7 +4152,7 @@ pub const Platform = struct {...@@ -4147,7 +4152,7 @@ pub const Platform = struct {
4147 else => unreachable,4152 else => unreachable,
4148 },4153 },
4149 .abi = .none,4154 .abi = .none,
4150 .version = appleVersionToSemanticVersion(cmd.version),4155 .version = @fromBackingInt(cmd.version),
4151 };4156 };
4152 },4157 },
4153 else => unreachable,4158 else => unreachable,
...@@ -4155,17 +4160,18 @@ pub const Platform = struct {...@@ -4155,17 +4160,18 @@ pub const Platform = struct {
4155 }4160 }
41564161
4157 pub fn fromTarget(target: *const std.Target) Platform {4162 pub fn fromTarget(target: *const std.Target) Platform {
4163 const semver = target.os.version_range.semver.min;
4158 return .{4164 return .{
4159 .os_tag = target.os.tag,4165 .os_tag = target.os.tag,
4160 .abi = target.abi,4166 .abi = target.abi,
4161 .version = target.os.version_range.semver.min,4167 .version = .{
4168 .major = @intCast(semver.major),
4169 .minor = @intCast(semver.minor),
4170 .patch = @intCast(semver.patch),
4171 },
4162 };4172 };
4163 }4173 }
41644174
4165 pub fn toAppleVersion(plat: Platform) u32 {
4166 return semanticVersionToAppleVersion(plat.version);
4167 }
4168
4169 pub fn toApplePlatform(plat: Platform) macho.PLATFORM {4175 pub fn toApplePlatform(plat: Platform) macho.PLATFORM {
4170 return switch (plat.os_tag) {4176 return switch (plat.os_tag) {
4171 .driverkit => .DRIVERKIT,4177 .driverkit => .DRIVERKIT,
...@@ -4182,7 +4188,7 @@ pub const Platform = struct {...@@ -4182,7 +4188,7 @@ pub const Platform = struct {
4182 pub fn isBuildVersionCompatible(plat: Platform) bool {4188 pub fn isBuildVersionCompatible(plat: Platform) bool {
4183 inline for (supported_platforms) |sup_plat| {4189 inline for (supported_platforms) |sup_plat| {
4184 if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {4190 if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
4185 return sup_plat[2] <= plat.toAppleVersion();4191 return sup_plat[2] <= @backingInt(plat.version);
4186 }4192 }
4187 }4193 }
4188 return false;4194 return false;
...@@ -4191,7 +4197,7 @@ pub const Platform = struct {...@@ -4191,7 +4197,7 @@ pub const Platform = struct {
4191 pub fn isVersionMinCompatible(plat: Platform) bool {4197 pub fn isVersionMinCompatible(plat: Platform) bool {
4192 inline for (supported_platforms) |sup_plat| {4198 inline for (supported_platforms) |sup_plat| {
4193 if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {4199 if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
4194 return sup_plat[3] <= plat.toAppleVersion();4200 return sup_plat[3] <= @backingInt(plat.version);
4195 }4201 }
4196 }4202 }
4197 return false;4203 return false;
...@@ -4250,86 +4256,8 @@ const supported_platforms = [_]SupportedPlatforms{...@@ -4250,86 +4256,8 @@ const supported_platforms = [_]SupportedPlatforms{
4250};4256};
4251// zig fmt: on4257// zig fmt: on
42524258
4253pub inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 {4259pub fn setDarwinSdkVersion(self: *MachO, version: link.DarwinSdkVersion) link.Error!void {
4254 const major = version.major;4260 self.sdk_version = version;
4255 const minor = version.minor;
4256 const patch = version.patch;
4257 return (@as(u32, @intCast(major)) << 16) | (@as(u32, @intCast(minor)) << 8) | @as(u32, @intCast(patch));
4258}
4259
4260pub inline fn appleVersionToSemanticVersion(version: u32) std.SemanticVersion {
4261 return .{
4262 .major = @as(u16, @truncate(version >> 16)),
4263 .minor = @as(u8, @truncate(version >> 8)),
4264 .patch = @as(u8, @truncate(version)),
4265 };
4266}
4267
4268fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersion {
4269 const gpa = comp.gpa;
4270
4271 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
4272 defer arena_allocator.deinit();
4273 const arena = arena_allocator.allocator();
4274
4275 const io = comp.io;
4276
4277 const sdk_dir = switch (sdk_layout) {
4278 .sdk => comp.sysroot.?,
4279 .vendored => fs.path.join(arena, &.{ comp.dirs.zig_lib.path.?, "libc", "darwin" }) catch return null,
4280 };
4281 if (readSdkVersionFromSettings(arena, io, sdk_dir)) |ver| {
4282 return parseSdkVersion(ver);
4283 } else |_| {
4284 // Read from settings should always succeed when vendored.
4285 // TODO: convert to fatal linker error
4286 if (sdk_layout == .vendored) @panic("zig installation bug: unable to parse SDK version");
4287 }
4288
4289 // infer from pathname
4290 const stem = fs.path.stem(sdk_dir);
4291 const start = for (stem, 0..) |c, i| {
4292 if (std.ascii.isDigit(c)) break i;
4293 } else stem.len;
4294 const end = for (stem[start..], start..) |c, i| {
4295 if (std.ascii.isDigit(c) or c == '.') continue;
4296 break i;
4297 } else stem.len;
4298 return parseSdkVersion(stem[start..end]);
4299}
4300
4301// Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout.
4302// Use property `MinimalDisplayName` to determine version.
4303// The file/property is also available with vendored libc.
4304fn readSdkVersionFromSettings(arena: Allocator, io: Io, dir: []const u8) ![]const u8 {
4305 const sdk_path = try fs.path.join(arena, &.{ dir, "SDKSettings.json" });
4306 const contents = try Io.Dir.cwd().readFileAlloc(io, sdk_path, arena, .limited(std.math.maxInt(u16)));
4307 const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{});
4308 if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string;
4309 return error.SdkVersionFailure;
4310}
4311
4312// Versions reported by Apple aren't exactly semantically valid as they usually omit
4313// the patch component, so we parse SDK value by hand.
4314fn parseSdkVersion(raw: []const u8) ?std.SemanticVersion {
4315 var parsed: std.SemanticVersion = .{
4316 .major = 0,
4317 .minor = 0,
4318 .patch = 0,
4319 };
4320
4321 const parseNext = struct {
4322 fn parseNext(it: anytype) ?u16 {
4323 const nn = it.next() orelse return null;
4324 return std.fmt.parseInt(u16, nn, 10) catch null;
4325 }
4326 }.parseNext;
4327
4328 var it = std.mem.splitAny(u8, raw, ".");
4329 parsed.major = parseNext(&it) orelse return null;
4330 parsed.minor = parseNext(&it) orelse return null;
4331 parsed.patch = parseNext(&it) orelse 0;
4332 return parsed;
4333}4261}
43344262
4335/// When allocating, the ideal_capacity is calculated by4263/// When allocating, the ideal_capacity is calculated by
...@@ -4377,8 +4305,6 @@ const SystemLib = struct {...@@ -4377,8 +4305,6 @@ const SystemLib = struct {
4377 }4305 }
4378};4306};
43794307
4380pub const SdkLayout = std.zig.LibCDirs.DarwinSdkLayout;
4381
4382const UndefinedTreatment = enum {4308const UndefinedTreatment = enum {
4383 @"error",4309 @"error",
4384 warn,4310 warn,
src/link/MachO/load_commands.zig+7-12
...@@ -9,6 +9,7 @@ const Allocator = std.mem.Allocator;...@@ -9,6 +9,7 @@ const Allocator = std.mem.Allocator;
9const DebugSymbols = @import("DebugSymbols.zig");9const DebugSymbols = @import("DebugSymbols.zig");
10const Dylib = @import("Dylib.zig");10const Dylib = @import("Dylib.zig");
11const MachO = @import("../MachO.zig");11const MachO = @import("../MachO.zig");
12const link = @import("../../link.zig");
1213
13pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld";14pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld";
1415
...@@ -296,7 +297,7 @@ pub fn writeRpathLC(rpath: []const u8, writer: *Writer) !void {...@@ -296,7 +297,7 @@ pub fn writeRpathLC(rpath: []const u8, writer: *Writer) !void {
296 }297 }
297}298}
298299
299pub fn writeVersionMinLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: *Writer) !void {300pub fn writeVersionMinLC(platform: MachO.Platform, sdk_version: ?link.DarwinSdkVersion, writer: *Writer) !void {
300 const cmd: macho.LC = switch (platform.os_tag) {301 const cmd: macho.LC = switch (platform.os_tag) {
301 .macos => .VERSION_MIN_MACOSX,302 .macos => .VERSION_MIN_MACOSX,
302 .ios, .maccatalyst => .VERSION_MIN_IPHONEOS,303 .ios, .maccatalyst => .VERSION_MIN_IPHONEOS,
...@@ -306,24 +307,18 @@ pub fn writeVersionMinLC(platform: MachO.Platform, sdk_version: ?std.SemanticVer...@@ -306,24 +307,18 @@ pub fn writeVersionMinLC(platform: MachO.Platform, sdk_version: ?std.SemanticVer
306 };307 };
307 try writer.writeAll(mem.asBytes(&macho.version_min_command{308 try writer.writeAll(mem.asBytes(&macho.version_min_command{
308 .cmd = cmd,309 .cmd = cmd,
309 .version = platform.toAppleVersion(),310 .version = @backingInt(platform.version),
310 .sdk = if (sdk_version) |ver|311 .sdk = @backingInt(sdk_version orelse platform.version),
311 MachO.semanticVersionToAppleVersion(ver)
312 else
313 platform.toAppleVersion(),
314 }));312 }));
315}313}
316314
317pub fn writeBuildVersionLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: *Writer) !void {315pub fn writeBuildVersionLC(platform: MachO.Platform, sdk_version: ?link.DarwinSdkVersion, writer: *Writer) !void {
318 const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);316 const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
319 try writer.writeStruct(@as(macho.build_version_command, .{317 try writer.writeStruct(@as(macho.build_version_command, .{
320 .cmdsize = cmdsize,318 .cmdsize = cmdsize,
321 .platform = platform.toApplePlatform(),319 .platform = platform.toApplePlatform(),
322 .minos = platform.toAppleVersion(),320 .minos = @backingInt(platform.version),
323 .sdk = if (sdk_version) |ver|321 .sdk = @backingInt(sdk_version orelse platform.version),
324 MachO.semanticVersionToAppleVersion(ver)
325 else
326 platform.toAppleVersion(),
327 .ntools = 1,322 .ntools = 1,
328 }), .little);323 }), .little);
329 try writer.writeAll(mem.asBytes(&macho.build_tool_version{324 try writer.writeAll(mem.asBytes(&macho.build_tool_version{
src/link/MachO2.zig+4
...@@ -138,6 +138,10 @@ pub fn loadInput(macho: *MachO, input: link.Input) link.Error!void {...@@ -138,6 +138,10 @@ pub fn loadInput(macho: *MachO, input: link.Input) link.Error!void {
138 _ = input;138 _ = input;
139 @panic("TODO");139 @panic("TODO");
140}140}
141pub fn setDarwinSdkVersion(macho: *MachO, version: link.DarwinSdkVersion) link.Error!void {
142 _ = macho;
143 std.log.err("MLUGG TODO: sdk version is '{d}.{d}.{d}'", .{ version.major, version.minor, version.patch });
144}
141pub fn prelink(macho: *MachO, prog_node: std.Progress.Node) link.Error!void {145pub fn prelink(macho: *MachO, prog_node: std.Progress.Node) link.Error!void {
142 _ = macho;146 _ = macho;
143 _ = prog_node;147 _ = prog_node;