| ... | @@ -2,7 +2,13 @@ const std = @import("std"); | ... | @@ -2,7 +2,13 @@ const std = @import("std"); |
| 2 | const fatal = std.process.fatal; | 2 | const fatal = std.process.fatal; |
| 3 | const Allocator = std.mem.Allocator; | 3 | const Allocator = std.mem.Allocator; |
| 4 | | 4 | |
| 5 | const usage = "usage: incr-check <zig binary path> <input file> [-fno-emit-bin] [--zig-lib-dir lib] [--debug-zcu]"; | 5 | const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-zcu] [--emit none|bin|c] [--zig-cc-binary /path/to/zig]"; |
| | 6 | |
| | 7 | const EmitMode = enum { |
| | 8 | none, |
| | 9 | bin, |
| | 10 | c, |
| | 11 | }; |
| 6 | | 12 | |
| 7 | pub fn main() !void { | 13 | pub fn main() !void { |
| 8 | var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); | 14 | var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| ... | @@ -12,19 +18,24 @@ pub fn main() !void { | ... | @@ -12,19 +18,24 @@ pub fn main() !void { |
| 12 | var opt_zig_exe: ?[]const u8 = null; | 18 | var opt_zig_exe: ?[]const u8 = null; |
| 13 | var opt_input_file_name: ?[]const u8 = null; | 19 | var opt_input_file_name: ?[]const u8 = null; |
| 14 | var opt_lib_dir: ?[]const u8 = null; | 20 | var opt_lib_dir: ?[]const u8 = null; |
| 15 | var no_bin = false; | 21 | var opt_cc_zig: ?[]const u8 = null; |
| | 22 | var emit: EmitMode = .bin; |
| 16 | var debug_zcu = false; | 23 | var debug_zcu = false; |
| 17 | | 24 | |
| 18 | var arg_it = try std.process.argsWithAllocator(arena); | 25 | var arg_it = try std.process.argsWithAllocator(arena); |
| 19 | _ = arg_it.skip(); | 26 | _ = arg_it.skip(); |
| 20 | while (arg_it.next()) |arg| { | 27 | while (arg_it.next()) |arg| { |
| 21 | if (arg.len > 0 and arg[0] == '-') { | 28 | if (arg.len > 0 and arg[0] == '-') { |
| 22 | if (std.mem.eql(u8, arg, "-fno-emit-bin")) { | 29 | if (std.mem.eql(u8, arg, "--emit")) { |
| 23 | no_bin = true; | 30 | const emit_str = arg_it.next() orelse fatal("expected arg after '--emit'\n{s}", .{usage}); |
| 24 | } else if (std.mem.eql(u8, arg, "--debug-zcu")) { | 31 | emit = std.meta.stringToEnum(EmitMode, emit_str) orelse |
| 25 | debug_zcu = true; | 32 | fatal("invalid emit mode '{s}'\n{s}", .{ emit_str, usage }); |
| 26 | } else if (std.mem.eql(u8, arg, "--zig-lib-dir")) { | 33 | } else if (std.mem.eql(u8, arg, "--zig-lib-dir")) { |
| 27 | opt_lib_dir = arg_it.next() orelse fatal("expected arg after '--zig-lib-dir'\n{s}", .{usage}); | 34 | opt_lib_dir = arg_it.next() orelse fatal("expected arg after '--zig-lib-dir'\n{s}", .{usage}); |
| | 35 | } else if (std.mem.eql(u8, arg, "--debug-zcu")) { |
| | 36 | debug_zcu = true; |
| | 37 | } else if (std.mem.eql(u8, arg, "--zig-cc-binary")) { |
| | 38 | opt_cc_zig = arg_it.next() orelse fatal("expect arg after '--zig-cc-binary'\n{s}", .{usage}); |
| 28 | } else { | 39 | } else { |
| 29 | fatal("unknown option '{s}'\n{s}", .{ arg, usage }); | 40 | fatal("unknown option '{s}'\n{s}", .{ arg, usage }); |
| 30 | } | 41 | } |
| ... | @@ -51,20 +62,19 @@ pub fn main() !void { | ... | @@ -51,20 +62,19 @@ pub fn main() !void { |
| 51 | const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int); | 62 | const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int); |
| 52 | const tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{}); | 63 | const tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{}); |
| 53 | | 64 | |
| 54 | if (opt_lib_dir) |lib_dir| { | | |
| 55 | if (!std.fs.path.isAbsolute(lib_dir)) { | | |
| 56 | // The cwd of the subprocess is within the tmp dir, so prepend `..` to the path. | | |
| 57 | opt_lib_dir = try std.fs.path.join(arena, &.{ "..", lib_dir }); | | |
| 58 | } | | |
| 59 | } | | |
| 60 | | | |
| 61 | const child_prog_node = prog_node.start("zig build-exe", 0); | 65 | const child_prog_node = prog_node.start("zig build-exe", 0); |
| 62 | defer child_prog_node.end(); | 66 | defer child_prog_node.end(); |
| 63 | | 67 | |
| | 68 | // Convert paths to be relative to the cwd of the subprocess. |
| | 69 | const resolved_zig_exe = try std.fs.path.relative(arena, tmp_dir_path, zig_exe); |
| | 70 | const opt_resolved_lib_dir = if (opt_lib_dir) |lib_dir| |
| | 71 | try std.fs.path.relative(arena, tmp_dir_path, lib_dir) |
| | 72 | else |
| | 73 | null; |
| | 74 | |
| 64 | var child_args: std.ArrayListUnmanaged([]const u8) = .{}; | 75 | var child_args: std.ArrayListUnmanaged([]const u8) = .{}; |
| 65 | try child_args.appendSlice(arena, &.{ | 76 | try child_args.appendSlice(arena, &.{ |
| 66 | // Convert incr-check-relative path to subprocess-relative path. | 77 | resolved_zig_exe, |
| 67 | try std.fs.path.relative(arena, tmp_dir_path, zig_exe), | | |
| 68 | "build-exe", | 78 | "build-exe", |
| 69 | case.root_source_file, | 79 | case.root_source_file, |
| 70 | "-fincremental", | 80 | "-fincremental", |
| ... | @@ -76,13 +86,13 @@ pub fn main() !void { | ... | @@ -76,13 +86,13 @@ pub fn main() !void { |
| 76 | ".global_cache", | 86 | ".global_cache", |
| 77 | "--listen=-", | 87 | "--listen=-", |
| 78 | }); | 88 | }); |
| 79 | if (opt_lib_dir) |lib_dir| { | 89 | if (opt_resolved_lib_dir) |resolved_lib_dir| { |
| 80 | try child_args.appendSlice(arena, &.{ "--zig-lib-dir", lib_dir }); | 90 | try child_args.appendSlice(arena, &.{ "--zig-lib-dir", resolved_lib_dir }); |
| 81 | } | 91 | } |
| 82 | if (no_bin) { | 92 | switch (emit) { |
| 83 | try child_args.append(arena, "-fno-emit-bin"); | 93 | .bin => try child_args.appendSlice(arena, &.{ "-fno-llvm", "-fno-lld" }), |
| 84 | } else { | 94 | .none => try child_args.append(arena, "-fno-emit-bin"), |
| 85 | try child_args.appendSlice(arena, &.{ "-fno-llvm", "-fno-lld" }); | 95 | .c => try child_args.appendSlice(arena, &.{ "-ofmt=c", "-lc" }), |
| 86 | } | 96 | } |
| 87 | if (debug_zcu) { | 97 | if (debug_zcu) { |
| 88 | try child_args.appendSlice(arena, &.{ "--debug-log", "zcu" }); | 98 | try child_args.appendSlice(arena, &.{ "--debug-log", "zcu" }); |
| ... | @@ -96,6 +106,24 @@ pub fn main() !void { | ... | @@ -96,6 +106,24 @@ pub fn main() !void { |
| 96 | child.cwd_dir = tmp_dir; | 106 | child.cwd_dir = tmp_dir; |
| 97 | child.cwd = tmp_dir_path; | 107 | child.cwd = tmp_dir_path; |
| 98 | | 108 | |
| | 109 | var cc_child_args: std.ArrayListUnmanaged([]const u8) = .{}; |
| | 110 | if (emit == .c) { |
| | 111 | const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe| |
| | 112 | try std.fs.path.relative(arena, tmp_dir_path, cc_zig_exe) |
| | 113 | else |
| | 114 | resolved_zig_exe; |
| | 115 | |
| | 116 | try cc_child_args.appendSlice(arena, &.{ |
| | 117 | resolved_cc_zig_exe, |
| | 118 | "cc", |
| | 119 | "-target", |
| | 120 | case.target_query, |
| | 121 | "-I", |
| | 122 | opt_resolved_lib_dir orelse fatal("'--zig-lib-dir' required when using '--emit c'", .{}), |
| | 123 | "-o", |
| | 124 | }); |
| | 125 | } |
| | 126 | |
| 99 | var eval: Eval = .{ | 127 | var eval: Eval = .{ |
| 100 | .arena = arena, | 128 | .arena = arena, |
| 101 | .case = case, | 129 | .case = case, |
| ... | @@ -103,6 +131,8 @@ pub fn main() !void { | ... | @@ -103,6 +131,8 @@ pub fn main() !void { |
| 103 | .tmp_dir_path = tmp_dir_path, | 131 | .tmp_dir_path = tmp_dir_path, |
| 104 | .child = &child, | 132 | .child = &child, |
| 105 | .allow_stderr = debug_zcu, | 133 | .allow_stderr = debug_zcu, |
| | 134 | .emit = emit, |
| | 135 | .cc_child_args = &cc_child_args, |
| 106 | }; | 136 | }; |
| 107 | | 137 | |
| 108 | try child.spawn(); | 138 | try child.spawn(); |
| ... | @@ -123,7 +153,7 @@ pub fn main() !void { | ... | @@ -123,7 +153,7 @@ pub fn main() !void { |
| 123 | | 153 | |
| 124 | eval.write(update); | 154 | eval.write(update); |
| 125 | try eval.requestUpdate(); | 155 | try eval.requestUpdate(); |
| 126 | try eval.check(&poller, update); | 156 | try eval.check(&poller, update, update_node); |
| 127 | } | 157 | } |
| 128 | | 158 | |
| 129 | try eval.end(&poller); | 159 | try eval.end(&poller); |
| ... | @@ -138,6 +168,10 @@ const Eval = struct { | ... | @@ -138,6 +168,10 @@ const Eval = struct { |
| 138 | tmp_dir_path: []const u8, | 168 | tmp_dir_path: []const u8, |
| 139 | child: *std.process.Child, | 169 | child: *std.process.Child, |
| 140 | allow_stderr: bool, | 170 | allow_stderr: bool, |
| | 171 | emit: EmitMode, |
| | 172 | /// When `emit == .c`, this contains the first few arguments to `zig cc` to build the generated binary. |
| | 173 | /// The arguments `out.c in.c` must be appended before spawning the subprocess. |
| | 174 | cc_child_args: *std.ArrayListUnmanaged([]const u8), |
| 141 | | 175 | |
| 142 | const StreamEnum = enum { stdout, stderr }; | 176 | const StreamEnum = enum { stdout, stderr }; |
| 143 | const Poller = std.io.Poller(StreamEnum); | 177 | const Poller = std.io.Poller(StreamEnum); |
| ... | @@ -159,7 +193,7 @@ const Eval = struct { | ... | @@ -159,7 +193,7 @@ const Eval = struct { |
| 159 | } | 193 | } |
| 160 | } | 194 | } |
| 161 | | 195 | |
| 162 | fn check(eval: *Eval, poller: *Poller, update: Case.Update) !void { | 196 | fn check(eval: *Eval, poller: *Poller, update: Case.Update, prog_node: std.Progress.Node) !void { |
| 163 | const arena = eval.arena; | 197 | const arena = eval.arena; |
| 164 | const Header = std.zig.Server.Message.Header; | 198 | const Header = std.zig.Server.Message.Header; |
| 165 | const stdout = poller.fifo(.stdout); | 199 | const stdout = poller.fifo(.stdout); |
| ... | @@ -201,12 +235,7 @@ const Eval = struct { | ... | @@ -201,12 +235,7 @@ const Eval = struct { |
| 201 | } | 235 | } |
| 202 | if (result_error_bundle.errorMessageCount() == 0) { | 236 | if (result_error_bundle.errorMessageCount() == 0) { |
| 203 | // Empty bundle indicates successful update in a `-fno-emit-bin` build. | 237 | // Empty bundle indicates successful update in a `-fno-emit-bin` build. |
| 204 | // We can't do a full success check since we don't have a binary, but let's | 238 | try eval.checkSuccessOutcome(update, null, prog_node); |
| 205 | // at least check that no errors were expected. | | |
| 206 | switch (update.outcome) { | | |
| 207 | .unknown, .stdout, .exit_code => {}, | | |
| 208 | .compile_errors => fatal("expected compile errors but compilation incorrectly succeeded", .{}), | | |
| 209 | } | | |
| 210 | } else { | 239 | } else { |
| 211 | try eval.checkErrorOutcome(update, result_error_bundle); | 240 | try eval.checkErrorOutcome(update, result_error_bundle); |
| 212 | } | 241 | } |
| ... | @@ -227,7 +256,7 @@ const Eval = struct { | ... | @@ -227,7 +256,7 @@ const Eval = struct { |
| 227 | fatal("emit_bin_path included unexpected stderr:\n{s}", .{stderr_data}); | 256 | fatal("emit_bin_path included unexpected stderr:\n{s}", .{stderr_data}); |
| 228 | } | 257 | } |
| 229 | } | 258 | } |
| 230 | try eval.checkSuccessOutcome(update, result_binary); | 259 | try eval.checkSuccessOutcome(update, result_binary, prog_node); |
| 231 | // This message indicates the end of the update. | 260 | // This message indicates the end of the update. |
| 232 | stdout.discard(body.len); | 261 | stdout.discard(body.len); |
| 233 | return; | 262 | return; |
| ... | @@ -270,12 +299,28 @@ const Eval = struct { | ... | @@ -270,12 +299,28 @@ const Eval = struct { |
| 270 | } | 299 | } |
| 271 | } | 300 | } |
| 272 | | 301 | |
| 273 | fn checkSuccessOutcome(eval: *Eval, update: Case.Update, binary_path: []const u8) !void { | 302 | fn checkSuccessOutcome(eval: *Eval, update: Case.Update, opt_emitted_path: ?[]const u8, prog_node: std.Progress.Node) !void { |
| 274 | switch (update.outcome) { | 303 | switch (update.outcome) { |
| 275 | .unknown => return, | 304 | .unknown => return, |
| 276 | .compile_errors => fatal("expected compile errors but compilation incorrectly succeeded", .{}), | 305 | .compile_errors => fatal("expected compile errors but compilation incorrectly succeeded", .{}), |
| 277 | .stdout, .exit_code => {}, | 306 | .stdout, .exit_code => {}, |
| 278 | } | 307 | } |
| | 308 | const emitted_path = opt_emitted_path orelse { |
| | 309 | std.debug.assert(eval.emit == .none); |
| | 310 | return; |
| | 311 | }; |
| | 312 | |
| | 313 | const binary_path = switch (eval.emit) { |
| | 314 | .none => unreachable, |
| | 315 | .bin => emitted_path, |
| | 316 | .c => bin: { |
| | 317 | const rand_int = std.crypto.random.int(u64); |
| | 318 | const out_bin_name = "./out_" ++ std.fmt.hex(rand_int); |
| | 319 | try eval.buildCOutput(update, emitted_path, out_bin_name, prog_node); |
| | 320 | break :bin out_bin_name; |
| | 321 | }, |
| | 322 | }; |
| | 323 | |
| 279 | const result = std.process.Child.run(.{ | 324 | const result = std.process.Child.run(.{ |
| 280 | .allocator = eval.arena, | 325 | .allocator = eval.arena, |
| 281 | .argv = &.{binary_path}, | 326 | .argv = &.{binary_path}, |
| ... | @@ -345,6 +390,50 @@ const Eval = struct { | ... | @@ -345,6 +390,50 @@ const Eval = struct { |
| 345 | fatal("unexpected stderr:\n{s}", .{stderr_data}); | 390 | fatal("unexpected stderr:\n{s}", .{stderr_data}); |
| 346 | } | 391 | } |
| 347 | } | 392 | } |
| | 393 | |
| | 394 | fn buildCOutput(eval: *Eval, update: Case.Update, c_path: []const u8, out_path: []const u8, prog_node: std.Progress.Node) !void { |
| | 395 | std.debug.assert(eval.cc_child_args.items.len > 0); |
| | 396 | |
| | 397 | const child_prog_node = prog_node.start("build cbe output", 0); |
| | 398 | defer child_prog_node.end(); |
| | 399 | |
| | 400 | try eval.cc_child_args.appendSlice(eval.arena, &.{ out_path, c_path }); |
| | 401 | defer eval.cc_child_args.items.len -= 2; |
| | 402 | |
| | 403 | const result = std.process.Child.run(.{ |
| | 404 | .allocator = eval.arena, |
| | 405 | .argv = eval.cc_child_args.items, |
| | 406 | .cwd_dir = eval.tmp_dir, |
| | 407 | .cwd = eval.tmp_dir_path, |
| | 408 | .progress_node = child_prog_node, |
| | 409 | }) catch |err| { |
| | 410 | fatal("update '{s}': failed to spawn zig cc for '{s}': {s}", .{ |
| | 411 | update.name, c_path, @errorName(err), |
| | 412 | }); |
| | 413 | }; |
| | 414 | switch (result.term) { |
| | 415 | .Exited => |code| if (code != 0) { |
| | 416 | if (result.stderr.len != 0) { |
| | 417 | std.log.err("update '{s}': zig cc stderr:\n{s}", .{ |
| | 418 | update.name, result.stderr, |
| | 419 | }); |
| | 420 | } |
| | 421 | fatal("update '{s}': zig cc for '{s}' failed with code {d}", .{ |
| | 422 | update.name, c_path, code, |
| | 423 | }); |
| | 424 | }, |
| | 425 | .Signal, .Stopped, .Unknown => { |
| | 426 | if (result.stderr.len != 0) { |
| | 427 | std.log.err("update '{s}': zig cc stderr:\n{s}", .{ |
| | 428 | update.name, result.stderr, |
| | 429 | }); |
| | 430 | } |
| | 431 | fatal("update '{s}': zig cc for '{s}' terminated unexpectedly", .{ |
| | 432 | update.name, c_path, |
| | 433 | }); |
| | 434 | }, |
| | 435 | } |
| | 436 | } |
| 348 | }; | 437 | }; |
| 349 | | 438 | |
| 350 | const Case = struct { | 439 | const Case = struct { |