authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-08-28 21:39:25+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-08-29 06:43:41+02:00
log3b2b9fcbc5e162063febf989883f29e55cc64c65
treec85bbce50c24a18df10e08889b1cb3cc3f69e335
parentc429bb5d2feb36f83e33ef95a25dd5bb7455f16c

darwin: move inference of SDK version into the linker

`std.zig.system.darwin.getSdk` now pulls only the SDK path so we execute a child process only once and not twice as it was until now since we parse the SDK version directly from the pulled path. This is actually how `ld64` does it too.

6 files changed, 88 insertions(+), 102 deletions(-)

lib/std/zig/system/NativePaths.zig+3-3
......@@ -81,9 +81,9 @@ pub fn detect(arena: Allocator, native_info: NativeTargetInfo) !NativePaths {
8181 if (comptime builtin.target.isDarwin()) {
8282 if (std.zig.system.darwin.isSdkInstalled(arena)) sdk: {
8383 const sdk = std.zig.system.darwin.getSdk(arena, native_target) orelse break :sdk;
84 try self.addLibDir(try std.fs.path.join(arena, &.{ sdk.path, "usr/lib" }));
85 try self.addFrameworkDir(try std.fs.path.join(arena, &.{ sdk.path, "System/Library/Frameworks" }));
86 try self.addIncludeDir(try std.fs.path.join(arena, &.{ sdk.path, "usr/include" }));
84 try self.addLibDir(try std.fs.path.join(arena, &.{ sdk, "usr/lib" }));
85 try self.addFrameworkDir(try std.fs.path.join(arena, &.{ sdk, "System/Library/Frameworks" }));
86 try self.addIncludeDir(try std.fs.path.join(arena, &.{ sdk, "usr/include" }));
8787 return self;
8888 }
8989 return self;
lib/std/zig/system/darwin.zig+14-87
......@@ -30,12 +30,11 @@ pub fn isSdkInstalled(allocator: Allocator) bool {
3030}
3131
3232/// Detect SDK on Darwin.
33/// Calls `xcrun --sdk <target_sdk> --show-sdk-path` which fetches the path to the SDK sysroot (if any).
34/// Subsequently calls `xcrun --sdk <target_sdk> --show-sdk-version` which fetches version of the SDK.
35/// The caller needs to deinit the resulting struct.
33/// Calls `xcrun --sdk <target_sdk> --show-sdk-path` which fetches the path to the SDK.
34/// Caller owns the memory.
3635/// stderr from xcrun is ignored.
3736/// If error.OutOfMemory occurs in Allocator, this function returns null.
38pub fn getSdk(allocator: Allocator, target: Target) ?Sdk {
37pub fn getSdk(allocator: Allocator, target: Target) ?[]const u8 {
3938 const is_simulator_abi = target.abi == .simulator;
4039 const sdk = switch (target.os.tag) {
4140 .macos => "macosx",
......@@ -44,91 +43,19 @@ pub fn getSdk(allocator: Allocator, target: Target) ?Sdk {
4443 .tvos => if (is_simulator_abi) "appletvsimulator" else "appletvos",
4544 else => return null,
4645 };
47 const path = path: {
48 const argv = &[_][]const u8{ "/usr/bin/xcrun", "--sdk", sdk, "--show-sdk-path" };
49 const result = std.process.Child.exec(.{ .allocator = allocator, .argv = argv }) catch return null;
50 defer {
51 allocator.free(result.stderr);
52 allocator.free(result.stdout);
53 }
54 switch (result.term) {
55 .Exited => |code| if (code != 0) return null,
56 else => return null,
57 }
58 const path = allocator.dupe(u8, mem.trimRight(u8, result.stdout, "\r\n")) catch return null;
59 break :path path;
60 };
61 const version = version: {
62 const argv = &[_][]const u8{ "/usr/bin/xcrun", "--sdk", sdk, "--show-sdk-version" };
63 const result = std.process.Child.exec(.{ .allocator = allocator, .argv = argv }) catch return null;
64 defer {
65 allocator.free(result.stderr);
66 allocator.free(result.stdout);
67 }
68 switch (result.term) {
69 .Exited => |code| if (code != 0) return null,
70 else => return null,
71 }
72 const raw_version = mem.trimRight(u8, result.stdout, "\r\n");
73 const version = parseSdkVersion(raw_version) orelse Version{
74 .major = 0,
75 .minor = 0,
76 .patch = 0,
77 };
78 break :version version;
79 };
80 return Sdk{
81 .path = path,
82 .version = version,
83 };
84}
85
86// Versions reported by Apple aren't exactly semantically valid as they usually omit
87// the patch component. Hence, we do a simple check for the number of components and
88// add the missing patch value if needed.
89fn parseSdkVersion(raw: []const u8) ?Version {
90 var buffer: [128]u8 = undefined;
91 if (raw.len > buffer.len) return null;
92 @memcpy(buffer[0..raw.len], raw);
93 const dots_count = mem.count(u8, raw, ".");
94 if (dots_count < 1) return null;
95 const len = if (dots_count < 2) blk: {
96 const patch_suffix = ".0";
97 buffer[raw.len..][0..patch_suffix.len].* = patch_suffix.*;
98 break :blk raw.len + patch_suffix.len;
99 } else raw.len;
100 return Version.parse(buffer[0..len]) catch null;
101}
102
103pub const Sdk = struct {
104 path: []const u8,
105 version: Version,
106
107 pub fn deinit(self: Sdk, allocator: Allocator) void {
108 allocator.free(self.path);
46 const argv = &[_][]const u8{ "/usr/bin/xcrun", "--sdk", sdk, "--show-sdk-path" };
47 const result = std.process.Child.exec(.{ .allocator = allocator, .argv = argv }) catch return null;
48 defer {
49 allocator.free(result.stderr);
50 allocator.free(result.stdout);
51 }
52 switch (result.term) {
53 .Exited => |code| if (code != 0) return null,
54 else => return null,
10955 }
110};
56 return allocator.dupe(u8, mem.trimRight(u8, result.stdout, "\r\n")) catch null;
57}
11158
11259test {
11360 _ = macos;
11461}
115
116const expect = std.testing.expect;
117const expectEqual = std.testing.expectEqual;
118
119fn testParseSdkVersionSuccess(exp: Version, raw: []const u8) !void {
120 const maybe_ver = parseSdkVersion(raw);
121 try expect(maybe_ver != null);
122 const ver = maybe_ver.?;
123 try expectEqual(exp.major, ver.major);
124 try expectEqual(exp.minor, ver.minor);
125 try expectEqual(exp.patch, ver.patch);
126}
127
128test "parseSdkVersion" {
129 try testParseSdkVersionSuccess(.{ .major = 13, .minor = 4, .patch = 0 }, "13.4");
130 try testParseSdkVersionSuccess(.{ .major = 13, .minor = 4, .patch = 1 }, "13.4.1");
131 try testParseSdkVersionSuccess(.{ .major = 11, .minor = 15, .patch = 0 }, "11.15");
132
133 try expect(parseSdkVersion("11") == null);
134}
src/libc_installation.zig+3-3
......@@ -187,13 +187,13 @@ pub const LibCInstallation = struct {
187187 return error.DarwinSdkNotFound;
188188 const sdk = std.zig.system.darwin.getSdk(args.allocator, args.target) orelse
189189 return error.DarwinSdkNotFound;
190 defer args.allocator.free(sdk.path);
190 defer args.allocator.free(sdk);
191191
192192 self.include_dir = try fs.path.join(args.allocator, &.{
193 sdk.path, "usr/include",
193 sdk, "usr/include",
194194 });
195195 self.sys_include_dir = try fs.path.join(args.allocator, &.{
196 sdk.path, "usr/include",
196 sdk, "usr/include",
197197 });
198198 return self;
199199 } else if (is_windows) {
src/link/MachO/load_commands.zig+61-2
......@@ -278,7 +278,11 @@ pub fn writeBuildVersionLC(options: *const link.Options, lc_writer: anytype) !vo
278278 const platform_version = @as(u32, @intCast(ver.major << 16 | ver.minor << 8));
279279 break :blk platform_version;
280280 };
281 const sdk_version: u32 = if (options.darwin_sdk_version) |ver|
281 const sdk_version: ?std.SemanticVersion = options.darwin_sdk_version orelse blk: {
282 if (options.sysroot) |path| break :blk inferSdkVersionFromSdkPath(path);
283 break :blk null;
284 };
285 const sdk_version_value: u32 = if (sdk_version) |ver|
282286 @intCast(ver.major << 16 | ver.minor << 8)
283287 else
284288 platform_version;
......@@ -293,7 +297,7 @@ pub fn writeBuildVersionLC(options: *const link.Options, lc_writer: anytype) !vo
293297 else => unreachable,
294298 },
295299 .minos = platform_version,
296 .sdk = sdk_version,
300 .sdk = sdk_version_value,
297301 .ntools = 1,
298302 });
299303 try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{
......@@ -315,3 +319,58 @@ pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, lc_writer: an
315319 }, lc_writer);
316320 }
317321}
322
323fn inferSdkVersionFromSdkPath(path: []const u8) ?std.SemanticVersion {
324 const stem = std.fs.path.stem(path);
325 const start = for (stem, 0..) |c, i| {
326 if (std.ascii.isDigit(c)) break i;
327 } else stem.len;
328 const end = for (stem[start..], start..) |c, i| {
329 if (std.ascii.isDigit(c) or c == '.') continue;
330 break i;
331 } else stem.len;
332 return parseSdkVersion(stem[start..end]);
333}
334
335// Versions reported by Apple aren't exactly semantically valid as they usually omit
336// the patch component, so we parse SDK value by hand.
337fn parseSdkVersion(raw: []const u8) ?std.SemanticVersion {
338 var parsed: std.SemanticVersion = .{
339 .major = 0,
340 .minor = 0,
341 .patch = 0,
342 };
343
344 const parseNext = struct {
345 fn parseNext(it: anytype) ?u16 {
346 const nn = it.next() orelse return null;
347 return std.fmt.parseInt(u16, nn, 10) catch null;
348 }
349 }.parseNext;
350
351 var it = std.mem.splitAny(u8, raw, ".");
352 parsed.major = parseNext(&it) orelse return null;
353 parsed.minor = parseNext(&it) orelse return null;
354 parsed.patch = parseNext(&it) orelse 0;
355 return parsed;
356}
357
358const expect = std.testing.expect;
359const expectEqual = std.testing.expectEqual;
360
361fn testParseSdkVersionSuccess(exp: std.SemanticVersion, raw: []const u8) !void {
362 const maybe_ver = parseSdkVersion(raw);
363 try expect(maybe_ver != null);
364 const ver = maybe_ver.?;
365 try expectEqual(exp.major, ver.major);
366 try expectEqual(exp.minor, ver.minor);
367 try expectEqual(exp.patch, ver.patch);
368}
369
370test "parseSdkVersion" {
371 try testParseSdkVersionSuccess(.{ .major = 13, .minor = 4, .patch = 0 }, "13.4");
372 try testParseSdkVersionSuccess(.{ .major = 13, .minor = 4, .patch = 1 }, "13.4.1");
373 try testParseSdkVersionSuccess(.{ .major = 11, .minor = 15, .patch = 0 }, "11.15");
374
375 try expect(parseSdkVersion("11") == null);
376}
test/link/macho/bugs/13056/build.zig+3-3
......@@ -23,13 +23,13 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2323 .name = "test",
2424 .optimize = optimize,
2525 });
26 exe.addSystemIncludePath(.{ .path = b.pathJoin(&.{ sdk.path, "/usr/include" }) });
27 exe.addIncludePath(.{ .path = b.pathJoin(&.{ sdk.path, "/usr/include/c++/v1" }) });
26 exe.addSystemIncludePath(.{ .path = b.pathJoin(&.{ sdk, "/usr/include" }) });
27 exe.addIncludePath(.{ .path = b.pathJoin(&.{ sdk, "/usr/include/c++/v1" }) });
2828 exe.addCSourceFile(.{ .file = .{ .path = "test.cpp" }, .flags = &.{
2929 "-nostdlib++",
3030 "-nostdinc++",
3131 } });
32 exe.addObjectFile(.{ .path = b.pathJoin(&.{ sdk.path, "/usr/lib/libc++.tbd" }) });
32 exe.addObjectFile(.{ .path = b.pathJoin(&.{ sdk, "/usr/lib/libc++.tbd" }) });
3333
3434 const run_cmd = b.addRunArtifact(exe);
3535 run_cmd.expectStdErrEqual("x: 5\n");
test/standalone/ios/build.zig+4-4
......@@ -14,7 +14,7 @@ pub fn build(b: *std.Build) void {
1414 };
1515 const target_info = std.zig.system.NativeTargetInfo.detect(target) catch @panic("couldn't detect native target");
1616 const sdk = std.zig.system.darwin.getSdk(b.allocator, target_info.target) orelse @panic("no iOS SDK found");
17 b.sysroot = sdk.path;
17 b.sysroot = sdk;
1818
1919 const exe = b.addExecutable(.{
2020 .name = "main",
......@@ -22,9 +22,9 @@ pub fn build(b: *std.Build) void {
2222 .target = target,
2323 });
2424 exe.addCSourceFile(.{ .file = .{ .path = "main.m" }, .flags = &.{} });
25 exe.addSystemIncludePath(.{ .path = b.pathJoin(&.{ sdk.path, "/usr/include" }) });
26 exe.addSystemFrameworkPath(.{ .path = b.pathJoin(&.{ sdk.path, "/System/Library/Frameworks" }) });
27 exe.addLibraryPath(.{ .path = b.pathJoin(&.{ sdk.path, "/usr/lib" }) });
25 exe.addSystemIncludePath(.{ .path = b.pathJoin(&.{ sdk, "/usr/include" }) });
26 exe.addSystemFrameworkPath(.{ .path = b.pathJoin(&.{ sdk, "/System/Library/Frameworks" }) });
27 exe.addLibraryPath(.{ .path = b.pathJoin(&.{ sdk, "/usr/lib" }) });
2828 exe.linkFramework("Foundation");
2929 exe.linkFramework("UIKit");
3030 exe.linkLibC();