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 <stephen@hexops.com>
This commit is contained in:
Stephen Gutekanst 2021-12-10 03:33:31 -07:00 committed by Stephen Gutekanst
parent a9d378583d
commit 77d2e8a9a3

View file

@ -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 { 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; 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) { if (options.set_sysroot) {
var sdk_sysroot = std.fs.path.join(b.allocator, &.{ sdk_root_dir, "root/" }) catch unreachable; 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); 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 { 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: // Find the directory where the SDK should be located. We'll consider two locations:
// //
// 1. $SDK_PATH/<name> (if set, e.g. for testing changes to SDKs easily) // 1. $SDK_PATH/<name> (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 the SDK exists, return it. Otherwise, clone it.
if (std.fs.openDirAbsolute(sdk_root_dir, .{})) { 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) }); const current_revision = try getCurrentGitRevision(allocator, sdk_root_dir);
if (!custom_sdk_path) try exec(allocator, &[_][]const u8{ "git", "reset", "--quiet", "--hard", revision }, 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; return sdk_root_dir;
} else |err| return switch (err) { } else |err| return switch (err) {
error.FileNotFound => { 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 { fn exec(allocator: std.mem.Allocator, argv: []const []const u8, cwd: []const u8) !void {
const child = try std.ChildProcess.init(argv, allocator); const child = try std.ChildProcess.init(argv, allocator);
child.cwd = cwd; child.cwd = cwd;
child.stdin = std.io.getStdOut(); _ = try child.spawnAndWait();
child.stderr = std.io.getStdErr(); }
child.stdout = std.io.getStdOut();
try child.spawn(); fn getCurrentGitRevision(allocator: std.mem.Allocator, cwd: []const u8) ![]const u8 {
_ = try child.wait(); 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 { fn confirmAppleSDKAgreement(allocator: std.mem.Allocator) !bool {