Commit 8799f7466d

Ryan Liptak <squeek502@hotmail.com>
2024-03-08 20:54:56
Report the progress of lazily building zig rc
jitCmd now takes a `server` option that will emit progress/errors via std.zig.Server when enabled.
1 parent dc4b058
Changed files (3)
lib
compiler
resinator
src
lib/compiler/resinator/main.zig
@@ -46,6 +46,12 @@ pub fn main() !void {
         },
     };
 
+    if (zig_integration) {
+        // Send progress with an empty string to indicate that the building of the
+        // resinator binary is finished and we've moved on to actually compiling the .rc file
+        try error_handler.server.serveStringMessage(.progress, "");
+    }
+
     var options = options: {
         var cli_diagnostics = cli.Diagnostics.init(allocator);
         defer cli_diagnostics.deinit();
src/Compilation.zig
@@ -4841,6 +4841,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
             try argv.appendSlice(&.{
                 self_exe_path,
                 "rc",
+                "--zig-integration",
                 "/:no-preprocess",
                 "/x", // ignore INCLUDE environment variable
                 "/c65001", // UTF-8 codepage
@@ -4849,31 +4850,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
             });
             try argv.appendSlice(&.{ "--", in_rc_path, out_res_path });
 
-            var child = std.ChildProcess.init(argv.items, arena);
-            child.stdin_behavior = .Ignore;
-            child.stdout_behavior = .Ignore;
-            child.stderr_behavior = .Pipe;
-
-            try child.spawn();
-
-            const stderr_reader = child.stderr.?.reader();
-            const stderr = try stderr_reader.readAllAlloc(arena, 10 * 1024 * 1024);
-            const term = child.wait() catch |err| {
-                return comp.failWin32Resource(win32_resource, "unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
-            };
-
-            switch (term) {
-                .Exited => |code| {
-                    if (code != 0) {
-                        log.err("zig rc failed with stderr:\n{s}", .{stderr});
-                        return comp.failWin32Resource(win32_resource, "zig rc exited with code {d}", .{code});
-                    }
-                },
-                else => {
-                    log.err("zig rc terminated with stderr:\n{s}", .{stderr});
-                    return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{});
-                },
-            }
+            try spawnZigRc(comp, win32_resource, src_basename, arena, argv.items, &child_progress_node);
 
             break :blk digest;
         };
@@ -4941,79 +4918,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
         try argv.appendSlice(rc_src.extra_flags);
         try argv.appendSlice(&.{ "--", rc_src.src_path, out_res_path });
 
-        {
-            var child = std.ChildProcess.init(argv.items, arena);
-            child.stdin_behavior = .Ignore;
-            child.stdout_behavior = .Pipe;
-            child.stderr_behavior = .Pipe;
-
-            child.spawn() catch |err| {
-                return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {s}", .{ argv.items[0], @errorName(err) });
-            };
-
-            var poller = std.io.poll(comp.gpa, enum { stdout }, .{
-                .stdout = child.stdout.?,
-            });
-            defer poller.deinit();
-
-            const stdout = poller.fifo(.stdout);
-
-            poll: while (true) {
-                while (stdout.readableLength() < @sizeOf(std.zig.Server.Message.Header)) {
-                    if (!(try poller.poll())) break :poll;
-                }
-                const header = stdout.reader().readStruct(std.zig.Server.Message.Header) catch unreachable;
-                while (stdout.readableLength() < header.bytes_len) {
-                    if (!(try poller.poll())) break :poll;
-                }
-                const body = stdout.readableSliceOfLen(header.bytes_len);
-
-                switch (header.tag) {
-                    // We expect exactly one ErrorBundle, and if any error_bundle header is
-                    // sent then it's a fatal error.
-                    .error_bundle => {
-                        const EbHdr = std.zig.Server.Message.ErrorBundle;
-                        const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body));
-                        const extra_bytes =
-                            body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len];
-                        const string_bytes =
-                            body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len];
-                        const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes);
-                        const extra_array = try comp.gpa.alloc(u32, unaligned_extra.len);
-                        @memcpy(extra_array, unaligned_extra);
-                        const error_bundle = .{
-                            .string_bytes = try comp.gpa.dupe(u8, string_bytes),
-                            .extra = extra_array,
-                        };
-                        return comp.failWin32ResourceWithOwnedBundle(win32_resource, error_bundle);
-                    },
-                    else => {}, // ignore other messages
-                }
-
-                stdout.discard(body.len);
-            }
-
-            // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace)
-            const stderr_reader = child.stderr.?.reader();
-            const stderr = try stderr_reader.readAllAlloc(arena, 10 * 1024 * 1024);
-
-            const term = child.wait() catch |err| {
-                return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {s}", .{ argv.items[0], @errorName(err) });
-            };
-
-            switch (term) {
-                .Exited => |code| {
-                    if (code != 0) {
-                        log.err("zig rc failed with stderr:\n{s}", .{stderr});
-                        return comp.failWin32Resource(win32_resource, "zig rc exited with code {d}", .{code});
-                    }
-                },
-                else => {
-                    log.err("zig rc terminated with stderr:\n{s}", .{stderr});
-                    return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{});
-                },
-            }
-        }
+        try spawnZigRc(comp, win32_resource, src_basename, arena, argv.items, &child_progress_node);
 
         // Read depfile and update cache manifest
         {
@@ -5079,6 +4984,100 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
     };
 }
 
+fn spawnZigRc(
+    comp: *Compilation,
+    win32_resource: *Win32Resource,
+    src_basename: []const u8,
+    arena: Allocator,
+    argv: []const []const u8,
+    child_progress_node: *std.Progress.Node,
+) !void {
+    var node_name: std.ArrayListUnmanaged(u8) = .{};
+    defer node_name.deinit(arena);
+
+    var child = std.ChildProcess.init(argv, arena);
+    child.stdin_behavior = .Ignore;
+    child.stdout_behavior = .Pipe;
+    child.stderr_behavior = .Pipe;
+
+    child.spawn() catch |err| {
+        return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {s}", .{ argv[0], @errorName(err) });
+    };
+
+    var poller = std.io.poll(comp.gpa, enum { stdout }, .{
+        .stdout = child.stdout.?,
+    });
+    defer poller.deinit();
+
+    const stdout = poller.fifo(.stdout);
+
+    poll: while (true) {
+        while (stdout.readableLength() < @sizeOf(std.zig.Server.Message.Header)) {
+            if (!(try poller.poll())) break :poll;
+        }
+        const header = stdout.reader().readStruct(std.zig.Server.Message.Header) catch unreachable;
+        while (stdout.readableLength() < header.bytes_len) {
+            if (!(try poller.poll())) break :poll;
+        }
+        const body = stdout.readableSliceOfLen(header.bytes_len);
+
+        switch (header.tag) {
+            // We expect exactly one ErrorBundle, and if any error_bundle header is
+            // sent then it's a fatal error.
+            .error_bundle => {
+                const EbHdr = std.zig.Server.Message.ErrorBundle;
+                const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body));
+                const extra_bytes =
+                    body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len];
+                const string_bytes =
+                    body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len];
+                const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes);
+                const extra_array = try comp.gpa.alloc(u32, unaligned_extra.len);
+                @memcpy(extra_array, unaligned_extra);
+                const error_bundle = std.zig.ErrorBundle{
+                    .string_bytes = try comp.gpa.dupe(u8, string_bytes),
+                    .extra = extra_array,
+                };
+                return comp.failWin32ResourceWithOwnedBundle(win32_resource, error_bundle);
+            },
+            .progress => {
+                node_name.clearRetainingCapacity();
+                if (body.len > 0) {
+                    try node_name.appendSlice(arena, "build 'zig rc'... ");
+                    try node_name.appendSlice(arena, body);
+                    child_progress_node.setName(node_name.items);
+                } else {
+                    child_progress_node.setName(src_basename);
+                }
+            },
+            else => {}, // ignore other messages
+        }
+
+        stdout.discard(body.len);
+    }
+
+    // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace)
+    const stderr_reader = child.stderr.?.reader();
+    const stderr = try stderr_reader.readAllAlloc(arena, 10 * 1024 * 1024);
+
+    const term = child.wait() catch |err| {
+        return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {s}", .{ argv[0], @errorName(err) });
+    };
+
+    switch (term) {
+        .Exited => |code| {
+            if (code != 0) {
+                log.err("zig rc failed with stderr:\n{s}", .{stderr});
+                return comp.failWin32Resource(win32_resource, "zig rc exited with code {d}", .{code});
+            }
+        },
+        else => {
+            log.err("zig rc terminated with stderr:\n{s}", .{stderr});
+            return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{});
+        },
+    }
+}
+
 pub fn tmpFilePath(comp: *Compilation, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 {
     const s = std.fs.path.sep_str;
     const rand_int = std.crypto.random.int(u64);
src/main.zig
@@ -291,11 +291,13 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
     } else if (mem.eql(u8, cmd, "translate-c")) {
         return buildOutputType(gpa, arena, args, .translate_c);
     } else if (mem.eql(u8, cmd, "rc")) {
+        const use_server = cmd_args.len > 0 and std.mem.eql(u8, cmd_args[0], "--zig-integration");
         return jitCmd(gpa, arena, cmd_args, .{
             .cmd_name = "resinator",
             .root_src_path = "resinator/main.zig",
             .depend_on_aro = true,
             .prepend_zig_lib_dir_path = true,
+            .server = use_server,
         });
     } else if (mem.eql(u8, cmd, "fmt")) {
         return jitCmd(gpa, arena, cmd_args, .{
@@ -5304,6 +5306,8 @@ const JitCmdOptions = struct {
     prepend_zig_exe_path: bool = false,
     depend_on_aro: bool = false,
     capture: ?*[]u8 = null,
+    /// Send progress and error bundles via std.zig.Server over stdout
+    server: bool = false,
 };
 
 fn jitCmd(
@@ -5449,10 +5453,52 @@ fn jitCmd(
         };
         defer comp.destroy();
 
-        updateModule(comp, color) catch |err| switch (err) {
-            error.SemanticAnalyzeFail => process.exit(2),
-            else => |e| return e,
-        };
+        if (options.server and !builtin.single_threaded) {
+            var reset: std.Thread.ResetEvent = .{};
+            var progress: std.Progress = .{
+                .terminal = null,
+                .root = .{
+                    .context = undefined,
+                    .parent = null,
+                    .name = "",
+                    .unprotected_estimated_total_items = 0,
+                    .unprotected_completed_items = 0,
+                },
+                .columns_written = 0,
+                .prev_refresh_timestamp = 0,
+                .timer = null,
+                .done = false,
+            };
+            const main_progress_node = &progress.root;
+            main_progress_node.context = &progress;
+            var server = std.zig.Server{
+                .out = std.io.getStdOut(),
+                .in = undefined, // won't be receiving messages
+                .receive_fifo = undefined, // won't be receiving messages
+            };
+
+            var progress_thread = try std.Thread.spawn(.{}, progressThread, .{
+                &progress, &server, &reset,
+            });
+            defer {
+                reset.set();
+                progress_thread.join();
+            }
+
+            try comp.update(main_progress_node);
+
+            var error_bundle = try comp.getAllErrorsAlloc();
+            defer error_bundle.deinit(comp.gpa);
+            if (error_bundle.errorMessageCount() > 0) {
+                try server.serveErrorBundle(error_bundle);
+                process.exit(2);
+            }
+        } else {
+            updateModule(comp, color) catch |err| switch (err) {
+                error.SemanticAnalyzeFail => process.exit(2),
+                else => |e| return e,
+            };
+        }
 
         const exe_path = try global_cache_directory.join(arena, &.{comp.cache_use.whole.bin_sub_path.?});
         child_argv.appendAssumeCapacity(exe_path);