authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-05 01:42:28+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-05 01:42:28+02:00
log5bf19f61ff5fe80a8f9aad5ce55812bd7e673fd0
tree5cf57fe99e767cfbfa827263d308f23616271232
parent5f74e4f3f8b909835ef794253a98a77868b3880e
parentd697d97a95688e873d2677367e94010cdaa3ac73

Merge pull request 'Implement foundation of the build system protocol' (#36147) from Techatrix/zig:build-system-protocol into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/36147 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

16 files changed, 958 insertions(+), 375 deletions(-)

lib/compiler/Maker.zig+272-44
......@@ -11,6 +11,7 @@ const File = std.Io.File;
1111const Io = std.Io;
1212const Dir = std.Io.Dir;
1313const Path = std.Build.Cache.Path;
14const Reader = std.Io.Reader;
1415const Writer = std.Io.Writer;
1516const assert = std.debug.assert;
1617const fatal = std.process.fatal;
......@@ -19,6 +20,8 @@ const log = std.log;
1920const mem = std.mem;
2021const process = std.process;
2122const Color = std.zig.Color;
23const Client = std.zig.Client;
24const Server = std.zig.Server;
2225const EnvVar = std.zig.EnvVar;
2326const default_local_zig_cache_basename = std.zig.default_local_zig_cache_basename;
2427const stringToEnum = std.meta.stringToEnum;
......@@ -51,10 +54,14 @@ max_rss_mutex: Io.Mutex,
5154skip_oom_steps: bool,
5255unit_test_timeout_ns: ?u64,
5356watch: bool,
57protocol_server: ?*AvoidableServer,
58protocol_server_mutex: Io.Mutex,
5459web_server: ?*AvoidableWebServer,
5560/// Allocated into `gpa`.
5661memory_blocked_steps: std.ArrayList(Configuration.Step.Index),
5762/// Allocated into `gpa`.
63initial_steps: std.array_hash_map.Auto(Configuration.Step.Index, void),
64/// Allocated into `gpa`.
5865step_stack: std.array_hash_map.Auto(Configuration.Step.Index, void),
5966pkg_config: PkgConfig,
6067
......@@ -67,6 +74,7 @@ var stdio_buffer_allocation: [256]u8 = undefined;
6774var stdout_writer_allocation: Io.File.Writer = undefined;
6875var debug_maker_leaks: bool = false;
6976
77const AvoidableServer = if (builtin.single_threaded) void else Server;
7078const AvoidableWebServer = if (builtin.single_threaded) void else WebServer;
7179
7280const is_debug_mode = builtin.mode == .debug;
......@@ -216,6 +224,7 @@ pub fn main(init: process.Init.Minimal) !void {
216224 var watch = false;
217225 var fuzz: ?Fuzz.Mode = null;
218226 var debounce_interval_ms: u16 = 50;
227 var listen: bool = false;
219228 var webui_listen: ?Io.net.IpAddress = null;
220229 var debug_pkg_config = false;
221230 var run_args: ?[]const []const u8 = null;
......@@ -422,6 +431,8 @@ pub fn main(init: process.Init.Minimal) !void {
422431 next_arg, err,
423432 });
424433 };
434 } else if (mem.eql(u8, arg, "--listen=-")) {
435 listen = true;
425436 } else if (mem.eql(u8, arg, "--webui")) {
426437 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
427438 } else if (mem.startsWith(u8, arg, "--webui=")) {
......@@ -559,7 +570,7 @@ pub fn main(init: process.Init.Minimal) !void {
559570 }
560571
561572 const early_exit_mode = fetch_only or help_menu or steps_menu or print_configuration != .none;
562 const server_mode = !early_exit_mode and (watch or webui_listen != null or fuzz != null);
573 const server_mode = !early_exit_mode and (watch or webui_listen != null or fuzz != null or listen);
563574
564575 process.raiseFileDescriptorLimit();
565576
......@@ -667,6 +678,25 @@ pub fn main(init: process.Init.Minimal) !void {
667678 break :ws &web_server_allocation;
668679 } else null;
669680
681 var stdin_buffer: [256]u8 = undefined;
682 var stdout_buffer: [256]u8 = undefined;
683 var stdin_reader = Io.File.stdin().reader(io, &stdin_buffer);
684 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
685
686 var protocol_server_allocation: AvoidableServer = undefined;
687 const protocol_server: ?*AvoidableServer = if (listen) s: {
688 if (builtin.single_threaded) fatal("--listen is not yet supported on single-threaded hosts", .{});
689 if (watch) fatal("using '--watch' and '--listen' together is not supported", .{});
690 if (fuzz != null) fatal("using '--fuzz' and '--listen' together is not supported", .{});
691 if (step_names.items.len > 0) fatal("build steps must be provided over the protocol instead of using CLI arguments", .{});
692 protocol_server_allocation = .{
693 .in = &stdin_reader.interface,
694 .out = &stdout_writer.interface,
695 };
696 try serveBSPHandshake(&protocol_server_allocation);
697 break :s &protocol_server_allocation;
698 } else null;
699
670700 while (true) {
671701 // If this fails, we can still start the server and wait for user
672702 // to request a rebuild. If it returns error.FailedButCacheIntact
......@@ -737,16 +767,25 @@ pub fn main(init: process.Init.Minimal) !void {
737767
738768 .watch = watch,
739769 .web_server = web_server,
770 .protocol_server = protocol_server,
771 .protocol_server_mutex = .init,
740772 .memory_blocked_steps = .empty,
773 .initial_steps = .empty,
741774 .step_stack = .empty,
742775 .pkg_config = .{ .debug = debug_pkg_config },
743776
744777 .error_style = error_style,
745778 .multiline_errors = multiline_errors,
746 .summary = summary orelse if (watch or webui_listen != null) .new else .failures,
779 .summary = summary orelse if (listen)
780 .none
781 else if (watch or webui_listen != null)
782 .new
783 else
784 .failures,
747785 };
748786 defer {
749787 maker.memory_blocked_steps.deinit(gpa);
788 maker.initial_steps.deinit(gpa);
750789 maker.step_stack.deinit(gpa);
751790 }
752791
......@@ -755,7 +794,91 @@ pub fn main(init: process.Init.Minimal) !void {
755794 maker.max_rss_is_default = true;
756795 }
757796
758 maker.prepare(step_names.items) catch |err| switch (err) {
797 if (protocol_server) |s| {
798 try s.serveStringMessage(.bsp_configuration, try arena.print("{f}", .{scanned_config.path}));
799
800 var w: ?Watch = null;
801
802 const Event = union(enum) {
803 message: Reader.Error!Client.Message.Header,
804 fs_event: if (Watch.have_impl) @typeInfo(@TypeOf(Watch.wait)).@"fn".return_type.? else noreturn,
805 };
806
807 var select_buffer: [2]Event = undefined;
808 var select: Io.Select(Event) = .init(io, &select_buffer);
809 defer select.cancelDiscard();
810
811 try select.concurrent(.message, Server.receiveMessage, .{s});
812
813 var in_debounce = false;
814 loop: switch (try select.await()) {
815 .message => |payload| {
816 const header: Client.Message.Header = try payload;
817 switch (header.tag) {
818 .exit => {
819 cleanExit(io, &scanned_config);
820 process.exit(0);
821 },
822 .bsp_build_steps => {
823 // Cancel existing file watching
824 select.cancelDiscard();
825 in_debounce = false;
826
827 const body = try s.in.takeStruct(Client.Message.BuildSteps, .little);
828 const steps = try s.in.readSliceEndianAlloc(gpa, Configuration.Step.Index, body.step_count, .little);
829 defer gpa.free(steps);
830 if (body.flags.watch and !Watch.have_impl) fatal("file watching is unavailable", .{});
831
832 try select.concurrent(.message, Server.receiveMessage, .{s});
833
834 maker.watch = body.flags.watch;
835 maker.prepare(steps) catch |err| switch (err) {
836 error.DependencyLoopDetected, error.InsufficientMemory => {
837 // TODO handle DependencyLoopDetected as error.FailedButCacheIntact
838 // and handle InsufficientMemory as error.AlreadyReported
839 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
840 process.exit(1);
841 },
842 else => |e| return e,
843 };
844
845 try maker.makeSteps(main_progress_node, null);
846
847 if (body.flags.watch) {
848 if (!Watch.have_impl) unreachable;
849 if (w == null) w = try .init(&maker);
850
851 try w.?.update(maker.step_stack.keys());
852 try select.concurrent(.fs_event, Watch.wait, .{ &w.?, if (in_debounce) .{ .ms = debounce_interval_ms } else .none });
853 }
854
855 continue :loop try select.await();
856 },
857 else => fatal("unsupported message: {t}", .{header.tag}),
858 }
859 },
860 .fs_event => |payload| {
861 if (!Watch.have_impl) unreachable;
862 switch (try payload) {
863 .timeout => {
864 assert(in_debounce);
865 markFailedStepsDirty(&maker);
866 try maker.makeSteps(main_progress_node, null);
867 in_debounce = false;
868 },
869 .dirty => in_debounce = true,
870 .clean => {},
871 }
872 try select.concurrent(.fs_event, Watch.wait, .{ &w.?, if (in_debounce) .{ .ms = debounce_interval_ms } else .none });
873 continue :loop try select.await();
874 },
875 }
876 }
877
878 const initial_steps = try maker.resolveTopLevelSteps(step_names.items);
879 defer gpa.free(initial_steps);
880
881 maker.prepare(initial_steps) catch |err| switch (err) {
759882 error.DependencyLoopDetected, error.InsufficientMemory => {
760883 // TODO handle DependencyLoopDetected as error.FailedButCacheIntact
761884 // and handle InsufficientMemory as error.AlreadyReported
......@@ -780,18 +903,7 @@ pub fn main(init: process.Init.Minimal) !void {
780903 error.WriteFailed => return stderr.file_writer.err.?,
781904 };
782905 }) {
783 if (web_server) |ws| ws.startBuild();
784
785 try maker.makeStepNames(step_names.items, main_progress_node, fuzz);
786
787 if (web_server) |ws| {
788 if (fuzz) |mode| if (mode != .forever) fatal(
789 "error: limited fuzzing is not implemented yet for --webui",
790 .{},
791 );
792
793 ws.finishBuild(.{ .fuzz = fuzz != null });
794 }
906 try maker.makeSteps(main_progress_node, fuzz);
795907
796908 if (web_server) |ws| {
797909 const c = &scanned_config.configuration;
......@@ -856,6 +968,9 @@ pub fn main(init: process.Init.Minimal) !void {
856968 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
857969 process.exit(1);
858970 }
971 if (protocol_server != null) {
972 fatal("(zig build system) TODO send error messages to client when build.zig compilation fails", .{});
973 }
859974 if (watch and can_fs_watch) {
860975 fatal("(zig build system) TODO set up fs watching even when build.zig compilation fails", .{});
861976 } else {
......@@ -2022,11 +2137,37 @@ pub fn stepByIndex(maker: *const Maker, i: Configuration.Step.Index) *Step {
20222137 return &maker.steps[@backingInt(i)];
20232138}
20242139
2025fn prepare(maker: *Maker, step_names: []const []const u8) !void {
2140fn resolveTopLevelSteps(maker: *Maker, step_names: []const []const u8) ![]const Configuration.Step.Index {
2141 const gpa = maker.gpa;
2142 const c = &maker.scanned_config.configuration;
2143
2144 if (step_names.len == 0) {
2145 return try gpa.dupe(Configuration.Step.Index, &.{c.default_step});
2146 }
2147
2148 var result: std.array_hash_map.Auto(Configuration.Step.Index, void) = .empty;
2149 defer result.deinit(gpa);
2150
2151 try result.ensureTotalCapacity(gpa, step_names.len);
2152
2153 for (0..step_names.len) |i| {
2154 const step_name = step_names[step_names.len - i - 1];
2155 const s = maker.scanned_config.top_level_steps.get(step_name) orelse {
2156 log.info("to list available steps: zig build -l", .{});
2157 fatal("no such step: {s}", .{step_name});
2158 };
2159 result.putAssumeCapacity(s, {});
2160 }
2161
2162 return try gpa.dupe(Configuration.Step.Index, result.keys());
2163}
2164
2165fn prepare(maker: *Maker, step_indices: []const Configuration.Step.Index) !void {
20262166 const gpa = maker.gpa;
20272167 const graph = maker.graph;
20282168 const arena = graph.arena;
20292169 const seed: u32 = graph.random_seed;
2170 const initial_steps = &maker.initial_steps;
20302171 const step_stack = &maker.step_stack;
20312172 const c = &maker.scanned_config.configuration;
20322173
......@@ -2035,18 +2176,15 @@ fn prepare(maker: *Maker, step_names: []const []const u8) !void {
20352176 step.* = .{ .extended = .init(step_index.ptr(c).flags(c).tag) };
20362177 }
20372178
2038 if (step_names.len == 0) {
2039 try step_stack.put(gpa, c.default_step, {});
2040 } else {
2041 try step_stack.ensureUnusedCapacity(gpa, step_names.len);
2042 for (0..step_names.len) |i| {
2043 const step_name = step_names[step_names.len - i - 1];
2044 const s = maker.scanned_config.top_level_steps.get(step_name) orelse {
2045 log.info("to list available steps: zig build -l", .{});
2046 fatal("no such step: {s}", .{step_name});
2047 };
2048 step_stack.putAssumeCapacity(s, {});
2049 }
2179 try initial_steps.ensureUnusedCapacity(gpa, step_indices.len);
2180 try step_stack.ensureUnusedCapacity(gpa, step_indices.len);
2181
2182 initial_steps.clearRetainingCapacity();
2183 step_stack.clearRetainingCapacity();
2184
2185 for (step_indices) |step| {
2186 initial_steps.putAssumeCapacity(step, {});
2187 step_stack.putAssumeCapacity(step, {});
20502188 }
20512189
20522190 const starting_steps = try arena.dupe(Configuration.Step.Index, step_stack.keys());
......@@ -2095,9 +2233,8 @@ fn prepare(maker: *Maker, step_names: []const []const u8) !void {
20952233 }
20962234}
20972235
2098fn makeStepNames(
2236fn makeSteps(
20992237 maker: *Maker,
2100 step_names: []const []const u8,
21012238 parent_progress_node: std.Progress.Node,
21022239 fuzz: ?Fuzz.Mode,
21032240) !void {
......@@ -2108,6 +2245,12 @@ fn makeStepNames(
21082245 const top_level_steps = &maker.scanned_config.top_level_steps;
21092246 const c = &maker.scanned_config.configuration;
21102247
2248 if (maker.web_server) |ws| ws.startBuild();
2249
2250 if (maker.protocol_server) |s| {
2251 try s.serveBodylessMessage(.bsp_build_started);
2252 }
2253
21112254 {
21122255 // Collect the initial set of tasks (those with no outstanding dependencies) into a buffer,
21132256 // then spawn them. The buffer is so that we don't race with `makeStep` and end up thinking
......@@ -2133,6 +2276,19 @@ fn makeStepNames(
21332276 try group.await(io);
21342277 }
21352278
2279 if (maker.web_server) |ws| {
2280 if (fuzz) |mode| if (mode != .forever) fatal(
2281 "error: limited fuzzing is not implemented yet for --webui",
2282 .{},
2283 );
2284
2285 ws.finishBuild(.{ .fuzz = fuzz != null });
2286 }
2287
2288 if (maker.protocol_server) |s| {
2289 try s.serveBodylessMessage(.bsp_build_completed);
2290 }
2291
21362292 assert(maker.memory_blocked_steps.items.len == 0);
21372293
21382294 var test_pass_count: usize = 0;
......@@ -2285,7 +2441,7 @@ fn makeStepNames(
22852441 defer step_stack_copy.deinit(gpa);
22862442
22872443 var print_node: PrintNode = .{ .parent = null };
2288 if (step_names.len == 0) {
2444 if (maker.initial_steps.count() == 0) {
22892445 print_node.last = true;
22902446 printTreeStep(maker, c.default_step, t, &print_node, &step_stack_copy) catch |err| switch (err) {
22912447 error.Canceled => |e| return e,
......@@ -2293,10 +2449,10 @@ fn makeStepNames(
22932449 };
22942450 } else {
22952451 const last_index = if (maker.summary == .all) top_level_steps.count() else blk: {
2296 var i: usize = step_names.len;
2452 var i: usize = maker.initial_steps.count();
22972453 while (i > 0) {
22982454 i -= 1;
2299 const step_index = top_level_steps.get(step_names[i]).?;
2455 const step_index = maker.initial_steps.keys()[i];
23002456 const step = maker.stepByIndex(step_index);
23012457 const found = switch (maker.summary) {
23022458 .all, .line, .none => unreachable,
......@@ -2307,8 +2463,7 @@ fn makeStepNames(
23072463 }
23082464 break :blk top_level_steps.count();
23092465 };
2310 for (step_names, 0..) |step_name, i| {
2311 const step_index = top_level_steps.get(step_name).?;
2466 for (maker.initial_steps.keys(), 0..) |step_index, i| {
23122467 print_node.last = i + 1 == last_index;
23132468 printTreeStep(maker, step_index, t, &print_node, &step_stack_copy) catch |err| switch (err) {
23142469 error.Canceled => |e| return e,
......@@ -2319,7 +2474,7 @@ fn makeStepNames(
23192474 w.writeByte('\n') catch {};
23202475 }
23212476
2322 if (maker.watch or maker.web_server != null) return;
2477 if (maker.watch or maker.web_server != null or maker.protocol_server != null) return;
23232478
23242479 const code: u8 = code: {
23252480 if (failure_count == 0) break :code 0; // success
......@@ -2394,6 +2549,15 @@ fn makeStep(
23942549 defer step_prog_node.end();
23952550
23962551 if (maker.web_server) |ws| ws.updateStepStatus(step_index, .wip);
2552 if (maker.protocol_server) |s| {
2553 maker.protocol_server_mutex.lockUncancelable(io);
2554 defer maker.protocol_server_mutex.unlock(io);
2555
2556 s.serveU32Message(
2557 .bsp_step_started,
2558 @backingInt(step_index),
2559 ) catch @panic("TODO propagate error when failing to send protocol message");
2560 }
23972561
23982562 const new_state: Step.State = for (deps) |dep_index| {
23992563 const dep_make_step = maker.stepByIndex(dep_index);
......@@ -2419,7 +2583,7 @@ fn makeStep(
24192583
24202584 @atomicStore(Step.State, &make_step.state, new_state, .monotonic);
24212585
2422 switch (new_state) {
2586 const success = switch (new_state) {
24232587 .precheck_unstarted => unreachable,
24242588 .precheck_started => unreachable,
24252589 .precheck_done => unreachable,
......@@ -2427,17 +2591,37 @@ fn makeStep(
24272591 .failure,
24282592 .dependency_failure,
24292593 .skipped_oom,
2430 => {
2431 if (maker.web_server) |ws| ws.updateStepStatus(step_index, .failure);
2432 std.Progress.setStatus(.failure_working);
2433 },
2594 => false,
24342595
24352596 .success,
24362597 .skipped,
2437 => {
2438 if (maker.web_server) |ws| ws.updateStepStatus(step_index, .success);
2439 },
2598 => true,
2599 };
2600
2601 if (maker.web_server) |ws| {
2602 ws.updateStepStatus(step_index, if (success) .success else .failure);
24402603 }
2604 if (maker.protocol_server != null) {
2605 maker.protocol_server_mutex.lockUncancelable(io);
2606 defer maker.protocol_server_mutex.unlock(io);
2607
2608 const status: Server.Message.BuildStepCompleted.Status = switch (new_state) {
2609 .precheck_unstarted => unreachable,
2610 .precheck_started => unreachable,
2611 .precheck_done => unreachable,
2612 .success => .success,
2613 .failure, .dependency_failure => .failure,
2614 .skipped => .skipped,
2615 .skipped_oom => .skipped_oom,
2616 };
2617 serveBuildStepCompleted(
2618 maker,
2619 step_index,
2620 status,
2621 ) catch |err| std.debug.panic("TODO propagate error when failing to send protocol message: {t}", .{err});
2622 }
2623
2624 if (!success) std.Progress.setStatus(.failure_working);
24412625 }
24422626
24432627 // No matter the result, we want to display error/warning messages.
......@@ -2992,6 +3176,50 @@ fn cleanTmpFiles(maker: *Maker, steps: []const Configuration.Step.Index) void {
29923176 }
29933177}
29943178
3179fn serveBSPHandshake(s: *const std.zig.Server) !void {
3180 const handshake_header: Server.Message.Handshake = .{
3181 .version = Server.build_system_version,
3182 .flags = .{
3183 .file_system_watch_supported = Watch.have_impl,
3184 },
3185 };
3186 try s.serveMessageHeader(.{
3187 .tag = .bsp_handshake,
3188 .bytes_len = @sizeOf(Server.Message.Handshake),
3189 });
3190 try s.out.writeStruct(handshake_header, .little);
3191 try s.out.flush();
3192}
3193
3194fn serveBuildStepCompleted(
3195 maker: *Maker,
3196 step_index: Configuration.Step.Index,
3197 status: Server.Message.BuildStepCompleted.Status,
3198) !void {
3199 const s: *Server = maker.protocol_server.?;
3200 const step = maker.stepByIndex(step_index);
3201 const error_bundle = step.result_error_bundle;
3202
3203 const body: Server.Message.BuildStepCompleted = .{
3204 .step_index = step_index,
3205 .status = status,
3206 .error_bundle = .{
3207 .extra_len = @intCast(error_bundle.extra.len),
3208 .string_bytes_len = @intCast(error_bundle.string_bytes.len),
3209 },
3210 };
3211 const eb_bytes_len = @sizeOf(u32) * error_bundle.extra.len + error_bundle.string_bytes.len;
3212 const bytes_len = @sizeOf(Server.Message.BuildStepCompleted) + eb_bytes_len;
3213 try s.serveMessageHeader(.{
3214 .tag = .bsp_step_completed,
3215 .bytes_len = @intCast(bytes_len),
3216 });
3217 try s.out.writeStruct(body, .little);
3218 try s.out.writeSliceEndian(u32, error_bundle.extra, .little);
3219 try s.out.writeAll(error_bundle.string_bytes);
3220 try s.out.flush();
3221}
3222
29953223fn initStdoutWriter(io: Io) *Writer {
29963224 stdout_writer_allocation = Io.File.stdout().writerStreaming(io, &stdio_buffer_allocation);
29973225 return &stdout_writer_allocation.interface;
lib/compiler/Maker/Step.zig+10-8
......@@ -561,24 +561,26 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi
561561 var result: ?Path = null;
562562 var eos_err: error{EndOfStream}!void = {};
563563
564 const stdout = zp.multi_reader.fileReader(0);
564 var client: std.zig.Client = .{
565 .in = zp.multi_reader.reader(0),
566 .out = undefined,
567 };
565568
566569 while (true) {
567 const Header = std.zig.Server.Message.Header;
568 const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) {
569 error.EndOfStream => break,
570 error.ReadFailed => return stdout.err.?,
571 };
572 const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) {
570 const header = client.receiveMessageWithMultiReader(&zp.multi_reader, .none) catch |err| switch (err) {
571 error.Timeout => unreachable,
573572 error.EndOfStream => |e| {
573 if (client.in.bufferedLen() == 0) break;
574574 // Better to report the crash with stderr below, but we set
575575 // this in case the child exits successfully while violating
576576 // this protocol.
577577 eos_err = e;
578578 break;
579579 },
580 error.ReadFailed => return stdout.err.?,
580 else => |e| return e,
581581 };
582 const body = client.in.take(header.bytes_len) catch unreachable;
583
582584 switch (header.tag) {
583585 .zig_version => {
584586 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
lib/compiler/Maker/Step/Run.zig+51-125
......@@ -384,13 +384,23 @@ fn waitZigTest(
384384 var sub_prog_node: ?std.Progress.Node = null;
385385 defer if (sub_prog_node) |n| n.end();
386386
387 const stdout = multi_reader.reader(0);
388 const stderr = multi_reader.reader(1);
389
390 var stdin_writer = child.stdin.?.writerStreaming(io, &.{});
391
392 var client: std.zig.Client = .{
393 .in = stdout,
394 .out = &stdin_writer.interface,
395 };
396
387397 if (opt_metadata.*) |*md| {
388398 // Previous unit test process died or was killed; we're continuing where it left off
389 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
399 requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
390400 } else {
391401 // Running unit tests normally
392402 run.fuzz_tests.clearRetainingCapacity();
393 sendMessage(io, child.stdin.?, .query_test_metadata) catch |err| return .{ .write_failed = err };
403 client.serveBodylessMessage(.query_test_metadata) catch |err| return .{ .write_failed = err };
394404 }
395405
396406 var active_test_index: ?u32 = null;
......@@ -410,10 +420,6 @@ fn waitZigTest(
410420 .raw = .fromNanoseconds(ns),
411421 } else null;
412422
413 const stdout = multi_reader.reader(0);
414 const stderr = multi_reader.reader(1);
415 const Header = std.zig.Server.Message.Header;
416
417423 while (true) {
418424 const timeout: Io.Timeout = t: {
419425 const opt_duration = if (active_test_index == null) response_timeout else test_timeout;
......@@ -421,46 +427,20 @@ fn waitZigTest(
421427 break :t .{ .deadline = last_update.addDuration(duration) };
422428 };
423429
424 // This block is exited when `stdout` contains enough bytes for a `Header`.
425 header_ready: {
426 if (stdout.buffered().len >= @sizeOf(Header)) {
427 // We already have one, no need to poll!
428 break :header_ready;
429 }
430
431 multi_reader.fill(64, timeout) catch |err| switch (err) {
432 error.Timeout => return .{ .timeout = .{
433 .active_test_index = active_test_index,
434 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
435 } },
436 error.EndOfStream => return .{ .no_poll = .{
437 .active_test_index = active_test_index,
438 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
439 } },
440 else => |e| return e,
441 };
442
443 continue;
444 }
445 // There is definitely a header available now -- read it.
446 const header = stdout.takeStruct(Header, .little) catch unreachable;
447
448 while (stdout.buffered().len < header.bytes_len) {
449 multi_reader.fill(64, timeout) catch |err| switch (err) {
450 error.Timeout => return .{ .timeout = .{
451 .active_test_index = active_test_index,
452 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
453 } },
454 error.EndOfStream => return .{ .no_poll = .{
455 .active_test_index = active_test_index,
456 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
457 } },
458 else => |e| return e,
459 };
460 }
461
462 const body = stdout.take(header.bytes_len) catch unreachable;
430 const header = client.receiveMessageWithMultiReader(multi_reader, timeout) catch |err| switch (err) {
431 error.Timeout => return .{ .timeout = .{
432 .active_test_index = active_test_index,
433 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
434 } },
435 error.EndOfStream => return .{ .no_poll = .{
436 .active_test_index = active_test_index,
437 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
438 } },
439 else => |e| return e,
440 };
441 const body = client.in.take(header.bytes_len) catch unreachable;
463442 var body_r: std.Io.Reader = .fixed(body);
443
464444 switch (header.tag) {
465445 .zig_version => {
466446 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail(
......@@ -500,7 +480,7 @@ fn waitZigTest(
500480 active_test_index = null;
501481 last_update = .now(io, .awake);
502482
503 requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err };
483 requestNextTest(&client, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err };
504484 },
505485 .test_started => {
506486 active_test_index = opt_metadata.*.?.next_index - 1;
......@@ -551,7 +531,7 @@ fn waitZigTest(
551531 md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds);
552532 last_update = now;
553533
554 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
534 requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
555535 },
556536 else => {}, // ignore other messages
557537 }
......@@ -697,17 +677,18 @@ const FuzzTestRunner = struct {
697677
698678 for (0.., f.instances) |id, *instance| {
699679 const id32: u32 = @intCast(id);
680 var writer = instance.child.stdin.?.writerStreaming(io, &.{});
681 const client: std.zig.Client = .{
682 .in = undefined,
683 .out = &writer.interface,
684 };
700685 (switch (f.ctx.fuzz.mode) {
701 .forever => sendRunFuzzTestMessage(
702 io,
703 instance.child.stdin.?,
686 .forever => client.serveRunFuzzTestMessage(
704687 run.fuzz_tests.items,
705688 .forever,
706689 id32,
707690 ),
708 .limit => |limit| sendRunFuzzTestMessage(
709 io,
710 instance.child.stdin.?,
691 .limit => |limit| client.serveRunFuzzTestMessage(
711692 run.fuzz_tests.items,
712693 .iterations,
713694 limit.amount,
......@@ -1315,7 +1296,7 @@ pub const CachedTestMetadata = struct {
13151296 }
13161297};
13171298
1318fn requestNextTest(io: Io, in: Io.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void {
1299fn requestNextTest(client: *std.zig.Client, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void {
13191300 while (metadata.next_index < metadata.names.len) {
13201301 const i = metadata.next_index;
13211302 metadata.next_index += 1;
......@@ -1326,76 +1307,11 @@ fn requestNextTest(io: Io, in: Io.File, metadata: *TestMetadata, sub_prog_node:
13261307 if (sub_prog_node.*) |n| n.end();
13271308 sub_prog_node.* = metadata.prog_node.start(name, 0);
13281309
1329 try sendRunTestMessage(io, in, .run_test, i);
1310 try client.serveRunTest(i);
13301311 return;
13311312 } else {
13321313 metadata.next_index = std.math.maxInt(u32); // indicate that all tests are done
1333 try sendMessage(io, in, .exit);
1334 }
1335}
1336
1337fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
1338 const header: std.zig.Client.Message.Header = .{
1339 .tag = tag,
1340 .bytes_len = 0,
1341 };
1342 var w = file.writerStreaming(io, &.{});
1343 w.interface.writeStruct(header, .little) catch |err| switch (err) {
1344 error.WriteFailed => return w.err.?,
1345 };
1346}
1347
1348fn sendRunTestMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag, index: u32) !void {
1349 const header: std.zig.Client.Message.Header = .{
1350 .tag = tag,
1351 .bytes_len = 4,
1352 };
1353 var w = file.writerStreaming(io, &.{});
1354 w.interface.writeStruct(header, .little) catch |err| switch (err) {
1355 error.WriteFailed => return w.err.?,
1356 };
1357 w.interface.writeInt(u32, index, .little) catch |err| switch (err) {
1358 error.WriteFailed => return w.err.?,
1359 };
1360}
1361
1362fn sendRunFuzzTestMessage(
1363 io: Io,
1364 file: Io.File,
1365 test_names: []const []const u8,
1366 kind: std.Build.abi.fuzz.LimitKind,
1367 amount_or_instance: u64,
1368) !void {
1369 const header: std.zig.Client.Message.Header = .{
1370 .tag = .start_fuzzing,
1371 .bytes_len = 1 + 8 + 4 + count: {
1372 var c: u32 = @intCast(test_names.len * 4);
1373 for (test_names) |name| {
1374 c += @intCast(name.len);
1375 }
1376 break :count c;
1377 },
1378 };
1379 var w = file.writerStreaming(io, &.{});
1380 w.interface.writeStruct(header, .little) catch |err| switch (err) {
1381 error.WriteFailed => return w.err.?,
1382 };
1383 w.interface.writeByte(@backingInt(kind)) catch |err| switch (err) {
1384 error.WriteFailed => return w.err.?,
1385 };
1386 w.interface.writeInt(u64, amount_or_instance, .little) catch |err| switch (err) {
1387 error.WriteFailed => return w.err.?,
1388 };
1389 w.interface.writeInt(u32, @intCast(test_names.len), .little) catch |err| switch (err) {
1390 error.WriteFailed => return w.err.?,
1391 };
1392 for (test_names) |test_name| {
1393 w.interface.writeInt(u32, @intCast(test_name.len), .little) catch |err| switch (err) {
1394 error.WriteFailed => return w.err.?,
1395 };
1396 w.interface.writeAll(test_name) catch |err| switch (err) {
1397 error.WriteFailed => return w.err.?,
1398 };
1314 try client.serveBodylessMessage(.exit);
13991315 }
14001316}
14011317
......@@ -2285,25 +2201,35 @@ fn spawnChildAndCollect(
22852201 assert(conf_run.flags.stdio != .inherit);
22862202 break :s .pipe;
22872203 } else switch (conf_run.flags.stdio) {
2288 .infer_from_args => if (has_side_effects) .inherit else .ignore,
2204 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore,
22892205 .inherit => .inherit,
22902206 .check => .ignore,
22912207 .zig_test => .pipe,
22922208 },
22932209 .stdout = if (conf_run.captured_stdout.value != null) .pipe else switch (conf_run.flags.stdio) {
2294 .infer_from_args => if (has_side_effects) .inherit else .ignore,
2210 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore,
22952211 .inherit => .inherit,
22962212 .check => if (checksContainStdout(&conf_run)) .pipe else .ignore,
22972213 .zig_test => .pipe,
22982214 },
22992215 .stderr = if (conf_run.captured_stderr.value != null) .pipe else switch (conf_run.flags.stdio) {
2300 .infer_from_args => if (has_side_effects) .inherit else .pipe,
2301 .inherit => .inherit,
2216 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .pipe,
2217 .inherit => if (maker.protocol_server == null) .inherit else .pipe,
23022218 .check => .pipe,
23032219 .zig_test => .pipe,
23042220 },
23052221 };
23062222
2223 if (maker.protocol_server != null) {
2224 if (spawn_options.stdin == .inherit) {
2225 return step.fail(maker, "Cannot inherit stdin when running through over the build system protocol", .{});
2226 }
2227 if (spawn_options.stdout == .inherit) {
2228 return step.fail(maker, "Cannot inherit stdout when running through over the build system protocol", .{});
2229 }
2230 assert(spawn_options.stderr != .inherit);
2231 }
2232
23072233 if (conf_run.flags.stdio == .zig_test) {
23082234 try setColorEnvironmentVariables(&conf_run, environ_map, graph.stderr_mode.?);
23092235 const started: Io.Clock.Timestamp = .now(io, .awake);
lib/compiler/objcopy.zig+3-3
......@@ -214,11 +214,11 @@ fn cmdObjCopy(arena: Allocator, io: Io, args: []const []const u8) !void {
214214 if (listen) {
215215 var stdin_reader = Io.File.stdin().reader(io, &stdin_buffer);
216216 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
217 var server = try Server.init(.{
217 var server: Server = .{
218218 .in = &stdin_reader.interface,
219219 .out = &stdout_writer.interface,
220 .zig_version = builtin.zig_version_string,
221 });
220 };
221 try server.serveStringMessage(.zig_version, builtin.zig_version_string);
222222
223223 var seen_update = false;
224224 while (true) {
lib/compiler/std-docs.zig+21-22
......@@ -346,29 +346,39 @@ fn buildWasmBinary(
346346 multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? });
347347 defer multi_reader.deinit();
348348
349 try sendMessage(io, child.stdin.?, .update);
350 try sendMessage(io, child.stdin.?, .exit);
349 const stdout = multi_reader.reader(0);
350
351 var stdin_buffer: [256]u8 = undefined;
352 var stdin_writer = child.stdin.?.writerStreaming(io, &stdin_buffer);
353
354 var client: std.zig.Client = .{
355 .in = stdout,
356 .out = &stdin_writer.interface,
357 };
358
359 try client.serveMessageHeader(.{ .tag = .update, .bytes_len = 0 });
360 try client.serveMessageHeader(.{ .tag = .exit, .bytes_len = 0 });
361 try client.out.flush();
351362
352363 var result: ?Cache.Path = null;
353364 var result_error_bundle = std.zig.ErrorBundle.empty;
354365
355 const stdout = multi_reader.fileReader(0);
356 const MessageHeader = std.zig.Server.Message.Header;
357
358366 var eos_err: error{EndOfStream}!void = {};
359367
360368 while (true) {
361 const header = stdout.interface.takeStruct(MessageHeader, .little) catch |err| switch (err) {
362 error.EndOfStream => break,
363 error.ReadFailed => return stdout.err.?,
364 };
365 const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) {
369 const header = client.receiveMessageWithMultiReader(&multi_reader, .none) catch |err| switch (err) {
370 error.Timeout => unreachable,
366371 error.EndOfStream => |e| {
372 if (client.in.bufferedLen() == 0) break;
373 // Better to report the crash with stderr below, but we set
374 // this in case the child exits successfully while violating
375 // this protocol.
367376 eos_err = e;
368377 break;
369378 },
370 error.ReadFailed => return stdout.err.?,
379 else => |e| return e,
371380 };
381 const body = client.in.take(header.bytes_len) catch unreachable;
372382
373383 switch (header.tag) {
374384 .zig_version => {
......@@ -435,17 +445,6 @@ fn buildWasmBinary(
435445 };
436446}
437447
438fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
439 const header: std.zig.Client.Message.Header = .{
440 .tag = tag,
441 .bytes_len = 0,
442 };
443 var w = file.writer(io, &.{});
444 w.interface.writeStruct(header, .little) catch |err| switch (err) {
445 error.WriteFailed => return w.err.?,
446 };
447}
448
449448fn openBrowserTab(io: Io, url: []const u8) !void {
450449 // Until https://github.com/ziglang/zig/issues/19205 is implemented, we
451450 // spawn and then leak a concurrent task for this child process.
lib/compiler/test_runner.zig+3-3
......@@ -78,11 +78,11 @@ fn mainServer(init: std.process.Init.Minimal) !void {
7878 @disableInstrumentation();
7979 stdin_reader = .initStreaming(.stdin(), runner_threaded_io, &stdin_buffer);
8080 stdout_writer = .initStreaming(.stdout(), runner_threaded_io, &stdout_buffer);
81 var server = try std.zig.Server.init(.{
81 var server: std.zig.Server = .{
8282 .in = &stdin_reader.interface,
8383 .out = &stdout_writer.interface,
84 .zig_version = builtin.zig_version_string,
85 });
84 };
85 try server.serveStringMessage(.zig_version, builtin.zig_version_string);
8686
8787 while (true) {
8888 const hdr = try server.receiveMessage();
lib/std/Io/Reader.zig+3-5
......@@ -718,7 +718,7 @@ pub inline fn readSliceEndian(
718718 endian: std.builtin.Endian,
719719) Error!void {
720720 try readSliceAll(r, @ptrCast(buffer));
721 if (native_endian != endian) for (buffer) |*elem| std.mem.byteSwapAllFields(Elem, elem);
721 if (native_endian != endian) std.mem.byteSwapAllElements(Elem, buffer);
722722}
723723
724724pub const ReadAllocError = Error || Allocator.Error;
......@@ -734,8 +734,7 @@ pub inline fn readSliceEndianAlloc(
734734) ReadAllocError![]Elem {
735735 const dest = try allocator.alloc(Elem, len);
736736 errdefer allocator.free(dest);
737 try readSliceAll(r, @ptrCast(dest));
738 if (native_endian != endian) for (dest) |*elem| std.mem.byteSwapAllFields(Elem, elem);
737 try r.readSliceEndian(Elem, dest, endian);
739738 return dest;
740739}
741740
......@@ -1227,8 +1226,7 @@ pub inline fn takeStruct(r: *Reader, comptime T: type, endian: std.builtin.Endia
12271226 .auto => @compileError("ill-defined memory layout"),
12281227 .@"extern" => {
12291228 var res: T = undefined;
1230 try r.readSliceAll(std.mem.asBytes(&res));
1231 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1229 try r.readSliceEndian(T, (&res)[0..1], endian);
12321230 return res;
12331231 },
12341232 .@"packed" => {
lib/std/mem.zig+66-43
......@@ -2215,33 +2215,54 @@ test writeVarPackedInt {
22152215 try testing.expectEqual(T{ .a = 1, .b = value, .c = 4 }, st);
22162216}
22172217
2218/// Swap the byte order of all the members of the fields of a struct
2219/// (Changing their endianness)
2220pub fn byteSwapAllFields(comptime S: type, ptr: *S) void {
2221 byteSwapAllFieldsAligned(S, .of(S), ptr);
2218/// Deprecated: use `byteSwap` instead.
2219pub const byteSwapAllFields = byteSwap;
2220
2221/// Deprecated: use `byteSwapAligned` instead.
2222pub const byteSwapAllFieldsAligned = byteSwapAligned;
2223
2224/// Reverses the byte order.
2225/// Handles structs, unions, arrays, enums, floats, and integers recursively.
2226/// The order of extern struct fields and array elements remains unchanged and
2227/// will be byte swapped recursively.
2228/// Useful for converting between little-endian and big-endian representations.
2229pub fn byteSwap(comptime S: type, ptr: *S) void {
2230 byteSwapAligned(S, .of(S), ptr);
22222231}
22232232
2224/// Swap the byte order of all the members of the fields of a struct
2225/// (Changing their endianness)
2226pub fn byteSwapAllFieldsAligned(comptime S: type, comptime a: Alignment, ptr: *align(a.toByteUnits()) S) void {
2233/// Reverses the byte order.
2234/// Handles structs, unions, arrays, enums, floats, and integers recursively.
2235/// The order of extern struct fields and array elements remains unchanged and
2236/// will be byte swapped recursively.
2237/// Useful for converting between little-endian and big-endian representations.
2238pub fn byteSwapAligned(
2239 comptime S: type,
2240 comptime a: Alignment,
2241 ptr: *align(a.toByteUnits()) S,
2242) void {
22272243 switch (@typeInfo(S)) {
22282244 .@"struct" => |@"struct"| {
22292245 if (@"struct".backing_integer) |Int| {
22302246 ptr.* = @bitCast(@byteSwap(@as(Int, @bitCast(ptr.*))));
2231 } else inline for (@"struct".field_types, @"struct".field_names, @"struct".field_attrs) |f_type, f_name, f_attr| {
2232 switch (@typeInfo(f_type)) {
2233 .@"struct" => byteSwapAllFieldsAligned(f_type, .fromByteUnits(f_attr.@"align" orelse @alignOf(f_type)), &@field(ptr, f_name)),
2234 .@"union", .array => byteSwapAllFieldsAligned(f_type, .fromByteUnits(f_attr.@"align" orelse @alignOf(f_type)), &@field(ptr, f_name)),
2235 .@"enum" => {
2236 @field(ptr, f_name) = @fromBackingInt(@intCast(@byteSwap(@backingInt(@field(ptr, f_name)))));
2237 },
2238 .bool => {},
2239 .float => |float| {
2240 @field(ptr, f_name) = @bitCast(@byteSwap(@as(@Int(.unsigned, float.bits), @bitCast(@field(ptr, f_name)))));
2241 },
2242 else => {
2243 @field(ptr, f_name) = @byteSwap(@field(ptr, f_name));
2244 },
2247 } else {
2248 if (@"struct".layout != .@"extern") {
2249 @compileError("byteSwapAligned expects a packed or extern struct");
2250 }
2251 inline for (@"struct".field_types, @"struct".field_names, @"struct".field_attrs) |f_type, f_name, f_attr| {
2252 switch (@typeInfo(f_type)) {
2253 .@"struct" => byteSwapAligned(f_type, .fromByteUnits(f_attr.@"align" orelse @alignOf(f_type)), &@field(ptr, f_name)),
2254 .@"union", .array => byteSwapAligned(f_type, .fromByteUnits(f_attr.@"align" orelse @alignOf(f_type)), &@field(ptr, f_name)),
2255 .@"enum" => {
2256 @field(ptr, f_name) = @fromBackingInt(@byteSwap(@backingInt(@field(ptr, f_name))));
2257 },
2258 .bool => {},
2259 .float => |float| {
2260 @field(ptr, f_name) = @bitCast(@byteSwap(@as(@Int(.unsigned, float.bits), @bitCast(@field(ptr, f_name)))));
2261 },
2262 else => {
2263 @field(ptr, f_name) = @byteSwap(@field(ptr, f_name));
2264 },
2265 }
22452266 }
22462267 }
22472268 },
......@@ -2249,7 +2270,7 @@ pub fn byteSwapAllFieldsAligned(comptime S: type, comptime a: Alignment, ptr: *a
22492270 ptr.* = @bitCast(@byteSwap(@as(Int, @bitCast(ptr.*))));
22502271 } else {
22512272 if (@"union".layout != .@"extern") {
2252 @compileError("byteSwapAllFields expects a packed or extern union");
2273 @compileError("byteSwapAligned expects a packed or extern union");
22532274 }
22542275
22552276 const first_size = @bitSizeOf(@"union".field_types[0]);
......@@ -2266,13 +2287,21 @@ pub fn byteSwapAllFieldsAligned(comptime S: type, comptime a: Alignment, ptr: *a
22662287 .array => |array| {
22672288 byteSwapAllElements(array.child, ptr);
22682289 },
2290 .@"enum" => {
2291 ptr.* = @fromBackingInt(@byteSwap(@backingInt(ptr.*)));
2292 },
2293 .bool => {},
2294 .float => |float| {
2295 const int_repr: @Int(.unsigned, float.bits) = @bitCast(ptr.*);
2296 ptr.* = @bitCast(@byteSwap(int_repr));
2297 },
22692298 else => {
22702299 ptr.* = @byteSwap(ptr.*);
22712300 },
22722301 }
22732302}
22742303
2275test byteSwapAllFields {
2304test byteSwap {
22762305 const T = extern struct {
22772306 f0: u8,
22782307 f1: u16,
......@@ -2304,6 +2333,9 @@ test byteSwapAllFields {
23042333 } align(4),
23052334 f2: u32,
23062335 };
2336 const E = enum(u32) {
2337 _,
2338 };
23072339 var s = T{
23082340 .f0 = 0x12,
23092341 .f1 = 0x1234,
......@@ -2327,10 +2359,14 @@ test byteSwapAllFields {
23272359 .f1 = .{ .f0 = 0x123456789ABCDEF0 },
23282360 .f2 = 0x87654321,
23292361 };
2330 byteSwapAllFields(T, &s);
2331 byteSwapAllFields(K, &k);
2332 byteSwapAllFields(P, &p);
2333 byteSwapAllFields(A, &a);
2362 var e: E = @fromBackingInt(0x12345678);
2363 var f: f32 = @bitCast(@as(u32, 0x4640e400));
2364 byteSwap(T, &s);
2365 byteSwap(K, &k);
2366 byteSwap(P, &p);
2367 byteSwap(A, &a);
2368 byteSwap(E, &e);
2369 byteSwap(f32, &f);
23342370 try std.testing.expectEqual(T{
23352371 .f0 = 0x12,
23362372 .f1 = 0x3412,
......@@ -2354,28 +2390,15 @@ test byteSwapAllFields {
23542390 .f1 = .{ .f0 = 0xF0DEBC9A78563412 },
23552391 .f2 = 0x21436587,
23562392 }, a);
2393 try std.testing.expectEqual(@as(E, @fromBackingInt(0x78563412)), e);
2394 try std.testing.expectEqual(@as(f32, @bitCast(@as(u32, 0x00e44046))), f);
23572395}
23582396
23592397/// Reverses the byte order of all elements in a slice.
23602398/// Handles structs, unions, arrays, enums, floats, and integers recursively.
23612399/// Useful for converting between little-endian and big-endian representations.
23622400pub fn byteSwapAllElements(comptime Elem: type, slice: []Elem) void {
2363 for (slice) |*elem| {
2364 switch (@typeInfo(@TypeOf(elem.*))) {
2365 .@"struct", .@"union", .array => byteSwapAllFields(@TypeOf(elem.*), elem),
2366 .@"enum" => {
2367 elem.* = @fromBackingInt(@intCast(@byteSwap(@backingInt(elem.*))));
2368 },
2369 .bool => {},
2370 .float => |float| {
2371 const int_repr: @Int(.unsigned, float.bits) = @bitCast(elem.*);
2372 elem.* = @bitCast(@byteSwap(int_repr));
2373 },
2374 else => {
2375 elem.* = @byteSwap(elem.*);
2376 },
2377 }
2378 }
2401 for (slice) |*elem| byteSwap(Elem, elem);
23792402}
23802403
23812404/// Returns an iterator that iterates over the slices of `buffer` that are not
lib/std/zig.zig+46-58
......@@ -1658,31 +1658,32 @@ pub fn buildExeSubprocess(
16581658 };
16591659 defer child.kill(io);
16601660
1661 var stderr_task = io.concurrent(readStreamAlloc, .{ gpa, io, child.stderr.?, .unlimited }) catch
1662 @panic("TODO use multireader instead");
1663 defer if (stderr_task.cancel(io)) |slice| gpa.free(slice) else |_| {};
1664
1665 var stdout_buffer: [512]u8 = undefined;
1666 var stdout_reader: Io.File.Reader = .initStreaming(child.stdout.?, io, &stdout_buffer);
1667 const stdout = &stdout_reader.interface;
1668
1669 {
1670 var w = child.stdin.?.writer(io, &.{});
1671 w.interface.writeStruct(Client.Message.Header{ .tag = .update, .bytes_len = 0 }, .little) catch |err| switch (err) {
1672 error.WriteFailed => {
1673 log.err("{t} writing to command: {f}", .{ w.err.?, cmd });
1674 return error.AlreadyReported;
1675 },
1676 };
1677 w.interface.writeStruct(Client.Message.Header{ .tag = .exit, .bytes_len = 0 }, .little) catch |err| switch (err) {
1678 error.WriteFailed => {
1679 log.err("{t} writing to command: {f}", .{ w.err.?, cmd });
1680 return error.AlreadyReported;
1681 },
1682 };
1683 }
1661 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
1662 var multi_reader: Io.File.MultiReader = undefined;
1663 multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? });
1664 defer multi_reader.deinit();
1665 const stdout = multi_reader.reader(0);
1666 const stderr = multi_reader.reader(1);
1667
1668 var stdin_buffer: [8]u8 = undefined;
1669 var stdin_writer = child.stdin.?.writerStreaming(io, &stdin_buffer);
1670
1671 var client: Client = .{
1672 .in = stdout,
1673 .out = &stdin_writer.interface,
1674 };
16841675
1685 const Header = Server.Message.Header;
1676 (blk: {
1677 client.serveMessageHeader(.{ .tag = .update, .bytes_len = 0 }) catch |err| break :blk err;
1678 client.serveMessageHeader(.{ .tag = .exit, .bytes_len = 0 }) catch |err| break :blk err;
1679 client.out.flush() catch |err| break :blk err;
1680 }) catch |err| switch (err) {
1681 error.WriteFailed => {
1682 if (stdin_writer.err.? == error.Canceled) return error.Canceled;
1683 log.err("{t} writing to command: {f}", .{ stdin_writer.err.?, cmd });
1684 return error.AlreadyReported;
1685 },
1686 };
16861687
16871688 var result: ?Cache.Path = null;
16881689 defer if (result) |r| gpa.free(r.sub_path);
......@@ -1690,33 +1691,29 @@ pub fn buildExeSubprocess(
16901691 var result_error_bundle: ErrorBundle = .empty;
16911692 defer result_error_bundle.deinit(gpa);
16921693
1693 var body_buffer: std.ArrayList(u8) = .empty;
1694 defer body_buffer.deinit(gpa);
1695
16961694 var received_fs_inputs = false;
16971695 var cache_hit = false;
16981696
1697 var eos_err: error{EndOfStream}!void = {};
1698
16991699 while (true) {
1700 const header = stdout.takeStruct(Header, .little) catch |err| switch (err) {
1701 error.ReadFailed => {
1702 log.err("{t} reading from command: {f}", .{ stdout_reader.err.?, cmd });
1703 return error.AlreadyReported;
1704 },
1705 error.EndOfStream => break,
1706 };
1707 body_buffer.clearRetainingCapacity();
1708 stdout.appendExact(gpa, &body_buffer, header.bytes_len) catch |err| switch (err) {
1709 error.ReadFailed => {
1710 log.err("{t} reading from command: {f}", .{ stdout_reader.err.?, cmd });
1711 return error.AlreadyReported;
1700 const header = client.receiveMessageWithMultiReader(&multi_reader, .none) catch |err| switch (err) {
1701 error.Timeout => unreachable,
1702 error.EndOfStream => |e| {
1703 if (client.in.bufferedLen() == 0) break;
1704 // Better to report the crash with stderr below, but we set
1705 // this in case the child exits successfully while violating
1706 // this protocol.
1707 eos_err = e;
1708 break;
17121709 },
1713 error.OutOfMemory => |e| return e,
1714 error.EndOfStream => {
1715 log.err("unexpected end of stream from command: {f}", .{cmd});
1710 error.Canceled, error.OutOfMemory => |e| return e,
1711 else => |e| {
1712 log.err("{t} reading from command: {f}", .{ e, cmd });
17161713 return error.AlreadyReported;
17171714 },
17181715 };
1719 const body = body_buffer.items;
1716 const body = stdout.take(header.bytes_len) catch unreachable;
17201717
17211718 switch (header.tag) {
17221719 .zig_version => {
......@@ -1767,16 +1764,15 @@ pub fn buildExeSubprocess(
17671764 }
17681765 }
17691766
1770 const stderr_contents = stderr_task.await(io) catch |err| switch (err) {
1771 error.Canceled, error.OutOfMemory => |e| return e,
1772 else => |e| c: {
1773 log.warn("{t} reading stderr from command: {f}", .{ e, cmd });
1774 break :c "";
1775 },
1776 };
1767 const stderr_contents = stderr.buffered();
17771768 if (stderr_contents.len > 0)
17781769 log.warn("unexpected stderr from {s} command:\n{s}", .{ options.argv[0], stderr_contents });
17791770
1771 eos_err catch {
1772 log.err("unexpected end of stream from command: {f}", .{cmd});
1773 return error.AlreadyReported;
1774 };
1775
17801776 // Send EOF to stdin.
17811777 child.stdin.?.close(io);
17821778 child.stdin = null;
......@@ -1834,14 +1830,6 @@ pub fn buildExeSubprocess(
18341830 };
18351831}
18361832
1837fn readStreamAlloc(gpa: Allocator, io: Io, file: Io.File, limit: Io.Limit) ![]u8 {
1838 var file_reader: Io.File.Reader = .initStreaming(file, io, &.{});
1839 return file_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) {
1840 error.ReadFailed => return file_reader.err.?,
1841 else => |e| return e,
1842 };
1843}
1844
18451833test {
18461834 _ = Ast;
18471835 _ = AstRlAnnotate;
lib/std/zig/Client.zig+126-2
......@@ -1,3 +1,18 @@
1const Client = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const Allocator = std.mem.Allocator;
6const assert = std.debug.assert;
7const Configuration = std.Build.Configuration;
8const OutMessage = std.zig.Client.Message;
9const InMessage = std.zig.Server.Message;
10const Reader = Io.Reader;
11const Writer = Io.Writer;
12
13in: *Reader,
14out: *Writer,
15
116pub const Message = struct {
217 pub const Header = extern struct {
318 tag: Tag,
......@@ -46,11 +61,120 @@ pub const Message = struct {
4661 /// The message body has the same format as in Server.
4762 new_fuzz_input,
4863
64 /// Asks the server to run a list of steps.
65 /// Body is a `BuildSteps`.
66 /// This message only applies to the build system protocol.
67 bsp_build_steps = 0x80000000,
68
4969 _,
5070 };
5171
72 /// Trailing:
73 /// * step_indices: [step_count]std.Build.Configuration.Step.Index,
74 pub const BuildSteps = extern struct {
75 step_count: u32,
76 flags: Flags,
77
78 pub const Flags = packed struct(u32) {
79 /// Can only be enabled when the server declared support for file
80 /// watching.
81 watch: bool,
82 reserved: u31 = 0,
83 };
84 };
85
5286 comptime {
53 const std = @import("std");
54 std.debug.assert(@sizeOf(std.Build.abi.fuzz.LimitKind) == 1);
87 assert(@sizeOf(std.Build.abi.fuzz.LimitKind) == 1);
5588 }
5689};
90
91pub fn receiveMessage(c: *const Client) Reader.Error!InMessage.Header {
92 return c.in.takeStruct(InMessage.Header, .little);
93}
94
95/// Assumes that `c.in` is a reader in `multi_reader`.
96/// Guarantees that the response body will be buffered in `c.in` on success.
97pub fn receiveMessageWithMultiReader(
98 c: *Client,
99 multi_reader: *Io.File.MultiReader,
100 timeout: Io.Timeout,
101) (Io.File.MultiReader.Error || Io.Timeout.Error)!InMessage.Header {
102 while (c.in.bufferedLen() < @sizeOf(InMessage.Header)) {
103 multi_reader.fill(64, timeout) catch |err| switch (err) {
104 error.Canceled,
105 error.Timeout,
106 error.ConcurrencyUnavailable,
107 error.EndOfStream,
108 => |e| return e,
109 };
110 }
111 const header = c.in.takeStruct(InMessage.Header, .little) catch unreachable;
112 while (c.in.bufferedLen() < header.bytes_len) {
113 try multi_reader.fill(header.bytes_len - c.in.bufferedLen(), timeout);
114 }
115 try multi_reader.checkAnyError();
116 return header;
117}
118
119/// Don't forget to flush!
120pub fn serveMessageHeader(c: *const Client, header: OutMessage.Header) Writer.Error!void {
121 try c.out.writeStruct(header, .little);
122}
123
124pub fn serveBodylessMessage(c: *const Client, tag: OutMessage.Tag) Writer.Error!void {
125 try c.serveMessageHeader(.{ .tag = tag, .bytes_len = 0 });
126 try c.out.flush();
127}
128
129pub fn serveRunTest(c: *const Client, index: u32) !void {
130 try c.serveMessageHeader(.{
131 .tag = .run_test,
132 .bytes_len = @sizeOf(u32),
133 });
134 try c.out.writeInt(u32, index, .little);
135 try c.out.flush();
136}
137
138pub fn serveRunFuzzTestMessage(
139 c: *const Client,
140 test_names: []const []const u8,
141 kind: std.Build.abi.fuzz.LimitKind,
142 amount_or_instance: u64,
143) !void {
144 try c.serveMessageHeader(.{
145 .tag = .start_fuzzing,
146 .bytes_len = 1 + 8 + 4 + count: {
147 var bytes_len: u32 = @intCast(test_names.len * 4);
148 for (test_names) |name| {
149 bytes_len += @intCast(name.len);
150 }
151 break :count bytes_len;
152 },
153 });
154 try c.out.writeByte(@backingInt(kind));
155 try c.out.writeInt(u64, amount_or_instance, .little);
156 try c.out.writeInt(u32, @intCast(test_names.len), .little);
157 for (test_names) |test_name| {
158 try c.out.writeInt(u32, @intCast(test_name.len), .little);
159 try c.out.writeAll(test_name);
160 }
161 try c.out.flush();
162}
163
164pub fn serveBuildSteps(
165 c: *const Client,
166 steps: []const Configuration.Step.Index,
167 flags: OutMessage.BuildSteps.Flags,
168) !void {
169 try c.serveMessageHeader(.{
170 .tag = .bsp_build_steps,
171 .bytes_len = @intCast(@sizeOf(OutMessage.BuildSteps) + steps.len * @sizeOf(Configuration.Step.Index)),
172 });
173 const body: OutMessage.BuildSteps = .{
174 .step_count = @intCast(steps.len),
175 .flags = flags,
176 };
177 try c.out.writeStruct(body, .little);
178 try c.out.writeSliceEndian(Configuration.Step.Index, steps, .little);
179 try c.out.flush();
180}
lib/std/zig/Server.zig+66-19
......@@ -1,12 +1,8 @@
11const Server = @This();
22
3const builtin = @import("builtin");
4
53const std = @import("std");
64const Allocator = std.mem.Allocator;
75const assert = std.debug.assert;
8const native_endian = builtin.target.cpu.arch.endian();
9const need_bswap = native_endian != .little;
106const Cache = std.Build.Cache;
117const OutMessage = std.zig.Server.Message;
128const InMessage = std.zig.Client.Message;
......@@ -16,6 +12,14 @@ const Writer = std.Io.Writer;
1612in: *Reader,
1713out: *Writer,
1814
15/// The ABI version of the build system protocol. Will be bumped whenever a
16/// backwards incompatible changes to the protocol is made.
17///
18/// Does not apply to the internal compiler protocol or test runner.
19///
20/// See `version` in `Message.Handshake`.
21pub const build_system_version: u32 = 1;
22
1923pub const Message = struct {
2024 pub const Header = extern struct {
2125 tag: Tag,
......@@ -70,9 +74,62 @@ pub const Message = struct {
7074 /// Body is a TimeReport.
7175 time_report,
7276
77 /// The first message sent by the server over the build system protocol.
78 /// Body is a `Handshake`.
79 /// This message only applies to the build system protocol.
80 bsp_handshake = 0x80000000,
81 /// Notifies that a new configuration file is available.
82 /// Body is a cwd relative path to the configuration file.
83 /// This message only applies to the build system protocol.
84 bsp_configuration,
85 /// Does not have a body.
86 /// This message only applies to the build system protocol.
87 bsp_build_started,
88 /// Does not have a body.
89 /// This message only applies to the build system protocol.
90 bsp_build_completed,
91 /// Body is a `Configuration.Step.Index`.
92 /// This message only applies to the build system protocol.
93 bsp_step_started,
94 /// Body is a `BuildStepCompleted`.
95 /// This message only applies to the build system protocol.
96 bsp_step_completed,
97
7398 _,
7499 };
75100
101 /// Trailing:
102 /// * base_paths: BasePaths,
103 pub const Handshake = extern struct {
104 /// See `build_system_version`.
105 version: u32,
106 flags: Flags,
107
108 pub const Flags = packed struct(u32) {
109 file_system_watch_supported: bool,
110 _: u31 = 0,
111 };
112 };
113
114 /// Trailing:
115 /// * error_bundle: ErrorBundle,
116 pub const BuildStepCompleted = extern struct {
117 step_index: std.Build.Configuration.Step.Index,
118 status: Status,
119 error_bundle: ErrorBundle,
120 // TODO result_error_msgs
121 // TODO result_stderr
122 // TODO result_peak_rss
123 // TODO result_duration_ns
124
125 pub const Status = enum(u32) {
126 success,
127 failure,
128 skipped,
129 skipped_oom,
130 };
131 };
132
76133 pub const PathPrefix = enum(u8) {
77134 cwd,
78135 zig_lib,
......@@ -140,21 +197,6 @@ pub const Message = struct {
140197 };
141198};
142199
143pub const Options = struct {
144 in: *Reader,
145 out: *Writer,
146 zig_version: []const u8,
147};
148
149pub fn init(options: Options) !Server {
150 var s: Server = .{
151 .in = options.in,
152 .out = options.out,
153 };
154 try s.serveStringMessage(.zig_version, options.zig_version);
155 return s;
156}
157
158200pub fn receiveMessage(s: *Server) !InMessage.Header {
159201 return s.in.takeStruct(InMessage.Header, .little);
160202}
......@@ -183,6 +225,11 @@ pub fn serveMessageHeader(s: *const Server, header: OutMessage.Header) !void {
183225 try s.out.writeStruct(header, .little);
184226}
185227
228pub fn serveBodylessMessage(s: *const Server, tag: OutMessage.Tag) Writer.Error!void {
229 try s.serveMessageHeader(.{ .tag = tag, .bytes_len = 0 });
230 try s.out.flush();
231}
232
186233pub fn serveU32Message(s: *const Server, tag: OutMessage.Tag, int: u32) !void {
187234 try serveMessageHeader(s, .{
188235 .tag = tag,
src/Compilation.zig+12-8
......@@ -6028,26 +6028,30 @@ fn spawnZigRc(
60286028 multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? });
60296029 defer multi_reader.deinit();
60306030
6031 const stdout = multi_reader.fileReader(0);
6032 const MessageHeader = std.zig.Server.Message.Header;
6031 const stdout = multi_reader.reader(0);
60336032
60346033 var eos_err: error{EndOfStream}!void = {};
60356034
6035 var client: std.zig.Client = .{
6036 .in = stdout,
6037 .out = undefined,
6038 };
6039
60366040 while (true) {
6037 const header = stdout.interface.takeStruct(MessageHeader, .little) catch |err| switch (err) {
6038 error.EndOfStream => break,
6039 error.ReadFailed => return stdout.err.?,
6040 };
6041 const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) {
6041 const header = client.receiveMessageWithMultiReader(&multi_reader, .none) catch |err| switch (err) {
6042 error.Timeout => unreachable,
60426043 error.EndOfStream => |e| {
6044 if (client.in.bufferedLen() == 0) break;
60436045 // Better to report the crash with stderr below, but we set
60446046 // this in case the child exits successfully while violating
60456047 // this protocol.
60466048 eos_err = e;
60476049 break;
60486050 },
6049 error.ReadFailed => return stdout.err.?,
6051 else => |e| return e,
60506052 };
6053 const body = client.in.take(header.bytes_len) catch unreachable;
6054
60516055 switch (header.tag) {
60526056 // We expect exactly one ErrorBundle, and if any error_bundle header is
60536057 // sent then it's a fatal error.
src/main.zig+2-5
......@@ -4297,11 +4297,8 @@ fn serve(
42974297 const gpa = comp.gpa;
42984298 const io = comp.io;
42994299
4300 var server = try Server.init(.{
4301 .in = in,
4302 .out = out,
4303 .zig_version = build_options.version,
4304 });
4300 var server: Server = .{ .in = in, .out = out };
4301 try server.serveStringMessage(.zig_version, build_options.version);
43054302
43064303 var child_pid: ?std.process.Child.Id = null;
43074304
test/standalone/build.zig+1
......@@ -31,6 +31,7 @@ pub fn build(b: *std.Build) void {
3131 const tools_target = b.resolveTargetQuery(.{});
3232 for ([_][]const u8{
3333 // Alphabetically sorted. No need to build `tools/spirv/grammar.zig`.
34 "../../tools/bsp.zig",
3435 "../../tools/check_mingw.zig",
3536 "../../tools/dump-cov.zig",
3637 "../../tools/fetch_them_macos_headers.zig",
tools/bsp.zig created+242
......@@ -0,0 +1,242 @@
1//! CLI tool to interface with the build system protocol (zig build --listen=-)
2
3const std = @import("std");
4const Io = std.Io;
5const Allocator = std.mem.Allocator;
6const Configuration = std.Build.Configuration;
7const Client = std.zig.Client;
8const Server = std.zig.Server;
9const log = std.log.scoped(.bsp);
10
11pub fn main(init: std.process.Init) !void {
12 const io = init.io;
13 const gpa = init.gpa;
14 const arena = init.arena.allocator();
15
16 var maker_args: std.ArrayList([]const u8) = .empty;
17
18 const args = try init.minimal.args.toSlice(arena);
19 for (args[1..]) |arg| {
20 try maker_args.append(arena, try arena.dupe(u8, arg));
21 }
22 if (maker_args.items.len < 1) try maker_args.append(arena, "zig");
23 if (maker_args.items.len < 2) try maker_args.append(arena, "build");
24 if (!std.mem.eql(u8, maker_args.last().?.*, "--listen=-")) try maker_args.append(arena, "--listen=-");
25
26 log.debug("cmd: {f}", .{std.zig.SubprocessCommand{
27 .argv = maker_args.items,
28 }});
29
30 var child_process = std.process.spawn(io, .{
31 .argv = maker_args.items,
32 .stdin = .pipe,
33 .stdout = .pipe,
34 .stderr = .pipe,
35 }) catch |err| std.debug.panic("failed to spawn process: {}", .{err});
36 errdefer child_process.kill(io);
37
38 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
39 var multi_reader: Io.File.MultiReader = undefined;
40 defer multi_reader.deinit();
41 multi_reader.init(
42 gpa,
43 io,
44 multi_reader_buffer.toStreams(),
45 &.{ child_process.stdout.?, child_process.stderr.? },
46 );
47 const client_stdout = multi_reader.reader(0);
48 const client_stderr = multi_reader.reader(1);
49
50 var client_stdout_buffer: [256]u8 = undefined;
51 var client_stdout_writer = child_process.stdin.?.writerStreaming(io, &client_stdout_buffer);
52
53 var client: Client = .{
54 .in = client_stdout,
55 .out = &client_stdout_writer.interface,
56 };
57
58 const err = blk: {
59 const handshake: Server.Message.Handshake = handshake: {
60 const header = client.receiveMessageWithMultiReader(&multi_reader, .none) catch |err| switch (err) {
61 error.Canceled, error.ConcurrencyUnavailable => |e| return e,
62 error.Timeout => unreachable,
63 else => |e| {
64 log.err("failed to receive message: {t}", .{err});
65 break :blk e;
66 },
67 };
68 const body = client_stdout.take(header.bytes_len) catch unreachable;
69 log.debug("received {f} ({d} bytes)", .{ fmtEnum(header.tag), body.len });
70
71 if (header.tag != .bsp_handshake) {
72 log.err("received unexpected message: {f}", .{fmtEnum(header.tag)});
73 return error.UnexpectedMessage;
74 }
75
76 var r: Io.Reader = .fixed(body);
77 break :handshake try r.takeStruct(Server.Message.Handshake, .little);
78 };
79 _ = handshake;
80
81 var conf_arena_allocator: std.heap.ArenaAllocator = .init(gpa);
82 defer conf_arena_allocator.deinit();
83 const conf_arena = conf_arena_allocator.allocator();
84
85 const configuration = configuration: {
86 const header = client.receiveMessageWithMultiReader(&multi_reader, .none) catch |err| switch (err) {
87 error.Canceled, error.ConcurrencyUnavailable => |e| return e,
88 error.Timeout => unreachable,
89 else => |e| {
90 log.err("failed to receive message: {t}", .{err});
91 break :blk e;
92 },
93 };
94 const body = client_stdout.take(header.bytes_len) catch unreachable;
95 log.debug("received {t} ({d} bytes)", .{ header.tag, body.len });
96
97 if (header.tag != .bsp_configuration) {
98 log.err("received unexpected message: {f}", .{fmtEnum(header.tag)});
99 return error.UnexpectedMessage;
100 }
101
102 const configuration_path = body;
103 var file = Io.Dir.cwd().openFile(io, configuration_path, .{}) catch |err|
104 std.debug.panic("failed to open configuration file {q}: {t}", .{ configuration_path, err });
105 defer file.close(io);
106 break :configuration Configuration.loadFile(conf_arena, io, file) catch |err|
107 std.debug.panic("failed to load configuration file {q}: {t}", .{ configuration_path, err });
108 };
109 const c = &configuration;
110
111 var top_level_steps: std.array_hash_map.String(Configuration.Step.Index) = .empty;
112 defer top_level_steps.deinit(gpa);
113
114 for (c.steps, 0..) |*conf_step, step_index_usize| {
115 if (conf_step.owner != .root) continue;
116 const step_index: Configuration.Step.Index = @fromBackingInt(@intCast(step_index_usize));
117 const flags = conf_step.flags(c);
118 if (flags.tag != .top_level) continue;
119 const name = step_index.ptr(c).name.slice(c);
120 try top_level_steps.putNoClobber(gpa, name, step_index);
121 }
122
123 std.debug.print("Steps:\n", .{});
124 for (top_level_steps.keys()) |name| {
125 std.debug.print(" - {q}\n", .{name});
126 }
127 std.debug.print(
128 \\Available Commands:
129 \\ - build [step names / step indices]
130 \\ - watch [step names / step indices]
131 \\ - exit
132 \\
133 , .{});
134
135 var stdin_reader_buffer: [256]u8 = undefined;
136 var stdin_reader = Io.File.stdin().reader(io, &stdin_reader_buffer);
137 const stdin = &stdin_reader.interface;
138
139 while (true) {
140 try Io.File.stdout().writeStreamingAll(io, "> ");
141 const command = try stdin.takeDelimiterExclusive('\n');
142 stdin.toss(1);
143 if (std.mem.startsWith(u8, command, "build") or
144 std.mem.startsWith(u8, command, "watch"))
145 {
146 var steps: std.ArrayList(Configuration.Step.Index) = .empty;
147 defer steps.deinit(gpa);
148
149 const watch = std.mem.startsWith(u8, command, "watch");
150
151 if (std.mem.cutPrefix(u8, command, "build ") orelse
152 std.mem.cutPrefix(u8, command, "watch ")) |command_args|
153 {
154 var it = std.mem.tokenizeScalar(u8, command_args, ' ');
155 while (it.next()) |arg| {
156 const step: Configuration.Step.Index =
157 if (std.fmt.parseInt(u32, arg, 10)) |i|
158 @fromBackingInt(i)
159 else |_|
160 top_level_steps.get(arg) orelse std.debug.panic("unexpected step name or index", .{});
161 try steps.append(gpa, step);
162 }
163 }
164
165 if (steps.items.len < 1) {
166 try steps.append(gpa, c.default_step);
167 }
168
169 try client.serveBuildSteps(steps.items, .{ .watch = watch });
170
171 while (true) {
172 const header: Server.Message.Header = client.receiveMessageWithMultiReader(&multi_reader, .none) catch |err| switch (err) {
173 error.Canceled, error.ConcurrencyUnavailable => |e| return e,
174 error.Timeout => unreachable,
175 else => |e| {
176 log.err("failed to receive message: {t}", .{err});
177 break :blk e;
178 },
179 };
180 const body = client_stdout.take(header.bytes_len) catch unreachable;
181 log.debug("received {f} ({d} bytes)", .{ fmtEnum(header.tag), body.len });
182
183 switch (header.tag) {
184 .bsp_build_started => {},
185 .bsp_build_completed => if (!watch) break,
186 .bsp_step_started => {},
187 .bsp_step_completed => {},
188 .bsp_configuration => @panic("TODO"),
189 else => std.debug.panic("received unexpected message: {f}", .{fmtEnum(header.tag)}),
190 }
191 }
192 continue;
193 } else if (std.mem.eql(u8, command, "exit")) {
194 try client.serveBodylessMessage(.exit);
195 break;
196 } else {
197 log.err("unknown command: {q}", .{command});
198 continue;
199 }
200 }
201 };
202
203 try multi_reader.fillRemaining(.none);
204
205 if (client_stderr.bufferedLen() > 0) {
206 log.err("stderr:\n{s}\n", .{client_stderr.buffered()});
207 }
208
209 try err;
210
211 const term = try child_process.wait(io);
212
213 if (!term.success()) {
214 log.err("maker {f}", .{term});
215 }
216}
217
218const FormatEnum = union(enum) {
219 named: []const u8,
220 unnamed: usize,
221
222 pub fn format(
223 e: FormatEnum,
224 writer: *std.Io.Writer,
225 ) std.Io.Writer.Error!void {
226 switch (e) {
227 .named => |name| {
228 try writer.writeByte('.');
229 try writer.writeAll(name);
230 },
231 .unnamed => |number| try writer.print("0x{x}", .{number}),
232 }
233 }
234};
235
236fn fmtEnum(e: anytype) FormatEnum {
237 if (std.enums.tagName(@TypeOf(e), e)) |name| {
238 return .{ .named = name };
239 } else {
240 return .{ .unnamed = @backingInt(e) };
241 }
242}
tools/incr-check.zig+34-30
......@@ -305,21 +305,23 @@ const Eval = struct {
305305
306306 fn check(eval: *Eval, mr: *Io.File.MultiReader, update: Case.Update, prog_node: std.Progress.Node) !void {
307307 const arena = eval.arena;
308 const stdout = mr.fileReader(0);
309 const stderr = &mr.fileReader(1).interface;
310 const Header = std.zig.Server.Message.Header;
308 const stdout = mr.reader(0);
309 const stderr = mr.reader(1);
310
311 var client: std.zig.Client = .{
312 .in = stdout,
313 .out = undefined,
314 };
311315
312316 while (true) {
313 const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) {
314 error.EndOfStream => break,
315 error.ReadFailed => return stdout.err.?,
316 };
317 const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) {
317 const header = client.receiveMessageWithMultiReader(mr, .none) catch |err| switch (err) {
318 error.Timeout => unreachable,
318319 // If this panic triggers it might be helpful to rework this
319320 // code to print the stderr from the abnormally terminated child.
320321 error.EndOfStream => @panic("unexpected mid-message end of stream"),
321 error.ReadFailed => return stdout.err.?,
322 else => |e| return e,
322323 };
324 const body = client.in.take(header.bytes_len) catch unreachable;
323325
324326 switch (header.tag) {
325327 .error_bundle => {
......@@ -605,12 +607,13 @@ const Eval = struct {
605607
606608 fn requestUpdate(eval: *Eval) !void {
607609 const io = eval.io;
608 const header: std.zig.Client.Message.Header = .{
609 .tag = .update,
610 .bytes_len = 0,
610
611 var w = eval.child.stdin.?.writerStreaming(io, &.{});
612 var client: std.zig.Client = .{
613 .in = undefined,
614 .out = &w.interface,
611615 };
612 var w = eval.child.stdin.?.writer(io, &.{});
613 w.interface.writeStruct(header, .little) catch |err| switch (err) {
616 client.serveBodylessMessage(.update) catch |err| switch (err) {
614617 error.WriteFailed => return w.err.?,
615618 };
616619 }
......@@ -618,22 +621,23 @@ const Eval = struct {
618621 fn end(eval: *Eval, mr: *Io.File.MultiReader) !void {
619622 requestExit(eval.child, eval);
620623
621 const stdout = mr.fileReader(0);
622 const Header = std.zig.Server.Message.Header;
624 var client: std.zig.Client = .{
625 .in = mr.reader(0),
626 .out = undefined,
627 };
623628
624629 while (true) {
625 const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) {
626 error.EndOfStream => break,
627 error.ReadFailed => return stdout.err.?,
628 };
629 stdout.interface.discardAll(header.bytes_len) catch |err| switch (err) {
630 error.ReadFailed => return stdout.err.?,
631 error.EndOfStream => |e| return e,
630 const header = client.receiveMessageWithMultiReader(mr, .none) catch |err| switch (err) {
631 error.Timeout => unreachable,
632 error.EndOfStream => |e| {
633 if (client.in.bufferedLen() == 0) break;
634 return e;
635 },
636 else => |e| return e,
632637 };
638 try client.in.discardAll(header.bytes_len);
633639 }
634640
635 try mr.fillRemaining(.none);
636
637641 const stderr = mr.reader(1).buffered();
638642 if (stderr.len > 0) eval.fatal("unexpected stderr:\n{s}", .{stderr});
639643 }
......@@ -899,12 +903,12 @@ fn requestExit(child: *std.process.Child, eval: *Eval) void {
899903 if (child.stdin == null) return;
900904 const io = eval.io;
901905
902 const header: std.zig.Client.Message.Header = .{
903 .tag = .exit,
904 .bytes_len = 0,
906 var w = eval.child.stdin.?.writerStreaming(io, &.{});
907 var client: std.zig.Client = .{
908 .in = undefined,
909 .out = &w.interface,
905910 };
906 var w = eval.child.stdin.?.writer(io, &.{});
907 w.interface.writeStruct(header, .little) catch |err| switch (err) {
911 client.serveBodylessMessage(.exit) catch |err| switch (err) {
908912 error.WriteFailed => switch (w.err.?) {
909913 error.BrokenPipe => {},
910914 else => |e| eval.fatal("failed to send exit: {t}", .{e}),