authorgravatar for techatrix@mailbox.orgTechatrix <techatrix@mailbox.org> 2026-07-21 16:31:29+02:00
committergravatar for techatrix@mailbox.orgTechatrix <techatrix@mailbox.org> 2026-07-21 16:31:29+02:00
log54162b1b9d1df526a145d6c825de3640dee50ede
tree85659212fb0c0e81b0035087f70c2148700d6f83
parentf58c93b5663beacb71dc7add21a85bd0b4f4dc46
signaturebadge-check Signed by SSH key SHA256:HYC3SjXQcAt6uwv9pu/6OoVQ2rUH8rb5zKiUHSe9uxk

Maker: implement build steps request


3 files changed, 151 insertions(+), 25 deletions(-)

lib/compiler/Maker.zig+87-24
......@@ -60,6 +60,8 @@ web_server: ?*AvoidableWebServer,
6060/// Allocated into `gpa`.
6161memory_blocked_steps: std.ArrayList(Configuration.Step.Index),
6262/// Allocated into `gpa`.
63initial_steps: std.array_hash_map.Auto(Configuration.Step.Index, void),
64/// Allocated into `gpa`.
6365step_stack: std.array_hash_map.Auto(Configuration.Step.Index, void),
6466pkg_config: PkgConfig,
6567
......@@ -762,6 +764,7 @@ pub fn main(init: process.Init.Minimal) !void {
762764 .protocol_server = protocol_server,
763765 .protocol_server_mutex = .init,
764766 .memory_blocked_steps = .empty,
767 .initial_steps = .empty,
765768 .step_stack = .empty,
766769 .pkg_config = .{ .debug = debug_pkg_config },
767770
......@@ -776,6 +779,7 @@ pub fn main(init: process.Init.Minimal) !void {
776779 };
777780 defer {
778781 maker.memory_blocked_steps.deinit(gpa);
782 maker.initial_steps.deinit(gpa);
779783 maker.step_stack.deinit(gpa);
780784 }
781785
......@@ -809,6 +813,41 @@ pub fn main(init: process.Init.Minimal) !void {
809813 cleanExit(io, &scanned_config);
810814 process.exit(0);
811815 },
816 .bsp_build_steps => {
817 // Cancel existing file watching
818 select.cancelDiscard();
819 in_debounce = false;
820
821 const body = try s.in.takeStruct(Client.Message.BuildSteps, .little);
822 const steps = try s.in.readSliceEndianAlloc(gpa, Configuration.Step.Index, body.step_count, .little);
823 defer gpa.free(steps);
824 if (body.flags.watch and !Watch.have_impl) fatal("file watching is unavailable", .{});
825
826 try select.concurrent(.message, Server.receiveMessage, .{s});
827
828 maker.watch = body.flags.watch;
829 maker.prepare(steps) catch |err| switch (err) {
830 error.DependencyLoopDetected, error.InsufficientMemory => {
831 // TODO handle DependencyLoopDetected as error.FailedButCacheIntact
832 // and handle InsufficientMemory as error.AlreadyReported
833 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
834 process.exit(1);
835 },
836 else => |e| return e,
837 };
838
839 try maker.makeSteps(main_progress_node, null);
840
841 if (body.flags.watch) {
842 if (!Watch.have_impl) unreachable;
843 if (w == null) w = try .init(&maker);
844
845 try w.?.update(maker.step_stack.keys());
846 try select.concurrent(.fs_event, Watch.wait, .{ &w.?, if (in_debounce) .{ .ms = debounce_interval_ms } else .none });
847 }
848
849 continue :loop try select.await();
850 },
812851 else => fatal("unsupported message: {t}", .{header.tag}),
813852 }
814853 },
......@@ -818,7 +857,7 @@ pub fn main(init: process.Init.Minimal) !void {
818857 .timeout => {
819858 assert(in_debounce);
820859 markFailedStepsDirty(&maker);
821 if (true) @panic("TODO run steps that were previous specified over the build system protocol");
860 try maker.makeSteps(main_progress_node, null);
822861 in_debounce = false;
823862 },
824863 .dirty => in_debounce = true,
......@@ -830,7 +869,10 @@ pub fn main(init: process.Init.Minimal) !void {
830869 }
831870 }
832871
833 maker.prepare(step_names.items) catch |err| switch (err) {
872 const initial_steps = try maker.resolveTopLevelSteps(step_names.items);
873 defer gpa.free(initial_steps);
874
875 maker.prepare(initial_steps) catch |err| switch (err) {
834876 error.DependencyLoopDetected, error.InsufficientMemory => {
835877 // TODO handle DependencyLoopDetected as error.FailedButCacheIntact
836878 // and handle InsufficientMemory as error.AlreadyReported
......@@ -857,7 +899,7 @@ pub fn main(init: process.Init.Minimal) !void {
857899 }) {
858900 if (web_server) |ws| ws.startBuild();
859901
860 try maker.makeStepNames(step_names.items, main_progress_node, fuzz);
902 try maker.makeSteps(main_progress_node, fuzz);
861903
862904 if (web_server) |ws| {
863905 if (fuzz) |mode| if (mode != .forever) fatal(
......@@ -2104,11 +2146,37 @@ pub fn stepByIndex(maker: *const Maker, i: Configuration.Step.Index) *Step {
21042146 return &maker.steps[@backingInt(i)];
21052147}
21062148
2107fn prepare(maker: *Maker, step_names: []const []const u8) !void {
2149fn resolveTopLevelSteps(maker: *Maker, step_names: []const []const u8) ![]const Configuration.Step.Index {
2150 const gpa = maker.gpa;
2151 const c = &maker.scanned_config.configuration;
2152
2153 if (step_names.len == 0) {
2154 return try gpa.dupe(Configuration.Step.Index, &.{c.default_step});
2155 }
2156
2157 var result: std.array_hash_map.Auto(Configuration.Step.Index, void) = .empty;
2158 defer result.deinit(gpa);
2159
2160 try result.ensureTotalCapacity(gpa, step_names.len);
2161
2162 for (0..step_names.len) |i| {
2163 const step_name = step_names[step_names.len - i - 1];
2164 const s = maker.scanned_config.top_level_steps.get(step_name) orelse {
2165 log.info("to list available steps: zig build -l", .{});
2166 fatal("no such step: {s}", .{step_name});
2167 };
2168 result.putAssumeCapacity(s, {});
2169 }
2170
2171 return try gpa.dupe(Configuration.Step.Index, result.keys());
2172}
2173
2174fn prepare(maker: *Maker, step_indices: []const Configuration.Step.Index) !void {
21082175 const gpa = maker.gpa;
21092176 const graph = maker.graph;
21102177 const arena = graph.arena;
21112178 const seed: u32 = graph.random_seed;
2179 const initial_steps = &maker.initial_steps;
21122180 const step_stack = &maker.step_stack;
21132181 const c = &maker.scanned_config.configuration;
21142182
......@@ -2117,18 +2185,15 @@ fn prepare(maker: *Maker, step_names: []const []const u8) !void {
21172185 step.* = .{ .extended = .init(step_index.ptr(c).flags(c).tag) };
21182186 }
21192187
2120 if (step_names.len == 0) {
2121 try step_stack.put(gpa, c.default_step, {});
2122 } else {
2123 try step_stack.ensureUnusedCapacity(gpa, step_names.len);
2124 for (0..step_names.len) |i| {
2125 const step_name = step_names[step_names.len - i - 1];
2126 const s = maker.scanned_config.top_level_steps.get(step_name) orelse {
2127 log.info("to list available steps: zig build -l", .{});
2128 fatal("no such step: {s}", .{step_name});
2129 };
2130 step_stack.putAssumeCapacity(s, {});
2131 }
2188 try initial_steps.ensureUnusedCapacity(gpa, step_indices.len);
2189 try step_stack.ensureUnusedCapacity(gpa, step_indices.len);
2190
2191 initial_steps.clearRetainingCapacity();
2192 step_stack.clearRetainingCapacity();
2193
2194 for (step_indices) |step| {
2195 initial_steps.putAssumeCapacity(step, {});
2196 step_stack.putAssumeCapacity(step, {});
21322197 }
21332198
21342199 const starting_steps = try arena.dupe(Configuration.Step.Index, step_stack.keys());
......@@ -2177,9 +2242,8 @@ fn prepare(maker: *Maker, step_names: []const []const u8) !void {
21772242 }
21782243}
21792244
2180fn makeStepNames(
2245fn makeSteps(
21812246 maker: *Maker,
2182 step_names: []const []const u8,
21832247 parent_progress_node: std.Progress.Node,
21842248 fuzz: ?Fuzz.Mode,
21852249) !void {
......@@ -2367,7 +2431,7 @@ fn makeStepNames(
23672431 defer step_stack_copy.deinit(gpa);
23682432
23692433 var print_node: PrintNode = .{ .parent = null };
2370 if (step_names.len == 0) {
2434 if (maker.initial_steps.count() == 0) {
23712435 print_node.last = true;
23722436 printTreeStep(maker, c.default_step, t, &print_node, &step_stack_copy) catch |err| switch (err) {
23732437 error.Canceled => |e| return e,
......@@ -2375,10 +2439,10 @@ fn makeStepNames(
23752439 };
23762440 } else {
23772441 const last_index = if (maker.summary == .all) top_level_steps.count() else blk: {
2378 var i: usize = step_names.len;
2442 var i: usize = maker.initial_steps.count();
23792443 while (i > 0) {
23802444 i -= 1;
2381 const step_index = top_level_steps.get(step_names[i]).?;
2445 const step_index = maker.initial_steps.keys()[i];
23822446 const step = maker.stepByIndex(step_index);
23832447 const found = switch (maker.summary) {
23842448 .all, .line, .none => unreachable,
......@@ -2389,8 +2453,7 @@ fn makeStepNames(
23892453 }
23902454 break :blk top_level_steps.count();
23912455 };
2392 for (step_names, 0..) |step_name, i| {
2393 const step_index = top_level_steps.get(step_name).?;
2456 for (maker.initial_steps.keys(), 0..) |step_index, i| {
23942457 print_node.last = i + 1 == last_index;
23952458 printTreeStep(maker, step_index, t, &print_node, &step_stack_copy) catch |err| switch (err) {
23962459 error.Canceled => |e| return e,
......@@ -2401,7 +2464,7 @@ fn makeStepNames(
24012464 w.writeByte('\n') catch {};
24022465 }
24032466
2404 if (maker.watch or maker.web_server != null) return;
2467 if (maker.watch or maker.web_server != null or maker.protocol_server != null) return;
24052468
24062469 const code: u8 = code: {
24072470 if (failure_count == 0) break :code 0; // success
lib/std/zig/Client.zig+38
......@@ -4,6 +4,7 @@ const std = @import("std");
44const Io = std.Io;
55const Allocator = std.mem.Allocator;
66const assert = std.debug.assert;
7const Configuration = std.Build.Configuration;
78const OutMessage = std.zig.Client.Message;
89const InMessage = std.zig.Server.Message;
910const Reader = Io.Reader;
......@@ -60,9 +61,28 @@ pub const Message = struct {
6061 /// The message body has the same format as in Server.
6162 new_fuzz_input,
6263
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
6369 _,
6470 };
6571
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
6686 comptime {
6787 assert(@sizeOf(std.Build.abi.fuzz.LimitKind) == 1);
6888 }
......@@ -140,3 +160,21 @@ pub fn serveRunFuzzTestMessage(
140160 }
141161 try c.out.flush();
142162}
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}
tools/bsp.zig+26-1
......@@ -143,7 +143,32 @@ pub fn main(init: std.process.Init) !void {
143143 if (std.mem.startsWith(u8, command, "build") or
144144 std.mem.startsWith(u8, command, "watch"))
145145 {
146 @panic("TODO");
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 continue;
147172 } else if (std.mem.eql(u8, command, "exit")) {
148173 try client.serveBodylessMessage(.exit);
149174 break;