╭ incr-check.zig ───────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 const std = @import("std"); 2 const Io = std.Io; 3 const Dir = std.Io.Dir; 4 const Allocator = std.mem.Allocator; 5 const Cache = std.Build.Cache; 6 7 const usage = 8 \\Usage: incr-check <zig binary path> <input file> [options] 9 \\Options: 10 \\ --target triple-backend 11 \\ --quiet 12 \\ --zig-lib-dir /path/to/zig/lib 13 \\ --zig-cc-binary /path/to/zig 14 \\ -fqemu 15 \\ -fwine 16 \\ -fwasmtime 17 \\Debug Options: 18 \\ --preserve-tmp 19 \\ --debug-log foo 20 \\ --debug-link-snapshot 21 ; 22 23 pub const std_options: std.Options = .{ 24 .logFn = logImpl, 25 }; 26 var log_cur_update: ?*const Case.Update = null; 27 fn logImpl( 28 comptime level: std.log.Level, 29 comptime scope: @EnumLiteral(), 30 comptime format: []const u8, 31 args: anytype, 32 ) void { 33 const update = log_cur_update orelse { 34 return std.log.defaultLog(level, scope, format, args); 35 }; 36 std.log.defaultLog( 37 level, 38 scope, 39 "['{s}'] " ++ format, 40 .{update.name} ++ args, 41 ); 42 } 43 44 pub fn main(init: std.process.Init) !void { 45 const gpa = init.gpa; 46 const arena = init.arena.allocator(); 47 const io = init.io; 48 const environ_map = init.environ_map; 49 const cwd_path = try std.process.currentPathAlloc(io, arena); 50 51 var opt_zig_exe: ?[]const u8 = null; 52 var opt_input_file_name: ?[]const u8 = null; 53 var opt_lib_dir: ?[]const u8 = null; 54 var opt_cc_zig: ?[]const u8 = null; 55 var opt_target: ?struct { std.Target.Query, Backend } = null; 56 var preserve_tmp = false; 57 var enable_qemu: bool = false; 58 var enable_wine: bool = false; 59 var enable_wasmtime: bool = false; 60 var enable_darling: bool = false; 61 var quiet: bool = false; 62 63 var debug_log_args: std.ArrayList([]const u8) = .empty; 64 var debug_link_snapshot = false; 65 66 var arg_it = try init.minimal.args.iterateAllocator(arena); 67 _ = arg_it.skip(); 68 while (arg_it.next()) |arg| { 69 if (arg.len > 0 and arg[0] == '-') { 70 if (std.mem.eql(u8, arg, "--zig-lib-dir")) { 71 opt_lib_dir = arg_it.next() orelse badUsage("expected arg after --zig-lib-dir", .{}); 72 } else if (std.mem.eql(u8, arg, "--target")) { 73 const str = arg_it.next() orelse badUsage("expected arg after --zig-cc-binary", .{}); 74 opt_target = parseTargetQueryAndBackend(str, ""); 75 } else if (std.mem.eql(u8, arg, "--quiet")) { 76 quiet = true; 77 } else if (std.mem.eql(u8, arg, "--debug-log")) { 78 try debug_log_args.append( 79 arena, 80 arg_it.next() orelse badUsage("expected arg after --debug-log", .{}), 81 ); 82 } else if (std.mem.eql(u8, arg, "--debug-link-snapshot")) { 83 debug_link_snapshot = true; 84 } else if (std.mem.eql(u8, arg, "--preserve-tmp")) { 85 preserve_tmp = true; 86 } else if (std.mem.eql(u8, arg, "-fqemu")) { 87 enable_qemu = true; 88 } else if (std.mem.eql(u8, arg, "-fwine")) { 89 enable_wine = true; 90 } else if (std.mem.eql(u8, arg, "-fwasmtime")) { 91 enable_wasmtime = true; 92 } else if (std.mem.eql(u8, arg, "-fdarling")) { 93 enable_darling = true; 94 } else if (std.mem.eql(u8, arg, "--zig-cc-binary")) { 95 opt_cc_zig = arg_it.next() orelse badUsage("expected arg after --zig-cc-binary", .{}); 96 } else { 97 badUsage("unknown option '{s}'", .{arg}); 98 } 99 continue; 100 } 101 if (opt_zig_exe == null) { 102 opt_zig_exe = arg; 103 } else if (opt_input_file_name == null) { 104 opt_input_file_name = arg; 105 } else { 106 badUsage("unknown argument '{s}'\n{s}", .{ arg, usage }); 107 } 108 } 109 const zig_exe = opt_zig_exe orelse badUsage("missing path to zig", .{}); 110 const input_file_name = opt_input_file_name orelse badUsage("missing input file", .{}); 111 const target_query, const backend = opt_target orelse badUsage("missing required option '--target'", .{}); 112 113 if (backend == .cbe and opt_lib_dir == null) { 114 std.process.fatal("'--zig-lib-dir' required when using backend 'cbe'", .{}); 115 } 116 117 const input_file_bytes = try Dir.cwd().readFileAlloc(io, input_file_name, arena, .limited(std.math.maxInt(u32))); 118 const case: Case = try .parse(arena, input_file_bytes); 119 120 for (case.skip_targets) |skip| { 121 if (target_query.eql(skip.query) and backend == skip.backend) { 122 if (!quiet) std.log.warn("skipping test because of a 'skip_target' match", .{}); 123 return; 124 } 125 } 126 127 const target = try std.zig.system.resolveTargetQuery(io, target_query); 128 129 const prog_node = std.Progress.start(io, .{}); 130 defer prog_node.end(); 131 132 const rand_int = rand64(io); 133 const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int); 134 var tmp_dir = try Dir.cwd().createDirPathOpen(io, tmp_dir_path, .{}); 135 defer { 136 tmp_dir.close(io); 137 if (!preserve_tmp) { 138 Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| { 139 std.log.warn("failed to delete tree '{s}': {t}", .{ tmp_dir_path, err }); 140 }; 141 } 142 } 143 144 // Convert paths to be relative to the cwd of the subprocess. 145 const resolved_zig_exe = try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, zig_exe); 146 const opt_resolved_lib_dir = if (opt_lib_dir) |lib_dir| 147 try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, lib_dir) 148 else 149 null; 150 151 const host = try std.zig.system.resolveTargetQuery(io, .{}); 152 153 var child_args: std.ArrayList([]const u8) = .empty; 154 try child_args.appendSlice(arena, &.{ 155 resolved_zig_exe, "build-exe", 156 "-fincremental", "-fno-ubsan-rt", 157 "-target", try target_query.zigTriple(arena), 158 "--cache-dir", ".local-cache", 159 "--global-cache-dir", ".global-cache", 160 }); 161 162 if (opt_resolved_lib_dir) |resolved_lib_dir| { 163 try child_args.appendSlice(arena, &.{ "--zig-lib-dir", resolved_lib_dir }); 164 } 165 switch (backend) { 166 .sema => try child_args.append(arena, "-fno-emit-bin"), 167 .selfhosted => try child_args.appendSlice(arena, &.{ "-fno-llvm", "-fno-lld" }), 168 .llvm => try child_args.appendSlice(arena, &.{ "-fllvm", "-flld" }), 169 .cbe => try child_args.appendSlice(arena, &.{ "-ofmt=c", "-lc" }), 170 } 171 for (debug_log_args.items) |arg| { 172 try child_args.appendSlice(arena, &.{ "--debug-log", arg }); 173 } 174 if (debug_link_snapshot) { 175 try child_args.append(arena, "--debug-link-snapshot"); 176 } 177 for (case.modules) |mod| { 178 try child_args.appendSlice(arena, &.{ "--dep", mod.name }); 179 } 180 try child_args.append(arena, try std.fmt.allocPrint(arena, "-Mroot={s}", .{case.root_source_file})); 181 for (case.modules) |mod| { 182 try child_args.append(arena, try std.fmt.allocPrint(arena, "-M{s}={s}", .{ mod.name, mod.file })); 183 } 184 try child_args.append(arena, "--listen=-"); 185 186 const zig_prog_node = prog_node.start("zig", 0); 187 defer zig_prog_node.end(); 188 189 var cc_child_args: std.ArrayList([]const u8) = .empty; 190 if (backend == .cbe) { 191 const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe| 192 try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, cc_zig_exe) 193 else 194 resolved_zig_exe; 195 196 try cc_child_args.appendSlice(arena, &.{ 197 resolved_cc_zig_exe, "build-exe", 198 "-target", try target_query.zigTriple(arena), 199 "--cache-dir", ".local-cache", 200 "--global-cache-dir", ".global-cache", 201 "-I", opt_resolved_lib_dir.?, // verified earlier 202 "-lc", 203 }); 204 } 205 206 const allow_compiler_stderr = debug_log_args.items.len != 0; 207 208 if (allow_compiler_stderr) { 209 const cmd: std.zig.SubprocessCommand = .{ 210 .argv = child_args.items, 211 .cwd = tmp_dir_path, 212 }; 213 std.log.scoped(.spawn).info("{f}", .{cmd}); 214 } 215 216 var child = try std.process.spawn(io, .{ 217 .argv = child_args.items, 218 .stdin = .pipe, 219 .stdout = .pipe, 220 .stderr = .pipe, 221 .progress_node = zig_prog_node, 222 .cwd = .{ .path = tmp_dir_path }, 223 }); 224 defer child.kill(io); 225 226 const updates_prog_node = prog_node.start("updates", case.updates.len); 227 defer updates_prog_node.end(); 228 229 var eval: Eval = .{ 230 .arena = arena, 231 .io = io, 232 .case = case, 233 .host = host, 234 .target = target, 235 .backend = backend, 236 .tmp_dir = tmp_dir, 237 .tmp_dir_path = tmp_dir_path, 238 .child = &child, 239 .allow_compiler_stderr = allow_compiler_stderr, 240 .quiet = quiet, 241 .preserve_tmp_on_fatal = preserve_tmp, 242 .cc_child_args = &cc_child_args, 243 .enable_qemu = enable_qemu, 244 .enable_wine = enable_wine, 245 .enable_wasmtime = enable_wasmtime, 246 .enable_darling = enable_darling, 247 }; 248 249 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; 250 var multi_reader: Io.File.MultiReader = undefined; 251 multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); 252 defer multi_reader.deinit(); 253 254 var update_mtime = Io.Clock.real.now(io); 255 for (case.updates) |update| { 256 var update_prog_node = updates_prog_node.start(update.name, 0); 257 defer update_prog_node.end(); 258 259 if (debug_log_args.items.len != 0) { 260 // Print a line separating the debug logs from the compiler in the stderr output. 261 std.log.scoped(.status).info("update: '{s}'", .{update.name}); 262 } 263 264 log_cur_update = &update; 265 defer log_cur_update = null; 266 267 eval.write(update, update_mtime); 268 update_mtime = update_mtime.addDuration(.fromSeconds(5)); 269 try eval.requestUpdate(); 270 try eval.check(&multi_reader, update, update_prog_node); 271 } 272 273 try eval.end(&multi_reader); 274 275 waitChild(&child, &eval); 276 } 277 278 const Eval = struct { 279 arena: Allocator, 280 io: Io, 281 case: Case, 282 host: std.Target, 283 target: std.Target, 284 backend: Backend, 285 tmp_dir: Dir, 286 tmp_dir_path: []const u8, 287 child: *std.process.Child, 288 allow_compiler_stderr: bool, 289 quiet: bool, 290 preserve_tmp_on_fatal: bool, 291 /// When `backend == .cbe`, this contains the first few arguments to `zig cc` to build the generated binary. 292 /// The arguments `out.c in.c` must be appended before spawning the subprocess. 293 cc_child_args: *std.ArrayList([]const u8), 294 295 enable_qemu: bool, 296 enable_wine: bool, 297 enable_wasmtime: bool, 298 enable_darling: bool, 299 300 /// Currently this function assumes the previous updates have already been written. 301 fn write(eval: *Eval, update: Case.Update, mtime: Io.Timestamp) void { 302 const io = eval.io; 303 for (update.changes) |full_contents| { 304 var update_file = eval.tmp_dir.createFile(io, full_contents.name, .{}) catch |err| { 305 eval.fatal("failed to create update '{s}': {t}", .{ full_contents.name, err }); 306 }; 307 defer update_file.close(io); 308 update_file.writeStreamingAll(io, full_contents.bytes) catch |err| { 309 eval.fatal("failed to write update '{s}': {t}", .{ full_contents.name, err }); 310 }; 311 update_file.setTimestamps(io, .{ .modify_timestamp = .{ .new = mtime } }) catch |err| { 312 eval.fatal("failed to set mtime for '{s}': {t}", .{ full_contents.name, err }); 313 }; 314 } 315 for (update.deletes) |doomed_name| { 316 eval.tmp_dir.deleteFile(io, doomed_name) catch |err| { 317 eval.fatal("failed to delete '{s}': {t}", .{ doomed_name, err }); 318 }; 319 } 320 } 321 322 fn check(eval: *Eval, mr: *Io.File.MultiReader, update: Case.Update, prog_node: std.Progress.Node) !void { 323 const arena = eval.arena; 324 const stdout = mr.reader(0); 325 const stderr = mr.reader(1); 326 327 var client: std.zig.Client = .{ 328 .in = stdout, 329 .out = undefined, 330 }; 331 332 while (true) { 333 const header = client.receiveMessageWithMultiReader(mr, .none) catch |err| switch (err) { 334 error.Timeout => unreachable, 335 error.EndOfStream => break, 336 else => |e| return e, 337 }; 338 const body = client.in.take(header.bytes_len) catch unreachable; 339 340 switch (header.tag) { 341 .error_bundle => { 342 const result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body); 343 if (stderr.bufferedLen() > 0) { 344 if (eval.allow_compiler_stderr) { 345 std.log.info("error_bundle stderr:\n{s}", .{stderr.buffered()}); 346 } else { 347 eval.fatal("error_bundle unexpected stderr:\n{s}", .{stderr.buffered()}); 348 } 349 stderr.tossBuffered(); 350 } 351 if (result_error_bundle.errorMessageCount() != 0) { 352 try eval.checkErrorOutcome(update, result_error_bundle); 353 } 354 // This message indicates the end of the update. 355 return; 356 }, 357 .emit_digest => { 358 var r: std.Io.Reader = .fixed(body); 359 _ = r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable; 360 361 if (stderr.bufferedLen() > 0) { 362 if (eval.allow_compiler_stderr) { 363 std.log.info("emit_digest stderr:\n{s}", .{stderr.buffered()}); 364 } else { 365 eval.fatal("emit_digest unexpected stderr:\n{s}", .{stderr.buffered()}); 366 } 367 stderr.tossBuffered(); 368 } 369 if (eval.backend == .sema) { 370 try eval.checkSuccessOutcome(update, null, prog_node); 371 continue; 372 } 373 374 const digest = r.takeArray(Cache.bin_digest_len) catch unreachable; 375 const result_dir = ".local-cache" ++ Dir.path.sep_str ++ "o" ++ Dir.path.sep_str ++ Cache.binToHex(digest.*); 376 377 const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{ 378 .root_name = "root", // corresponds to the module name "root" 379 .cpu_arch = eval.target.cpu.arch, 380 .os_tag = eval.target.os.tag, 381 .ofmt = eval.target.ofmt, 382 .abi = eval.target.abi, 383 .output_mode = .Exe, 384 }); 385 const bin_path = try Dir.path.join(arena, &.{ result_dir, bin_name }); 386 387 try eval.checkSuccessOutcome(update, bin_path, prog_node); 388 }, 389 else => { 390 // Ignore other messages. 391 }, 392 } 393 } 394 395 const buffered_stderr = stderr.buffered(); 396 if (buffered_stderr.len > 0) { 397 if (eval.allow_compiler_stderr) { 398 std.log.info("stderr:\n{s}", .{buffered_stderr}); 399 } else { 400 eval.fatal("unexpected stderr:\n{s}", .{buffered_stderr}); 401 } 402 } 403 404 waitChild(eval.child, eval); 405 eval.fatal("compiler failed to send terminating error_bundle", .{}); 406 } 407 408 fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void { 409 const io = eval.io; 410 const expected = switch (update.outcome) { 411 .unknown => return, 412 .compile_errors => |ce| ce, 413 .stdout, .exit_code => { 414 try error_bundle.renderToStderr(io, .{}, .auto); 415 eval.fatal("unexpected compile errors", .{}); 416 }, 417 }; 418 419 var expected_idx: usize = 0; 420 421 for (error_bundle.getMessages()) |err_idx| { 422 if (expected_idx == expected.errors.len) { 423 try error_bundle.renderToStderr(io, .{}, .auto); 424 eval.fatal("more errors than expected", .{}); 425 } 426 try eval.checkOneError(error_bundle, expected.errors[expected_idx], false, err_idx); 427 expected_idx += 1; 428 429 for (error_bundle.getNotes(err_idx)) |note_idx| { 430 if (expected_idx == expected.errors.len) { 431 try error_bundle.renderToStderr(io, .{}, .auto); 432 eval.fatal("more error notes than expected", .{}); 433 } 434 try eval.checkOneError(error_bundle, expected.errors[expected_idx], true, note_idx); 435 expected_idx += 1; 436 } 437 } 438 439 if (!std.mem.eql(u8, error_bundle.getCompileLogOutput(), expected.compile_log_output)) { 440 try error_bundle.renderToStderr(io, .{}, .auto); 441 eval.fatal("unexpected compile log output", .{}); 442 } 443 } 444 445 fn checkOneError( 446 eval: *Eval, 447 eb: std.zig.ErrorBundle, 448 expected: Case.ExpectedError, 449 is_note: bool, 450 err_idx: std.zig.ErrorBundle.MessageIndex, 451 ) Allocator.Error!void { 452 const io = eval.io; 453 const err = eb.getErrorMessage(err_idx); 454 if (err.count != 1) @panic("TODO error message with count>1"); 455 const msg = eb.nullTerminatedString(err.msg); 456 const matches = matches: { 457 if (expected.is_note != is_note) break :matches false; 458 if (!std.mem.eql(u8, expected.msg, msg)) break :matches false; 459 if (err.src_loc == .none) { 460 break :matches expected.src == null; 461 } 462 const expected_src = expected.src orelse break :matches false; 463 const src = eb.getSourceLocation(err.src_loc); 464 const raw_filename = eb.nullTerminatedString(src.src_path); 465 // We need to replace backslashes for consistency between platforms. 466 const filename = name: { 467 if (std.mem.findScalar(u8, raw_filename, '\\') == null) break :name raw_filename; 468 const copied = try eval.arena.dupe(u8, raw_filename); 469 std.mem.replaceScalar(u8, copied, '\\', '/'); 470 break :name copied; 471 }; 472 if (!std.mem.eql(u8, expected_src.filename, filename)) break :matches false; 473 if (expected_src.line != src.line + 1) break :matches false; 474 if (expected_src.column != src.column + 1) break :matches false; 475 break :matches true; 476 }; 477 if (!matches) { 478 eb.renderToStderr(io, .{}, .auto) catch {}; 479 eval.fatal("compile error did not match expected error", .{}); 480 } 481 } 482 483 fn checkSuccessOutcome(eval: *Eval, update: Case.Update, opt_emitted_path: ?[]const u8, prog_node: std.Progress.Node) !void { 484 switch (update.outcome) { 485 .unknown => return, 486 .compile_errors => eval.fatal("expected compile errors but compilation incorrectly succeeded", .{}), 487 .stdout, .exit_code => {}, 488 } 489 const emitted_path = opt_emitted_path orelse { 490 std.debug.assert(eval.backend == .sema); 491 return; 492 }; 493 const io = eval.io; 494 495 const binary_path = switch (eval.backend) { 496 .sema => unreachable, 497 .selfhosted, .llvm => emitted_path, 498 .cbe => bin: { 499 const rand_int = rand64(io); 500 const out_bin_name = "./out_" ++ std.fmt.hex(rand_int); 501 try eval.buildCOutput(emitted_path, out_bin_name, prog_node); 502 break :bin out_bin_name; 503 }, 504 }; 505 506 var argv_buf: [2][]const u8 = undefined; 507 const argv: []const []const u8, const is_foreign: bool = sw: switch (std.zig.system.getExternalExecutor( 508 io, 509 &eval.target, 510 .{ 511 .link_libc = eval.backend == .cbe, 512 .host_cpu_arch = eval.host.cpu.arch, 513 .host_os_tag = eval.host.os.tag, 514 }, 515 )) { 516 .bad_dl, .bad_os_or_cpu => { 517 // This binary cannot be executed on this host. 518 if (!eval.quiet) { 519 std.log.warn("skipping execution because host '{s}' cannot execute binaries for foreign target '{s}'", .{ 520 try eval.host.zigTriple(eval.arena), 521 try eval.target.zigTriple(eval.arena), 522 }); 523 } 524 return; 525 }, 526 .native, .rosetta => argv: { 527 argv_buf[0] = binary_path; 528 break :argv .{ argv_buf[0..1], false }; 529 }, 530 .qemu => |executor_cmd| argv: { 531 if (eval.enable_qemu) { 532 argv_buf[0] = executor_cmd; 533 argv_buf[1] = binary_path; 534 break :argv .{ argv_buf[0..2], true }; 535 } else { 536 continue :sw .bad_os_or_cpu; 537 } 538 }, 539 .wine => |executor_cmd| argv: { 540 if (eval.enable_wine) { 541 argv_buf[0] = executor_cmd; 542 argv_buf[1] = binary_path; 543 break :argv .{ argv_buf[0..2], true }; 544 } else { 545 continue :sw .bad_os_or_cpu; 546 } 547 }, 548 .wasmtime => |executor_cmd| argv: { 549 if (eval.enable_wasmtime) { 550 argv_buf[0] = executor_cmd; 551 argv_buf[1] = binary_path; 552 break :argv .{ argv_buf[0..2], true }; 553 } else { 554 continue :sw .bad_os_or_cpu; 555 } 556 }, 557 .darling => |executor_cmd| argv: { 558 if (eval.enable_darling) { 559 argv_buf[0] = executor_cmd; 560 argv_buf[1] = binary_path; 561 break :argv .{ argv_buf[0..2], true }; 562 } else { 563 continue :sw .bad_os_or_cpu; 564 } 565 }, 566 }; 567 568 const run_prog_node = prog_node.start("run generated executable", 0); 569 defer run_prog_node.end(); 570 571 const result = std.process.run(eval.arena, io, .{ 572 .argv = argv, 573 .cwd = .{ .path = eval.tmp_dir_path }, 574 }) catch |err| { 575 if (is_foreign) { 576 // Chances are the foreign executor isn't available. Skip this evaluation. 577 if (!eval.quiet) { 578 std.log.warn("skipping execution of '{s}' via executor for foreign target '{s}': {t}", .{ 579 binary_path, 580 try eval.target.zigTriple(eval.arena), 581 err, 582 }); 583 } 584 return; 585 } 586 eval.fatal("failed to run the generated executable '{s}': {t}", .{ binary_path, err }); 587 }; 588 589 // Some executors (looking at you, Wine) like throwing some stderr in, just for fun. 590 // Therefore, we'll ignore stderr when using a foreign executor. 591 if (!is_foreign and result.stderr.len != 0) { 592 std.log.err("generated executable '{s}' had unexpected stderr:\n{s}", .{ 593 binary_path, result.stderr, 594 }); 595 } 596 597 switch (result.term) { 598 .exited => |code| switch (update.outcome) { 599 .unknown, .compile_errors => unreachable, 600 .stdout => |expected_stdout| { 601 if (code != 0) { 602 eval.fatal("generated executable '{s}' failed with code {d}", .{ binary_path, code }); 603 } 604 try std.testing.expectEqualStrings(expected_stdout, result.stdout); 605 }, 606 .exit_code => |expected_code| try std.testing.expectEqual(expected_code, code), 607 }, 608 .signal => |sig| { 609 eval.fatal("generated executable '{s}' terminated with signal {t}", .{ binary_path, sig }); 610 }, 611 .stopped => |sig| { 612 eval.fatal("generated executable '{s}' stopped with signal {t}", .{ binary_path, sig }); 613 }, 614 .unknown => { 615 eval.fatal("generated executable '{s}' terminated unexpectedly", .{binary_path}); 616 }, 617 } 618 619 if (!is_foreign and result.stderr.len != 0) std.process.exit(1); 620 } 621 622 fn requestUpdate(eval: *Eval) !void { 623 const io = eval.io; 624 625 var w = eval.child.stdin.?.writerStreaming(io, &.{}); 626 var client: std.zig.Client = .{ 627 .in = undefined, 628 .out = &w.interface, 629 }; 630 client.serveBodylessMessage(.update) catch |err| switch (err) { 631 error.WriteFailed => return w.err.?, 632 }; 633 } 634 635 fn end(eval: *Eval, mr: *Io.File.MultiReader) !void { 636 requestExit(eval.child, eval); 637 638 var client: std.zig.Client = .{ 639 .in = mr.reader(0), 640 .out = undefined, 641 }; 642 643 while (true) { 644 const header = client.receiveMessageWithMultiReader(mr, .none) catch |err| switch (err) { 645 error.Timeout => unreachable, 646 error.EndOfStream => |e| { 647 if (client.in.bufferedLen() == 0) break; 648 return e; 649 }, 650 else => |e| return e, 651 }; 652 try client.in.discardAll(header.bytes_len); 653 } 654 655 const stderr = mr.reader(1).buffered(); 656 if (stderr.len > 0) eval.fatal("unexpected stderr:\n{s}", .{stderr}); 657 } 658 659 fn buildCOutput(eval: *Eval, c_path: []const u8, out_path: []const u8, prog_node: std.Progress.Node) !void { 660 std.debug.assert(eval.cc_child_args.items.len > 0); 661 662 const child_prog_node = prog_node.start("build cbe output", 0); 663 defer child_prog_node.end(); 664 665 try eval.cc_child_args.append(eval.arena, try std.fmt.allocPrint(eval.arena, "-femit-bin={s}", .{out_path})); 666 try eval.cc_child_args.append(eval.arena, c_path); 667 defer eval.cc_child_args.items.len -= 2; 668 669 if (eval.allow_compiler_stderr) { 670 const cmd: std.zig.SubprocessCommand = .{ 671 .argv = eval.cc_child_args.items, 672 .cwd = eval.tmp_dir_path, 673 }; 674 std.log.scoped(.spawn).info("{f}", .{cmd}); 675 } 676 677 const result = std.process.run(eval.arena, eval.io, .{ 678 .argv = eval.cc_child_args.items, 679 .cwd = .{ .path = eval.tmp_dir_path }, 680 .progress_node = child_prog_node, 681 }) catch |err| { 682 eval.fatal("failed to spawn zig cc for '{s}': {t}", .{ c_path, err }); 683 }; 684 685 if (result.term == .exited and result.term.exited == 0) return; 686 687 if (result.stderr.len != 0) { 688 std.log.err("zig cc stderr:\n{s}", .{result.stderr}); 689 } 690 switch (result.term) { 691 .exited => |code| eval.fatal("zig cc for '{s}' failed with code {d}", .{ c_path, code }), 692 .signal => |sig| eval.fatal("zig cc for '{s}' terminated unexpectedly with signal {t}", .{ c_path, sig }), 693 .stopped => |sig| eval.fatal("zig cc for '{s}' stopped unexpectedly with signal {t}", .{ c_path, sig }), 694 .unknown => eval.fatal("zig cc for '{s}' terminated unexpectedly", .{c_path}), 695 } 696 } 697 698 fn fatal(eval: *Eval, comptime fmt: []const u8, args: anytype) noreturn { 699 const io = eval.io; 700 eval.tmp_dir.close(io); 701 if (!eval.preserve_tmp_on_fatal) { 702 // Kill the child since it holds an open handle to its CWD which is the tmp dir path 703 eval.child.kill(io); 704 Dir.cwd().deleteTree(io, eval.tmp_dir_path) catch |err| { 705 std.log.warn("failed to delete tree '{s}': {t}", .{ eval.tmp_dir_path, err }); 706 }; 707 } 708 std.process.fatal(fmt, args); 709 } 710 }; 711 712 const Backend = enum { 713 /// Run semantic analysis only. Runtime output will not be tested, but we still verify 714 /// that compilation succeeds. Corresponds to `-fno-emit-bin`. 715 sema, 716 /// Use the self-hosted code generation backend for this target. 717 /// Corresponds to `-fno-llvm -fno-lld`. 718 selfhosted, 719 /// Use the LLVM backend. 720 /// Corresponds to `-fllvm -flld`. 721 llvm, 722 /// Use the C backend. The output is compiled with `zig cc`. 723 /// Corresponds to `-ofmt=c`. 724 cbe, 725 }; 726 727 const Case = struct { 728 updates: []Update, 729 root_source_file: []const u8, 730 skip_targets: []const SkipTarget, 731 modules: []const Module, 732 733 const SkipTarget = struct { 734 query: std.Target.Query, 735 backend: Backend, 736 }; 737 738 const Module = struct { 739 name: []const u8, 740 file: []const u8, 741 }; 742 743 const Update = struct { 744 name: []const u8, 745 outcome: Outcome, 746 changes: []const FullContents = &.{}, 747 deletes: []const []const u8 = &.{}, 748 }; 749 750 const FullContents = struct { 751 name: []const u8, 752 bytes: []const u8, 753 }; 754 755 const Outcome = union(enum) { 756 unknown, 757 compile_errors: struct { 758 errors: []const ExpectedError, 759 compile_log_output: []const u8, 760 }, 761 stdout: []const u8, 762 exit_code: u8, 763 }; 764 765 const ExpectedError = struct { 766 is_note: bool, 767 msg: []const u8, 768 src: ?struct { 769 filename: []const u8, 770 line: u32, 771 column: u32, 772 }, 773 }; 774 775 fn parse(arena: Allocator, bytes: []const u8) !Case { 776 const fatal = std.process.fatal; 777 778 var skip_targets: std.ArrayList(SkipTarget) = .empty; 779 var modules: std.ArrayList(Module) = .empty; 780 var updates: std.ArrayList(Update) = .empty; 781 var changes: std.ArrayList(FullContents) = .empty; 782 var deletes: std.ArrayList([]const u8) = .empty; 783 var it = std.mem.splitScalar(u8, bytes, '\n'); 784 var line_n: usize = 1; 785 var root_source_file: ?[]const u8 = null; 786 while (it.next()) |line| : (line_n += 1) { 787 if (std.mem.startsWith(u8, line, "#")) { 788 var line_it = std.mem.splitScalar(u8, line, '='); 789 const key = line_it.first()[1..]; 790 const val = std.mem.trimEnd(u8, line_it.rest(), "\r"); // windows moment 791 if (val.len == 0) { 792 fatal("line {d}: missing value", .{line_n}); 793 } else if (std.mem.eql(u8, key, "skip_target")) { 794 const query, const backend = parseTargetQueryAndBackend( 795 val, 796 try std.fmt.allocPrint(arena, "line {d}: ", .{line_n}), 797 ); 798 try skip_targets.append(arena, .{ 799 .query = query, 800 .backend = backend, 801 }); 802 } else if (std.mem.eql(u8, key, "module")) { 803 const split_idx = std.mem.findScalar(u8, val, '=') orelse 804 fatal("line {d}: module does not include file", .{line_n}); 805 const name = val[0..split_idx]; 806 const file = val[split_idx + 1 ..]; 807 try modules.append(arena, .{ 808 .name = name, 809 .file = file, 810 }); 811 } else if (std.mem.eql(u8, key, "update")) { 812 if (updates.items.len > 0) { 813 const last_update = &updates.items[updates.items.len - 1]; 814 last_update.changes = try changes.toOwnedSlice(arena); 815 last_update.deletes = try deletes.toOwnedSlice(arena); 816 } 817 try updates.append(arena, .{ 818 .name = val, 819 .outcome = .unknown, 820 }); 821 } else if (std.mem.eql(u8, key, "file")) { 822 if (updates.items.len == 0) fatal("line {d}: file directive before update", .{line_n}); 823 824 if (root_source_file == null) 825 root_source_file = val; 826 827 // Because Windows is so excellent, we need to convert CRLF to LF, so 828 // can't just slice into the input here. How delightful! 829 var src: std.ArrayList(u8) = .empty; 830 831 while (true) { 832 const next_line_raw = it.peek() orelse fatal("line {d}: unexpected EOF", .{line_n}); 833 const next_line = std.mem.trimEnd(u8, next_line_raw, "\r"); 834 if (std.mem.startsWith(u8, next_line, "#")) break; 835 836 _ = it.next(); 837 line_n += 1; 838 839 try src.ensureUnusedCapacity(arena, next_line.len + 1); 840 src.appendSliceAssumeCapacity(next_line); 841 src.appendAssumeCapacity('\n'); 842 } 843 844 try changes.append(arena, .{ 845 .name = val, 846 .bytes = src.items, 847 }); 848 } else if (std.mem.eql(u8, key, "rm_file")) { 849 if (updates.items.len == 0) fatal("line {d}: rm_file directive before update", .{line_n}); 850 try deletes.append(arena, val); 851 } else if (std.mem.eql(u8, key, "expect_stdout")) { 852 if (updates.items.len == 0) fatal("line {d}: expect directive before update", .{line_n}); 853 const last_update = &updates.items[updates.items.len - 1]; 854 if (last_update.outcome != .unknown) fatal("line {d}: conflicting expect directive", .{line_n}); 855 last_update.outcome = .{ 856 .stdout = std.zig.string_literal.parseAlloc(arena, val) catch |err| { 857 fatal("line {d}: bad string literal: {t}", .{ line_n, err }); 858 }, 859 }; 860 } else if (std.mem.eql(u8, key, "expect_error")) { 861 if (updates.items.len == 0) fatal("line {d}: expect directive before update", .{line_n}); 862 const last_update = &updates.items[updates.items.len - 1]; 863 if (last_update.outcome != .unknown) fatal("line {d}: conflicting expect directive", .{line_n}); 864 865 var errors: std.ArrayList(ExpectedError) = .empty; 866 try errors.append(arena, parseExpectedError(val, line_n)); 867 while (true) { 868 const next_line = it.peek() orelse break; 869 if (!std.mem.startsWith(u8, next_line, "#")) break; 870 var new_line_it = std.mem.splitScalar(u8, next_line, '='); 871 const new_key = new_line_it.first()[1..]; 872 const new_val = std.mem.trimEnd(u8, new_line_it.rest(), "\r"); 873 if (new_val.len == 0) break; 874 if (!std.mem.eql(u8, new_key, "expect_error")) break; 875 876 _ = it.next(); 877 line_n += 1; 878 try errors.append(arena, parseExpectedError(new_val, line_n)); 879 } 880 881 var compile_log_output: std.ArrayList(u8) = .empty; 882 while (true) { 883 const next_line = it.peek() orelse break; 884 if (!std.mem.startsWith(u8, next_line, "#")) break; 885 var new_line_it = std.mem.splitScalar(u8, next_line, '='); 886 const new_key = new_line_it.first()[1..]; 887 const new_val = std.mem.trimEnd(u8, new_line_it.rest(), "\r"); 888 if (new_val.len == 0) break; 889 if (!std.mem.eql(u8, new_key, "expect_compile_log")) break; 890 891 _ = it.next(); 892 line_n += 1; 893 try compile_log_output.ensureUnusedCapacity(arena, new_val.len + 1); 894 compile_log_output.appendSliceAssumeCapacity(new_val); 895 compile_log_output.appendAssumeCapacity('\n'); 896 } 897 898 last_update.outcome = .{ .compile_errors = .{ 899 .errors = errors.items, 900 .compile_log_output = compile_log_output.items, 901 } }; 902 } else if (std.mem.eql(u8, key, "expect_compile_log")) { 903 fatal("line {d}: 'expect_compile_log' must immediately follow 'expect_error'", .{line_n}); 904 } else { 905 fatal("line {d}: unrecognized key '{s}'", .{ line_n, key }); 906 } 907 } 908 } 909 910 if (changes.items.len > 0) { 911 const last_update = &updates.items[updates.items.len - 1]; 912 last_update.changes = changes.items; // arena so no need for toOwnedSlice 913 last_update.deletes = deletes.items; 914 } 915 916 return .{ 917 .updates = updates.items, 918 .root_source_file = root_source_file orelse fatal("missing root source file", .{}), 919 .skip_targets = skip_targets.items, // arena so no need for toOwnedSlice 920 .modules = modules.items, 921 }; 922 } 923 }; 924 925 fn requestExit(child: *std.process.Child, eval: *Eval) void { 926 if (child.stdin == null) return; 927 const io = eval.io; 928 929 var w = eval.child.stdin.?.writerStreaming(io, &.{}); 930 var client: std.zig.Client = .{ 931 .in = undefined, 932 .out = &w.interface, 933 }; 934 client.serveBodylessMessage(.exit) catch |err| switch (err) { 935 error.WriteFailed => switch (w.err.?) { 936 error.BrokenPipe => {}, 937 else => |e| eval.fatal("failed to send exit: {t}", .{e}), 938 }, 939 }; 940 941 // Send EOF to stdin. 942 child.stdin.?.close(io); 943 child.stdin = null; 944 } 945 946 fn waitChild(child: *std.process.Child, eval: *Eval) void { 947 const io = eval.io; 948 requestExit(child, eval); 949 const term = child.wait(io) catch |err| eval.fatal("child process failed: {t}", .{err}); 950 switch (term) { 951 .exited => |code| if (code != 0) eval.fatal("compiler failed with code {d}", .{code}), 952 .signal => |sig| eval.fatal("compiler terminated with signal {t}", .{sig}), 953 .stopped => |sig| eval.fatal("compiler stopped unexpectedly with signal {t}", .{sig}), 954 .unknown => eval.fatal("compiler terminated unexpectedly", .{}), 955 } 956 } 957 958 fn parseExpectedError(str: []const u8, l: usize) Case.ExpectedError { 959 // #expect_error=foo.zig:1:2: error: the error message 960 // #expect_error=foo.zig:1:2: note: and a note 961 962 const fatal = std.process.fatal; 963 964 var it = std.mem.splitScalar(u8, str, ':'); 965 const filename = it.first(); 966 const line_str, const column_str = if (filename.len > 0) .{ 967 it.next() orelse fatal("line {d}: incomplete error specification", .{l}), 968 it.next() orelse fatal("line {d}: incomplete error specification", .{l}), 969 } else .{ undefined, undefined }; 970 const error_or_note_str = std.mem.trim( 971 u8, 972 it.next() orelse fatal("line {d}: incomplete error specification", .{l}), 973 " ", 974 ); 975 976 const is_note = if (std.mem.eql(u8, error_or_note_str, "error")) 977 false 978 else if (std.mem.eql(u8, error_or_note_str, "note")) 979 true 980 else 981 fatal("line {d}: expeted 'error' or 'note', found '{s}'", .{ l, error_or_note_str }); 982 983 const message = std.mem.trim(u8, it.rest(), " "); 984 if (message.len == 0) fatal("line {d}: empty error message", .{l}); 985 986 return .{ 987 .is_note = is_note, 988 .msg = message, 989 .src = if (filename.len == 0) null else .{ 990 .filename = filename, 991 .line = std.fmt.parseInt(u32, line_str, 10) catch 992 fatal("line {d}: invalid line number '{s}'", .{ l, line_str }), 993 .column = std.fmt.parseInt(u32, column_str, 10) catch 994 fatal("line {d}: invalid column number '{s}'", .{ l, column_str }), 995 }, 996 }; 997 } 998 999 fn rand64(io: Io) u64 { 1000 var x: u64 = undefined; 1001 io.random(@ptrCast(&x)); 1002 return x; 1003 } 1004 1005 /// Calls `std.process.fatal` on error. The error messages are prefixed with `err_prefix`. 1006 fn parseTargetQueryAndBackend(input_str: []const u8, err_prefix: []const u8) struct { std.Target.Query, Backend } { 1007 const fatal = std.process.fatal; 1008 1009 const split_idx = std.mem.findScalarLast(u8, input_str, '-') orelse 1010 fatal("{s}target does not include backend", .{err_prefix}); 1011 1012 const query = input_str[0..split_idx]; 1013 1014 const backend_str = input_str[split_idx + 1 ..]; 1015 const backend: Backend = std.meta.stringToEnum(Backend, backend_str) orelse 1016 fatal("{s}invalid backend '{s}'", .{ err_prefix, backend_str }); 1017 1018 const parsed_query = std.Build.parseTargetQuery(.{ 1019 .arch_os_abi = query, 1020 .object_format = switch (backend) { 1021 .sema, .selfhosted, .llvm => null, 1022 .cbe => "c", 1023 }, 1024 }) catch fatal("{s}invalid target query '{s}'", .{ err_prefix, query }); 1025 1026 return .{ parsed_query, backend }; 1027 } 1028 1029 fn badUsage(comptime fmt: []const u8, args: anytype) noreturn { 1030 std.log.err(fmt ++ "\n{s}", args ++ .{usage}); 1031 std.process.exit(1); 1032 } 1033