| ... | @@ -19,64 +19,70 @@ pub fn main() !void { | ... | @@ -19,64 +19,70 @@ pub fn main() !void { |
| 19 | | 19 | |
| 20 | const rand_int = std.crypto.random.int(u64); | 20 | const rand_int = std.crypto.random.int(u64); |
| 21 | const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int); | 21 | const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int); |
| 22 | const local_cache_path = tmp_dir_path ++ std.fs.path.sep_str ++ ".local-cache"; | | |
| 23 | const global_cache_path = tmp_dir_path ++ std.fs.path.sep_str ++ ".global-cache"; | | |
| 24 | const tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{}); | 22 | const tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{}); |
| 25 | | 23 | |
| 26 | const child_prog_node = prog_node.start("zig build-exe", 0); | 24 | const child_prog_node = prog_node.start("zig build-exe", 0); |
| 27 | defer child_prog_node.end(); | 25 | defer child_prog_node.end(); |
| 28 | | 26 | |
| 29 | var child = std.process.Child.init(&.{ | 27 | var child = std.process.Child.init(&.{ |
| 30 | zig_exe, | 28 | // Convert incr-check-relative path to subprocess-relative path. |
| | 29 | try std.fs.path.relative(arena, tmp_dir_path, zig_exe), |
| 31 | "build-exe", | 30 | "build-exe", |
| 32 | case.root_source_file, | 31 | case.root_source_file, |
| 33 | "-fno-llvm", | 32 | "-fno-llvm", |
| 34 | "-fno-lld", | 33 | "-fno-lld", |
| 35 | "-fincremental", | 34 | "-fincremental", |
| 36 | "--listen=-", | | |
| 37 | "-target", | 35 | "-target", |
| 38 | case.target_query, | 36 | case.target_query, |
| 39 | "--cache-dir", | 37 | "--cache-dir", |
| 40 | local_cache_path, | 38 | ".local-cache", |
| 41 | "--global-cache-dir", | 39 | "--global-cache-dir", |
| 42 | global_cache_path, | 40 | ".global_cache", |
| | 41 | "--listen=-", |
| 43 | }, arena); | 42 | }, arena); |
| 44 | | 43 | |
| 45 | child.stdin_behavior = .Pipe; | 44 | child.stdin_behavior = .Pipe; |
| 46 | child.stdout_behavior = .Pipe; | 45 | child.stdout_behavior = .Pipe; |
| 47 | child.stderr_behavior = .Pipe; | 46 | child.stderr_behavior = .Pipe; |
| 48 | child.progress_node = child_prog_node; | 47 | child.progress_node = child_prog_node; |
| | 48 | child.cwd_dir = tmp_dir; |
| | 49 | child.cwd = tmp_dir_path; |
| 49 | | 50 | |
| 50 | var eval: Eval = .{ | 51 | var eval: Eval = .{ |
| | 52 | .arena = arena, |
| 51 | .case = case, | 53 | .case = case, |
| 52 | .tmp_dir = tmp_dir, | 54 | .tmp_dir = tmp_dir, |
| 53 | .child = &child, | 55 | .child = &child, |
| 54 | }; | 56 | }; |
| 55 | | 57 | |
| 56 | eval.write(case.updates[0]); | | |
| 57 | | | |
| 58 | try child.spawn(); | 58 | try child.spawn(); |
| 59 | | 59 | |
| 60 | var poller = std.io.poll(arena, enum { stdout, stderr }, .{ | 60 | var poller = std.io.poll(arena, Eval.StreamEnum, .{ |
| 61 | .stdout = child.stdout.?, | 61 | .stdout = child.stdout.?, |
| 62 | .stderr = child.stderr.?, | 62 | .stderr = child.stderr.?, |
| 63 | }); | 63 | }); |
| 64 | defer poller.deinit(); | 64 | defer poller.deinit(); |
| 65 | | 65 | |
| 66 | try eval.check(case.updates[0]); | 66 | for (case.updates) |update| { |
| 67 | | | |
| 68 | for (case.updates[1..]) |update| { | | |
| 69 | eval.write(update); | 67 | eval.write(update); |
| 70 | try eval.requestIncrementalUpdate(); | 68 | try eval.requestUpdate(); |
| 71 | try eval.check(update); | 69 | try eval.check(&poller, update); |
| 72 | } | 70 | } |
| | 71 | |
| | 72 | try eval.end(&poller); |
| | 73 | |
| | 74 | waitChild(&child); |
| 73 | } | 75 | } |
| 74 | | 76 | |
| 75 | const Eval = struct { | 77 | const Eval = struct { |
| | 78 | arena: Allocator, |
| 76 | case: Case, | 79 | case: Case, |
| 77 | tmp_dir: std.fs.Dir, | 80 | tmp_dir: std.fs.Dir, |
| 78 | child: *std.process.Child, | 81 | child: *std.process.Child, |
| 79 | | 82 | |
| | 83 | const StreamEnum = enum { stdout, stderr }; |
| | 84 | const Poller = std.io.Poller(StreamEnum); |
| | 85 | |
| 80 | /// Currently this function assumes the previous updates have already been written. | 86 | /// Currently this function assumes the previous updates have already been written. |
| 81 | fn write(eval: *Eval, update: Case.Update) void { | 87 | fn write(eval: *Eval, update: Case.Update) void { |
| 82 | for (update.changes) |full_contents| { | 88 | for (update.changes) |full_contents| { |
| ... | @@ -94,15 +100,137 @@ const Eval = struct { | ... | @@ -94,15 +100,137 @@ const Eval = struct { |
| 94 | } | 100 | } |
| 95 | } | 101 | } |
| 96 | | 102 | |
| 97 | fn check(eval: *Eval, update: Case.Update) !void { | 103 | fn check(eval: *Eval, poller: *Poller, update: Case.Update) !void { |
| | 104 | const arena = eval.arena; |
| | 105 | const Header = std.zig.Server.Message.Header; |
| | 106 | const stdout = poller.fifo(.stdout); |
| | 107 | const stderr = poller.fifo(.stderr); |
| | 108 | |
| | 109 | poll: while (true) { |
| | 110 | while (stdout.readableLength() < @sizeOf(Header)) { |
| | 111 | if (!(try poller.poll())) break :poll; |
| | 112 | } |
| | 113 | const header = stdout.reader().readStruct(Header) catch unreachable; |
| | 114 | while (stdout.readableLength() < header.bytes_len) { |
| | 115 | if (!(try poller.poll())) break :poll; |
| | 116 | } |
| | 117 | const body = stdout.readableSliceOfLen(header.bytes_len); |
| | 118 | std.log.debug("received message: {s}", .{@tagName(header.tag)}); |
| | 119 | |
| | 120 | switch (header.tag) { |
| | 121 | .error_bundle => { |
| | 122 | const EbHdr = std.zig.Server.Message.ErrorBundle; |
| | 123 | const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body)); |
| | 124 | const extra_bytes = |
| | 125 | body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; |
| | 126 | const string_bytes = |
| | 127 | body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; |
| | 128 | // TODO: use @ptrCast when the compiler supports it |
| | 129 | const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes); |
| | 130 | const extra_array = try arena.alloc(u32, unaligned_extra.len); |
| | 131 | @memcpy(extra_array, unaligned_extra); |
| | 132 | const result_error_bundle: std.zig.ErrorBundle = .{ |
| | 133 | .string_bytes = try arena.dupe(u8, string_bytes), |
| | 134 | .extra = extra_array, |
| | 135 | }; |
| | 136 | if (stderr.readableLength() > 0) { |
| | 137 | const stderr_data = try stderr.toOwnedSlice(); |
| | 138 | fatal("error_bundle included unexpected stderr:\n{s}", .{stderr_data}); |
| | 139 | } |
| | 140 | try eval.checkErrorOutcome(update, result_error_bundle); |
| | 141 | // This message indicates the end of the update. |
| | 142 | stdout.discard(body.len); |
| | 143 | return; |
| | 144 | }, |
| | 145 | .emit_bin_path => { |
| | 146 | const EbpHdr = std.zig.Server.Message.EmitBinPath; |
| | 147 | const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body)); |
| | 148 | _ = ebp_hdr; |
| | 149 | const result_binary = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]); |
| | 150 | if (stderr.readableLength() > 0) { |
| | 151 | const stderr_data = try stderr.toOwnedSlice(); |
| | 152 | fatal("emit_bin_path included unexpected stderr:\n{s}", .{stderr_data}); |
| | 153 | } |
| | 154 | try eval.checkSuccessOutcome(update, result_binary); |
| | 155 | // This message indicates the end of the update. |
| | 156 | stdout.discard(body.len); |
| | 157 | return; |
| | 158 | }, |
| | 159 | else => { |
| | 160 | // Ignore other messages. |
| | 161 | stdout.discard(body.len); |
| | 162 | }, |
| | 163 | } |
| | 164 | } |
| | 165 | |
| | 166 | if (stderr.readableLength() > 0) { |
| | 167 | const stderr_data = try stderr.toOwnedSlice(); |
| | 168 | fatal("update '{s}' failed:\n{s}", .{ update.name, stderr_data }); |
| | 169 | } |
| | 170 | |
| | 171 | waitChild(eval.child); |
| | 172 | fatal("update '{s}': compiler failed to send error_bundle or emit_bin_path", .{update.name}); |
| | 173 | } |
| | 174 | |
| | 175 | fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void { |
| 98 | _ = eval; | 176 | _ = eval; |
| 99 | _ = update; | 177 | switch (update.outcome) { |
| 100 | @panic("TODO: read messages from the compiler"); | 178 | .unknown => return, |
| | 179 | .compile_errors => |expected_errors| { |
| | 180 | for (expected_errors) |expected_error| { |
| | 181 | _ = expected_error; |
| | 182 | @panic("TODO check if the expected error matches the compile errors"); |
| | 183 | } |
| | 184 | }, |
| | 185 | .stdout, .exit_code => { |
| | 186 | const color: std.zig.Color = .auto; |
| | 187 | error_bundle.renderToStdErr(color.renderOptions()); |
| | 188 | fatal("update '{s}': unexpected compile errors", .{update.name}); |
| | 189 | }, |
| | 190 | } |
| 101 | } | 191 | } |
| 102 | | 192 | |
| 103 | fn requestIncrementalUpdate(eval: *Eval) !void { | 193 | fn checkSuccessOutcome(eval: *Eval, update: Case.Update, binary_path: []const u8) !void { |
| 104 | _ = eval; | 194 | _ = eval; |
| 105 | @panic("TODO: send update request to the compiler"); | 195 | switch (update.outcome) { |
| | 196 | .unknown => return, |
| | 197 | .compile_errors => fatal("expected compile errors but compilation incorrectly succeeded", .{}), |
| | 198 | .stdout, .exit_code => {}, |
| | 199 | } |
| | 200 | fatal("TODO: run this binary: '{s}'", .{binary_path}); |
| | 201 | } |
| | 202 | |
| | 203 | fn requestUpdate(eval: *Eval) !void { |
| | 204 | const header: std.zig.Client.Message.Header = .{ |
| | 205 | .tag = .update, |
| | 206 | .bytes_len = 0, |
| | 207 | }; |
| | 208 | try eval.child.stdin.?.writeAll(std.mem.asBytes(&header)); |
| | 209 | } |
| | 210 | |
| | 211 | fn end(eval: *Eval, poller: *Poller) !void { |
| | 212 | requestExit(eval.child); |
| | 213 | |
| | 214 | const Header = std.zig.Server.Message.Header; |
| | 215 | const stdout = poller.fifo(.stdout); |
| | 216 | const stderr = poller.fifo(.stderr); |
| | 217 | |
| | 218 | poll: while (true) { |
| | 219 | while (stdout.readableLength() < @sizeOf(Header)) { |
| | 220 | if (!(try poller.poll())) break :poll; |
| | 221 | } |
| | 222 | const header = stdout.reader().readStruct(Header) catch unreachable; |
| | 223 | while (stdout.readableLength() < header.bytes_len) { |
| | 224 | if (!(try poller.poll())) break :poll; |
| | 225 | } |
| | 226 | const body = stdout.readableSliceOfLen(header.bytes_len); |
| | 227 | stdout.discard(body.len); |
| | 228 | } |
| | 229 | |
| | 230 | if (stderr.readableLength() > 0) { |
| | 231 | const stderr_data = try stderr.toOwnedSlice(); |
| | 232 | fatal("unexpected stderr:\n{s}", .{stderr_data}); |
| | 233 | } |
| 106 | } | 234 | } |
| 107 | }; | 235 | }; |
| 108 | | 236 | |
| ... | @@ -213,3 +341,29 @@ const Case = struct { | ... | @@ -213,3 +341,29 @@ const Case = struct { |
| 213 | }; | 341 | }; |
| 214 | } | 342 | } |
| 215 | }; | 343 | }; |
| | 344 | |
| | 345 | fn requestExit(child: *std.process.Child) void { |
| | 346 | if (child.stdin == null) return; |
| | 347 | |
| | 348 | const header: std.zig.Client.Message.Header = .{ |
| | 349 | .tag = .exit, |
| | 350 | .bytes_len = 0, |
| | 351 | }; |
| | 352 | child.stdin.?.writeAll(std.mem.asBytes(&header)) catch |err| switch (err) { |
| | 353 | error.BrokenPipe => {}, |
| | 354 | else => fatal("failed to send exit: {s}", .{@errorName(err)}), |
| | 355 | }; |
| | 356 | |
| | 357 | // Send EOF to stdin. |
| | 358 | child.stdin.?.close(); |
| | 359 | child.stdin = null; |
| | 360 | } |
| | 361 | |
| | 362 | fn waitChild(child: *std.process.Child) void { |
| | 363 | requestExit(child); |
| | 364 | const term = child.wait() catch |err| fatal("child process failed: {s}", .{@errorName(err)}); |
| | 365 | switch (term) { |
| | 366 | .Exited => |code| if (code != 0) fatal("compiler failed with code {d}", .{code}), |
| | 367 | .Signal, .Stopped, .Unknown => fatal("compiler terminated unexpectedly", .{}), |
| | 368 | } |
| | 369 | } |