| ... | ... | @@ -19,64 +19,70 @@ pub fn main() !void { |
| 19 | 19 | |
| 20 | 20 | const rand_int = std.crypto.random.int(u64); |
| 21 | 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 | 22 | const tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{}); |
| 25 | 23 | |
| 26 | 24 | const child_prog_node = prog_node.start("zig build-exe", 0); |
| 27 | 25 | defer child_prog_node.end(); |
| 28 | 26 | |
| 29 | 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 | 30 | "build-exe", |
| 32 | 31 | case.root_source_file, |
| 33 | 32 | "-fno-llvm", |
| 34 | 33 | "-fno-lld", |
| 35 | 34 | "-fincremental", |
| 36 | | "--listen=-", |
| 37 | 35 | "-target", |
| 38 | 36 | case.target_query, |
| 39 | 37 | "--cache-dir", |
| 40 | | local_cache_path, |
| 38 | ".local-cache", |
| 41 | 39 | "--global-cache-dir", |
| 42 | | global_cache_path, |
| 40 | ".global_cache", |
| 41 | "--listen=-", |
| 43 | 42 | }, arena); |
| 44 | 43 | |
| 45 | 44 | child.stdin_behavior = .Pipe; |
| 46 | 45 | child.stdout_behavior = .Pipe; |
| 47 | 46 | child.stderr_behavior = .Pipe; |
| 48 | 47 | child.progress_node = child_prog_node; |
| 48 | child.cwd_dir = tmp_dir; |
| 49 | child.cwd = tmp_dir_path; |
| 49 | 50 | |
| 50 | 51 | var eval: Eval = .{ |
| 52 | .arena = arena, |
| 51 | 53 | .case = case, |
| 52 | 54 | .tmp_dir = tmp_dir, |
| 53 | 55 | .child = &child, |
| 54 | 56 | }; |
| 55 | 57 | |
| 56 | | eval.write(case.updates[0]); |
| 57 | | |
| 58 | 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 | 61 | .stdout = child.stdout.?, |
| 62 | 62 | .stderr = child.stderr.?, |
| 63 | 63 | }); |
| 64 | 64 | defer poller.deinit(); |
| 65 | 65 | |
| 66 | | try eval.check(case.updates[0]); |
| 67 | | |
| 68 | | for (case.updates[1..]) |update| { |
| 66 | for (case.updates) |update| { |
| 69 | 67 | eval.write(update); |
| 70 | | try eval.requestIncrementalUpdate(); |
| 71 | | try eval.check(update); |
| 68 | try eval.requestUpdate(); |
| 69 | try eval.check(&poller, update); |
| 72 | 70 | } |
| 71 | |
| 72 | try eval.end(&poller); |
| 73 | |
| 74 | waitChild(&child); |
| 73 | 75 | } |
| 74 | 76 | |
| 75 | 77 | const Eval = struct { |
| 78 | arena: Allocator, |
| 76 | 79 | case: Case, |
| 77 | 80 | tmp_dir: std.fs.Dir, |
| 78 | 81 | child: *std.process.Child, |
| 79 | 82 | |
| 83 | const StreamEnum = enum { stdout, stderr }; |
| 84 | const Poller = std.io.Poller(StreamEnum); |
| 85 | |
| 80 | 86 | /// Currently this function assumes the previous updates have already been written. |
| 81 | 87 | fn write(eval: *Eval, update: Case.Update) void { |
| 82 | 88 | for (update.changes) |full_contents| { |
| ... | ... | @@ -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 | 176 | _ = eval; |
| 99 | | _ = update; |
| 100 | | @panic("TODO: read messages from the compiler"); |
| 177 | switch (update.outcome) { |
| 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 | 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 | 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 | } |