diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 5df77014b5..668fc1ac20 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -103,12 +103,13 @@ pub fn main() !void { var install_prefix: ?[]const u8 = null; var dir_list = std.Build.DirList{}; + var error_style: ErrorStyle = .verbose; + var multiline_errors: MultilineErrors = .indent; var summary: ?Summary = null; var max_rss: u64 = 0; var skip_oom_steps = false; var test_timeout_ms: ?u64 = null; var color: Color = .auto; - var prominent_compile_errors = false; var help_menu = false; var steps_menu = false; var output_tmp_nonce: ?[16]u8 = null; @@ -117,6 +118,18 @@ pub fn main() !void { var debounce_interval_ms: u16 = 50; var webui_listen: ?std.net.Address = null; + if (try std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(arena)) |str| { + if (std.meta.stringToEnum(ErrorStyle, str)) |style| { + error_style = style; + } + } + + if (try std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(arena)) |str| { + if (std.meta.stringToEnum(MultilineErrors, str)) |style| { + multiline_errors = style; + } + } + while (nextArg(args, &arg_idx)) |arg| { if (mem.startsWith(u8, arg, "-Z")) { if (arg.len != 18) fatalWithHint("bad argument: '{s}'", .{arg}); @@ -197,11 +210,23 @@ pub fn main() !void { arg, next_arg, }); }; + } else if (mem.eql(u8, arg, "--error-style")) { + const next_arg = nextArg(args, &arg_idx) orelse + fatalWithHint("expected style after '{s}'", .{arg}); + error_style = std.meta.stringToEnum(ErrorStyle, next_arg) orelse { + fatalWithHint("expected style after '{s}', found '{s}'", .{ arg, next_arg }); + }; + } else if (mem.eql(u8, arg, "--multiline-errors")) { + const next_arg = nextArg(args, &arg_idx) orelse + fatalWithHint("expected style after '{s}'", .{arg}); + multiline_errors = std.meta.stringToEnum(MultilineErrors, next_arg) orelse { + fatalWithHint("expected style after '{s}', found '{s}'", .{ arg, next_arg }); + }; } else if (mem.eql(u8, arg, "--summary")) { const next_arg = nextArg(args, &arg_idx) orelse - fatalWithHint("expected [all|new|failures|none] after '{s}'", .{arg}); + fatalWithHint("expected [all|new|failures|line|none] after '{s}'", .{arg}); summary = std.meta.stringToEnum(Summary, next_arg) orelse { - fatalWithHint("expected [all|new|failures|none] after '{s}', found '{s}'", .{ + fatalWithHint("expected [all|new|failures|line|none] after '{s}', found '{s}'", .{ arg, next_arg, }); }; @@ -273,8 +298,6 @@ pub fn main() !void { builder.verbose_cc = true; } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) { builder.verbose_llvm_cpu_features = true; - } else if (mem.eql(u8, arg, "--prominent-compile-errors")) { - prominent_compile_errors = true; } else if (mem.eql(u8, arg, "--watch")) { watch = true; } else if (mem.eql(u8, arg, "--time-report")) { @@ -466,10 +489,11 @@ pub fn main() !void { .web_server = undefined, // set after `prepare` .memory_blocked_steps = .empty, .step_stack = .empty, - .prominent_compile_errors = prominent_compile_errors, .claimed_rss = 0, - .summary = summary orelse if (watch) .new else .failures, + .error_style = error_style, + .multiline_errors = multiline_errors, + .summary = summary orelse if (watch or webui_listen != null) .line else .failures, .ttyconf = ttyconf, .stderr = stderr, .thread_pool = undefined, @@ -485,8 +509,14 @@ pub fn main() !void { } prepare(arena, builder, targets.items, &run, graph.random_seed) catch |err| switch (err) { - error.UncleanExit => process.exit(1), - else => return err, + error.DependencyLoopDetected => { + // Perhaps in the future there could be an Advanced Options flag such as + // --debug-build-runner-leaks which would make this code return instead of + // calling exit. + std.debug.lockStdErr(); + process.exit(1); + }, + else => |e| return e, }; var w: Watch = w: { @@ -516,22 +546,20 @@ pub fn main() !void { ws.start() catch |err| fatal("failed to start web server: {s}", .{@errorName(err)}); } - rebuild: while (true) { + rebuild: while (true) : (if (run.error_style.clearOnUpdate()) { + const bw = std.debug.lockStderrWriter(&stdio_buffer_allocation); + defer std.debug.unlockStderrWriter(); + try bw.writeAll("\x1B[2J\x1B[3J\x1B[H"); + }) { if (run.web_server) |*ws| ws.startBuild(); - runStepNames( + try runStepNames( builder, targets.items, main_progress_node, &run, fuzz, - ) catch |err| switch (err) { - error.UncleanExit => { - assert(!run.watch and run.web_server == null); - process.exit(1); - }, - else => return err, - }; + ); if (run.web_server) |*web_server| { if (fuzz) |mode| if (mode != .forever) fatal( @@ -542,10 +570,6 @@ pub fn main() !void { web_server.finishBuild(.{ .fuzz = fuzz != null }); } - if (!watch and run.web_server == null) { - return cleanExit(); - } - if (run.web_server) |*ws| { assert(!watch); // fatal error after CLI parsing while (true) switch (ws.wait()) { @@ -626,18 +650,14 @@ const Run = struct { memory_blocked_steps: std.ArrayListUnmanaged(*Step), /// Allocated into `gpa`. step_stack: std.AutoArrayHashMapUnmanaged(*Step, void), - prominent_compile_errors: bool, thread_pool: std.Thread.Pool, claimed_rss: usize, + error_style: ErrorStyle, + multiline_errors: MultilineErrors, summary: Summary, ttyconf: tty.Config, stderr: File, - - fn cleanExit(run: Run) void { - if (run.watch or run.web_server != null) return; - return runner.cleanExit(); - } }; fn prepare( @@ -671,10 +691,7 @@ fn prepare( rand.shuffle(*Step, starting_steps); for (starting_steps) |s| { - constructGraphAndCheckForDependencyLoop(gpa, b, s, &run.step_stack, rand) catch |err| switch (err) { - error.DependencyLoopDetected => return uncleanExit(), - else => |e| return e, - }; + try constructGraphAndCheckForDependencyLoop(gpa, b, s, &run.step_stack, rand); } { @@ -827,26 +844,25 @@ fn runStepNames( // Every test has a state assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count); - // A proper command line application defaults to silently succeeding. - // The user may request verbose mode if they have a different preference. - const failures_only = switch (run.summary) { - .failures, .none => true, - else => false, - }; if (failure_count == 0) { std.Progress.setStatus(.success); - if (failures_only) return run.cleanExit(); } else { std.Progress.setStatus(.failure); } - if (run.summary != .none) { + summary: { + switch (run.summary) { + .all, .new, .line => {}, + .failures => if (failure_count == 0) break :summary, + .none => break :summary, + } + const w = std.debug.lockStderrWriter(&stdio_buffer_allocation); defer std.debug.unlockStderrWriter(); const total_count = success_count + failure_count + pending_count + skipped_count; ttyconf.setColor(w, .cyan) catch {}; - w.writeAll("\nBuild Summary:") catch {}; + w.writeAll("Build Summary:") catch {}; ttyconf.setColor(w, .reset) catch {}; w.print(" {d}/{d} steps succeeded", .{ success_count, total_count }) catch {}; if (skipped_count > 0) w.print(", {d} skipped", .{skipped_count}) catch {}; @@ -862,6 +878,8 @@ fn runStepNames( w.writeAll("\n") catch {}; + if (run.summary == .line) break :summary; + // Print a fancy tree with build results. var step_stack_copy = try step_stack.clone(gpa); defer step_stack_copy.deinit(gpa); @@ -877,7 +895,7 @@ fn runStepNames( i -= 1; const step = b.top_level_steps.get(step_names[i]).?.step; const found = switch (run.summary) { - .all, .none => unreachable, + .all, .line, .none => unreachable, .failures => step.state != .success, .new => !step.result_cached, }; @@ -894,28 +912,19 @@ fn runStepNames( w.writeByte('\n') catch {}; } - if (failure_count == 0) { - return run.cleanExit(); - } + if (run.watch or run.web_server != null) return; - // Finally, render compile errors at the bottom of the terminal. - if (run.prominent_compile_errors and total_compile_errors > 0) { - for (step_stack.keys()) |s| { - if (s.result_error_bundle.errorMessageCount() > 0) { - s.result_error_bundle.renderToStdErr(.{ .ttyconf = ttyconf }); - } - } + // Perhaps in the future there could be an Advanced Options flag such as + // --debug-build-runner-leaks which would make this code return instead of + // calling exit. - if (!run.watch and run.web_server == null) { - // Signal to parent process that we have printed compile errors. The - // parent process may choose to omit the "following command failed" - // line in this case. - std.debug.lockStdErr(); - process.exit(2); - } - } - - if (!run.watch and run.web_server == null) return uncleanExit(); + const code: u8 = code: { + if (failure_count == 0) break :code 0; // success + if (run.error_style.verboseContext()) break :code 1; // failure; print build command + break :code 2; // failure; do not print build command + }; + std.debug.lockStdErr(); + process.exit(code); } const PrintNode = struct { @@ -1124,7 +1133,7 @@ fn printTreeStep( const first = step_stack.swapRemove(s); const summary = run.summary; const skip = switch (summary) { - .none => unreachable, + .none, .line => unreachable, .all => false, .new => s.result_cached, .failures => s.state == .success, @@ -1157,7 +1166,7 @@ fn printTreeStep( const step = s.dependencies.items[i]; const found = switch (summary) { - .all, .none => unreachable, + .all, .line, .none => unreachable, .failures => step.state != .success, .new => !step.result_cached, }; @@ -1316,15 +1325,13 @@ fn workerMakeOneStep( }); // No matter the result, we want to display error/warning messages. - const show_compile_errors = !run.prominent_compile_errors and - s.result_error_bundle.errorMessageCount() > 0; + const show_compile_errors = s.result_error_bundle.errorMessageCount() > 0; const show_error_msgs = s.result_error_msgs.items.len > 0; const show_stderr = s.result_stderr.len > 0; - if (show_error_msgs or show_compile_errors or show_stderr) { const bw = std.debug.lockStderrWriter(&stdio_buffer_allocation); defer std.debug.unlockStderrWriter(); - printErrorMessages(run.gpa, s, .{ .ttyconf = run.ttyconf }, bw, run.prominent_compile_errors) catch {}; + printErrorMessages(run.gpa, s, .{ .ttyconf = run.ttyconf }, bw, run.error_style, run.multiline_errors) catch {}; } handle_result: { @@ -1388,37 +1395,46 @@ pub fn printErrorMessages( failing_step: *Step, options: std.zig.ErrorBundle.RenderOptions, stderr: *Writer, - prominent_compile_errors: bool, + error_style: ErrorStyle, + multiline_errors: MultilineErrors, ) !void { - // Provide context for where these error messages are coming from by - // printing the corresponding Step subtree. - - var step_stack: std.ArrayListUnmanaged(*Step) = .empty; - defer step_stack.deinit(gpa); - try step_stack.append(gpa, failing_step); - while (step_stack.items[step_stack.items.len - 1].dependants.items.len != 0) { - try step_stack.append(gpa, step_stack.items[step_stack.items.len - 1].dependants.items[0]); - } - - // Now, `step_stack` has the subtree that we want to print, in reverse order. const ttyconf = options.ttyconf; - try ttyconf.setColor(stderr, .dim); - var indent: usize = 0; - while (step_stack.pop()) |s| : (indent += 1) { - if (indent > 0) { - try stderr.splatByteAll(' ', (indent - 1) * 3); - try printChildNodePrefix(stderr, ttyconf); + + if (error_style.verboseContext()) { + // Provide context for where these error messages are coming from by + // printing the corresponding Step subtree. + var step_stack: std.ArrayListUnmanaged(*Step) = .empty; + defer step_stack.deinit(gpa); + try step_stack.append(gpa, failing_step); + while (step_stack.items[step_stack.items.len - 1].dependants.items.len != 0) { + try step_stack.append(gpa, step_stack.items[step_stack.items.len - 1].dependants.items[0]); } - try stderr.writeAll(s.name); + // Now, `step_stack` has the subtree that we want to print, in reverse order. + try ttyconf.setColor(stderr, .dim); + var indent: usize = 0; + while (step_stack.pop()) |s| : (indent += 1) { + if (indent > 0) { + try stderr.splatByteAll(' ', (indent - 1) * 3); + try printChildNodePrefix(stderr, ttyconf); + } - if (s == failing_step) { - try printStepFailure(s, stderr, ttyconf); - } else { - try stderr.writeAll("\n"); + try stderr.writeAll(s.name); + + if (s == failing_step) { + try printStepFailure(s, stderr, ttyconf); + } else { + try stderr.writeAll("\n"); + } } + try ttyconf.setColor(stderr, .reset); + } else { + // Just print the failing step itself. + try ttyconf.setColor(stderr, .dim); + try stderr.writeAll(failing_step.name); + try printStepFailure(failing_step, stderr, ttyconf); + try ttyconf.setColor(stderr, .reset); } - try ttyconf.setColor(stderr, .reset); if (failing_step.result_stderr.len > 0) { try stderr.writeAll(failing_step.result_stderr); @@ -1427,30 +1443,38 @@ pub fn printErrorMessages( } } - if (!prominent_compile_errors and failing_step.result_error_bundle.errorMessageCount() > 0) { - try failing_step.result_error_bundle.renderToWriter(options, stderr); - } + try failing_step.result_error_bundle.renderToWriter(options, stderr); for (failing_step.result_error_msgs.items) |msg| { try ttyconf.setColor(stderr, .red); - try stderr.writeAll("error: "); + try stderr.writeAll("error:"); try ttyconf.setColor(stderr, .reset); - // If the message has multiple lines, indent the non-initial ones to align them with the 'error:' text. - var it = std.mem.splitScalar(u8, msg, '\n'); - try stderr.writeAll(it.first()); - while (it.next()) |line| { - try stderr.print("\n {s}", .{line}); + if (std.mem.indexOfScalar(u8, msg, '\n') == null) { + try stderr.print(" {s}\n", .{msg}); + } else switch (multiline_errors) { + .indent => { + var it = std.mem.splitScalar(u8, msg, '\n'); + try stderr.print(" {s}\n", .{it.first()}); + while (it.next()) |line| { + try stderr.print(" {s}\n", .{line}); + } + }, + .newline => try stderr.print("\n{s}\n", .{msg}), + .none => try stderr.print(" {s}\n", .{msg}), } - try stderr.writeAll("\n"); } - if (failing_step.result_failed_command) |cmd_str| { - try ttyconf.setColor(stderr, .red); - try stderr.writeAll("failed command: "); - try ttyconf.setColor(stderr, .reset); - try stderr.writeAll(cmd_str); - try stderr.writeByte('\n'); + if (error_style.verboseContext()) { + if (failing_step.result_failed_command) |cmd_str| { + try ttyconf.setColor(stderr, .red); + try stderr.writeAll("failed command: "); + try ttyconf.setColor(stderr, .reset); + try stderr.writeAll(cmd_str); + try stderr.writeByte('\n'); + } } + + try stderr.writeByte('\n'); } fn printSteps(builder: *std.Build, w: *Writer) !void { @@ -1505,11 +1529,20 @@ fn printUsage(b: *std.Build, w: *Writer) !void { \\ -l, --list-steps Print available steps \\ --verbose Print commands before executing them \\ --color [auto|off|on] Enable or disable colored error messages - \\ --prominent-compile-errors Buffer compile errors and display at end + \\ --error-style [style] Control how build errors are printed + \\ verbose (Default) Report errors with full context + \\ minimal Report errors after summary, excluding context like command lines + \\ verbose_clear Like 'verbose', but clear the terminal at the start of each update + \\ minimal_clear Like 'minimal', but clear the terminal at the start of each update + \\ --multiline-errors [style] Control how multi-line error messages are printed + \\ indent (Default) Indent non-initial lines to align with initial line + \\ newline Include a leading newline so that the error message is on its own lines + \\ none Print as usual so the first line is misaligned \\ --summary [mode] Control the printing of the build summary \\ all Print the build summary in its entirety \\ new Omit cached steps - \\ failures (Default) Only print failed steps + \\ failures (Default if short-lived) Only print failed steps + \\ line (Default if long-lived) Only print the single-line summary \\ none Do not print the build summary \\ -j Limit concurrent jobs (default is to use all CPU cores) \\ --maxrss Limit memory usage (default is to use available memory) @@ -1633,24 +1666,27 @@ fn argsRest(args: []const [:0]const u8, idx: usize) ?[]const [:0]const u8 { return args[idx..]; } -/// Perhaps in the future there could be an Advanced Options flag such as -/// --debug-build-runner-leaks which would make this function return instead of -/// calling exit. -fn cleanExit() void { - std.debug.lockStdErr(); - process.exit(0); -} - -/// Perhaps in the future there could be an Advanced Options flag such as -/// --debug-build-runner-leaks which would make this function return instead of -/// calling exit. -fn uncleanExit() error{UncleanExit} { - std.debug.lockStdErr(); - process.exit(1); -} - const Color = std.zig.Color; -const Summary = enum { all, new, failures, none }; +const ErrorStyle = enum { + verbose, + minimal, + verbose_clear, + minimal_clear, + fn verboseContext(s: ErrorStyle) bool { + return switch (s) { + .verbose, .verbose_clear => true, + .minimal, .minimal_clear => false, + }; + } + fn clearOnUpdate(s: ErrorStyle) bool { + return switch (s) { + .verbose, .minimal => false, + .verbose_clear, .minimal_clear => true, + }; + } +}; +const MultilineErrors = enum { indent, newline, none }; +const Summary = enum { all, new, failures, line, none }; fn get_tty_conf(color: Color, stderr: File) tty.Config { return switch (color) { diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index 27c2f35d72..8281fc4726 100644 --- a/lib/std/Build/Fuzz.zig +++ b/lib/std/Build/Fuzz.zig @@ -178,7 +178,7 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, ttyconf: std.Io var buf: [256]u8 = undefined; const w = std.debug.lockStderrWriter(&buf); defer std.debug.unlockStderrWriter(); - build_runner.printErrorMessages(gpa, &compile.step, .{ .ttyconf = ttyconf }, w, false) catch {}; + build_runner.printErrorMessages(gpa, &compile.step, .{ .ttyconf = ttyconf }, w, .verbose, .indent) catch {}; } const rebuilt_bin_path = result catch |err| switch (err) { @@ -204,7 +204,7 @@ fn fuzzWorkerRun( var buf: [256]u8 = undefined; const w = std.debug.lockStderrWriter(&buf); defer std.debug.unlockStderrWriter(); - build_runner.printErrorMessages(gpa, &run.step, .{ .ttyconf = fuzz.ttyconf }, w, false) catch {}; + build_runner.printErrorMessages(gpa, &run.step, .{ .ttyconf = fuzz.ttyconf }, w, .verbose, .indent) catch {}; return; }, else => { diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 9fc64460ec..dfae03dea7 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -697,6 +697,8 @@ pub const EnvVar = enum { ZIG_LIB_DIR, ZIG_LIBC, ZIG_BUILD_RUNNER, + ZIG_BUILD_ERROR_STYLE, + ZIG_BUILD_MULTILINE_ERRORS, ZIG_VERBOSE_LINK, ZIG_VERBOSE_CC, ZIG_BTRFS_WORKAROUND,