authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-09-22 13:57:40-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-09-26 19:08:50-04:00
logb6f68ca4ba477213ea89bc0ac6c9422f78116692
treea3120517a5fe907b0c011cc8d49333d4566758e1
parentec261c5e8d569a7bf90a51638d41de6053f98e1a

Runner: implement cbe backend


13 files changed, 1269 insertions(+), 1004 deletions(-)

build.zig+1-1
......@@ -791,7 +791,7 @@ pub fn build(b: *std.Build) !void {
791791 const runner = b.addExecutable(.{
792792 .name = "runner",
793793 .root_module = b.createModule(.{
794 .root_source_file = b.path("test/src/runner.zig"),
794 .root_source_file = b.path("test/src/Runner.zig"),
795795 .target = b.graph.host,
796796 }),
797797 });
lib/compiler/Maker/Step.zig+12-6
......@@ -624,6 +624,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi
624624 else => |e| return e,
625625 };
626626 const body = client.in.take(header.bytes_len) catch unreachable;
627 var body_r: std.Io.Reader = .fixed(body);
627628
628629 switch (header.tag) {
629630 .zig_version => {
......@@ -635,16 +636,22 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi
635636 );
636637 }
637638 },
639 .config => switch (s.extended) {
640 else => unreachable,
641 .compile => |*compile| compile.config =
642 body_r.takeStruct(std.zig.Server.Message.Config, .little) catch unreachable,
643 .translate_c => {},
644 },
638645 .error_bundle => {
639646 s.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body);
640647 // This message indicates the end of the update.
641648 if (watch) break;
642649 },
643650 .emit_digest => {
644 const EmitDigest = std.zig.Server.Message.EmitDigest;
645 const emit_digest: *align(1) const EmitDigest = @ptrCast(body);
651 const emit_digest = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable;
652 const digest = body_r.takeArray(Cache.bin_digest_len) catch unreachable;
646653 s.result_cached = emit_digest.flags.cache_hit;
647 result = .{ .bin = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len].* };
654 result = .{ .bin = digest.* };
648655 },
649656 .file_system_inputs => {
650657 clearWatchInputs(s, maker);
......@@ -704,8 +711,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi
704711 }
705712 },
706713 .time_report => if (maker.web_server) |ws| {
707 const TimeReport = std.zig.Server.Message.TimeReport;
708 const tr: *align(1) const TimeReport = @ptrCast(body[0..@sizeOf(TimeReport)]);
714 const tr = body_r.takeStruct(std.zig.Server.Message.TimeReport, .little) catch unreachable;
709715 ws.updateTimeReportCompile(.{
710716 .compile_step = step_index,
711717 .use_llvm = tr.flags.use_llvm,
......@@ -714,7 +720,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi
714720 .llvm_pass_timings_len = tr.llvm_pass_timings_len,
715721 .files_len = tr.files_len,
716722 .decls_len = tr.decls_len,
717 .trailing = body[@sizeOf(TimeReport)..],
723 .trailing = body_r.buffered(),
718724 });
719725 },
720726 else => {}, // ignore other messages
lib/compiler/Maker/Step/Compile.zig+5-6
......@@ -21,7 +21,7 @@ zig_process: ?*Step.ZigProcess = null,
2121/// Populated by InstallArtifact.
2222installed_path: ?Path = null,
2323/// Populated by `make`, used by `Run`.
24is_linking_libc: bool = false,
24config: ?std.zig.Server.Message.Config = null,
2525
2626pub fn make(
2727 compile: *Compile,
......@@ -29,6 +29,7 @@ pub fn make(
2929 maker: *Maker,
3030 progress_node: std.Progress.Node,
3131) Step.ExtendedMakeError!void {
32 _ = compile; // only accessed by `Step.evalZigProcess`.
3233 const graph = maker.graph;
3334 const gpa = maker.gpa;
3435 const conf = &maker.scanned_config.configuration;
......@@ -42,7 +43,7 @@ pub fn make(
4243 var argv: std.ArrayList([]const u8) = .empty;
4344 defer argv.deinit(gpa);
4445
45 try lowerZigArgs(arena, compile, compile_index, maker, progress_node, &argv, false);
46 try lowerZigArgs(arena, compile_index, maker, progress_node, &argv, false);
4647
4748 const incremental = conf_comp.flags4.incremental.toBool() orelse graph.incremental == true;
4849
......@@ -158,7 +159,6 @@ const ModuleListContext = struct {
158159
159160fn lowerZigArgs(
160161 arena: Allocator,
161 compile: *Compile,
162162 compile_index: Configuration.Step.Index,
163163 maker: *Maker,
164164 progress_node: std.Progress.Node,
......@@ -575,8 +575,6 @@ fn lowerZigArgs(
575575 try zig_args.ensureUnusedCapacity(gpa, 2);
576576 if (is_linking_libcpp) zig_args.appendAssumeCapacity("-lc++");
577577 if (is_linking_libc) zig_args.appendAssumeCapacity("-lc");
578
579 compile.is_linking_libc = is_linking_libc;
580578 }
581579
582580 if (conf_comp.win32_manifest.value) |manifest_file| {
......@@ -944,6 +942,7 @@ pub fn rebuildInFuzzMode(
944942 compile_index: Configuration.Step.Index,
945943 progress_node: std.Progress.Node,
946944) !Step.OptCacheDigest {
945 _ = compile;
947946 const gpa = maker.gpa;
948947 const step = maker.stepByIndex(compile_index);
949948
......@@ -962,7 +961,7 @@ pub fn rebuildInFuzzMode(
962961 var argv: std.ArrayList([]const u8) = .empty;
963962 defer argv.deinit(gpa);
964963
965 try lowerZigArgs(arena, compile, compile_index, maker, progress_node, &argv, true);
964 try lowerZigArgs(arena, compile_index, maker, progress_node, &argv, true);
966965 return Step.evalZigProcess(compile_index, maker, argv.items, progress_node, false);
967966}
968967
lib/compiler/Maker/Step/Run.zig+4-3
......@@ -2212,14 +2212,14 @@ fn runCommand(
22122212 const root_module = producer.root_module.get(conf);
22132213 const root_module_target = root_module.resolved_target.get(conf).?.result.get(conf);
22142214 const root_target = root_module_target.unwrapTarget(conf);
2215 const link_libc = maker.stepByIndex(producer_index).extended.compile.is_linking_libc;
2215 const config = maker.stepByIndex(producer_index).extended.compile.config.?;
22162216
22172217 const host: std.Target = std.zig.system.resolveTargetQuery(io, .{}) catch |he| switch (he) {
22182218 error.Canceled => |e| return e,
22192219 else => builtin.target,
22202220 };
22212221
2222 const need_cross_libc = link_libc and root_target.os.tag == .linux and
2222 const need_cross_libc = config.flags.link_libc and root_target.os.tag == .linux and
22232223 switch (producer.flags2.linkage) {
22242224 .static => false,
22252225 .dynamic => true,
......@@ -2229,7 +2229,8 @@ fn runCommand(
22292229 .host_cpu_arch = host.cpu.arch,
22302230 .host_os_tag = host.os.tag,
22312231 .qemu_fixes_dl = need_cross_libc and graph.libc_runtimes_dir != null,
2232 .link_libc = link_libc,
2232 .link_mode = config.flags.link_mode,
2233 .link_libc = config.flags.link_libc,
22332234 })) {
22342235 .native, .rosetta => {
22352236 if (allow_skip) return error.MakeSkipped;
lib/std/lang.zig+1-1
......@@ -971,7 +971,7 @@ pub const Signedness = enum(u1) {
971971
972972/// This data structure is used by the Zig language code generation and
973973/// therefore must be kept in sync with the compiler implementation.
974pub const OutputMode = enum {
974pub const OutputMode = enum(u2) {
975975 Exe,
976976 Lib,
977977 Obj,
lib/std/zig/Server.zig+28-2
......@@ -78,6 +78,8 @@ pub const Message = struct {
7878 /// - root_dir: InputDir,
7979 /// - sub_path: [:0]u8,
8080 discovered_inputs,
81 /// Body is a Config.
82 config,
8183
8284 /// The first message sent by the server over the build system protocol.
8385 /// Body is a `Handshake`.
......@@ -116,7 +118,7 @@ pub const Message = struct {
116118
117119 pub const Flags = packed struct(u32) {
118120 file_system_watch_supported: bool,
119 _: u31 = 0,
121 unused: u31 = 0,
120122 };
121123 };
122124
......@@ -202,7 +204,7 @@ pub const Message = struct {
202204 flags: Flags,
203205 pub const Flags = packed struct(u32) {
204206 use_llvm: bool,
205 _: u31 = 0,
207 unused: u31 = 0,
206208 };
207209 };
208210
......@@ -224,6 +226,21 @@ pub const Message = struct {
224226 /// Numbered in the order that `.input_dir`s appear in the `.args` message.
225227 _,
226228 };
229
230 /// Properties of the output binary target that are decided by the compiler.
231 pub const Config = extern struct {
232 flags: Flags,
233
234 pub const Flags = packed struct(u8) {
235 output_mode: std.lang.OutputMode,
236 link_mode: std.lang.LinkMode,
237 link_libc: bool,
238 link_libcpp: bool,
239 link_libunwind: bool,
240 pie: bool,
241 unused: u1 = 0,
242 };
243 };
227244};
228245
229246pub fn receiveMessage(s: *Server) !InMessage.Header {
......@@ -377,3 +394,12 @@ pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void {
377394 try s.out.writeAll(test_metadata.string_bytes);
378395 try s.out.flush();
379396}
397
398pub fn serveConfig(s: *Server, config: OutMessage.Config) !void {
399 try s.serveMessageHeader(.{
400 .tag = .config,
401 .bytes_len = @intCast(@sizeOf(OutMessage.Config)),
402 });
403 try s.out.writeStruct(config, .little);
404 try s.out.flush();
405}
lib/std/zig/system.zig+22-34
......@@ -33,13 +33,9 @@ pub const Executor = union(enum) {
3333pub const GetExternalExecutorOptions = struct {
3434 host_cpu_arch: std.Target.Cpu.Arch,
3535 host_os_tag: std.Target.Os.Tag,
36 allow_darling: bool = true,
37 allow_qemu: bool = true,
38 allow_rosetta: bool = true,
39 allow_wasmtime: bool = true,
40 allow_wine: bool = true,
4136 qemu_fixes_dl: bool = false,
42 link_libc: bool = false,
37 link_mode: std.lang.LinkMode,
38 link_libc: bool,
4339};
4440
4541/// Return whether or not the given host is capable of running executables of
......@@ -71,7 +67,7 @@ pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExt
7167 var bad_result: Executor = .bad_os_or_cpu;
7268
7369 if (os_match and cpu_ok) native: {
74 if (options.link_libc) {
70 if (options.link_libc and options.link_mode == .dynamic) {
7571 if (candidate.dynamic_linker.get()) |candidate_dl| {
7672 Io.Dir.cwd().access(io, candidate_dl, .{}) catch {
7773 bad_result = .{ .bad_dl = candidate_dl };
......@@ -84,9 +80,7 @@ pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExt
8480
8581 // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2
8682 // to emulate the foreign architecture.
87 if (options.allow_rosetta and os_match and
88 (host_os_tag == .maccatalyst or host_os_tag == .macos) and host_cpu_arch == .aarch64)
89 {
83 if (os_match and (host_os_tag == .maccatalyst or host_os_tag == .macos) and host_cpu_arch == .aarch64) {
9084 switch (candidate.cpu.arch) {
9185 .x86_64 => return .rosetta,
9286 else => return bad_result,
......@@ -94,7 +88,7 @@ pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExt
9488 }
9589
9690 // If the OS matches, we can use QEMU to emulate a foreign architecture.
97 if (options.allow_qemu and os_match and (!cpu_ok or options.qemu_fixes_dl)) {
91 if (os_match and (!cpu_ok or options.qemu_fixes_dl)) {
9892 return switch (candidate.cpu.arch) {
9993 inline .aarch64,
10094 .arm,
......@@ -167,38 +161,32 @@ pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExt
167161 };
168162 }
169163
170 if (options.allow_wasmtime and candidate.cpu.arch.isWasm()) {
164 if (candidate.cpu.arch.isWasm()) {
171165 return .{ .wasmtime = "wasmtime" };
172166 }
173167
174168 switch (candidate.os.tag) {
175169 .windows => {
176 if (options.allow_wine) {
177 const wine_supported = switch (candidate.cpu.arch) {
178 .thumb => switch (host_cpu_arch) {
179 .arm, .thumb, .aarch64 => true,
180 else => false,
181 },
182 .aarch64 => host_cpu_arch == .aarch64,
183 .x86 => host_cpu_arch.isX86(),
184 .x86_64 => host_cpu_arch == .x86_64,
170 const wine_supported = switch (candidate.cpu.arch) {
171 .thumb => switch (host_cpu_arch) {
172 .arm, .thumb, .aarch64 => true,
185173 else => false,
186 };
187 return if (wine_supported) .{ .wine = "wine" } else bad_result;
188 }
189 return bad_result;
174 },
175 .aarch64 => host_cpu_arch == .aarch64,
176 .x86 => host_cpu_arch.isX86(),
177 .x86_64 => host_cpu_arch == .x86_64,
178 else => false,
179 };
180 return if (wine_supported) .{ .wine = "wine" } else bad_result;
190181 },
191182 .driverkit, .macos => {
192 if (options.allow_darling) {
193 // This check can be loosened once darling adds a QEMU-based emulation
194 // layer for non-host architectures:
195 // https://github.com/darlinghq/darling/issues/863
196 if (candidate.cpu.arch != host_cpu_arch) {
197 return bad_result;
198 }
199 return .{ .darling = "darling" };
183 // This check can be loosened once darling adds a QEMU-based emulation
184 // layer for non-host architectures:
185 // https://github.com/darlinghq/darling/issues/863
186 if (candidate.cpu.arch != host_cpu_arch) {
187 return bad_result;
200188 }
201 return bad_result;
189 return .{ .darling = "darling" };
202190 },
203191 else => return bad_result,
204192 }
src/main.zig+40-28
......@@ -4000,11 +4000,9 @@ fn buildOutputType(
40004000 test_exec_args.items,
40014001 self_exe_path_or_argv0,
40024002 arg_mode,
4003 target,
40044003 &comp_destroyed,
40054004 all_args,
40064005 runtime_args_start,
4007 create_module.resolved_options.link_libc,
40084006 test_execve,
40094007 environ_map,
40104008 );
......@@ -4409,6 +4407,15 @@ fn serve(
44094407 var server: Server = .{ .in = in, .out = out };
44104408 try server.serveStringMessage(.zig_version, build_options.version);
44114409
4410 try server.serveConfig(.{ .flags = .{
4411 .output_mode = comp.config.output_mode,
4412 .link_mode = comp.config.link_mode,
4413 .link_libc = comp.config.link_libc,
4414 .link_libcpp = comp.config.link_libcpp,
4415 .link_libunwind = comp.config.link_libunwind,
4416 .pie = comp.config.pie,
4417 } });
4418
44124419 var child_pid: ?std.process.Child.Id = null;
44134420
44144421 const main_progress_node = std.Progress.start(io, .{});
......@@ -4481,8 +4488,6 @@ fn serve(
44814488 // test_exec_args,
44824489 // self_exe_path.?,
44834490 // arg_mode,
4484 // target,
4485 // true,
44864491 // &comp_destroyed,
44874492 // all_args,
44884493 // runtime_args_start,
......@@ -4633,11 +4638,9 @@ fn runOrTest(
46334638 test_exec_args: []const ?[]const u8,
46344639 self_exe_path: []const u8,
46354640 arg_mode: ArgMode,
4636 target: *const std.Target,
46374641 comp_destroyed: *bool,
46384642 all_args: []const []const u8,
46394643 runtime_args_start: ?usize,
4640 link_libc: bool,
46414644 test_execve: bool,
46424645 environ_map: *process.Environ.Map,
46434646) !void {
......@@ -4685,7 +4688,12 @@ fn runOrTest(
46854688 _ = try io.lockStderr(&.{}, .no_color);
46864689 const err = process.replace(io, .{ .argv = argv.items, .environ_map = environ_map });
46874690 io.unlockStderr();
4688 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);
4691 try warnAboutForeignBinaries(io, arena, .{
4692 .arg_mode = arg_mode,
4693 .target = comp.getTarget(),
4694 .link_mode = comp.config.link_mode,
4695 .link_libc = comp.config.link_libc,
4696 });
46894697 const cmd = try std.mem.join(arena, " ", argv.items);
46904698 fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd });
46914699 } else if (!process.can_spawn) {
......@@ -4715,7 +4723,12 @@ fn runOrTest(
47154723 });
47164724
47174725 const term = term_result catch |err| {
4718 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);
4726 try warnAboutForeignBinaries(io, arena, .{
4727 .arg_mode = arg_mode,
4728 .target = comp.getTarget(),
4729 .link_mode = comp.config.link_mode,
4730 .link_libc = comp.config.link_libc,
4731 });
47194732 const cmd = try std.mem.join(arena, " ", argv.items);
47204733 fatal("the following command failed with {t}:\n{s}", .{ err, cmd });
47214734 };
......@@ -5866,33 +5879,32 @@ fn prefixedIntArg(arg: []const u8, prefix: []const u8) ?u64 {
58665879 return std.fmt.parseUnsigned(u64, number, 0) catch |err| fatal("unable to parse {q}: {t}", .{ arg, err });
58675880}
58685881
5869fn warnAboutForeignBinaries(
5870 io: Io,
5871 arena: Allocator,
5882fn warnAboutForeignBinaries(io: Io, arena: Allocator, opts: struct {
58725883 arg_mode: ArgMode,
58735884 target: *const std.Target,
5885 link_mode: std.lang.LinkMode,
58745886 link_libc: bool,
5875) !void {
5876 const host_query: std.Target.Query = .{};
5877 const host_target = std.zig.resolveTargetQueryOrFatal(io, host_query);
5887}) !void {
5888 const host_target = std.zig.resolveTargetQueryOrFatal(io, .{});
58785889
5879 switch (std.zig.system.getExternalExecutor(io, target, .{
5890 switch (std.zig.system.getExternalExecutor(io, opts.target, .{
58805891 .host_cpu_arch = host_target.cpu.arch,
58815892 .host_os_tag = host_target.os.tag,
5882 .link_libc = link_libc,
5893 .link_mode = opts.link_mode,
5894 .link_libc = opts.link_libc,
58835895 })) {
58845896 .native => return,
58855897 .rosetta => {
58865898 const host_name = try host_target.zigTriple(arena);
5887 const foreign_name = try target.zigTriple(arena);
5899 const foreign_name = try opts.target.zigTriple(arena);
58885900 warn("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}). Consider installing Rosetta.", .{
58895901 host_name, foreign_name,
58905902 });
58915903 },
58925904 .qemu => |qemu| {
58935905 const host_name = try host_target.zigTriple(arena);
5894 const foreign_name = try target.zigTriple(arena);
5895 switch (arg_mode) {
5906 const foreign_name = try opts.target.zigTriple(arena);
5907 switch (opts.arg_mode) {
58965908 .zig_test => warn(
58975909 "the host system ({s}) does not appear to be capable of executing binaries " ++
58985910 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
......@@ -5908,8 +5920,8 @@ fn warnAboutForeignBinaries(
59085920 },
59095921 .wine => |wine| {
59105922 const host_name = try host_target.zigTriple(arena);
5911 const foreign_name = try target.zigTriple(arena);
5912 switch (arg_mode) {
5923 const foreign_name = try opts.target.zigTriple(arena);
5924 switch (opts.arg_mode) {
59135925 .zig_test => warn(
59145926 "the host system ({s}) does not appear to be capable of executing binaries " ++
59155927 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
......@@ -5925,8 +5937,8 @@ fn warnAboutForeignBinaries(
59255937 },
59265938 .wasmtime => |wasmtime| {
59275939 const host_name = try host_target.zigTriple(arena);
5928 const foreign_name = try target.zigTriple(arena);
5929 switch (arg_mode) {
5940 const foreign_name = try opts.target.zigTriple(arena);
5941 switch (opts.arg_mode) {
59305942 .zig_test => warn(
59315943 "the host system ({s}) does not appear to be capable of executing binaries " ++
59325944 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
......@@ -5942,8 +5954,8 @@ fn warnAboutForeignBinaries(
59425954 },
59435955 .darling => |darling| {
59445956 const host_name = try host_target.zigTriple(arena);
5945 const foreign_name = try target.zigTriple(arena);
5946 switch (arg_mode) {
5957 const foreign_name = try opts.target.zigTriple(arena);
5958 switch (opts.arg_mode) {
59475959 .zig_test => warn(
59485960 "the host system ({s}) does not appear to be capable of executing binaries " ++
59495961 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
......@@ -5959,7 +5971,7 @@ fn warnAboutForeignBinaries(
59595971 },
59605972 .bad_dl => |foreign_dl| {
59615973 const host_dl = host_target.dynamic_linker.get() orelse "(none)";
5962 const tip_suffix = switch (arg_mode) {
5974 const tip_suffix = switch (opts.arg_mode) {
59635975 .zig_test => ", '--test-no-exec', or '--test-cmd'",
59645976 else => "",
59655977 };
......@@ -5969,8 +5981,8 @@ fn warnAboutForeignBinaries(
59695981 },
59705982 .bad_os_or_cpu => {
59715983 const host_name = try host_target.zigTriple(arena);
5972 const foreign_name = try target.zigTriple(arena);
5973 const tip_suffix = switch (arg_mode) {
5984 const foreign_name = try opts.target.zigTriple(arena);
5985 const tip_suffix = switch (opts.arg_mode) {
59745986 .zig_test => ". Consider using '--test-no-exec' or '--test-cmd'",
59755987 else => "",
59765988 };
test/src/Cases.zig+1
......@@ -626,6 +626,7 @@ pub fn lowerToBuildSteps(
626626 if (getExternalExecutor(io, &case.target.result, .{
627627 .host_cpu_arch = host.result.cpu.arch,
628628 .host_os_tag = host.result.os.tag,
629 .link_mode = .dynamic, // TODO: this emulates old behavior until this file is deleted
629630 .link_libc = true,
630631 }) != .native) {
631632 // We wouldn't be able to run the compiled C code.
test/src/Runner.zig created+1147
......@@ -0,0 +1,1147 @@
1const std = @import("std");
2
3pub fn main(init: std.process.Init) !void {
4 const arena = init.arena.allocator();
5
6 var test_path_arg: ?[]const u8 = null;
7 var zig_path_arg: ?[]const u8 = null;
8 var lib_path_arg: ?[]const u8 = null;
9 var target_args: std.ArrayList([]const u8) = .empty;
10 defer target_args.deinit(init.gpa);
11 var keep_src = false;
12 var enable_qemu = false;
13 var enable_wine = false;
14 var enable_wasmtime = false;
15 var enable_darling = false;
16
17 var arg_it = try init.minimal.args.iterateAllocator(arena);
18 const self = arg_it.next().?;
19 while (arg_it.next()) |arg| {
20 if (std.mem.eql(u8, arg, "--listen=-")) {
21 var stdin_buffer: [512]u8 = undefined;
22 var stdin: std.Io.File.Reader = .initStreaming(.stdin(), init.io, &stdin_buffer);
23 var stdout_buffer: [512]u8 = undefined;
24 var stdout: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer);
25 var runner: Runner = .{
26 .gpa = init.gpa,
27 .arena = init.arena,
28 .io = init.io,
29 .prog_node = std.Progress.start(init.io, .{}),
30 .args = .{
31 .@"test" = null,
32 .manifest_file = null,
33 .zig_exe = null,
34 .lib_dir = null,
35 .src_dir = null,
36 .targets = .empty,
37 .target_bytes = .empty,
38 .enable_qemu = false,
39 .enable_wine = false,
40 .enable_wasmtime = false,
41 .enable_darling = false,
42 },
43 .host = try std.zig.system.resolveTargetQuery(init.io, .{}),
44 .server = .{
45 .in = &stdin.interface,
46 .out = &stdout.interface,
47 },
48 };
49 defer runner.deinit();
50 return runner.protocol();
51 } else if (std.mem.eql(u8, arg, "--zig")) {
52 zig_path_arg = arg_it.next() orelse fatal("missing arg after '{s}'", .{arg});
53 } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| {
54 zig_path_arg = zig_path;
55 } else if (std.mem.eql(u8, arg, "--lib")) {
56 lib_path_arg = arg_it.next() orelse fatal("missing arg after '{s}'", .{arg});
57 } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| {
58 lib_path_arg = lib_path;
59 } else if (std.mem.eql(u8, arg, "--target")) {
60 try target_args.append(
61 init.gpa,
62 try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after '{s}'", .{arg})),
63 );
64 } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| {
65 try target_args.append(init.gpa, try arena.dupe(u8, target));
66 } else if (std.mem.eql(u8, arg, "--keep-src")) {
67 keep_src = true;
68 } else if (std.mem.eql(u8, arg, "-fqemu")) {
69 enable_qemu = true;
70 } else if (std.mem.eql(u8, arg, "-fwine")) {
71 enable_wine = true;
72 } else if (std.mem.eql(u8, arg, "-fwasmtime")) {
73 enable_wasmtime = true;
74 } else if (std.mem.eql(u8, arg, "-fdarling")) {
75 enable_darling = true;
76 } else {
77 if (test_path_arg) |_| fatal("unknown arg '{s}'", .{arg});
78 test_path_arg = arg;
79 }
80 }
81
82 const test_path = test_path_arg orelse fatal("missing 'path/to/test'", .{});
83 const zig_path = zig_path_arg orelse fatal("missing '--zig=path/to/zig'", .{});
84 const lib_path = lib_path_arg orelse fatal("missing '--lib=path/to/lib'", .{});
85
86 const cwd: std.Io.Dir = .cwd();
87 const test_file = try cwd.openFile(init.io, test_path, .{
88 .allow_directory = true,
89 });
90 const zig_exe = try cwd.openFile(init.io, zig_path, .{});
91 defer zig_exe.close(init.io);
92 const lib_dir = try cwd.openDir(init.io, lib_path, .{});
93 defer lib_dir.close(init.io);
94
95 const src_dir_path = "src_" ++ std.fmt.hex(rand_int: {
96 var rand_int: u64 = undefined;
97 init.io.random(@ptrCast(&rand_int));
98 break :rand_int rand_int;
99 });
100 var src_dir = try cwd.createDirPathOpen(init.io, src_dir_path, .{});
101 defer {
102 src_dir.close(init.io);
103 if (!keep_src) cwd.deleteTree(init.io, src_dir_path) catch |err| {
104 std.log.warn("failed to delete tree '{s}': {t}", .{ src_dir_path, err });
105 };
106 }
107
108 var stdout_buffer: [512]u8 = undefined;
109 var stdout_fw: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer);
110 const stdout = &stdout_fw.interface;
111
112 var child = try std.process.spawn(init.io, .{
113 .argv = &.{ self, "--listen=-" },
114 .stdin = .pipe,
115 .stdout = .pipe,
116 .inherit_dirs = &.{ lib_dir, src_dir },
117 .inherit_files = &.{ zig_exe, test_file },
118 });
119
120 var child_reader_buffer: [512]u8 = undefined;
121 var child_reader = child.stdout.?.readerStreaming(init.io, &child_reader_buffer);
122 var child_writer_buffer: [512]u8 = undefined;
123 var child_writer = child.stdin.?.writerStreaming(init.io, &child_writer_buffer);
124 var client: std.zig.Client = .{
125 .in = &child_reader.interface,
126 .out = &child_writer.interface,
127 };
128 {
129 var args: std.ArrayList(u8) = .empty;
130 defer args.deinit(init.gpa);
131 const Arg = std.zig.Client.Message.Arg;
132
133 try args.append(init.gpa, @backingInt(@as(Arg, switch ((try test_file.stat(init.io)).kind) {
134 else => unreachable,
135 .file => .input_file,
136 .directory => .input_dir,
137 })));
138 try args.appendSlice(init.gpa, @ptrCast(&test_file.handle));
139
140 try args.append(init.gpa, @backingInt(Arg.prefix));
141 try args.appendSlice(init.gpa, "--zig=");
142 try args.append(init.gpa, 0);
143
144 try args.append(init.gpa, @backingInt(Arg.input_file));
145 try args.appendSlice(init.gpa, @ptrCast(&zig_exe.handle));
146
147 try args.append(init.gpa, @backingInt(Arg.prefix));
148 try args.appendSlice(init.gpa, "--lib=");
149 try args.append(init.gpa, 0);
150
151 try args.append(init.gpa, @backingInt(Arg.input_dir));
152 try args.appendSlice(init.gpa, @ptrCast(&lib_dir.handle));
153
154 try args.append(init.gpa, @backingInt(Arg.prefix));
155 try args.appendSlice(init.gpa, "--src=");
156 try args.append(init.gpa, 0);
157
158 try args.append(init.gpa, @backingInt(Arg.output_dir));
159 try args.appendSlice(init.gpa, @ptrCast(&src_dir.handle));
160
161 for (target_args.items) |target_arg| {
162 try args.append(init.gpa, @backingInt(Arg.string));
163 try args.appendSlice(init.gpa, "--target");
164 try args.append(init.gpa, 0);
165
166 try args.append(init.gpa, @backingInt(Arg.string));
167 try args.appendSlice(init.gpa, target_arg);
168 try args.append(init.gpa, 0);
169 }
170
171 if (enable_qemu) {
172 try args.append(init.gpa, @backingInt(Arg.string));
173 try args.appendSlice(init.gpa, "-fqemu");
174 try args.append(init.gpa, 0);
175 }
176 if (enable_wine) {
177 try args.append(init.gpa, @backingInt(Arg.string));
178 try args.appendSlice(init.gpa, "-fwine");
179 try args.append(init.gpa, 0);
180 }
181 if (enable_wasmtime) {
182 try args.append(init.gpa, @backingInt(Arg.string));
183 try args.appendSlice(init.gpa, "-fwasmtime");
184 try args.append(init.gpa, 0);
185 }
186 if (enable_darling) {
187 try args.append(init.gpa, @backingInt(Arg.string));
188 try args.appendSlice(init.gpa, "-fdarling");
189 try args.append(init.gpa, 0);
190 }
191
192 try client.serveMessageHeader(.{
193 .tag = .args,
194 .bytes_len = @intCast(args.items.len),
195 });
196 try client.out.writeAll(args.items);
197
198 var metadata: struct {
199 index: u32,
200 len: u32,
201 names: []u32,
202 expected_panic_msgs: []u32,
203 string_bytes: []u8,
204
205 fn name(m: *@This()) []const u8 {
206 return std.mem.sliceTo(m.string_bytes[m.names[m.index - 1]..], 0);
207 }
208
209 fn expectedPanicMsg(m: *@This()) ?[:0]const u8 {
210 return switch (m.expected_panic_msgs[m.index - 1]) {
211 0 => null,
212 else => |offset| m.string_bytes[offset..std.mem.findScalarPos(
213 u8,
214 m.string_bytes,
215 offset,
216 0,
217 ).? :0],
218 };
219 }
220
221 fn next(m: *@This()) ?u32 {
222 if (m.len - m.index == 0) return null;
223 defer m.index += 1;
224 return m.index;
225 }
226 } = .{ .index = 0, .len = 0, .names = &.{}, .expected_panic_msgs = &.{}, .string_bytes = &.{} };
227 try client.serveBodylessMessage(.query_test_metadata);
228 while (true) {
229 const hdr = try client.receiveMessage();
230 switch (hdr.tag) {
231 else => try client.in.discardAll(hdr.bytes_len),
232 .zig_version => {
233 const body = try arena.alloc(u8, hdr.bytes_len);
234 try client.in.readSliceAll(body);
235 if (!std.mem.eql(u8, @import("builtin").zig_version_string, body)) return fatal(
236 "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
237 .{ @import("builtin").zig_version_string, body },
238 );
239 },
240 .test_metadata => {
241 const tm_hdr =
242 try client.in.takeStruct(std.zig.Server.Message.TestMetadata, .little);
243 metadata = .{
244 .index = 0,
245 .len = tm_hdr.tests_len,
246 .names = try arena.alloc(u32, tm_hdr.tests_len),
247 .expected_panic_msgs = try arena.alloc(u32, tm_hdr.tests_len),
248 .string_bytes = try arena.alloc(u8, tm_hdr.string_bytes_len),
249 };
250 try client.in.readSliceEndian(u32, metadata.names, .little);
251 try client.in.readSliceEndian(u32, metadata.expected_panic_msgs, .little);
252 try client.in.readSliceAll(metadata.string_bytes);
253 try client.serveRunTest(metadata.next() orelse break);
254 },
255 .test_results => {
256 const tr = try client.in.takeStruct(std.zig.Server.Message.TestResults, .little);
257 try stdout.print("[{t}] {s}\n", .{ tr.flags.status, metadata.name() });
258 try stdout.flush();
259 try client.serveRunTest(metadata.next() orelse break);
260 },
261 }
262 }
263 try client.serveBodylessMessage(.exit);
264 }
265 _ = try child.wait(init.io);
266}
267fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
268 std.log.err(fmt, args);
269 std.process.exit(1);
270}
271
272const Runner = @This();
273
274gpa: std.mem.Allocator,
275arena: *std.heap.ArenaAllocator,
276io: std.Io,
277prog_node: std.Progress.Node,
278args: Args,
279host: std.Target,
280server: std.zig.Server,
281
282const Args = struct {
283 @"test": ?struct {
284 input_dir: std.zig.Server.Message.InputDir,
285 dir: std.Io.Dir,
286 },
287 manifest_file: ?std.Io.File,
288 zig_exe: ?std.Io.File,
289 lib_dir: ?std.Io.Dir,
290 src_dir: ?std.Io.Dir,
291 targets: std.ArrayList(u32),
292 target_bytes: std.ArrayList(u8),
293 enable_qemu: bool,
294 enable_wine: bool,
295 enable_wasmtime: bool,
296 enable_darling: bool,
297
298 fn deinit(args: *Args, gpa: std.mem.Allocator) void {
299 args.targets.deinit(gpa);
300 args.target_bytes.deinit(gpa);
301 args.* = undefined;
302 }
303
304 fn addTarget(args: *Args, gpa: std.mem.Allocator, target: []const u8) std.mem.Allocator.Error!void {
305 try args.target_bytes.ensureUnusedCapacity(gpa, target.len + 1);
306 try args.targets.append(gpa, @intCast(args.target_bytes.items.len));
307 args.target_bytes.appendSliceAssumeCapacity(target);
308 args.target_bytes.appendAssumeCapacity(0);
309 }
310};
311
312fn deinit(runner: *Runner) void {
313 runner.prog_node.end();
314 runner.args.deinit(runner.gpa);
315 runner.* = undefined;
316}
317
318pub fn protocol(runner: *Runner) !void {
319 try runner.server.serveStringMessage(.zig_version, @import("builtin").zig_version_string);
320 while (true) {
321 _ = runner.arena.reset(.retain_capacity);
322 const arena = runner.arena.allocator();
323 const hdr = try runner.server.receiveMessage();
324 switch (hdr.tag) {
325 else => {
326 std.log.warn("unsupported message: {t}\n", .{hdr.tag});
327 std.process.exit(1);
328 },
329 .exit => std.process.exit(0),
330 .args => {
331 const args_body = try arena.alloc(u8, hdr.bytes_len);
332 try runner.server.in.readSliceAll(args_body);
333 var state: enum { positional, zig, lib, src, target } = .positional;
334 var input_dir: std.zig.Server.Message.InputDir = .cwd;
335 var args_body_offset: usize = 0;
336 while (args_body.len - args_body_offset > 0) {
337 const arg: std.zig.Client.Message.Arg =
338 @fromBackingInt(args_body[args_body_offset]);
339 args_body_offset += 1;
340 arg: switch (arg) {
341 .string => {
342 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
343 const string = args_body[args_body_offset..end];
344 args_body_offset = end + 1;
345 switch (state) {
346 .positional => if (std.mem.eql(u8, string, "--target")) {
347 state = .target;
348 continue;
349 } else if (std.mem.cutPrefix(u8, string, "--target=")) |target| {
350 try runner.args.addTarget(runner.gpa, target);
351 } else if (std.mem.eql(u8, string, "-fqemu")) {
352 runner.args.enable_qemu = true;
353 } else if (std.mem.eql(u8, string, "-fwine")) {
354 runner.args.enable_wine = true;
355 } else if (std.mem.eql(u8, string, "-fwasmtime")) {
356 runner.args.enable_wasmtime = true;
357 } else if (std.mem.eql(u8, string, "-fdarling")) {
358 runner.args.enable_darling = true;
359 },
360 .zig => unreachable,
361 .lib => unreachable,
362 .src => unreachable,
363 .target => try runner.args.addTarget(runner.gpa, string),
364 }
365 },
366 .prefix => {
367 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
368 const string = args_body[args_body_offset..end];
369 args_body_offset = end + 1;
370 state = if (std.mem.eql(u8, string, "--zig="))
371 .zig
372 else if (std.mem.eql(u8, string, "--lib="))
373 .lib
374 else if (std.mem.eql(u8, string, "--src="))
375 .src
376 else
377 unreachable;
378 continue;
379 },
380 .suffix => unreachable,
381 .input_dir => {
382 input_dir = @fromBackingInt(@backingInt(input_dir) + 1);
383 continue :arg .output_dir;
384 },
385 .output_dir => {
386 const dir_handle: *align(1) const std.Io.Dir.Handle = @ptrCast(
387 args_body[args_body_offset..][0..@sizeOf(std.Io.Dir.Handle)],
388 );
389 args_body_offset += @sizeOf(std.Io.Dir.Handle);
390 const dir: std.Io.Dir = .{
391 .handle = dir_handle.*,
392 };
393 switch (state) {
394 .positional => {
395 std.debug.assert(runner.args.@"test" == null);
396 runner.args.@"test" = .{ .input_dir = input_dir, .dir = dir };
397 std.debug.assert(runner.args.manifest_file == null);
398 const manifest_path = "manifest";
399 runner.args.manifest_file =
400 try dir.openFile(runner.io, manifest_path, .{});
401
402 try runner.server.serveMessageHeader(.{
403 .tag = .discovered_inputs,
404 .bytes_len = @sizeOf(std.zig.Server.Message.InputDir) +
405 manifest_path.len + 1,
406 });
407 try runner.server.out.writeInt(u32, @backingInt(
408 input_dir,
409 ), .little);
410 try runner.server.out.writeAll(manifest_path);
411 try runner.server.out.writeByte(0);
412 try runner.server.out.flush();
413 },
414 .zig => unreachable,
415 .lib => {
416 std.debug.assert(runner.args.lib_dir == null);
417 runner.args.lib_dir = dir;
418 },
419 .src => {
420 std.debug.assert(runner.args.src_dir == null);
421 runner.args.src_dir = dir;
422 },
423 .target => unreachable,
424 }
425 },
426 .input_file, .input_file_content, .output_file => {
427 const file_handle: *align(1) const std.Io.File.Handle = @ptrCast(
428 args_body[args_body_offset..][0..@sizeOf(std.Io.File.Handle)],
429 );
430 args_body_offset += @sizeOf(std.Io.File.Handle);
431 const file: std.Io.File = .{
432 .handle = file_handle.*,
433 .flags = .{ .nonblocking = false },
434 };
435 switch (state) {
436 .positional => {
437 std.debug.assert(runner.args.manifest_file == null);
438 runner.args.manifest_file = file;
439 },
440 .zig => {
441 std.debug.assert(runner.args.zig_exe == null);
442 runner.args.zig_exe = file;
443 },
444 .lib => unreachable,
445 .src => unreachable,
446 .target => unreachable,
447 }
448 },
449 }
450 state = .positional;
451 }
452 },
453 .query_test_metadata => {
454 const expected_panic_msgs = try arena.alloc(u32, runner.args.targets.items.len);
455 @memset(expected_panic_msgs, 0);
456 try runner.server.serveTestMetadata(.{
457 .names = runner.args.targets.items,
458 .expected_panic_msgs = expected_panic_msgs,
459 .string_bytes = runner.args.target_bytes.items,
460 });
461 },
462 .run_test => {
463 const test_index = try runner.server.receiveBody_u32();
464 try runner.server.serveBodylessMessage(.test_started);
465 try runner.server.serveTestResults(.{
466 .index = test_index,
467 .flags = .{
468 .status = if (runner.testOne(test_index)) .pass else |err| switch (err) {
469 else => status: {
470 std.log.err("{t}\n", .{err});
471 if (@errorReturnTrace()) |trace| std.debug.dumpErrorReturnTrace(trace);
472 break :status .fail;
473 },
474 error.SkipZigTest => .skip,
475 },
476 .fuzz = false,
477 .log_err_count = 0,
478 .leak_count = 0,
479 },
480 });
481 },
482 }
483 }
484}
485const DelimitedReader = struct {
486 const Io = std.Io;
487
488 unlimited: *Io.Reader,
489 remaining: Io.Limit,
490 delimiter: []const u8,
491 interface: Io.Reader,
492
493 pub fn init(reader: *Io.Reader, delimiter: []const u8, buffer: []u8) DelimitedReader {
494 return .{
495 .unlimited = reader,
496 .remaining = .nothing,
497 .delimiter = delimiter,
498 .interface = .{
499 .vtable = &.{
500 .stream = stream,
501 .discard = discard,
502 },
503 .buffer = buffer,
504 .seek = 0,
505 .end = 0,
506 },
507 };
508 }
509 fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
510 const d: *DelimitedReader = @fieldParentPtr("interface", r);
511 const block = try d.unlimited.peekGreedy(d.delimiter.len);
512 d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse
513 block.len - d.delimiter.len + 1);
514 if (d.remaining == .nothing) return error.EndOfStream;
515 const n = try d.unlimited.stream(w, limit.min(d.remaining));
516 d.remaining = d.remaining.subtract(n).?;
517 return n;
518 }
519 fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
520 const d: *DelimitedReader = @fieldParentPtr("interface", r);
521 const block = try d.unlimited.peekGreedy(d.delimiter.len);
522 d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse
523 block.len - d.delimiter.len + 1);
524 if (d.remaining == .nothing) return error.EndOfStream;
525 const n = try d.unlimited.discard(limit.min(d.remaining));
526 d.remaining = d.remaining.subtract(n).?;
527 return n;
528 }
529};
530
531fn testOne(runner: *const Runner, test_index: u32) !void {
532 const target_query =
533 std.mem.sliceTo(runner.args.target_bytes.items[runner.args.targets.items[test_index]..], 0);
534 const src_dir = try (runner.args.src_dir orelse return error.MissingSrcDir).createDirPathOpen(
535 runner.io,
536 target_query,
537 .{},
538 );
539 const manifest_file = runner.args.manifest_file orelse return error.MissingManifestArg;
540 var manifest_buffer: [512]u8 = undefined;
541 var manifest_fr = manifest_file.reader(runner.io, &manifest_buffer);
542 var allow_skip = true;
543 var skip_delimiter = false;
544 var compiler: ?Compiler = null;
545 var update_prog_node: std.Progress.Node = .none;
546 defer update_prog_node.end();
547 var update_mtime = std.Io.Clock.real.now(runner.io);
548 var update_num: usize = 0;
549 while (manifest_fr.interface.takeSentinel('\n')) |untrimmed_line| {
550 const line = std.mem.trimEnd(u8, untrimmed_line, "\r");
551 var line_it = std.mem.tokenizeScalar(u8, line, ' ');
552 const cmd_str = std.mem.cutPrefix(u8, line_it.next() orelse continue, "#").?;
553 if (skip_delimiter) {
554 std.debug.assert(std.mem.eql(u8, cmd_str, "}"));
555 skip_delimiter = false;
556 continue;
557 }
558 const cmd = std.meta.stringToEnum(
559 enum { todo, skip, exe, lib, obj, write, delete, update, check },
560 cmd_str,
561 ) orelse return error.UnknownCommand;
562 var maybe_arg = line_it.next();
563 var contents_file: ?std.Io.File = null;
564 defer if (contents_file) |file| file.close(runner.io);
565 var contents_buffer: [512]u8 = undefined;
566 var contents_impl: union {
567 dr: DelimitedReader,
568 fr: std.Io.File.Reader,
569 } = undefined;
570 const contents_r: *std.Io.Reader = contents_r: switch (cmd) {
571 .skip, .delete => {
572 if (line_it.next()) |_| return error.UnexpectedArg;
573 break :contents_r .ending;
574 },
575 else => {
576 const contents_path = contents_path: {
577 const line_rest = line_it.rest();
578 if (line_rest.len > 0) break :contents_path line_rest;
579 const contents_path = maybe_arg orelse break :contents_r .ending;
580 maybe_arg = null;
581 break :contents_path contents_path;
582 };
583 if (std.mem.eql(u8, contents_path, "{")) {
584 contents_impl = .{ .dr = .init(&manifest_fr.interface, "#}", &contents_buffer) };
585 skip_delimiter = true;
586 break :contents_r &contents_impl.dr.interface;
587 }
588
589 const args_test = runner.args.@"test" orelse return error.ContentsFileInFileTest;
590 try runner.server.serveMessageHeader(.{
591 .tag = .discovered_inputs,
592 .bytes_len = @intCast(@sizeOf(std.zig.Server.Message.InputDir) +
593 contents_path.len + 1),
594 });
595 try runner.server.out.writeInt(u32, @backingInt(args_test.input_dir), .little);
596 try runner.server.out.writeAll(contents_path);
597 try runner.server.out.writeByte(0);
598 try runner.server.out.flush();
599
600 contents_file = try args_test.dir.openFile(runner.io, contents_path, .{});
601 contents_impl = .{ .fr = contents_file.?.reader(runner.io, &contents_buffer) };
602 break :contents_r &contents_impl.fr.interface;
603 },
604 };
605
606 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
607 const cmd_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "#{t} {s}", .{
608 cmd, maybe_arg orelse "",
609 }) catch &name_buffer, 0);
610 defer cmd_prog_node.end();
611
612 cmd: switch (cmd) {
613 .todo => _ = try contents_r.discardRemaining(),
614 .skip => if (!allow_skip)
615 return error.NonInitialSkipCommand // #skip must appear first
616 else if (std.mem.eql(u8, target_query, maybe_arg orelse return error.MissingArg))
617 return error.SkipZigTest,
618 .exe, .lib, .obj => try runner.spawnCompiler(&compiler, src_dir, switch (cmd) {
619 else => unreachable,
620 .exe => .Exe,
621 .lib => .Lib,
622 .obj => .Obj,
623 }, contents_r, target: {
624 const backend_split = std.mem.findScalarLast(u8, target_query, '-') orelse
625 return error.TargetMissingBackend;
626 const mode_split =
627 std.mem.findScalarLast(u8, target_query[0..backend_split], '-') orelse
628 return error.TargetMissingMode;
629 const triple = target_query[0..mode_split];
630 const backend = std.meta.stringToEnum(
631 Compiler.Target.Backend,
632 target_query[backend_split + 1 ..],
633 ) orelse return error.TargetMissingBackend;
634 const mode = std.meta.stringToEnum(
635 Compiler.Target.Mode,
636 target_query[mode_split + 1 .. backend_split],
637 ) orelse return error.TargetMissingMode;
638 break :target .{
639 .triple = triple,
640 .resolved = try std.zig.system.resolveTargetQuery(
641 runner.io,
642 try std.Build.parseTargetQuery(.{
643 .arch_os_abi = triple,
644 .object_format = switch (backend) {
645 .sema, .selfhosted, .llvm => null,
646 .cbe => "c",
647 },
648 }),
649 ),
650 .mode = mode,
651 .backend = backend,
652 };
653 }),
654 .write => {
655 const file = try src_dir.createFile(
656 runner.io,
657 maybe_arg orelse return error.MissingFileArg,
658 .{},
659 );
660 defer file.close(runner.io);
661 var fw = file.writer(runner.io, &.{});
662 _ = try contents_r.streamRemaining(&fw.interface);
663 try file.setTimestamps(runner.io, .{ .modify_timestamp = .{ .new = update_mtime } });
664 },
665 .delete => try src_dir.deleteFile(
666 runner.io,
667 maybe_arg orelse return error.MissingFileArg,
668 ),
669 .update => {
670 const comp = &(compiler orelse return error.MissingCompiler);
671 update_prog_node.end();
672 update_prog_node = runner.prog_node.start(
673 switch (try contents_r.readSliceShort(&name_buffer)) {
674 0 => switch (update_num) {
675 0 => "initial update",
676 else => |num| std.mem.print(&name_buffer, "update {d}", .{num}) catch
677 &name_buffer,
678 },
679 else => |name_len| name_buffer[0..name_len],
680 },
681 0,
682 );
683 _ = try contents_r.discardRemaining();
684 if (comp.state != .idle) return error.MissingCheck;
685 try comp.client.serveBodylessMessage(.update);
686 comp.state = .update;
687 update_mtime = update_mtime.addDuration(.fromSeconds(2));
688 update_num += 1;
689 },
690 .check => {
691 const check = std.meta.stringToEnum(
692 Compiler.Check,
693 maybe_arg orelse return error.MissingCheckArg,
694 ) orelse return error.UnknownCheck;
695 const expected = try contents_r.allocRemaining(runner.gpa, .unlimited);
696 defer runner.gpa.free(expected);
697
698 const comp = &(compiler orelse return error.MissingCompiler);
699 if (comp.state != .update) return error.MissingUpdate;
700 const stderr = comp.mr.reader(1);
701 while (try comp.receiveMessage()) |message| switch (message.tag) {
702 .config => {
703 var body_r: std.Io.Reader = .fixed(message.body);
704 comp.config = body_r.takeStruct(std.zig.Server.Message.Config, .little) catch
705 unreachable;
706 },
707 .emit_digest => {
708 var body_r: std.Io.Reader = .fixed(message.body);
709 _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch
710 unreachable;
711 const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch
712 unreachable;
713 try comp.checkSuccess(check, expected, src_dir, digest);
714 },
715 .error_bundle => {
716 const error_bundle =
717 try std.zig.Server.allocErrorBundle(runner.arena.allocator(), message.body);
718 switch (check) {
719 .errors => {
720 var error_aw: std.Io.Writer.Allocating = .init(runner.gpa);
721 defer error_aw.deinit();
722 try error_bundle.renderToWriter(.{
723 .include_reference_trace = false,
724 .include_source_line = false,
725 .include_log_text = true,
726 }, &error_aw.writer);
727 try std.testing.expectEqualStrings(expected, error_aw.written());
728 },
729 .stdout, .exit, .lldb => if (error_bundle.errorMessageCount() > 0) {
730 try error_bundle.renderToStderr(runner.io, .{}, .auto);
731 fatal("unexpected compile errors", .{});
732 },
733 }
734 comp.state = .idle;
735 break :cmd;
736 },
737 else => {}, // Ignore other messages,
738 };
739
740 const buffered_stderr = stderr.buffered();
741 if (buffered_stderr.len > 0) {
742 if (comp.allow_compiler_stderr) {
743 std.log.info("stderr:\n{s}", .{buffered_stderr});
744 } else {
745 fatal("unexpected stderr:\n{s}", .{buffered_stderr});
746 }
747 }
748
749 try comp.exit();
750 fatal("compiler failed to send terminating error_bundle", .{});
751 },
752 }
753 std.debug.assert(try contents_r.discardRemaining() == 0);
754 switch (cmd) {
755 .todo, .skip => {},
756 else => allow_skip = false,
757 }
758 } else |err| switch (err) {
759 else => |e| return e,
760 error.EndOfStream => if (skip_delimiter) return error.MissingDelimiter,
761 }
762 if (compiler) |*comp| {
763 if (comp.state != .idle) return error.MissingCheck;
764 try comp.exit();
765 }
766}
767
768fn spawnCompiler(
769 runner: *const Runner,
770 compiler: *?Compiler,
771 src_dir: std.Io.Dir,
772 output_mode: std.lang.OutputMode,
773 args_r: *std.Io.Reader,
774 target: Compiler.Target,
775) !void {
776 const gpa = runner.gpa;
777 const arena = runner.arena.allocator();
778 if (compiler.*) |*comp| {
779 try comp.exit();
780 compiler.* = null;
781 }
782
783 var argv: std.ArrayList([]const u8) = .empty;
784 defer argv.deinit(gpa);
785 try argv.appendSlice(gpa, &.{
786 "zig",
787 switch (output_mode) {
788 .Obj => "build-obj",
789 .Exe => "build-exe",
790 .Lib => "build-lib",
791 },
792 "-target",
793 target.triple,
794 "--cache-dir",
795 ".zig-cache",
796 });
797 switch (target.mode) {
798 .whole => {},
799 .incremental => try argv.append(gpa, "-fincremental"),
800 }
801 var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
802 if (target.backend) |b| switch (b) {
803 .sema => try argv.append(gpa, "-fno-emit-bin"),
804 .selfhosted => try argv.append(gpa, "-fno-llvm"),
805 .llvm => try argv.append(gpa, "-fllvm"),
806 .cbe => try argv.append(gpa, "-ofmt=c"),
807 } else try argv.appendSlice(gpa, &.{ "-I", path_buffer[0..try std.Io.Dir.readLinkAbsolute(
808 runner.io,
809 try arena.print("/proc/self/fd/{d}", .{
810 (runner.args.lib_dir orelse return error.MissingLibDir).handle,
811 }),
812 &path_buffer,
813 )] });
814 var need: usize = 1;
815 var root_name: ?[]const u8 = null;
816 while (true) {
817 const done = if (args_r.fill(need)) false else |err| switch (err) {
818 else => |e| return e,
819 error.EndOfStream => true,
820 };
821 const buffered = args_r.buffered();
822 const start = std.mem.findNone(u8, buffered, "\n ") orelse buffered.len;
823 const end = std.mem.findAnyPos(u8, buffered, start, "\n ") orelse buffered.len;
824 if (end - start > 0) {
825 const arg = try arena.dupe(u8, buffered[start..end]);
826 if (std.mem.cutPrefix(u8, arg, "-M")) |module| {
827 var module_name_it = std.mem.splitScalar(u8, module, '=');
828 const module_name = module_name_it.next().?;
829 root_name = root_name orelse module_name;
830 }
831 try argv.append(gpa, arg);
832 args_r.toss(end);
833 need = 1;
834 } else {
835 args_r.toss(start);
836 need = buffered.len - start + 1;
837 }
838 if (done) break;
839 }
840 try argv.append(gpa, "--listen=-");
841
842 const out_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{
843 .root_name = root_name orelse "root",
844 .cpu_arch = target.resolved.cpu.arch,
845 .os_tag = target.resolved.os.tag,
846 .ofmt = target.resolved.ofmt,
847 .abi = target.resolved.abi,
848 .output_mode = output_mode,
849 });
850
851 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
852 const comp_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "compile {s}", .{
853 out_name,
854 }) catch &name_buffer, 0);
855 errdefer comp_prog_node.end();
856
857 const child = try std.process.spawn(runner.io, .{
858 .exe = .{ .file = runner.args.zig_exe orelse return error.MissingZigExe },
859 .argv = argv.items,
860 .cwd = .{ .dir = src_dir },
861 .progress_node = comp_prog_node,
862 .stdin = .pipe,
863 .stdout = .pipe,
864 .stderr = .pipe,
865 });
866 compiler.* = .{
867 .runner = runner,
868 .prog_node = comp_prog_node,
869 .allow_compiler_stderr = true,
870 .output_mode = output_mode,
871 .state = .idle,
872 .child = child,
873 .fw = undefined,
874 .mr_buffer = undefined,
875 .mr = undefined,
876 .client = undefined,
877 .target = target,
878 .out_name = out_name,
879 .config = null,
880 };
881 const comp = &compiler.*.?;
882 comp.fw = child.stdin.?.writerStreaming(runner.io, &.{});
883 comp.mr.init(gpa, runner.io, comp.mr_buffer.toStreams(), &.{
884 child.stdout.?, child.stderr.?,
885 });
886 comp.client = .{ .in = comp.mr.reader(0), .out = &comp.fw.interface };
887}
888const Compiler = struct {
889 runner: *const Runner,
890 prog_node: std.Progress.Node,
891 allow_compiler_stderr: bool,
892 output_mode: std.lang.OutputMode,
893 state: enum { idle, update },
894 child: std.process.Child,
895 fw: std.Io.File.Writer,
896 mr_buffer: std.Io.File.MultiReader.Buffer(2),
897 mr: std.Io.File.MultiReader,
898 client: std.zig.Client,
899 target: Target,
900 out_name: []const u8,
901 config: ?std.zig.Server.Message.Config,
902
903 const Target = struct {
904 triple: []const u8,
905 resolved: std.Target,
906 mode: Mode,
907 backend: ?Backend,
908
909 const Mode = enum {
910 whole,
911 incremental,
912 };
913
914 const Backend = enum {
915 /// Run semantic analysis only. Runtime output will not be tested, but we still verify
916 /// that compilation succeeds. Corresponds to `-fno-emit-bin`.
917 sema,
918 /// Use the self-hosted code generation backend for this target.
919 /// Corresponds to `-fno-llvm -fno-lld`.
920 selfhosted,
921 /// Use the LLVM backend.
922 /// Corresponds to `-fllvm -flld`.
923 llvm,
924 /// Use the C backend. The output is compiled with `zig cc`.
925 /// Corresponds to `-ofmt=c`.
926 cbe,
927 };
928 };
929
930 fn receiveMessage(comp: *Compiler) !?struct {
931 tag: std.zig.Server.Message.Tag,
932 body: []const u8,
933 } {
934 const header = comp.client.receiveMessageWithMultiReader(
935 &comp.mr,
936 .none,
937 ) catch |err| switch (err) {
938 error.Timeout => unreachable,
939 error.EndOfStream => return null,
940 else => |e| return e,
941 };
942 const body = comp.client.in.take(header.bytes_len) catch unreachable;
943 const stderr = comp.mr.reader(1);
944 if (stderr.bufferedLen() > 0) {
945 if (comp.allow_compiler_stderr) {
946 std.log.info("{t} stderr:\n{s}", .{ header.tag, stderr.buffered() });
947 } else {
948 fatal("{t} unexpected stderr:\n{s}", .{ header.tag, stderr.buffered() });
949 }
950 stderr.tossBuffered();
951 }
952 return .{ .tag = header.tag, .body = body };
953 }
954
955 const Check = enum { errors, stdout, exit, lldb };
956 fn checkSuccess(
957 comp: *Compiler,
958 check: Check,
959 expected: []const u8,
960 src_dir: std.Io.Dir,
961 digest: *const std.Build.Cache.BinDigest,
962 ) !void {
963 const runner = comp.runner;
964 const gpa = runner.gpa;
965 const arena = runner.arena.allocator();
966 const io = runner.io;
967 const config = &comp.config.?;
968 const out_dir = ".zig-cache" ++ std.Io.Dir.path.sep_str ++
969 "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*);
970 const out_path = try std.Io.Dir.path.join(arena, &.{ out_dir, comp.out_name });
971 const bin_path = switch (comp.target.backend.?) {
972 .sema => return,
973 .selfhosted, .llvm => out_path,
974 .cbe => try comp.compileC(src_dir, out_path),
975 };
976 switch (check) {
977 .errors => return,
978 .stdout, .exit, .lldb => {},
979 }
980
981 const executor = executor: switch (std.zig.system.getExternalExecutor(
982 io,
983 &comp.target.resolved,
984 .{
985 .host_cpu_arch = runner.host.cpu.arch,
986 .host_os_tag = runner.host.os.tag,
987 .link_mode = config.flags.link_mode,
988 .link_libc = config.flags.link_libc,
989 },
990 )) {
991 .bad_dl, .bad_os_or_cpu => {
992 // This binary cannot be executed on this host.
993 std.log.warn("skipping execution because host '{s}' cannot execute binaries for " ++
994 "foreign target '{s}'", .{
995 try runner.host.zigTriple(arena), comp.target.triple,
996 });
997 return;
998 },
999 .native, .rosetta => null,
1000 .qemu => |executor| if (runner.args.enable_qemu)
1001 executor
1002 else
1003 continue :executor .bad_os_or_cpu,
1004 .wine => |executor| if (runner.args.enable_wine)
1005 executor
1006 else
1007 continue :executor .bad_os_or_cpu,
1008 .wasmtime => |executor| if (runner.args.enable_wasmtime)
1009 executor
1010 else
1011 continue :executor .bad_os_or_cpu,
1012 .darling => |executor| if (runner.args.enable_darling)
1013 executor
1014 else
1015 continue :executor .bad_os_or_cpu,
1016 };
1017
1018 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
1019 const run_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "run {s}", .{
1020 comp.out_name,
1021 }) catch &name_buffer, 0);
1022 defer run_prog_node.end();
1023
1024 const bin_file = try src_dir.openFile(io, bin_path, .{});
1025 defer bin_file.close(io);
1026 switch (check) {
1027 .errors => unreachable,
1028 .stdout, .exit => {
1029 const result = std.process.run(gpa, io, .{
1030 .exe = if (executor) |_| .search else .{ .file = bin_file },
1031 .argv = if (executor) |e| &.{ e, bin_path } else &.{bin_path},
1032 .cwd = .{ .dir = src_dir },
1033 .progress_node = run_prog_node,
1034 }) catch |err| if (executor) |_| {
1035 // Chances are the foreign executor isn't available. Skip this evaluation.
1036 std.log.warn("skipping execution of '{s}' via executor for foreign target '{s}': {t}", .{
1037 bin_path,
1038 comp.target.triple,
1039 err,
1040 });
1041 return;
1042 } else fatal("failed to run the generated executable '{s}': {t}", .{ bin_path, err });
1043 defer {
1044 gpa.free(result.stdout);
1045 gpa.free(result.stderr);
1046 }
1047 switch (result.term) {
1048 .exited => |code| switch (check) {
1049 .errors, .lldb => unreachable,
1050 .stdout => {
1051 if (code != 0) fatal("generated executable '{s}' failed with code {d}", .{ bin_path, code });
1052 try std.testing.expectEqualStrings(expected, result.stdout);
1053 },
1054 .exit => {
1055 var actual_code_buffer: [std.fmt.count("{d}", .{std.math.maxInt(u8)})]u8 = undefined;
1056 try std.testing.expectEqualStrings(
1057 expected,
1058 std.mem.print(&actual_code_buffer, "{d}", .{code}) catch unreachable,
1059 );
1060 },
1061 },
1062 .signal => |sig| fatal("generated executable '{s}' terminated with signal {t}", .{ bin_path, sig }),
1063 .stopped => |sig| fatal("generated executable '{s}' stopped with signal {t}", .{ bin_path, sig }),
1064 .unknown => fatal("generated executable '{s}' terminated unexpectedly", .{bin_path}),
1065 }
1066 if (executor == null and result.stderr.len > 0) {
1067 std.log.err("generated executable '{s}' had unexpected stderr:\n{s}", .{
1068 bin_path, result.stderr,
1069 });
1070 }
1071 },
1072 .lldb => {},
1073 }
1074 }
1075
1076 fn compileC(parent_comp: *Compiler, src_dir: std.Io.Dir, c_path: []const u8) ![]const u8 {
1077 const runner = parent_comp.runner;
1078 const arena = runner.arena.allocator();
1079 var compiler: ?Compiler = null;
1080 var args_r: std.Io.Reader = .fixed(c_path);
1081 try parent_comp.runner.spawnCompiler(&compiler, src_dir, parent_comp.output_mode, &args_r, .{
1082 .triple = parent_comp.target.triple,
1083 .resolved = resolved: {
1084 var resolved = parent_comp.target.resolved;
1085 resolved.ofmt = .default(resolved.os.tag, resolved.cpu.arch);
1086 break :resolved resolved;
1087 },
1088 .mode = .whole,
1089 .backend = null,
1090 });
1091 const comp = &compiler.?;
1092 try comp.client.serveBodylessMessage(.update);
1093 comp.state = .update;
1094 var out_path: ?[]const u8 = null;
1095 while (try comp.receiveMessage()) |message| switch (message.tag) {
1096 .config => {
1097 var body_r: std.Io.Reader = .fixed(message.body);
1098 comp.config =
1099 body_r.takeStruct(std.zig.Server.Message.Config, .little) catch unreachable;
1100 },
1101 .emit_digest => {
1102 var body_r: std.Io.Reader = .fixed(message.body);
1103 _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable;
1104 const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch unreachable;
1105
1106 const out_dir = ".zig-cache" ++ std.Io.Dir.path.sep_str ++
1107 "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*);
1108 out_path = try std.Io.Dir.path.join(arena, &.{ out_dir, comp.out_name });
1109 },
1110 .error_bundle => {
1111 const error_bundle = try std.zig.Server.allocErrorBundle(arena, message.body);
1112
1113 if (error_bundle.errorMessageCount() > 0) {
1114 try error_bundle.renderToStderr(runner.io, .{}, .auto);
1115 fatal("unexpected compile errors", .{});
1116 }
1117 comp.state = .idle;
1118 break;
1119 },
1120 else => {}, // Ignore other messages,
1121 };
1122 try comp.exit();
1123 return out_path.?;
1124 }
1125
1126 fn exit(comp: *Compiler) !void {
1127 const io = comp.runner.io;
1128 comp.client.serveBodylessMessage(.exit) catch |err| switch (err) {
1129 error.WriteFailed => switch (comp.fw.err.?) {
1130 error.BrokenPipe => {},
1131 else => |e| fatal("failed to send exit: {t}", .{e}),
1132 },
1133 };
1134 comp.child.stdin.?.close(io);
1135 comp.child.stdin = null;
1136 while (try comp.receiveMessage()) |_| {}
1137 if (comp.client.in.bufferedLen() > 0) return error.EndOfStream;
1138 const term = comp.child.wait(io) catch |err| fatal("child process failed: {t}", .{err});
1139 comp.prog_node.end();
1140 switch (term) {
1141 .exited => |code| if (code != 0) fatal("compiler failed with code {d}", .{code}),
1142 .signal => |sig| fatal("compiler terminated with signal {t}", .{sig}),
1143 .stopped => |sig| fatal("compiler stopped unexpectedly with signal {t}", .{sig}),
1144 .unknown => fatal("compiler terminated unexpectedly", .{}),
1145 }
1146 }
1147};
test/src/runner.zig deleted-918
......@@ -1,918 +0,0 @@
1const std = @import("std");
2
3pub fn main(init: std.process.Init) !void {
4 const arena = init.arena.allocator();
5
6 var test_path_arg: ?[]const u8 = null;
7 var zig_path_arg: ?[]const u8 = null;
8 var lib_path_arg: ?[]const u8 = null;
9 var target_args: std.ArrayList([]const u8) = .empty;
10 defer target_args.deinit(init.gpa);
11 var keep_src = false;
12
13 var arg_it = try init.minimal.args.iterateAllocator(arena);
14 const self = arg_it.next().?;
15 while (arg_it.next()) |arg| {
16 if (std.mem.eql(u8, arg, "--listen=-")) {
17 return protocol(init.gpa, init.arena, init.io);
18 } else if (std.mem.eql(u8, arg, "--zig")) {
19 zig_path_arg = arg_it.next() orelse fatal("missing arg after '{s}'", .{arg});
20 } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| {
21 zig_path_arg = zig_path;
22 } else if (std.mem.eql(u8, arg, "--lib")) {
23 lib_path_arg = arg_it.next() orelse fatal("missing arg after '{s}'", .{arg});
24 } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| {
25 lib_path_arg = lib_path;
26 } else if (std.mem.eql(u8, arg, "--target")) {
27 try target_args.append(
28 init.gpa,
29 try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after '{s}'", .{arg})),
30 );
31 } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| {
32 try target_args.append(init.gpa, try arena.dupe(u8, target));
33 } else if (std.mem.eql(u8, arg, "--keep-src")) {
34 keep_src = true;
35 } else {
36 if (test_path_arg) |_| fatal("unknown arg '{s}'", .{arg});
37 test_path_arg = arg;
38 }
39 }
40
41 const test_path = test_path_arg orelse fatal("missing 'path/to/test'", .{});
42 const zig_path = zig_path_arg orelse fatal("missing '--zig=path/to/zig'", .{});
43 const lib_path = lib_path_arg orelse fatal("missing '--lib=path/to/lib'", .{});
44
45 const cwd: std.Io.Dir = .cwd();
46 const test_file = try cwd.openFile(init.io, test_path, .{
47 .allow_directory = true,
48 });
49 const zig_exe = try cwd.openFile(init.io, zig_path, .{});
50 defer zig_exe.close(init.io);
51 const lib_dir = try cwd.openDir(init.io, lib_path, .{});
52 defer lib_dir.close(init.io);
53
54 const src_dir_path = "src_" ++ std.fmt.hex(rand_int: {
55 var rand_int: u64 = undefined;
56 init.io.random(@ptrCast(&rand_int));
57 break :rand_int rand_int;
58 });
59 var src_dir = try cwd.createDirPathOpen(init.io, src_dir_path, .{});
60 defer {
61 src_dir.close(init.io);
62 if (!keep_src) cwd.deleteTree(init.io, src_dir_path) catch |err| {
63 std.log.warn("fataled to delete tree '{s}': {t}", .{ src_dir_path, err });
64 };
65 }
66
67 var stdout_buffer: [512]u8 = undefined;
68 var stdout_fw: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer);
69 const stdout = &stdout_fw.interface;
70
71 var child = try std.process.spawn(init.io, .{
72 .argv = &.{ self, "--listen=-" },
73 .stdin = .pipe,
74 .stdout = .pipe,
75 .inherit_dirs = &.{ lib_dir, src_dir },
76 .inherit_files = &.{ zig_exe, test_file },
77 });
78
79 var child_reader_buffer: [512]u8 = undefined;
80 var child_reader = child.stdout.?.readerStreaming(init.io, &child_reader_buffer);
81 var child_writer_buffer: [512]u8 = undefined;
82 var child_writer = child.stdin.?.writerStreaming(init.io, &child_writer_buffer);
83 var client: std.zig.Client = .{
84 .in = &child_reader.interface,
85 .out = &child_writer.interface,
86 };
87 {
88 var args: std.ArrayList(u8) = .empty;
89 defer args.deinit(init.gpa);
90 const Arg = std.zig.Client.Message.Arg;
91
92 try args.append(init.gpa, @backingInt(@as(Arg, switch ((try test_file.stat(init.io)).kind) {
93 else => unreachable,
94 .file => .input_file,
95 .directory => .input_dir,
96 })));
97 try args.appendSlice(init.gpa, @ptrCast(&test_file.handle));
98
99 try args.append(init.gpa, @backingInt(Arg.prefix));
100 try args.appendSlice(init.gpa, "--zig=");
101 try args.append(init.gpa, 0);
102
103 try args.append(init.gpa, @backingInt(Arg.input_file));
104 try args.appendSlice(init.gpa, @ptrCast(&zig_exe.handle));
105
106 try args.append(init.gpa, @backingInt(Arg.prefix));
107 try args.appendSlice(init.gpa, "--lib=");
108 try args.append(init.gpa, 0);
109
110 try args.append(init.gpa, @backingInt(Arg.input_dir));
111 try args.appendSlice(init.gpa, @ptrCast(&lib_dir.handle));
112
113 try args.append(init.gpa, @backingInt(Arg.prefix));
114 try args.appendSlice(init.gpa, "--src=");
115 try args.append(init.gpa, 0);
116
117 try args.append(init.gpa, @backingInt(Arg.output_dir));
118 try args.appendSlice(init.gpa, @ptrCast(&src_dir.handle));
119
120 for (target_args.items) |target_arg| {
121 try args.append(init.gpa, @backingInt(Arg.string));
122 try args.appendSlice(init.gpa, "--target");
123 try args.append(init.gpa, 0);
124
125 try args.append(init.gpa, @backingInt(Arg.string));
126 try args.appendSlice(init.gpa, target_arg);
127 try args.append(init.gpa, 0);
128 }
129
130 try client.serveMessageHeader(.{
131 .tag = .args,
132 .bytes_len = @intCast(args.items.len),
133 });
134 try client.out.writeAll(args.items);
135
136 var metadata: struct {
137 index: u32,
138 len: u32,
139 names: []u32,
140 expected_panic_msgs: []u32,
141 string_bytes: []u8,
142
143 fn name(m: *@This()) []const u8 {
144 return std.mem.sliceTo(m.string_bytes[m.names[m.index - 1]..], 0);
145 }
146
147 fn expectedPanicMsg(m: *@This()) ?[:0]const u8 {
148 return switch (m.expected_panic_msgs[m.index - 1]) {
149 0 => null,
150 else => |offset| m.string_bytes[offset..std.mem.findScalarPos(
151 u8,
152 m.string_bytes,
153 offset,
154 0,
155 ).? :0],
156 };
157 }
158
159 fn next(m: *@This()) ?u32 {
160 if (m.len - m.index == 0) return null;
161 defer m.index += 1;
162 return m.index;
163 }
164 } = .{ .index = 0, .len = 0, .names = &.{}, .expected_panic_msgs = &.{}, .string_bytes = &.{} };
165 try client.serveBodylessMessage(.query_test_metadata);
166 while (true) {
167 const hdr = try client.receiveMessage();
168 switch (hdr.tag) {
169 else => try client.in.discardAll(hdr.bytes_len),
170 .zig_version => {
171 const body = try arena.alloc(u8, hdr.bytes_len);
172 try client.in.readSliceAll(body);
173 if (!std.mem.eql(u8, @import("builtin").zig_version_string, body)) return fatal(
174 "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
175 .{ @import("builtin").zig_version_string, body },
176 );
177 },
178 .test_metadata => {
179 const tm_hdr =
180 try client.in.takeStruct(std.zig.Server.Message.TestMetadata, .little);
181 metadata = .{
182 .index = 0,
183 .len = tm_hdr.tests_len,
184 .names = try arena.alloc(u32, tm_hdr.tests_len),
185 .expected_panic_msgs = try arena.alloc(u32, tm_hdr.tests_len),
186 .string_bytes = try arena.alloc(u8, tm_hdr.string_bytes_len),
187 };
188 try client.in.readSliceEndian(u32, metadata.names, .little);
189 try client.in.readSliceEndian(u32, metadata.expected_panic_msgs, .little);
190 try client.in.readSliceAll(metadata.string_bytes);
191 try client.serveRunTest(metadata.next() orelse break);
192 },
193 .test_results => {
194 const tr = try client.in.takeStruct(std.zig.Server.Message.TestResults, .little);
195 try stdout.print("[{t}] {s}\n", .{ tr.flags.status, metadata.name() });
196 try stdout.flush();
197 try client.serveRunTest(metadata.next() orelse break);
198 },
199 }
200 }
201 try client.serveBodylessMessage(.exit);
202 }
203 _ = try child.wait(init.io);
204}
205fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
206 std.log.err(fmt, args);
207 std.process.exit(1);
208}
209
210const Args = struct {
211 @"test": ?struct {
212 input_dir: std.zig.Server.Message.InputDir,
213 dir: std.Io.Dir,
214 },
215 manifest_file: ?std.Io.File,
216 zig_exe: ?std.Io.File,
217 lib_dir: ?std.Io.Dir,
218 src_dir: ?std.Io.Dir,
219 targets: std.ArrayList(u32),
220 target_bytes: std.ArrayList(u8),
221
222 fn deinit(args: *Args, gpa: std.mem.Allocator) void {
223 args.targets.deinit(gpa);
224 args.target_bytes.deinit(gpa);
225 }
226
227 fn addTarget(args: *Args, gpa: std.mem.Allocator, target: []const u8) std.mem.Allocator.Error!void {
228 try args.target_bytes.ensureUnusedCapacity(gpa, target.len + 1);
229 try args.targets.append(gpa, @intCast(args.target_bytes.items.len));
230 args.target_bytes.appendSliceAssumeCapacity(target);
231 args.target_bytes.appendAssumeCapacity(0);
232 }
233};
234pub fn protocol(gpa: std.mem.Allocator, arena_impl: *std.heap.ArenaAllocator, io: std.Io) !void {
235 const prog_node = std.Progress.start(io, .{});
236 defer prog_node.end();
237
238 var stdin_buffer: [512]u8 = undefined;
239 var stdout_buffer: [512]u8 = undefined;
240 var stdin: std.Io.File.Reader = .initStreaming(.stdin(), io, &stdin_buffer);
241 var stdout: std.Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buffer);
242 var server: std.zig.Server = .{
243 .in = &stdin.interface,
244 .out = &stdout.interface,
245 };
246
247 var args: Args = .{
248 .@"test" = null,
249 .manifest_file = null,
250 .zig_exe = null,
251 .lib_dir = null,
252 .src_dir = null,
253 .targets = .empty,
254 .target_bytes = .empty,
255 };
256 defer args.deinit(gpa);
257
258 try server.serveStringMessage(.zig_version, @import("builtin").zig_version_string);
259 while (true) {
260 _ = arena_impl.reset(.retain_capacity);
261 const arena = arena_impl.allocator();
262 const hdr = try server.receiveMessage();
263 switch (hdr.tag) {
264 else => {
265 std.debug.print("unsupported message: {t}\n", .{hdr.tag});
266 std.process.exit(1);
267 },
268 .exit => std.process.exit(0),
269 .args => {
270 const args_body = try arena.alloc(u8, hdr.bytes_len);
271 try server.in.readSliceAll(args_body);
272 var state: enum { positional, zig, lib, src, target } = .positional;
273 var input_dir: std.zig.Server.Message.InputDir = .cwd;
274 var args_body_offset: usize = 0;
275 while (args_body.len - args_body_offset > 0) {
276 const arg: std.zig.Client.Message.Arg =
277 @fromBackingInt(args_body[args_body_offset]);
278 args_body_offset += 1;
279 arg: switch (arg) {
280 .string => {
281 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
282 const string = args_body[args_body_offset..end];
283 args_body_offset = end + 1;
284 switch (state) {
285 .positional => if (std.mem.eql(u8, string, "--target")) {
286 state = .target;
287 continue;
288 } else if (std.mem.cutPrefix(u8, string, "--target=")) |target| {
289 try args.addTarget(gpa, target);
290 },
291 .zig => unreachable,
292 .lib => unreachable,
293 .src => unreachable,
294 .target => try args.addTarget(gpa, string),
295 }
296 },
297 .prefix => {
298 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
299 const string = args_body[args_body_offset..end];
300 args_body_offset = end + 1;
301 state = if (std.mem.eql(u8, string, "--zig="))
302 .zig
303 else if (std.mem.eql(u8, string, "--lib="))
304 .lib
305 else if (std.mem.eql(u8, string, "--src="))
306 .src
307 else
308 unreachable;
309 continue;
310 },
311 .suffix => unreachable,
312 .input_dir => {
313 input_dir = @fromBackingInt(@backingInt(input_dir) + 1);
314 continue :arg .output_dir;
315 },
316 .output_dir => {
317 const dir_handle: *align(1) const std.Io.Dir.Handle = @ptrCast(
318 args_body[args_body_offset..][0..@sizeOf(std.Io.Dir.Handle)],
319 );
320 args_body_offset += @sizeOf(std.Io.Dir.Handle);
321 const dir: std.Io.Dir = .{
322 .handle = dir_handle.*,
323 };
324 switch (state) {
325 .positional => {
326 std.debug.assert(args.@"test" == null);
327 args.@"test" = .{ .input_dir = input_dir, .dir = dir };
328 std.debug.assert(args.manifest_file == null);
329 const manifest_path = "manifest";
330 args.manifest_file = try dir.openFile(io, manifest_path, .{});
331
332 try server.serveMessageHeader(.{
333 .tag = .discovered_inputs,
334 .bytes_len = @sizeOf(std.zig.Server.Message.InputDir) +
335 manifest_path.len + 1,
336 });
337 try server.out.writeInt(u32, @backingInt(input_dir), .little);
338 try server.out.writeAll(manifest_path);
339 try server.out.writeByte(0);
340 try server.out.flush();
341 },
342 .zig => unreachable,
343 .lib => {
344 std.debug.assert(args.lib_dir == null);
345 args.lib_dir = dir;
346 },
347 .src => {
348 std.debug.assert(args.src_dir == null);
349 args.src_dir = dir;
350 },
351 .target => unreachable,
352 }
353 },
354 .input_file, .input_file_content, .output_file => {
355 const file_handle: *align(1) const std.Io.File.Handle = @ptrCast(
356 args_body[args_body_offset..][0..@sizeOf(std.Io.File.Handle)],
357 );
358 args_body_offset += @sizeOf(std.Io.File.Handle);
359 const file: std.Io.File = .{
360 .handle = file_handle.*,
361 .flags = .{ .nonblocking = false },
362 };
363 switch (state) {
364 .positional => {
365 std.debug.assert(args.manifest_file == null);
366 args.manifest_file = file;
367 },
368 .zig => {
369 std.debug.assert(args.zig_exe == null);
370 args.zig_exe = file;
371 },
372 .lib => unreachable,
373 .src => unreachable,
374 .target => unreachable,
375 }
376 },
377 }
378 state = .positional;
379 }
380 },
381 .query_test_metadata => {
382 const expected_panic_msgs = try arena.alloc(u32, args.targets.items.len);
383 @memset(expected_panic_msgs, 0);
384 try server.serveTestMetadata(.{
385 .names = args.targets.items,
386 .expected_panic_msgs = expected_panic_msgs,
387 .string_bytes = args.target_bytes.items,
388 });
389 },
390 .run_test => {
391 const test_index = try server.receiveBody_u32();
392 const target_query =
393 std.mem.sliceTo(args.target_bytes.items[args.targets.items[test_index]..], 0);
394 try server.serveBodylessMessage(.test_started);
395 const test_result =
396 runTest(gpa, arena, io, prog_node, &server, &args, target_query);
397 try server.serveTestResults(.{
398 .index = test_index,
399 .flags = .{
400 .status = if (test_result) .pass else |err| switch (err) {
401 else => status: {
402 std.debug.print("{t}\n", .{err});
403 if (@errorReturnTrace()) |trace| std.debug.dumpErrorReturnTrace(trace);
404 break :status .fail;
405 },
406 error.SkipZigTest => .skip,
407 },
408 .fuzz = false,
409 .log_err_count = 0,
410 .leak_count = 0,
411 },
412 });
413 },
414 }
415 }
416}
417fn runTest(
418 gpa: std.mem.Allocator,
419 arena: std.mem.Allocator,
420 io: std.Io,
421 prog_node: std.Progress.Node,
422 server: *std.zig.Server,
423 args: *const Args,
424 target_string: []const u8,
425) !void {
426 const manifest_file = args.manifest_file orelse return error.MissingManifestArg;
427 const src_dir =
428 try (args.src_dir orelse return error.MissingSrcDir).createDirPathOpen(io, target_string, .{});
429 const target: Compiler.Target = target: {
430 const backend_split = std.mem.findScalarLast(u8, target_string, '-') orelse
431 return error.TargetMissingBackend;
432 const mode_split =
433 std.mem.findScalarLast(u8, target_string[0..backend_split], '-') orelse
434 return error.TargetMissingMode;
435 const triple = target_string[0..mode_split];
436 const backend = std.meta.stringToEnum(
437 Compiler.Target.Backend,
438 target_string[backend_split + 1 ..],
439 ) orelse return error.TargetMissingBackend;
440 const mode = std.meta.stringToEnum(
441 Compiler.Target.Mode,
442 target_string[mode_split + 1 .. backend_split],
443 ) orelse return error.TargetMissingMode;
444 break :target .{
445 .triple = triple,
446 .resolved = try std.zig.system.resolveTargetQuery(io, try std.Build.parseTargetQuery(.{
447 .arch_os_abi = triple,
448 .object_format = switch (backend) {
449 .sema, .selfhosted, .llvm => null,
450 .cbe => "c",
451 },
452 })),
453 .mode = mode,
454 .backend = backend,
455 };
456 };
457 var manifest_buffer: [512]u8 = undefined;
458 var manifest_fr = manifest_file.reader(io, &manifest_buffer);
459 var allow_skip = true;
460 var skip_delimiter = false;
461 var compiler: ?Compiler = null;
462 var update_mtime = std.Io.Clock.real.now(io);
463 var update_num: usize = 0;
464 while (manifest_fr.interface.takeSentinel('\n')) |untrimmed_line| {
465 const line = std.mem.trimEnd(u8, untrimmed_line, "\r");
466 var line_it = std.mem.tokenizeScalar(u8, line, ' ');
467 const cmd_str = std.mem.cutPrefix(u8, line_it.next() orelse continue, "#").?;
468 if (skip_delimiter) {
469 std.debug.assert(std.mem.eql(u8, cmd_str, "}"));
470 skip_delimiter = false;
471 continue;
472 }
473 const cmd = std.meta.stringToEnum(
474 enum { todo, skip, exe, lib, obj, write, delete, update, check },
475 cmd_str,
476 ) orelse return error.UnknownCommand;
477 var maybe_arg = line_it.next();
478 var contents_file: ?std.Io.File = null;
479 defer if (contents_file) |file| file.close(io);
480 var contents_buffer: [512]u8 = undefined;
481 var contents_impl: union {
482 dr: DelimitedReader,
483 fr: std.Io.File.Reader,
484 } = undefined;
485 const contents_r: *std.Io.Reader = contents_r: switch (cmd) {
486 .skip, .delete => {
487 if (line_it.next()) |_| return error.UnexpectedArg;
488 break :contents_r .ending;
489 },
490 else => {
491 const contents_path = contents_path: {
492 const line_rest = line_it.rest();
493 if (line_rest.len > 0) break :contents_path line_rest;
494 const contents_path = maybe_arg orelse break :contents_r .ending;
495 maybe_arg = null;
496 break :contents_path contents_path;
497 };
498 if (std.mem.eql(u8, contents_path, "{")) {
499 contents_impl = .{ .dr = .init(&manifest_fr.interface, "#}", &contents_buffer) };
500 skip_delimiter = true;
501 break :contents_r &contents_impl.dr.interface;
502 }
503
504 const @"test" = args.@"test" orelse return error.ContentsFileInFileTest;
505 try server.serveMessageHeader(.{
506 .tag = .discovered_inputs,
507 .bytes_len = @intCast(@sizeOf(std.zig.Server.Message.InputDir) +
508 contents_path.len + 1),
509 });
510 try server.out.writeInt(u32, @backingInt(@"test".input_dir), .little);
511 try server.out.writeAll(contents_path);
512 try server.out.writeByte(0);
513 try server.out.flush();
514
515 contents_file = try @"test".dir.openFile(io, contents_path, .{});
516 contents_impl = .{ .fr = contents_file.?.reader(io, &contents_buffer) };
517 break :contents_r &contents_impl.fr.interface;
518 },
519 };
520 const cmd_prog_node = cmd_prog_node: {
521 var cmd_name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
522 break :cmd_prog_node prog_node.start(std.mem.print(&cmd_name_buffer, "#{t} {s}", .{
523 cmd, maybe_arg orelse "",
524 }) catch &cmd_name_buffer, 0);
525 };
526 defer cmd_prog_node.end();
527 cmd: switch (cmd) {
528 .todo => _ = try contents_r.discardRemaining(),
529 .skip => if (!allow_skip)
530 return error.NonInitialSkipCommand // #skip must appear first
531 else if (std.mem.eql(u8, target_string, maybe_arg orelse return error.MissingArg))
532 return error.SkipZigTest,
533 .exe, .lib, .obj => {
534 if (compiler) |*comp| {
535 try comp.exit(io);
536 compiler = null;
537 }
538 var argv: std.ArrayList([]const u8) = .empty;
539 defer argv.deinit(gpa);
540 try argv.appendSlice(gpa, &.{
541 "zig",
542 switch (cmd) {
543 else => unreachable,
544 .obj => "build-obj",
545 .exe => "build-exe",
546 .lib => "build-lib",
547 },
548 "-target",
549 target.triple,
550 "--cache-dir",
551 ".zig-cache",
552 });
553 switch (target.mode) {
554 .whole => {},
555 .incremental => try argv.append(gpa, "-fincremental"),
556 }
557 switch (target.backend) {
558 .sema => try argv.append(gpa, "-fno-emit-bin"),
559 .selfhosted => try argv.append(gpa, "-fno-llvm"),
560 .llvm => try argv.append(gpa, "-fllvm"),
561 .cbe => try argv.append(gpa, "-ofmt=c"),
562 }
563 try argv.append(gpa, "--listen=-");
564 var need: usize = 1;
565 var root_name: ?[]const u8 = null;
566 while (contents_r.peekGreedy(need)) |buffered| {
567 const start = std.mem.findNone(u8, buffered, "\n ") orelse buffered.len;
568 if (std.mem.findAnyPos(u8, buffered, start, "\n ")) |end| {
569 const arg = try arena.dupe(u8, buffered[start..end]);
570 if (std.mem.cutPrefix(u8, arg, "-M")) |module| {
571 var module_name_it = std.mem.splitScalar(u8, module, '=');
572 const module_name = module_name_it.next().?;
573 root_name = root_name orelse module_name;
574 }
575 try argv.append(gpa, arg);
576 contents_r.toss(end);
577 need = 1;
578 } else {
579 contents_r.toss(start);
580 need = buffered.len - start + 1;
581 }
582 } else |err| switch (err) {
583 else => |e| return e,
584 error.EndOfStream => {},
585 }
586 const compile_prog_node = prog_node.start("initial update", 0);
587 const child = try std.process.spawn(io, .{
588 .exe = .{ .file = args.zig_exe orelse return error.MissingZigExe },
589 .argv = argv.items,
590 .cwd = .{ .dir = src_dir },
591 .progress_node = compile_prog_node,
592 .stdin = .pipe,
593 .stdout = .pipe,
594 .stderr = .pipe,
595 });
596 compiler = .{
597 .prog_node = compile_prog_node,
598 .allow_compiler_stderr = true,
599 .output_mode = switch (cmd) {
600 else => unreachable,
601 .exe => .Exe,
602 .lib => .Lib,
603 .obj => .Obj,
604 },
605 .root_name = root_name orelse return error.MissingRootModule,
606 .state = .idle,
607 .child = child,
608 .fw = undefined,
609 .mr_buffer = undefined,
610 .mr = undefined,
611 .client = undefined,
612 };
613 const comp = &compiler.?;
614 comp.fw = child.stdin.?.writerStreaming(io, &.{});
615 comp.mr.init(gpa, io, comp.mr_buffer.toStreams(), &.{
616 child.stdout.?, child.stderr.?,
617 });
618 comp.client = .{ .in = comp.mr.reader(0), .out = &comp.fw.interface };
619 },
620 .write => {
621 const file =
622 try src_dir.createFile(io, maybe_arg orelse return error.MissingFileArg, .{});
623 defer file.close(io);
624 var fw = file.writer(io, &.{});
625 _ = try contents_r.streamRemaining(&fw.interface);
626 try file.setTimestamps(io, .{ .modify_timestamp = .{ .new = update_mtime } });
627 },
628 .delete => try src_dir.deleteFile(io, maybe_arg orelse return error.MissingFileArg),
629 .update => {
630 const comp = &(compiler orelse return error.MissingCompiler);
631 name: {
632 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
633 comp.prog_node.setName(switch (try contents_r.readSliceShort(&name_buffer)) {
634 0 => switch (update_num) {
635 0 => break :name,
636 else => |num| std.mem.print(&name_buffer, "update {d}", .{num}) catch
637 &name_buffer,
638 },
639 else => |name_len| name_buffer[0..name_len],
640 });
641 }
642 _ = try contents_r.discardRemaining();
643 if (comp.state != .idle) return error.MissingCheck;
644 try comp.client.serveBodylessMessage(.update);
645 comp.state = .update;
646 update_mtime = update_mtime.addDuration(.fromSeconds(2));
647 update_num += 1;
648 },
649 .check => {
650 const check = std.meta.stringToEnum(
651 enum { errors, stdout, exit, lldb },
652 maybe_arg orelse return error.MissingCheckArg,
653 ) orelse return error.UnknownCheck;
654 const expected = try contents_r.allocRemaining(gpa, .unlimited);
655 defer gpa.free(expected);
656
657 const comp = &(compiler orelse return error.MissingCompiler);
658 if (comp.state != .update) return error.MissingUpdate;
659 const stderr = comp.mr.reader(1);
660 while (true) {
661 const header = comp.client.receiveMessageWithMultiReader(
662 &comp.mr,
663 .none,
664 ) catch |err| switch (err) {
665 error.Timeout => unreachable,
666 error.EndOfStream => break,
667 else => |e| return e,
668 };
669 const body = comp.client.in.take(header.bytes_len) catch unreachable;
670 switch (header.tag) {
671 .emit_digest => {
672 var r: std.Io.Reader = .fixed(body);
673 _ = r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch
674 unreachable;
675
676 if (stderr.bufferedLen() > 0) {
677 if (comp.allow_compiler_stderr) {
678 std.log.info("emit_digest stderr:\n{s}", .{stderr.buffered()});
679 } else {
680 fatal("emit_digest unexpected stderr:\n{s}", .{stderr.buffered()});
681 }
682 stderr.tossBuffered();
683 }
684 switch (target.backend) {
685 .sema => continue,
686 .selfhosted, .llvm, .cbe => {},
687 }
688 switch (check) {
689 .errors => continue,
690 .stdout, .exit, .lldb => {},
691 }
692 const digest = r.takeArray(std.Build.Cache.bin_digest_len) catch
693 unreachable;
694 const bin_dir = ".zig-cache" ++ std.Io.Dir.path.sep_str ++
695 "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*);
696 const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{
697 .root_name = comp.root_name,
698 .cpu_arch = target.resolved.cpu.arch,
699 .os_tag = target.resolved.os.tag,
700 .ofmt = target.resolved.ofmt,
701 .abi = target.resolved.abi,
702 .output_mode = comp.output_mode,
703 });
704 const bin_path =
705 try std.Io.Dir.path.join(arena, &.{ bin_dir, bin_name });
706 const bin_file = try src_dir.openFile(io, bin_path, .{});
707 defer bin_file.close(io);
708 switch (check) {
709 .errors => unreachable,
710 .stdout, .exit => {
711 const result = std.process.run(gpa, io, .{
712 .exe = .{ .file = bin_file },
713 .argv = &.{bin_path},
714 }) catch continue;
715 defer {
716 gpa.free(result.stdout);
717 gpa.free(result.stderr);
718 }
719 switch (result.term) {
720 .exited => |code| switch (check) {
721 .errors, .lldb => unreachable,
722 .stdout => {
723 if (code != 0) fatal("generated executable '{s}' failed with code {d}", .{ bin_path, code });
724 try std.testing.expectEqualStrings(expected, result.stdout);
725 },
726 .exit => {
727 var actual_code_buffer: [std.fmt.count("{d}", .{std.math.maxInt(u8)})]u8 = undefined;
728 try std.testing.expectEqualStrings(
729 expected,
730 std.mem.print(&actual_code_buffer, "{d}", .{code}) catch unreachable,
731 );
732 },
733 },
734 .signal => |sig| fatal("generated executable '{s}' terminated with signal {t}", .{ bin_path, sig }),
735 .stopped => |sig| fatal("generated executable '{s}' stopped with signal {t}", .{ bin_path, sig }),
736 .unknown => fatal("generated executable '{s}' terminated unexpectedly", .{bin_path}),
737 }
738 },
739 .lldb => {},
740 }
741 },
742 .error_bundle => {
743 const error_bundle = try std.zig.Server.allocErrorBundle(arena, body);
744 if (stderr.bufferedLen() > 0) {
745 if (comp.allow_compiler_stderr) {
746 std.log.info("error_bundle stderr:\n{s}", .{stderr.buffered()});
747 } else {
748 fatal("error_bundle unexpected stderr:\n{s}", .{stderr.buffered()});
749 }
750 stderr.tossBuffered();
751 }
752 switch (check) {
753 .errors => {
754 var error_aw: std.Io.Writer.Allocating = .init(gpa);
755 defer error_aw.deinit();
756 try error_bundle.renderToWriter(.{
757 .include_reference_trace = false,
758 .include_source_line = false,
759 .include_log_text = true,
760 }, &error_aw.writer);
761 try std.testing.expectEqualStrings(expected, error_aw.written());
762 },
763 .stdout, .exit, .lldb => if (error_bundle.errorMessageCount() != 0) {
764 try error_bundle.renderToStderr(io, .{}, .auto);
765 fatal("unexpected compile errors", .{});
766 },
767 }
768 comp.state = .idle;
769 break :cmd;
770 },
771 else => {}, // Ignore other messages,
772 }
773 }
774
775 const buffered_stderr = stderr.buffered();
776 if (buffered_stderr.len > 0) {
777 if (comp.allow_compiler_stderr) {
778 std.log.info("stderr:\n{s}", .{buffered_stderr});
779 } else {
780 fatal("unexpected stderr:\n{s}", .{buffered_stderr});
781 }
782 }
783
784 try comp.exit(io);
785 fatal("compiler failed to send terminating error_bundle", .{});
786 },
787 }
788 std.debug.assert(try contents_r.discardRemaining() == 0);
789 switch (cmd) {
790 .todo, .skip => {},
791 else => allow_skip = false,
792 }
793 } else |err| switch (err) {
794 else => |e| return e,
795 error.EndOfStream => if (skip_delimiter) return error.MissingDelimiter,
796 }
797 if (compiler) |*comp| {
798 if (comp.state != .idle) return error.MissingCheck;
799 try comp.exit(io);
800 }
801}
802const DelimitedReader = struct {
803 const Io = std.Io;
804
805 unlimited: *Io.Reader,
806 remaining: Io.Limit,
807 delimiter: []const u8,
808 interface: Io.Reader,
809
810 pub fn init(reader: *Io.Reader, delimiter: []const u8, buffer: []u8) DelimitedReader {
811 return .{
812 .unlimited = reader,
813 .remaining = .nothing,
814 .delimiter = delimiter,
815 .interface = .{
816 .vtable = &.{
817 .stream = stream,
818 .discard = discard,
819 },
820 .buffer = buffer,
821 .seek = 0,
822 .end = 0,
823 },
824 };
825 }
826 fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
827 const d: *DelimitedReader = @fieldParentPtr("interface", r);
828 const block = try d.unlimited.peekGreedy(d.delimiter.len);
829 d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse
830 block.len - d.delimiter.len + 1);
831 if (d.remaining == .nothing) return error.EndOfStream;
832 const n = try d.unlimited.stream(w, limit.min(d.remaining));
833 d.remaining = d.remaining.subtract(n).?;
834 return n;
835 }
836 fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
837 const d: *DelimitedReader = @fieldParentPtr("interface", r);
838 const block = try d.unlimited.peekGreedy(d.delimiter.len);
839 d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse
840 block.len - d.delimiter.len + 1);
841 if (d.remaining == .nothing) return error.EndOfStream;
842 const n = try d.unlimited.discard(limit.min(d.remaining));
843 d.remaining = d.remaining.subtract(n).?;
844 return n;
845 }
846};
847const Compiler = struct {
848 prog_node: std.Progress.Node,
849 allow_compiler_stderr: bool,
850 output_mode: std.lang.OutputMode,
851 root_name: []const u8,
852 state: enum { idle, update },
853 child: std.process.Child,
854 fw: std.Io.File.Writer,
855 mr_buffer: std.Io.File.MultiReader.Buffer(2),
856 mr: std.Io.File.MultiReader,
857 client: std.zig.Client,
858
859 const Target = struct {
860 triple: []const u8,
861 resolved: std.Target,
862 mode: Mode,
863 backend: Backend,
864
865 const Mode = enum {
866 whole,
867 incremental,
868 };
869
870 const Backend = enum {
871 /// Run semantic analysis only. Runtime output will not be tested, but we still verify
872 /// that compilation succeeds. Corresponds to `-fno-emit-bin`.
873 sema,
874 /// Use the self-hosted code generation backend for this target.
875 /// Corresponds to `-fno-llvm -fno-lld`.
876 selfhosted,
877 /// Use the LLVM backend.
878 /// Corresponds to `-fllvm -flld`.
879 llvm,
880 /// Use the C backend. The output is compiled with `zig cc`.
881 /// Corresponds to `-ofmt=c`.
882 cbe,
883 };
884 };
885
886 fn exit(comp: *Compiler, io: std.Io) !void {
887 comp.client.serveBodylessMessage(.exit) catch |err| switch (err) {
888 error.WriteFailed => switch (comp.fw.err.?) {
889 error.BrokenPipe => {},
890 else => |e| fatal("failed to send exit: {t}", .{e}),
891 },
892 };
893 comp.child.stdin.?.close(io);
894 comp.child.stdin = null;
895 while (true) {
896 const header = comp.client.receiveMessageWithMultiReader(
897 &comp.mr,
898 .none,
899 ) catch |err| switch (err) {
900 error.Timeout => unreachable,
901 error.EndOfStream => |e| {
902 if (comp.client.in.bufferedLen() == 0) break;
903 return e;
904 },
905 else => |e| return e,
906 };
907 try comp.client.in.discardAll(header.bytes_len);
908 }
909 const term = comp.child.wait(io) catch |err| fatal("child process failed: {t}", .{err});
910 comp.prog_node.end();
911 switch (term) {
912 .exited => |code| if (code != 0) fatal("compiler failed with code {d}", .{code}),
913 .signal => |sig| fatal("compiler terminated with signal {t}", .{sig}),
914 .stopped => |sig| fatal("compiler stopped unexpectedly with signal {t}", .{sig}),
915 .unknown => fatal("compiler terminated unexpectedly", .{}),
916 }
917 }
918};
test/tests.zig+7-5
......@@ -3356,6 +3356,10 @@ pub fn addIncrementalTests(
33563356 defer tests_dir.close(b.graph.io);
33573357 var test_it = tests_dir.iterate();
33583358 while (try test_it.next(b.graph.io)) |@"test"| {
3359 for (options.test_filters) |filter| {
3360 if (std.mem.find(u8, @"test".name, filter) != null) break;
3361 } else if (options.test_filters.len > 0) continue;
3362
33593363 const test_path = tests_path.path(b, @"test".name);
33603364 switch (@"test".kind) {
33613365 else => continue,
......@@ -3400,11 +3404,9 @@ pub fn addIncrementalTests(
34003404 test_target.backend,
34013405 });
34023406
3403 if (options.test_target_filters.len > 0) {
3404 for (options.test_target_filters) |filter| {
3405 if (std.mem.find(u8, target_str, filter) != null) break;
3406 } else continue;
3407 }
3407 for (options.test_target_filters) |filter| {
3408 if (std.mem.find(u8, target_str, filter) != null) break;
3409 } else if (options.test_target_filters.len > 0) continue;
34083410
34093411 run.addArgs(&.{ "--target", target_str });
34103412 }
tools/doctest.zig+1
......@@ -314,6 +314,7 @@ fn printOutput(
314314 switch (getExternalExecutor(io, &target, .{
315315 .host_cpu_arch = host.cpu.arch,
316316 .host_os_tag = host.os.tag,
317 .link_mode = code.link_mode orelse .dynamic,
317318 .link_libc = code.link_libc,
318319 })) {
319320 .native => {},