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,...@@ -60,6 +60,8 @@ web_server: ?*AvoidableWebServer,
60/// Allocated into `gpa`.60/// Allocated into `gpa`.
61memory_blocked_steps: std.ArrayList(Configuration.Step.Index),61memory_blocked_steps: std.ArrayList(Configuration.Step.Index),
62/// Allocated into `gpa`.62/// Allocated into `gpa`.
63initial_steps: std.array_hash_map.Auto(Configuration.Step.Index, void),
64/// Allocated into `gpa`.
63step_stack: std.array_hash_map.Auto(Configuration.Step.Index, void),65step_stack: std.array_hash_map.Auto(Configuration.Step.Index, void),
64pkg_config: PkgConfig,66pkg_config: PkgConfig,
6567
...@@ -762,6 +764,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -762,6 +764,7 @@ pub fn main(init: process.Init.Minimal) !void {
762 .protocol_server = protocol_server,764 .protocol_server = protocol_server,
763 .protocol_server_mutex = .init,765 .protocol_server_mutex = .init,
764 .memory_blocked_steps = .empty,766 .memory_blocked_steps = .empty,
767 .initial_steps = .empty,
765 .step_stack = .empty,768 .step_stack = .empty,
766 .pkg_config = .{ .debug = debug_pkg_config },769 .pkg_config = .{ .debug = debug_pkg_config },
767770
...@@ -776,6 +779,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -776,6 +779,7 @@ pub fn main(init: process.Init.Minimal) !void {
776 };779 };
777 defer {780 defer {
778 maker.memory_blocked_steps.deinit(gpa);781 maker.memory_blocked_steps.deinit(gpa);
782 maker.initial_steps.deinit(gpa);
779 maker.step_stack.deinit(gpa);783 maker.step_stack.deinit(gpa);
780 }784 }
781785
...@@ -809,6 +813,41 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -809,6 +813,41 @@ pub fn main(init: process.Init.Minimal) !void {
809 cleanExit(io, &scanned_config);813 cleanExit(io, &scanned_config);
810 process.exit(0);814 process.exit(0);
811 },815 },
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 },
812 else => fatal("unsupported message: {t}", .{header.tag}),851 else => fatal("unsupported message: {t}", .{header.tag}),
813 }852 }
814 },853 },
...@@ -818,7 +857,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -818,7 +857,7 @@ pub fn main(init: process.Init.Minimal) !void {
818 .timeout => {857 .timeout => {
819 assert(in_debounce);858 assert(in_debounce);
820 markFailedStepsDirty(&maker);859 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);
822 in_debounce = false;861 in_debounce = false;
823 },862 },
824 .dirty => in_debounce = true,863 .dirty => in_debounce = true,
...@@ -830,7 +869,10 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -830,7 +869,10 @@ pub fn main(init: process.Init.Minimal) !void {
830 }869 }
831 }870 }
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) {
834 error.DependencyLoopDetected, error.InsufficientMemory => {876 error.DependencyLoopDetected, error.InsufficientMemory => {
835 // TODO handle DependencyLoopDetected as error.FailedButCacheIntact877 // TODO handle DependencyLoopDetected as error.FailedButCacheIntact
836 // and handle InsufficientMemory as error.AlreadyReported878 // and handle InsufficientMemory as error.AlreadyReported
...@@ -857,7 +899,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -857,7 +899,7 @@ pub fn main(init: process.Init.Minimal) !void {
857 }) {899 }) {
858 if (web_server) |ws| ws.startBuild();900 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
862 if (web_server) |ws| {904 if (web_server) |ws| {
863 if (fuzz) |mode| if (mode != .forever) fatal(905 if (fuzz) |mode| if (mode != .forever) fatal(
...@@ -2104,11 +2146,37 @@ pub fn stepByIndex(maker: *const Maker, i: Configuration.Step.Index) *Step {...@@ -2104,11 +2146,37 @@ pub fn stepByIndex(maker: *const Maker, i: Configuration.Step.Index) *Step {
2104 return &maker.steps[@backingInt(i)];2146 return &maker.steps[@backingInt(i)];
2105}2147}
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 {
2108 const gpa = maker.gpa;2175 const gpa = maker.gpa;
2109 const graph = maker.graph;2176 const graph = maker.graph;
2110 const arena = graph.arena;2177 const arena = graph.arena;
2111 const seed: u32 = graph.random_seed;2178 const seed: u32 = graph.random_seed;
2179 const initial_steps = &maker.initial_steps;
2112 const step_stack = &maker.step_stack;2180 const step_stack = &maker.step_stack;
2113 const c = &maker.scanned_config.configuration;2181 const c = &maker.scanned_config.configuration;
21142182
...@@ -2117,18 +2185,15 @@ fn prepare(maker: *Maker, step_names: []const []const u8) !void {...@@ -2117,18 +2185,15 @@ fn prepare(maker: *Maker, step_names: []const []const u8) !void {
2117 step.* = .{ .extended = .init(step_index.ptr(c).flags(c).tag) };2185 step.* = .{ .extended = .init(step_index.ptr(c).flags(c).tag) };
2118 }2186 }
21192187
2120 if (step_names.len == 0) {2188 try initial_steps.ensureUnusedCapacity(gpa, step_indices.len);
2121 try step_stack.put(gpa, c.default_step, {});2189 try step_stack.ensureUnusedCapacity(gpa, step_indices.len);
2122 } else {2190
2123 try step_stack.ensureUnusedCapacity(gpa, step_names.len);2191 initial_steps.clearRetainingCapacity();
2124 for (0..step_names.len) |i| {2192 step_stack.clearRetainingCapacity();
2125 const step_name = step_names[step_names.len - i - 1];2193
2126 const s = maker.scanned_config.top_level_steps.get(step_name) orelse {2194 for (step_indices) |step| {
2127 log.info("to list available steps: zig build -l", .{});2195 initial_steps.putAssumeCapacity(step, {});
2128 fatal("no such step: {s}", .{step_name});2196 step_stack.putAssumeCapacity(step, {});
2129 };
2130 step_stack.putAssumeCapacity(s, {});
2131 }
2132 }2197 }
21332198
2134 const starting_steps = try arena.dupe(Configuration.Step.Index, step_stack.keys());2199 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 {...@@ -2177,9 +2242,8 @@ fn prepare(maker: *Maker, step_names: []const []const u8) !void {
2177 }2242 }
2178}2243}
21792244
2180fn makeStepNames(2245fn makeSteps(
2181 maker: *Maker,2246 maker: *Maker,
2182 step_names: []const []const u8,
2183 parent_progress_node: std.Progress.Node,2247 parent_progress_node: std.Progress.Node,
2184 fuzz: ?Fuzz.Mode,2248 fuzz: ?Fuzz.Mode,
2185) !void {2249) !void {
...@@ -2367,7 +2431,7 @@ fn makeStepNames(...@@ -2367,7 +2431,7 @@ fn makeStepNames(
2367 defer step_stack_copy.deinit(gpa);2431 defer step_stack_copy.deinit(gpa);
23682432
2369 var print_node: PrintNode = .{ .parent = null };2433 var print_node: PrintNode = .{ .parent = null };
2370 if (step_names.len == 0) {2434 if (maker.initial_steps.count() == 0) {
2371 print_node.last = true;2435 print_node.last = true;
2372 printTreeStep(maker, c.default_step, t, &print_node, &step_stack_copy) catch |err| switch (err) {2436 printTreeStep(maker, c.default_step, t, &print_node, &step_stack_copy) catch |err| switch (err) {
2373 error.Canceled => |e| return e,2437 error.Canceled => |e| return e,
...@@ -2375,10 +2439,10 @@ fn makeStepNames(...@@ -2375,10 +2439,10 @@ fn makeStepNames(
2375 };2439 };
2376 } else {2440 } else {
2377 const last_index = if (maker.summary == .all) top_level_steps.count() else blk: {2441 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();
2379 while (i > 0) {2443 while (i > 0) {
2380 i -= 1;2444 i -= 1;
2381 const step_index = top_level_steps.get(step_names[i]).?;2445 const step_index = maker.initial_steps.keys()[i];
2382 const step = maker.stepByIndex(step_index);2446 const step = maker.stepByIndex(step_index);
2383 const found = switch (maker.summary) {2447 const found = switch (maker.summary) {
2384 .all, .line, .none => unreachable,2448 .all, .line, .none => unreachable,
...@@ -2389,8 +2453,7 @@ fn makeStepNames(...@@ -2389,8 +2453,7 @@ fn makeStepNames(
2389 }2453 }
2390 break :blk top_level_steps.count();2454 break :blk top_level_steps.count();
2391 };2455 };
2392 for (step_names, 0..) |step_name, i| {2456 for (maker.initial_steps.keys(), 0..) |step_index, i| {
2393 const step_index = top_level_steps.get(step_name).?;
2394 print_node.last = i + 1 == last_index;2457 print_node.last = i + 1 == last_index;
2395 printTreeStep(maker, step_index, t, &print_node, &step_stack_copy) catch |err| switch (err) {2458 printTreeStep(maker, step_index, t, &print_node, &step_stack_copy) catch |err| switch (err) {
2396 error.Canceled => |e| return e,2459 error.Canceled => |e| return e,
...@@ -2401,7 +2464,7 @@ fn makeStepNames(...@@ -2401,7 +2464,7 @@ fn makeStepNames(
2401 w.writeByte('\n') catch {};2464 w.writeByte('\n') catch {};
2402 }2465 }
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
2406 const code: u8 = code: {2469 const code: u8 = code: {
2407 if (failure_count == 0) break :code 0; // success2470 if (failure_count == 0) break :code 0; // success
lib/std/zig/Client.zig+38
...@@ -4,6 +4,7 @@ const std = @import("std");...@@ -4,6 +4,7 @@ const std = @import("std");
4const Io = std.Io;4const Io = std.Io;
5const Allocator = std.mem.Allocator;5const Allocator = std.mem.Allocator;
6const assert = std.debug.assert;6const assert = std.debug.assert;
7const Configuration = std.Build.Configuration;
7const OutMessage = std.zig.Client.Message;8const OutMessage = std.zig.Client.Message;
8const InMessage = std.zig.Server.Message;9const InMessage = std.zig.Server.Message;
9const Reader = Io.Reader;10const Reader = Io.Reader;
...@@ -60,9 +61,28 @@ pub const Message = struct {...@@ -60,9 +61,28 @@ pub const Message = struct {
60 /// The message body has the same format as in Server.61 /// The message body has the same format as in Server.
61 new_fuzz_input,62 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
63 _,69 _,
64 };70 };
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
66 comptime {86 comptime {
67 assert(@sizeOf(std.Build.abi.fuzz.LimitKind) == 1);87 assert(@sizeOf(std.Build.abi.fuzz.LimitKind) == 1);
68 }88 }
...@@ -140,3 +160,21 @@ pub fn serveRunFuzzTestMessage(...@@ -140,3 +160,21 @@ pub fn serveRunFuzzTestMessage(
140 }160 }
141 try c.out.flush();161 try c.out.flush();
142}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}
tools/bsp.zig+26-1
...@@ -143,7 +143,32 @@ pub fn main(init: std.process.Init) !void {...@@ -143,7 +143,32 @@ pub fn main(init: std.process.Init) !void {
143 if (std.mem.startsWith(u8, command, "build") or143 if (std.mem.startsWith(u8, command, "build") or
144 std.mem.startsWith(u8, command, "watch"))144 std.mem.startsWith(u8, command, "watch"))
145 {145 {
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;
147 } else if (std.mem.eql(u8, command, "exit")) {172 } else if (std.mem.eql(u8, command, "exit")) {
148 try client.serveBodylessMessage(.exit);173 try client.serveBodylessMessage(.exit);
149 break;174 break;