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,
1111libc_installation: ?*const LibCInstallation,
1212libc_framework_dir_list: []const []const u8,
1313sysroot: ?[]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
2415pub fn detect(
2516 arena: Allocator,
......@@ -37,7 +28,6 @@ pub fn detect(
3728 .libc_installation = null,
3829 .libc_framework_dir_list = &.{},
3930 .sysroot = null,
40 .darwin_sdk_layout = null,
4131 };
4232 }
4333
......@@ -102,13 +92,11 @@ pub fn detect(
10292 .libc_installation = null,
10393 .libc_framework_dir_list = &.{},
10494 .sysroot = null,
105 .darwin_sdk_layout = null,
10695 };
10796}
10897
10998fn detectFromInstallation(arena: Allocator, target: *const std.Target, lci: *const LibCInstallation) !LibCDirs {
11099 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
113101 list.appendAssumeCapacity(lci.include_dir.?);
114102
......@@ -149,21 +137,15 @@ fn detectFromInstallation(arena: Allocator, target: *const std.Target, lci: *con
149137 }
150138 }
151139
152 var sysroot: ?[]const u8 = null;
153
154 if (target.os.tag.isDarwin()) d: {
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 }
140 const frameworks: []const []const u8 = if (target.os.tag.isDarwin()) try arena.dupe([]const u8, &.{
141 try std.fs.path.join(arena, &.{ lci.darwin_sdk_dir.?, "System", "Library", "Frameworks" }),
142 }) else &.{};
160143
161144 return .{
162145 .libc_include_dir_list = list.items,
163146 .libc_installation = lci,
164 .libc_framework_dir_list = framework_list.items,
165 .sysroot = sysroot,
166 .darwin_sdk_layout = if (sysroot == null) null else .sdk,
147 .libc_framework_dir_list = frameworks,
148 .sysroot = if (target.os.tag.isDarwin()) lci.darwin_sdk_dir.? else null,
167149 };
168150}
169151
......@@ -178,7 +160,6 @@ pub fn detectFromBuilding(arena: Allocator, zig_lib_dir: Path, target: *const st
178160 .libc_installation = null,
179161 .libc_framework_dir_list = &.{},
180162 .sysroot = null,
181 .darwin_sdk_layout = .vendored,
182163 };
183164 }
184165
......@@ -230,7 +211,6 @@ pub fn detectFromBuilding(arena: Allocator, zig_lib_dir: Path, target: *const st
230211 .libc_installation = null,
231212 .libc_framework_dir_list = &.{},
232213 .sysroot = null,
233 .darwin_sdk_layout = .vendored,
234214 };
235215}
236216
lib/std/zig/LibCInstallation.zig+18-15
......@@ -23,6 +23,7 @@ crt_dir: ?[]const u8 = null,
2323msvc_lib_dir: ?[]const u8 = null,
2424kernel32_lib_dir: ?[]const u8 = null,
2525cc_dir: ?[]const u8 = null,
26darwin_sdk_dir: ?[]const u8 = null,
2627
2728pub const FindError = error{
2829 OutOfMemory,
......@@ -121,19 +122,16 @@ pub fn parse(allocator: Allocator, io: Io, libc_file: []const u8, target: *const
121122 });
122123 return error.ParseError;
123124 }
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
125130 return self;
126131}
127132
128133pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void {
129134 @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
137135 try out.print(
138136 \\# The directory that contains `stdlib.h`.
139137 \\# 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 {
161159 \\# Only needed when targeting MSVC on Windows.
162160 \\kernel32_lib_dir={s}
163161 \\
162 \\# The directory that contains `SDKSettings.json`
163 \\# Only needed when targeting Darwin.
164 \\darwin_sdk_dir={s}
165 \\
164166 , .{
165 include_dir,
166 sys_include_dir,
167 crt_dir,
168 msvc_lib_dir,
169 kernel32_lib_dir,
170 cc_dir,
167 self.include_dir orelse "",
168 self.sys_include_dir orelse "",
169 self.cc_dir orelse "",
170 self.crt_dir orelse "",
171 self.msvc_lib_dir orelse "",
172 self.kernel32_lib_dir orelse "",
173 self.darwin_sdk_dir orelse "",
171174 });
172175}
173176
......@@ -186,10 +189,10 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib
186189 if (is_darwin and args.target.os.tag.isDarwin()) {
187190 if (!std.zig.system.darwin.isSdkInstalled(gpa, io))
188191 return error.DarwinSdkNotFound;
189 const sdk = std.zig.system.darwin.getSdk(gpa, io, args.target) orelse
192 self.darwin_sdk_dir = std.zig.system.darwin.getSdk(gpa, io, args.target) orelse
190193 return error.DarwinSdkNotFound;
191 defer gpa.free(sdk);
192194
195 const sdk = self.darwin_sdk_dir.?;
193196 self.include_dir = try fs.path.join(gpa, &.{ sdk, "usr/include" });
194197 self.sys_include_dir = try fs.path.join(gpa, &.{ sdk, "usr/include" });
195198 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,
22082208 .z_relro = options.linker_z_relro,
22092209 .z_common_page_size = options.linker_z_common_page_size,
22102210 .z_max_page_size = options.linker_z_max_page_size,
2211 .darwin_sdk_layout = libc_dirs.darwin_sdk_layout,
22122211 .frameworks = options.frameworks,
22132212 .lib_directories = options.lib_directories,
22142213 .framework_dirs = options.framework_dirs,
......@@ -2453,10 +2452,16 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
24532452 // Loads the libraries provided by `target_util.libcFullLinkFlags(target)`.
24542453 comp.oneshot_prelink_tasks.appendAssumeCapacity(.load_host_libc);
24552454 } else if (target.isDarwinLibC()) {
2456 try comp.oneshot_prelink_tasks.append(gpa, .{ .load_tbd = .{
2457 .root_dir = comp.dirs.zig_lib,
2458 .sub_path = "libc" ++ fs.path.sep_str ++ "darwin" ++ fs.path.sep_str ++ "libSystem.tbd",
2459 } });
2455 try comp.oneshot_prelink_tasks.appendSlice(gpa, &.{
2456 .{ .load_tbd = .{
2457 .root_dir = comp.dirs.zig_lib,
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 });
24602465 // TODO delete logic from MachO flush() and queue up tasks here instead.
24612466 } else if (target.isMuslLibC()) {
24622467 if (!std.zig.target.canBuildLibC(target)) return diag.fail(.cross_libc_unavailable);
......@@ -3487,7 +3492,6 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
34873492 man.hash.add(opts.discard_local_symbols);
34883493 man.hash.addOptional(opts.compatibility_version);
34893494 man.hash.addOptionalBytes(opts.install_name);
3490 man.hash.addOptional(opts.darwin_sdk_layout);
34913495
34923496 // COFF specific stuff
34933497 man.hash.addOptional(opts.subsystem);
src/link.zig+150-2
......@@ -502,7 +502,6 @@ pub const File = struct {
502502 /// Remove dylibs that are unreachable by the entry point or exported symbols
503503 dead_strip_dylibs: bool,
504504 frameworks: []const MachO.Framework,
505 darwin_sdk_layout: ?MachO.SdkLayout,
506505 /// Force load all members of static archives that implement an
507506 /// Objective-C class or category
508507 force_load_objc: bool,
......@@ -1248,6 +1247,59 @@ pub const File = struct {
12481247 }
12491248 }
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
12511303 /// Called when all linker inputs have been sent via `loadInput`. After
12521304 /// this, `loadInput` will not be called anymore.
12531305 pub fn prelink(base: *File) Error!void {
......@@ -1451,6 +1503,7 @@ pub const PrelinkTask = union(enum) {
14511503 /// GNU ld script.
14521504 load_dso: Path,
14531505 load_tbd: Path,
1506 load_darwin_sdk_settings: Path,
14541507};
14551508pub const ZcuTask = union(enum) {
14561509 /// 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 {
15891642 error.FileNotFound => {},
15901643 error.AlreadyReported => return,
15911644 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)}),
15931646 }
15941647
15951648 diags.addError("failed to find library '{s}'", .{lib_name});
......@@ -1612,6 +1665,19 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
16121665 }
16131666 }
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
16151681 if (target.os.tag == .windows and target.abi == .msvc) {
16161682 const inputs: []const struct {
16171683 dir: enum { crt, msvc_lib, kernel32_lib },
......@@ -1709,6 +1775,15 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
17091775 else => |e| diags.addParseError(path, "failed to parse tbd: {t}", .{e}),
17101776 }
17111777 },
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 },
17121787 }
17131788}
17141789pub fn doZcuTask(comp: *Compilation, tid: Zcu.PerThread.Id, task: ZcuTask) void {
......@@ -2793,3 +2868,76 @@ pub fn firstObjectInput(inputs: []const Input) ?Input.Object {
27932868 };
27942869 return null;
27952870}
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),
106106binds_to_weak: AtomicBool = AtomicBool.init(false),
107107weak_defines: AtomicBool = AtomicBool.init(false),
108108
109/// Options
110/// SDK layout
111sdk_layout: ?SdkLayout,
109// Options
112110/// Size of the __PAGEZERO segment.
113111pagezero_size: ?u64,
114112/// Minimum space for future expansion of the load commands.
......@@ -134,7 +132,7 @@ compatibility_version: ?std.SemanticVersion,
134132/// Entry name
135133entry_name: ?[]const u8,
136134platform: Platform,
137sdk_version: ?std.SemanticVersion,
135sdk_version: ?link.DarwinSdkVersion,
138136/// When set to true, the linker will hoist all dylibs including system dependent dylibs.
139137no_implicit_dylibs: bool = false,
140138/// Whether the linker should parse and always force load objects containing ObjC in archives.
......@@ -198,7 +196,6 @@ pub fn createEmpty(
198196 .headerpad_size = options.headerpad_size,
199197 .headerpad_max_install_names = options.headerpad_max_install_names,
200198 .dead_strip_dylibs = options.dead_strip_dylibs,
201 .sdk_layout = options.darwin_sdk_layout,
202199 .frameworks = options.frameworks,
203200 .install_name = options.install_name,
204201 .entitlements = options.entitlements,
......@@ -210,7 +207,7 @@ pub fn createEmpty(
210207 .named => |name| name,
211208 },
212209 .platform = Platform.fromTarget(target),
213 .sdk_version = if (options.darwin_sdk_layout) |layout| inferSdkVersion(comp, layout) else null,
210 .sdk_version = null,
214211 .undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error",
215212 // TODO delete this, directories must instead be resolved by the frontend
216213 .lib_directories = options.lib_directories,
......@@ -604,12 +601,20 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
604601
605602 try argv.append("-platform_version");
606603 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
609610 if (self.sdk_version) |ver| {
610611 try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}", .{ ver.major, ver.minor }));
611612 } 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 }));
613618 }
614619
615620 if (comp.sysroot) |syslibroot| {
......@@ -4101,7 +4106,7 @@ pub const null_sym = macho.nlist_64{
41014106pub const Platform = struct {
41024107 os_tag: std.Target.Os.Tag,
41034108 abi: std.Target.Abi,
4104 version: std.SemanticVersion,
4109 version: link.DarwinSdkVersion,
41054110
41064111 /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to
41074112 /// the extracted minimum platform version.
......@@ -4128,7 +4133,7 @@ pub const Platform = struct {
41284133 => .simulator,
41294134 else => .none,
41304135 },
4131 .version = appleVersionToSemanticVersion(cmd.minos),
4136 .version = @fromBackingInt(cmd.minos),
41324137 };
41334138 },
41344139 .VERSION_MIN_IPHONEOS,
......@@ -4147,7 +4152,7 @@ pub const Platform = struct {
41474152 else => unreachable,
41484153 },
41494154 .abi = .none,
4150 .version = appleVersionToSemanticVersion(cmd.version),
4155 .version = @fromBackingInt(cmd.version),
41514156 };
41524157 },
41534158 else => unreachable,
......@@ -4155,17 +4160,18 @@ pub const Platform = struct {
41554160 }
41564161
41574162 pub fn fromTarget(target: *const std.Target) Platform {
4163 const semver = target.os.version_range.semver.min;
41584164 return .{
41594165 .os_tag = target.os.tag,
41604166 .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 },
41624172 };
41634173 }
41644174
4165 pub fn toAppleVersion(plat: Platform) u32 {
4166 return semanticVersionToAppleVersion(plat.version);
4167 }
4168
41694175 pub fn toApplePlatform(plat: Platform) macho.PLATFORM {
41704176 return switch (plat.os_tag) {
41714177 .driverkit => .DRIVERKIT,
......@@ -4182,7 +4188,7 @@ pub const Platform = struct {
41824188 pub fn isBuildVersionCompatible(plat: Platform) bool {
41834189 inline for (supported_platforms) |sup_plat| {
41844190 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);
41864192 }
41874193 }
41884194 return false;
......@@ -4191,7 +4197,7 @@ pub const Platform = struct {
41914197 pub fn isVersionMinCompatible(plat: Platform) bool {
41924198 inline for (supported_platforms) |sup_plat| {
41934199 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);
41954201 }
41964202 }
41974203 return false;
......@@ -4250,86 +4256,8 @@ const supported_platforms = [_]SupportedPlatforms{
42504256};
42514257// zig fmt: on
42524258
4253pub inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 {
4254 const major = version.major;
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;
4259pub fn setDarwinSdkVersion(self: *MachO, version: link.DarwinSdkVersion) link.Error!void {
4260 self.sdk_version = version;
43334261}
43344262
43354263/// When allocating, the ideal_capacity is calculated by
......@@ -4377,8 +4305,6 @@ const SystemLib = struct {
43774305 }
43784306};
43794307
4380pub const SdkLayout = std.zig.LibCDirs.DarwinSdkLayout;
4381
43824308const UndefinedTreatment = enum {
43834309 @"error",
43844310 warn,
src/link/MachO/load_commands.zig+7-12
......@@ -9,6 +9,7 @@ const Allocator = std.mem.Allocator;
99const DebugSymbols = @import("DebugSymbols.zig");
1010const Dylib = @import("Dylib.zig");
1111const MachO = @import("../MachO.zig");
12const link = @import("../../link.zig");
1213
1314pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld";
1415
......@@ -296,7 +297,7 @@ pub fn writeRpathLC(rpath: []const u8, writer: *Writer) !void {
296297 }
297298}
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 {
300301 const cmd: macho.LC = switch (platform.os_tag) {
301302 .macos => .VERSION_MIN_MACOSX,
302303 .ios, .maccatalyst => .VERSION_MIN_IPHONEOS,
......@@ -306,24 +307,18 @@ pub fn writeVersionMinLC(platform: MachO.Platform, sdk_version: ?std.SemanticVer
306307 };
307308 try writer.writeAll(mem.asBytes(&macho.version_min_command{
308309 .cmd = cmd,
309 .version = platform.toAppleVersion(),
310 .sdk = if (sdk_version) |ver|
311 MachO.semanticVersionToAppleVersion(ver)
312 else
313 platform.toAppleVersion(),
310 .version = @backingInt(platform.version),
311 .sdk = @backingInt(sdk_version orelse platform.version),
314312 }));
315313}
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 {
318316 const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
319317 try writer.writeStruct(@as(macho.build_version_command, .{
320318 .cmdsize = cmdsize,
321319 .platform = platform.toApplePlatform(),
322 .minos = platform.toAppleVersion(),
323 .sdk = if (sdk_version) |ver|
324 MachO.semanticVersionToAppleVersion(ver)
325 else
326 platform.toAppleVersion(),
320 .minos = @backingInt(platform.version),
321 .sdk = @backingInt(sdk_version orelse platform.version),
327322 .ntools = 1,
328323 }), .little);
329324 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 {
138138 _ = input;
139139 @panic("TODO");
140140}
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}
141145pub fn prelink(macho: *MachO, prog_node: std.Progress.Node) link.Error!void {
142146 _ = macho;
143147 _ = prog_node;