From 77d2e8a9a3da771a877b403d8e015b8ecdcbbe82 Mon Sep 17 00:00:00 2001 From: Stephen Gutekanst Date: Fri, 10 Dec 2021 03:33:31 -0700 Subject: [PATCH] glfw: improve consecutive build times by 4-12x This substantially reduces consecutive build times when using mach/glfw. `system_sdk.getSdkPath` is frequently invoked as part of the build process, and previously it was doing some fairly involved work (ensuring the native SDK is at the right revision, needless `git fetch` in native SDKs to check for updates, etc.) We now do far less work in `getSdkPath`, and additionally cache the result in-memory. This improves build times substantially, but especially so with consecutive (non-cold-cache) build times: * For `mach/glfw`: ~2s before, ~160ms after * For `mach/gpu`: , ~16s before, ~3.6s after Signed-off-by: Stephen Gutekanst --- glfw/system_sdk.zig | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/glfw/system_sdk.zig b/glfw/system_sdk.zig index c7a87409..ccacd497 100644 --- a/glfw/system_sdk.zig +++ b/glfw/system_sdk.zig @@ -93,7 +93,6 @@ fn includeSdkMacOS(b: *Builder, step: *std.build.LibExeObjStep, options: Options fn includeSdkLinuxX8664(b: *Builder, step: *std.build.LibExeObjStep, options: Options) void { const sdk_root_dir = getSdkRoot(b.allocator, options.github_org, options.linux_x86_64, options.linux_x86_64_revision) catch unreachable; - defer b.allocator.free(sdk_root_dir); if (options.set_sysroot) { var sdk_sysroot = std.fs.path.join(b.allocator, &.{ sdk_root_dir, "root/" }) catch unreachable; @@ -110,7 +109,19 @@ fn includeSdkLinuxX8664(b: *Builder, step: *std.build.LibExeObjStep, options: Op step.addLibPath(sdk_root_libs); } +var cached_sdk_root: ?[]const u8 = null; + +/// returns the SDK root path, determining it iff necessary. In a real application, this may be +/// tens or hundreds of times and so the result is cached in-memory (this also means the result +/// cannot be freed until the result will never be used again, which is fine as the Zig build system +/// Builder.allocator is an arena, you don't need to free.) fn getSdkRoot(allocator: std.mem.Allocator, org: []const u8, name: []const u8, revision: []const u8) ![]const u8 { + if (cached_sdk_root) |cached| return cached; + cached_sdk_root = try determineSdkRoot(allocator, org, name, revision); + return cached_sdk_root.?; +} + +fn determineSdkRoot(allocator: std.mem.Allocator, org: []const u8, name: []const u8, revision: []const u8) ![]const u8 { // Find the directory where the SDK should be located. We'll consider two locations: // // 1. $SDK_PATH/ (if set, e.g. for testing changes to SDKs easily) @@ -134,9 +145,16 @@ fn getSdkRoot(allocator: std.mem.Allocator, org: []const u8, name: []const u8, r // If the SDK exists, return it. Otherwise, clone it. if (std.fs.openDirAbsolute(sdk_root_dir, .{})) { - exec(allocator, &[_][]const u8{ "git", "fetch" }, sdk_root_dir) catch |err| std.debug.print("warning: failed to check for updates to {s}/{s}: {s}\n", .{ org, name, @errorName(err) }); - if (!custom_sdk_path) try exec(allocator, &[_][]const u8{ "git", "reset", "--quiet", "--hard", revision }, sdk_root_dir); - + const current_revision = try getCurrentGitRevision(allocator, sdk_root_dir); + if (!std.mem.eql(u8, current_revision, revision)) { + // Update the SDK to the target revision. This may be either forward or backwards in + // history (e.g. if building an old project) and so we use a hard reset. + // + // No reset is performed if specifying a custom SDK_PATH, as that is a development/debug + // option and could wipe out dev history. + exec(allocator, &[_][]const u8{ "git", "fetch" }, sdk_root_dir) catch |err| std.debug.print("warning: failed to check for updates to {s}/{s}: {s}\n", .{ org, name, @errorName(err) }); + if (!custom_sdk_path) try exec(allocator, &[_][]const u8{ "git", "reset", "--quiet", "--hard", revision }, sdk_root_dir); + } return sdk_root_dir; } else |err| return switch (err) { error.FileNotFound => { @@ -160,11 +178,14 @@ fn getSdkRoot(allocator: std.mem.Allocator, org: []const u8, name: []const u8, r fn exec(allocator: std.mem.Allocator, argv: []const []const u8, cwd: []const u8) !void { const child = try std.ChildProcess.init(argv, allocator); child.cwd = cwd; - child.stdin = std.io.getStdOut(); - child.stderr = std.io.getStdErr(); - child.stdout = std.io.getStdOut(); - try child.spawn(); - _ = try child.wait(); + _ = try child.spawnAndWait(); +} + +fn getCurrentGitRevision(allocator: std.mem.Allocator, cwd: []const u8) ![]const u8 { + const result = try std.ChildProcess.exec(.{ .allocator = allocator, .argv = &.{ "git", "rev-parse", "HEAD" }, .cwd = cwd }); + allocator.free(result.stderr); + if (result.stdout.len > 0) return result.stdout[0 .. result.stdout.len - 1]; // trim newline + return result.stdout; } fn confirmAppleSDKAgreement(allocator: std.mem.Allocator) !bool {