| ... | @@ -1,11 +1,12 @@ | ... | @@ -1,11 +1,12 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const fatal = std.process.fatal; | | |
| 3 | const Allocator = std.mem.Allocator; | 2 | const Allocator = std.mem.Allocator; |
| 4 | const Cache = std.Build.Cache; | 3 | const Cache = std.Build.Cache; |
| 5 | | 4 | |
| 6 | const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-zcu] [--debug-link] [--zig-cc-binary /path/to/zig]"; | 5 | const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-zcu] [--debug-link] [--preserve-tmp] [--zig-cc-binary /path/to/zig]"; |
| 7 | | 6 | |
| 8 | pub fn main() !void { | 7 | pub fn main() !void { |
| | 8 | const fatal = std.process.fatal; |
| | 9 | |
| 9 | var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); | 10 | var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| 10 | defer arena_instance.deinit(); | 11 | defer arena_instance.deinit(); |
| 11 | const arena = arena_instance.allocator(); | 12 | const arena = arena_instance.allocator(); |
| ... | @@ -16,6 +17,7 @@ pub fn main() !void { | ... | @@ -16,6 +17,7 @@ pub fn main() !void { |
| 16 | var opt_cc_zig: ?[]const u8 = null; | 17 | var opt_cc_zig: ?[]const u8 = null; |
| 17 | var debug_zcu = false; | 18 | var debug_zcu = false; |
| 18 | var debug_link = false; | 19 | var debug_link = false; |
| | 20 | var preserve_tmp = false; |
| 19 | | 21 | |
| 20 | var arg_it = try std.process.argsWithAllocator(arena); | 22 | var arg_it = try std.process.argsWithAllocator(arena); |
| 21 | _ = arg_it.skip(); | 23 | _ = arg_it.skip(); |
| ... | @@ -27,6 +29,8 @@ pub fn main() !void { | ... | @@ -27,6 +29,8 @@ pub fn main() !void { |
| 27 | debug_zcu = true; | 29 | debug_zcu = true; |
| 28 | } else if (std.mem.eql(u8, arg, "--debug-link")) { | 30 | } else if (std.mem.eql(u8, arg, "--debug-link")) { |
| 29 | debug_link = true; | 31 | debug_link = true; |
| | 32 | } else if (std.mem.eql(u8, arg, "--preserve-tmp")) { |
| | 33 | preserve_tmp = true; |
| 30 | } else if (std.mem.eql(u8, arg, "--zig-cc-binary")) { | 34 | } else if (std.mem.eql(u8, arg, "--zig-cc-binary")) { |
| 31 | opt_cc_zig = arg_it.next() orelse fatal("expect arg after '--zig-cc-binary'\n{s}", .{usage}); | 35 | opt_cc_zig = arg_it.next() orelse fatal("expect arg after '--zig-cc-binary'\n{s}", .{usage}); |
| 32 | } else { | 36 | } else { |
| ... | @@ -48,15 +52,29 @@ pub fn main() !void { | ... | @@ -48,15 +52,29 @@ pub fn main() !void { |
| 48 | const input_file_bytes = try std.fs.cwd().readFileAlloc(arena, input_file_name, std.math.maxInt(u32)); | 52 | const input_file_bytes = try std.fs.cwd().readFileAlloc(arena, input_file_name, std.math.maxInt(u32)); |
| 49 | const case = try Case.parse(arena, input_file_bytes); | 53 | const case = try Case.parse(arena, input_file_bytes); |
| 50 | | 54 | |
| | 55 | // Check now: if there are any targets using the `cbe` backend, we need the lib dir. |
| | 56 | if (opt_lib_dir == null) { |
| | 57 | for (case.targets) |target| { |
| | 58 | if (target.backend == .cbe) { |
| | 59 | fatal("'--zig-lib-dir' requried when using backend 'cbe'", .{}); |
| | 60 | } |
| | 61 | } |
| | 62 | } |
| | 63 | |
| 51 | const prog_node = std.Progress.start(.{}); | 64 | const prog_node = std.Progress.start(.{}); |
| 52 | defer prog_node.end(); | 65 | defer prog_node.end(); |
| 53 | | 66 | |
| 54 | const rand_int = std.crypto.random.int(u64); | 67 | const rand_int = std.crypto.random.int(u64); |
| 55 | const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int); | 68 | const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int); |
| 56 | const tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{}); | 69 | var tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{}); |
| 57 | | 70 | defer { |
| 58 | const child_prog_node = prog_node.start("zig build-exe", 0); | 71 | tmp_dir.close(); |
| 59 | defer child_prog_node.end(); | 72 | if (!preserve_tmp) { |
| | 73 | std.fs.cwd().deleteTree(tmp_dir_path) catch |err| { |
| | 74 | std.log.warn("failed to delete tree '{s}': {s}", .{ tmp_dir_path, @errorName(err) }); |
| | 75 | }; |
| | 76 | } |
| | 77 | } |
| 60 | | 78 | |
| 61 | // Convert paths to be relative to the cwd of the subprocess. | 79 | // Convert paths to be relative to the cwd of the subprocess. |
| 62 | const resolved_zig_exe = try std.fs.path.relative(arena, tmp_dir_path, zig_exe); | 80 | const resolved_zig_exe = try std.fs.path.relative(arena, tmp_dir_path, zig_exe); |
| ... | @@ -65,10 +83,21 @@ pub fn main() !void { | ... | @@ -65,10 +83,21 @@ pub fn main() !void { |
| 65 | else | 83 | else |
| 66 | null; | 84 | null; |
| 67 | | 85 | |
| | 86 | const host = try std.zig.system.resolveTargetQuery(.{}); |
| | 87 | |
| 68 | const debug_log_verbose = debug_zcu or debug_link; | 88 | const debug_log_verbose = debug_zcu or debug_link; |
| 69 | | 89 | |
| 70 | for (case.targets) |target| { | 90 | for (case.targets) |target| { |
| 71 | std.log.scoped(.status).info("target: '{s}-{s}'", .{ target.query, @tagName(target.backend) }); | 91 | const target_prog_node = node: { |
| | 92 | var name_buf: [std.Progress.Node.max_name_len]u8 = undefined; |
| | 93 | const name = std.fmt.bufPrint(&name_buf, "{s}-{s}", .{ target.query, @tagName(target.backend) }) catch &name_buf; |
| | 94 | break :node prog_node.start(name, case.updates.len); |
| | 95 | }; |
| | 96 | defer target_prog_node.end(); |
| | 97 | |
| | 98 | if (debug_log_verbose) { |
| | 99 | std.log.scoped(.status).info("target: '{s}-{s}'", .{ target.query, @tagName(target.backend) }); |
| | 100 | } |
| 72 | | 101 | |
| 73 | var child_args: std.ArrayListUnmanaged([]const u8) = .empty; | 102 | var child_args: std.ArrayListUnmanaged([]const u8) = .empty; |
| 74 | try child_args.appendSlice(arena, &.{ | 103 | try child_args.appendSlice(arena, &.{ |
| ... | @@ -81,7 +110,7 @@ pub fn main() !void { | ... | @@ -81,7 +110,7 @@ pub fn main() !void { |
| 81 | "--cache-dir", | 110 | "--cache-dir", |
| 82 | ".local-cache", | 111 | ".local-cache", |
| 83 | "--global-cache-dir", | 112 | "--global-cache-dir", |
| 84 | ".global_cache", | 113 | ".global-cache", |
| 85 | "--listen=-", | 114 | "--listen=-", |
| 86 | }); | 115 | }); |
| 87 | if (opt_resolved_lib_dir) |resolved_lib_dir| { | 116 | if (opt_resolved_lib_dir) |resolved_lib_dir| { |
| ... | @@ -100,11 +129,14 @@ pub fn main() !void { | ... | @@ -100,11 +129,14 @@ pub fn main() !void { |
| 100 | try child_args.appendSlice(arena, &.{ "--debug-log", "link", "--debug-log", "link_state", "--debug-log", "link_relocs" }); | 129 | try child_args.appendSlice(arena, &.{ "--debug-log", "link", "--debug-log", "link_state", "--debug-log", "link_relocs" }); |
| 101 | } | 130 | } |
| 102 | | 131 | |
| | 132 | const zig_prog_node = target_prog_node.start("zig build-exe", 0); |
| | 133 | defer zig_prog_node.end(); |
| | 134 | |
| 103 | var child = std.process.Child.init(child_args.items, arena); | 135 | var child = std.process.Child.init(child_args.items, arena); |
| 104 | child.stdin_behavior = .Pipe; | 136 | child.stdin_behavior = .Pipe; |
| 105 | child.stdout_behavior = .Pipe; | 137 | child.stdout_behavior = .Pipe; |
| 106 | child.stderr_behavior = .Pipe; | 138 | child.stderr_behavior = .Pipe; |
| 107 | child.progress_node = child_prog_node; | 139 | child.progress_node = zig_prog_node; |
| 108 | child.cwd_dir = tmp_dir; | 140 | child.cwd_dir = tmp_dir; |
| 109 | child.cwd = tmp_dir_path; | 141 | child.cwd = tmp_dir_path; |
| 110 | | 142 | |
| ... | @@ -121,7 +153,7 @@ pub fn main() !void { | ... | @@ -121,7 +153,7 @@ pub fn main() !void { |
| 121 | "-target", | 153 | "-target", |
| 122 | target.query, | 154 | target.query, |
| 123 | "-I", | 155 | "-I", |
| 124 | opt_resolved_lib_dir orelse fatal("'--zig-lib-dir' required when using backend 'cbe'", .{}), | 156 | opt_resolved_lib_dir.?, // verified earlier |
| 125 | "-o", | 157 | "-o", |
| 126 | }); | 158 | }); |
| 127 | } | 159 | } |
| ... | @@ -129,11 +161,13 @@ pub fn main() !void { | ... | @@ -129,11 +161,13 @@ pub fn main() !void { |
| 129 | var eval: Eval = .{ | 161 | var eval: Eval = .{ |
| 130 | .arena = arena, | 162 | .arena = arena, |
| 131 | .case = case, | 163 | .case = case, |
| | 164 | .host = host, |
| 132 | .target = target, | 165 | .target = target, |
| 133 | .tmp_dir = tmp_dir, | 166 | .tmp_dir = tmp_dir, |
| 134 | .tmp_dir_path = tmp_dir_path, | 167 | .tmp_dir_path = tmp_dir_path, |
| 135 | .child = &child, | 168 | .child = &child, |
| 136 | .allow_stderr = debug_log_verbose, | 169 | .allow_stderr = debug_log_verbose, |
| | 170 | .preserve_tmp_on_fatal = preserve_tmp, |
| 137 | .cc_child_args = &cc_child_args, | 171 | .cc_child_args = &cc_child_args, |
| 138 | }; | 172 | }; |
| 139 | | 173 | |
| ... | @@ -146,7 +180,7 @@ pub fn main() !void { | ... | @@ -146,7 +180,7 @@ pub fn main() !void { |
| 146 | defer poller.deinit(); | 180 | defer poller.deinit(); |
| 147 | | 181 | |
| 148 | for (case.updates) |update| { | 182 | for (case.updates) |update| { |
| 149 | var update_node = prog_node.start(update.name, 0); | 183 | var update_node = target_prog_node.start(update.name, 0); |
| 150 | defer update_node.end(); | 184 | defer update_node.end(); |
| 151 | | 185 | |
| 152 | if (debug_log_verbose) { | 186 | if (debug_log_verbose) { |
| ... | @@ -160,18 +194,20 @@ pub fn main() !void { | ... | @@ -160,18 +194,20 @@ pub fn main() !void { |
| 160 | | 194 | |
| 161 | try eval.end(&poller); | 195 | try eval.end(&poller); |
| 162 | | 196 | |
| 163 | waitChild(&child); | 197 | waitChild(&child, &eval); |
| 164 | } | 198 | } |
| 165 | } | 199 | } |
| 166 | | 200 | |
| 167 | const Eval = struct { | 201 | const Eval = struct { |
| 168 | arena: Allocator, | 202 | arena: Allocator, |
| | 203 | host: std.Target, |
| 169 | case: Case, | 204 | case: Case, |
| 170 | target: Case.Target, | 205 | target: Case.Target, |
| 171 | tmp_dir: std.fs.Dir, | 206 | tmp_dir: std.fs.Dir, |
| 172 | tmp_dir_path: []const u8, | 207 | tmp_dir_path: []const u8, |
| 173 | child: *std.process.Child, | 208 | child: *std.process.Child, |
| 174 | allow_stderr: bool, | 209 | allow_stderr: bool, |
| | 210 | preserve_tmp_on_fatal: bool, |
| 175 | /// When `target.backend == .cbe`, this contains the first few arguments to `zig cc` to build the generated binary. | 211 | /// When `target.backend == .cbe`, this contains the first few arguments to `zig cc` to build the generated binary. |
| 176 | /// The arguments `out.c in.c` must be appended before spawning the subprocess. | 212 | /// The arguments `out.c in.c` must be appended before spawning the subprocess. |
| 177 | cc_child_args: *std.ArrayListUnmanaged([]const u8), | 213 | cc_child_args: *std.ArrayListUnmanaged([]const u8), |
| ... | @@ -186,12 +222,12 @@ const Eval = struct { | ... | @@ -186,12 +222,12 @@ const Eval = struct { |
| 186 | .sub_path = full_contents.name, | 222 | .sub_path = full_contents.name, |
| 187 | .data = full_contents.bytes, | 223 | .data = full_contents.bytes, |
| 188 | }) catch |err| { | 224 | }) catch |err| { |
| 189 | fatal("failed to update '{s}': {s}", .{ full_contents.name, @errorName(err) }); | 225 | eval.fatal("failed to update '{s}': {s}", .{ full_contents.name, @errorName(err) }); |
| 190 | }; | 226 | }; |
| 191 | } | 227 | } |
| 192 | for (update.deletes) |doomed_name| { | 228 | for (update.deletes) |doomed_name| { |
| 193 | eval.tmp_dir.deleteFile(doomed_name) catch |err| { | 229 | eval.tmp_dir.deleteFile(doomed_name) catch |err| { |
| 194 | fatal("failed to delete '{s}': {s}", .{ doomed_name, @errorName(err) }); | 230 | eval.fatal("failed to delete '{s}': {s}", .{ doomed_name, @errorName(err) }); |
| 195 | }; | 231 | }; |
| 196 | } | 232 | } |
| 197 | } | 233 | } |
| ... | @@ -233,7 +269,7 @@ const Eval = struct { | ... | @@ -233,7 +269,7 @@ const Eval = struct { |
| 233 | if (eval.allow_stderr) { | 269 | if (eval.allow_stderr) { |
| 234 | std.log.info("error_bundle included stderr:\n{s}", .{stderr_data}); | 270 | std.log.info("error_bundle included stderr:\n{s}", .{stderr_data}); |
| 235 | } else { | 271 | } else { |
| 236 | fatal("error_bundle included unexpected stderr:\n{s}", .{stderr_data}); | 272 | eval.fatal("error_bundle included unexpected stderr:\n{s}", .{stderr_data}); |
| 237 | } | 273 | } |
| 238 | } | 274 | } |
| 239 | if (result_error_bundle.errorMessageCount() != 0) { | 275 | if (result_error_bundle.errorMessageCount() != 0) { |
| ... | @@ -252,7 +288,7 @@ const Eval = struct { | ... | @@ -252,7 +288,7 @@ const Eval = struct { |
| 252 | if (eval.allow_stderr) { | 288 | if (eval.allow_stderr) { |
| 253 | std.log.info("emit_digest included stderr:\n{s}", .{stderr_data}); | 289 | std.log.info("emit_digest included stderr:\n{s}", .{stderr_data}); |
| 254 | } else { | 290 | } else { |
| 255 | fatal("emit_digest included unexpected stderr:\n{s}", .{stderr_data}); | 291 | eval.fatal("emit_digest included unexpected stderr:\n{s}", .{stderr_data}); |
| 256 | } | 292 | } |
| 257 | } | 293 | } |
| 258 | | 294 | |
| ... | @@ -268,14 +304,7 @@ const Eval = struct { | ... | @@ -268,14 +304,7 @@ const Eval = struct { |
| 268 | const name = std.fs.path.stem(std.fs.path.basename(eval.case.root_source_file)); | 304 | const name = std.fs.path.stem(std.fs.path.basename(eval.case.root_source_file)); |
| 269 | const bin_name = try std.zig.binNameAlloc(arena, .{ | 305 | const bin_name = try std.zig.binNameAlloc(arena, .{ |
| 270 | .root_name = name, | 306 | .root_name = name, |
| 271 | .target = try std.zig.system.resolveTargetQuery(try std.Build.parseTargetQuery(.{ | 307 | .target = eval.target.resolved, |
| 272 | .arch_os_abi = eval.target.query, | | |
| 273 | .object_format = switch (eval.target.backend) { | | |
| 274 | .sema => unreachable, | | |
| 275 | .selfhosted, .llvm => null, | | |
| 276 | .cbe => "c", | | |
| 277 | }, | | |
| 278 | })), | | |
| 279 | .output_mode = .Exe, | 308 | .output_mode = .Exe, |
| 280 | }); | 309 | }); |
| 281 | const bin_path = try std.fs.path.join(arena, &.{ result_dir, bin_name }); | 310 | const bin_path = try std.fs.path.join(arena, &.{ result_dir, bin_name }); |
| ... | @@ -296,16 +325,15 @@ const Eval = struct { | ... | @@ -296,16 +325,15 @@ const Eval = struct { |
| 296 | if (eval.allow_stderr) { | 325 | if (eval.allow_stderr) { |
| 297 | std.log.info("update '{s}' included stderr:\n{s}", .{ update.name, stderr_data }); | 326 | std.log.info("update '{s}' included stderr:\n{s}", .{ update.name, stderr_data }); |
| 298 | } else { | 327 | } else { |
| 299 | fatal("update '{s}' failed:\n{s}", .{ update.name, stderr_data }); | 328 | eval.fatal("update '{s}' failed:\n{s}", .{ update.name, stderr_data }); |
| 300 | } | 329 | } |
| 301 | } | 330 | } |
| 302 | | 331 | |
| 303 | waitChild(eval.child); | 332 | waitChild(eval.child, eval); |
| 304 | fatal("update '{s}': compiler failed to send error_bundle or emit_bin_path", .{update.name}); | 333 | eval.fatal("update '{s}': compiler failed to send error_bundle or emit_bin_path", .{update.name}); |
| 305 | } | 334 | } |
| 306 | | 335 | |
| 307 | fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void { | 336 | fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void { |
| 308 | _ = eval; | | |
| 309 | switch (update.outcome) { | 337 | switch (update.outcome) { |
| 310 | .unknown => return, | 338 | .unknown => return, |
| 311 | .compile_errors => |expected_errors| { | 339 | .compile_errors => |expected_errors| { |
| ... | @@ -317,7 +345,7 @@ const Eval = struct { | ... | @@ -317,7 +345,7 @@ const Eval = struct { |
| 317 | .stdout, .exit_code => { | 345 | .stdout, .exit_code => { |
| 318 | const color: std.zig.Color = .auto; | 346 | const color: std.zig.Color = .auto; |
| 319 | error_bundle.renderToStdErr(color.renderOptions()); | 347 | error_bundle.renderToStdErr(color.renderOptions()); |
| 320 | fatal("update '{s}': unexpected compile errors", .{update.name}); | 348 | eval.fatal("update '{s}': unexpected compile errors", .{update.name}); |
| 321 | }, | 349 | }, |
| 322 | } | 350 | } |
| 323 | } | 351 | } |
| ... | @@ -325,7 +353,7 @@ const Eval = struct { | ... | @@ -325,7 +353,7 @@ const Eval = struct { |
| 325 | fn checkSuccessOutcome(eval: *Eval, update: Case.Update, opt_emitted_path: ?[]const u8, prog_node: std.Progress.Node) !void { | 353 | fn checkSuccessOutcome(eval: *Eval, update: Case.Update, opt_emitted_path: ?[]const u8, prog_node: std.Progress.Node) !void { |
| 326 | switch (update.outcome) { | 354 | switch (update.outcome) { |
| 327 | .unknown => return, | 355 | .unknown => return, |
| 328 | .compile_errors => fatal("expected compile errors but compilation incorrectly succeeded", .{}), | 356 | .compile_errors => eval.fatal("expected compile errors but compilation incorrectly succeeded", .{}), |
| 329 | .stdout, .exit_code => {}, | 357 | .stdout, .exit_code => {}, |
| 330 | } | 358 | } |
| 331 | const emitted_path = opt_emitted_path orelse { | 359 | const emitted_path = opt_emitted_path orelse { |
| ... | @@ -344,27 +372,73 @@ const Eval = struct { | ... | @@ -344,27 +372,73 @@ const Eval = struct { |
| 344 | }, | 372 | }, |
| 345 | }; | 373 | }; |
| 346 | | 374 | |
| | 375 | var argv_buf: [2][]const u8 = undefined; |
| | 376 | const argv: []const []const u8, const is_foreign: bool = switch (std.zig.system.getExternalExecutor( |
| | 377 | eval.host, |
| | 378 | &eval.target.resolved, |
| | 379 | .{ .link_libc = eval.target.backend == .cbe }, |
| | 380 | )) { |
| | 381 | .bad_dl, .bad_os_or_cpu => { |
| | 382 | // This binary cannot be executed on this host. |
| | 383 | if (eval.allow_stderr) { |
| | 384 | std.log.warn("skipping execution because host '{s}' cannot execute binaries for foreign target '{s}'", .{ |
| | 385 | try eval.host.zigTriple(eval.arena), |
| | 386 | try eval.target.resolved.zigTriple(eval.arena), |
| | 387 | }); |
| | 388 | } |
| | 389 | return; |
| | 390 | }, |
| | 391 | .native, .rosetta => argv: { |
| | 392 | argv_buf[0] = binary_path; |
| | 393 | break :argv .{ argv_buf[0..1], false }; |
| | 394 | }, |
| | 395 | .qemu, .wine, .wasmtime, .darling => |executor_cmd| argv: { |
| | 396 | argv_buf[0] = executor_cmd; |
| | 397 | argv_buf[1] = binary_path; |
| | 398 | break :argv .{ argv_buf[0..2], true }; |
| | 399 | }, |
| | 400 | }; |
| | 401 | |
| | 402 | const run_prog_node = prog_node.start("run generated executable", 0); |
| | 403 | defer run_prog_node.end(); |
| | 404 | |
| 347 | const result = std.process.Child.run(.{ | 405 | const result = std.process.Child.run(.{ |
| 348 | .allocator = eval.arena, | 406 | .allocator = eval.arena, |
| 349 | .argv = &.{binary_path}, | 407 | .argv = argv, |
| 350 | .cwd_dir = eval.tmp_dir, | 408 | .cwd_dir = eval.tmp_dir, |
| 351 | .cwd = eval.tmp_dir_path, | 409 | .cwd = eval.tmp_dir_path, |
| 352 | }) catch |err| { | 410 | }) catch |err| { |
| 353 | fatal("update '{s}': failed to run the generated executable '{s}': {s}", .{ | 411 | if (is_foreign) { |
| | 412 | // Chances are the foreign executor isn't available. Skip this evaluation. |
| | 413 | if (eval.allow_stderr) { |
| | 414 | std.log.warn("update '{s}': skipping execution of '{s}' via executor for foreign target '{s}': {s}", .{ |
| | 415 | update.name, |
| | 416 | binary_path, |
| | 417 | try eval.target.resolved.zigTriple(eval.arena), |
| | 418 | @errorName(err), |
| | 419 | }); |
| | 420 | } |
| | 421 | return; |
| | 422 | } |
| | 423 | eval.fatal("update '{s}': failed to run the generated executable '{s}': {s}", .{ |
| 354 | update.name, binary_path, @errorName(err), | 424 | update.name, binary_path, @errorName(err), |
| 355 | }); | 425 | }); |
| 356 | }; | 426 | }; |
| 357 | if (result.stderr.len != 0) { | 427 | |
| | 428 | // Some executors (looking at you, Wine) like throwing some stderr in, just for fun. |
| | 429 | // Therefore, we'll ignore stderr when using a foreign executor. |
| | 430 | if (!is_foreign and result.stderr.len != 0) { |
| 358 | std.log.err("update '{s}': generated executable '{s}' had unexpected stderr:\n{s}", .{ | 431 | std.log.err("update '{s}': generated executable '{s}' had unexpected stderr:\n{s}", .{ |
| 359 | update.name, binary_path, result.stderr, | 432 | update.name, binary_path, result.stderr, |
| 360 | }); | 433 | }); |
| 361 | } | 434 | } |
| | 435 | |
| 362 | switch (result.term) { | 436 | switch (result.term) { |
| 363 | .Exited => |code| switch (update.outcome) { | 437 | .Exited => |code| switch (update.outcome) { |
| 364 | .unknown, .compile_errors => unreachable, | 438 | .unknown, .compile_errors => unreachable, |
| 365 | .stdout => |expected_stdout| { | 439 | .stdout => |expected_stdout| { |
| 366 | if (code != 0) { | 440 | if (code != 0) { |
| 367 | fatal("update '{s}': generated executable '{s}' failed with code {d}", .{ | 441 | eval.fatal("update '{s}': generated executable '{s}' failed with code {d}", .{ |
| 368 | update.name, binary_path, code, | 442 | update.name, binary_path, code, |
| 369 | }); | 443 | }); |
| 370 | } | 444 | } |
| ... | @@ -373,12 +447,13 @@ const Eval = struct { | ... | @@ -373,12 +447,13 @@ const Eval = struct { |
| 373 | .exit_code => |expected_code| try std.testing.expectEqual(expected_code, result.term.Exited), | 447 | .exit_code => |expected_code| try std.testing.expectEqual(expected_code, result.term.Exited), |
| 374 | }, | 448 | }, |
| 375 | .Signal, .Stopped, .Unknown => { | 449 | .Signal, .Stopped, .Unknown => { |
| 376 | fatal("update '{s}': generated executable '{s}' terminated unexpectedly", .{ | 450 | eval.fatal("update '{s}': generated executable '{s}' terminated unexpectedly", .{ |
| 377 | update.name, binary_path, | 451 | update.name, binary_path, |
| 378 | }); | 452 | }); |
| 379 | }, | 453 | }, |
| 380 | } | 454 | } |
| 381 | if (result.stderr.len != 0) std.process.exit(1); | 455 | |
| | 456 | if (!is_foreign and result.stderr.len != 0) std.process.exit(1); |
| 382 | } | 457 | } |
| 383 | | 458 | |
| 384 | fn requestUpdate(eval: *Eval) !void { | 459 | fn requestUpdate(eval: *Eval) !void { |
| ... | @@ -390,7 +465,7 @@ const Eval = struct { | ... | @@ -390,7 +465,7 @@ const Eval = struct { |
| 390 | } | 465 | } |
| 391 | | 466 | |
| 392 | fn end(eval: *Eval, poller: *Poller) !void { | 467 | fn end(eval: *Eval, poller: *Poller) !void { |
| 393 | requestExit(eval.child); | 468 | requestExit(eval.child, eval); |
| 394 | | 469 | |
| 395 | const Header = std.zig.Server.Message.Header; | 470 | const Header = std.zig.Server.Message.Header; |
| 396 | const stdout = poller.fifo(.stdout); | 471 | const stdout = poller.fifo(.stdout); |
| ... | @@ -410,7 +485,7 @@ const Eval = struct { | ... | @@ -410,7 +485,7 @@ const Eval = struct { |
| 410 | | 485 | |
| 411 | if (stderr.readableLength() > 0) { | 486 | if (stderr.readableLength() > 0) { |
| 412 | const stderr_data = try stderr.toOwnedSlice(); | 487 | const stderr_data = try stderr.toOwnedSlice(); |
| 413 | fatal("unexpected stderr:\n{s}", .{stderr_data}); | 488 | eval.fatal("unexpected stderr:\n{s}", .{stderr_data}); |
| 414 | } | 489 | } |
| 415 | } | 490 | } |
| 416 | | 491 | |
| ... | @@ -430,7 +505,7 @@ const Eval = struct { | ... | @@ -430,7 +505,7 @@ const Eval = struct { |
| 430 | .cwd = eval.tmp_dir_path, | 505 | .cwd = eval.tmp_dir_path, |
| 431 | .progress_node = child_prog_node, | 506 | .progress_node = child_prog_node, |
| 432 | }) catch |err| { | 507 | }) catch |err| { |
| 433 | fatal("update '{s}': failed to spawn zig cc for '{s}': {s}", .{ | 508 | eval.fatal("update '{s}': failed to spawn zig cc for '{s}': {s}", .{ |
| 434 | update.name, c_path, @errorName(err), | 509 | update.name, c_path, @errorName(err), |
| 435 | }); | 510 | }); |
| 436 | }; | 511 | }; |
| ... | @@ -441,7 +516,7 @@ const Eval = struct { | ... | @@ -441,7 +516,7 @@ const Eval = struct { |
| 441 | update.name, result.stderr, | 516 | update.name, result.stderr, |
| 442 | }); | 517 | }); |
| 443 | } | 518 | } |
| 444 | fatal("update '{s}': zig cc for '{s}' failed with code {d}", .{ | 519 | eval.fatal("update '{s}': zig cc for '{s}' failed with code {d}", .{ |
| 445 | update.name, c_path, code, | 520 | update.name, c_path, code, |
| 446 | }); | 521 | }); |
| 447 | }, | 522 | }, |
| ... | @@ -451,12 +526,22 @@ const Eval = struct { | ... | @@ -451,12 +526,22 @@ const Eval = struct { |
| 451 | update.name, result.stderr, | 526 | update.name, result.stderr, |
| 452 | }); | 527 | }); |
| 453 | } | 528 | } |
| 454 | fatal("update '{s}': zig cc for '{s}' terminated unexpectedly", .{ | 529 | eval.fatal("update '{s}': zig cc for '{s}' terminated unexpectedly", .{ |
| 455 | update.name, c_path, | 530 | update.name, c_path, |
| 456 | }); | 531 | }); |
| 457 | }, | 532 | }, |
| 458 | } | 533 | } |
| 459 | } | 534 | } |
| | 535 | |
| | 536 | fn fatal(eval: *Eval, comptime fmt: []const u8, args: anytype) noreturn { |
| | 537 | eval.tmp_dir.close(); |
| | 538 | if (!eval.preserve_tmp_on_fatal) { |
| | 539 | std.fs.cwd().deleteTree(eval.tmp_dir_path) catch |err| { |
| | 540 | std.log.warn("failed to delete tree '{s}': {s}", .{ eval.tmp_dir_path, @errorName(err) }); |
| | 541 | }; |
| | 542 | } |
| | 543 | std.process.fatal(fmt, args); |
| | 544 | } |
| 460 | }; | 545 | }; |
| 461 | | 546 | |
| 462 | const Case = struct { | 547 | const Case = struct { |
| ... | @@ -466,6 +551,7 @@ const Case = struct { | ... | @@ -466,6 +551,7 @@ const Case = struct { |
| 466 | | 551 | |
| 467 | const Target = struct { | 552 | const Target = struct { |
| 468 | query: []const u8, | 553 | query: []const u8, |
| | 554 | resolved: std.Target, |
| 469 | backend: Backend, | 555 | backend: Backend, |
| 470 | const Backend = enum { | 556 | const Backend = enum { |
| 471 | /// Run semantic analysis only. Runtime output will not be tested, but we still verify | 557 | /// Run semantic analysis only. Runtime output will not be tested, but we still verify |
| ... | @@ -511,6 +597,8 @@ const Case = struct { | ... | @@ -511,6 +597,8 @@ const Case = struct { |
| 511 | }; | 597 | }; |
| 512 | | 598 | |
| 513 | fn parse(arena: Allocator, bytes: []const u8) !Case { | 599 | fn parse(arena: Allocator, bytes: []const u8) !Case { |
| | 600 | const fatal = std.process.fatal; |
| | 601 | |
| 514 | var targets: std.ArrayListUnmanaged(Target) = .empty; | 602 | var targets: std.ArrayListUnmanaged(Target) = .empty; |
| 515 | var updates: std.ArrayListUnmanaged(Update) = .empty; | 603 | var updates: std.ArrayListUnmanaged(Update) = .empty; |
| 516 | var changes: std.ArrayListUnmanaged(FullContents) = .empty; | 604 | var changes: std.ArrayListUnmanaged(FullContents) = .empty; |
| ... | @@ -521,18 +609,32 @@ const Case = struct { | ... | @@ -521,18 +609,32 @@ const Case = struct { |
| 521 | if (std.mem.startsWith(u8, line, "#")) { | 609 | if (std.mem.startsWith(u8, line, "#")) { |
| 522 | var line_it = std.mem.splitScalar(u8, line, '='); | 610 | var line_it = std.mem.splitScalar(u8, line, '='); |
| 523 | const key = line_it.first()[1..]; | 611 | const key = line_it.first()[1..]; |
| 524 | const val = line_it.rest(); | 612 | const val = std.mem.trimRight(u8, line_it.rest(), "\r"); // windows moment |
| 525 | if (val.len == 0) { | 613 | if (val.len == 0) { |
| 526 | fatal("line {d}: missing value", .{line_n}); | 614 | fatal("line {d}: missing value", .{line_n}); |
| 527 | } else if (std.mem.eql(u8, key, "target")) { | 615 | } else if (std.mem.eql(u8, key, "target")) { |
| 528 | const split_idx = std.mem.lastIndexOfScalar(u8, val, '-') orelse | 616 | const split_idx = std.mem.lastIndexOfScalar(u8, val, '-') orelse |
| 529 | fatal("line {d}: target does not include backend", .{line_n}); | 617 | fatal("line {d}: target does not include backend", .{line_n}); |
| | 618 | |
| 530 | const query = val[0..split_idx]; | 619 | const query = val[0..split_idx]; |
| | 620 | |
| 531 | const backend_str = val[split_idx + 1 ..]; | 621 | const backend_str = val[split_idx + 1 ..]; |
| 532 | const backend: Target.Backend = std.meta.stringToEnum(Target.Backend, backend_str) orelse | 622 | const backend: Target.Backend = std.meta.stringToEnum(Target.Backend, backend_str) orelse |
| 533 | fatal("line {d}: invalid backend '{s}'", .{ line_n, backend_str }); | 623 | fatal("line {d}: invalid backend '{s}'", .{ line_n, backend_str }); |
| | 624 | |
| | 625 | const parsed_query = std.Build.parseTargetQuery(.{ |
| | 626 | .arch_os_abi = query, |
| | 627 | .object_format = switch (backend) { |
| | 628 | .sema, .selfhosted, .llvm => null, |
| | 629 | .cbe => "c", |
| | 630 | }, |
| | 631 | }) catch fatal("line {d}: invalid target query '{s}'", .{ line_n, query }); |
| | 632 | |
| | 633 | const resolved = try std.zig.system.resolveTargetQuery(parsed_query); |
| | 634 | |
| 534 | try targets.append(arena, .{ | 635 | try targets.append(arena, .{ |
| 535 | .query = query, | 636 | .query = query, |
| | 637 | .resolved = resolved, |
| 536 | .backend = backend, | 638 | .backend = backend, |
| 537 | }); | 639 | }); |
| 538 | } else if (std.mem.eql(u8, key, "update")) { | 640 | } else if (std.mem.eql(u8, key, "update")) { |
| ... | @@ -603,7 +705,7 @@ const Case = struct { | ... | @@ -603,7 +705,7 @@ const Case = struct { |
| 603 | } | 705 | } |
| 604 | }; | 706 | }; |
| 605 | | 707 | |
| 606 | fn requestExit(child: *std.process.Child) void { | 708 | fn requestExit(child: *std.process.Child, eval: *Eval) void { |
| 607 | if (child.stdin == null) return; | 709 | if (child.stdin == null) return; |
| 608 | | 710 | |
| 609 | const header: std.zig.Client.Message.Header = .{ | 711 | const header: std.zig.Client.Message.Header = .{ |
| ... | @@ -612,7 +714,7 @@ fn requestExit(child: *std.process.Child) void { | ... | @@ -612,7 +714,7 @@ fn requestExit(child: *std.process.Child) void { |
| 612 | }; | 714 | }; |
| 613 | child.stdin.?.writeAll(std.mem.asBytes(&header)) catch |err| switch (err) { | 715 | child.stdin.?.writeAll(std.mem.asBytes(&header)) catch |err| switch (err) { |
| 614 | error.BrokenPipe => {}, | 716 | error.BrokenPipe => {}, |
| 615 | else => fatal("failed to send exit: {s}", .{@errorName(err)}), | 717 | else => eval.fatal("failed to send exit: {s}", .{@errorName(err)}), |
| 616 | }; | 718 | }; |
| 617 | | 719 | |
| 618 | // Send EOF to stdin. | 720 | // Send EOF to stdin. |
| ... | @@ -620,11 +722,11 @@ fn requestExit(child: *std.process.Child) void { | ... | @@ -620,11 +722,11 @@ fn requestExit(child: *std.process.Child) void { |
| 620 | child.stdin = null; | 722 | child.stdin = null; |
| 621 | } | 723 | } |
| 622 | | 724 | |
| 623 | fn waitChild(child: *std.process.Child) void { | 725 | fn waitChild(child: *std.process.Child, eval: *Eval) void { |
| 624 | requestExit(child); | 726 | requestExit(child, eval); |
| 625 | const term = child.wait() catch |err| fatal("child process failed: {s}", .{@errorName(err)}); | 727 | const term = child.wait() catch |err| eval.fatal("child process failed: {s}", .{@errorName(err)}); |
| 626 | switch (term) { | 728 | switch (term) { |
| 627 | .Exited => |code| if (code != 0) fatal("compiler failed with code {d}", .{code}), | 729 | .Exited => |code| if (code != 0) eval.fatal("compiler failed with code {d}", .{code}), |
| 628 | .Signal, .Stopped, .Unknown => fatal("compiler terminated unexpectedly", .{}), | 730 | .Signal, .Stopped, .Unknown => eval.fatal("compiler terminated unexpectedly", .{}), |
| 629 | } | 731 | } |
| 630 | } | 732 | } |