Commit 5d6521d281

Michael Dusan <michael.dusan@gmail.com>
2023-09-15 07:05:14
macos: better SDK version detection
SDK version detection: - read SDKSettings.json before inferral from SDK path - vendored libc: add SDKSettings.json for SDK version info resolveLibSystem: - adjust search order to { search_dirs, { sysroot or vendored }} - previous search order was { sysroot, search_dirs, vendored }
1 parent 15fd7cd
Changed files (6)
lib/libc/darwin/SDKSettings.json
@@ -0,0 +1,1 @@
+{"MinimalDisplayName":"14.0"}
src/link/MachO/load_commands.zig
@@ -467,8 +467,34 @@ pub inline fn appleVersionToSemanticVersion(version: u32) std.SemanticVersion {
     };
 }
 
-pub fn inferSdkVersionFromSdkPath(path: []const u8) ?std.SemanticVersion {
-    const stem = std.fs.path.stem(path);
+fn readSdkVersionString(arena: Allocator, dir: []const u8) ![]const u8 {
+    const sdk_path = try std.fs.path.join(arena, &.{ dir, "SDKSettings.json" });
+    const contents = try std.fs.cwd().readFileAlloc(arena, sdk_path, std.math.maxInt(u16));
+    const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{});
+    if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string;
+    return error.SdkVersionFailure;
+}
+
+pub fn inferSdkVersion(comp: *const Compilation) ?std.SemanticVersion {
+    var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
+    defer arena_allocator.deinit();
+    const arena = arena_allocator.allocator();
+    const options = comp.bin_file.options;
+
+    const sdk_dir = switch (options.libc_provider) {
+        .none => unreachable,
+        .installation => unreachable,
+        .sysroot => options.sysroot.?,
+        .vendored => std.fs.path.join(arena, &.{ comp.zig_lib_directory.path.?, "libc", "darwin" }) catch return null,
+    };
+
+    // prefer meta information if available
+    if (readSdkVersionString(arena, sdk_dir)) |ver| {
+        return parseSdkVersion(ver);
+    } else |_| {}
+
+    // infer from pathname
+    const stem = std.fs.path.stem(sdk_dir);
     const start = for (stem, 0..) |c, i| {
         if (std.ascii.isDigit(c)) break i;
     } else stem.len;
@@ -532,3 +558,4 @@ const mem = std.mem;
 const Allocator = mem.Allocator;
 const Dylib = @import("Dylib.zig");
 const MachO = @import("../MachO.zig");
+const Compilation = @import("../../Compilation.zig");
src/link/MachO/zld.zig
@@ -241,10 +241,7 @@ pub fn linkWithZld(
                 try argv.append(@tagName(platform.os_tag));
                 try argv.append(try std.fmt.allocPrint(arena, "{}", .{platform.version}));
 
-                const sdk_version: ?std.SemanticVersion = if (options.sysroot) |path|
-                    load_commands.inferSdkVersionFromSdkPath(path)
-                else
-                    null;
+                const sdk_version: ?std.SemanticVersion = load_commands.inferSdkVersion(comp);
                 if (sdk_version) |ver| {
                     try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}", .{ ver.major, ver.minor }));
                 } else {
@@ -591,10 +588,7 @@ pub fn linkWithZld(
         });
         {
             const platform = Platform.fromTarget(macho_file.base.options.target);
-            const sdk_version: ?std.SemanticVersion = if (macho_file.base.options.sysroot) |path|
-                load_commands.inferSdkVersionFromSdkPath(path)
-            else
-                null;
+            const sdk_version: ?std.SemanticVersion = load_commands.inferSdkVersion(comp);
             if (platform.isBuildVersionCompatible()) {
                 try load_commands.writeBuildVersionLC(platform, sdk_version, lc_writer);
             } else {
src/link/MachO.zig
@@ -558,10 +558,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
     });
     {
         const platform = Platform.fromTarget(self.base.options.target);
-        const sdk_version: ?std.SemanticVersion = if (self.base.options.sysroot) |path|
-            load_commands.inferSdkVersionFromSdkPath(path)
-        else
-            null;
+        const sdk_version: ?std.SemanticVersion = load_commands.inferSdkVersion(comp);
         if (platform.isBuildVersionCompatible()) {
             try load_commands.writeBuildVersionLC(platform, sdk_version, lc_writer);
         } else if (platform.isVersionMinCompatible()) {
@@ -647,11 +644,6 @@ pub fn resolveLibSystem(
     var checked_paths = std.ArrayList([]const u8).init(tmp_arena);
 
     success: {
-        if (self.base.options.sysroot) |root| {
-            const dir = try fs.path.join(tmp_arena, &[_][]const u8{ root, "usr", "lib" });
-            if (try accessLibPath(tmp_arena, &test_path, &checked_paths, dir, "libSystem")) break :success;
-        }
-
         for (search_dirs) |dir| if (try accessLibPath(
             tmp_arena,
             &test_path,
@@ -660,8 +652,18 @@ pub fn resolveLibSystem(
             "libSystem",
         )) break :success;
 
-        const dir = try comp.zig_lib_directory.join(tmp_arena, &[_][]const u8{ "libc", "darwin" });
-        if (try accessLibPath(tmp_arena, &test_path, &checked_paths, dir, "libSystem")) break :success;
+        switch (self.base.options.libc_provider) {
+            .none => unreachable,
+            .installation => unreachable,
+            .sysroot => {
+                const dir = try fs.path.join(tmp_arena, &[_][]const u8{ self.base.options.sysroot.?, "usr", "lib" });
+                if (try accessLibPath(tmp_arena, &test_path, &checked_paths, dir, "libSystem")) break :success;
+            },
+            .vendored => {
+                const dir = try comp.zig_lib_directory.join(tmp_arena, &[_][]const u8{ "libc", "darwin" });
+                if (try accessLibPath(tmp_arena, &test_path, &checked_paths, dir, "libSystem")) break :success;
+            },
+        }
 
         try self.reportMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{});
         return;
src/Compilation.zig
@@ -1554,6 +1554,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
             .use_lld = use_lld,
             .use_llvm = use_llvm,
             .use_lib_llvm = use_lib_llvm,
+            .libc_provider = libc_dirs.provider,
             .link_libc = link_libc,
             .link_libcpp = link_libcpp,
             .link_libunwind = link_libunwind,
@@ -5654,6 +5655,7 @@ const LibCDirs = struct {
     libc_installation: ?*const LibCInstallation,
     libc_framework_dir_list: []const []const u8,
     sysroot: ?[]const u8,
+    provider: link.LibCProvider,
 };
 
 fn getZigShippedLibCIncludeDirsDarwin(arena: Allocator, zig_lib_dir: []const u8) !LibCDirs {
@@ -5669,6 +5671,7 @@ fn getZigShippedLibCIncludeDirsDarwin(arena: Allocator, zig_lib_dir: []const u8)
         .libc_installation = null,
         .libc_framework_dir_list = &.{},
         .sysroot = null,
+        .provider = .vendored,
     };
 }
 
@@ -5686,6 +5689,7 @@ pub fn detectLibCIncludeDirs(
             .libc_installation = null,
             .libc_framework_dir_list = &.{},
             .sysroot = null,
+            .provider = .none,
         };
     }
 
@@ -5743,6 +5747,7 @@ pub fn detectLibCIncludeDirs(
         .libc_installation = null,
         .libc_framework_dir_list = &.{},
         .sysroot = null,
+        .provider = .none,
     };
 }
 
@@ -5797,6 +5802,7 @@ fn detectLibCFromLibCInstallation(arena: Allocator, target: Target, lci: *const
         .libc_installation = lci,
         .libc_framework_dir_list = framework_list.items,
         .sysroot = sysroot,
+        .provider = if (sysroot == null) .installation else .sysroot,
     };
 }
 
@@ -5858,6 +5864,7 @@ fn detectLibCFromBuilding(
         .libc_installation = null,
         .libc_framework_dir_list = &.{},
         .sysroot = null,
+        .provider = .vendored,
     };
 }
 
src/link.zig
@@ -134,6 +134,7 @@ pub const Options = struct {
     /// Otherwise (depending on `use_lld`) this link code directly outputs and updates the final binary.
     use_llvm: bool,
     use_lib_llvm: bool,
+    libc_provider: LibCProvider,
     link_libc: bool,
     link_libcpp: bool,
     link_libunwind: bool,
@@ -282,6 +283,13 @@ pub const HashStyle = enum { sysv, gnu, both };
 
 pub const CompressDebugSections = enum { none, zlib };
 
+pub const LibCProvider = enum {
+    none,
+    installation,
+    sysroot,
+    vendored,
+};
+
 pub const File = struct {
     tag: Tag,
     options: Options,