authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-02-16 15:04:10-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-15 10:48:12-07:00
logc5edd8b7f87f8432bbf058b3456393793118906e
tree318aeac422d4af88f4e5f5d2696c0b6caa52e1c4
parent7ebaa05bb138ea397859edf8ec96a9209482ae8e

std.Build: better handling of stderr of child processes

With this commit, the build runner now communicates progress towards completion of the step graph to the terminal, while also printing the stderr of child processes as soon as possible, without clobbering each other, and without clobbering the CLI progress output.

3 files changed, 86 insertions(+), 46 deletions(-)

lib/build_runner.zig+44-10
...@@ -243,14 +243,22 @@ pub fn main() !void {...@@ -243,14 +243,22 @@ pub fn main() !void {
243 }243 }
244 }244 }
245245
246 var progress: std.Progress = .{};
247 const main_progress_node = progress.start("", 0);
248 defer main_progress_node.end();
249
246 builder.debug_log_scopes = debug_log_scopes.items;250 builder.debug_log_scopes = debug_log_scopes.items;
247 builder.resolveInstallPrefix(install_prefix, dir_list);251 builder.resolveInstallPrefix(install_prefix, dir_list);
248 try builder.runBuild(root);252 {
253 var prog_node = main_progress_node.start("user build.zig logic", 0);
254 defer prog_node.end();
255 try builder.runBuild(root);
256 }
249257
250 if (builder.validateUserInputDidItFail())258 if (builder.validateUserInputDidItFail())
251 usageAndErr(builder, true, stderr_stream);259 usageAndErr(builder, true, stderr_stream);
252260
253 runStepNames(builder, targets.items) catch |err| {261 runStepNames(builder, targets.items, main_progress_node) catch |err| {
254 switch (err) {262 switch (err) {
255 error.UncleanExit => process.exit(1),263 error.UncleanExit => process.exit(1),
256 else => return err,264 else => return err,
...@@ -258,7 +266,11 @@ pub fn main() !void {...@@ -258,7 +266,11 @@ pub fn main() !void {
258 };266 };
259}267}
260268
261fn runStepNames(b: *std.Build, step_names: []const []const u8) !void {269fn runStepNames(
270 b: *std.Build,
271 step_names: []const []const u8,
272 parent_prog_node: *std.Progress.Node,
273) !void {
262 var step_stack = ArrayList(*Step).init(b.allocator);274 var step_stack = ArrayList(*Step).init(b.allocator);
263 defer step_stack.deinit();275 defer step_stack.deinit();
264276
...@@ -289,6 +301,9 @@ fn runStepNames(b: *std.Build, step_names: []const []const u8) !void {...@@ -289,6 +301,9 @@ fn runStepNames(b: *std.Build, step_names: []const []const u8) !void {
289 defer thread_pool.deinit();301 defer thread_pool.deinit();
290302
291 {303 {
304 var step_prog = parent_prog_node.start("run steps", step_stack.items.len);
305 defer step_prog.end();
306
292 var wait_group: std.Thread.WaitGroup = .{};307 var wait_group: std.Thread.WaitGroup = .{};
293 defer wait_group.wait();308 defer wait_group.wait();
294309
...@@ -301,8 +316,9 @@ fn runStepNames(b: *std.Build, step_names: []const []const u8) !void {...@@ -301,8 +316,9 @@ fn runStepNames(b: *std.Build, step_names: []const []const u8) !void {
301 const step = step_stack.items[i];316 const step = step_stack.items[i];
302317
303 wait_group.start();318 wait_group.start();
304 thread_pool.spawn(workerMakeOneStep, .{ &wait_group, &thread_pool, b, step }) catch319 thread_pool.spawn(workerMakeOneStep, .{
305 @panic("OOM");320 &wait_group, &thread_pool, b, step, &step_prog,
321 }) catch @panic("OOM");
306 }322 }
307 }323 }
308324
...@@ -312,14 +328,18 @@ fn runStepNames(b: *std.Build, step_names: []const []const u8) !void {...@@ -312,14 +328,18 @@ fn runStepNames(b: *std.Build, step_names: []const []const u8) !void {
312 switch (s.state) {328 switch (s.state) {
313 .precheck_unstarted => unreachable,329 .precheck_unstarted => unreachable,
314 .precheck_started => unreachable,330 .precheck_started => unreachable,
315 .precheck_done => unreachable,
316 .running => unreachable,331 .running => unreachable,
317 .dependency_failure => continue,332 // precheck_done is equivalent to dependency_failure in the case of
333 // transitive dependencies. For example:
334 // A -> B -> C (failure)
335 // B will be marked as dependency_failure, while A may never be queued, and thus
336 // remain in the initial state of precheck_done.
337 .dependency_failure, .precheck_done => continue,
318 .success => continue,338 .success => continue,
319 .failure => {339 .failure => {
320 any_failed = true;340 any_failed = true;
321 std.debug.print("{s}: {s}\n{s}", .{341 std.debug.print("{s}: {s}\n", .{
322 s.name, @errorName(s.result.err_code), s.result.stderr,342 s.name, @errorName(s.result.err_code),
323 });343 });
324 },344 },
325 }345 }
...@@ -371,6 +391,7 @@ fn workerMakeOneStep(...@@ -371,6 +391,7 @@ fn workerMakeOneStep(
371 thread_pool: *std.Thread.Pool,391 thread_pool: *std.Thread.Pool,
372 b: *std.Build,392 b: *std.Build,
373 s: *Step,393 s: *Step,
394 prog_node: *std.Progress.Node,
374) void {395) void {
375 defer wg.finish();396 defer wg.finish();
376397
...@@ -399,6 +420,10 @@ fn workerMakeOneStep(...@@ -399,6 +420,10 @@ fn workerMakeOneStep(
399 return;420 return;
400 }421 }
401422
423 var sub_prog_node = prog_node.start(s.name, 0);
424 sub_prog_node.activate();
425 defer sub_prog_node.end();
426
402 // I suspect we will want to pass `b` to make() in a future modification.427 // I suspect we will want to pass `b` to make() in a future modification.
403 // For example, CompileStep does some sus things with modifying the saved428 // For example, CompileStep does some sus things with modifying the saved
404 // *Build object in install header steps that might be able to be removed429 // *Build object in install header steps that might be able to be removed
...@@ -406,6 +431,13 @@ fn workerMakeOneStep(...@@ -406,6 +431,13 @@ fn workerMakeOneStep(
406 s.make() catch |err| {431 s.make() catch |err| {
407 s.result.err_code = err;432 s.result.err_code = err;
408 @atomicStore(Step.State, &s.state, .failure, .SeqCst);433 @atomicStore(Step.State, &s.state, .failure, .SeqCst);
434
435 sub_prog_node.context.lock_stderr();
436 defer sub_prog_node.context.unlock_stderr();
437
438 for (s.result.error_msgs.items) |msg| {
439 std.io.getStdErr().writeAll(msg) catch return;
440 }
409 return;441 return;
410 };442 };
411443
...@@ -414,7 +446,9 @@ fn workerMakeOneStep(...@@ -414,7 +446,9 @@ fn workerMakeOneStep(
414 // Successful completion of a step, so we queue up its dependants as well.446 // Successful completion of a step, so we queue up its dependants as well.
415 for (s.dependants.items) |dep| {447 for (s.dependants.items) |dep| {
416 wg.start();448 wg.start();
417 thread_pool.spawn(workerMakeOneStep, .{ wg, thread_pool, b, dep }) catch @panic("OOM");449 thread_pool.spawn(workerMakeOneStep, .{
450 wg, thread_pool, b, dep, prog_node,
451 }) catch @panic("OOM");
418 }452 }
419}453}
420454
lib/std/Build.zig+18-34
...@@ -1417,59 +1417,43 @@ pub fn execFromStep(b: *Build, argv: []const []const u8, s: *Step) ![]u8 {...@@ -1417,59 +1417,43 @@ pub fn execFromStep(b: *Build, argv: []const []const u8, s: *Step) ![]u8 {
1417 }1417 }
14181418
1419 if (!process.can_spawn) {1419 if (!process.can_spawn) {
1420 s.result.stderr = b.fmt("Unable to spawn the following command: cannot spawn child processes\n{s}", .{1420 try s.result.error_msgs.append(b.allocator, b.fmt("Unable to spawn the following command: cannot spawn child processes\n{s}", .{
1421 try allocPrintCmd(b.allocator, null, argv),1421 try allocPrintCmd(b.allocator, null, argv),
1422 });1422 }));
1423 return error.CannotSpawnProcesses;1423 return error.CannotSpawnProcesses;
1424 }1424 }
14251425
1426 var code: u8 = undefined;1426 const result = std.ChildProcess.exec(.{
1427 const result = unwrapExecResult(&code, std.ChildProcess.exec(.{
1428 .allocator = b.allocator,1427 .allocator = b.allocator,
1429 .argv = argv,1428 .argv = argv,
1430 .env_map = b.env_map,1429 .env_map = b.env_map,
1431 .max_output_bytes = 10 * 1024 * 1024,1430 .max_output_bytes = 10 * 1024 * 1024,
1432 })) catch |err| switch (err) {1431 }) catch |err| {
1433 error.FileNotFound => {1432 try s.result.error_msgs.append(b.allocator, b.fmt("unable to spawn the following command: {s}\n{s}", .{
1434 s.result.stderr = b.fmt("unable to spawn the following command: file not found\n{s}", .{1433 @errorName(err), try allocPrintCmd(b.allocator, null, argv),
1435 try allocPrintCmd(b.allocator, null, argv),1434 }));
1436 });1435 return error.ExecFailed;
1437 return error.ExecFailed;
1438 },
1439 error.ExitCodeFailure => {
1440 s.result.stderr = b.fmt("the following command exited with error code {d}:\n{s}", .{
1441 code, try allocPrintCmd(b.allocator, null, argv),
1442 });
1443 return error.ExecFailed;
1444 },
1445 error.ProcessTerminated => {
1446 s.result.stderr = b.fmt("the following command terminated unexpectedly:\n{s}", .{
1447 try allocPrintCmd(b.allocator, null, argv),
1448 });
1449 return error.ExecFailed;
1450 },
1451 else => |e| return e,
1452 };1436 };
14531437
1454 s.result.stderr = result.stderr;1438 if (result.stderr.len != 0) {
1455 return result.stdout;1439 try s.result.error_msgs.append(b.allocator, result.stderr);
1456}1440 }
14571441
1458fn unwrapExecResult(
1459 code_ptr: *u8,
1460 wrapped: std.ChildProcess.ExecError!std.ChildProcess.ExecResult,
1461) !std.ChildProcess.ExecResult {
1462 const result = try wrapped;
1463 switch (result.term) {1442 switch (result.term) {
1464 .Exited => |code| {1443 .Exited => |code| {
1465 code_ptr.* = code;
1466 if (code != 0) {1444 if (code != 0) {
1445 try s.result.error_msgs.append(b.allocator, b.fmt("the following command exited with error code {d}:\n{s}", .{
1446 code, try allocPrintCmd(b.allocator, null, argv),
1447 }));
1467 return error.ExitCodeFailure;1448 return error.ExitCodeFailure;
1468 }1449 }
1469 return result;1450 return result.stdout;
1470 },1451 },
1471 .Signal, .Stopped, .Unknown => |code| {1452 .Signal, .Stopped, .Unknown => |code| {
1472 _ = code;1453 _ = code;
1454 try s.result.error_msgs.append(b.allocator, b.fmt("the following command terminated unexpectedly:\n{s}", .{
1455 try allocPrintCmd(b.allocator, null, argv),
1456 }));
1473 return error.ProcessTerminated;1457 return error.ProcessTerminated;
1474 },1458 },
1475 }1459 }
lib/std/Build/Step.zig+24-2
...@@ -9,7 +9,7 @@ state: State,...@@ -9,7 +9,7 @@ state: State,
9/// Populated only if state is success.9/// Populated only if state is success.
10result: struct {10result: struct {
11 err_code: anyerror,11 err_code: anyerror,
12 stderr: []u8,12 error_msgs: std.ArrayListUnmanaged([]const u8),
13},13},
14/// The return addresss associated with creation of this step that can be useful14/// The return addresss associated with creation of this step that can be useful
15/// to print along with debugging messages.15/// to print along with debugging messages.
...@@ -96,7 +96,7 @@ pub fn init(allocator: Allocator, options: Options) Step {...@@ -96,7 +96,7 @@ pub fn init(allocator: Allocator, options: Options) Step {
96 .state = .precheck_unstarted,96 .state = .precheck_unstarted,
97 .result = .{97 .result = .{
98 .err_code = undefined,98 .err_code = undefined,
99 .stderr = &.{},99 .error_msgs = .{},
100 },100 },
101 .debug_stack_trace = addresses,101 .debug_stack_trace = addresses,
102 };102 };
...@@ -133,6 +133,28 @@ pub fn cast(step: *Step, comptime T: type) ?*T {...@@ -133,6 +133,28 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
133 return null;133 return null;
134}134}
135135
136/// For debugging purposes, prints identifying information about this Step.
137pub fn dump(step: *Step) void {
138 std.debug.getStderrMutex().lock();
139 defer std.debug.getStderrMutex().unlock();
140
141 const stderr = std.io.getStdErr();
142 const w = stderr.writer();
143 const tty_config = std.debug.detectTTYConfig(stderr);
144 const debug_info = std.debug.getSelfDebugInfo() catch |err| {
145 w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{
146 @errorName(err),
147 }) catch {};
148 return;
149 };
150 const ally = debug_info.allocator;
151 w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};
152 std.debug.writeStackTrace(step.getStackTrace(), w, ally, debug_info, tty_config) catch |err| {
153 stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch {};
154 return;
155 };
156}
157
136const Step = @This();158const Step = @This();
137const std = @import("../std.zig");159const std = @import("../std.zig");
138const Build = std.Build;160const Build = std.Build;