1const assert = std.debug.assert;
2const builtin = @import("builtin");
3const std = @import("std");
4const Runner = @This();
5
6/// These are passed as `<target>-<cache_mode>-<backend>-<linker>-<pic>` strings.
7///
8/// If only one specific test is failing on a target, instead of entirely disabling the target here, you
9/// can skip the target for that specific test only by adding a line like this to top of the manifest:
10///
11/// #skip x86_64-linux-none-baseline-whole-selfhosted-new-pic
12pub const Target = struct {
13 cpu: Cpu = .{ .native = .{} },
14 os: Os = .native,
15 abi: Abi = .default,
16 ofmt: ObjectFormat = .default,
17 dynamic_linker: DynamicLinker = .default,
18
19 cache_mode: enum { whole, incremental },
20 backend: enum { selfhosted, llvm, cbe },
21 linker: enum { lld, old, new },
22 pic: enum { nopic, pic, pie },
23
24 pub const Cpu = Cpu: {
25 @setEvalBranchQuota(3_000);
26 const Arch = std.Target.Cpu.Arch;
27 const arch_info = @typeInfo(Arch).@"enum";
28 const field_names = .{"native"} ++ arch_info.field_names;
29 var field_types: [field_names.len]type = undefined;
30 field_types[0] = struct {
31 model: Model = .native,
32
33 pub const Model = enum { native, baseline };
34 };
35 for (field_types[1..], field_names[1..]) |*field_type, field_name| {
36 const arch = @field(Arch, field_name);
37 const models = arch.allCpuModels();
38 const family = @field(std.Target, @tagName(arch.family()));
39 field_type.* = struct {
40 model: Model = .baseline,
41 features: struct {
42 add: []const Feature = &.{},
43 remove: []const Feature = &.{},
44 comptime {
45 _ = &family; // hack around compiler bug
46 }
47 } = .{},
48
49 pub const Model = Model: {
50 const explicit_models = @typeInfo(family.cpu).@"struct".decl_names;
51 var model_names: []const []const u8 = &.{"native"};
52 if (!@hasDecl(family.cpu, "baseline")) model_names = model_names ++ .{"baseline"};
53 model_names = model_names ++ explicit_models;
54 const TagInt = std.math.IntFittingRange(0, model_names.len - 1);
55 var model_values: [model_names.len]TagInt = undefined;
56 var model_index = 0;
57 {
58 var model_value = explicit_models.len;
59 model_values[model_index] = model_value;
60 model_index += 1;
61 model_value += 1;
62 if (!@hasDecl(family.cpu, "baseline")) {
63 model_values[model_index] = model_value;
64 model_index += 1;
65 model_value += 1;
66 }
67 }
68 for (model_values[model_index..], 0..) |*model_value, value| model_value.* = value;
69 break :Model @Enum(TagInt, .exhaustive, model_names, &model_values);
70 };
71
72 pub const Feature = family.Feature;
73
74 pub fn updateQuery(cpu: *const @This(), query: *std.Target.Query) void {
75 query.cpu_model = switch (cpu.model) {
76 .native => .native,
77 .baseline => .baseline,
78 else => |model| .{ .explicit = models[@backingInt(model)] },
79 };
80 for (cpu.features.add) |add_feature|
81 query.cpu_features_add.addFeature(@backingInt(add_feature));
82 for (cpu.features.remove) |remove_feature|
83 query.cpu_features_sub.addFeature(@backingInt(remove_feature));
84 }
85 };
86 }
87 const TagInt = std.math.IntFittingRange(0, field_names.len - 1);
88 var field_values: [field_names.len]TagInt = undefined;
89 field_values[0] = field_names.len - 1;
90 for (field_values[1..], arch_info.field_values) |*field_value, value| field_value.* = value;
91 const Tag = @Enum(TagInt, .exhaustive, field_names, &field_values);
92 break :Cpu @Union(.auto, Tag, field_names, &field_types, &@splat(.{}));
93 };
94
95 pub const Os = Os: {
96 const os_info = @typeInfo(std.Target.Os.Tag).@"enum";
97 const field_names = .{"native"} ++ os_info.field_names;
98 var field_types: [field_names.len]type = undefined;
99 field_types[0] = void;
100 for (field_types[1..], field_names[1..]) |*field_type, field_name| {
101 const version: enum {
102 none,
103 semver,
104 windows,
105 } = switch (@field(std.Target.Os.Tag, field_name).versionRangeTag()) {
106 .none => .none,
107 .semver, .hurd, .linux => .semver,
108 .windows => .windows,
109 };
110 field_type.* = struct {
111 version: ?Version = null,
112
113 pub const Version = switch (version) {
114 .none => struct {},
115 .semver => struct {
116 min: std.SemanticVersion,
117 max: ?std.SemanticVersion = null,
118 },
119 .windows => struct {
120 min: std.Target.Os.WindowsVersion,
121 max: ?std.Target.Os.WindowsVersion = null,
122 },
123 };
124
125 pub fn updateQuery(os: *const @This(), query: *std.Target.Query) void {
126 if (os.version) |v| switch (version) {
127 .none => {},
128 .semver => {
129 query.os_version_min = .{ .semver = v.min };
130 if (v.max) |max| query.os_version_max = .{ .semver = max };
131 },
132 .windows => {
133 query.os_version_min = .{ .windows = v.min };
134 if (v.max) |max| query.os_version_max = .{ .windows = max };
135 },
136 };
137 }
138 };
139 }
140 const TagInt = std.math.IntFittingRange(0, field_names.len - 1);
141 var field_values: [field_names.len]TagInt = undefined;
142 field_values[0] = field_names.len - 1;
143 for (field_values[1..], os_info.field_values) |*field_value, value| field_value.* = value;
144 const Tag = @Enum(TagInt, .exhaustive, field_names, &field_values);
145 break :Os @Union(.auto, Tag, field_names, &field_types, &@splat(.{}));
146 };
147
148 pub const Abi = Abi: {
149 const abi_info = @typeInfo(std.Target.Abi).@"enum";
150 const field_names = .{"default"} ++ abi_info.field_names;
151 var field_types: [field_names.len]type = undefined;
152 field_types[0] = void;
153 for (field_types[1..], field_names[1..]) |*field_type, field_name| {
154 const abi_tag = @field(std.Target.Abi, field_name);
155 const version: enum { none, libc, android } = if (abi_tag.isAndroid())
156 .android
157 else if (abi_tag.isGnu() or abi_tag.isMusl())
158 .libc
159 else
160 .none;
161 field_type.* = struct {
162 version: ?Version = null,
163
164 pub const Version = switch (version) {
165 .none => struct {},
166 .libc => std.SemanticVersion,
167 .android => u32,
168 };
169
170 pub fn updateQuery(abi: *const @This(), query: *std.Target.Query) void {
171 if (abi.version) |v| switch (version) {
172 .none => {},
173 .libc => query.glibc_version = v,
174 .android => query.android_api_level = v,
175 };
176 }
177 };
178 }
179 const TagInt = std.math.IntFittingRange(0, field_names.len - 1);
180 var field_values: [field_names.len]TagInt = undefined;
181 field_values[0] = field_names.len - 1;
182 for (field_values[1..], abi_info.field_values) |*field_value, value| field_value.* = value;
183 const Tag = @Enum(TagInt, .exhaustive, field_names, &field_values);
184 break :Abi @Union(.auto, Tag, field_names, &field_types, &@splat(.{}));
185 };
186
187 pub const ObjectFormat = ObjectFormat: {
188 const ofmt_info = @typeInfo(std.Target.ObjectFormat).@"enum";
189 const field_names = .{"default"} ++ ofmt_info.field_names;
190 const TagInt = std.math.IntFittingRange(0, field_names.len - 1);
191 var field_values: [field_names.len]TagInt = undefined;
192 field_values[0] = field_names.len - 1;
193 for (field_values[1..], ofmt_info.field_values) |*field_value, value| field_value.* = value;
194 break :ObjectFormat @Enum(TagInt, .exhaustive, field_names, &field_values);
195 };
196
197 pub const DynamicLinker = union(enum) {
198 default,
199 none,
200 explicit: []const u8,
201 };
202
203 pub fn toQuery(target: *const Target) std.Target.Query {
204 var query: std.Target.Query = .{};
205 query.cpu_arch = switch (target.cpu) {
206 .native => null,
207 else => |arch| @fromBackingInt(@backingInt(arch)),
208 };
209 switch (target.cpu) {
210 .native => |native| query.cpu_model = switch (native.model) {
211 .native => .native,
212 .baseline => .baseline,
213 },
214 inline else => |cpu| cpu.updateQuery(&query),
215 }
216 query.os_tag = switch (target.os) {
217 .native => null,
218 else => |os| @fromBackingInt(@backingInt(os)),
219 };
220 switch (target.os) {
221 .native => {},
222 inline else => |os| os.updateQuery(&query),
223 }
224 query.abi = switch (target.abi) {
225 .default => null,
226 else => |abi| @fromBackingInt(@backingInt(abi)),
227 };
228 switch (target.abi) {
229 .default => {},
230 inline else => |abi| abi.updateQuery(&query),
231 }
232 query.ofmt = switch (target.ofmt) {
233 .default => null,
234 else => |ofmt| @fromBackingInt(@backingInt(ofmt)),
235 };
236 query.dynamic_linker = switch (target.dynamic_linker) {
237 .default => null,
238 .none => .none,
239 .explicit => |dynamic_linker| .init(dynamic_linker),
240 };
241 return query;
242 }
243
244 pub fn format(target: *const Target, w: *std.Io.Writer) std.Io.Writer.Error!void {
245 try w.print("{f}-{f}-{t}-{t}-{t}-{t}", .{
246 std.fmt.alt(&target.toQuery(), .printZigTriple),
247 std.fmt.alt(&target.toQuery(), .printCpu),
248 target.cache_mode,
249 target.backend,
250 target.linker,
251 target.pic,
252 });
253 }
254};
255
256gpa: std.mem.Allocator,
257arena: *std.heap.ArenaAllocator,
258environ_map: std.process.Environ.Map,
259io: std.Io,
260prog_node: std.Progress.Node,
261args: Args,
262host: std.Target,
263server: std.zig.Server,
264eb_wip: std.zig.ErrorBundle.Wip,
265
266const Args = struct {
267 @"test": ?struct {
268 input_dir: std.zig.Server.Message.InputDir,
269 dir: std.Io.Dir,
270 },
271 manifest_file: ?std.Io.File,
272 zig_exe: ?std.Io.File,
273 lib_dir: ?std.Io.Dir,
274 src_dir: ?std.Io.Dir,
275 targets: std.ArrayList(u32),
276 target_bytes: std.ArrayList(u8),
277
278 libc_runtimes_dir: ?std.Io.Dir,
279 enable_darling: bool,
280 enable_qemu: bool,
281 enable_rosetta: bool,
282 enable_wasmtime: bool,
283 enable_wine: bool,
284
285 gdb_exe: ?std.Io.File,
286 lldb_exe: ?std.Io.File,
287
288 quiet: bool,
289
290 fn deinit(args: *Args, gpa: std.mem.Allocator) void {
291 args.targets.deinit(gpa);
292 args.target_bytes.deinit(gpa);
293 args.* = undefined;
294 }
295
296 fn addTarget(args: *Args, gpa: std.mem.Allocator, target: []const u8) std.mem.Allocator.Error!void {
297 try args.target_bytes.ensureUnusedCapacity(gpa, target.len + 1);
298 try args.targets.append(gpa, @intCast(args.target_bytes.items.len));
299 args.target_bytes.appendSliceAssumeCapacity(target);
300 args.target_bytes.appendAssumeCapacity(0);
301 }
302};
303
304fn deinit(runner: *Runner) void {
305 runner.environ_map.deinit();
306 runner.prog_node.end();
307 runner.args.deinit(runner.gpa);
308 runner.eb_wip.deinit();
309 runner.* = undefined;
310}
311
312const FailError = error{AlreadyReported} || std.mem.Allocator.Error || std.Io.Cancelable;
313fn fail(runner: *Runner, comptime fmt: []const u8, args: anytype) FailError {
314 return runner.failString(try runner.eb_wip.printString(fmt, args));
315}
316fn failString(runner: *Runner, msg: std.zig.ErrorBundle.String) FailError {
317 try runner.eb_wip.addRootErrorMessage(.{ .msg = msg });
318 return error.AlreadyReported;
319}
320
321pub const ProtocolError = error{
322 ServerReadFailed,
323 ServerWriteFailed,
324 ServerEndOfStream,
325} || FailError;
326pub fn runServer(runner: *Runner) ProtocolError {
327 runner.server.serveStringMessage(.zig_version, builtin.zig_version_string) catch |err| switch (err) {
328 error.WriteFailed => return error.ServerWriteFailed,
329 };
330 while (true) {
331 _ = runner.arena.reset(.retain_capacity);
332 const arena = runner.arena.allocator();
333 const hdr = runner.server.receiveMessage() catch |err| switch (err) {
334 error.ReadFailed => return error.ServerReadFailed,
335 error.EndOfStream => return error.ServerEndOfStream,
336 };
337 switch (hdr.tag) {
338 else => |tag| return runner.fail("unsupported message: {t}", .{tag}),
339 .exit => std.process.exit(0),
340 .args => {
341 const args_body = try arena.alloc(u8, hdr.bytes_len);
342 runner.server.in.readSliceAll(args_body) catch unreachable;
343 const State = enum {
344 positional,
345 @"--zig",
346 @"--lib",
347 @"--src",
348 @"--target",
349 @"--libc-runtimes",
350 @"--gdb",
351 @"--lldb",
352 };
353 var state: State = .positional;
354 const state_expected: std.enums.EnumArray(
355 State,
356 enum { path, dir, file, string },
357 ) = .init(.{
358 .positional = .path,
359 .@"--zig" = .file,
360 .@"--lib" = .dir,
361 .@"--src" = .dir,
362 .@"--target" = .string,
363 .@"--libc-runtimes" = .dir,
364 .@"--gdb" = .file,
365 .@"--lldb" = .file,
366 });
367 var input_dir: std.zig.Server.Message.InputDir = .cwd;
368 var args_body_offset: usize = 0;
369 while (args_body.len - args_body_offset > 0) {
370 const arg: std.zig.Client.Message.Arg =
371 @fromBackingInt(args_body[args_body_offset]);
372 args_body_offset += 1;
373 arg: switch (arg) {
374 .string => {
375 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
376 const string = args_body[args_body_offset..end];
377 args_body_offset = end + 1;
378 switch (state) {
379 .positional => if (std.mem.eql(u8, string, "--target")) {
380 state = .@"--target";
381 continue;
382 } else if (std.mem.cutPrefix(u8, string, "--target=")) |target| {
383 try runner.args.addTarget(runner.gpa, target);
384 } else if (std.mem.eql(u8, string, "--libc-runtimes")) {
385 state = .@"--libc-runtimes";
386 continue;
387 } else if (std.mem.eql(u8, string, "-fdarling")) {
388 runner.args.enable_darling = true;
389 } else if (std.mem.eql(u8, string, "-fqemu")) {
390 runner.args.enable_qemu = true;
391 } else if (std.mem.eql(u8, string, "-frosetta")) {
392 runner.args.enable_rosetta = true;
393 } else if (std.mem.eql(u8, string, "-fwasmtime")) {
394 runner.args.enable_wasmtime = true;
395 } else if (std.mem.eql(u8, string, "-fwine")) {
396 runner.args.enable_wine = true;
397 } else if (std.mem.eql(u8, string, "--gdb")) {
398 state = .@"--gdb";
399 continue;
400 } else if (std.mem.eql(u8, string, "--lldb")) {
401 state = .@"--lldb";
402 continue;
403 } else if (std.mem.eql(u8, string, "--quiet")) {
404 runner.args.quiet = true;
405 },
406 .@"--target" => try runner.args.addTarget(runner.gpa, string),
407 else => return runner.fail(
408 "\"{t}\" expected {t}, got {t}",
409 .{ state, state_expected.get(state), arg },
410 ),
411 }
412 },
413 .prefix => {
414 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
415 const string = args_body[args_body_offset..end];
416 args_body_offset = end + 1;
417 assert(state == .positional);
418 state = if (std.mem.eql(u8, string, "--zig="))
419 .@"--zig"
420 else if (std.mem.eql(u8, string, "--lib="))
421 .@"--lib"
422 else if (std.mem.eql(u8, string, "--src="))
423 .@"--src"
424 else if (std.mem.eql(u8, string, "--libc-runtimes="))
425 .@"--libc-runtimes"
426 else if (std.mem.eql(u8, string, "--gdb="))
427 .@"--gdb"
428 else if (std.mem.eql(u8, string, "--lldb="))
429 .@"--lldb"
430 else
431 return runner.fail("unsupported arg prefix: {q}", .{string});
432 continue;
433 },
434 .suffix => {
435 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
436 const string = args_body[args_body_offset..end];
437 args_body_offset = end + 1;
438 return runner.fail("unsupported arg suffix: {q}", .{string});
439 },
440 .input_dir => {
441 input_dir = @fromBackingInt(@backingInt(input_dir) + 1);
442 continue :arg .output_dir;
443 },
444 .output_dir => {
445 const dir_handle: *align(1) const std.Io.Dir.Handle = @ptrCast(
446 args_body[args_body_offset..][0..@sizeOf(std.Io.Dir.Handle)],
447 );
448 args_body_offset += @sizeOf(std.Io.Dir.Handle);
449 const dir = runner.io.vtable.inheritParentDir(
450 runner.io.userdata,
451 dir_handle.*,
452 ) catch |err| switch (err) {
453 error.Canceled => |e| return e,
454 else => |e| return runner.fail(
455 "unable to inherit parent dir: {t}",
456 .{e},
457 ),
458 };
459 switch (state) {
460 .positional => {
461 if (runner.args.@"test" != null or
462 runner.args.manifest_file != null) return runner.fail(
463 "{t} specified multiple times",
464 .{state},
465 );
466 runner.args.@"test" = .{ .input_dir = input_dir, .dir = dir };
467 const manifest_path = "manifest";
468 runner.args.manifest_file = dir.openFile(
469 runner.io,
470 manifest_path,
471 .{},
472 ) catch |err| switch (err) {
473 error.Canceled => |e| return e,
474 else => |e| return runner.fail(
475 "unable to open manifest file: {t}",
476 .{e},
477 ),
478 };
479 try runner.discoverInput(input_dir, manifest_path);
480 },
481 .@"--lib" => {
482 if (runner.args.lib_dir != null) return runner.fail(
483 "\"{t}\" specified multiple times",
484 .{state},
485 );
486 runner.args.lib_dir = dir;
487 },
488 .@"--src" => {
489 if (runner.args.src_dir != null) return runner.fail(
490 "\"{t}\" specified multiple times",
491 .{state},
492 );
493 runner.args.src_dir = dir;
494 },
495 .@"--libc-runtimes" => {
496 if (runner.args.libc_runtimes_dir != null) return runner.fail(
497 "\"{t}\" specified multiple times",
498 .{state},
499 );
500 runner.args.libc_runtimes_dir = dir;
501 },
502 else => return runner.fail(
503 "\"{t}\" expected {t}, got {t}",
504 .{ state, state_expected.get(state), arg },
505 ),
506 }
507 },
508 .input_file, .input_file_content, .output_file => {
509 const file_handle: *align(1) const std.Io.File.Handle = @ptrCast(
510 args_body[args_body_offset..][0..@sizeOf(std.Io.File.Handle)],
511 );
512 args_body_offset += @sizeOf(std.Io.File.Handle);
513 const file = runner.io.vtable.inheritParentFile(
514 runner.io.userdata,
515 file_handle.*,
516 .{ .nonblocking = false },
517 ) catch |err| switch (err) {
518 error.Canceled => |e| return e,
519 else => |e| return runner.fail(
520 "unable to inherit parent file: {t}",
521 .{e},
522 ),
523 };
524 switch (state) {
525 .positional => {
526 if (runner.args.@"test" != null or
527 runner.args.manifest_file != null) return runner.fail(
528 "{t} specified multiple times",
529 .{state},
530 );
531 runner.args.manifest_file = file;
532 },
533 .@"--zig" => {
534 if (runner.args.zig_exe != null) return runner.fail(
535 "\"{t}\" specified multiple times",
536 .{state},
537 );
538 runner.args.zig_exe = file;
539 },
540 .@"--gdb" => {
541 if (runner.args.gdb_exe != null) return runner.fail(
542 "\"{t}\" specified multiple times",
543 .{state},
544 );
545 runner.args.gdb_exe = file;
546 },
547 .@"--lldb" => {
548 if (runner.args.lldb_exe != null) return runner.fail(
549 "\"{t}\" specified multiple times",
550 .{state},
551 );
552 runner.args.lldb_exe = file;
553 },
554 else => return runner.fail(
555 "\"{t}\" expected {t}, got {t}",
556 .{ state, state_expected.get(state), arg },
557 ),
558 }
559 },
560 }
561 state = .positional;
562 }
563 },
564 .query_test_metadata => {
565 const expected_panic_msgs = try arena.alloc(u32, runner.args.targets.items.len);
566 @memset(expected_panic_msgs, 0);
567 runner.server.serveTestMetadata(.{
568 .names = runner.args.targets.items,
569 .expected_panic_msgs = expected_panic_msgs,
570 .string_bytes = runner.args.target_bytes.items,
571 }) catch |err| switch (err) {
572 error.WriteFailed => return error.ServerWriteFailed,
573 };
574 },
575 .run_test => {
576 const test_index = runner.server.receiveBody_u32() catch unreachable;
577 runner.server.serveBodylessMessage(.test_started) catch |err| switch (err) {
578 error.WriteFailed => return error.ServerWriteFailed,
579 };
580 runner.server.serveTestResults(.{
581 .index = test_index,
582 .flags = .{
583 .status = if (runner.testOne(test_index))
584 .pass
585 else |err| status: switch (err) {
586 error.Canceled,
587 error.OutOfMemory,
588 error.ServerWriteFailed,
589 => |e| return e,
590 error.AlreadyReported => {
591 var eb = try runner.eb_wip.toOwnedBundle("");
592 defer eb.deinit(runner.gpa);
593 assert(eb.errorMessageCount() > 0); // already reported what?
594 runner.server.serveErrorBundle(
595 .error_bundle,
596 eb,
597 ) catch |server_err| switch (server_err) {
598 error.WriteFailed => return error.ServerWriteFailed,
599 };
600 const eb_wip: std.zig.ErrorBundle.Wip = try .init(runner.gpa);
601 runner.eb_wip.deinit();
602 runner.eb_wip = eb_wip;
603 break :status .fail;
604 },
605 error.SkipTest => .skip,
606 },
607 .fuzz = false,
608 .log_err_count = 0,
609 .leak_count = 0,
610 },
611 }) catch |err| switch (err) {
612 error.WriteFailed => return error.ServerWriteFailed,
613 };
614 assert(runner.eb_wip.root_list.items.len == 0); // failed to report
615 },
616 }
617 }
618}
619
620fn discoverInput(
621 runner: *Runner,
622 dir: std.zig.Server.Message.InputDir,
623 sub_path: []const u8,
624) error{ServerWriteFailed}!void {
625 return runner.discoverInputInner(dir, sub_path) catch |err| switch (err) {
626 error.WriteFailed => return error.ServerWriteFailed,
627 };
628}
629fn discoverInputInner(
630 runner: *Runner,
631 dir: std.zig.Server.Message.InputDir,
632 sub_path: []const u8,
633) std.Io.Writer.Error!void {
634 try runner.server.serveMessageHeader(.{
635 .tag = .discovered_inputs,
636 .bytes_len = @intCast(@sizeOf(std.zig.Server.Message.InputDir) +
637 sub_path.len + 1),
638 });
639 try runner.server.out.writeInt(u32, @backingInt(dir), .little);
640 try runner.server.out.writeAll(sub_path);
641 try runner.server.out.writeByte(0);
642 try runner.server.out.flush();
643}
644
645pub const SourceReader = struct {
646 const Io = std.Io;
647
648 backing: *std.Io.Reader,
649 line_aw: std.Io.Writer.Allocating,
650 interface: std.Io.Reader,
651 line: u32,
652 eb_wip: *std.zig.ErrorBundle.Wip,
653 src_path: std.zig.ErrorBundle.String,
654
655 pub fn init(
656 reader: *Io.Reader,
657 gpa: std.mem.Allocator,
658 eb_wip: *std.zig.ErrorBundle.Wip,
659 src_path: std.zig.ErrorBundle.String,
660 ) SourceReader {
661 return .{
662 .backing = reader,
663 .line_aw = .init(gpa),
664 .interface = .{
665 .vtable = &.{
666 .stream = stream,
667 .discard = discard,
668 .readVec = readVec,
669 .rebase = rebase,
670 },
671 .buffer = &.{},
672 .seek = 0,
673 .end = 0,
674 },
675 .line = 0,
676 .eb_wip = eb_wip,
677 .src_path = src_path,
678 };
679 }
680 fn deinit(s: *SourceReader) void {
681 s.line_aw.deinit();
682 }
683 fn stream(r: *Io.Reader, _: *Io.Writer, _: Io.Limit) Io.Reader.StreamError!usize {
684 const s: *SourceReader = @fieldParentPtr("interface", r);
685 try s.bufferLine();
686 return 0;
687 }
688 fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
689 const s: *SourceReader = @fieldParentPtr("interface", r);
690 const remaining = r.buffered();
691 if (remaining.len > 0) {
692 const n = limit.minInt(remaining.len);
693 r.toss(n);
694 return n;
695 }
696 try s.bufferLine();
697 return 0;
698 }
699 fn readVec(r: *Io.Reader, _: [][]u8) Io.Reader.Error!usize {
700 const s: *SourceReader = @fieldParentPtr("interface", r);
701 try s.bufferLine();
702 return 0;
703 }
704 fn rebase(r: *Io.Reader, capacity: usize) Io.Reader.RebaseError!void {
705 const s: *SourceReader = @fieldParentPtr("interface", r);
706 defer s.updateBuffer();
707 s.line_aw.ensureUnusedCapacity(capacity) catch |err| switch (err) {
708 error.OutOfMemory => return error.ReadFailed,
709 };
710 }
711 fn updateBuffer(s: *SourceReader) void {
712 s.interface.buffer = s.line_aw.writer.buffer;
713 s.interface.end = s.line_aw.writer.end;
714 }
715 fn bufferLine(s: *SourceReader) Io.Reader.Error!void {
716 defer s.updateBuffer();
717 {
718 const written = s.line_aw.written();
719 const start = if (std.mem.findScalarLast(u8, written[0..s.interface.seek], '\n')) |newline|
720 newline + 1
721 else
722 0;
723 s.line += @intCast(std.mem.countScalar(u8, written[0..start], '\n'));
724 const preserve = written[start..];
725 @memmove(written[0..preserve.len], preserve);
726 s.line_aw.shrinkRetainingCapacity(preserve.len);
727 s.interface.seek -= start;
728 }
729 const n = s.backing.streamDelimiterEnding(&s.line_aw.writer, '\n') catch |err| switch (err) {
730 error.ReadFailed => |e| return e,
731 error.WriteFailed => return error.ReadFailed,
732 };
733 if (std.mem.endsWith(u8, s.line_aw.written(), "\r")) s.line_aw.writer.end -= 1;
734 const buffered = s.backing.buffered();
735 if (n == 0 and buffered.len == 0) return error.EndOfStream;
736 if (std.mem.startsWith(u8, buffered, "\n")) {
737 s.line_aw.writer.writeByte('\n') catch |err| switch (err) {
738 error.WriteFailed => return error.ReadFailed,
739 };
740 s.backing.toss(1);
741 }
742 }
743 fn fail(
744 s: *SourceReader,
745 src_loc: []const u8,
746 comptime fmt: []const u8,
747 args: anytype,
748 ) FailError {
749 return s.failString(src_loc, try s.eb_wip.printString(fmt, args));
750 }
751 fn failString(
752 s: *SourceReader,
753 src_loc: []const u8,
754 msg: std.zig.ErrorBundle.String,
755 ) FailError {
756 try s.eb_wip.addRootErrorMessage(.{ .msg = msg, .src_loc = try s.srcLoc(src_loc) });
757 return error.AlreadyReported;
758 }
759 fn srcLoc(
760 s: *SourceReader,
761 src_loc: []const u8,
762 ) std.mem.Allocator.Error!std.zig.ErrorBundle.SourceLocationIndex {
763 const line: u32, const column: u32, const source_line = src_loc: {
764 const buffer = s.interface.buffer[0..s.interface.end];
765 const pos = src_loc.ptr - buffer.ptr;
766 const end = std.mem.findScalarPos(u8, buffer, pos, '\n') orelse buffer.len;
767 const start =
768 if (std.mem.findScalarLast(u8, buffer[0..end], '\n')) |newline| newline + 1 else 0;
769 break :src_loc .{
770 @intCast(s.line + std.mem.countScalar(u8, buffer[0..start], '\n')),
771 @intCast(pos - start),
772 buffer[start..end],
773 };
774 };
775 return s.eb_wip.addSourceLocation(.{
776 .src_path = s.src_path,
777 .line = line,
778 .column = column,
779 .span_start = column,
780 .span_main = column,
781 .span_end = @intCast(column + src_loc.len),
782 .source_line = try s.eb_wip.addString(source_line),
783 });
784 }
785};
786pub const DelimitedReader = struct {
787 const Io = std.Io;
788
789 unlimited: *Io.Reader,
790 remaining: Io.Limit,
791 delimiter: []const u8,
792 interface: Io.Reader,
793
794 pub fn init(reader: *Io.Reader, delimiter: []const u8, buffer: []u8) DelimitedReader {
795 return .{
796 .unlimited = reader,
797 .remaining = .nothing,
798 .delimiter = delimiter,
799 .interface = .{
800 .vtable = &.{
801 .stream = stream,
802 .discard = discard,
803 },
804 .buffer = buffer,
805 .seek = 0,
806 .end = 0,
807 },
808 };
809 }
810 fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
811 const d: *DelimitedReader = @fieldParentPtr("interface", r);
812 const block = try d.unlimited.peekGreedy(d.delimiter.len);
813 d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse
814 (block.len - d.delimiter.len + 1));
815 if (d.remaining == .nothing) return error.EndOfStream;
816 const n = try d.unlimited.stream(w, limit.min(d.remaining));
817 d.remaining = d.remaining.subtract(n).?;
818 return n;
819 }
820 fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
821 const d: *DelimitedReader = @fieldParentPtr("interface", r);
822 const block = try d.unlimited.peekGreedy(d.delimiter.len);
823 d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse
824 (block.len - d.delimiter.len + 1));
825 if (d.remaining == .nothing) return error.EndOfStream;
826 const n = try d.unlimited.discard(limit.min(d.remaining));
827 d.remaining = d.remaining.subtract(n).?;
828 return n;
829 }
830};
831
832const Update = struct {
833 target_query: []const u8,
834 manifest_sr: SourceReader,
835 compiler: ?Compiler,
836 src_dir: std.Io.Dir,
837 eb_wip: *std.zig.ErrorBundle.Wip,
838 cmd_src_loc: std.zig.ErrorBundle.SourceLocationIndex,
839 src_loc: ?std.zig.ErrorBundle.SourceLocationIndex,
840 num: usize,
841 name: []const u8,
842 prog_node: std.Progress.Node,
843 mtime: std.Io.Timestamp,
844 allow_skip: bool,
845
846 fn checkMatches(
847 update: *const Update,
848 what: []const u8,
849 expected: []const u8,
850 actual: []const u8,
851 ) FailError!void {
852 var expected_match_it = std.mem.splitSequence(u8, expected, "#...");
853 var pos: usize = 0;
854 while (expected_match_it.next()) |expected_match| : (pos += expected_match.len) {
855 var context_len: usize = expected_match.len;
856 pos = while (true) : (context_len -= 1) break std.mem.findPos(
857 u8,
858 actual,
859 pos,
860 expected_match[0..context_len],
861 ) orelse continue;
862 const actual_match = actual[pos..];
863 std.testing.expectEqualStrings(
864 expected_match,
865 actual_match[0..@min(actual_match.len, expected_match.len)],
866 ) catch |err| switch (err) {
867 error.TestExpectedEqual => {
868 std.testing.failPrint("\n=============== full output: =========\n", .{});
869 std.testing.failPrintWithVisibleNewlines(actual_match);
870 std.testing.failPrint("\n======================================\n", .{});
871 return update.fail("{s} did not match expected", .{what});
872 },
873 };
874 }
875 }
876
877 fn fail(update: *const Update, comptime fmt: []const u8, args: anytype) FailError {
878 return update.failString(try update.eb_wip.printString(fmt, args));
879 }
880 fn failString(update: *const Update, msg: std.zig.ErrorBundle.String) FailError {
881 var notes_buffer: [1]std.zig.ErrorBundle.ErrorMessage = undefined;
882 var notes_len: u32 = 0;
883 if (update.src_loc) |src_loc| {
884 notes_buffer[notes_len] = .{
885 .msg = try update.eb_wip.addString("after this update"),
886 .src_loc = src_loc,
887 };
888 notes_len += 1;
889 }
890 try update.eb_wip.addRootErrorMessageWithNotes(.{
891 .msg = msg,
892 .src_loc = update.cmd_src_loc,
893 .notes_len = 1,
894 }, notes_buffer[0..notes_len]);
895 return error.AlreadyReported;
896 }
897};
898
899const TestError = error{
900 SkipTest,
901} || FailError;
902fn testOne(runner: *Runner, test_index: u32) (error{ServerWriteFailed} || TestError)!void {
903 const target_query =
904 std.mem.sliceTo(runner.args.target_bytes.items[runner.args.targets.items[test_index]..], 0);
905 const manifest_file = runner.args.manifest_file orelse
906 return runner.fail("missing \"path/to/test\" arg", .{});
907 var manifest_buffer: [512]u8 = undefined;
908 var manifest_fr = manifest_file.reader(runner.io, &manifest_buffer);
909 var update: Update = .{
910 .target_query = target_query,
911 .manifest_sr = .init(&manifest_fr.interface, runner.gpa, &runner.eb_wip, src_path_string: {
912 var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
913 const src_path = src_path: {
914 break :src_path path_buffer[0 .. manifest_file.realPath(
915 runner.io,
916 &path_buffer,
917 ) catch |err| switch (err) {
918 error.Canceled => |e| return e,
919 else => break :src_path "manifest",
920 }];
921 };
922 break :src_path_string try runner.eb_wip.addString(src_path);
923 }),
924 .compiler = null,
925 .src_dir = (runner.args.src_dir orelse
926 return runner.fail("missing \"--src=path/to/src\" arg", .{})).createDirPathOpen(
927 runner.io,
928 target_query,
929 .{},
930 ) catch |err| switch (err) {
931 error.Canceled => |e| return e,
932 else => |e| return runner.fail("unable to create {q}: {t}", .{ target_query, e }),
933 },
934 .eb_wip = &runner.eb_wip,
935 .cmd_src_loc = undefined,
936 .src_loc = null,
937 .num = 0,
938 .name = undefined,
939 .prog_node = .none,
940 .mtime = .now(runner.io, .real),
941 .allow_skip = true,
942 };
943 defer {
944 update.manifest_sr.deinit();
945 if (update.compiler) |*comp| comp.deinit();
946 update.src_dir.close(runner.io);
947 update.prog_node.end();
948 }
949
950 // hack around `std.Io.Reader` interface bugs by ensuring the
951 // buffer is larger than the longest supported manifest line
952 try update.manifest_sr.line_aw.ensureTotalCapacityPrecise(512);
953 update.manifest_sr.updateBuffer();
954
955 var skip_delimiter = false;
956 while (update.manifest_sr.interface.takeSentinel('\n')) |line| {
957 var line_it = std.mem.tokenizeScalar(u8, line, ' ');
958 const cmd_src_loc = line_it.next() orelse continue;
959 if (skip_delimiter) {
960 assert(std.mem.eql(u8, cmd_src_loc, "#}"));
961 skip_delimiter = false;
962 const unexpected = line_it.rest();
963 if (unexpected.len > 0)
964 return update.manifest_sr.fail(unexpected, "unexpected argument {q}", .{unexpected});
965 continue;
966 }
967 update.cmd_src_loc = try update.manifest_sr.srcLoc(cmd_src_loc);
968 const cmd = std.meta.stringToEnum(Command, std.mem.cutPrefix(u8, cmd_src_loc, "#") orelse
969 return update.manifest_sr.fail(cmd_src_loc, "expected command", .{})) orelse
970 return update.manifest_sr.fail(cmd_src_loc, "unknown command", .{});
971 const arg = switch (cmd) {
972 .todo, .exe, .lib, .obj, .update => null,
973 .skip, .write, .delete, .check => line_it.next() orelse
974 return update.manifest_sr.fail(line_it.rest()[0..0], "missing argument", .{}),
975 };
976 var contents_file: ?std.Io.File = null;
977 defer if (contents_file) |file| file.close(runner.io);
978 var contents_buffer: [512]u8 = undefined;
979 var contents_impl: union(enum) {
980 r: std.Io.Reader,
981 dr: DelimitedReader,
982 fr: std.Io.File.Reader,
983 } = undefined;
984 const contents_r: *std.Io.Reader = contents_r: switch (cmd) {
985 .skip, .delete => {
986 const unexpected = line_it.rest();
987 if (unexpected.len > 0) return update.manifest_sr.fail(
988 unexpected,
989 "unexpected argument {q}",
990 .{unexpected},
991 );
992 contents_impl = .{ .r = .ending_instance };
993 break :contents_r &contents_impl.r;
994 },
995 else => {
996 const contents = line_it.rest();
997 if (contents.len == 0) {
998 contents_impl = .{ .r = .ending_instance };
999 break :contents_r &contents_impl.r;
1000 }
1001 contents: switch (contents[0]) {
1002 else => return update.manifest_sr.fail(contents, "invalid contents syntax", .{}),
1003 '"' => {
1004 if (contents.len < 2 or contents[contents.len - 1] != '"') continue :contents 0;
1005 var contents_fw: std.Io.Writer = .fixed(&contents_buffer);
1006 switch (std.zig.string_literal.parseWrite(
1007 &contents_fw,
1008 contents,
1009 ) catch |err| switch (err) {
1010 error.WriteFailed => return update.manifest_sr.fail(
1011 contents,
1012 "contents string too long, use inline or file syntax instead",
1013 .{},
1014 ),
1015 }) {
1016 .success => {
1017 contents_impl = .{ .r = .fixed(contents_fw.buffered()) };
1018 break :contents_r &contents_impl.r;
1019 },
1020 .failure => |err| return update.manifest_sr.fail(
1021 contents[err.offset()..][0..0],
1022 "{f}",
1023 .{err.fmt(contents)},
1024 ),
1025 }
1026 },
1027 '{' => {
1028 if (contents.len != 1) continue :contents 0;
1029
1030 contents_impl = .{
1031 .dr = .init(&update.manifest_sr.interface, "#}", &contents_buffer),
1032 };
1033 skip_delimiter = true;
1034 break :contents_r &contents_impl.dr.interface;
1035 },
1036 '@' => {
1037 const args_test = runner.args.@"test" orelse return update.manifest_sr.fail(
1038 contents,
1039 "contents file requires directory test",
1040 .{},
1041 );
1042 const contents_path = contents[1..];
1043 try runner.discoverInput(args_test.input_dir, contents_path);
1044
1045 contents_file = args_test.dir.openFile(
1046 runner.io,
1047 contents_path,
1048 .{},
1049 ) catch |err| switch (err) {
1050 error.Canceled => |e| return e,
1051 else => |e| return update.manifest_sr.fail(
1052 contents_path,
1053 "unable to open contents file: {t}",
1054 .{e},
1055 ),
1056 };
1057 contents_impl = .{ .fr = contents_file.?.reader(runner.io, &contents_buffer) };
1058 break :contents_r &contents_impl.fr.interface;
1059 },
1060 }
1061 },
1062 };
1063
1064 runner.handleCommand(&update, cmd_src_loc, cmd, arg, contents_r) catch |err| switch (err) {
1065 error.Canceled,
1066 error.OutOfMemory,
1067 error.AlreadyReported,
1068 error.SkipTest,
1069 => |e| return e,
1070 error.ContentsReadFailed => switch (contents_impl) {
1071 .r => unreachable,
1072 .dr => switch (manifest_fr.err orelse return error.OutOfMemory) {
1073 error.Canceled => |e| return e,
1074 else => |e| return update.fail("unable to read manifest: {t}", .{e}),
1075 },
1076 .fr => |fr| switch (fr.err.?) {
1077 error.Canceled => |e| return e,
1078 else => |e| return update.fail("unable to read contents file: {t}", .{e}),
1079 },
1080 },
1081 };
1082 } else |err| switch (err) {
1083 error.ReadFailed => switch (manifest_fr.err orelse return error.OutOfMemory) {
1084 error.Canceled => |e| return e,
1085 else => |e| return update.fail("unable to read manifest: {t}", .{e}),
1086 },
1087 error.StreamTooLong => unreachable,
1088 error.EndOfStream => {
1089 const final_line = update.manifest_sr.interface.buffered();
1090 switch (final_line.len) {
1091 0 => if (skip_delimiter) return update.manifest_sr.fail(
1092 final_line[0..0],
1093 "missing \"#}}\" delimiter",
1094 .{},
1095 ),
1096 else => return update.manifest_sr.fail(
1097 final_line[final_line.len..],
1098 "missing trailing newline",
1099 .{},
1100 ),
1101 }
1102 },
1103 }
1104 if (update.compiler) |*comp| {
1105 if (comp.state != .idle) return update.fail("missing \"#check\"", .{});
1106 try comp.exit();
1107 }
1108}
1109const Command = enum { todo, skip, exe, lib, obj, write, delete, update, check };
1110fn handleCommand(
1111 runner: *Runner,
1112 update: *Update,
1113 cmd_src_loc: []const u8,
1114 cmd: Command,
1115 arg: ?[]const u8,
1116 contents_r: *std.Io.Reader,
1117) (error{ContentsReadFailed} || TestError)!void {
1118 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
1119 const cmd_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "#{t}{s}{s}", .{
1120 cmd, if (arg) |_| " " else "", arg orelse "",
1121 }) catch &name_buffer, 0);
1122 defer cmd_prog_node.end();
1123
1124 cmd: switch (cmd) {
1125 .todo => _ = contents_r.discardRemaining() catch |err| switch (err) {
1126 error.ReadFailed => {},
1127 },
1128 .skip => if (!update.allow_skip) return update.manifest_sr.fail(
1129 cmd_src_loc,
1130 "\"#skip\" must appear before other commands",
1131 .{},
1132 ) else if (std.mem.eql(u8, update.target_query, arg.?)) return error.SkipTest,
1133 .exe, .lib, .obj => runner.spawnCompiler(&update.compiler, update.src_dir, switch (cmd) {
1134 else => unreachable,
1135 .exe => .Exe,
1136 .lib => .Lib,
1137 .obj => .Obj,
1138 }, contents_r, target: {
1139 var component_it = std.mem.splitBackwardsScalar(u8, update.target_query, '-');
1140 const pic_str = component_it.next() orelse
1141 return update.fail("target {q} missing pic", .{update.target_query});
1142 const pic = std.meta.stringToEnum(Compiler.Target.Pic, pic_str) orelse
1143 return update.fail("target {q} unknown pic {q}", .{ update.target_query, pic_str });
1144 const linker_str = component_it.next() orelse
1145 return update.fail("target {q} missing linker", .{update.target_query});
1146 const linker = std.meta.stringToEnum(Compiler.Target.Linker, linker_str) orelse
1147 return update.fail("target {q} unknown linker {q}", .{
1148 update.target_query, linker_str,
1149 });
1150 const backend_str = component_it.next() orelse
1151 return update.fail("target {q} missing backend", .{update.target_query});
1152 const backend = std.meta.stringToEnum(Compiler.Target.Backend, backend_str) orelse
1153 return update.fail("target {q} unknown backend {q}", .{
1154 update.target_query, backend_str,
1155 });
1156 const cache_mode_str = component_it.next() orelse
1157 return update.fail("target {q} missing cache mode", .{update.target_query});
1158 const cache_mode = std.meta.stringToEnum(Compiler.Target.CacheMode, cache_mode_str) orelse
1159 return update.fail("target {q} unknown cache mode {q}", .{
1160 update.target_query, cache_mode_str,
1161 });
1162 const triple_cpu = component_it.rest();
1163 var triple_len: usize = 0;
1164 for (0..3) |_| {
1165 const component_start = triple_len;
1166 triple_len = if (std.mem.findScalarPos(u8, triple_cpu, triple_len, '-')) |dash_pos|
1167 dash_pos + 1
1168 else
1169 return update.fail("target {q} missing cpu", .{update.target_query});
1170 if (std.mem.eql(u8, triple_cpu[component_start..triple_len], "native-")) break;
1171 }
1172 const triple = triple_cpu[0 .. triple_len - 1];
1173 const cpu = triple_cpu[triple_len..];
1174 var query_parse_diags: std.Target.Query.ParseOptions.Diagnostics = undefined;
1175 break :target .{
1176 .triple = triple,
1177 .resolved = std.zig.system.resolveTargetQuery(
1178 runner.io,
1179 std.Target.Query.parse(.{
1180 .arch_os_abi = triple,
1181 .cpu_features = cpu,
1182 .object_format = switch (backend) {
1183 .sema, .selfhosted, .llvm => null,
1184 .cbe => "c",
1185 },
1186 .diagnostics = &query_parse_diags,
1187 }) catch |err| switch (err) {
1188 error.UnknownArchitecture => return update.fail(
1189 "unable to resolve target {q}: unknown architecture: {q}",
1190 .{ update.target_query, query_parse_diags.unknown_architecture_name.? },
1191 ),
1192 error.MissingOperatingSystem => return update.fail(
1193 "unable to resolve target {q}: missing operating system",
1194 .{update.target_query},
1195 ),
1196 error.UnknownOperatingSystem => return update.fail(
1197 "unable to resolve target {q}: unknown operating system: {q}",
1198 .{ update.target_query, query_parse_diags.os_name.? },
1199 ),
1200 error.InvalidOperatingSystemVersion => return update.fail(
1201 "unable to resolve target {q}: invalid operating system version",
1202 .{update.target_query},
1203 ),
1204 error.UnknownApplicationBinaryInterface => return update.fail(
1205 "unable to resolve target {q}: unknown abi",
1206 .{update.target_query},
1207 ),
1208 error.InvalidAbiVersion => return update.fail(
1209 "unable to resolve target {q}: invalid abi version",
1210 .{update.target_query},
1211 ),
1212 error.UnexpectedExtraField => unreachable, // validated above
1213 error.UnknownCpuModel => return update.fail(
1214 "unable to resolve target {q}: unknown cpu: {q}",
1215 .{ update.target_query, query_parse_diags.cpu_name.? },
1216 ),
1217 error.UnknownCpuFeature => return update.fail(
1218 "unable to resolve target {q}: unknown cpu feature: {q}",
1219 .{ update.target_query, query_parse_diags.unknown_feature_name.? },
1220 ),
1221 error.UnknownObjectFormat => return update.fail(
1222 "unable to resolve target {q}: unknown object format",
1223 .{update.target_query},
1224 ),
1225 },
1226 ) catch |err| switch (err) {
1227 error.Canceled => |e| return e,
1228 else => |e| return update.fail("unable to resolve target {q}: {t}", .{ triple, e }),
1229 },
1230 .cache_mode = cache_mode,
1231 .backend = backend,
1232 .linker = linker,
1233 .pic = pic,
1234 };
1235 }) catch |err| switch (err) {
1236 error.Canceled, error.OutOfMemory, error.AlreadyReported, error.SkipTest => |e| return e,
1237 error.ReadFailed => return error.ContentsReadFailed,
1238 },
1239 .write => {
1240 const file = update.src_dir.createFile(runner.io, arg.?, .{}) catch |err| switch (err) {
1241 error.Canceled => |e| return e,
1242 else => |e| return update.manifest_sr.fail(arg.?, "unable to create file: {t}", .{e}),
1243 };
1244 defer file.close(runner.io);
1245 var fw_buffer: [512]u8 = undefined;
1246 var fw = file.writer(runner.io, &fw_buffer);
1247 _ = contents_r.streamRemaining(&fw.interface) catch |err| switch (err) {
1248 error.ReadFailed => return error.ContentsReadFailed,
1249 error.WriteFailed => switch (fw.err.?) {
1250 error.Canceled => |e| return e,
1251 else => |e| return update.manifest_sr.fail(
1252 arg.?,
1253 "unable to write file: {t}",
1254 .{e},
1255 ),
1256 },
1257 };
1258 fw.flush() catch |err| switch (err) {
1259 error.Canceled => |e| return e,
1260 else => |e| return update.manifest_sr.fail(
1261 arg.?,
1262 "unable to write file: {t}",
1263 .{e},
1264 ),
1265 };
1266 file.setTimestamps(runner.io, .{ .modify_timestamp = .{
1267 .new = update.mtime,
1268 } }) catch |err| switch (err) {
1269 error.Canceled => |e| return e,
1270 else => |e| return update.manifest_sr.fail(
1271 arg.?,
1272 "unable to update file timestamps: {t}",
1273 .{e},
1274 ),
1275 };
1276 },
1277 .delete => update.src_dir.deleteFile(runner.io, arg.?) catch |err| switch (err) {
1278 error.Canceled => |e| return e,
1279 else => |e| return update.manifest_sr.fail(arg.?, "unable to delete file: {t}", .{e}),
1280 },
1281 .update => {
1282 update.src_loc = update.cmd_src_loc;
1283 const comp = &(update.compiler orelse return update.manifest_sr.fail(
1284 cmd_src_loc,
1285 "missing \"#exe\", \"#lib\", or \"#obj\"",
1286 .{},
1287 ));
1288 update.prog_node.end();
1289 const arena = runner.arena.allocator();
1290 update.name = switch (contents_r.readSliceShort(&name_buffer) catch |err| switch (err) {
1291 error.ReadFailed => return error.ContentsReadFailed,
1292 }) {
1293 0 => switch (update.num) {
1294 0 => "initial update",
1295 else => |num| try arena.print("update {d}", .{num}),
1296 },
1297 else => |name_len| try arena.dupe(u8, name_buffer[0..name_len]),
1298 };
1299 update.prog_node = runner.prog_node.start(update.name, 0);
1300 _ = contents_r.discardRemaining() catch |err| switch (err) {
1301 error.ReadFailed => return error.ContentsReadFailed,
1302 };
1303 if (comp.state != .idle)
1304 return update.manifest_sr.fail(cmd_src_loc, "missing \"#check\"", .{});
1305 comp.client.serveBodylessMessage(.update) catch |err| switch (err) {
1306 error.WriteFailed => switch (comp.fw.err.?) {
1307 error.Canceled => |e| return e,
1308 else => |e| return update.fail("unable to send message to compiler: {t}", .{e}),
1309 },
1310 };
1311 comp.state = .update;
1312 update.mtime = update.mtime.addDuration(.fromSeconds(2));
1313 update.num += 1;
1314 },
1315 .check => {
1316 const check = std.meta.stringToEnum(Compiler.Check, arg.?) orelse
1317 return update.manifest_sr.fail(arg.?, "unknown check", .{});
1318 const expected =
1319 contents_r.allocRemaining(runner.gpa, .unlimited) catch |err| switch (err) {
1320 error.OutOfMemory => |e| return e,
1321 error.ReadFailed => return error.ContentsReadFailed,
1322 error.StreamTooLong => unreachable, // .unlimited
1323 };
1324 defer runner.gpa.free(expected);
1325
1326 const comp = &(update.compiler orelse return update.manifest_sr.fail(
1327 cmd_src_loc,
1328 "missing \"#exe\", \"#lib\", or \"#obj\"",
1329 .{},
1330 ));
1331 if (comp.state != .update)
1332 return update.manifest_sr.fail(cmd_src_loc, "missing \"#update\"", .{});
1333 const stderr = comp.mr.reader(1);
1334 while (try comp.receiveMessage()) |message| switch (message.tag) {
1335 else => {}, // Ignore other messages,
1336 .config => {
1337 var body_r: std.Io.Reader = .fixed(message.body);
1338 comp.config = body_r.takeStruct(std.zig.Server.Message.Config, .little) catch
1339 unreachable;
1340 },
1341 .emit_digest => {
1342 var body_r: std.Io.Reader = .fixed(message.body);
1343 _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch
1344 unreachable;
1345 const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch
1346 unreachable;
1347 try comp.checkSuccess(update, check, expected, digest);
1348 },
1349 .error_bundle => {
1350 const eb = std.zig.Server.allocErrorBundle(
1351 runner.arena.allocator(),
1352 message.body,
1353 ) catch |err| switch (err) {
1354 error.OutOfMemory => |e| return e,
1355 error.EndOfStream => return update.fail("error bundle truncated", .{}),
1356 };
1357 switch (check) {
1358 .errors => {
1359 var error_aw: std.Io.Writer.Allocating = .init(runner.gpa);
1360 defer error_aw.deinit();
1361 eb.renderToWriter(.{
1362 .include_reference_trace = false,
1363 .include_source_line = false,
1364 .include_log_text = true,
1365 }, &error_aw.writer) catch |err| switch (err) {
1366 error.WriteFailed => return error.OutOfMemory,
1367 };
1368 const actual = error_aw.written();
1369 std.mem.replaceScalar(u8, actual, '\\', '/');
1370 std.testing.expectEqualStrings(expected, actual) catch |err| switch (err) {
1371 error.TestExpectedEqual => return update.fail(
1372 "errors did not match expected",
1373 .{},
1374 ),
1375 };
1376 },
1377 .stdout, .exit, .llvm, .gdb, .lldb => if (eb.errorMessageCount() > 0) {
1378 eb.renderToStderr(runner.io, .{}, .auto) catch |err| switch (err) {
1379 error.Canceled => {},
1380 else => {},
1381 };
1382 return update.fail("unexpected compile errors", .{});
1383 },
1384 }
1385 comp.state = .idle;
1386 break :cmd;
1387 },
1388 };
1389
1390 const stderr_buffered = stderr.buffered();
1391 if (stderr_buffered.len > 0) {
1392 if (comp.allow_compiler_stderr) {
1393 std.log.info("#update {q} stderr:\n{s}", .{ update.name, stderr_buffered });
1394 } else return update.fail("unexpected stderr:\n{s}", .{stderr_buffered});
1395 }
1396
1397 return update.fail("compiler failed to send terminating error_bundle", .{});
1398 },
1399 }
1400 assert((contents_r.discardRemaining() catch |err| switch (err) {
1401 error.ReadFailed => return error.ContentsReadFailed,
1402 }) == 0);
1403 switch (cmd) {
1404 .todo, .skip => {},
1405 else => update.allow_skip = false,
1406 }
1407}
1408
1409fn spawnCompiler(
1410 runner: *Runner,
1411 compiler: *?Compiler,
1412 src_dir: std.Io.Dir,
1413 output_mode: std.lang.OutputMode,
1414 args_r: *std.Io.Reader,
1415 target: Compiler.Target,
1416) (std.Io.Reader.ShortError || TestError)!void {
1417 const gpa = runner.gpa;
1418 const arena = runner.arena.allocator();
1419 if (compiler.*) |*comp| {
1420 comp.deinit();
1421 compiler.* = null;
1422 }
1423 errdefer compiler.* = null;
1424
1425 compiler.* = .{
1426 .runner = runner,
1427 .allow_compiler_stderr = true,
1428 .output_mode = output_mode,
1429 .state = .idle,
1430 .child = undefined,
1431 .fw = undefined,
1432 .mr_buffer = undefined,
1433 .mr = undefined,
1434 .client = undefined,
1435 .target = target,
1436 .emit_name = null,
1437 .prog_node = .none,
1438 .config = null,
1439 };
1440 const comp = &compiler.*.?;
1441
1442 var argv: std.ArrayList([]const u8) = .empty;
1443 defer argv.deinit(gpa);
1444 try argv.appendSlice(gpa, &.{
1445 "zig",
1446 switch (output_mode) {
1447 .Obj => "build-obj",
1448 .Exe => "build-exe",
1449 .Lib => "build-lib",
1450 },
1451 "-target",
1452 comp.target.triple,
1453 "--cache-dir",
1454 ".zig-cache",
1455 });
1456 switch (comp.target.cache_mode) {
1457 .whole => {},
1458 .incremental => try argv.append(gpa, "-fincremental"),
1459 }
1460 var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
1461 if (comp.target.backend) |b| switch (b) {
1462 .sema => try argv.append(gpa, "-fno-emit-bin"),
1463 .selfhosted => try argv.append(gpa, "-fno-llvm"),
1464 .llvm => try argv.append(gpa, "-fllvm"),
1465 .cbe => try argv.append(gpa, "-ofmt=c"),
1466 } else try argv.appendSlice(gpa, &.{ "-I", path_buffer[0 .. (runner.args.lib_dir orelse
1467 return runner.fail("\"--lib=path/to/lib\" arg required with cbe", .{}))
1468 .realPath(runner.io, &path_buffer) catch |err| switch (err) {
1469 error.Canceled => |e| return e,
1470 else => |e| return runner.fail("unable to get lib path: {t}", .{e}),
1471 }] });
1472 switch (comp.target.linker) {
1473 .lld => try argv.appendSlice(gpa, &.{ "-flld", "-fno-new-linker" }),
1474 .old => try argv.appendSlice(gpa, &.{ "-fno-lld", "-fno-new-linker" }),
1475 .new => try argv.appendSlice(gpa, &.{ "-fno-lld", "-fnew-linker" }),
1476 }
1477 switch (comp.target.pic) {
1478 .nopic => try argv.appendSlice(gpa, &.{ "-fno-PIC", "-fno-PIE" }),
1479 .pic => try argv.appendSlice(gpa, &.{ "-fPIC", "-fno-PIE" }),
1480 .pie => try argv.appendSlice(gpa, &.{ "-fPIC", "-fPIE" }),
1481 }
1482
1483 var need: usize = 1;
1484 var state: enum { positional, @"--name" } = .positional;
1485 var root_name: ?[]const u8 = null;
1486 var emit: ?std.zig.EmitArtifact = .bin;
1487 while (true) {
1488 const done = if (args_r.fill(need)) false else |err| switch (err) {
1489 error.ReadFailed => |e| return e,
1490 error.EndOfStream => true,
1491 };
1492 const buffered = args_r.buffered();
1493 const start = std.mem.findNone(u8, buffered, "\n ") orelse buffered.len;
1494 const end = std.mem.findAnyPos(u8, buffered, start, "\n ") orelse buffered.len;
1495 if (end - start > 0) {
1496 const arg = try arena.dupe(u8, buffered[start..end]);
1497 switch (state) {
1498 .positional => if (std.mem.eql(u8, arg, "--name")) {
1499 state = .@"--name";
1500 } else if (std.mem.cutPrefix(u8, arg, "-ofmt=")) |ofmt| {
1501 comp.target.resolved.ofmt =
1502 std.meta.stringToEnum(std.Target.ObjectFormat, ofmt) orelse
1503 return runner.fail("invalid object format {q}", .{ofmt});
1504 } else if (std.mem.cutPrefix(u8, arg, "-M")) |module| {
1505 var module_name_it = std.mem.splitScalar(u8, module, '=');
1506 const module_name = module_name_it.next().?;
1507 root_name = root_name orelse module_name;
1508 } else if (std.mem.eql(u8, arg, "-femit-asm")) {
1509 emit = .@"asm";
1510 } else if (std.mem.eql(u8, arg, "-femit-bin")) {
1511 emit = .bin;
1512 } else if (std.mem.eql(u8, arg, "-femit-docs")) {
1513 emit = .docs;
1514 } else if (std.mem.eql(u8, arg, "-femit-h")) {
1515 emit = .h;
1516 } else if (std.mem.eql(u8, arg, "-femit-implib")) {
1517 emit = .implib;
1518 } else if (std.mem.eql(u8, arg, "-femit-llvm-bc")) {
1519 emit = .llvm_bc;
1520 } else if (std.mem.eql(u8, arg, "-femit-llvm-ir")) {
1521 emit = .llvm_ir;
1522 } else if (std.mem.eql(u8, arg, "-fno-emit-asm")) {
1523 if (emit == .@"asm") emit = null;
1524 } else if (std.mem.eql(u8, arg, "-fno-emit-bin")) {
1525 if (emit == .bin) emit = null;
1526 } else if (std.mem.eql(u8, arg, "-fno-emit-docs")) {
1527 if (emit == .docs) emit = null;
1528 } else if (std.mem.eql(u8, arg, "-fno-emit-h")) {
1529 if (emit == .h) emit = null;
1530 } else if (std.mem.eql(u8, arg, "-fno-emit-implib")) {
1531 if (emit == .implib) emit = null;
1532 } else if (std.mem.eql(u8, arg, "-fno-emit-llvm-bc")) {
1533 if (emit == .llvm_bc) emit = null;
1534 } else if (std.mem.eql(u8, arg, "-fno-emit-llvm-ir")) {
1535 if (emit == .llvm_ir) emit = null;
1536 },
1537 .@"--name" => {
1538 root_name = arg;
1539 state = .positional;
1540 },
1541 }
1542 try argv.append(gpa, arg);
1543 args_r.toss(end);
1544 need = 1;
1545 } else {
1546 args_r.toss(start);
1547 need = buffered.len - start + 1;
1548 }
1549 if (done) break;
1550 }
1551 try argv.append(gpa, "--listen=-");
1552
1553 comp.emit_name = if (emit) |e| try e.cacheName(arena, .{
1554 .root_name = root_name orelse "root",
1555 .cpu_arch = comp.target.resolved.cpu.arch,
1556 .os_tag = comp.target.resolved.os.tag,
1557 .ofmt = comp.target.resolved.ofmt,
1558 .abi = comp.target.resolved.abi,
1559 .output_mode = output_mode,
1560 }) else null;
1561
1562 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
1563 comp.prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "compile {s}", .{
1564 comp.emit_name orelse root_name orelse "root",
1565 }) catch &name_buffer, 0);
1566 errdefer comp.prog_node.end();
1567
1568 comp.child = std.process.spawn(runner.io, .{
1569 .exe = .{ .file = runner.args.zig_exe orelse
1570 return runner.fail("missing \"--zig=path/to/zig\" arg", .{}) },
1571 .argv = argv.items,
1572 .cwd = .{ .dir = src_dir },
1573 .progress_node = comp.prog_node,
1574 .stdin = .pipe,
1575 .stdout = .pipe,
1576 .stderr = .pipe,
1577 }) catch |err| switch (err) {
1578 error.Canceled, error.OutOfMemory => |e| return e,
1579 error.OperationUnsupported => return error.SkipTest,
1580 else => |e| return runner.fail("unable to spawn compiler: {t}", .{e}),
1581 };
1582 comp.fw = comp.child.stdin.?.writerStreaming(runner.io, &.{});
1583 comp.mr.init(gpa, runner.io, comp.mr_buffer.toStreams(), &.{
1584 comp.child.stdout.?, comp.child.stderr.?,
1585 });
1586 comp.client = .{ .in = comp.mr.reader(0), .out = &comp.fw.interface };
1587}
1588const Compiler = struct {
1589 runner: *Runner,
1590 allow_compiler_stderr: bool,
1591 output_mode: std.lang.OutputMode,
1592 state: enum { idle, update },
1593 child: std.process.Child,
1594 fw: std.Io.File.Writer,
1595 mr_buffer: std.Io.File.MultiReader.Buffer(2),
1596 mr: std.Io.File.MultiReader,
1597 client: std.zig.Client,
1598 target: Compiler.Target,
1599 emit_name: ?[]const u8,
1600 prog_node: std.Progress.Node,
1601 config: ?std.zig.Server.Message.Config,
1602
1603 const Target = struct {
1604 triple: []const u8,
1605 resolved: std.Target,
1606 cache_mode: CacheMode,
1607 backend: ?Backend,
1608 linker: Linker,
1609 pic: Pic,
1610
1611 const CacheMode = enum { whole, incremental };
1612
1613 const Backend = enum {
1614 /// Run semantic analysis only. Runtime output will not be tested, but we still verify
1615 /// that compilation succeeds. Corresponds to `-fno-emit-bin`.
1616 sema,
1617 /// Use the self-hosted code generation backend for this target.
1618 /// Corresponds to `-fno-llvm -fno-lld`.
1619 selfhosted,
1620 /// Use the LLVM backend.
1621 /// Corresponds to `-fllvm -flld`.
1622 llvm,
1623 /// Use the C backend. The output is compiled with `zig cc`.
1624 /// Corresponds to `-ofmt=c`.
1625 cbe,
1626 };
1627
1628 const Linker = enum { old, new, lld };
1629
1630 const Pic = enum { nopic, pic, pie };
1631 };
1632
1633 fn deinit(comp: *Compiler) void {
1634 comp.child.kill(comp.runner.io);
1635 comp.prog_node.end();
1636 comp.prog_node = .none;
1637 }
1638
1639 fn receiveMessage(comp: *Compiler) FailError!?struct {
1640 tag: std.zig.Server.Message.Tag,
1641 body: []const u8,
1642 } {
1643 const header = comp.client.receiveMessageWithMultiReader(
1644 &comp.mr,
1645 .none,
1646 ) catch |err| switch (err) {
1647 error.Canceled, error.OutOfMemory => |e| return e,
1648 error.Timeout => unreachable,
1649 error.EndOfStream => return null,
1650 else => |e| return comp.runner.fail("unable to receive message: {t}", .{e}),
1651 };
1652 const body = comp.client.in.take(header.bytes_len) catch unreachable;
1653 const stderr = comp.mr.reader(1);
1654 if (stderr.bufferedLen() > 0) {
1655 if (comp.allow_compiler_stderr) {
1656 std.log.info("{t} stderr:\n{s}", .{ header.tag, stderr.buffered() });
1657 } else {
1658 return comp.runner.fail("{t} unexpected stderr:\n{s}", .{
1659 header.tag, stderr.buffered(),
1660 });
1661 }
1662 stderr.tossBuffered();
1663 }
1664 return .{ .tag = header.tag, .body = body };
1665 }
1666
1667 const Check = enum { errors, stdout, exit, llvm, gdb, lldb };
1668 fn checkSuccess(
1669 comp: *Compiler,
1670 update: *const Update,
1671 check: Check,
1672 expected: []const u8,
1673 digest: *const std.Build.Cache.BinDigest,
1674 ) TestError!void {
1675 const runner = comp.runner;
1676 const gpa = runner.gpa;
1677 const arena = runner.arena.allocator();
1678 const io = runner.io;
1679 const target = &comp.target.resolved;
1680 const emit_name = switch (check) {
1681 else => comp.emit_name orelse
1682 return update.fail("unable to perform {t} check without emitted file", .{check}),
1683 .errors => return, // expect no emitted file
1684 };
1685 const config = &comp.config.?;
1686 var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
1687
1688 // After a failure while running a script, the debugger starts accepting commands from stdin, and
1689 // because it is empty, the debugger exits normally with status 0. Choose a non-zero status to
1690 // return from the debugger script instead to detect it running to completion and indicate success.
1691 const debug_success = 99;
1692
1693 const tmp_dir_path = ".zig-cache" ++ std.Io.Dir.path.sep_str ++ "tmp";
1694 update.src_dir.createDirPath(io, tmp_dir_path) catch |err| switch (err) {
1695 error.Canceled => |e| return e,
1696 else => |e| return update.fail("unable to create dir path {q}: {t}", .{
1697 tmp_dir_path, e,
1698 }),
1699 };
1700
1701 const emit_path = emit_path: {
1702 const emit_dir_path = ".zig-cache" ++ std.Io.Dir.path.sep_str ++
1703 "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*);
1704 const emit_path =
1705 try std.Io.Dir.path.join(arena, &.{ emit_dir_path, emit_name });
1706 break :emit_path switch (comp.target.backend.?) {
1707 .sema => return,
1708 .selfhosted, .llvm => emit_path,
1709 .cbe => try comp.compileC(update.src_dir, emit_path),
1710 };
1711 };
1712
1713 const emit_file = update.src_dir.openFile(io, emit_path, .{}) catch |err| switch (err) {
1714 error.Canceled => |e| return e,
1715 else => |e| return update.fail("unable to open {q}: {t}", .{ emit_path, e }),
1716 };
1717 defer emit_file.close(io);
1718
1719 var argv: std.ArrayList([]const u8) = try .initCapacity(arena, 5 + 4);
1720 var environ_map: std.process.Environ.Map = .init(gpa);
1721 defer environ_map.deinit();
1722 try environ_map.putAll(&runner.environ_map);
1723 var script_buffer: [512]u8 = undefined;
1724 const script_path, const script_file, var script_fw = script: switch (check) {
1725 .errors => unreachable,
1726 .stdout, .exit => .{ undefined, null, undefined },
1727 .llvm => {
1728 var emit_fr = emit_file.reader(io, &.{});
1729 const actual = emit_fr.interface.readAllocAll(arena, @intCast(
1730 emit_fr.getSize() catch |err| switch (err) {
1731 error.Canceled => |e| return e,
1732 else => |e| return runner.fail("unable to get size of {q}: {t}", .{ emit_path, e }),
1733 },
1734 )) catch |err| switch (switch (err) {
1735 error.OutOfMemory => |e| return e,
1736 error.EndOfStream => |e| e,
1737 error.ReadFailed => emit_fr.err.?,
1738 }) {
1739 error.Canceled => |e| return e,
1740 else => |e| return runner.fail("unable to read {q}: {t}", .{ emit_path, e }),
1741 };
1742 return update.checkMatches("LLVM IR", expected, actual);
1743 },
1744 .gdb => {
1745 const script_path = tmp_dir_path ++ std.Io.Dir.path.sep_str ++ "gdb";
1746 argv.appendSliceAssumeCapacity(&.{ "gdb", "--batch", "--command", script_path, "--args" });
1747 const script_file =
1748 update.src_dir.createFile(io, script_path, .{}) catch |err| switch (err) {
1749 error.Canceled => |e| return e,
1750 else => |e| return update.fail("unable to create debugger script {q}: {t}", .{
1751 script_path, e,
1752 }),
1753 };
1754 errdefer script_file.close(io);
1755 var script_fw = script_file.writer(io, &script_buffer);
1756 script_fw.interface.writeAll(
1757 \\set remotetimeout 0
1758 \\
1759 \\
1760 ) catch |err| switch (err) {
1761 error.WriteFailed => switch (script_fw.err.?) {
1762 error.Canceled => |e| return e,
1763 else => |e| return update.fail("unable to write debugger script {q}: {t}", .{
1764 script_path, e,
1765 }),
1766 },
1767 };
1768 break :script .{ script_path, script_file, script_fw };
1769 },
1770 .lldb => {
1771 const script_path = tmp_dir_path ++ std.Io.Dir.path.sep_str ++ "lldb";
1772 argv.appendSliceAssumeCapacity(&.{ "lldb", "--batch", "--source", script_path, "--" });
1773 try environ_map.put("LANG", "C.UTF-8"); // affects output formatting
1774 const script_file =
1775 update.src_dir.createFile(io, script_path, .{}) catch |err| switch (err) {
1776 error.Canceled => |e| return e,
1777 else => |e| return update.fail("unable to create debugger script {q}: {t}", .{
1778 script_path, e,
1779 }),
1780 };
1781 errdefer script_file.close(io);
1782 var script_fw = script_file.writer(io, &script_buffer);
1783 script_fw.interface.writeAll(
1784 \\settings set frame-format 'frame #${frame.index}:{ ${module.file.basename}{\`${function.name-with-args}{${frame.no-debug}${function.pc-offset}}}}{ at ${line.file.basename}:${line.number}{:${line.column}}}{${function.is-optimized} [opt]}{${frame.is-artificial} [artificial]}\n'
1785 \\settings set plugin.process.gdb-remote.packet-timeout 0
1786 \\settings set stop-line-count-after 0
1787 \\settings set stop-line-count-before 0
1788 \\
1789 ) catch |err| switch (err) {
1790 error.WriteFailed => switch (script_fw.err.?) {
1791 error.Canceled => |e| return e,
1792 else => |e| return update.fail("unable to write debugger script {q}: {t}", .{
1793 script_path, e,
1794 }),
1795 },
1796 };
1797 break :script .{ script_path, script_file, script_fw };
1798 },
1799 };
1800 defer if (script_file) |file| file.close(io);
1801
1802 const need_cross_libc =
1803 target.os.tag == .linux and config.flags.link_libc and config.flags.link_mode == .dynamic;
1804 const use_executor = use_executor: switch (std.zig.system.getExternalExecutor(io, target, .{
1805 .host_cpu_arch = runner.host.cpu.arch,
1806 .host_os_tag = runner.host.os.tag,
1807 .qemu_fixes_dl = need_cross_libc and runner.args.libc_runtimes_dir != null,
1808 .link_mode = config.flags.link_mode,
1809 .link_libc = config.flags.link_libc,
1810 })) {
1811 .bad_dl, .bad_os_or_cpu => {
1812 // This binary cannot be executed on this host.
1813 if (!runner.args.quiet) std.log.warn("skipping execution because host {q} cannot " ++
1814 "execute binaries for foreign target {q}", .{
1815 try runner.host.zigTriple(arena), comp.target.triple,
1816 });
1817 return;
1818 },
1819 .native => false,
1820 .darling => |executor| if (runner.args.enable_darling) {
1821 argv.appendAssumeCapacity(executor);
1822 break :use_executor true;
1823 } else continue :use_executor .bad_os_or_cpu,
1824 .qemu => |executor| if (runner.args.enable_qemu) {
1825 argv.appendAssumeCapacity(executor);
1826 if (need_cross_libc) {
1827 const libc_runtimes_dir = runner.args.libc_runtimes_dir orelse
1828 continue :use_executor .bad_os_or_cpu;
1829 const libc_runtimes_path = path_buffer[0 .. libc_runtimes_dir.realPath(
1830 io,
1831 &path_buffer,
1832 ) catch |err| switch (err) {
1833 error.Canceled => |e| return e,
1834 else => |e| return update.fail("unable to get libc runtimes path: {t}", .{e}),
1835 }];
1836 argv.appendSliceAssumeCapacity(&.{ "-L", try std.Io.Dir.path.join(arena, &.{
1837 libc_runtimes_path,
1838 try if (target.isGnuLibC()) std.zig.target.glibcRuntimeTriple(
1839 arena,
1840 target.cpu.arch,
1841 target.os.tag,
1842 target.abi,
1843 ) else if (target.isMuslLibC()) std.zig.target.muslRuntimeTriple(
1844 arena,
1845 target.cpu.arch,
1846 target.abi,
1847 ) else unreachable,
1848 }) });
1849 }
1850 break :use_executor true;
1851 } else continue :use_executor .bad_os_or_cpu,
1852 .rosetta => if (runner.args.enable_rosetta)
1853 true
1854 else
1855 continue :use_executor .bad_os_or_cpu,
1856 .wine => |executor| if (runner.args.enable_wine) {
1857 if (!environ_map.contains("WINEDEBUG")) try environ_map.put("WINEDEBUG", "-all");
1858 argv.appendAssumeCapacity(executor);
1859 break :use_executor true;
1860 } else continue :use_executor .bad_os_or_cpu,
1861 .wasmtime => |executor| if (runner.args.enable_wasmtime) {
1862 if (!environ_map.contains("WASMTIME_BACKTRACE_DETAILS"))
1863 try environ_map.put("WASMTIME_BACKTRACE_DETAILS", "1");
1864 argv.appendSliceAssumeCapacity(&.{ executor, "--dir=.", "-Sinherit-env" });
1865 break :use_executor true;
1866 } else continue :use_executor .bad_os_or_cpu,
1867 };
1868 argv.appendAssumeCapacity(emit_path);
1869
1870 if (!runner.args.quiet) std.log.info("{f}", .{std.zig.SubprocessCommand{
1871 .argv = argv.items,
1872 .cwd = cwd_path: {
1873 break :cwd_path path_buffer[0 .. update.src_dir.realPath(
1874 io,
1875 &path_buffer,
1876 ) catch |err| switch (err) {
1877 error.Canceled => |e| return e,
1878 else => break :cwd_path null,
1879 }];
1880 },
1881 .parent_env = &runner.environ_map,
1882 .child_env = &environ_map,
1883 }});
1884
1885 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
1886 const run_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "run {s}", .{
1887 emit_name,
1888 }) catch &name_buffer, 0);
1889 defer run_prog_node.end();
1890
1891 const result = std.process.run(gpa, io, .{
1892 .exe = exe: switch (check) {
1893 .errors, .llvm => unreachable,
1894 .stdout, .exit => if (use_executor) .search else .{ .file = emit_file },
1895 .gdb, .lldb => {
1896 if (use_executor) {
1897 if (!runner.args.quiet) std.log.warn(
1898 "skipping debugging of {q} via executor for foreign target {q}",
1899 .{ emit_path, comp.target.triple },
1900 );
1901 return;
1902 }
1903
1904 var line_it = std.mem.splitScalar(u8, expected, '\n');
1905 while (line_it.next()) |line| if (std.mem.cutPrefix(u8, line, switch (check) {
1906 else => unreachable,
1907 .gdb => "(gdb) ",
1908 .lldb => "(lldb) ",
1909 })) |cmd| script_fw.interface.print(
1910 \\{s}
1911 \\
1912 , .{cmd}) catch |err| switch (err) {
1913 error.WriteFailed => switch (script_fw.err.?) {
1914 error.Canceled => |e| return e,
1915 else => |e| return update.fail("unable to write debugger script {q}: {t}", .{
1916 script_path, e,
1917 }),
1918 },
1919 };
1920 script_fw.interface.print(
1921 \\
1922 \\quit {d}
1923 \\
1924 , .{debug_success}) catch |err| switch (err) {
1925 error.WriteFailed => switch (script_fw.err.?) {
1926 error.Canceled => |e| return e,
1927 else => |e| return update.fail("unable to write debugger script {q}: {t}", .{
1928 script_path, e,
1929 }),
1930 },
1931 };
1932 script_fw.flush() catch |err| switch (err) {
1933 error.Canceled => |e| return e,
1934 else => |e| return update.fail("unable to write debugger script {q}: {t}", .{
1935 script_path, e,
1936 }),
1937 };
1938 break :exe .{ .file = switch (check) {
1939 else => unreachable,
1940 .gdb => runner.args.gdb_exe,
1941 .lldb => runner.args.lldb_exe,
1942 } orelse {
1943 if (!runner.args.quiet) std.log.warn(
1944 "skipping debugging of {q} because of missing \"--{t}=path/to/{[1]t}\" arg",
1945 .{ emit_path, check },
1946 );
1947 return;
1948 } };
1949 },
1950 },
1951 .argv = argv.items,
1952 .cwd = .{ .dir = update.src_dir },
1953 .environ_map = &environ_map,
1954 .progress_node = run_prog_node,
1955 }) catch |err| if (use_executor) {
1956 // Chances are the foreign executor isn't available. Skip this evaluation.
1957 if (!runner.args.quiet) std.log.warn(
1958 "skipping execution of {q} via executor for foreign target {q}: {t}",
1959 .{ emit_path, comp.target.triple, err },
1960 );
1961 return;
1962 } else return update.fail("unable to run the generated executable {q}: {t}", .{
1963 emit_path, err,
1964 });
1965 defer {
1966 gpa.free(result.stdout);
1967 gpa.free(result.stderr);
1968 }
1969 switch (result.term) {
1970 .exited => |code| switch (check) {
1971 .errors, .llvm => unreachable,
1972 .stdout => {
1973 if (code != 0) return update.fail(
1974 "generated executable {q} failed with code {d}",
1975 .{ emit_path, code },
1976 );
1977 std.testing.expectEqualStrings(
1978 expected,
1979 result.stdout,
1980 ) catch |err| switch (err) {
1981 error.TestExpectedEqual => return update.fail(
1982 "stdout did not match expected",
1983 .{},
1984 ),
1985 };
1986 },
1987 .exit => {
1988 var actual_code_buffer: [std.fmt.count("{d}", .{std.math.maxInt(u8)})]u8 =
1989 undefined;
1990 std.testing.expectEqualStrings(
1991 expected,
1992 std.mem.print(&actual_code_buffer, "{d}", .{code}) catch unreachable,
1993 ) catch |err| switch (err) {
1994 error.TestExpectedEqual => return update.fail(
1995 "exit code did not match expected",
1996 .{},
1997 ),
1998 };
1999 },
2000 .gdb, .lldb => {
2001 if (code != debug_success) return update.fail(
2002 "debugging {q} failed with code {d}",
2003 .{ emit_path, code },
2004 );
2005 try update.checkMatches("stdout", expected, result.stdout);
2006 },
2007 },
2008 .signal => |sig| return update.fail(
2009 "generated executable {q} terminated with signal {t}",
2010 .{ emit_path, sig },
2011 ),
2012 .stopped => |sig| return update.fail(
2013 "generated executable {q} stopped with signal {t}",
2014 .{ emit_path, sig },
2015 ),
2016 .unknown => return update.fail(
2017 "generated executable {q} terminated unexpectedly",
2018 .{emit_path},
2019 ),
2020 }
2021 if (!use_executor and result.stderr.len > 0) {
2022 std.log.err("#update {q} generated executable {q} had unexpected stderr:\n{s}", .{
2023 update.name, emit_path, result.stderr,
2024 });
2025 }
2026 }
2027
2028 fn compileC(parent_comp: *Compiler, src_dir: std.Io.Dir, c_path: []const u8) TestError![]const u8 {
2029 const runner = parent_comp.runner;
2030 const arena = runner.arena.allocator();
2031 var compiler: ?Compiler = null;
2032 var args_r: std.Io.Reader = .fixed(c_path);
2033 parent_comp.runner.spawnCompiler(&compiler, src_dir, parent_comp.output_mode, &args_r, .{
2034 .triple = parent_comp.target.triple,
2035 .resolved = resolved: {
2036 var resolved = parent_comp.target.resolved;
2037 resolved.ofmt = .default(resolved.os.tag, resolved.cpu.arch);
2038 break :resolved resolved;
2039 },
2040 .cache_mode = .whole,
2041 .backend = null,
2042 .linker = .lld,
2043 .pic = parent_comp.target.pic,
2044 }) catch |err| switch (err) {
2045 error.Canceled, error.OutOfMemory, error.AlreadyReported, error.SkipTest => |e| return e,
2046 error.ReadFailed => unreachable, // .fixed
2047 };
2048 const comp = &compiler.?;
2049 defer comp.deinit();
2050 comp.client.serveBodylessMessage(.update) catch |err| switch (err) {
2051 error.WriteFailed => switch (comp.fw.err.?) {
2052 error.Canceled => |e| return e,
2053 else => |e| return runner.fail("unable to send message: {t}", .{e}),
2054 },
2055 };
2056 comp.state = .update;
2057 var emit_path: ?[]const u8 = null;
2058 while (try comp.receiveMessage()) |message| switch (message.tag) {
2059 else => {}, // Ignore other messages,
2060 .config => {
2061 var body_r: std.Io.Reader = .fixed(message.body);
2062 comp.config =
2063 body_r.takeStruct(std.zig.Server.Message.Config, .little) catch unreachable;
2064 },
2065 .emit_digest => {
2066 var body_r: std.Io.Reader = .fixed(message.body);
2067 _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable;
2068 const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch unreachable;
2069
2070 const emit_dir = ".zig-cache" ++ std.Io.Dir.path.sep_str ++
2071 "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*);
2072 emit_path = try std.Io.Dir.path.join(arena, &.{ emit_dir, comp.emit_name.? });
2073 },
2074 .error_bundle => {
2075 const eb = std.zig.Server.allocErrorBundle(
2076 runner.arena.allocator(),
2077 message.body,
2078 ) catch |err| switch (err) {
2079 error.OutOfMemory => |e| return e,
2080 error.EndOfStream => return runner.fail("error bundle truncated", .{}),
2081 };
2082 if (eb.errorMessageCount() > 0) {
2083 try runner.eb_wip.addBundleAsRoots(eb);
2084 return runner.fail("unexpected compile errors", .{});
2085 }
2086 comp.state = .idle;
2087 break;
2088 },
2089 };
2090 try comp.exit();
2091 return emit_path.?;
2092 }
2093
2094 fn exit(comp: *Compiler) FailError!void {
2095 const runner = comp.runner;
2096 comp.client.serveBodylessMessage(.exit) catch |err| switch (err) {
2097 error.WriteFailed => switch (comp.fw.err.?) {
2098 error.Canceled => |e| return e,
2099 error.BrokenPipe => {},
2100 else => |e| return comp.runner.fail("unable to send exit: {t}", .{e}),
2101 },
2102 };
2103 comp.child.stdin.?.close(runner.io);
2104 comp.child.stdin = null;
2105 while (try comp.receiveMessage()) |_| {}
2106 if (comp.client.in.bufferedLen() > 0) return runner.fail("unable to receive messages", .{});
2107 const term = comp.child.wait(runner.io) catch |err|
2108 return runner.fail("child process failed: {t}", .{err});
2109 switch (term) {
2110 .exited => |code| if (code != 0)
2111 return runner.fail("compiler failed with code {d}", .{code}),
2112 .signal => |sig| return runner.fail("compiler terminated with signal {t}", .{sig}),
2113 .stopped => |sig| return runner.fail("compiler stopped unexpectedly with signal {t}", .{
2114 sig,
2115 }),
2116 .unknown => return runner.fail("compiler terminated unexpectedly", .{}),
2117 }
2118 }
2119};
2120
2121const ClientArgs = struct {
2122 test_file: std.Io.File,
2123 test_kind: std.Io.File.Kind,
2124 zig_exe: std.Io.File,
2125 lib_dir: std.Io.Dir,
2126 src_dir: std.Io.Dir,
2127 targets: std.ArrayList([]const u8),
2128
2129 libc_runtimes_dir: ?std.Io.Dir,
2130 enable_darling: bool,
2131 enable_qemu: bool,
2132 enable_rosetta: bool,
2133 enable_wine: bool,
2134 enable_wasmtime: bool,
2135
2136 gdb_exe: ?std.Io.File,
2137 lldb_exe: ?std.Io.File,
2138
2139 quiet: bool,
2140 keep_src: bool,
2141};
2142pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelable)!u8 {
2143 const fatal = struct {
2144 fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
2145 std.log.err(fmt, args);
2146 std.process.exit(1);
2147 }
2148 }.fatal;
2149 const arena = init.arena.allocator();
2150
2151 var test_path_arg: ?[]const u8 = null;
2152 var zig_path_arg: ?[]const u8 = null;
2153 var lib_path_arg: ?[]const u8 = null;
2154 var libc_runtimes_path_arg: ?[]const u8 = null;
2155 var gdb_path_arg: ?[]const u8 = null;
2156 var lldb_path_arg: ?[]const u8 = null;
2157 var args: ClientArgs = .{
2158 .test_file = undefined,
2159 .test_kind = undefined,
2160 .zig_exe = undefined,
2161 .lib_dir = undefined,
2162 .src_dir = undefined,
2163 .targets = .empty,
2164
2165 .libc_runtimes_dir = null,
2166 .enable_darling = false,
2167 .enable_qemu = false,
2168 .enable_rosetta = false,
2169 .enable_wine = false,
2170 .enable_wasmtime = false,
2171
2172 .gdb_exe = null,
2173 .lldb_exe = null,
2174
2175 .quiet = false,
2176 .keep_src = false,
2177 };
2178 defer args.targets.deinit(init.gpa);
2179
2180 var arg_it = try init.minimal.args.iterateAllocator(arena);
2181 const self = arg_it.next().?;
2182 while (arg_it.next()) |arg| {
2183 if (std.mem.eql(u8, arg, "--listen=-")) {
2184 var stdin_buffer: [512]u8 = undefined;
2185 var stdin: std.Io.File.Reader = .initStreaming(.stdin(), init.io, &stdin_buffer);
2186 var stdout_buffer: [512]u8 = undefined;
2187 var stdout: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer);
2188 var runner: Runner = .{
2189 .gpa = init.gpa,
2190 .arena = init.arena,
2191 .environ_map = init.minimal.environ.createMap(init.gpa) catch |err| switch (err) {
2192 error.OutOfMemory => |e| return e,
2193 else => |e| fatal("unable to get environment: {t}", .{e}),
2194 },
2195 .io = init.io,
2196 .prog_node = std.Progress.start(init.io, .{}),
2197 .args = .{
2198 .@"test" = null,
2199 .manifest_file = null,
2200 .zig_exe = null,
2201 .lib_dir = null,
2202 .src_dir = null,
2203 .targets = .empty,
2204 .target_bytes = .empty,
2205
2206 .libc_runtimes_dir = null,
2207 .enable_darling = false,
2208 .enable_qemu = false,
2209 .enable_rosetta = false,
2210 .enable_wasmtime = false,
2211 .enable_wine = false,
2212
2213 .gdb_exe = null,
2214 .lldb_exe = null,
2215
2216 .quiet = false,
2217 },
2218 .host = std.zig.system.resolveTargetQuery(init.io, .{}) catch |err| switch (err) {
2219 error.Canceled => |e| return e,
2220 else => |e| fatal("unable to resolve host target: {t}", .{e}),
2221 },
2222 .server = .{
2223 .in = &stdin.interface,
2224 .out = &stdout.interface,
2225 },
2226 .eb_wip = try .init(init.gpa),
2227 };
2228 defer runner.deinit();
2229 switch (runner.runServer()) {
2230 error.Canceled, error.OutOfMemory => |e| return e,
2231 error.ServerReadFailed => switch (stdin.err.?) {
2232 error.Canceled => |e| return e,
2233 else => |e| fatal("unable to receive message: {t}", .{e}),
2234 },
2235 error.ServerWriteFailed => switch (stdout.err.?) {
2236 error.Canceled => |e| return e,
2237 else => |e| fatal("unable to send message: {t}", .{e}),
2238 },
2239 error.ServerEndOfStream => fatal("no more messages", .{}),
2240 error.AlreadyReported => {
2241 var eb = try runner.eb_wip.toOwnedBundle("");
2242 defer eb.deinit(init.gpa);
2243 eb.renderToStderr(runner.io, .{}, .auto) catch |err| switch (err) {
2244 error.Canceled => |e| return e,
2245 else => {},
2246 };
2247 std.process.exit(1);
2248 },
2249 }
2250 } else if (std.mem.eql(u8, arg, "--zig")) {
2251 zig_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
2252 } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| {
2253 zig_path_arg = zig_path;
2254 } else if (std.mem.eql(u8, arg, "--lib")) {
2255 lib_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
2256 } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| {
2257 lib_path_arg = lib_path;
2258 } else if (std.mem.eql(u8, arg, "--target")) {
2259 try args.targets.append(
2260 init.gpa,
2261 try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after {q}", .{arg})),
2262 );
2263 } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| {
2264 try args.targets.append(init.gpa, try arena.dupe(u8, target));
2265 } else if (std.mem.eql(u8, arg, "--libc-runtimes")) {
2266 libc_runtimes_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
2267 } else if (std.mem.cutPrefix(u8, arg, "--libc-runtimes=")) |libc_runtimes_path| {
2268 libc_runtimes_path_arg = libc_runtimes_path;
2269 } else if (std.mem.eql(u8, arg, "-fdarling")) {
2270 args.enable_darling = true;
2271 } else if (std.mem.eql(u8, arg, "-fqemu")) {
2272 args.enable_qemu = true;
2273 } else if (std.mem.eql(u8, arg, "-frosetta")) {
2274 args.enable_rosetta = true;
2275 } else if (std.mem.eql(u8, arg, "-fwine")) {
2276 args.enable_wine = true;
2277 } else if (std.mem.eql(u8, arg, "-fwasmtime")) {
2278 args.enable_wasmtime = true;
2279 } else if (std.mem.eql(u8, arg, "--gdb")) {
2280 gdb_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
2281 } else if (std.mem.cutPrefix(u8, arg, "--gdb=")) |gdb_path| {
2282 gdb_path_arg = gdb_path;
2283 } else if (std.mem.eql(u8, arg, "--lldb")) {
2284 lldb_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
2285 } else if (std.mem.cutPrefix(u8, arg, "--lldb=")) |lldb_path| {
2286 lldb_path_arg = lldb_path;
2287 } else if (std.mem.eql(u8, arg, "--quiet")) {
2288 args.quiet = true;
2289 } else if (std.mem.eql(u8, arg, "--keep-src")) {
2290 args.keep_src = true;
2291 } else {
2292 if (test_path_arg) |_| fatal("unexpected arg {q}", .{arg});
2293 test_path_arg = arg;
2294 }
2295 }
2296
2297 const test_path = test_path_arg orelse fatal("missing \"path/to/test\" arg", .{});
2298 const zig_path = zig_path_arg orelse fatal("missing \"--zig=path/to/zig\" arg", .{});
2299 const lib_path = lib_path_arg orelse fatal("missing \"--lib=path/to/lib\" arg", .{});
2300
2301 const cwd: std.Io.Dir = .cwd();
2302 args.test_file = cwd.openFile(init.io, test_path, .{
2303 .allow_directory = true,
2304 }) catch |err| switch (err) {
2305 error.Canceled => |e| return e,
2306 else => |e| fatal("unable to open {q}: {t}", .{ test_path, e }),
2307 };
2308 defer args.test_file.close(init.io);
2309 args.test_kind = (args.test_file.stat(init.io) catch |err| switch (err) {
2310 error.Canceled => |e| return e,
2311 else => |e| fatal("unable to stat {q}: {t}", .{ test_path, e }),
2312 }).kind;
2313 args.zig_exe = cwd.openFile(init.io, zig_path, .{}) catch |err| switch (err) {
2314 error.Canceled => |e| return e,
2315 else => |e| fatal("unable to open {q}: {t}", .{ zig_path, e }),
2316 };
2317 defer args.zig_exe.close(init.io);
2318 args.lib_dir = cwd.openDir(init.io, lib_path, .{}) catch |err| switch (err) {
2319 error.Canceled => |e| return e,
2320 else => |e| fatal("unable to open {q}: {t}", .{ lib_path, e }),
2321 };
2322 defer args.lib_dir.close(init.io);
2323 if (libc_runtimes_path_arg) |libc_runtimes_path| args.libc_runtimes_dir = cwd.openDir(
2324 init.io,
2325 libc_runtimes_path,
2326 .{},
2327 ) catch |err| switch (err) {
2328 error.Canceled => |e| return e,
2329 else => |e| fatal("unable to open {q}: {t}", .{ lib_path, e }),
2330 };
2331 defer if (args.libc_runtimes_dir) |libc_runtimes_dir| libc_runtimes_dir.close(init.io);
2332 if (gdb_path_arg) |gdb_path| args.gdb_exe = cwd.openFile(init.io, gdb_path, .{}) catch |err| switch (err) {
2333 error.Canceled => |e| return e,
2334 else => |e| fatal("unable to open {q}: {t}", .{ gdb_path, e }),
2335 };
2336 defer if (args.gdb_exe) |gdb_exe| gdb_exe.close(init.io);
2337 if (lldb_path_arg) |lldb_path| args.lldb_exe = cwd.openFile(init.io, lldb_path, .{}) catch |err| switch (err) {
2338 error.Canceled => |e| return e,
2339 else => |e| fatal("unable to open {q}: {t}", .{ lldb_path, e }),
2340 };
2341 defer if (args.lldb_exe) |lldb_exe| lldb_exe.close(init.io);
2342
2343 const src_dir_path = "src_" ++ std.fmt.hex(rand_int: {
2344 var rand_int: u64 = undefined;
2345 init.io.random(@ptrCast(&rand_int));
2346 break :rand_int rand_int;
2347 });
2348 args.src_dir = cwd.createDirPathOpen(init.io, src_dir_path, .{}) catch |err| switch (err) {
2349 error.Canceled => |e| return e,
2350 else => |e| fatal("unable to create {q}: {t}", .{ src_dir_path, e }),
2351 };
2352 defer {
2353 args.src_dir.close(init.io);
2354 if (!args.keep_src) cwd.deleteTree(init.io, src_dir_path) catch |err| {
2355 std.log.warn("unable to delete tree {q}: {t}", .{ src_dir_path, err });
2356 };
2357 }
2358
2359 var inherit_dirs: std.ArrayList(std.Io.Dir) = try .initCapacity(arena, 3);
2360 var inherit_files: std.ArrayList(std.Io.File) = try .initCapacity(arena, 4);
2361 inherit_files.appendSliceAssumeCapacity(&.{ args.test_file, args.zig_exe });
2362 inherit_dirs.appendSliceAssumeCapacity(&.{ args.lib_dir, args.src_dir });
2363 if (args.libc_runtimes_dir) |libc_runtimes_dir| inherit_dirs.appendAssumeCapacity(libc_runtimes_dir);
2364 if (args.gdb_exe) |gdb_exe| inherit_files.appendAssumeCapacity(gdb_exe);
2365 if (args.lldb_exe) |lldb_exe| inherit_files.appendAssumeCapacity(lldb_exe);
2366 var child = std.process.spawn(init.io, .{
2367 .argv = &.{ self, "--listen=-" },
2368 .stdin = .pipe,
2369 .stdout = .pipe,
2370 .inherit_dirs = inherit_dirs.items,
2371 .inherit_files = inherit_files.items,
2372 }) catch |err| switch (err) {
2373 error.Canceled, error.OutOfMemory => |e| return e,
2374 else => |e| fatal("unable to spawn runner: {t}", .{e}),
2375 };
2376
2377 var child_reader_buffer: [512]u8 = undefined;
2378 var child_reader = child.stdout.?.readerStreaming(init.io, &child_reader_buffer);
2379 var child_writer_buffer: [512]u8 = undefined;
2380 var child_writer = child.stdin.?.writerStreaming(init.io, &child_writer_buffer);
2381 var client: std.zig.Client = .{
2382 .in = &child_reader.interface,
2383 .out = &child_writer.interface,
2384 };
2385 const success = runClient(init.gpa, arena, init.io, &client, &args) catch |err| switch (err) {
2386 error.OutOfMemory => |e| return e,
2387 error.ReadFailed => switch (child_reader.err.?) {
2388 error.Canceled => |e| return e,
2389 else => |e| fatal("unable to receive message: {t}", .{e}),
2390 },
2391 error.WriteFailed => switch (child_writer.err.?) {
2392 error.Canceled => |e| return e,
2393 else => |e| fatal("unable to send message: {t}", .{e}),
2394 },
2395 error.EndOfStream => fatal("no more messages", .{}),
2396 };
2397 _ = child.wait(init.io) catch |err| switch (err) {
2398 error.Canceled => |e| return e,
2399 else => |e| fatal("unable to wait for runner: {t}", .{e}),
2400 };
2401 return if (success) 0 else 1;
2402}
2403fn runClient(
2404 gpa: std.mem.Allocator,
2405 arena: std.mem.Allocator,
2406 io: std.Io,
2407 client: *std.zig.Client,
2408 args: *const ClientArgs,
2409) (std.mem.Allocator.Error || std.Io.Reader.Error || std.Io.Writer.Error)!bool {
2410 var stdout_buffer: [512]u8 = undefined;
2411 var stdout_fw: std.Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buffer);
2412 const stdout = &stdout_fw.interface;
2413 var stderr_buffer: [512]u8 = undefined;
2414 var stderr_fw: std.Io.File.Writer = .initStreaming(.stderr(), io, &stderr_buffer);
2415 const stderr = &stderr_fw.interface;
2416
2417 var argv: std.ArrayList(u8) = .empty;
2418 defer argv.deinit(gpa);
2419 const Arg = std.zig.Client.Message.Arg;
2420
2421 try argv.append(gpa, @backingInt(@as(Arg, switch (args.test_kind) {
2422 else => unreachable,
2423 .file => .input_file,
2424 .directory => .input_dir,
2425 })));
2426 try argv.appendSlice(gpa, @ptrCast(&args.test_file.handle));
2427
2428 try argv.append(gpa, @backingInt(Arg.prefix));
2429 try argv.appendSlice(gpa, "--zig=");
2430 try argv.append(gpa, 0);
2431 try argv.append(gpa, @backingInt(Arg.input_file));
2432 try argv.appendSlice(gpa, @ptrCast(&args.zig_exe.handle));
2433
2434 try argv.append(gpa, @backingInt(Arg.prefix));
2435 try argv.appendSlice(gpa, "--lib=");
2436 try argv.append(gpa, 0);
2437 try argv.append(gpa, @backingInt(Arg.input_dir));
2438 try argv.appendSlice(gpa, @ptrCast(&args.lib_dir.handle));
2439
2440 try argv.append(gpa, @backingInt(Arg.prefix));
2441 try argv.appendSlice(gpa, "--src=");
2442 try argv.append(gpa, 0);
2443 try argv.append(gpa, @backingInt(Arg.output_dir));
2444 try argv.appendSlice(gpa, @ptrCast(&args.src_dir.handle));
2445
2446 for (args.targets.items) |target| {
2447 try argv.append(gpa, @backingInt(Arg.string));
2448 try argv.appendSlice(gpa, "--target");
2449 try argv.append(gpa, 0);
2450
2451 try argv.append(gpa, @backingInt(Arg.string));
2452 try argv.appendSlice(gpa, target);
2453 try argv.append(gpa, 0);
2454 }
2455
2456 if (args.libc_runtimes_dir) |libc_runtimes_dir| {
2457 try argv.append(gpa, @backingInt(Arg.prefix));
2458 try argv.appendSlice(gpa, "--libc-runtimes=");
2459 try argv.append(gpa, 0);
2460 try argv.append(gpa, @backingInt(Arg.input_dir));
2461 try argv.appendSlice(gpa, @ptrCast(&libc_runtimes_dir.handle));
2462 }
2463 if (args.enable_darling) {
2464 try argv.append(gpa, @backingInt(Arg.string));
2465 try argv.appendSlice(gpa, "-fdarling");
2466 try argv.append(gpa, 0);
2467 }
2468 if (args.enable_qemu) {
2469 try argv.append(gpa, @backingInt(Arg.string));
2470 try argv.appendSlice(gpa, "-fqemu");
2471 try argv.append(gpa, 0);
2472 }
2473 if (args.enable_rosetta) {
2474 try argv.append(gpa, @backingInt(Arg.string));
2475 try argv.appendSlice(gpa, "-frosetta");
2476 try argv.append(gpa, 0);
2477 }
2478 if (args.enable_wine) {
2479 try argv.append(gpa, @backingInt(Arg.string));
2480 try argv.appendSlice(gpa, "-fwine");
2481 try argv.append(gpa, 0);
2482 }
2483 if (args.enable_wasmtime) {
2484 try argv.append(gpa, @backingInt(Arg.string));
2485 try argv.appendSlice(gpa, "-fwasmtime");
2486 try argv.append(gpa, 0);
2487 }
2488
2489 if (args.gdb_exe) |gdb_exe| {
2490 try argv.append(gpa, @backingInt(Arg.prefix));
2491 try argv.appendSlice(gpa, "--gdb=");
2492 try argv.append(gpa, 0);
2493 try argv.append(gpa, @backingInt(Arg.input_file));
2494 try argv.appendSlice(gpa, @ptrCast(&gdb_exe.handle));
2495 }
2496 if (args.lldb_exe) |lldb_exe| {
2497 try argv.append(gpa, @backingInt(Arg.prefix));
2498 try argv.appendSlice(gpa, "--lldb=");
2499 try argv.append(gpa, 0);
2500 try argv.append(gpa, @backingInt(Arg.input_file));
2501 try argv.appendSlice(gpa, @ptrCast(&lldb_exe.handle));
2502 }
2503
2504 if (args.quiet) {
2505 try argv.append(gpa, @backingInt(Arg.string));
2506 try argv.appendSlice(gpa, "--quiet");
2507 try argv.append(gpa, 0);
2508 }
2509
2510 try client.serveMessageHeader(.{
2511 .tag = .args,
2512 .bytes_len = @intCast(argv.items.len),
2513 });
2514 try client.out.writeAll(argv.items);
2515
2516 var success = true;
2517 var metadata: struct {
2518 index: u32,
2519 len: u32,
2520 names: []u32,
2521 expected_panic_msgs: []u32,
2522 string_bytes: []u8,
2523
2524 fn name(m: *@This()) []const u8 {
2525 return std.mem.sliceTo(m.string_bytes[m.names[m.index - 1]..], 0);
2526 }
2527
2528 fn expectedPanicMsg(m: *@This()) ?[:0]const u8 {
2529 return switch (m.expected_panic_msgs[m.index - 1]) {
2530 0 => null,
2531 else => |offset| m.string_bytes[offset..std.mem.findScalarPos(
2532 u8,
2533 m.string_bytes,
2534 offset,
2535 0,
2536 ).? :0],
2537 };
2538 }
2539
2540 fn next(m: *@This()) ?u32 {
2541 if (m.len - m.index == 0) return null;
2542 defer m.index += 1;
2543 return m.index;
2544 }
2545 } = .{ .index = 0, .len = 0, .names = &.{}, .expected_panic_msgs = &.{}, .string_bytes = &.{} };
2546 try client.serveBodylessMessage(.query_test_metadata);
2547 while (true) {
2548 const hdr = try client.receiveMessage();
2549 switch (hdr.tag) {
2550 else => try client.in.discardAll(hdr.bytes_len),
2551 .zig_version => {
2552 const actual_version = try client.in.take(hdr.bytes_len);
2553 if (!std.mem.eql(u8, builtin.zig_version_string, actual_version)) {
2554 try stderr.print(
2555 "error: zig version mismatch compiler test runner vs compiler: {q} vs {q}\n",
2556 .{ builtin.zig_version_string, actual_version },
2557 );
2558 try stderr.flush();
2559 std.process.exit(1);
2560 }
2561 },
2562 .error_bundle => {
2563 const body = try arena.alloc(u8, hdr.bytes_len);
2564 try client.in.readSliceAll(body);
2565 const eb = std.zig.Server.allocErrorBundle(arena, body) catch |err| switch (err) {
2566 error.OutOfMemory => |e| return e,
2567 error.EndOfStream => {
2568 try stderr.writeAll("error bundle truncated\n");
2569 try stderr.flush();
2570 std.process.exit(1);
2571 },
2572 };
2573 success = false;
2574 eb.renderToStderr(io, .{}, .auto) catch {};
2575 },
2576 .test_metadata => {
2577 const tm_hdr =
2578 try client.in.takeStruct(std.zig.Server.Message.TestMetadata, .little);
2579 metadata = .{
2580 .index = 0,
2581 .len = tm_hdr.tests_len,
2582 .names = try arena.alloc(u32, tm_hdr.tests_len),
2583 .expected_panic_msgs = try arena.alloc(u32, tm_hdr.tests_len),
2584 .string_bytes = try arena.alloc(u8, tm_hdr.string_bytes_len),
2585 };
2586 try client.in.readSliceEndian(u32, metadata.names, .little);
2587 try client.in.readSliceEndian(u32, metadata.expected_panic_msgs, .little);
2588 try client.in.readSliceAll(metadata.string_bytes);
2589 try client.serveRunTest(metadata.next() orelse break);
2590 },
2591 .test_results => {
2592 const tr = try client.in.takeStruct(std.zig.Server.Message.TestResults, .little);
2593 switch (tr.flags.status) {
2594 .pass, .skip => {},
2595 .fail => success = false,
2596 }
2597 try stdout.print("[{t}] {s}\n", .{ tr.flags.status, metadata.name() });
2598 try stdout.flush();
2599 try client.serveRunTest(metadata.next() orelse break);
2600 },
2601 }
2602 }
2603 try client.serveBodylessMessage(.exit);
2604 return success;
2605}