diff --git a/lib/build_runner.zig b/lib/build_runner.zig index bb04b3e132e9..8e0ebb455898 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -84,8 +84,6 @@ pub fn main() !void { ); defer builder.destroy(); - const Color = enum { auto, off, on }; - var targets = ArrayList([]const u8).init(arena); var debug_log_scopes = ArrayList([]const u8).init(arena); var thread_pool_options: std.Thread.Pool.Options = .{ .allocator = arena }; @@ -273,13 +271,9 @@ pub fn main() !void { } const stderr = std.io.getStdErr(); - const ttyconf: std.debug.TTY.Config = switch (color) { - .auto => std.debug.detectTTYConfig(stderr), - .on => .escape_codes, - .off => .no_color, - }; + const ttyconf = get_tty_conf(color, stderr); - var progress: std.Progress = .{}; + var progress: std.Progress = .{ .dont_print_on_dumb = true }; const main_progress_node = progress.start("", 0); builder.debug_log_scopes = debug_log_scopes.items; @@ -498,7 +492,7 @@ fn runStepNames( if (total_compile_errors > 0) { for (compile_error_steps.items) |s| { if (s.result_error_bundle.errorMessageCount() > 0) { - s.result_error_bundle.renderToStdErr(ttyconf); + s.result_error_bundle.renderToStdErr(renderOptions(ttyconf)); } } @@ -961,3 +955,21 @@ fn cleanExit() void { // of calling exit. process.exit(0); } + +const Color = enum { auto, off, on }; + +fn get_tty_conf(color: Color, stderr: std.fs.File) std.debug.TTY.Config { + return switch (color) { + .auto => std.debug.detectTTYConfig(stderr), + .on => .escape_codes, + .off => .no_color, + }; +} + +fn renderOptions(ttyconf: std.debug.TTY.Config) std.zig.ErrorBundle.RenderOptions { + return .{ + .ttyconf = ttyconf, + .include_source_line = ttyconf != .no_color, + .include_reference_trace = ttyconf != .no_color, + }; +} diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index 42cc63e8b436..5c44ab82d339 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -207,6 +207,12 @@ want_lto: ?bool = null, use_llvm: ?bool = null, use_lld: ?bool = null, +/// This is an advanced setting that can change the intent of this CompileStep. +/// If this slice has nonzero length, it means that this CompileStep exists to +/// check for compile errors and return *success* if they match, and failure +/// otherwise. +expect_errors: []const []const u8 = &.{}, + output_path_source: GeneratedFile, output_lib_path_source: GeneratedFile, output_h_path_source: GeneratedFile, @@ -552,8 +558,8 @@ pub fn run(cs: *CompileStep) *RunStep { return cs.step.owner.addRunArtifact(cs); } -pub fn checkObject(self: *CompileStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep { - return CheckObjectStep.create(self.step.owner, self.getOutputSource(), obj_format); +pub fn checkObject(self: *CompileStep) *CheckObjectStep { + return CheckObjectStep.create(self.step.owner, self.getOutputSource(), self.target_info.target.ofmt); } pub fn setLinkerScriptPath(self: *CompileStep, source: FileSource) void { @@ -1838,14 +1844,38 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } for (b.search_prefixes.items) |search_prefix| { - try zig_args.append("-L"); - try zig_args.append(b.pathJoin(&.{ - search_prefix, "lib", - })); - try zig_args.append("-I"); - try zig_args.append(b.pathJoin(&.{ - search_prefix, "include", - })); + var prefix_dir = fs.cwd().openDir(search_prefix, .{}) catch |err| { + return step.fail("unable to open prefix directory '{s}': {s}", .{ + search_prefix, @errorName(err), + }); + }; + defer prefix_dir.close(); + + // Avoid passing -L and -I flags for nonexistent directories. + // This prevents a warning, that should probably be upgraded to an error in Zig's + // CLI parsing code, when the linker sees an -L directory that does not exist. + + if (prefix_dir.accessZ("lib", .{})) |_| { + try zig_args.appendSlice(&.{ + "-L", try fs.path.join(b.allocator, &.{ search_prefix, "lib" }), + }); + } else |err| switch (err) { + error.FileNotFound => {}, + else => |e| return step.fail("unable to access '{s}/lib' directory: {s}", .{ + search_prefix, @errorName(e), + }), + } + + if (prefix_dir.accessZ("include", .{})) |_| { + try zig_args.appendSlice(&.{ + "-I", try fs.path.join(b.allocator, &.{ search_prefix, "include" }), + }); + } else |err| switch (err) { + error.FileNotFound => {}, + else => |e| return step.fail("unable to access '{s}/include' directory: {s}", .{ + search_prefix, @errorName(e), + }), + } } try addFlag(&zig_args, "valgrind", self.valgrind_support); @@ -1943,7 +1973,14 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append(resolved_args_file); } - const output_bin_path = try step.evalZigProcess(zig_args.items, prog_node); + const output_bin_path = step.evalZigProcess(zig_args.items, prog_node) catch |err| switch (err) { + error.NeedCompileErrorCheck => { + assert(self.expect_errors.len != 0); + try checkCompileErrors(self); + return; + }, + else => |e| return e, + }; const build_output_dir = fs.path.dirname(output_bin_path).?; if (self.output_dir) |output_dir| { @@ -2178,3 +2215,57 @@ const TransitiveDeps = struct { } } }; + +fn checkCompileErrors(self: *CompileStep) !void { + // Clear this field so that it does not get printed by the build runner. + const actual_eb = self.step.result_error_bundle; + self.step.result_error_bundle = std.zig.ErrorBundle.empty; + + const arena = self.step.owner.allocator; + + var actual_stderr_list = std.ArrayList(u8).init(arena); + try actual_eb.renderToWriter(.{ + .ttyconf = .no_color, + .include_reference_trace = false, + .include_source_line = false, + }, actual_stderr_list.writer()); + const actual_stderr = try actual_stderr_list.toOwnedSlice(); + + // Render the expected lines into a string that we can compare verbatim. + var expected_generated = std.ArrayList(u8).init(arena); + + var actual_line_it = mem.split(u8, actual_stderr, "\n"); + for (self.expect_errors) |expect_line| { + const actual_line = actual_line_it.next() orelse { + try expected_generated.appendSlice(expect_line); + try expected_generated.append('\n'); + continue; + }; + if (mem.endsWith(u8, actual_line, expect_line)) { + try expected_generated.appendSlice(actual_line); + try expected_generated.append('\n'); + continue; + } + if (mem.startsWith(u8, expect_line, ":?:?: ")) { + if (mem.endsWith(u8, actual_line, expect_line[":?:?: ".len..])) { + try expected_generated.appendSlice(actual_line); + try expected_generated.append('\n'); + continue; + } + } + try expected_generated.appendSlice(expect_line); + try expected_generated.append('\n'); + } + + if (mem.eql(u8, expected_generated.items, actual_stderr)) return; + + // TODO merge this with the testing.expectEqualStrings logic, and also CheckFile + return self.step.fail( + \\ + \\========= expected: ===================== + \\{s} + \\========= but found: ==================== + \\{s} + \\========================================= + , .{ expected_generated.items, actual_stderr }); +} diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index f1edab58815b..45aa635972d7 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -295,8 +295,8 @@ pub fn evalZigProcess( var node_name: std.ArrayListUnmanaged(u8) = .{}; defer node_name.deinit(gpa); - var sub_prog_node: ?std.Progress.Node = null; - defer if (sub_prog_node) |*n| n.end(); + var sub_prog_node = prog_node.start("", 0); + defer sub_prog_node.end(); const stdout = poller.fifo(.stdout); @@ -336,11 +336,9 @@ pub fn evalZigProcess( }; }, .progress => { - if (sub_prog_node) |*n| n.end(); node_name.clearRetainingCapacity(); try node_name.appendSlice(gpa, body); - sub_prog_node = prog_node.start(node_name.items, 0); - sub_prog_node.?.activate(); + sub_prog_node.setName(node_name.items); }, .emit_bin_path => { const EbpHdr = std.zig.Server.Message.EmitBinPath; @@ -371,6 +369,18 @@ pub fn evalZigProcess( s.result_duration_ns = timer.read(); s.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0; + // Special handling for CompileStep that is expecting compile errors. + if (s.cast(Build.CompileStep)) |compile| switch (term) { + .Exited => { + // Note that the exit code may be 0 in this case due to the + // compiler server protocol. + if (compile.expect_errors.len != 0 and s.result_error_bundle.errorMessageCount() > 0) { + return error.NeedCompileErrorCheck; + } + }, + else => {}, + }; + try handleChildProcessTerm(s, term, null, argv); if (s.result_error_bundle.errorMessageCount() > 0) { diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index 94f80797a831..845e9d8ff511 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -141,32 +141,35 @@ pub fn nullTerminatedString(eb: ErrorBundle, index: usize) [:0]const u8 { return string_bytes[index..end :0]; } -pub fn renderToStdErr(eb: ErrorBundle, ttyconf: std.debug.TTY.Config) void { +pub const RenderOptions = struct { + ttyconf: std.debug.TTY.Config, + include_reference_trace: bool = true, + include_source_line: bool = true, +}; + +pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions) void { std.debug.getStderrMutex().lock(); defer std.debug.getStderrMutex().unlock(); const stderr = std.io.getStdErr(); - return renderToWriter(eb, ttyconf, stderr.writer()) catch return; + return renderToWriter(eb, options, stderr.writer()) catch return; } -pub fn renderToWriter( - eb: ErrorBundle, - ttyconf: std.debug.TTY.Config, - writer: anytype, -) anyerror!void { +pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, writer: anytype) anyerror!void { for (eb.getMessages()) |err_msg| { - try renderErrorMessageToWriter(eb, err_msg, ttyconf, writer, "error", .Red, 0); + try renderErrorMessageToWriter(eb, options, err_msg, writer, "error", .Red, 0); } } fn renderErrorMessageToWriter( eb: ErrorBundle, + options: RenderOptions, err_msg_index: MessageIndex, - ttyconf: std.debug.TTY.Config, stderr: anytype, kind: []const u8, color: std.debug.TTY.Color, indent: usize, ) anyerror!void { + const ttyconf = options.ttyconf; var counting_writer = std.io.countingWriter(stderr); const counting_stderr = counting_writer.writer(); const err_msg = eb.getErrorMessage(err_msg_index); @@ -196,7 +199,7 @@ fn renderErrorMessageToWriter( try stderr.print(" ({d} times)\n", .{err_msg.count}); } try ttyconf.setColor(stderr, .Reset); - if (src.data.source_line != 0) { + if (src.data.source_line != 0 and options.include_source_line) { const line = eb.nullTerminatedString(src.data.source_line); for (line) |b| switch (b) { '\t' => try stderr.writeByte(' '), @@ -216,9 +219,9 @@ fn renderErrorMessageToWriter( try ttyconf.setColor(stderr, .Reset); } for (eb.getNotes(err_msg_index)) |note| { - try renderErrorMessageToWriter(eb, note, ttyconf, stderr, "note", .Cyan, indent); + try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent); } - if (src.data.reference_trace_len > 0) { + if (src.data.reference_trace_len > 0 and options.include_reference_trace) { try ttyconf.setColor(stderr, .Reset); try ttyconf.setColor(stderr, .Dim); try stderr.print("referenced by:\n", .{}); @@ -266,7 +269,7 @@ fn renderErrorMessageToWriter( } try ttyconf.setColor(stderr, .Reset); for (eb.getNotes(err_msg_index)) |note| { - try renderErrorMessageToWriter(eb, note, ttyconf, stderr, "note", .Cyan, indent + 4); + try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent + 4); } } } diff --git a/src/Sema.zig b/src/Sema.zig index 03ebbbdbac3c..c9f4f27fe2db 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2220,7 +2220,7 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError { Compilation.addModuleErrorMsg(&wip_errors, err_msg.*) catch unreachable; std.debug.print("compile error during Sema:\n", .{}); var error_bundle = wip_errors.toOwnedBundle() catch unreachable; - error_bundle.renderToStdErr(.no_color); + error_bundle.renderToStdErr(.{ .ttyconf = .no_color }); crash_report.compilerPanic("unexpected compile error occurred", null, null); } diff --git a/src/main.zig b/src/main.zig index 669f1afd0c33..03d746af0cf6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4082,7 +4082,7 @@ fn updateModule(gpa: Allocator, comp: *Compilation, hook: AfterUpdateHook) !void defer errors.deinit(comp.gpa); if (errors.errorMessageCount() > 0) { - errors.renderToStdErr(get_tty_conf(comp.color)); + errors.renderToStdErr(renderOptions(comp.color)); const log_text = comp.getCompileLogOutput(); if (log_text.len != 0) { std.debug.print("\nCompile Log Output:\n{s}", .{log_text}); @@ -4711,7 +4711,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi if (wip_errors.root_list.items.len > 0) { var errors = try wip_errors.toOwnedBundle(); defer errors.deinit(gpa); - errors.renderToStdErr(get_tty_conf(color)); + errors.renderToStdErr(renderOptions(color)); process.exit(1); } try fetch_result; @@ -4974,7 +4974,7 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void try Compilation.addZirErrorMessages(&wip_errors, &file); var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(get_tty_conf(color)); + error_bundle.renderToStdErr(renderOptions(color)); process.exit(2); } } else if (tree.errors.len != 0) { @@ -5180,7 +5180,7 @@ fn fmtPathFile( try Compilation.addZirErrorMessages(&wip_errors, &file); var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(get_tty_conf(fmt.color)); + error_bundle.renderToStdErr(renderOptions(fmt.color)); fmt.any_error = true; } } @@ -5217,7 +5217,7 @@ fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Co var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(get_tty_conf(color)); + error_bundle.renderToStdErr(renderOptions(color)); } pub fn putAstErrorsIntoBundle( @@ -5834,7 +5834,7 @@ pub fn cmdAstCheck( try Compilation.addZirErrorMessages(&wip_errors, &file); var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(get_tty_conf(color)); + error_bundle.renderToStdErr(renderOptions(color)); process.exit(1); } @@ -5892,6 +5892,7 @@ pub fn cmdChangelist( arena: Allocator, args: []const []const u8, ) !void { + const color: Color = .auto; const Zir = @import("Zir.zig"); const old_source_file = args[0]; @@ -5948,10 +5949,9 @@ pub fn cmdChangelist( try wip_errors.init(gpa); defer wip_errors.deinit(); try Compilation.addZirErrorMessages(&wip_errors, &file); - const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr()); var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(ttyconf); + error_bundle.renderToStdErr(renderOptions(color)); process.exit(1); } @@ -5984,10 +5984,9 @@ pub fn cmdChangelist( try wip_errors.init(gpa); defer wip_errors.deinit(); try Compilation.addZirErrorMessages(&wip_errors, &file); - const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr()); var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(ttyconf); + error_bundle.renderToStdErr(renderOptions(color)); process.exit(1); } @@ -6256,3 +6255,12 @@ fn get_tty_conf(color: Color) std.debug.TTY.Config { .off => .no_color, }; } + +fn renderOptions(color: Color) std.zig.ErrorBundle.RenderOptions { + const ttyconf = get_tty_conf(color); + return .{ + .ttyconf = ttyconf, + .include_source_line = ttyconf != .no_color, + .include_reference_trace = ttyconf != .no_color, + }; +}