authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-28 07:13:35+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-28 07:13:35+02:00
logd8aab4878d471222daf448c63c8ae33cbc461bb3
tree0b065775a36f54ad1b838698ebd23284da0d48fd
parentda561521dd78dcde03fc51de8fd64551b368f56d
parent36a308ce42b8dc8c33c733b587152ee533b4bbd4

Merge pull request 'Rewrite incr-check into a more general purpose compiler test runner' (#36967) from compiler-test-runner into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/36967 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

118 files changed, 5873 insertions(+), 2989 deletions(-)

build.zig+17-13
......@@ -125,7 +125,6 @@ pub fn build(b: *std.Build) !void {
125125 const skip_darwin = b.option(bool, "skip-darwin", "Main test suite skips targets with darwin OSs") orelse false;
126126 const skip_linux = b.option(bool, "skip-linux", "Main test suite skips targets with linux OS") orelse false;
127127 const skip_llvm = b.option(bool, "skip-llvm", "Main test suite skips targets that use LLVM backend") orelse false;
128 const skip_test_incremental = b.option(bool, "skip-test-incremental", "Main test step omits dependency on test-incremental step") orelse false;
129128
130129 const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false;
131130
......@@ -781,8 +780,22 @@ pub fn build(b: *std.Build) !void {
781780 test_step.dependOn(check_oracle_step);
782781 }
783782
784 const test_incremental_step = b.step("test-incremental", "Run the incremental compilation test cases");
785 try tests.addIncrementalTests(b, test_incremental_step, .{
783 if (tests.addLibcTestNszTests(b, .{
784 .optimize_modes = optimize_modes,
785 .test_filters = test_filters,
786 .test_target_filters = test_target_filters,
787 .skip_wasm = skip_wasm,
788 .max_rss = 4_300_000_000,
789 })) |test_libc_nsz_step| test_step.dependOn(test_libc_nsz_step);
790
791 const runner = b.addExecutable(.{
792 .name = "runner",
793 .root_module = b.createModule(.{
794 .root_source_file = b.path("test/src/Runner.zig"),
795 .target = b.graph.host,
796 }),
797 });
798 test_step.dependOn(try tests.addIncrementalTests(b, runner, .{
786799 .test_filters = test_filters,
787800 .test_target_filters = test_target_filters,
788801 .skip_non_native = skip_non_native,
......@@ -794,16 +807,7 @@ pub fn build(b: *std.Build) !void {
794807 .skip_darwin = skip_darwin,
795808 .skip_linux = skip_linux,
796809 .skip_llvm = skip_llvm,
797 });
798 if (!skip_test_incremental) test_step.dependOn(test_incremental_step);
799
800 if (tests.addLibcTestNszTests(b, .{
801 .optimize_modes = optimize_modes,
802 .test_filters = test_filters,
803 .test_target_filters = test_target_filters,
804 .skip_wasm = skip_wasm,
805 .max_rss = 4_300_000_000,
806 })) |test_libc_nsz_step| test_step.dependOn(test_libc_nsz_step);
810 }));
807811}
808812
809813fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
lib/compiler/Maker.zig+27-13
......@@ -3631,7 +3631,7 @@ pub fn resolveLazyPath(
36313631 const c = &maker.scanned_config.configuration;
36323632 return switch (lazy_path) {
36333633 .source_path => |sp| try packagePath(maker, arena, sp.owner, sp.sub_path.slice(c)),
3634 .relative => |relative| relativePath(maker, arena, relative),
3634 .relative => |relative| try relativePath(maker, arena, relative, stepByIndex(maker, asking_step_index)),
36353635 .generated => |gen| {
36363636 const base = generatedPath(maker, gen.index);
36373637 var file_path = base;
......@@ -3768,7 +3768,12 @@ pub fn packagePath(
37683768 };
37693769}
37703770
3771pub fn relativePath(maker: *const Maker, arena: Allocator, relative: Configuration.LazyPath.Relative) Allocator.Error!Path {
3771pub fn relativePath(
3772 maker: *const Maker,
3773 arena: Allocator,
3774 relative: Configuration.LazyPath.Relative,
3775 asking_step: *Step,
3776) error{ OutOfMemory, MakeFailed }!Path {
37723777 const graph = maker.graph;
37733778 const c = &maker.scanned_config.configuration;
37743779 const sub_path = relative.sub_path.slice(c);
......@@ -3789,12 +3794,12 @@ pub fn relativePath(maker: *const Maker, arena: Allocator, relative: Configurati
37893794 .root_dir = graph.build_root_directory,
37903795 .sub_path = sub_path,
37913796 },
3792 .zig_exe => .{
3793 .root_dir = .cwd(),
3794 .sub_path = if (sub_path.len == 0)
3795 graph.zig_exe
3796 else
3797 try Io.Dir.path.join(arena, &.{ graph.zig_exe, sub_path }),
3797 .zig_exe => {
3798 assert(sub_path.len == 0);
3799 return .{
3800 .root_dir = .cwd(),
3801 .sub_path = graph.zig_exe,
3802 };
37983803 },
37993804 .zig_lib => .{
38003805 .root_dir = graph.zig_lib_directory,
......@@ -3804,6 +3809,10 @@ pub fn relativePath(maker: *const Maker, arena: Allocator, relative: Configurati
38043809 .install_lib => try maker.install_paths.lib.join(arena, sub_path),
38053810 .install_bin => try maker.install_paths.bin.join(arena, sub_path),
38063811 .install_include => try maker.install_paths.include.join(arena, sub_path),
3812 .libc_runtimes => if (maker.graph.libc_runtimes_dir) |libc_runtimes_dir| .{
3813 .root_dir = .cwd(),
3814 .sub_path = try Dir.path.join(arena, &.{ libc_runtimes_dir, sub_path }),
3815 } else asking_step.fail(maker, "unknown LazyPath: \"--libc-runtimes\" not specified", .{}),
38073816 };
38083817}
38093818
......@@ -4125,8 +4134,7 @@ const Fork = struct {
41254134 fork.arena_allocator = .init(gpa);
41264135 const arena = fork.arena_allocator.allocator();
41274136
4128 var error_bundle: std.zig.ErrorBundle.Wip = undefined;
4129 try error_bundle.init(gpa);
4137 var error_bundle: std.zig.ErrorBundle.Wip = try .init(gpa);
41304138 defer error_bundle.deinit();
41314139
41324140 const manifest_path = try fork.path.join(arena, Package.Manifest.basename);
......@@ -4227,8 +4235,7 @@ fn loadManifest(
42274235 errdefer manifest.deinit(gpa);
42284236
42294237 if (manifest.errors.len > 0) {
4230 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
4231 try wip_errors.init(gpa);
4238 var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa);
42324239 defer wip_errors.deinit();
42334240
42344241 const src_path = try wip_errors.addString(Package.Manifest.basename);
......@@ -4393,15 +4400,22 @@ fn confPathDepToCachePath(
43934400 else => |index| try Dir.path.join(arena, &.{ index.get(c).?.root_path.slice(c), sub_path }),
43944401 },
43954402 },
4403 .zig_exe => {
4404 assert(sub_path.len == 0);
4405 return .{
4406 .root_dir = .cwd(),
4407 .sub_path = graph.zig_exe,
4408 };
4409 },
43964410 .zig_lib => .{
43974411 .root_dir = graph.zig_lib_directory,
43984412 .sub_path = sub_path,
43994413 },
4400 .zig_exe => @panic("TODO"),
44014414 .install_prefix => @panic("TODO"),
44024415 .install_lib => @panic("TODO"),
44034416 .install_bin => @panic("TODO"),
44044417 .install_include => @panic("TODO"),
4418 .libc_runtimes => @panic("TODO"),
44054419 };
44064420}
44074421
lib/compiler/Maker/Fetch.zig+1-1
......@@ -527,7 +527,7 @@ pub fn run(f: *Fetch) RunError!void {
527527 const arena = f.arena.allocator();
528528 const gpa = f.arena.child_allocator;
529529
530 try eb.init(gpa);
530 eb.* = try .init(gpa);
531531
532532 // Check the global zig package cache to see if the hash already exists. If
533533 // so, load, parse, and validate the build.zig.zon file therein, and skip
lib/compiler/Maker/Step.zig+21-12
......@@ -270,7 +270,10 @@ pub fn make(
270270 .compile => break :t null,
271271 .run => {
272272 const run_flags: Configuration.Step.Run.Flags = @bitCast(flags);
273 if (run_flags.stdio == .zig_test) break :t null;
273 switch (run_flags.stdio) {
274 .infer_from_args, .inherit, .check => {},
275 .zig_test, .protocol => break :t null,
276 }
274277 },
275278 else => {},
276279 }
......@@ -621,6 +624,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi
621624 else => |e| return e,
622625 };
623626 const body = client.in.take(header.bytes_len) catch unreachable;
627 var body_r: std.Io.Reader = .fixed(body);
624628
625629 switch (header.tag) {
626630 .zig_version => {
......@@ -632,16 +636,22 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi
632636 );
633637 }
634638 },
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 },
635645 .error_bundle => {
636646 s.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body);
637647 // This message indicates the end of the update.
638648 if (watch) break;
639649 },
640650 .emit_digest => {
641 const EmitDigest = std.zig.Server.Message.EmitDigest;
642 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;
643653 s.result_cached = emit_digest.flags.cache_hit;
644 result = .{ .bin = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len].* };
654 result = .{ .bin = digest.* };
645655 },
646656 .file_system_inputs => {
647657 clearWatchInputs(s, maker);
......@@ -701,8 +711,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi
701711 }
702712 },
703713 .time_report => if (maker.web_server) |ws| {
704 const TimeReport = std.zig.Server.Message.TimeReport;
705 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;
706715 ws.updateTimeReportCompile(.{
707716 .compile_step = step_index,
708717 .use_llvm = tr.flags.use_llvm,
......@@ -711,7 +720,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi
711720 .llvm_pass_timings_len = tr.llvm_pass_timings_len,
712721 .files_len = tr.files_len,
713722 .decls_len = tr.decls_len,
714 .trailing = body[@sizeOf(TimeReport)..],
723 .trailing = body_r.buffered(),
715724 });
716725 },
717726 else => {}, // ignore other messages
......@@ -848,7 +857,7 @@ pub fn setWatchInputsFromManifestFiles(
848857}
849858
850859/// For steps that have a single input that never changes when re-running `make`.
851pub fn singleUnchangingWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_path: LazyPath) Allocator.Error!void {
860pub fn singleUnchangingWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_path: LazyPath) FailError!void {
852861 if (!step.inputs.populated()) try step.addWatchInput(maker, arena, lazy_path);
853862}
854863
......@@ -857,7 +866,7 @@ pub fn clearWatchInputs(step: *Step, maker: *Maker) void {
857866}
858867
859868/// Places a *file* dependency on the path.
860pub fn addWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_file: LazyPath) Allocator.Error!void {
869pub fn addWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_file: LazyPath) FailError!void {
861870 const conf = &maker.scanned_config.configuration;
862871 switch (lazy_file) {
863872 .source_path => |source_path| {
......@@ -866,7 +875,7 @@ pub fn addWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_file: La
866875 try addWatchInputPath(step, maker, pkg_path);
867876 },
868877 .relative => |relative| {
869 const resolved_path = try maker.relativePath(arena, relative);
878 const resolved_path = try maker.relativePath(arena, relative, step);
870879 try addWatchInputPath(step, maker, resolved_path);
871880 },
872881 // Nothing to watch because this dependency edge is modeled instead via `dependants`.
......@@ -881,7 +890,7 @@ pub fn addWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_file: La
881890/// Paths derived from this directory should also be manually added via
882891/// `addDirectoryWatchInputFromPath` if and only if this function returns
883892/// `true`.
884pub fn addDirectoryWatchInput(step: *Step, maker: *Maker, lazy_directory: LazyPath) Allocator.Error!bool {
893pub fn addDirectoryWatchInput(step: *Step, maker: *Maker, lazy_directory: LazyPath) FailError!bool {
885894 const graph = maker.graph;
886895 const arena = graph.arena; // TODO don't leak into the process arena
887896 switch (lazy_directory) {
......@@ -892,7 +901,7 @@ pub fn addDirectoryWatchInput(step: *Step, maker: *Maker, lazy_directory: LazyPa
892901 try addDirectoryWatchInputFromPath(step, maker, pkg_path);
893902 },
894903 .relative => |relative| {
895 const resolved_path = try maker.relativePath(arena, relative);
904 const resolved_path = try maker.relativePath(arena, relative, step);
896905 try addDirectoryWatchInputFromPath(step, maker, resolved_path);
897906 },
898907 // Nothing to watch because this dependency edge is modeled instead via `dependants`.
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+821-326
......@@ -55,237 +55,598 @@ pub fn make(
5555 var man = graph.cache.obtain();
5656 defer man.deinit();
5757
58 if (conf_run.environ_map.value) |environ_map_index| {
59 const environ_map = environ_map_index.get(conf);
60 for (environ_map.keys.slice(conf), environ_map.values.slice(conf)) |key, value| {
61 man.hash.addBytesZ(key.slice(conf));
62 man.hash.addBytesZ(value.slice(conf));
58 var tmp_dir_path: ["tmp".len + Dir.path.sep_str.len + std.fmt.hex(@as(u64, 0)).len]u8 = undefined;
59
60 const has_side_effects = has_side_effects: {
61 if (conf_run.environ_map.value) |environ_map_index| {
62 const environ_map = environ_map_index.get(conf);
63 for (environ_map.keys.slice(conf), environ_map.values.slice(conf)) |key, value| {
64 man.hash.addBytesZ(key.slice(conf));
65 man.hash.addBytesZ(value.slice(conf));
66 }
6367 }
64 }
6568
66 for (conf_run.preopens.slice) |preopen| {
67 man.hash.addBytesZ(preopen.name.slice(conf));
68 const cwd_path = try maker.resolveLazyPathIndex(arena, preopen.path, run_index);
69 man.hash.addBytes(try cwd_path.toString(arena));
70 }
69 for (conf_run.preopens.slice) |preopen| {
70 man.hash.addBytesZ(preopen.name.slice(conf));
71 const cwd_path = try maker.resolveLazyPathIndex(arena, preopen.path, run_index);
72 man.hash.addBytes(try cwd_path.toString(arena));
73 }
7174
72 man.hash.add(graph.fuzzing);
73 man.hash.add(conf_run.flags.color);
74 man.hash.add(conf_run.flags.disable_zig_progress);
75 man.hash.add(graph.fuzzing);
76 man.hash.add(conf_run.flags.color);
77 man.hash.add(conf_run.flags.disable_zig_progress);
7578
76 var any_dep_files = false;
77 var any_output_args = false;
78 var any_cli_positionals = false;
79 var any_discovered_inputs = switch (conf_run.flags.stdio) {
80 .infer_from_args, .inherit, .check, .zig_test => false,
81 .protocol => true,
82 };
83 var any_output_args = false;
84 var any_cli_positionals = false;
85
86 for (switch (conf_run.flags.stdio) {
87 .infer_from_args, .inherit, .check, .zig_test => conf_run.args.slice,
88 .protocol => conf_run.args.slice[0..1], // arguments communicated over protocol
89 }) |arg_index| {
90 const arg = arg_index.get(conf);
91 try argv_list.ensureUnusedCapacity(gpa, 1);
92 switch (arg.flags.tag) {
93 .string => {
94 const string = arg.prefix.value.?.slice(conf);
95 argv_list.appendAssumeCapacity(string);
96 man.hash.addBytesZ(string);
97 },
98 .path_file => {
99 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
100 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
101 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
102 argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{
103 prefix, try convertPathArg(arena, run_index, maker, file_path, arg.flags.make_absolute), suffix,
104 }));
105 man.hash.add(arg.flags.make_absolute);
106 man.hash.addBytesZ(prefix);
107 man.hash.addBytesZ(suffix);
108 _ = try man.addInputPath(file_path, .{});
109 },
110 .path_directory => {
111 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
112 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
113 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
114 const resolved_arg = try mem.concat(arena, u8, &.{
115 prefix, try convertPathArg(arena, run_index, maker, file_path, arg.flags.make_absolute), suffix,
116 });
117 argv_list.appendAssumeCapacity(resolved_arg);
118 man.hash.add(arg.flags.make_absolute);
119 man.hash.addBytes(resolved_arg);
120 },
121 .file_content => {
122 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
123 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
124 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
125
126 var result: std.Io.Writer.Allocating = .init(arena);
127 result.writer.writeAll(prefix) catch return error.OutOfMemory;
128
129 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|
130 return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err });
131 defer file.close(io);
132
133 var file_reader = file.reader(io, &.{});
134 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
135 error.ReadFailed => switch (file_reader.err.?) {
136 error.Canceled => |e| return e,
137 else => |e| return step.fail(maker, "failed to read from {qf}: {t}", .{ file_path, e }),
138 },
139 error.WriteFailed => return error.OutOfMemory,
140 };
141 result.writer.writeAll(suffix) catch return error.OutOfMemory;
142
143 argv_list.appendAssumeCapacity(result.written());
144 man.hash.addBytesZ(prefix);
145 _ = try man.addInputPath(file_path, .{});
146 man.hash.addBytesZ(suffix);
147 },
148 .artifact => {
149 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
150 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
151 const producer_index = arg.producer.value.?;
152 const producer_step = producer_index.ptr(conf);
153 const producer = producer_step.extended.get(conf.extra).compile;
154 const producer_make_comp_step = maker.stepByIndex(producer_index);
155 const producer_make_comp = &producer_make_comp_step.extended.compile;
156
157 const file_path = producer_make_comp.installed_path orelse
158 maker.generatedPath(producer.generated_bin.value.?);
159
160 argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{
161 prefix, try convertPathArg(arena, run_index, maker, file_path, arg.flags.make_absolute), suffix,
162 }));
163
164 man.hash.add(arg.flags.make_absolute);
165 man.hash.addBytesZ(prefix);
166 man.hash.addBytesZ(suffix);
167 _ = try man.addInputPath(file_path, .{});
168 },
169 .output_file, .output_directory => {
170 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
171 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
172 const basename = arg.basename.value.?.slice(conf);
173
174 man.hash.add(arg.flags.make_absolute);
175 man.hash.addBytesZ(prefix);
176 man.hash.addBytesZ(basename);
177 man.hash.addBytesZ(suffix);
178 man.hash.add(arg.flags.dep_file);
179
180 if (arg.flags.dep_file) any_discovered_inputs = true;
181 any_output_args = true;
182
183 // Add a placeholder into the argument list because we need the
184 // manifest hash to be updated with all arguments before the
185 // object directory is computed.
186 try output_placeholders.append(gpa, .{
187 .index = @intCast(argv_list.items.len),
188 .offset = 0,
189 .arg_index = arg_index,
190 });
191 _ = argv_list.addOneAssumeCapacity();
192 },
193 .passthru => {
194 any_cli_positionals = true;
195 if (maker.run_args) |run_args| {
196 try argv_list.appendSlice(gpa, run_args);
197 man.hash.addListOfBytes(run_args);
198 }
199 },
200 .enable_darling => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_darling, arg.prefix.value, arg.suffix.value),
201 .enable_qemu => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_qemu, arg.prefix.value, arg.suffix.value),
202 .enable_rosetta => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_rosetta, arg.prefix.value, arg.suffix.value),
203 .enable_wasmtime => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_wasmtime, arg.prefix.value, arg.suffix.value),
204 .enable_wine => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value),
205 }
206 }
79207
80 for (conf_run.args.slice) |arg_index| {
81 const arg = arg_index.get(conf);
82 try argv_list.ensureUnusedCapacity(gpa, 1);
83 switch (arg.flags.tag) {
84 .string => {
85 const prefix = arg.prefix.value.?.slice(conf);
86 argv_list.appendAssumeCapacity(prefix);
87 man.hash.addBytesZ(prefix);
88 },
89 .path_file => {
90 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
91 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
92 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
93 argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{
94 prefix, try convertPathArg(arena, run_index, maker, file_path, arg.flags.make_absolute), suffix,
95 }));
96 man.hash.add(arg.flags.make_absolute);
97 man.hash.addBytesZ(prefix);
98 man.hash.addBytesZ(suffix);
99 _ = try man.addInputPath(file_path, .{});
100 },
101 .path_directory => {
102 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
103 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
104 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
105 const resolved_arg = try mem.concat(arena, u8, &.{
106 prefix, try convertPathArg(arena, run_index, maker, file_path, arg.flags.make_absolute), suffix,
107 });
108 argv_list.appendAssumeCapacity(resolved_arg);
109 man.hash.add(arg.flags.make_absolute);
110 man.hash.addBytes(resolved_arg);
208 var owned_dirs: std.bit_set.Dynamic = .{};
209 var inherit_dirs: std.ArrayList(Io.Dir) = .empty;
210 var owned_files: std.bit_set.Dynamic = .{};
211 var inherit_files: std.ArrayList(Io.File) = .empty;
212 var protocol_args: std.ArrayList(u8) = .empty;
213 var input_dirs: std.ArrayList(Cache.Path) = .empty;
214 defer {
215 for (inherit_dirs.items, 0..) |inherit_dir, inherit_dir_index|
216 if (owned_dirs.isSet(inherit_dir_index)) inherit_dir.close(io);
217 for (inherit_files.items, 0..) |inherit_file, inherit_file_index|
218 if (owned_files.isSet(inherit_file_index)) inherit_file.close(io);
219 owned_dirs.deinit(gpa);
220 inherit_dirs.deinit(gpa);
221 owned_files.deinit(gpa);
222 inherit_files.deinit(gpa);
223 protocol_args.deinit(gpa);
224 input_dirs.deinit(gpa);
225 }
226 switch (conf_run.flags.stdio) {
227 .infer_from_args, .inherit, .check => {},
228 .zig_test => {
229 const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false);
230
231 try argv_list.ensureUnusedCapacity(gpa, 3);
232 argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string}));
233 argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed}));
234 argv_list.appendAssumeCapacity("--listen=-");
111235 },
112 .file_content => {
113 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
114 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
115 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
116
117 var result: std.Io.Writer.Allocating = .init(arena);
118 result.writer.writeAll(prefix) catch return error.OutOfMemory;
236 .protocol => {
237 argv_list.appendAssumeCapacity("--listen=-");
238 try input_dirs.append(gpa, if (conf_run.cwd.value) |lazy_cwd|
239 try maker.resolveLazyPathIndex(arena, lazy_cwd, run_index)
240 else
241 .cwd());
242 for (conf_run.args.slice[1..]) |arg_index| {
243 const arg = arg_index.get(conf);
244 switch (arg.flags.tag) {
245 .string => {
246 const string = arg.prefix.value.?.slice(conf);
247
248 man.hash.addBytesZ(string);
249
250 try protocol_args.ensureUnusedCapacity(gpa, 1 + string.len + 1);
251 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.string));
252 protocol_args.appendSliceAssumeCapacity(string);
253 protocol_args.appendAssumeCapacity(0);
254 },
255 .path_file => {
256 const lazy_path = arg.path.value.?.get(conf);
257 switch (lazy_path) {
258 else => {},
259 .relative => |relative| switch (relative.flags.base) {
260 else => {},
261 .libc_runtimes => if (graph.libc_runtimes_dir == null) continue,
262 },
263 }
119264
120 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|
121 return step.fail(maker, "unable to open input file {f}: {t}", .{ file_path, err });
122 defer file.close(io);
265 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
266 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
267 const file_path = try maker.resolveLazyPath(arena, lazy_path, run_index);
268
269 try owned_files.resize(gpa, owned_files.bit_length + 1, true);
270 try inherit_files.ensureUnusedCapacity(gpa, 1);
271 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|
272 return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err });
273 inherit_files.appendAssumeCapacity(file);
274
275 man.hash.addBytesZ(prefix);
276 _ = try man.addInputPath(file_path, .{});
277 man.hash.addBytesZ(suffix);
278
279 try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 +
280 1 + @sizeOf(Io.File.Handle) +
281 1 + suffix.len + 1);
282 if (prefix.len > 0) {
283 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix));
284 protocol_args.appendSliceAssumeCapacity(prefix);
285 protocol_args.appendAssumeCapacity(0);
286 }
287 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_file));
288 const file_handle: *align(1) Io.File.Handle =
289 @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle)));
290 file_handle.* = file.handle;
291 if (suffix.len > 0) {
292 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix));
293 protocol_args.appendSliceAssumeCapacity(prefix);
294 protocol_args.appendAssumeCapacity(0);
295 }
296 },
297 .path_directory => {
298 const lazy_path = arg.path.value.?.get(conf);
299 switch (lazy_path) {
300 else => {},
301 .relative => |relative| switch (relative.flags.base) {
302 else => {},
303 .libc_runtimes => if (graph.libc_runtimes_dir == null) continue,
304 },
305 }
123306
124 var file_reader = file.reader(io, &.{});
125 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
126 error.ReadFailed => switch (file_reader.err.?) {
127 error.Canceled => |e| return e,
128 else => |e| return step.fail(maker, "failed to read from {f}: {t}", .{ file_path, e }),
129 },
130 error.WriteFailed => return error.OutOfMemory,
131 };
132 result.writer.writeAll(suffix) catch return error.OutOfMemory;
307 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
308 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
309 const dir_path = try maker.resolveLazyPath(arena, lazy_path, run_index);
310 try input_dirs.append(gpa, dir_path);
311
312 const has_sub_path = dir_path.sub_path.len > 0;
313 try owned_dirs.resize(gpa, owned_dirs.bit_length + 1, has_sub_path);
314 try inherit_dirs.ensureUnusedCapacity(gpa, 1);
315 const dir = if (has_sub_path)
316 dir_path.root_dir.handle.openDir(io, dir_path.sub_path, .{
317 .iterate = true,
318 }) catch |err| return step.fail(maker, "unable to open input dir {qf}: {t}", .{ dir_path, err })
319 else
320 dir_path.root_dir.handle;
321 inherit_dirs.appendAssumeCapacity(dir);
322
323 man.hash.addBytesZ(prefix);
324 man.hash.addOptionalBytes(dir_path.root_dir.path);
325 man.hash.addBytes(dir_path.sub_path);
326 man.hash.addBytesZ(suffix);
327
328 try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 +
329 1 + @sizeOf(Io.Dir.Handle) +
330 1 + suffix.len + 1);
331 if (prefix.len > 0) {
332 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix));
333 protocol_args.appendSliceAssumeCapacity(prefix);
334 protocol_args.appendAssumeCapacity(0);
335 }
336 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_dir));
337 const dir_handle: *align(1) Io.Dir.Handle =
338 @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.Dir.Handle)));
339 dir_handle.* = dir.handle;
340 if (suffix.len > 0) {
341 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix));
342 protocol_args.appendSliceAssumeCapacity(prefix);
343 protocol_args.appendAssumeCapacity(0);
344 }
345 },
346 .file_content => {
347 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
348 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
349 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
350
351 try owned_files.resize(gpa, owned_files.bit_length + 1, true);
352 try inherit_files.ensureUnusedCapacity(gpa, 1);
353 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|
354 return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err });
355 inherit_files.appendAssumeCapacity(file);
356
357 man.hash.addBytesZ(prefix);
358 _ = try man.addInputPath(file_path, .{});
359 man.hash.addBytesZ(suffix);
360
361 try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 +
362 1 + @sizeOf(Io.File.Handle) +
363 1 + suffix.len + 1);
364 if (prefix.len > 0) {
365 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix));
366 protocol_args.appendSliceAssumeCapacity(prefix);
367 protocol_args.appendAssumeCapacity(0);
368 }
369 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_file_content));
370 const file_handle: *align(1) Io.File.Handle =
371 @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle)));
372 file_handle.* = file.handle;
373 if (suffix.len > 0) {
374 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix));
375 protocol_args.appendSliceAssumeCapacity(prefix);
376 protocol_args.appendAssumeCapacity(0);
377 }
378 },
379 .artifact => {
380 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
381 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
382 const producer_index = arg.producer.value.?;
383 const producer_step = producer_index.ptr(conf);
384 const producer = producer_step.extended.get(conf.extra).compile;
385 const producer_make_comp_step = maker.stepByIndex(producer_index);
386 const producer_make_comp = &producer_make_comp_step.extended.compile;
387
388 const file_path = producer_make_comp.installed_path orelse
389 maker.generatedPath(producer.generated_bin.value.?);
390
391 try owned_files.resize(gpa, owned_files.bit_length + 1, true);
392 try inherit_files.ensureUnusedCapacity(gpa, 1);
393 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|
394 return step.fail(maker, "unable to open input artifact {qf}: {t}", .{ file_path, err });
395 inherit_files.appendAssumeCapacity(file);
396
397 man.hash.addBytesZ(prefix);
398 _ = try man.addInputPath(file_path, .{});
399 man.hash.addBytesZ(suffix);
400
401 try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 +
402 1 + @sizeOf(Io.File.Handle) +
403 1 + suffix.len + 1);
404 if (prefix.len > 0) {
405 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix));
406 protocol_args.appendSliceAssumeCapacity(prefix);
407 protocol_args.appendAssumeCapacity(0);
408 }
409 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_file));
410 const file_handle: *align(1) Io.File.Handle =
411 @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle)));
412 file_handle.* = file.handle;
413 if (suffix.len > 0) {
414 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix));
415 protocol_args.appendSliceAssumeCapacity(prefix);
416 protocol_args.appendAssumeCapacity(0);
417 }
418 },
419 .output_file => {
420 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
421 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
422 const basename = arg.basename.value.?.slice(conf);
423
424 man.hash.addBytesZ(prefix);
425 man.hash.addBytesZ(basename);
426 man.hash.addBytesZ(suffix);
427 assert(!arg.flags.dep_file);
428
429 try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 +
430 1 + @sizeOf(Io.File.Handle) +
431 1 + suffix.len + 1);
432 if (prefix.len > 0) {
433 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix));
434 protocol_args.appendSliceAssumeCapacity(prefix);
435 protocol_args.appendAssumeCapacity(0);
436 }
437 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.output_file));
438
439 // Add a placeholder into the argument list because we need the
440 // manifest hash to be updated with all arguments before the
441 // object directory is computed.
442 try owned_files.resize(gpa, owned_files.bit_length + 1, false);
443 try inherit_files.ensureUnusedCapacity(gpa, 1);
444 try output_placeholders.append(gpa, .{
445 .index = @intCast(inherit_files.items.len),
446 .offset = @intCast(protocol_args.items.len),
447 .arg_index = arg_index,
448 });
449 _ = inherit_files.addOneAssumeCapacity();
450
451 _ = protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle));
452 if (suffix.len > 0) {
453 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix));
454 protocol_args.appendSliceAssumeCapacity(prefix);
455 protocol_args.appendAssumeCapacity(0);
456 }
457 },
458 .output_directory => {
459 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
460 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
461 const basename = arg.basename.value.?.slice(conf);
462
463 man.hash.addBytesZ(prefix);
464 man.hash.addBytesZ(basename);
465 man.hash.addBytesZ(suffix);
466 assert(!arg.flags.dep_file);
467
468 try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 +
469 1 + @sizeOf(Io.Dir.Handle) +
470 1 + suffix.len + 1);
471 if (prefix.len > 0) {
472 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix));
473 protocol_args.appendSliceAssumeCapacity(prefix);
474 protocol_args.appendAssumeCapacity(0);
475 }
476 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.output_dir));
477
478 // Add a placeholder into the argument list because we need the
479 // manifest hash to be updated with all arguments before the
480 // object directory is computed.
481 try owned_dirs.resize(gpa, owned_dirs.bit_length + 1, false);
482 try inherit_dirs.ensureUnusedCapacity(gpa, 1);
483 try output_placeholders.append(gpa, .{
484 .index = @intCast(inherit_dirs.items.len),
485 .offset = @intCast(protocol_args.items.len),
486 .arg_index = arg_index,
487 });
488 _ = inherit_dirs.addOneAssumeCapacity();
489
490 _ = protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.Dir.Handle));
491 if (suffix.len > 0) {
492 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix));
493 protocol_args.appendSliceAssumeCapacity(prefix);
494 protocol_args.appendAssumeCapacity(0);
495 }
496 },
497 .passthru => if (maker.run_args) |run_args| {
498 man.hash.addListOfBytes(run_args);
499
500 for (run_args) |run_arg| {
501 try protocol_args.ensureUnusedCapacity(gpa, 1 + run_arg.len + 1);
502 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.string));
503 protocol_args.appendSliceAssumeCapacity(run_arg);
504 protocol_args.appendAssumeCapacity(0);
505 }
506 },
507 .enable_darling => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_darling, arg.prefix.value, arg.suffix.value),
508 .enable_qemu => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_qemu, arg.prefix.value, arg.suffix.value),
509 .enable_rosetta => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_rosetta, arg.prefix.value, arg.suffix.value),
510 .enable_wasmtime => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_wasmtime, arg.prefix.value, arg.suffix.value),
511 .enable_wine => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value),
512 }
513 }
514 },
515 }
133516
134 argv_list.appendAssumeCapacity(result.written());
135 man.hash.addBytesZ(prefix);
136 man.hash.addBytesZ(suffix);
517 switch (conf_run.stdin.u) {
518 .bytes => |bytes| {
519 man.hash.addBytes(bytes.slice(conf));
520 },
521 .lazy_path => |lazy_path| {
522 const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);
137523 _ = try man.addInputPath(file_path, .{});
138524 },
139 .artifact => {
140 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
141 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
142 const producer_index = arg.producer.value.?;
143 const producer_step = producer_index.ptr(conf);
144 const producer = producer_step.extended.get(conf.extra).compile;
145 const producer_make_comp_step = maker.stepByIndex(producer_index);
146 const producer_make_comp = &producer_make_comp_step.extended.compile;
525 .none => {},
526 }
147527
148 const file_path = producer_make_comp.installed_path orelse
149 maker.generatedPath(producer.generated_bin.value.?);
528 if (conf_run.captured_stdout.value) |captured| {
529 man.hash.addBytes(captured.basename.slice(conf));
530 man.hash.add(conf_run.flags.stdout_trim_whitespace);
531 }
150532
151 argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{
152 prefix, try convertPathArg(arena, run_index, maker, file_path, arg.flags.make_absolute), suffix,
153 }));
533 if (conf_run.captured_stderr.value) |captured| {
534 man.hash.addBytes(captured.basename.slice(conf));
535 man.hash.add(conf_run.flags.stderr_trim_whitespace);
536 }
154537
155 man.hash.add(arg.flags.make_absolute);
156 man.hash.addBytesZ(prefix);
157 man.hash.addBytesZ(suffix);
158 _ = try man.addInputPath(file_path, .{});
159 },
160 .output_file, .output_directory => {
161 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
162 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
163 const basename = arg.basename.value.?.slice(conf);
164
165 man.hash.add(arg.flags.make_absolute);
166 man.hash.addBytesZ(prefix);
167 man.hash.addBytesZ(basename);
168 man.hash.addBytesZ(suffix);
169 man.hash.add(arg.flags.dep_file);
170
171 any_dep_files = any_dep_files or arg.flags.dep_file;
172 any_output_args = true;
173
174 // Add a placeholder into the argument list because we need the
175 // manifest hash to be updated with all arguments before the
176 // object directory is computed.
177 try output_placeholders.append(gpa, .{
178 .index = @intCast(argv_list.items.len),
179 .arg_index = arg_index,
180 });
181 argv_list.items.len += 1;
182 },
183 .passthru => {
184 any_cli_positionals = true;
185 if (maker.run_args) |run_args| {
186 try argv_list.appendSlice(gpa, run_args);
187 man.hash.addListOfBytes(run_args);
188 }
538 switch (conf_run.flags.stdio) {
539 .infer_from_args, .inherit, .zig_test, .protocol => {},
540 .check => {
541 man.hash.addBytes(if (conf_run.expect_stderr_exact.value) |bytes| bytes.slice(conf) else "");
542 man.hash.addBytes(if (conf_run.expect_stdout_exact.value) |bytes| bytes.slice(conf) else "");
543 for (conf_run.expect_stderr_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf));
544 for (conf_run.expect_stdout_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf));
545 man.hash.add(conf_run.flags2.expect_term_status);
546 man.hash.addOptional(conf_run.expect_term_value.value);
189547 },
190 .enable_darling => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_darling, arg.prefix.value, arg.suffix.value),
191 .enable_qemu => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_qemu, arg.prefix.value, arg.suffix.value),
192 .enable_rosetta => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_rosetta, arg.prefix.value, arg.suffix.value),
193 .enable_wasmtime => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_wasmtime, arg.prefix.value, arg.suffix.value),
194 .enable_wine => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value),
195548 }
196 }
197549
198 man.hash.add(conf_run.flags.test_runner_mode);
199 if (conf_run.flags.test_runner_mode) {
200 const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false);
201
202 try argv_list.ensureUnusedCapacity(gpa, 3);
203 argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string}));
204 argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed}));
205 argv_list.appendAssumeCapacity("--listen=-");
206 }
207
208 switch (conf_run.stdin.u) {
209 .bytes => |bytes| {
210 man.hash.addBytes(bytes.slice(conf));
211 },
212 .lazy_path => |lazy_path| {
550 for (conf_run.file_inputs.slice) |lazy_path| {
213551 const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);
214552 _ = try man.addInputPath(file_path, .{});
215 },
216 .none => {},
217 }
218
219 if (conf_run.captured_stdout.value) |captured| {
220 man.hash.addBytes(captured.basename.slice(conf));
221 man.hash.add(conf_run.flags.stdout_trim_whitespace);
222 }
553 }
223554
224 if (conf_run.captured_stderr.value) |captured| {
225 man.hash.addBytes(captured.basename.slice(conf));
226 man.hash.add(conf_run.flags.stderr_trim_whitespace);
227 }
555 if (conf_run.cwd.value) |lazy_path| {
556 const cwd_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);
557 _ = man.hash.addBytes(try cwd_path.toString(arena));
558 }
228559
229 switch (conf_run.flags.stdio) {
230 .infer_from_args, .inherit, .zig_test => {},
231 .check => {
232 man.hash.addBytes(if (conf_run.expect_stderr_exact.value) |bytes| bytes.slice(conf) else "");
233 man.hash.addBytes(if (conf_run.expect_stdout_exact.value) |bytes| bytes.slice(conf) else "");
234 for (conf_run.expect_stderr_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf));
235 for (conf_run.expect_stdout_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf));
236 man.hash.add(conf_run.flags2.expect_term_status);
237 man.hash.addOptional(conf_run.expect_term_value.value);
238 },
239 }
560 // Whether the Run step has side effects *other than* updating the output arguments.
561 // When fuzzing we need to always run the test runner to populate fuzz_tests.
562 const has_side_effects = graph.fuzzing or conf_run.flags.has_side_effects or any_cli_positionals or
563 switch (conf_run.flags.stdio) {
564 .infer_from_args => !any_output_args and
565 conf_run.captured_stdout.value == null and
566 conf_run.captured_stderr.value == null,
567 .inherit => true,
568 .check, .zig_test, .protocol => false,
569 };
240570
241 for (conf_run.file_inputs.slice) |lazy_path| {
242 const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);
243 _ = try man.addInputPath(file_path, .{});
244 }
571 if (!has_side_effects and try step.cacheHitWatched(maker, &man, progress_node)) {
572 // Cache hit; skip running command.
573 const digest = man.hitDigestHex();
574 try populateGeneratedStdIo(maker, &conf_run, &digest);
575 try populateGeneratedPaths(maker, output_placeholders.items, &digest);
576 step.result_cached = true;
577 return;
578 }
245579
246 if (conf_run.cwd.value) |lazy_path| {
247 const cwd_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);
248 _ = man.hash.addBytes(try cwd_path.toString(arena));
249 }
580 if (!any_discovered_inputs) {
581 // We already know the final output paths; use them directly.
582 const digest = if (has_side_effects) man.hash.final() else man.missDigestHex();
583 const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest;
584 try populateGeneratedStdIo(maker, &conf_run, &digest);
585 try populateGeneratedPathsCreateDirs(arena, run_index, maker, output_dir_path, output_placeholders.items, switch (conf_run.flags.stdio) {
586 .infer_from_args, .inherit, .check, .zig_test => .{ .argv = argv_list.items },
587 .protocol => .{ .protocol = .{
588 .owned_dirs = &owned_dirs,
589 .inherit_dirs = inherit_dirs.items,
590 .owned_files = &owned_files,
591 .inherit_files = inherit_files.items,
592 .args = protocol_args.items,
593 } },
594 });
595 runCommand(arena, run, run_index, maker, progress_node, argv_list.items, &.{}, &.{}, &.{}, &.{}, null, has_side_effects, output_dir_path, null) catch |err| switch (err) {
596 else => |e| return e,
597 error.MakeFailed, error.MakeSkipped => |e| {
598 try step.setWatchInputsFromManifest(maker, &man);
599 return e;
600 },
601 };
602 if (has_side_effects) {
603 try step.setWatchInputsFromManifest(maker, &man);
604 } else {
605 try step.finalizeManifestAndWatch(maker, &man);
606 }
607 return;
608 }
250609
251 // Whether the Run step has side effects *other than* updating the output arguments.
252 // When fuzzing we need to always run the test runner to populate fuzz_tests.
253 const has_side_effects = graph.fuzzing or conf_run.flags.has_side_effects or any_cli_positionals or
254 switch (conf_run.flags.stdio) {
255 .infer_from_args => !any_output_args and
256 conf_run.captured_stdout.value == null and
257 conf_run.captured_stderr.value == null,
258 .inherit => true,
259 .check, .zig_test => false,
610 // We do not know the final output paths yet; use temporary directory to run the command.
611 var rand_int: u64 = undefined;
612 io.random(@ptrCast(&rand_int));
613 tmp_dir_path = ("tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int)).*;
614
615 try populateGeneratedPathsCreateDirs(arena, run_index, maker, &tmp_dir_path, output_placeholders.items, switch (conf_run.flags.stdio) {
616 .infer_from_args, .inherit, .check, .zig_test => .{ .argv = argv_list.items },
617 .protocol => .{ .protocol = .{
618 .owned_dirs = &owned_dirs,
619 .inherit_dirs = inherit_dirs.items,
620 .owned_files = &owned_files,
621 .inherit_files = inherit_files.items,
622 .args = protocol_args.items,
623 } },
624 });
625 runCommand(
626 arena,
627 run,
628 run_index,
629 maker,
630 progress_node,
631 argv_list.items,
632 inherit_dirs.items,
633 inherit_files.items,
634 protocol_args.items,
635 input_dirs.items,
636 &man,
637 has_side_effects,
638 &tmp_dir_path,
639 null,
640 ) catch |err| switch (err) {
641 else => |e| return e,
642 error.MakeFailed, error.MakeSkipped => |e| {
643 try step.setWatchInputsFromManifest(maker, &man);
644 return e;
645 },
260646 };
261647
262 if (!has_side_effects and try step.cacheHitWatched(maker, &man, progress_node)) {
263 // Cache hit; skip running command.
264 const digest = man.hitDigestHex();
265 try populateGeneratedStdIo(maker, &conf_run, &digest);
266 try populateGeneratedPaths(maker, output_placeholders.items, &digest);
267 step.result_cached = true;
268 return;
269 }
270
271 if (!any_dep_files) {
272 // We already know the final output paths; use them directly.
273 const digest = if (has_side_effects) man.hash.final() else man.missDigestHex();
274 const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest;
275 try populateGeneratedStdIo(maker, &conf_run, &digest);
276 try populateGeneratedPathsCreateDirs(arena, run_index, maker, output_dir_path, output_placeholders.items, argv_list.items);
277 try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, has_side_effects, output_dir_path, null);
278 if (!has_side_effects) try step.finalizeManifestAndWatch(maker, &man);
279 return;
280 }
281
282 // We do not know the final output paths yet; use temporary directory to run the command.
283 var rand_int: u64 = undefined;
284 io.random(@ptrCast(&rand_int));
285 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
286
287 try populateGeneratedPathsCreateDirs(arena, run_index, maker, tmp_dir_path, output_placeholders.items, argv_list.items);
288 try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, has_side_effects, tmp_dir_path, null);
648 break :has_side_effects has_side_effects;
649 };
289650
290651 for (output_placeholders.items) |placeholder| {
291652 const arg = placeholder.arg_index.get(conf);
......@@ -296,10 +657,10 @@ pub fn make(
296657 var diagnostic: Cache.DepTokenizer.Token = undefined;
297658 man.addInputDepFile(generated_path, &diagnostic) catch |err| switch (err) {
298659 error.OutOfMemory, error.Canceled => |e| return e,
299 error.InvalidDepFile => return step.fail(maker, "failed adding dep file {f} to cache: {f}", .{
660 error.InvalidDepFile => return step.fail(maker, "failed adding dep file {qf} to cache: {f}", .{
300661 generated_path, diagnostic,
301662 }),
302 else => |e| return step.fail(maker, "failed adding dep file {f} to cache: {t}", .{
663 else => |e| return step.fail(maker, "failed adding dep file {qf} to cache: {t}", .{
303664 generated_path, e,
304665 }),
305666 };
......@@ -307,13 +668,13 @@ pub fn make(
307668 var diagnostic: Cache.Manifest.AddDiscoveredDepFileDiagnostic = undefined;
308669 man.addDiscoveredDepFile(generated_path, &diagnostic) catch |err| switch (err) {
309670 error.OutOfMemory, error.Canceled => |e| return e,
310 error.InvalidDepFile => return step.fail(maker, "failed adding dep file {f} to cache: {f}", .{
671 error.InvalidDepFile => return step.fail(maker, "failed adding dep file {qf} to cache: {f}", .{
311672 generated_path, diagnostic.dep_tokenizer,
312673 }),
313 error.FileSystemFailure => return step.fail(maker, "failed adding a path from dep file {f} to cache: {f}", .{
674 error.FileSystemFailure => return step.fail(maker, "failed adding a path from dep file {qf} to cache: {f}", .{
314675 generated_path, diagnostic.add_discovered_path,
315676 }),
316 else => |e| return step.fail(maker, "failed adding dep file {f} to cache: {t}", .{
677 else => |e| return step.fail(maker, "failed adding dep file {qf} to cache: {t}", .{
317678 generated_path, e,
318679 }),
319680 };
......@@ -331,7 +692,7 @@ pub fn make(
331692
332693 if (any_output) {
333694 // Rename into place.
334 const tmp_path: Path = .{ .root_dir = cache_root, .sub_path = tmp_dir_path };
695 const tmp_path: Path = .{ .root_dir = cache_root, .sub_path = &tmp_dir_path };
335696 const dst_path: Path = .{ .root_dir = cache_root, .sub_path = "o" ++ Dir.path.sep_str ++ &digest };
336697 Dir.rename(
337698 tmp_path.root_dir.handle,
......@@ -342,7 +703,7 @@ pub fn make(
342703 ) catch |err| switch (err) {
343704 error.DirNotEmpty => {
344705 dst_path.root_dir.handle.deleteTree(io, dst_path.sub_path) catch |del_err|
345 return step.fail(maker, "failed to remove tree {f}: {t}", .{ dst_path, del_err });
706 return step.fail(maker, "failed to remove tree {qf}: {t}", .{ dst_path, del_err });
346707
347708 Dir.rename(
348709 tmp_path.root_dir.handle,
......@@ -350,17 +711,21 @@ pub fn make(
350711 dst_path.root_dir.handle,
351712 dst_path.sub_path,
352713 io,
353 ) catch |retry_err| return step.fail(maker, "failed to rename directory {f} to {f}: {t}", .{
714 ) catch |retry_err| return step.fail(maker, "failed to rename directory {qf} to {qf}: {t}", .{
354715 tmp_path, dst_path, retry_err,
355716 });
356717 },
357 else => return step.fail(maker, "failed to rename directory {f} to {f}: {t}", .{
718 else => return step.fail(maker, "failed to rename directory {qf} to {qf}: {t}", .{
358719 tmp_path, dst_path, err,
359720 }),
360721 };
361722 }
362723
363 if (!has_side_effects) try step.finalizeManifestAndWatch(maker, &man);
724 if (has_side_effects) {
725 try step.setWatchInputsFromManifest(maker, &man);
726 } else {
727 try step.finalizeManifestAndWatch(maker, &man);
728 }
364729
365730 try populateGeneratedStdIo(maker, &conf_run, &digest);
366731 try populateGeneratedPaths(maker, output_placeholders.items, &digest);
......@@ -386,19 +751,30 @@ fn thirdPartyToggle(
386751 enable: ?Configuration.String,
387752 disable: ?Configuration.String,
388753) void {
389 if (setting) {
390 if (enable) |string| {
391 const slice = string.slice(conf);
392 if (man_hash) |h| h.addBytesZ(slice);
393 argv_list.appendAssumeCapacity(slice);
394 }
395 } else {
396 if (disable) |string| {
397 const slice = string.slice(conf);
398 if (man_hash) |h| h.addBytesZ(slice);
399 argv_list.appendAssumeCapacity(slice);
400 }
401 }
754 const string = (if (setting) enable else disable) orelse return;
755 const slice = string.slice(conf);
756 if (man_hash) |h| h.addBytesZ(slice);
757 argv_list.appendAssumeCapacity(slice);
758}
759
760fn thirdPartyToggleProtocol(
761 gpa: Allocator,
762 man_hash: *Cache.HashHelper,
763 protocol_args: *std.ArrayList(u8),
764 conf: *const Configuration,
765 setting: bool,
766 enable: ?Configuration.String,
767 disable: ?Configuration.String,
768) Allocator.Error!void {
769 const string = (if (setting) enable else disable) orelse return;
770 const slice = string.slice(conf);
771
772 man_hash.addBytesZ(slice);
773
774 try protocol_args.ensureUnusedCapacity(gpa, 1 + slice.len + 1);
775 protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.string));
776 protocol_args.appendSliceAssumeCapacity(slice);
777 protocol_args.appendAssumeCapacity(0);
402778}
403779
404780/// Reads stdout of a Zig test process until a termination condition is reached:
......@@ -411,7 +787,11 @@ fn waitZigTest(
411787 run_index: Configuration.Step.Index,
412788 maker: *Maker,
413789 child: *process.Child,
414 progress_node: std.Progress.Node,
790 tests_prog_node: std.Progress.Node,
791 test_prog_node: std.Progress.Node,
792 protocol_args: []const u8,
793 input_dirs: []const Cache.Path,
794 man: ?*Cache.Manifest,
415795 multi_reader: *Io.File.MultiReader,
416796 opt_metadata: *?TestMetadata,
417797 results: *Step.TestResults,
......@@ -431,9 +811,6 @@ fn waitZigTest(
431811 const io = graph.io;
432812 const step = maker.stepByIndex(run_index);
433813
434 var sub_prog_node: ?std.Progress.Node = null;
435 defer if (sub_prog_node) |n| n.end();
436
437814 const stdout = multi_reader.reader(0);
438815 const stderr = multi_reader.reader(1);
439816
......@@ -444,9 +821,17 @@ fn waitZigTest(
444821 .out = &stdin_writer.interface,
445822 };
446823
824 if (protocol_args.len > 0) {
825 try client.serveMessageHeader(.{
826 .tag = .args,
827 .bytes_len = @intCast(protocol_args.len),
828 });
829 try client.out.writeAll(protocol_args);
830 }
831
447832 if (opt_metadata.*) |*md| {
448833 // Previous unit test process died or was killed; we're continuing where it left off
449 requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
834 requestNextTest(&client, md, test_prog_node) catch |err| return .{ .write_failed = err };
450835 } else {
451836 // Running unit tests normally
452837 run.fuzz_tests.clearRetainingCapacity();
......@@ -492,6 +877,7 @@ fn waitZigTest(
492877 var body_r: std.Io.Reader = .fixed(body);
493878
494879 switch (header.tag) {
880 else => {}, // ignore other messages
495881 .zig_version => {
496882 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail(
497883 maker,
......@@ -499,6 +885,7 @@ fn waitZigTest(
499885 .{ builtin.zig_version_string, body },
500886 );
501887 },
888 .error_bundle => step.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body),
502889 .test_metadata => {
503890 // `metadata` would only be populated if we'd already seen a `test_metadata`, but we
504891 // only request it once (and importantly, we don't re-request it if we kill and
......@@ -516,27 +903,27 @@ fn waitZigTest(
516903
517904 const string_bytes = body_r.take(tm_hdr.string_bytes_len) catch unreachable;
518905
519 progress_node.setEstimatedTotalItems(names.len);
906 tests_prog_node.setEstimatedTotalItems(names.len);
520907 opt_metadata.* = .{
521908 .string_bytes = try arena.dupe(u8, string_bytes),
522909 .ns_per_test = try arena.alloc(u64, results.test_count),
523910 .names = names,
524911 .expected_panic_msgs = expected_panic_msgs,
525912 .next_index = 0,
526 .prog_node = progress_node,
527913 };
528914 @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64));
529915
530916 active_test_index = null;
531917 last_update = .now(io, .awake);
532918
533 requestNextTest(&client, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err };
919 requestNextTest(&client, &opt_metadata.*.?, test_prog_node) catch |err| return .{ .write_failed = err };
534920 },
535921 .test_started => {
536922 active_test_index = opt_metadata.*.?.next_index - 1;
537923 last_update = .now(io, .awake);
538924 },
539925 .test_results => {
926 tests_prog_node.completeOne();
540927 const md = &opt_metadata.*.?;
541928
542929 const tr_hdr = body_r.takeStruct(std.zig.Server.Message.TestResults, .little) catch unreachable;
......@@ -558,10 +945,12 @@ fn waitZigTest(
558945 const name = md.testName(tr_hdr.index);
559946 const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");
560947 stderr.tossBuffered();
561 if (stderr_bytes.len == 0) {
562 try step.addError(maker, "'{s}' failed without output", .{name});
563 } else {
948 if (stderr_bytes.len > 0) {
564949 try step.addError(maker, "'{s}' failed:\n{s}", .{ name, stderr_bytes });
950 } else if (step.result_error_bundle.errorMessageCount() > 0) {
951 try step.addError(maker, "'{s}' errored", .{name});
952 } else {
953 try step.addError(maker, "'{s}' failed without output", .{name});
565954 }
566955 } else if (leak_count > 0) {
567956 const name = md.testName(tr_hdr.index);
......@@ -581,9 +970,18 @@ fn waitZigTest(
581970 md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds);
582971 last_update = now;
583972
584 requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
973 requestNextTest(&client, md, test_prog_node) catch |err| return .{ .write_failed = err };
974 },
975 .discovered_inputs => while (body_r.takeEnum(
976 std.zig.Server.Message.InputDir,
977 .little,
978 )) |input_dir| try man.?.addDiscoveredPath(.{ .discovered_path = .{
979 .unresolved = try input_dirs[@backingInt(input_dir)]
980 .join(arena, try body_r.takeSentinel(0)),
981 } }) else |err| switch (err) {
982 else => |e| return e,
983 error.EndOfStream => {},
585984 },
586 else => {}, // ignore other messages
587985 }
588986 }
589987}
......@@ -820,6 +1218,7 @@ const FuzzTestRunner = struct {
8201218 }
8211219
8221220 switch (header.tag) {
1221 else => {}, // ignore other messages
8231222 .zig_version => {
8241223 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail(
8251224 maker,
......@@ -882,7 +1281,6 @@ const FuzzTestRunner = struct {
8821281 f.pending_broadcasts.appendSliceAssumeCapacity(@ptrCast(&footer));
8831282 }
8841283 },
885 else => {}, // ignore other messages
8861284 }
8871285
8881286 instance.message.clearRetainingCapacity();
......@@ -1146,6 +1544,9 @@ fn evalZigTest(
11461544 maker: *Maker,
11471545 progress_node: std.Progress.Node,
11481546 spawn_options: process.SpawnOptions,
1547 protocol_args: []const u8,
1548 input_dirs: []const Cache.Path,
1549 man: ?*Cache.Manifest,
11491550 fuzz_context: ?FuzzContext,
11501551) !void {
11511552 if (fuzz_context != null) {
......@@ -1194,6 +1595,10 @@ fn evalZigTest(
11941595 maker,
11951596 &child,
11961597 progress_node,
1598 spawn_options.progress_node,
1599 protocol_args,
1600 input_dirs,
1601 man,
11971602 &multi_reader,
11981603 &test_metadata,
11991604 &test_results,
......@@ -1323,7 +1728,6 @@ const TestMetadata = struct {
13231728 expected_panic_msgs: []const u32,
13241729 string_bytes: []const u8,
13251730 next_index: u32,
1326 prog_node: std.Progress.Node,
13271731
13281732 fn toCachedTestMetadata(tm: TestMetadata) CachedTestMetadata {
13291733 return .{
......@@ -1346,16 +1750,14 @@ pub const CachedTestMetadata = struct {
13461750 }
13471751};
13481752
1349fn requestNextTest(client: *std.zig.Client, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void {
1753fn requestNextTest(client: *std.zig.Client, metadata: *TestMetadata, test_prog_node: std.Progress.Node) !void {
13501754 while (metadata.next_index < metadata.names.len) {
13511755 const i = metadata.next_index;
13521756 metadata.next_index += 1;
13531757
13541758 if (metadata.expected_panic_msgs[i] != 0) continue;
13551759
1356 const name = metadata.testName(i);
1357 if (sub_prog_node.*) |n| n.end();
1358 sub_prog_node.* = metadata.prog_node.start(name, 0);
1760 test_prog_node.setName(metadata.testName(i));
13591761
13601762 try client.serveRunTest(i);
13611763 return;
......@@ -1402,7 +1804,7 @@ fn evalGeneric(
14021804 var write_buffer: [1024]u8 = undefined;
14031805 var stdin_writer = child.stdin.?.writerStreaming(io, &write_buffer);
14041806 _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
1405 error.ReadFailed => return step.fail(maker, "failed to read from {f}: {t}", .{
1807 error.ReadFailed => return step.fail(maker, "failed to read from {qf}: {t}", .{
14061808 path, file_reader.err.?,
14071809 }),
14081810 error.WriteFailed => return step.fail(maker, "failed to write to stdin: {t}", .{
......@@ -1490,6 +1892,7 @@ fn evalGeneric(
14901892
14911893const IndexedOutput = struct {
14921894 index: u32,
1895 offset: u32,
14931896 arg_index: Configuration.Step.Run.Arg.Index,
14941897};
14951898
......@@ -1516,13 +1919,16 @@ pub fn rerunInFuzzMode(
15161919 var argv_list: std.ArrayList([]const u8) = .empty;
15171920 defer argv_list.deinit(gpa);
15181921
1519 for (conf_run.args.slice) |arg_index| {
1922 for (switch (conf_run.flags.stdio) {
1923 .infer_from_args, .inherit, .check, .zig_test => conf_run.args.slice,
1924 .protocol => @panic("protocol fuzz unimplemented"),
1925 }) |arg_index| {
15201926 const arg = arg_index.get(conf);
15211927 try argv_list.ensureUnusedCapacity(gpa, 1);
15221928 switch (arg.flags.tag) {
15231929 .string => {
1524 const prefix = arg.prefix.value.?.slice(conf);
1525 argv_list.appendAssumeCapacity(prefix);
1930 const string = arg.prefix.value.?.slice(conf);
1931 argv_list.appendAssumeCapacity(string);
15261932 },
15271933 .path_file => {
15281934 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
......@@ -1550,14 +1956,14 @@ pub fn rerunInFuzzMode(
15501956 result.writer.writeAll(prefix) catch return error.OutOfMemory;
15511957
15521958 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|
1553 return step.fail(maker, "unable to open input file {f}: {t}", .{ file_path, err });
1959 return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err });
15541960 defer file.close(io);
15551961
15561962 var file_reader = file.reader(io, &.{});
15571963 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
15581964 error.ReadFailed => switch (file_reader.err.?) {
15591965 error.Canceled => |e| return e,
1560 else => |e| return step.fail(maker, "failed to read from {f}: {t}", .{ file_path, e }),
1966 else => |e| return step.fail(maker, "failed to read from {qf}: {t}", .{ file_path, e }),
15611967 },
15621968 error.WriteFailed => return error.OutOfMemory,
15631969 };
......@@ -1591,14 +1997,17 @@ pub fn rerunInFuzzMode(
15911997 .enable_wine => thirdPartyToggle(null, &argv_list, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value),
15921998 }
15931999 }
1594
1595 if (conf_run.flags.test_runner_mode) {
1596 const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false);
1597
1598 try argv_list.ensureUnusedCapacity(gpa, 3);
1599 argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string}));
1600 argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed}));
1601 argv_list.appendAssumeCapacity("--listen=-");
2000 switch (conf_run.flags.stdio) {
2001 .infer_from_args, .inherit, .check => {},
2002 .zig_test => {
2003 const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false);
2004
2005 try argv_list.ensureUnusedCapacity(gpa, 3);
2006 argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string}));
2007 argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed}));
2008 argv_list.appendAssumeCapacity("--listen=-");
2009 },
2010 .protocol => unreachable,
16022011 }
16032012
16042013 step.clearFailedCommand(gpa);
......@@ -1607,7 +2016,7 @@ pub fn rerunInFuzzMode(
16072016 var rand_int: u64 = undefined;
16082017 io.random(@ptrCast(&rand_int));
16092018 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
1610 try runCommand(arena, run, run_index, maker, prog_node, argv_list.items, has_side_effects, tmp_dir_path, .{
2019 try runCommand(arena, run, run_index, maker, prog_node, argv_list.items, &.{}, &.{}, &.{}, &.{}, null, has_side_effects, tmp_dir_path, .{
16112020 .fuzz = fuzz,
16122021 });
16132022}
......@@ -1633,7 +2042,16 @@ fn populateGeneratedPathsCreateDirs(
16332042 maker: *Maker,
16342043 output_dir_path: []const u8,
16352044 output_placeholders: []const IndexedOutput,
1636 argv: [][]const u8,
2045 populate: union(enum) {
2046 argv: [][]const u8,
2047 protocol: struct {
2048 owned_dirs: *std.bit_set.Dynamic,
2049 inherit_dirs: []Io.Dir,
2050 owned_files: *std.bit_set.Dynamic,
2051 inherit_files: []Io.File,
2052 args: []u8,
2053 },
2054 },
16372055) !void {
16382056 const step = maker.stepByIndex(run_index);
16392057 const conf = &maker.scanned_config.configuration;
......@@ -1653,16 +2071,52 @@ fn populateGeneratedPathsCreateDirs(
16532071 const create_path: Path = .{
16542072 .root_dir = cache_root,
16552073 .sub_path = switch (arg.flags.tag) {
2074 else => unreachable,
16562075 .output_file => Dir.path.dirname(generated_path.sub_path).?,
16572076 .output_directory => generated_path.sub_path,
1658 else => unreachable,
16592077 },
16602078 };
16612079 create_path.root_dir.handle.createDirPath(io, create_path.sub_path) catch |err|
1662 return step.fail(maker, "unable to make path {f}: {t}", .{ create_path, err });
2080 return step.fail(maker, "unable to make path {qf}: {t}", .{ create_path, err });
16632081
1664 const arg_output_path = try convertPathArg(arena, run_index, maker, generated_path, arg.flags.make_absolute);
1665 argv[placeholder.index] = try mem.concat(arena, u8, &.{ prefix, arg_output_path, suffix });
2082 switch (populate) {
2083 .argv => |argv| {
2084 const arg_output_path = try convertPathArg(arena, run_index, maker, generated_path, arg.flags.make_absolute);
2085 argv[placeholder.index] = try mem.concat(arena, u8, &.{ prefix, arg_output_path, suffix });
2086 },
2087 .protocol => |protocol| switch (arg.flags.tag) {
2088 else => unreachable,
2089 .output_file => {
2090 const file = generated_path.root_dir.handle.createFile(
2091 io,
2092 generated_path.sub_path,
2093 .{ .read = true },
2094 ) catch |err| return step.fail(maker, "unable to create output file {qf}: {t}", .{
2095 generated_path, err,
2096 });
2097 protocol.inherit_files[placeholder.index] = file;
2098 protocol.owned_files.set(placeholder.index);
2099 const file_handle: *align(1) Io.File.Handle =
2100 @ptrCast(protocol.args[placeholder.offset..][0..@sizeOf(Io.File.Handle)]);
2101 file_handle.* = file.handle;
2102 },
2103 .output_directory => {
2104 assert(generated_path.sub_path.len > 0);
2105 const dir = generated_path.root_dir.handle.openDir(
2106 io,
2107 generated_path.sub_path,
2108 .{ .iterate = true },
2109 ) catch |err| return step.fail(maker, "unable to open output dir {qf}: {t}", .{
2110 generated_path, err,
2111 });
2112 protocol.inherit_dirs[placeholder.index] = dir;
2113 protocol.owned_dirs.set(placeholder.index);
2114 const dir_handle: *align(1) Io.Dir.Handle =
2115 @ptrCast(protocol.args[placeholder.offset..][0..@sizeOf(Io.Dir.Handle)]);
2116 dir_handle.* = dir.handle;
2117 },
2118 },
2119 }
16662120 }
16672121}
16682122
......@@ -1711,6 +2165,11 @@ fn runCommand(
17112165 maker: *Maker,
17122166 progress_node: std.Progress.Node,
17132167 argv: []const []const u8,
2168 inherit_dirs: []const Io.Dir,
2169 inherit_files: []const Io.File,
2170 protocol_args: []const u8,
2171 input_dirs: []const Cache.Path,
2172 man: ?*Cache.Manifest,
17142173 has_side_effects: bool,
17152174 output_dir_path: []const u8,
17162175 fuzz_context: ?FuzzContext,
......@@ -1777,10 +2236,16 @@ fn runCommand(
17772236 progress_node,
17782237 argv,
17792238 &environ_map,
2239 inherit_dirs,
2240 inherit_files,
2241 protocol_args,
2242 input_dirs,
2243 man,
17802244 has_side_effects,
17812245 fuzz_context,
17822246 ) catch |err| term: {
17832247 switch (err) {
2248 else => {},
17842249 error.InvalidExe, // cpu arch mismatch
17852250 error.FileNotFound, // can happen with a wrong dynamic linker path
17862251 => interpret: {
......@@ -1794,24 +2259,21 @@ fn runCommand(
17942259 const root_module = producer.root_module.get(conf);
17952260 const root_module_target = root_module.resolved_target.get(conf).?.result.get(conf);
17962261 const root_target = root_module_target.unwrapTarget(conf);
1797 const link_libc = maker.stepByIndex(producer_index).extended.compile.is_linking_libc;
2262 const config = maker.stepByIndex(producer_index).extended.compile.config.?;
17982263
17992264 const host: std.Target = std.zig.system.resolveTargetQuery(io, .{}) catch |he| switch (he) {
18002265 error.Canceled => |e| return e,
18012266 else => builtin.target,
18022267 };
18032268
1804 const need_cross_libc = link_libc and root_target.os.tag == .linux and
1805 switch (producer.flags2.linkage) {
1806 .static => false,
1807 .dynamic => true,
1808 .default => root_target.isGnuLibC(),
1809 };
2269 const need_cross_libc = root_target.os.tag == .linux and
2270 config.flags.link_libc and config.flags.link_mode == .dynamic;
18102271 switch (std.zig.system.getExternalExecutor(io, &root_target, .{
18112272 .host_cpu_arch = host.cpu.arch,
18122273 .host_os_tag = host.os.tag,
18132274 .qemu_fixes_dl = need_cross_libc and graph.libc_runtimes_dir != null,
1814 .link_libc = link_libc,
2275 .link_mode = config.flags.link_mode,
2276 .link_libc = config.flags.link_libc,
18152277 })) {
18162278 .native, .rosetta => {
18172279 if (allow_skip) return error.MakeSkipped;
......@@ -1874,7 +2336,7 @@ fn runCommand(
18742336 interp_argv.appendAssumeCapacity(bin_name);
18752337 interp_argv.appendAssumeCapacity("--dir=.");
18762338 for (conf_run.preopens.slice) |preopen| {
1877 const path = try maker.resolveLazyPath(arena, preopen.path.get(conf), run_index);
2339 const path = try maker.resolveLazyPathIndex(arena, preopen.path, run_index);
18782340 path.root_dir.handle.createDirPath(io, path.subPathOrDot()) catch |e|
18792341 return step.fail(maker, "failed creating directory {f}: {t}", .{ path, e });
18802342 interp_argv.appendAssumeCapacity(try arena.print("--dir={f}::{s}", .{ path, preopen.name.slice(conf) }));
......@@ -1928,6 +2390,11 @@ fn runCommand(
19282390 progress_node,
19292391 interp_argv.items,
19302392 &environ_map,
2393 inherit_dirs,
2394 inherit_files,
2395 protocol_args,
2396 input_dirs,
2397 man,
19312398 has_side_effects,
19322399 fuzz_context,
19332400 ) catch |e| {
......@@ -1937,21 +2404,21 @@ fn runCommand(
19372404 };
19382405 },
19392406 error.MakeFailed, error.OutOfMemory, error.Canceled => |e| return e,
1940 else => {},
19412407 }
19422408 return step.fail(maker, "failed to spawn and capture stdio from {s}: {t}", .{ argv[0], err });
19432409 };
19442410
1945 const generic_result = opt_generic_result orelse {
1946 assert(conf_run.flags.stdio == .zig_test);
1947 // Specific errors have already been reported, and test results are populated. All we need
1948 // to do is report step failure if any test failed.
1949 if (!step.test_results.isSuccess()) return error.MakeFailed;
1950 return;
2411 const generic_result = switch (conf_run.flags.stdio) {
2412 .infer_from_args, .inherit, .check => opt_generic_result.?,
2413 .zig_test, .protocol => {
2414 assert(opt_generic_result == null);
2415 // Specific errors have already been reported, and test results are populated. All we need
2416 // to do is report step failure if any test failed.
2417 if (!step.test_results.isSuccess()) return error.MakeFailed;
2418 return;
2419 },
19512420 };
1952
19532421 assert(fuzz_context == null);
1954 assert(conf_run.flags.stdio != .zig_test);
19552422
19562423 // Capture stdout and stderr to GeneratedFile objects.
19572424 const Stream = struct {
......@@ -1978,7 +2445,7 @@ fn runCommand(
19782445
19792446 const sub_path_parent = output_path.dirname().?;
19802447 sub_path_parent.root_dir.handle.createDirPath(io, sub_path_parent.sub_path) catch |err|
1981 return step.fail(maker, "unable to make path {f}: {t}", .{ sub_path_parent, err });
2448 return step.fail(maker, "unable to make path {qf}: {t}", .{ sub_path_parent, err });
19822449
19832450 const data = switch (stream.trim_whitespace) {
19842451 .none => stream.bytes.?,
......@@ -1989,12 +2456,12 @@ fn runCommand(
19892456 output_path.root_dir.handle.writeFile(io, .{
19902457 .sub_path = output_path.sub_path,
19912458 .data = data,
1992 }) catch |err| return step.fail(maker, "unable to write file {f}: {t}", .{ output_path, err });
2459 }) catch |err| return step.fail(maker, "unable to write file {qf}: {t}", .{ output_path, err });
19932460 }
19942461 }
19952462
19962463 switch (conf_run.flags.stdio) {
1997 .zig_test => unreachable,
2464 .zig_test, .protocol => unreachable,
19982465 .check => {
19992466 if (conf_run.expect_stderr_exact.value) |bytes| {
20002467 const expected_bytes = bytes.slice(conf);
......@@ -2186,6 +2653,11 @@ fn spawnChildAndCollect(
21862653 progress_node: std.Progress.Node,
21872654 argv: []const []const u8,
21882655 environ_map: *EnvMap,
2656 inherit_dirs: []const Io.Dir,
2657 inherit_files: []const Io.File,
2658 protocol_args: []const u8,
2659 input_dirs: []const Cache.Path,
2660 man: ?*Cache.Manifest,
21892661 has_side_effects: bool,
21902662 fuzz_context: ?FuzzContext,
21912663) !?EvalGenericResult {
......@@ -2199,7 +2671,10 @@ fn spawnChildAndCollect(
21992671
22002672 if (fuzz_context != null) {
22012673 assert(!has_side_effects);
2202 assert(conf_run.flags.stdio == .zig_test);
2674 switch (conf_run.flags.stdio) {
2675 .infer_from_args, .inherit, .check => unreachable,
2676 .zig_test, .protocol => {},
2677 }
22032678 }
22042679
22052680 const child_cwd: process.Child.Cwd = if (conf_run.cwd.value) |lazy_cwd|
......@@ -2230,68 +2705,88 @@ fn spawnChildAndCollect(
22302705 .cwd = child_cwd,
22312706 .environ_map = environ_map,
22322707 .request_resource_usage_statistics = true,
2233 .stdin = if (conf_run.stdin.u != .none) s: {
2708 .stdin = if (conf_run.stdin.u != .none) stdin: {
22342709 assert(conf_run.flags.stdio != .inherit);
2235 break :s .pipe;
2710 break :stdin .pipe;
22362711 } else switch (conf_run.flags.stdio) {
22372712 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore,
22382713 .inherit => .inherit,
22392714 .check => .ignore,
2240 .zig_test => .pipe,
2715 .zig_test, .protocol => .pipe,
22412716 },
22422717 .stdout = if (conf_run.captured_stdout.value != null) .pipe else switch (conf_run.flags.stdio) {
22432718 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore,
22442719 .inherit => .inherit,
22452720 .check => if (checksContainStdout(&conf_run)) .pipe else .ignore,
2246 .zig_test => .pipe,
2721 .zig_test, .protocol => .pipe,
22472722 },
22482723 .stderr = if (conf_run.captured_stderr.value != null) .pipe else switch (conf_run.flags.stdio) {
22492724 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .pipe,
22502725 .inherit => if (maker.protocol_server == null) .inherit else .pipe,
22512726 .check => .pipe,
2252 .zig_test => .pipe,
2727 .zig_test, .protocol => .pipe,
22532728 },
2729 .inherit_dirs = inherit_dirs,
2730 .inherit_files = inherit_files,
22542731 };
22552732
22562733 if (maker.protocol_server != null) {
22572734 if (spawn_options.stdin == .inherit) {
2258 return step.fail(maker, "Cannot inherit stdin when running through over the build system protocol", .{});
2735 return step.fail(maker, "Cannot inherit stdin when running over the build system protocol", .{});
22592736 }
22602737 if (spawn_options.stdout == .inherit) {
2261 return step.fail(maker, "Cannot inherit stdout when running through over the build system protocol", .{});
2738 return step.fail(maker, "Cannot inherit stdout when running over the build system protocol", .{});
22622739 }
22632740 assert(spawn_options.stderr != .inherit);
22642741 }
22652742
2266 if (conf_run.flags.stdio == .zig_test) {
2267 try setColorEnvironmentVariables(&conf_run, environ_map, graph.stderr_mode.?);
2268 const started: Io.Clock.Timestamp = .now(io, .awake);
2269 const result = evalZigTest(run, run_index, maker, progress_node, spawn_options, fuzz_context) catch |err| switch (err) {
2270 error.Canceled => |e| return e,
2271 else => |e| e,
2272 };
2273 step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);
2274 try result;
2275 return null;
2276 } else {
2277 const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit;
2278 if (!conf_run.flags.disable_zig_progress and !inherit) {
2279 spawn_options.progress_node = progress_node;
2280 }
2281 const terminal_mode: Io.Terminal.Mode = if (inherit) m: {
2282 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
2283 break :m stderr.terminal_mode;
2284 } else .no_color;
2285 defer if (inherit) io.unlockStderr();
2286 try setColorEnvironmentVariables(&conf_run, environ_map, terminal_mode);
2287
2288 const started: Io.Clock.Timestamp = .now(io, .awake);
2289 const result = evalGeneric(arena, run_index, maker, spawn_options) catch |err| switch (err) {
2290 error.Canceled => |e| return e,
2291 else => |e| e,
2292 };
2293 step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);
2294 return try result;
2743 switch (conf_run.flags.stdio) {
2744 .infer_from_args, .inherit, .check => {
2745 const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit;
2746 if (!conf_run.flags.disable_zig_progress and !inherit) {
2747 spawn_options.progress_node = progress_node;
2748 }
2749 const terminal_mode: Io.Terminal.Mode = if (inherit) m: {
2750 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
2751 break :m stderr.terminal_mode;
2752 } else .no_color;
2753 defer if (inherit) io.unlockStderr();
2754 try setColorEnvironmentVariables(&conf_run, environ_map, terminal_mode);
2755
2756 const started: Io.Clock.Timestamp = .now(io, .awake);
2757 const result = evalGeneric(arena, run_index, maker, spawn_options) catch |err| switch (err) {
2758 error.Canceled => |e| return e,
2759 else => |e| e,
2760 };
2761 step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);
2762 return try result;
2763 },
2764 .zig_test, .protocol => |stdio| {
2765 spawn_options.progress_node = if (conf_run.flags.disable_zig_progress or fuzz_context != null)
2766 .none
2767 else
2768 progress_node.start(@tagName(stdio), 0);
2769 defer spawn_options.progress_node.end();
2770 try setColorEnvironmentVariables(&conf_run, environ_map, graph.stderr_mode.?);
2771 const started: Io.Clock.Timestamp = .now(io, .awake);
2772 const result = evalZigTest(
2773 run,
2774 run_index,
2775 maker,
2776 progress_node,
2777 spawn_options,
2778 protocol_args,
2779 input_dirs,
2780 man,
2781 fuzz_context,
2782 ) catch |err| switch (err) {
2783 error.Canceled => |e| return e,
2784 else => |e| e,
2785 };
2786 step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);
2787 try result;
2788 return null;
2789 },
22952790 }
22962791}
22972792
......@@ -2341,7 +2836,7 @@ fn setColorEnvironmentVariables(
23412836 .auto => {
23422837 const capture_stderr = conf_run.captured_stderr.value != null or switch (conf_run.flags.stdio) {
23432838 .check => checksContainStderr(conf_run),
2344 .infer_from_args, .inherit, .zig_test => false,
2839 .infer_from_args, .inherit, .zig_test, .protocol => false,
23452840 };
23462841 if (capture_stderr) {
23472842 continue :color .disable;
lib/compiler/resinator/main.zig+3-6
......@@ -790,8 +790,7 @@ fn cliDiagnosticsToErrorBundle(
790790) !ErrorBundle {
791791 @branchHint(.cold);
792792
793 var bundle: ErrorBundle.Wip = undefined;
794 try bundle.init(gpa);
793 var bundle: ErrorBundle.Wip = try .init(gpa);
795794 errdefer bundle.deinit();
796795
797796 try bundle.addRootErrorMessage(.{
......@@ -837,8 +836,7 @@ fn diagnosticsToErrorBundle(
837836) !ErrorBundle {
838837 @branchHint(.cold);
839838
840 var bundle: ErrorBundle.Wip = undefined;
841 try bundle.init(gpa);
839 var bundle: ErrorBundle.Wip = try .init(gpa);
842840 errdefer bundle.deinit();
843841
844842 var msg_buf: std.Io.Writer.Allocating = .init(gpa);
......@@ -919,8 +917,7 @@ fn diagnosticsToErrorBundle(
919917
920918fn errorStringToErrorBundle(allocator: Allocator, comptime format: []const u8, args: anytype) !ErrorBundle {
921919 @branchHint(.cold);
922 var bundle: ErrorBundle.Wip = undefined;
923 try bundle.init(allocator);
920 var bundle: ErrorBundle.Wip = try .init(allocator);
924921 errdefer bundle.deinit();
925922 try bundle.addRootErrorMessage(.{
926923 .msg = try bundle.printString(format, args),
lib/compiler/util.zig+1-2
......@@ -10,8 +10,7 @@ pub fn aroDiagnosticsToErrorBundle(
1010) !ErrorBundle {
1111 @branchHint(.cold);
1212
13 var bundle: ErrorBundle.Wip = undefined;
14 try bundle.init(gpa);
13 var bundle: ErrorBundle.Wip = try .init(gpa);
1514 errdefer bundle.deinit();
1615
1716 if (fail_msg) |msg| {
lib/std/Build.zig+5-5
......@@ -768,9 +768,8 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
768768
769769 const run_step = Step.Run.create(b, step_name);
770770 run_step.producer = exe;
771 run_step.addArtifactArg(exe);
771772 if (exe.kind == .@"test") {
772 run_step.addArtifactArg(exe);
773
774773 const test_server_mode: bool = s: {
775774 if (exe.test_runner) |r| break :s r.mode == .server;
776775 if (exe.use_llvm == false) {
......@@ -802,8 +801,6 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
802801 // communicate failure via its exit code.
803802 run_step.expectExitCode(0);
804803 }
805 } else {
806 run_step.addArtifactArg(exe);
807804 }
808805
809806 return run_step;
......@@ -2696,12 +2693,15 @@ fn validateConfigureDependency(lazy_path: LazyPath) void {
26962693 .src_path, .cwd_relative, .dependency => {}, // OK
26972694 .generated => @panic("configure phase cannot depend on files generated during make phase"),
26982695 .relative => |relative| switch (relative.base) {
2699 .cwd, .build_root, .local_cache, .global_cache, .zig_exe, .zig_lib => {}, // OK
2696 .cwd, .build_root, .local_cache, .global_cache, .zig_lib => {}, // OK
2697 .zig_exe => if (relative.sub_path.len > 0) @panic("file base cannot have a sub path"),
27002698 .install_prefix,
27012699 .install_lib,
27022700 .install_bin,
27032701 .install_include,
27042702 => @panic("configure phase cannot depend on files installed during make phase"),
2703 .libc_runtimes,
2704 => @panic("configure phase cannot depend on directory known only during make phase"),
27052705 },
27062706 }
27072707}
lib/std/Build/Configuration.zig+2-2
......@@ -665,7 +665,7 @@ pub const Step = extern struct {
665665 pub const Tag = enum(u2) { none, bytes, lazy_path };
666666 };
667667 pub const TrimWhitespace = enum(u2) { none, all, leading, trailing };
668 pub const StdIo = enum(u2) { infer_from_args, inherit, check, zig_test };
668 pub const StdIo = enum(u3) { infer_from_args, inherit, check, zig_test, protocol };
669669
670670 pub const ExpectTermStatus = enum(u2) { exited, signal, stopped, unknown };
671671
......@@ -675,7 +675,6 @@ pub const Step = extern struct {
675675 skip_foreign_checks: bool,
676676 failing_to_execute_foreign_is_an_error: bool,
677677 has_side_effects: bool,
678 test_runner_mode: bool,
679678 color: Color,
680679 stdin: StdIn.Tag,
681680 stdio: StdIo,
......@@ -1576,6 +1575,7 @@ pub const LazyPath = union(@This().Tag) {
15761575 install_lib,
15771576 install_bin,
15781577 install_include,
1578 libc_runtimes,
15791579 };
15801580 };
15811581};
lib/std/Build/Serialize.zig+1-1
......@@ -462,13 +462,13 @@ pub fn write(b: *std.Build, wc: *Configuration.Wip, writer: *std.Io.Writer) !voi
462462 .skip_foreign_checks = run.skip_foreign_checks,
463463 .failing_to_execute_foreign_is_an_error = run.failing_to_execute_foreign_is_an_error,
464464 .has_side_effects = run.has_side_effects,
465 .test_runner_mode = run.test_runner_mode,
466465 .color = run.color,
467466 .stdio = switch (run.stdio) {
468467 .infer_from_args => .infer_from_args,
469468 .inherit => .inherit,
470469 .check => .check,
471470 .zig_test => .zig_test,
471 .protocol => .protocol,
472472 },
473473 .stdin = switch (run.stdin) {
474474 .none => .none,
lib/std/Build/Step.zig+1-1
......@@ -95,7 +95,7 @@ pub fn init(options: StepOptions) Step {
9595
9696 return .{
9797 .tag = options.tag,
98 .name = arena.dupe(u8, options.name) catch @panic("OOM"),
98 .name = options.owner.graph.dupeString(options.name),
9999 .owner = options.owner,
100100 .dependencies = .empty,
101101 .max_rss = options.max_rss,
lib/std/Build/Step/CheckFile.zig+1-1
......@@ -41,5 +41,5 @@ pub fn create(owner: *std.Build, file: std.Build.LazyPath, options: Options) *Ch
4141}
4242
4343pub fn setName(check_file: *CheckFile, name: []const u8) void {
44 check_file.step.name = name;
44 check_file.step.name = check_file.step.owner.graph.dupeString(name);
4545}
lib/std/Build/Step/Run.zig+17-8
......@@ -92,7 +92,6 @@ captured_stdout: ?*CapturedStdIo,
9292captured_stderr: ?*CapturedStdIo,
9393
9494has_side_effects: bool,
95test_runner_mode: bool = false,
9695
9796/// If this Run step was produced by a Compile step, it is tracked here.
9897producer: ?*Step.Compile,
......@@ -127,7 +126,10 @@ pub const StdIo = union(enum) {
127126 check: std.ArrayList(Check),
128127 /// This Run step is running a zig unit test binary and will communicate
129128 /// extra metadata over the IPC protocol.
129 /// Deprecated, use `.protocol`.
130130 zig_test,
131 /// This Run step is running a binary that will communicate over the IPC protocol.
132 protocol,
131133
132134 pub const Check = union(enum) {
133135 expect_stderr_exact: []const u8,
......@@ -239,14 +241,17 @@ pub fn create(owner: *std.Build, name: []const u8) *Run {
239241}
240242
241243pub fn setName(run: *Run, name: []const u8) void {
242 run.step.name = name;
244 run.step.name = run.step.owner.graph.dupeString(name);
243245 run.rename_step_with_output_arg = false;
244246}
245247
248/// Deprecated, use `enableProtocolMode`.
246249pub fn enableTestRunnerMode(run: *Run) void {
247 if (run.test_runner_mode) return;
248250 run.stdio = .zig_test;
249 run.test_runner_mode = true;
251}
252
253pub fn enableProtocolMode(run: *Run) void {
254 run.stdio = .protocol;
250255}
251256
252257pub const ArgOptions = struct {
......@@ -750,8 +755,10 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void {
750755}
751756
752757pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {
753 assert(run.stdio != .inherit);
754 assert(run.stdio != .zig_test);
758 switch (run.stdio) {
759 .infer_from_args, .check => {},
760 .inherit, .zig_test, .protocol => unreachable,
761 }
755762
756763 const b = run.step.owner;
757764 const graph = b.graph;
......@@ -771,8 +778,10 @@ pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPa
771778}
772779
773780pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {
774 assert(run.stdio != .inherit);
775 assert(run.stdio != .zig_test);
781 switch (run.stdio) {
782 .infer_from_args, .check => {},
783 .inherit, .zig_test, .protocol => unreachable,
784 }
776785
777786 const b = run.step.owner;
778787 const graph = b.graph;
lib/std/Io.zig+21-18
......@@ -213,13 +213,13 @@ pub const VTable = struct {
213213 processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void,
214214 processSetCurrentPath: *const fn (?*anyopaque, []const u8) std.process.SetCurrentPathError!void,
215215 processReplace: *const fn (?*anyopaque, std.process.ReplaceOptions) std.process.ReplaceError,
216 processReplacePath: *const fn (?*anyopaque, Dir, std.process.ReplaceOptions) std.process.ReplaceError,
217216 processSpawn: *const fn (?*anyopaque, std.process.SpawnOptions) std.process.SpawnError!std.process.Child,
218 processSpawnPath: *const fn (?*anyopaque, Dir, std.process.SpawnOptions) std.process.SpawnError!std.process.Child,
219217 childWait: *const fn (?*anyopaque, *std.process.Child) std.process.Child.WaitError!std.process.Child.Term,
220218 childKill: *const fn (?*anyopaque, *std.process.Child) void,
221219
222220 progressParentFile: *const fn (?*anyopaque) std.Progress.ParentFileError!File,
221 inheritParentDir: *const fn (?*anyopaque, handle: Dir.Handle) InheritParentHandleError!Dir,
222 inheritParentFile: *const fn (?*anyopaque, handle: File.Handle, flags: File.Flags) InheritParentHandleError!File,
223223
224224 now: *const fn (?*anyopaque, Clock) Timestamp,
225225 clockResolution: *const fn (?*anyopaque, Clock) Clock.ResolutionError!Duration,
......@@ -827,6 +827,10 @@ pub const UnexpectedError = error{
827827 Unexpected,
828828};
829829
830pub const InheritParentHandleError = error{
831 UnsupportedOperation,
832} || Cancelable || UnexpectedError;
833
830834pub const Clock = enum {
831835 /// A settable system-wide clock that measures real (i.e. wall-clock)
832836 /// time. This clock is affected by discontinuous jumps in the system
......@@ -2797,13 +2801,13 @@ pub const failing: std.Io = .{
27972801 .processSetCurrentDir = failingProcessSetCurrentDir,
27982802 .processSetCurrentPath = failingProcessSetCurrentPath,
27992803 .processReplace = failingProcessReplace,
2800 .processReplacePath = failingProcessReplacePath,
28012804 .processSpawn = failingProcessSpawn,
2802 .processSpawnPath = failingProcessSpawnPath,
28032805 .childWait = unreachableChildWait,
28042806 .childKill = unreachableChildKill,
28052807
28062808 .progressParentFile = failingProgressParentFile,
2809 .inheritParentDir = failingInheritParentDir,
2810 .inheritParentFile = failingInheritParentFile,
28072811
28082812 .random = noRandom,
28092813 .randomSecure = failingRandomSecure,
......@@ -3451,26 +3455,12 @@ pub fn failingProcessReplace(userdata: ?*anyopaque, options: std.process.Replace
34513455 return error.OperationUnsupported;
34523456}
34533457
3454pub fn failingProcessReplacePath(userdata: ?*anyopaque, dir: Dir, options: std.process.ReplaceOptions) std.process.ReplaceError {
3455 _ = userdata;
3456 _ = dir;
3457 _ = options;
3458 return error.OperationUnsupported;
3459}
3460
34613458pub fn failingProcessSpawn(userdata: ?*anyopaque, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child {
34623459 _ = userdata;
34633460 _ = options;
34643461 return error.OperationUnsupported;
34653462}
34663463
3467pub fn failingProcessSpawnPath(userdata: ?*anyopaque, dir: Dir, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child {
3468 _ = userdata;
3469 _ = dir;
3470 _ = options;
3471 return error.OperationUnsupported;
3472}
3473
34743464pub fn unreachableChildWait(userdata: ?*anyopaque, child: *std.process.Child) std.process.Child.WaitError!std.process.Child.Term {
34753465 _ = userdata;
34763466 _ = child;
......@@ -3488,6 +3478,19 @@ pub fn failingProgressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileE
34883478 return error.UnsupportedOperation;
34893479}
34903480
3481pub fn failingInheritParentDir(userdata: ?*anyopaque, handle: Dir.Handle) InheritParentHandleError!Dir {
3482 _ = userdata;
3483 _ = handle;
3484 return error.UnsupportedOperation;
3485}
3486
3487pub fn failingInheritParentFile(userdata: ?*anyopaque, handle: File.Handle, flags: File.Flags) InheritParentHandleError!File {
3488 _ = userdata;
3489 _ = handle;
3490 _ = flags;
3491 return error.UnsupportedOperation;
3492}
3493
34913494pub fn noRandom(userdata: ?*anyopaque, buffer: []u8) void {
34923495 _ = userdata;
34933496 @memset(buffer, 0);
lib/std/Io/Reader/Limited.zig+2-4
......@@ -28,8 +28,7 @@ pub fn init(reader: *Reader, limit: Limit, buffer: []u8) Limited {
2828fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {
2929 const l: *Limited = @fieldParentPtr("interface", r);
3030 if (l.remaining == .nothing) return error.EndOfStream;
31 const combined_limit = limit.min(l.remaining);
32 const n = try l.unlimited.stream(w, combined_limit);
31 const n = try l.unlimited.stream(w, limit.min(l.remaining));
3332 l.remaining = l.remaining.subtract(n).?;
3433 return n;
3534}
......@@ -53,8 +52,7 @@ test stream {
5352fn discard(r: *Reader, limit: Limit) Reader.Error!usize {
5453 const l: *Limited = @fieldParentPtr("interface", r);
5554 if (l.remaining == .nothing) return error.EndOfStream;
56 const combined_limit = limit.min(l.remaining);
57 const n = try l.unlimited.discard(combined_limit);
55 const n = try l.unlimited.discard(limit.min(l.remaining));
5856 l.remaining = l.remaining.subtract(n).?;
5957 return n;
6058}
lib/std/Io/Threaded.zig+872-226
......@@ -85,6 +85,8 @@ csprng: Csprng = .uninitialized,
8585
8686system_basic_information: SystemBasicInformation = .{},
8787
88process_spawn_mutex: if (is_windows) Io.Mutex else void = if (is_windows) .init else {},
89
8890const SystemBasicInformation = if (!is_windows) struct {} else struct {
8991 buffer: windows.SYSTEM.BASIC_INFORMATION = undefined,
9092 initialized: std.atomic.Value(bool) = .{ .raw = false },
......@@ -1567,6 +1569,10 @@ const poll_buffer_len = 64;
15671569pub const default_PATH = "/usr/local/bin:/bin:/usr/bin";
15681570/// There are multiple kernel bugs being worked around with retries.
15691571const max_windows_kernel_bug_retries = 13;
1572/// Similar in nature to `max_windows_kernel_bug_retries`, but much more likely to
1573/// be a real error that won't go away after a retry, so the number of retries
1574/// is lower.
1575const max_windows_rename_retries = 3;
15701576
15711577comptime {
15721578 if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX);
......@@ -1706,7 +1712,7 @@ var global_single_threaded_instance: Threaded = .init_single_threaded;
17061712pub const global_single_threaded: *Threaded = &global_single_threaded_instance;
17071713
17081714pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void {
1709 mutexLock(&t.mutex);
1715 mutexLockUncancelable(&t.mutex);
17101716 defer mutexUnlock(&t.mutex);
17111717 t.async_limit = new_limit;
17121718}
......@@ -1727,7 +1733,7 @@ pub fn deinit(t: *Threaded) void {
17271733fn join(t: *Threaded) void {
17281734 if (builtin.single_threaded) return;
17291735 {
1730 mutexLock(&t.mutex);
1736 mutexLockUncancelable(&t.mutex);
17311737 defer mutexUnlock(&t.mutex);
17321738 t.join_requested = true;
17331739 }
......@@ -1789,7 +1795,7 @@ fn worker(t: *Threaded) void {
17891795
17901796 defer t.wait_group.finish();
17911797
1792 mutexLock(&t.mutex);
1798 mutexLockUncancelable(&t.mutex);
17931799 defer mutexUnlock(&t.mutex);
17941800
17951801 while (true) {
......@@ -1798,7 +1804,7 @@ fn worker(t: *Threaded) void {
17981804 thread.cancel_protection = .unblocked;
17991805 const runnable: *Runnable = @fieldParentPtr("node", runnable_node);
18001806 runnable.startFn(runnable, &thread, t);
1801 mutexLock(&t.mutex);
1807 mutexLockUncancelable(&t.mutex);
18021808 t.busy_count -= 1;
18031809 }
18041810 if (t.join_requested) break;
......@@ -1901,13 +1907,13 @@ pub fn io(t: *Threaded) Io {
19011907 .processSetCurrentDir = processSetCurrentDir,
19021908 .processSetCurrentPath = processSetCurrentPath,
19031909 .processReplace = processReplace,
1904 .processReplacePath = processReplacePath,
19051910 .processSpawn = processSpawn,
1906 .processSpawnPath = processSpawnPath,
19071911 .childWait = childWait,
19081912 .childKill = childKill,
19091913
19101914 .progressParentFile = progressParentFile,
1915 .inheritParentDir = inheritParentDir,
1916 .inheritParentFile = inheritParentFile,
19111917
19121918 .now = now,
19131919 .clockResolution = clockResolution,
......@@ -1975,6 +1981,10 @@ const have_copy_file_range = switch (native_os) {
19751981 .linux, .freebsd => true,
19761982 else => false,
19771983};
1984const have_fcntl = switch (native_os) {
1985 .linux, .dragonfly, .freebsd, .netbsd, .openbsd, .illumos, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => true,
1986 else => false,
1987};
19781988const have_fcopyfile = is_darwin;
19791989const have_fchmodat2 = native_os == .linux and
19801990 (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse true) and
......@@ -2084,7 +2094,7 @@ fn async(
20842094 },
20852095 };
20862096
2087 mutexLock(&t.mutex);
2097 mutexLockUncancelable(&t.mutex);
20882098
20892099 const busy_count = t.busy_count;
20902100
......@@ -2136,7 +2146,7 @@ fn concurrent(
21362146 };
21372147 errdefer future.destroy(gpa);
21382148
2139 mutexLock(&t.mutex);
2149 mutexLockUncancelable(&t.mutex);
21402150 defer mutexUnlock(&t.mutex);
21412151
21422152 const busy_count = t.busy_count;
......@@ -2181,7 +2191,7 @@ fn groupAsync(
21812191 error.OutOfMemory => return groupAsyncEager(start, context.ptr),
21822192 };
21832193
2184 mutexLock(&t.mutex);
2194 mutexLockUncancelable(&t.mutex);
21852195
21862196 const busy_count = t.busy_count;
21872197
......@@ -2244,7 +2254,7 @@ fn groupConcurrent(
22442254 };
22452255 errdefer task.destroy(gpa);
22462256
2247 mutexLock(&t.mutex);
2257 mutexLockUncancelable(&t.mutex);
22482258 defer mutexUnlock(&t.mutex);
22492259
22502260 const busy_count = t.busy_count;
......@@ -4114,7 +4124,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
41144124
41154125fn systemBasicInformation(t: *Threaded) ?*const windows.SYSTEM.BASIC_INFORMATION {
41164126 if (!t.system_basic_information.initialized.load(.acquire)) {
4117 mutexLock(&t.mutex);
4127 mutexLockUncancelable(&t.mutex);
41184128 defer mutexUnlock(&t.mutex);
41194129
41204130 switch (windows.ntdll.NtQuerySystemInformation(
......@@ -4462,7 +4472,7 @@ fn dirCreateFilePosix(
44624472 var fl_flags: usize = fl: {
44634473 const syscall: Syscall = try .start();
44644474 while (true) {
4465 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
4475 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(u32, 0));
44664476 switch (posix.errno(rc)) {
44674477 .SUCCESS => {
44684478 syscall.finish();
......@@ -5058,7 +5068,7 @@ fn dirOpenFilePosix(
50585068 var fl_flags: usize = fl: {
50595069 const syscall: Syscall = try .start();
50605070 while (true) {
5061 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
5071 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(u32, 0));
50625072 switch (posix.errno(rc)) {
50635073 .SUCCESS => {
50645074 syscall.finish();
......@@ -7030,6 +7040,11 @@ fn fileRealPathPosix(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.R
70307040}
70317041
70327042fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize {
7043 if (fd == posix.AT.FDCWD) return processCurrentPathPosix(out_buffer) catch |err| switch (err) {
7044 else => |e| return e,
7045 error.CurrentDirUnlinked => return error.FileNotFound,
7046 error.PathExceedsLimit => return error.NameTooLong,
7047 };
70337048 switch (native_os) {
70347049 .dragonfly, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
70357050 var sufficient_buffer: [posix.PATH_MAX]u8 = undefined;
......@@ -7674,6 +7689,8 @@ fn dirRenameWindowsInner(
76747689 defer w.CloseHandle(src_fd);
76757690
76767691 var rc: w.NTSTATUS = undefined;
7692 var attempt: u5 = 0;
7693
76777694 // FileRenameInformationEx has varying levels of support:
76787695 // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1
76797696 // (INVALID_INFO_CLASS is returned if not supported)
......@@ -7697,24 +7714,43 @@ fn dirRenameWindowsInner(
76977714 });
76987715 var io_status_block: w.IO_STATUS_BLOCK = undefined;
76997716 const rename_info_buf = rename_info.toBuffer();
7700 rc = w.ntdll.NtSetInformationFile(
7701 src_fd,
7702 &io_status_block,
7703 rename_info_buf.ptr,
7704 @intCast(rename_info_buf.len),
7705 .RenameEx,
7706 );
7707 switch (rc) {
7708 .SUCCESS => return,
7709 // The filesystem does not support FileDispositionInformationEx
7710 .INVALID_PARAMETER,
7711 // The operating system does not support FileDispositionInformationEx
7712 .INVALID_INFO_CLASS,
7713 // The operating system does not support one of the flags
7714 .NOT_SUPPORTED,
7715 => break :need_fallback true,
7716 // For all other statuses, fall down to the switch below to handle them.
7717 else => break :need_fallback false,
7717 var syscall: Syscall = try .start();
7718 while (true) {
7719 rc = w.ntdll.NtSetInformationFile(
7720 src_fd,
7721 &io_status_block,
7722 rename_info_buf.ptr,
7723 @intCast(rename_info_buf.len),
7724 .RenameEx,
7725 );
7726 switch (rc) {
7727 // For all other statuses, fall down to the switch below to handle them.
7728 else => {
7729 syscall.finish();
7730 break :need_fallback false;
7731 },
7732 .CANCELLED => try syscall.checkCancel(),
7733 // The filesystem does not support FileDispositionInformationEx
7734 .INVALID_PARAMETER,
7735 // The operating system does not support FileDispositionInformationEx
7736 .INVALID_INFO_CLASS,
7737 // The operating system does not support one of the flags
7738 .NOT_SUPPORTED,
7739 => {
7740 syscall.finish();
7741 break :need_fallback true;
7742 },
7743 .ACCESS_DENIED => {
7744 syscall.finish();
7745 if (max_windows_rename_retries - attempt == 0) break :need_fallback false;
7746 try parking_sleep.sleep(.{ .duration = .{
7747 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
7748 .clock = .awake,
7749 } });
7750 attempt += 1;
7751 syscall = try .start();
7752 },
7753 }
77187754 }
77197755 };
77207756
......@@ -7726,13 +7762,33 @@ fn dirRenameWindowsInner(
77267762 });
77277763 var io_status_block: w.IO_STATUS_BLOCK = undefined;
77287764 const rename_info_buf = rename_info.toBuffer();
7729 rc = w.ntdll.NtSetInformationFile(
7730 src_fd,
7731 &io_status_block,
7732 rename_info_buf.ptr,
7733 @intCast(rename_info_buf.len),
7734 .Rename,
7735 );
7765 var syscall: Syscall = try .start();
7766 while (true) {
7767 rc = w.ntdll.NtSetInformationFile(
7768 src_fd,
7769 &io_status_block,
7770 rename_info_buf.ptr,
7771 @intCast(rename_info_buf.len),
7772 .Rename,
7773 );
7774 switch (rc) {
7775 else => {
7776 syscall.finish();
7777 break;
7778 },
7779 .CANCELLED => try syscall.checkCancel(),
7780 .ACCESS_DENIED => {
7781 syscall.finish();
7782 if (max_windows_rename_retries - attempt == 0) break;
7783 try parking_sleep.sleep(.{ .duration = .{
7784 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
7785 .clock = .awake,
7786 } });
7787 attempt += 1;
7788 syscall = try .start();
7789 },
7790 }
7791 }
77367792 }
77377793
77387794 switch (rc) {
......@@ -10002,7 +10058,7 @@ fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize {
1000210058 .PENDING => unreachable,
1000310059 .CANCELLED => unreachable,
1000410060 .SUCCESS => return io_status_block.Information,
10005 .END_OF_FILE, .PIPE_BROKEN => return error.EndOfStream,
10061 .END_OF_FILE, .PIPE_BROKEN, .PIPE_CLOSING => return error.EndOfStream,
1000610062 .INVALID_HANDLE => return error.NotOpenForReading,
1000710063 .INVALID_DEVICE_REQUEST => return error.IsDir,
1000810064 .FILE_LOCK_CONFLICT => return error.LockViolation,
......@@ -10019,7 +10075,7 @@ fn ntWriteFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize {
1001910075 .INVALID_USER_BUFFER => return error.SystemResources,
1002010076 .NO_MEMORY => return error.SystemResources,
1002110077 .QUOTA_EXCEEDED => return error.SystemResources,
10022 .PIPE_BROKEN => return error.BrokenPipe,
10078 .PIPE_BROKEN, .PIPE_CLOSING => return error.BrokenPipe,
1002310079 .INVALID_HANDLE => return error.NotOpenForWriting,
1002410080 .FILE_LOCK_CONFLICT => return error.LockViolation,
1002510081 .ACCESS_DENIED => return error.AccessDenied,
......@@ -14082,7 +14138,7 @@ fn lockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancel
1408214138 const current_thread_id = Thread.currentId();
1408314139
1408414140 if (@atomicLoad(std.Thread.Id, &t.stderr_mutex_locker, .unordered) != current_thread_id) {
14085 mutexLock(&t.stderr_mutex);
14141 try mutexLock(&t.stderr_mutex);
1408614142 assert(t.stderr_mutex_lock_count == 0);
1408714143 @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, current_thread_id, .unordered);
1408814144 }
......@@ -14145,6 +14201,7 @@ fn unlockStderr(userdata: ?*anyopaque) void {
1414514201fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize {
1414614202 const t: *Threaded = @ptrCast(@alignCast(userdata));
1414714203 _ = t;
14204
1414814205 if (is_windows) {
1414914206 var wtf16le_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined;
1415014207 const n = windows.ntdll.RtlGetCurrentDirectory_U(wtf16le_buf.len * 2 + 2, &wtf16le_buf) / 2;
......@@ -14160,12 +14217,18 @@ fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathEr
1416014217 end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable;
1416114218 }
1416214219 return end_index;
14163 } else if (native_os == .wasi and !builtin.link_libc) {
14220 }
14221
14222 if (native_os == .wasi and !builtin.link_libc) {
1416414223 if (buffer.len == 0) return error.NameTooLong;
1416514224 buffer[0] = '.';
1416614225 return 1;
1416714226 }
1416814227
14228 return processCurrentPathPosix(buffer);
14229}
14230
14231fn processCurrentPathPosix(buffer: []u8) process.CurrentPathError!usize {
1416914232 const err: posix.E = if (builtin.link_libc) err: {
1417014233 const c_err = if (std.c.getcwd(buffer.ptr, buffer.len)) |_| 0 else std.c._errno().*;
1417114234 break :err @fromBackingInt(@intCast(c_err));
......@@ -15101,7 +15164,7 @@ const WindowsEnvironStrings = struct {
1510115164};
1510215165
1510315166fn scanEnviron(t: *Threaded) void {
15104 mutexLock(&t.mutex);
15167 mutexLockUncancelable(&t.mutex);
1510515168 defer mutexUnlock(&t.mutex);
1510615169 if (t.environ_initialized) return;
1510715170 t.environ.scan(t.allocator);
......@@ -15133,28 +15196,54 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
1513315196 });
1513415197 };
1513515198
15136 return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
15137}
15138
15139fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError {
15140 if (!process.can_replace) return error.OperationUnsupported;
15141 _ = userdata;
15142 _ = dir;
15143 _ = options;
15144 @panic("TODO processReplacePath");
15145}
15146
15147fn processSpawnPath(userdata: ?*anyopaque, dir: Dir, options: process.SpawnOptions) process.SpawnError!process.Child {
15148 if (!process.can_spawn) return error.OperationUnsupported;
15149 _ = userdata;
15150 _ = dir;
15151 _ = options;
15152 @panic("TODO processSpawnPath");
15199 if (is_darwin) if (t.spawnDarwin(
15200 .replace,
15201 options.exe,
15202 argv_buf.ptr,
15203 options.cwd,
15204 env_block,
15205 options.expand_arg0,
15206 .none,
15207 .inherit,
15208 .inherit,
15209 .inherit,
15210 options.inherit_dirs,
15211 options.inherit_files,
15212 null,
15213 options.start_suspended,
15214 PATH,
15215 )) |_| unreachable else |err| switch (err) {
15216 else => |e| return e,
15217 error.WouldBlock => return error.SystemResources,
15218 error.FileTooBig,
15219 error.NoSpaceLeft,
15220 error.PathAlreadyExists,
15221 error.SymLinkLoop,
15222 error.NetworkNotFound,
15223 error.PipeBusy,
15224 error.AntivirusInterference,
15225 => return error.FileNotFound,
15226 error.DeviceBusy,
15227 error.NoDevice,
15228 error.UnrecognizedVolume,
15229 error.ReadOnlyFileSystem,
15230 => return error.FileSystem,
15231 error.ResourceLimitReached => return error.SystemResources,
15232 error.FileLocksUnsupported => unreachable, // lock not requested
15233 error.InvalidWtf8 => unreachable, // windows only
15234 error.InvalidBatchScriptArg => unreachable, // windows only
15235 error.InvalidUserId => unreachable, // not supported
15236 error.InvalidProcessGroupId => unreachable, // passed null
15237 error.InvalidName => unreachable, // windows only
15238 error.ProcessAlreadyExec => unreachable, // linux only
15239 };
15240 return posixExec(options.exe, argv_buf.ptr, options.expand_arg0, env_block, PATH);
1515315241}
1515415242
1515515243const processSpawn = switch (native_os) {
1515615244 .wasi, .emscripten, .ios, .tvos, .visionos, .watchos => processSpawnUnsupported,
1515715245 .windows => processSpawnWindows,
15246 .driverkit, .maccatalyst, .macos => processSpawnDarwin,
1515815247 else => processSpawnPosix,
1515915248};
1516015249
......@@ -15230,16 +15319,12 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1523015319 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
1523115320 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr;
1523215321
15233 const prog_fileno = 3;
15234 comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno);
15235
1523615322 const env_block = env_block: {
15237 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
1523815323 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
15239 .zig_progress_fd = prog_fd,
15324 .zig_progress_fd = prog_pipe[1],
1524015325 });
1524115326 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
15242 .zig_progress_fd = prog_fd,
15327 .zig_progress_fd = prog_pipe[1],
1524315328 });
1524415329 };
1524515330
......@@ -15267,23 +15352,42 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1526715352 if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked;
1526815353 const ep1 = err_pipe[1];
1526915354
15270 setUpChildIo(options.stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);
15271 setUpChildIo(options.stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);
15272 setUpChildIo(options.stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);
15273
15355 // Must happen before clobbering fds below.
1527415356 switch (options.cwd) {
1527515357 .inherit => {},
15276 .dir => |cwd| {
15277 fchdir(cwd.handle) catch |err| forkBail(ep1, err);
15278 },
15279 .path => |cwd| {
15280 chdir(cwd) catch |err| forkBail(ep1, err);
15281 },
15358 .dir => |cwd| fchdir(cwd.handle) catch |err| forkBail(ep1, err),
15359 .path => |cwd| chdir(cwd) catch |err| forkBail(ep1, err),
1528215360 }
1528315361
15284 // Must happen after fchdir above, the cwd file descriptor might be
15285 // equal to prog_fileno and be clobbered by this dup2 call.
15286 if (prog_pipe[1] != -1) dup2(prog_pipe[1], prog_fileno) catch |err| forkBail(ep1, err);
15362 for (options.inherit_dirs) |inherit_dir| switch (posix.errno(
15363 posix.system.fcntl(inherit_dir.handle, posix.F.SETFD, @as(u32, 0)),
15364 )) {
15365 .SUCCESS => {},
15366 else => |err| forkBail(ep1, posix.unexpectedErrno(err)),
15367 };
15368 for (options.inherit_files) |file| switch (posix.errno(
15369 posix.system.fcntl(file.handle, posix.F.SETFD, @as(u32, 0)),
15370 )) {
15371 .SUCCESS => {},
15372 else => |err| forkBail(ep1, posix.unexpectedErrno(err)),
15373 };
15374 if (prog_pipe[1] != -1) switch (posix.errno(
15375 posix.system.fcntl(prog_pipe[1], posix.F.SETFD, @as(u32, 0)),
15376 )) {
15377 .SUCCESS => {},
15378 else => |err| forkBail(ep1, posix.unexpectedErrno(err)),
15379 };
15380 for (
15381 [_]process.SpawnOptions.StdIo{ options.stdin, options.stdout, options.stderr },
15382 [_]posix.fd_t{ stdin_pipe[0], stdout_pipe[1], stderr_pipe[1] },
15383 [_]posix.fd_t{ posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO },
15384 ) |stdio, pipe_fd, std_fd| switch (stdio) {
15385 .pipe => dup2(pipe_fd, std_fd) catch |err| forkBail(ep1, err),
15386 .close => closeFd(std_fd),
15387 .inherit => {},
15388 .ignore => dup2(dev_null_fd, std_fd) catch |err| forkBail(ep1, err),
15389 .file => |file| dup2(file.handle, std_fd) catch |err| forkBail(ep1, err),
15390 };
1528715391
1528815392 if (options.gid) |gid| {
1528915393 switch (posix.errno(posix.system.setregid(gid, gid))) {
......@@ -15316,14 +15420,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1531615420 }
1531715421
1531815422 if (options.start_suspended) {
15319 switch (posix.errno(posix.system.kill(0, .STOP))) {
15423 switch (posix.errno(posix.system.kill(posix.system.getpid(), .STOP))) {
1532015424 .SUCCESS => {},
1532115425 .PERM => forkBail(ep1, error.PermissionDenied),
1532215426 else => forkBail(ep1, error.Unexpected),
1532315427 }
1532415428 }
1532515429
15326 const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
15430 const err = posixExec(options.exe, argv_buf.ptr, options.expand_arg0, env_block, PATH);
1532715431 forkBail(ep1, err);
1532815432 }
1532915433
......@@ -15359,7 +15463,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1535915463
1536015464fn getDevNullFd(t: *Threaded) !posix.fd_t {
1536115465 {
15362 mutexLock(&t.mutex);
15466 try mutexLock(&t.mutex);
1536315467 defer mutexUnlock(&t.mutex);
1536415468 if (t.null_file.fd != -1) return t.null_file.fd;
1536515469 }
......@@ -15371,7 +15475,7 @@ fn getDevNullFd(t: *Threaded) !posix.fd_t {
1537115475 .SUCCESS => {
1537215476 syscall.finish();
1537315477 const fresh_fd: posix.fd_t = @intCast(rc);
15374 mutexLock(&t.mutex); // Another thread might have won the race.
15478 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
1537515479 defer mutexUnlock(&t.mutex);
1537615480 if (t.null_file.fd != -1) {
1537715481 closeFd(fresh_fd);
......@@ -15746,19 +15850,137 @@ fn destroyPipe(pipe: [2]posix.fd_t) void {
1574615850 if (pipe[0] != pipe[1]) closeFd(pipe[1]);
1574715851}
1574815852
15749fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
15750 switch (stdio) {
15751 .pipe => try dup2(pipe_fd, std_fileno),
15752 .close => closeFd(std_fileno),
15753 .inherit => {},
15754 .ignore => try dup2(dev_null_fd, std_fileno),
15755 .file => |file| try dup2(file.handle, std_fileno),
15756 }
15853fn processSpawnDarwin(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
15854 const t: *Threaded = @ptrCast(@alignCast(userdata));
15855
15856 t.scanEnviron(); // for PATH
15857 const PATH = t.environ.string.PATH orelse default_PATH;
15858
15859 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
15860 defer arena_allocator.deinit();
15861 const arena = arena_allocator.allocator();
15862
15863 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
15864 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr;
15865
15866 const env_block = env_block: {
15867 const prog_fd: i32 = -1;
15868 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
15869 .zig_progress_fd = prog_fd,
15870 });
15871 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
15872 .zig_progress_fd = prog_fd,
15873 });
15874 };
15875
15876 const spawned = try t.spawnDarwin(
15877 .spawn,
15878 options.exe,
15879 argv_buf.ptr,
15880 options.cwd,
15881 env_block,
15882 options.expand_arg0,
15883 options.progress_node,
15884 options.stdin,
15885 options.stdout,
15886 options.stderr,
15887 options.inherit_dirs,
15888 options.inherit_files,
15889 options.pgid,
15890 options.start_suspended,
15891 PATH,
15892 );
15893 return .{
15894 .id = spawned.pid,
15895 .thread_handle = {},
15896 .stdin = spawned.stdin,
15897 .stdout = spawned.stdout,
15898 .stderr = spawned.stderr,
15899 .request_resource_usage_statistics = options.request_resource_usage_statistics,
15900 };
1575715901}
1575815902
1575915903fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
1576015904 const t: *Threaded = @ptrCast(@alignCast(userdata));
1576115905
15906 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
15907 defer arena_allocator.deinit();
15908 const arena = arena_allocator.allocator();
15909
15910 const cwd_w = cwd_w: {
15911 switch (options.cwd) {
15912 .inherit => break :cwd_w null,
15913 .dir => |cwd_dir| {
15914 var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1);
15915 const dir_path = try GetFinalPathNameByHandle(
15916 cwd_dir.handle,
15917 .{},
15918 dir_path_buffer[0..windows.PATH_MAX_WIDE],
15919 );
15920 dir_path_buffer[dir_path.len] = 0;
15921 // Shrink the allocation down to just the path buffer + sentinel
15922 dir_path_buffer = try arena.realloc(dir_path_buffer, dir_path.len + 1);
15923 break :cwd_w dir_path_buffer[0..dir_path.len :0];
15924 },
15925 .path => |cwd| {
15926 break :cwd_w try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd);
15927 },
15928 }
15929 };
15930 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
15931
15932 // If the app name has more than just a filename, then we need to separate
15933 // that into the basename and dirname and use the dirname as an addition to
15934 // the cwd path. This is because NtQueryDirectoryFile cannot accept
15935 // FileName params with path separators.
15936 var app_w_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
15937 // If the app name is absolute, then the cwd will already have the app's dirname in it,
15938 // so only populate app_dir if app name is a relative path with > 0 path separators.
15939 const app_path_w, const app_dir_w, const app_name_w = app: {
15940 const path = switch (options.exe) {
15941 .detect, .search, .path => options.argv[0],
15942 .file => |file| {
15943 const path_w = try GetFinalPathNameByHandle(file.handle, .{}, &app_w_buf);
15944 var path_it: Dir.path.ComponentIterator(.windows, u16) = .init(path_w);
15945 const base = path_it.last().?;
15946 break :app .{
15947 null,
15948 if (path_it.previous()) |dir| dir.path else path_it.root(),
15949 base.name,
15950 };
15951 },
15952 .explicit => |explicit| explicit.path,
15953 };
15954 break :app .{
15955 if (Dir.path.isAbsolute(path)) null else switch (options.exe) {
15956 // The cwd provided by options is in effect when choosing the executable
15957 // path to match POSIX semantics.
15958 .detect, .search => cwd_w,
15959 .path => |dir| try GetFinalPathNameByHandle(dir.handle, .{}, &app_w_buf),
15960 .file => unreachable,
15961 .explicit => |explicit| try GetFinalPathNameByHandle(
15962 explicit.dir.handle,
15963 .{},
15964 &app_w_buf,
15965 ),
15966 },
15967 if (Dir.path.dirname(path)) |app_dir|
15968 try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_dir)
15969 else
15970 null,
15971 try std.unicode.wtf8ToWtf16LeAllocZ(arena, Dir.path.basename(path)),
15972 };
15973 };
15974
15975 const flags: windows.CreateProcessFlags = .{
15976 .create_suspended = options.start_suspended,
15977 .create_unicode_environment = true,
15978 .create_no_window = options.create_no_window,
15979 };
15980
15981 try mutexLock(&t.process_spawn_mutex);
15982 defer mutexUnlock(&t.process_spawn_mutex);
15983
1576215984 const any_ignore =
1576315985 options.stdin == .ignore or
1576415986 options.stdout == .ignore or
......@@ -15800,6 +16022,43 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1580016022 }) else undefined;
1580116023 errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle);
1580216024
16025 defer for (options.inherit_dirs) |inherit_dir| switch (windows.ntdll.NtSetInformationObject(
16026 inherit_dir.handle,
16027 .HandleFlag,
16028 &windows.OBJECT.HANDLE_FLAG{ .INHERIT = false },
16029 @sizeOf(windows.OBJECT.HANDLE_FLAG),
16030 )) {
16031 .SUCCESS => {},
16032 else => |status| windows.unexpectedStatus(status) catch {},
16033 };
16034 for (options.inherit_dirs) |inherit_dir| switch (windows.ntdll.NtSetInformationObject(
16035 inherit_dir.handle,
16036 .HandleFlag,
16037 &windows.OBJECT.HANDLE_FLAG{ .INHERIT = true },
16038 @sizeOf(windows.OBJECT.HANDLE_FLAG),
16039 )) {
16040 .SUCCESS => {},
16041 else => |status| return windows.unexpectedStatus(status),
16042 };
16043 defer for (options.inherit_files) |inherit_file| switch (windows.ntdll.NtSetInformationObject(
16044 inherit_file.handle,
16045 .HandleFlag,
16046 &windows.OBJECT.HANDLE_FLAG{ .INHERIT = false },
16047 @sizeOf(windows.OBJECT.HANDLE_FLAG),
16048 )) {
16049 .SUCCESS => {},
16050 else => |status| windows.unexpectedStatus(status) catch {},
16051 };
16052 for (options.inherit_files) |inherit_file| switch (windows.ntdll.NtSetInformationObject(
16053 inherit_file.handle,
16054 .HandleFlag,
16055 &windows.OBJECT.HANDLE_FLAG{ .INHERIT = true },
16056 @sizeOf(windows.OBJECT.HANDLE_FLAG),
16057 )) {
16058 .SUCCESS => {},
16059 else => |status| return windows.unexpectedStatus(status),
16060 };
16061
1580316062 var siStartInfo: windows.STARTUPINFOW = .{
1580416063 .cb = @sizeOf(windows.STARTUPINFOW),
1580516064 .dwFlags = windows.STARTF_USESTDHANDLES,
......@@ -15877,32 +16136,6 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1587716136 };
1587816137 var piProcInfo: windows.PROCESS.INFORMATION = undefined;
1587916138
15880 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
15881 defer arena_allocator.deinit();
15882 const arena = arena_allocator.allocator();
15883
15884 const cwd_w = cwd_w: {
15885 switch (options.cwd) {
15886 .inherit => break :cwd_w null,
15887 .dir => |cwd_dir| {
15888 var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1);
15889 const dir_path = try GetFinalPathNameByHandle(
15890 cwd_dir.handle,
15891 .{},
15892 dir_path_buffer[0..windows.PATH_MAX_WIDE],
15893 );
15894 dir_path_buffer[dir_path.len] = 0;
15895 // Shrink the allocation down to just the path buffer + sentinel
15896 dir_path_buffer = try arena.realloc(dir_path_buffer, dir_path.len + 1);
15897 break :cwd_w dir_path_buffer[0..dir_path.len :0];
15898 },
15899 .path => |cwd| {
15900 break :cwd_w try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd);
15901 },
15902 }
15903 };
15904 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
15905
1590616139 const env_block = env_block: {
1590716140 const prog_handle = if (options.progress_node.index != .none)
1590816141 prog_pipe[1]
......@@ -15916,45 +16149,6 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1591616149 });
1591716150 };
1591816151
15919 const app_name_wtf8 = options.argv[0];
15920 const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8);
15921
15922 // The cwd provided by options is in effect when choosing the executable
15923 // path to match POSIX semantics.
15924 const cwd_path_w = x: {
15925 // If the app name is absolute, then we need to use its dirname as the cwd
15926 if (app_name_is_absolute) {
15927 const dir = Dir.path.dirname(app_name_wtf8).?;
15928 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, dir);
15929 } else if (cwd_w) |cwd| {
15930 break :x cwd;
15931 } else {
15932 break :x &[_:0]u16{}; // empty for cwd
15933 }
15934 };
15935
15936 // If the app name has more than just a filename, then we need to separate
15937 // that into the basename and dirname and use the dirname as an addition to
15938 // the cwd path. This is because NtQueryDirectoryFile cannot accept
15939 // FileName params with path separators.
15940 const app_basename_wtf8 = Dir.path.basename(app_name_wtf8);
15941 // If the app name is absolute, then the cwd will already have the app's dirname in it,
15942 // so only populate app_dirname if app name is a relative path with > 0 path separators.
15943 const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) Dir.path.dirname(app_name_wtf8) else null;
15944 const app_dirname_w: ?[:0]u16 = x: {
15945 if (maybe_app_dirname_wtf8) |app_dirname_wtf8| {
15946 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_dirname_wtf8);
15947 }
15948 break :x null;
15949 };
15950 const app_name_w = try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_basename_wtf8);
15951
15952 const flags: windows.CreateProcessFlags = .{
15953 .create_suspended = options.start_suspended,
15954 .create_unicode_environment = true,
15955 .create_no_window = options.create_no_window,
15956 };
15957
1595816152 run: {
1595916153 // We have to scan each time because the PEB environment pointer is not stable.
1596016154 const env_strings: WindowsEnvironStrings = .scan();
......@@ -15975,10 +16169,8 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1597516169
1597616170 var dir_buf: std.ArrayList(u16) = .empty;
1597716171
15978 if (cwd_path_w.len > 0) {
15979 try dir_buf.appendSlice(arena, cwd_path_w);
15980 }
15981 if (app_dirname_w) |app_dir| {
16172 if (app_path_w) |app_path| try dir_buf.appendSlice(arena, app_path);
16173 if (app_dir_w) |app_dir| {
1598216174 if (dir_buf.items.len > 0) try dir_buf.append(arena, Dir.path.sep);
1598316175 try dir_buf.appendSlice(arena, app_dir);
1598416176 }
......@@ -16003,15 +16195,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1600316195 else => |e| return e,
1600416196 };
1600516197
16006 // If the app name had path separators, that disallows PATH searching,
16007 // and there's no need to search the PATH if the app name is absolute.
1600816198 // We still search the path if the cwd is absolute because of the
1600916199 // "cwd provided by options is in effect when choosing the executable path
1601016200 // to match posix semantics" behavior--we don't want to skip searching
1601116201 // the PATH just because we were trying to set the cwd of the child process.
16012 if (app_dirname_w != null or app_name_is_absolute) {
16013 return original_err;
16014 }
16202 if (switch (options.exe) {
16203 // If the app name had path separators, that disallows PATH searching.
16204 .detect => app_dir_w != null,
16205 // There's no need to search the PATH if the app name is absolute.
16206 .search => app_path_w == null,
16207 // PATH searching is disallowed.
16208 .path, .file, .explicit => true,
16209 }) return original_err;
1601516210
1601616211 var it = std.mem.tokenizeScalar(u16, PATH, ';');
1601716212 while (it.next()) |search_path| {
......@@ -16089,11 +16284,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1608916284 };
1609016285}
1609116286
16092fn inheritFile() windows.HANDLE {}
16093
1609416287fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1609516288 {
16096 mutexLock(&t.mutex);
16289 try mutexLock(&t.mutex);
1609716290 defer mutexUnlock(&t.mutex);
1609816291 if (t.random_file.handle) |handle| return handle;
1609916292 }
......@@ -16116,7 +16309,7 @@ fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1611616309 )) {
1611716310 .SUCCESS => {
1611816311 syscall.finish();
16119 mutexLock(&t.mutex); // Another thread might have won the race.
16312 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
1612016313 defer mutexUnlock(&t.mutex);
1612116314 if (t.random_file.handle) |prev_handle| {
1612216315 windows.CloseHandle(fresh_handle);
......@@ -16137,7 +16330,7 @@ fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1613716330
1613816331fn getNulDevice(t: *Threaded) !windows.HANDLE {
1613916332 {
16140 mutexLock(&t.mutex);
16333 try mutexLock(&t.mutex);
1614116334 defer mutexUnlock(&t.mutex);
1614216335 if (t.null_file.handle) |handle| return handle;
1614316336 }
......@@ -16163,7 +16356,7 @@ fn getNulDevice(t: *Threaded) !windows.HANDLE {
1616316356 )) {
1616416357 .SUCCESS => {
1616516358 syscall.finish();
16166 mutexLock(&t.mutex); // Another thread might have won the race.
16359 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
1616716360 defer mutexUnlock(&t.mutex);
1616816361 if (t.null_file.handle) |prev_handle| {
1616916362 windows.CloseHandle(fresh_handle);
......@@ -16196,7 +16389,7 @@ fn getNulDevice(t: *Threaded) !windows.HANDLE {
1619616389
1619716390fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE {
1619816391 {
16199 mutexLock(&t.mutex);
16392 try mutexLock(&t.mutex);
1620016393 defer mutexUnlock(&t.mutex);
1620116394 if (t.pipe_file.handle) |handle| return handle;
1620216395 }
......@@ -16218,7 +16411,7 @@ fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE {
1621816411 )) {
1621916412 .SUCCESS => {
1622016413 syscall.finish();
16221 mutexLock(&t.mutex); // Another thread might have won the race.
16414 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
1622216415 defer mutexUnlock(&t.mutex);
1622316416 if (t.pipe_file.handle) |prev_handle| {
1622416417 windows.CloseHandle(fresh_handle);
......@@ -16503,7 +16696,15 @@ fn windowsCreateProcessPathExt(
1650316696 else
1650416697 full_app_name;
1650516698
16506 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, env_block, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
16699 if (windowsCreateProcess(
16700 app_name_w.ptr,
16701 cmd_line_w.ptr,
16702 env_block,
16703 cwd_ptr,
16704 flags,
16705 lpStartupInfo,
16706 lpProcessInformation,
16707 )) |_| {
1650716708 return;
1650816709 } else |err| switch (err) {
1650916710 error.FileNotFound => continue,
......@@ -17037,61 +17238,112 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c
1703717238 try std.testing.expectEqualStrings(expected_cmd_line, cmd_line);
1703817239}
1703917240
17040fn posixExecv(
17041 arg0_expand: process.ArgExpansion,
17042 file: [*:0]const u8,
17043 child_argv: [*:null]?[*:0]const u8,
17241fn posixExec(
17242 exe: process.ReplaceOptions.Exe,
17243 argv: [*:null]?[*:0]const u8,
17244 expand_arg0: process.ArgExpansion,
1704417245 env_block: process.Environ.PosixBlock,
1704517246 PATH: []const u8,
1704617247) process.ReplaceError {
17047 const file_slice = std.mem.sliceTo(file, 0);
17048 if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, env_block);
17049
17050 // Use of PATH_MAX here is valid as the path_buf will be passed
17051 // directly to the operating system in posixExecvPath.
17052 var path_buf: [posix.PATH_MAX]u8 = undefined;
17053 var it = std.mem.tokenizeScalar(u8, PATH, ':');
17054 var seen_eacces = false;
17055 var err: process.ReplaceError = error.FileNotFound;
17056
17057 // In case of expanding arg0 we must put it back if we return with an error.
17058 const prev_arg0 = child_argv[0];
17059 defer switch (arg0_expand) {
17060 .expand => child_argv[0] = prev_arg0,
17061 .no_expand => {},
17062 };
17248 const arg0_slice = std.mem.span(argv[0].?);
17249 exe: switch (exe) {
17250 .detect => continue :exe if (std.mem.findScalar(u8, arg0_slice, '/')) |_| .{
17251 .path = .cwd(),
17252 } else .search,
17253 .search => {
17254 if (Dir.path.isAbsolute(arg0_slice)) continue :exe .{ .path = .cwd() };
17255 // Use of PATH_MAX here is valid as the path_buf will be passed
17256 // directly to the operating system in posixExecveat.
17257 var path_buf: [posix.PATH_MAX]u8 = undefined;
17258 var it = std.mem.tokenizeScalar(u8, PATH, ':');
17259 var err: error{ AccessDenied, FileNotFound, NotDir } = error.FileNotFound;
1706317260
17064 while (it.next()) |search_path| {
17065 const path_len = search_path.len + file_slice.len + 1;
17066 if (path_buf.len < path_len + 1) return error.NameTooLong;
17067 @memcpy(path_buf[0..search_path.len], search_path);
17068 path_buf[search_path.len] = '/';
17069 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);
17070 path_buf[path_len] = 0;
17071 const full_path = path_buf[0..path_len :0].ptr;
17072 switch (arg0_expand) {
17073 .expand => child_argv[0] = full_path,
17074 .no_expand => {},
17075 }
17076 err = posixExecvPath(full_path, child_argv, env_block);
17077 switch (err) {
17078 error.AccessDenied => seen_eacces = true,
17079 error.FileNotFound, error.NotDir => {},
17080 else => |e| return e,
17081 }
17261 while (it.next()) |search_path| {
17262 const path_len = search_path.len + arg0_slice.len + 1;
17263 if (path_buf.len < path_len + 1) return error.NameTooLong;
17264 @memcpy(path_buf[0..search_path.len], search_path);
17265 path_buf[search_path.len] = Dir.path.sep;
17266 @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice);
17267 path_buf[path_len] = 0;
17268 const full_path = path_buf[0..path_len :0].ptr;
17269 switch (expand_arg0) {
17270 .expand => argv[0] = full_path,
17271 .no_expand => {},
17272 }
17273 switch (posixExecveat(posix.AT.FDCWD, full_path, argv, env_block)) {
17274 error.AccessDenied, error.FileNotFound, error.NotDir => |e| switch (err) {
17275 error.AccessDenied => {},
17276 error.FileNotFound, error.NotDir => err = e,
17277 },
17278 else => |e| return e,
17279 }
17280 }
17281 return err;
17282 },
17283 .path => |path| return posixExecveat(path.handle, arg0_slice, argv, env_block),
17284 .file => |file| return posixExecveat(file.handle, null, argv, env_block),
17285 .explicit => |explicit| {
17286 var path_buffer: [posix.PATH_MAX]u8 = undefined;
17287 const path_posix = try pathToPosix(explicit.path, &path_buffer);
17288 return posixExecveat(explicit.dir.handle, path_posix, argv, env_block);
17289 },
1708217290 }
17083 if (seen_eacces) return error.AccessDenied;
17084 return err;
1708517291}
1708617292
1708717293/// This function ignores PATH environment variable.
17088pub fn posixExecvPath(
17089 path: [*:0]const u8,
17090 child_argv: [*:null]const ?[*:0]const u8,
17294pub fn posixExecveat(
17295 dir: posix.fd_t,
17296 path: ?[*:0]const u8,
17297 argv: [*:null]const ?[*:0]const u8,
1709117298 env_block: process.Environ.PosixBlock,
1709217299) process.ReplaceError {
1709317300 try Thread.checkCancel();
17094 switch (posix.errno(posix.system.execve(path, child_argv, env_block.slice.ptr))) {
17301 switch (err: {
17302 if (path) |p| if (dir == posix.AT.FDCWD or Dir.path.isAbsoluteZ(p)) {
17303 break :err posix.errno(posix.system.execve(p, argv, env_block.slice.ptr));
17304 };
17305 if (comptime native_os == .linux and (!builtin.link_libc or
17306 (builtin.target.isGnuLibC() and builtin.target.os.version_range.linux.glibc.order(
17307 .{ .major = 2, .minor = 34, .patch = 0 },
17308 ).compare(.gte))))
17309 {
17310 switch (posix.errno(posix.system.execveat(dir, path orelse "", argv, env_block.slice.ptr, .{
17311 .EMPTY_PATH = path == null,
17312 .SYMLINK_NOFOLLOW = false,
17313 }))) {
17314 else => |err| break :err err,
17315 .NOSYS => {},
17316 }
17317 }
17318 var path_buffer: [posix.PATH_MAX]u8 = undefined;
17319 var path_len = realPathPosix(dir, &path_buffer) catch |err| switch (err) {
17320 else => |e| return e,
17321 error.InputOutput,
17322 error.FileTooBig,
17323 error.NoSpaceLeft,
17324 error.PathAlreadyExists,
17325 error.SymLinkLoop,
17326 error.NetworkNotFound,
17327 error.PipeBusy,
17328 error.AntivirusInterference,
17329 => return error.FileNotFound,
17330 error.DeviceBusy,
17331 error.NoDevice,
17332 error.UnrecognizedVolume,
17333 => return error.FileSystem,
17334 };
17335 if (path) |p| {
17336 const path_slice = std.mem.span(p);
17337 path_buffer[path_len] = Dir.path.sep;
17338 path_len += 1;
17339 @memcpy(path_buffer[path_len..][0..path_slice.len], path_slice);
17340 path_len += path_slice.len;
17341 }
17342 path_buffer[path_len] = 0;
17343 break :err posix.errno(
17344 posix.system.execve(path_buffer[0..path_len :0], argv, env_block.slice.ptr),
17345 );
17346 }) {
1709517347 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
1709617348 .@"2BIG" => return error.SystemResources,
1709717349 .MFILE => return error.ProcessFdQuotaExceeded,
......@@ -17123,6 +17375,334 @@ pub fn posixExecvPath(
1712317375 }
1712417376}
1712517377
17378fn spawnDarwin(
17379 t: *Threaded,
17380 mode: enum { spawn, replace },
17381 exe: process.ReplaceOptions.Exe,
17382 argv: [*:null]?[*:0]const u8,
17383 cwd: process.Child.Cwd,
17384 env_block: process.Environ.PosixBlock,
17385 expand_arg0: process.ArgExpansion,
17386 progress_node: std.Progress.Node,
17387 stdin: process.SpawnOptions.StdIo,
17388 stdout: process.SpawnOptions.StdIo,
17389 stderr: process.SpawnOptions.StdIo,
17390 inherit_dirs: []const Dir,
17391 inherit_files: []const File,
17392 pgid: ?posix.pid_t,
17393 start_suspended: bool,
17394 PATH: []const u8,
17395) (process.SpawnError || process.ReplaceError)!Spawned {
17396 var fa: std.c.posix_spawn_file_actions_t = undefined;
17397 switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawn_file_actions_init(&fa))))) {
17398 .SUCCESS => {},
17399 .NOMEM => return error.SystemResources,
17400 else => |err| return posix.unexpectedErrno(err),
17401 }
17402 defer switch (@as(std.c.E, @fromBackingInt(@intCast(
17403 std.c.posix_spawn_file_actions_destroy(&fa),
17404 )))) {
17405 .SUCCESS => {},
17406 .INVAL => recoverableOsBugDetected(),
17407 else => recoverableOsBugDetected(),
17408 };
17409
17410 var attr: std.c.posix_spawnattr_t = undefined;
17411 switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawnattr_init(&attr))))) {
17412 .SUCCESS => {},
17413 .NOMEM => return error.SystemResources,
17414 else => |err| return posix.unexpectedErrno(err),
17415 }
17416 defer switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawnattr_destroy(&attr))))) {
17417 .SUCCESS => {},
17418 .INVAL => recoverableOsBugDetected(),
17419 else => recoverableOsBugDetected(),
17420 };
17421
17422 // The child process does need to access (one end of) these pipes. However,
17423 // we must initially set CLOEXEC to avoid a race condition. If another thread
17424 // is racing to spawn a different child process, we don't want it to inherit
17425 // these FDs in any scenario; that would mean that, for instance, calls to
17426 // `poll` from the parent would not report the child's stdout as closing when
17427 // expected, since the other child may retain a reference to the write end of
17428 // the pipe. So, we create the pipes with CLOEXEC initially. After fork, we
17429 // need to do something in the new child to make sure we preserve the reference
17430 // we want. We could use `fcntl` to remove CLOEXEC from the FD, but as it
17431 // turns out, we `dup2` everything anyway, so there's no need!
17432 const pipe_flags: posix.O = .{ .CLOEXEC = true };
17433
17434 const stdin_pipe = if (stdin == .pipe) try pipe2(pipe_flags) else undefined;
17435 errdefer if (stdin == .pipe) {
17436 destroyPipe(stdin_pipe);
17437 };
17438
17439 const stdout_pipe = if (stdout == .pipe) try pipe2(pipe_flags) else undefined;
17440 errdefer if (stdout == .pipe) {
17441 destroyPipe(stdout_pipe);
17442 };
17443
17444 const stderr_pipe = if (stderr == .pipe) try pipe2(pipe_flags) else undefined;
17445 errdefer if (stderr == .pipe) {
17446 destroyPipe(stderr_pipe);
17447 };
17448
17449 const any_ignore = (stdin == .ignore or stdout == .ignore or stderr == .ignore);
17450 const dev_null_fd = if (any_ignore) try t.getDevNullFd() else undefined;
17451
17452 const prog_pipe: [2]posix.fd_t = if (progress_node.index != .none) pipe: {
17453 // We use CLOEXEC for the same reason as in `pipe_flags`.
17454 break :pipe try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true });
17455 } else .{ -1, -1 };
17456 errdefer destroyPipe(prog_pipe);
17457
17458 var cwd_buffer: [posix.PATH_MAX]u8 = undefined;
17459 switch (cwd) {
17460 .inherit => {},
17461 .dir => |dir| switch (@as(std.c.E, @fromBackingInt(@intCast(
17462 std.c.posix_spawn_file_actions_addfchdir_np(&fa, dir.handle),
17463 )))) {
17464 .SUCCESS => {},
17465 .BADF => |err| return errnoBug(err),
17466 .INVAL => |err| return errnoBug(err),
17467 .NOMEM => return error.SystemResources,
17468 else => |err| return posix.unexpectedErrno(err),
17469 },
17470 .path => |path| switch (@as(std.c.E, @fromBackingInt(@intCast(
17471 std.c.posix_spawn_file_actions_addchdir_np(&fa, try pathToPosix(path, &cwd_buffer)),
17472 )))) {
17473 .SUCCESS => {},
17474 .BADF => |err| return errnoBug(err),
17475 .INVAL => |err| return errnoBug(err),
17476 .NAMETOOLONG => return error.NameTooLong,
17477 .NOMEM => return error.SystemResources,
17478 else => |err| return posix.unexpectedErrno(err),
17479 },
17480 }
17481
17482 for (inherit_dirs) |inherit_dir| switch (@as(std.c.E, @fromBackingInt(@intCast(
17483 std.c.posix_spawn_file_actions_addinherit_np(&fa, inherit_dir.handle),
17484 )))) {
17485 .SUCCESS => {},
17486 .BADF => |err| return errnoBug(err),
17487 .INVAL => |err| return errnoBug(err),
17488 .NOMEM => return error.SystemResources,
17489 else => |err| return posix.unexpectedErrno(err),
17490 };
17491 for (inherit_files) |inherit_file| switch (@as(std.c.E, @fromBackingInt(@intCast(
17492 std.c.posix_spawn_file_actions_addinherit_np(&fa, inherit_file.handle),
17493 )))) {
17494 .SUCCESS => {},
17495 .BADF => |err| return errnoBug(err),
17496 .INVAL => |err| return errnoBug(err),
17497 .NOMEM => return error.SystemResources,
17498 else => |err| return posix.unexpectedErrno(err),
17499 };
17500 if (prog_pipe[1] != -1) switch (@as(std.c.E, @fromBackingInt(@intCast(
17501 std.c.posix_spawn_file_actions_addinherit_np(&fa, prog_pipe[1]),
17502 )))) {
17503 .SUCCESS => {},
17504 .BADF => |err| return errnoBug(err),
17505 .INVAL => |err| return errnoBug(err),
17506 .NOMEM => return error.SystemResources,
17507 else => |err| return posix.unexpectedErrno(err),
17508 };
17509 for (
17510 [_]process.SpawnOptions.StdIo{ stdin, stdout, stderr },
17511 [_]posix.fd_t{ stdin_pipe[0], stdout_pipe[1], stderr_pipe[1] },
17512 [_]posix.fd_t{ posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO },
17513 ) |stdio, pipe_fd, std_fd| switch (@as(std.c.E, @fromBackingInt(@intCast(switch (stdio) {
17514 .pipe => std.c.posix_spawn_file_actions_adddup2(&fa, pipe_fd, std_fd),
17515 .close => std.c.posix_spawn_file_actions_addclose(&fa, std_fd),
17516 .inherit => std.c.posix_spawn_file_actions_addinherit_np(&fa, std_fd),
17517 .ignore => std.c.posix_spawn_file_actions_adddup2(&fa, dev_null_fd, std_fd),
17518 .file => |file| std.c.posix_spawn_file_actions_adddup2(&fa, file.handle, std_fd),
17519 })))) {
17520 .SUCCESS => {},
17521 .BADF => |err| return errnoBug(err),
17522 .INVAL => |err| return errnoBug(err),
17523 .NOMEM => return error.SystemResources,
17524 else => |err| return posix.unexpectedErrno(err),
17525 };
17526
17527 if (pgid) |pid| switch (@as(std.c.E, @fromBackingInt(@intCast(
17528 std.c.posix_spawnattr_setpgroup(&attr, pid),
17529 )))) {
17530 .SUCCESS => {},
17531 .INVAL => return error.InvalidProcessGroupId,
17532 else => |err| return posix.unexpectedErrno(err),
17533 };
17534
17535 switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawnattr_setflags(&attr, .{
17536 .SETPGROUP = pgid != null,
17537 .SETEXEC = switch (mode) {
17538 .spawn => false,
17539 .replace => true,
17540 },
17541 .START_SUSPENDED = start_suspended,
17542 }))))) {
17543 .SUCCESS => {},
17544 .INVAL => |err| return errnoBug(err),
17545 else => |err| return posix.unexpectedErrno(err),
17546 }
17547
17548 var pid: posix.pid_t = undefined;
17549 const arg0_slice = std.mem.span(argv[0].?);
17550 var path_buffer: [posix.PATH_MAX]u8 = undefined;
17551 switch (err: {
17552 break :err @as(std.c.E, @fromBackingInt(@intCast(exe: switch (exe) {
17553 .detect => if (std.mem.findScalar(u8, arg0_slice, '/')) |_|
17554 std.c.posix_spawn(&pid, arg0_slice, &fa, &attr, argv, env_block.slice.ptr)
17555 else
17556 continue :exe .search,
17557 .search => if (!Dir.path.isAbsolute(arg0_slice)) {
17558 // Use of PATH_MAX here is valid as the path_buf will be passed
17559 // directly to the operating system in posixExecveat.
17560 var path_buf: [posix.PATH_MAX]u8 = undefined;
17561 var it = std.mem.tokenizeScalar(u8, PATH, ':');
17562 var err: error{ AccessDenied, FileNotFound, NotDir } = error.FileNotFound;
17563
17564 while (it.next()) |search_path| {
17565 const path_len = search_path.len + arg0_slice.len + 1;
17566 if (path_buf.len < path_len + 1) return error.NameTooLong;
17567 @memcpy(path_buf[0..search_path.len], search_path);
17568 path_buf[search_path.len] = Dir.path.sep;
17569 @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice);
17570 path_buf[path_len] = 0;
17571 const full_path = path_buf[0..path_len :0].ptr;
17572 switch (expand_arg0) {
17573 .expand => argv[0] = full_path,
17574 .no_expand => {},
17575 }
17576 switch (@as(std.c.E, @fromBackingInt(@intCast(
17577 std.c.posix_spawn(&pid, full_path, &fa, &attr, argv, env_block.slice.ptr),
17578 )))) {
17579 else => |e| break :err e,
17580 .ACCES, .NOENT, .NOTDIR => |e| switch (err) {
17581 error.AccessDenied => {},
17582 error.FileNotFound, error.NotDir => err = switch (e) {
17583 else => unreachable,
17584 .ACCES => error.AccessDenied,
17585 .NOENT => error.FileNotFound,
17586 .NOTDIR => error.NotDir,
17587 },
17588 },
17589 }
17590 }
17591 return err;
17592 } else std.c.posix_spawn(&pid, arg0_slice, &fa, &attr, argv, env_block.slice.ptr),
17593 .path => |path| if (path.handle != posix.AT.FDCWD and !Dir.path.isAbsolute(arg0_slice)) {
17594 var path_len = realPathPosix(path.handle, &path_buffer) catch |err| switch (err) {
17595 else => |e| return e,
17596 error.InputOutput => return error.FileNotFound,
17597 };
17598 path_buffer[path_len] = Dir.path.sep;
17599 path_len += 1;
17600 @memcpy(path_buffer[path_len..][0..arg0_slice.len], arg0_slice);
17601 path_len += arg0_slice.len;
17602 path_buffer[path_len] = 0;
17603 break :exe std.c.posix_spawn(
17604 &pid,
17605 path_buffer[0..path_len :0],
17606 &fa,
17607 &attr,
17608 argv,
17609 env_block.slice.ptr,
17610 );
17611 } else std.c.posix_spawn(&pid, arg0_slice, &fa, &attr, argv, env_block.slice.ptr),
17612 .file => |file| {
17613 const path_len = realPathPosix(file.handle, &path_buffer) catch |err| switch (err) {
17614 else => |e| return e,
17615 error.InputOutput => return error.FileNotFound,
17616 };
17617 path_buffer[path_len] = 0;
17618 break :exe std.c.posix_spawn(
17619 &pid,
17620 path_buffer[0..path_len :0],
17621 &fa,
17622 &attr,
17623 argv,
17624 env_block.slice.ptr,
17625 );
17626 },
17627 .explicit => |explicit| if (explicit.dir.handle != posix.AT.FDCWD and
17628 !Dir.path.isAbsolute(explicit.path))
17629 {
17630 var path_len = realPathPosix(
17631 explicit.dir.handle,
17632 &path_buffer,
17633 ) catch |err| switch (err) {
17634 else => |e| return e,
17635 error.InputOutput => return error.FileNotFound,
17636 };
17637 path_buffer[path_len] = Dir.path.sep;
17638 path_len += 1;
17639 @memcpy(path_buffer[path_len..][0..explicit.path.len], explicit.path);
17640 path_len += explicit.path.len;
17641 path_buffer[path_len] = 0;
17642 break :exe std.c.posix_spawn(
17643 &pid,
17644 path_buffer[0..path_len :0],
17645 &fa,
17646 &attr,
17647 argv,
17648 env_block.slice.ptr,
17649 );
17650 } else std.c.posix_spawn(
17651 &pid,
17652 try pathToPosix(explicit.path, &path_buffer),
17653 &fa,
17654 &attr,
17655 argv,
17656 env_block.slice.ptr,
17657 ),
17658 })));
17659 }) {
17660 .SUCCESS => {},
17661 .AGAIN => return error.SystemResources,
17662 .INVAL => |err| return errnoBug(err),
17663 .@"2BIG" => return error.SystemResources,
17664 .ACCES => return error.AccessDenied,
17665 .FAULT => |err| return errnoBug(err),
17666 .IO => return error.FileSystem,
17667 .LOOP => return error.FileSystem,
17668 .NAMETOOLONG => return error.NameTooLong,
17669 .NOENT => return error.FileNotFound,
17670 .NOEXEC => return error.InvalidExe,
17671 .NOMEM => return error.SystemResources,
17672 .NOTDIR => return error.NotDir,
17673 .TXTBSY => return error.FileBusy,
17674 .BADARCH => return error.InvalidExe,
17675 .BADF => |err| return errnoBug(err),
17676 else => |err| return posix.unexpectedErrno(err),
17677 }
17678
17679 errdefer comptime unreachable; // The child is spawned; we must not error from now on
17680
17681 if (stdin == .pipe) closeFd(stdin_pipe[0]);
17682 if (stdout == .pipe) closeFd(stdout_pipe[1]);
17683 if (stderr == .pipe) closeFd(stderr_pipe[1]);
17684
17685 if (prog_pipe[1] != -1) closeFd(prog_pipe[1]);
17686 progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
17687
17688 return .{
17689 .pid = pid,
17690 .err_fd = -1,
17691 .stdin = switch (stdin) {
17692 .pipe => .{ .handle = stdin_pipe[1], .flags = .{ .nonblocking = false } },
17693 else => null,
17694 },
17695 .stdout = switch (stdout) {
17696 .pipe => .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = false } },
17697 else => null,
17698 },
17699 .stderr = switch (stderr) {
17700 .pipe => .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = false } },
17701 else => null,
17702 },
17703 };
17704}
17705
1712617706pub const CreatePipeOptions = struct {
1712717707 server: End,
1712817708 client: End,
......@@ -17224,6 +17804,53 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
1722417804 return t.environ.zig_progress_file;
1722517805}
1722617806
17807fn inheritParentHandle(handle: posix.fd_t) Io.InheritParentHandleError!void {
17808 switch (native_os) {
17809 .linux,
17810 .dragonfly,
17811 .freebsd,
17812 .netbsd,
17813 .openbsd,
17814 .illumos,
17815 .driverkit,
17816 .ios,
17817 .maccatalyst,
17818 .macos,
17819 .tvos,
17820 .visionos,
17821 .watchos,
17822 => return setCloexec(handle),
17823 .windows => switch (windows.ntdll.NtSetInformationObject(
17824 handle,
17825 .HandleFlag,
17826 &windows.OBJECT.HANDLE_FLAG{ .INHERIT = false },
17827 @sizeOf(windows.OBJECT.HANDLE_FLAG),
17828 )) {
17829 .SUCCESS => return,
17830 else => |status| return windows.unexpectedStatus(status),
17831 },
17832 else => return error.UnsupportedOperation,
17833 }
17834}
17835
17836fn inheritParentDir(userdata: ?*anyopaque, handle: Dir.Handle) Io.InheritParentHandleError!Dir {
17837 const t: *Threaded = @ptrCast(@alignCast(userdata));
17838 _ = t;
17839 try inheritParentHandle(handle);
17840 return .{ .handle = handle };
17841}
17842
17843fn inheritParentFile(
17844 userdata: ?*anyopaque,
17845 handle: File.Handle,
17846 flags: File.Flags,
17847) Io.InheritParentHandleError!File {
17848 const t: *Threaded = @ptrCast(@alignCast(userdata));
17849 _ = t;
17850 try inheritParentHandle(handle);
17851 return .{ .handle = handle, .flags = flags };
17852}
17853
1722717854pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {
1722817855 t.scanEnviron();
1722917856 return @field(t.environ.string, name);
......@@ -17242,7 +17869,7 @@ fn random(userdata: ?*anyopaque, buffer: []u8) void {
1724217869}
1724317870
1724417871fn randomMainThread(t: *Threaded, buffer: []u8) void {
17245 mutexLock(&t.mutex);
17872 mutexLockUncancelable(&t.mutex);
1724617873 defer mutexUnlock(&t.mutex);
1724717874
1724817875 if (!t.csprng.isInitialized()) {
......@@ -17250,7 +17877,7 @@ fn randomMainThread(t: *Threaded, buffer: []u8) void {
1725017877 var seed: [Csprng.seed_len]u8 = undefined;
1725117878 {
1725217879 mutexUnlock(&t.mutex);
17253 defer mutexLock(&t.mutex);
17880 defer mutexLockUncancelable(&t.mutex);
1725417881
1725517882 const prev = swapCancelProtection(t, .blocked);
1725617883 defer _ = swapCancelProtection(t, prev);
......@@ -17437,7 +18064,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {
1743718064
1743818065fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {
1743918066 {
17440 mutexLock(&t.mutex);
18067 try mutexLock(&t.mutex);
1744118068 defer mutexUnlock(&t.mutex);
1744218069
1744318070 if (t.random_file.fd == -2) return error.EntropyUnavailable;
......@@ -17478,7 +18105,7 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {
1747818105 .SUCCESS => {
1747918106 syscall.finish();
1748018107 if (!statx.mask.TYPE) return error.EntropyUnavailable;
17481 mutexLock(&t.mutex); // Another thread might have won the race.
18108 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
1748218109 defer mutexUnlock(&t.mutex);
1748318110 if (t.random_file.fd >= 0) {
1748418111 closeFd(fd);
......@@ -17506,7 +18133,7 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {
1750618133 switch (posix.errno(fstat_sym(fd, &stat))) {
1750718134 .SUCCESS => {
1750818135 syscall.finish();
17509 mutexLock(&t.mutex); // Another thread might have won the race.
18136 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
1751018137 defer mutexUnlock(&t.mutex);
1751118138 if (t.random_file.fd >= 0) {
1751218139 closeFd(fd);
......@@ -18434,7 +19061,7 @@ const CreateFileMapError = error{
1843419061
1843519062fn createFileMap(
1843619063 file: File,
18437 protection: std.process.MemoryProtection,
19064 protection: process.MemoryProtection,
1843819065 offset: u64,
1843919066 populate: bool,
1844019067 len: usize,
......@@ -19062,7 +19689,7 @@ fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void {
1906219689 }
1906319690
1906419691 mutexUnlock(mutex);
19065 defer mutexLock(mutex);
19692 defer mutexLockUncancelable(mutex);
1906619693
1906719694 while (true) {
1906819695 Thread.futexWaitUncancelable(&cond.epoch.raw, epoch, null);
......@@ -19082,8 +19709,27 @@ fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void {
1908219709 }
1908319710}
1908419711
19712/// Same as `Io.Mutex.lock` but avoids the VTable.
19713pub fn mutexLock(m: *Io.Mutex) Io.Cancelable!void {
19714 const initial_state = m.state.cmpxchgStrong(
19715 .unlocked,
19716 .locked_once,
19717 .acquire,
19718 .monotonic,
19719 ) orelse {
19720 @branchHint(.likely);
19721 return;
19722 };
19723 if (initial_state == .contended) {
19724 try Thread.futexWait(@ptrCast(&m.state.raw), @backingInt(Io.Mutex.State.contended), null);
19725 }
19726 while (m.state.swap(.contended, .acquire) != .unlocked) {
19727 try Thread.futexWait(@ptrCast(&m.state.raw), @backingInt(Io.Mutex.State.contended), null);
19728 }
19729}
19730
1908519731/// Same as `Io.Mutex.lockUncancelable` but avoids the VTable.
19086pub fn mutexLock(m: *Io.Mutex) void {
19732pub fn mutexLockUncancelable(m: *Io.Mutex) void {
1908719733 const initial_state = m.state.cmpxchgStrong(
1908819734 .unlocked,
1908919735 .locked_once,
lib/std/bit_set.zig+2-7
......@@ -723,13 +723,8 @@ pub const Dynamic = struct {
723723 // That slot holds the size of the true allocation, which
724724 // is needed by Zig's allocator interface in case a shrink
725725 // fails.
726
727 // Don't modify this value. Ideally it would go in const data so
728 // modifications would cause a bus error, but the only way
729 // to discard a const qualifier is through intFromPtr, which
730 // cannot currently round trip at comptime.
731 var empty_masks_data = [_]MaskInt{ 0, undefined };
732 const empty_masks_ptr = empty_masks_data[1..2];
726 const empty_masks_data = [_]MaskInt{ 0, undefined };
727 const empty_masks_ptr = @constCast(empty_masks_data[1..2]);
733728
734729 /// Creates a bit set with no elements present.
735730 /// If bit_length is not zero, deinit must eventually be called.
lib/std/c.zig+8
......@@ -8809,6 +8809,11 @@ pub const O = switch (native_os) {
88098809 else => void,
88108810};
88118811
8812pub const EXECVEAT = switch (native_os) {
8813 .linux => linux.EXECVEAT,
8814 else => void,
8815};
8816
88128817pub const MAP = switch (native_os) {
88138818 .linux => linux.MAP,
88148819 .emscripten => packed struct(u32) {
......@@ -11045,6 +11050,7 @@ pub extern "c" fn renameat(olddirfd: fd_t, old: [*:0]const u8, newdirfd: fd_t, n
1104511050pub extern "c" fn chdir(path: [*:0]const u8) c_int;
1104611051pub extern "c" fn fchdir(fd: fd_t) c_int;
1104711052pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int;
11053pub extern "c" fn execveat(dirfd: fd_t, path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8, flags: EXECVEAT) c_int;
1104811054pub extern "c" fn dup(fd: fd_t) c_int;
1104911055pub extern "c" fn dup2(old_fd: fd_t, new_fd: fd_t) c_int;
1105011056pub extern "c" fn dup3(old: c_int, new: c_int, flags: c_uint) c_int;
......@@ -11779,8 +11785,10 @@ pub const posix_spawn_file_actions_init = darwin.posix_spawn_file_actions_init;
1177911785pub const posix_spawn_file_actions_t = darwin.posix_spawn_file_actions_t;
1178011786pub const posix_spawnattr_destroy = darwin.posix_spawnattr_destroy;
1178111787pub const posix_spawnattr_getflags = darwin.posix_spawnattr_getflags;
11788pub const posix_spawnattr_getpgroup = darwin.posix_spawnattr_getpgroup;
1178211789pub const posix_spawnattr_init = darwin.posix_spawnattr_init;
1178311790pub const posix_spawnattr_setflags = darwin.posix_spawnattr_setflags;
11791pub const posix_spawnattr_setpgroup = darwin.posix_spawnattr_setpgroup;
1178411792pub const posix_spawnattr_t = darwin.posix_spawnattr_t;
1178511793pub const posix_spawnp = darwin.posix_spawnp;
1178611794pub const pthread_attr_get_qos_class_np = darwin.pthread_attr_get_qos_class_np;
lib/std/c/darwin.zig+3-1
......@@ -1253,8 +1253,10 @@ pub const posix_spawnattr_t = *opaque {};
12531253pub const posix_spawn_file_actions_t = *opaque {};
12541254pub extern "c" fn posix_spawnattr_init(attr: *posix_spawnattr_t) c_int;
12551255pub extern "c" fn posix_spawnattr_destroy(attr: *posix_spawnattr_t) c_int;
1256pub extern "c" fn posix_spawnattr_setflags(attr: *posix_spawnattr_t, flags: POSIX_SPAWN) c_int;
12571256pub extern "c" fn posix_spawnattr_getflags(attr: *const posix_spawnattr_t, flags: *POSIX_SPAWN) c_int;
1257pub extern "c" fn posix_spawnattr_getpgroup(attr: *const posix_spawnattr_t, pgroup: *pid_t) c_int;
1258pub extern "c" fn posix_spawnattr_setflags(attr: *posix_spawnattr_t, flags: POSIX_SPAWN) c_int;
1259pub extern "c" fn posix_spawnattr_setpgroup(attr: *posix_spawnattr_t, pgroup: pid_t) c_int;
12581260pub extern "c" fn posix_spawn_file_actions_init(actions: *posix_spawn_file_actions_t) c_int;
12591261pub extern "c" fn posix_spawn_file_actions_destroy(actions: *posix_spawn_file_actions_t) c_int;
12601262pub extern "c" fn posix_spawn_file_actions_addclose(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int;
lib/std/fs/path.zig+32-32
......@@ -1438,7 +1438,7 @@ pub fn dirnamePosix(path: []const u8) ?[]const u8 {
14381438}
14391439
14401440fn dirnameInner(comptime path_type: PathType, path: []const u8) ?[]const u8 {
1441 var it = ComponentIterator(path_type, u8).init(path);
1441 var it: ComponentIterator(path_type, u8) = .init(path);
14421442 _ = it.last() orelse return null;
14431443 const up = it.previous() orelse return it.root();
14441444 return up.path;
......@@ -1539,7 +1539,7 @@ pub fn basenameWindows(path: []const u8) []const u8 {
15391539}
15401540
15411541fn basenameInner(comptime path_type: PathType, path: []const u8) []const u8 {
1542 var it = ComponentIterator(path_type, u8).init(path);
1542 var it: ComponentIterator(path_type, u8) = .init(path);
15431543 const last = it.last() orelse return &[_]u8{};
15441544 return last.name;
15451545}
......@@ -2338,14 +2338,14 @@ pub const NativeComponentIterator = ComponentIterator(switch (native_os) {
23382338}, u8);
23392339
23402340pub fn componentIterator(path: []const u8) NativeComponentIterator {
2341 return NativeComponentIterator.init(path);
2341 return .init(path);
23422342}
23432343
23442344test "ComponentIterator posix" {
23452345 const PosixComponentIterator = ComponentIterator(.posix, u8);
23462346 {
23472347 const path = "a/b/c/";
2348 var it = PosixComponentIterator.init(path);
2348 var it: PosixComponentIterator = .init(path);
23492349 try std.testing.expectEqual(0, it.root_len);
23502350 try std.testing.expectEqual(0, it.root_end_index);
23512351 try std.testing.expect(null == it.root());
......@@ -2393,7 +2393,7 @@ test "ComponentIterator posix" {
23932393
23942394 {
23952395 const path = "/a/b/c/";
2396 var it = PosixComponentIterator.init(path);
2396 var it: PosixComponentIterator = .init(path);
23972397 try std.testing.expectEqual(1, it.root_len);
23982398 try std.testing.expectEqual(1, it.root_end_index);
23992399 try std.testing.expectEqualStrings("/", it.root().?);
......@@ -2441,7 +2441,7 @@ test "ComponentIterator posix" {
24412441
24422442 {
24432443 const path = "////a///b///c////";
2444 var it = PosixComponentIterator.init(path);
2444 var it: PosixComponentIterator = .init(path);
24452445 try std.testing.expectEqual(1, it.root_len);
24462446 try std.testing.expectEqual(4, it.root_end_index);
24472447 try std.testing.expectEqualStrings("/", it.root().?);
......@@ -2489,7 +2489,7 @@ test "ComponentIterator posix" {
24892489
24902490 {
24912491 const path = "/";
2492 var it = PosixComponentIterator.init(path);
2492 var it: PosixComponentIterator = .init(path);
24932493 try std.testing.expectEqual(1, it.root_len);
24942494 try std.testing.expectEqual(1, it.root_end_index);
24952495 try std.testing.expectEqualStrings("/", it.root().?);
......@@ -2507,7 +2507,7 @@ test "ComponentIterator posix" {
25072507
25082508 {
25092509 const path = "";
2510 var it = PosixComponentIterator.init(path);
2510 var it: PosixComponentIterator = .init(path);
25112511 try std.testing.expectEqual(0, it.root_len);
25122512 try std.testing.expectEqual(0, it.root_end_index);
25132513 try std.testing.expect(null == it.root());
......@@ -2528,7 +2528,7 @@ test "ComponentIterator windows" {
25282528 const WindowsComponentIterator = ComponentIterator(.windows, u8);
25292529 {
25302530 const path = "a/b\\c//";
2531 var it = WindowsComponentIterator.init(path);
2531 var it: WindowsComponentIterator = .init(path);
25322532 try std.testing.expectEqual(0, it.root_len);
25332533 try std.testing.expectEqual(0, it.root_end_index);
25342534 try std.testing.expect(null == it.root());
......@@ -2576,7 +2576,7 @@ test "ComponentIterator windows" {
25762576
25772577 {
25782578 const path = "C:\\a/b/c/";
2579 var it = WindowsComponentIterator.init(path);
2579 var it: WindowsComponentIterator = .init(path);
25802580 try std.testing.expectEqual(3, it.root_len);
25812581 try std.testing.expectEqual(3, it.root_end_index);
25822582 try std.testing.expectEqualStrings("C:\\", it.root().?);
......@@ -2614,7 +2614,7 @@ test "ComponentIterator windows" {
26142614
26152615 {
26162616 const path = "C:\\\\//a/\\/\\b///c////";
2617 var it = WindowsComponentIterator.init(path);
2617 var it: WindowsComponentIterator = .init(path);
26182618 try std.testing.expectEqual(3, it.root_len);
26192619 try std.testing.expectEqual(6, it.root_end_index);
26202620 try std.testing.expectEqualStrings("C:\\", it.root().?);
......@@ -2652,7 +2652,7 @@ test "ComponentIterator windows" {
26522652
26532653 {
26542654 const path = "/";
2655 var it = WindowsComponentIterator.init(path);
2655 var it: WindowsComponentIterator = .init(path);
26562656 try std.testing.expectEqual(1, it.root_len);
26572657 try std.testing.expectEqual(1, it.root_end_index);
26582658 try std.testing.expectEqualStrings("/", it.root().?);
......@@ -2670,7 +2670,7 @@ test "ComponentIterator windows" {
26702670
26712671 {
26722672 const path = "";
2673 var it = WindowsComponentIterator.init(path);
2673 var it: WindowsComponentIterator = .init(path);
26742674 try std.testing.expectEqual(0, it.root_len);
26752675 try std.testing.expectEqual(0, it.root_end_index);
26762676 try std.testing.expect(null == it.root());
......@@ -2692,7 +2692,7 @@ test "ComponentIterator windows WTF-16" {
26922692 const L = std.unicode.utf8ToUtf16LeStringLiteral;
26932693
26942694 const path = L("C:\\a/b/c/");
2695 var it = WindowsComponentIterator.init(path);
2695 var it: WindowsComponentIterator = .init(path);
26962696 try std.testing.expectEqual(3, it.root_len);
26972697 try std.testing.expectEqual(3, it.root_end_index);
26982698 try std.testing.expectEqualSlices(u16, L("C:\\"), it.root().?);
......@@ -2731,59 +2731,59 @@ test "ComponentIterator windows WTF-16" {
27312731test "ComponentIterator roots" {
27322732 // UEFI
27332733 {
2734 var it = ComponentIterator(.uefi, u8).init("\\\\a");
2734 var it: ComponentIterator(.uefi, u8) = .init("\\\\a");
27352735 try std.testing.expectEqualStrings("\\", it.root().?);
27362736
2737 it = ComponentIterator(.uefi, u8).init("//a");
2737 it = .init("//a");
27382738 try std.testing.expect(null == it.root());
27392739 }
27402740 // POSIX
27412741 {
2742 var it = ComponentIterator(.posix, u8).init("//a");
2742 var it: ComponentIterator(.posix, u8) = .init("//a");
27432743 try std.testing.expectEqualStrings("/", it.root().?);
27442744
2745 it = ComponentIterator(.posix, u8).init("\\\\a");
2745 it = .init("\\\\a");
27462746 try std.testing.expect(null == it.root());
27472747 }
27482748 // Windows
27492749 {
27502750 // Drive relative
2751 var it = ComponentIterator(.windows, u8).init("C:a");
2751 var it: ComponentIterator(.windows, u8) = .init("C:a");
27522752 try std.testing.expectEqualStrings("C:", it.root().?);
27532753
27542754 // Drive absolute
2755 it = ComponentIterator(.windows, u8).init("C:/a");
2755 it = .init("C:/a");
27562756 try std.testing.expectEqualStrings("C:/", it.root().?);
2757 it = ComponentIterator(.windows, u8).init("C:\\a");
2757 it = .init("C:\\a");
27582758 try std.testing.expectEqualStrings("C:\\", it.root().?);
2759 it = ComponentIterator(.windows, u8).init("C:///a");
2759 it = .init("C:///a");
27602760 try std.testing.expectEqualStrings("C:/", it.root().?);
27612761
27622762 // Rooted
2763 it = ComponentIterator(.windows, u8).init("\\a");
2763 it = .init("\\a");
27642764 try std.testing.expectEqualStrings("\\", it.root().?);
2765 it = ComponentIterator(.windows, u8).init("/a");
2765 it = .init("/a");
27662766 try std.testing.expectEqualStrings("/", it.root().?);
27672767
27682768 // Root local device
2769 it = ComponentIterator(.windows, u8).init("\\\\.");
2769 it = .init("\\\\.");
27702770 try std.testing.expectEqualStrings("\\\\.", it.root().?);
2771 it = ComponentIterator(.windows, u8).init("//?");
2771 it = .init("//?");
27722772 try std.testing.expectEqualStrings("//?", it.root().?);
27732773
27742774 // UNC absolute
2775 it = ComponentIterator(.windows, u8).init("//");
2775 it = .init("//");
27762776 try std.testing.expectEqualStrings("//", it.root().?);
2777 it = ComponentIterator(.windows, u8).init("\\\\a");
2777 it = .init("\\\\a");
27782778 try std.testing.expectEqualStrings("\\\\a", it.root().?);
2779 it = ComponentIterator(.windows, u8).init("\\\\a\\b\\\\c");
2779 it = .init("\\\\a\\b\\\\c");
27802780 try std.testing.expectEqualStrings("\\\\a\\b\\", it.root().?);
2781 it = ComponentIterator(.windows, u8).init("//a");
2781 it = .init("//a");
27822782 try std.testing.expectEqualStrings("//a", it.root().?);
2783 it = ComponentIterator(.windows, u8).init("//a/b//c");
2783 it = .init("//a/b//c");
27842784 try std.testing.expectEqualStrings("//a/b/", it.root().?);
27852785 // Malformed UNC path with empty server name
2786 it = ComponentIterator(.windows, u8).init("\\\\\\a\\b\\c");
2786 it = .init("\\\\\\a\\b\\c");
27872787 try std.testing.expectEqualStrings("\\\\\\a\\", it.root().?);
27882788 }
27892789}
lib/std/heap/debug_allocator.zig+4-4
......@@ -718,7 +718,7 @@ pub fn DebugAllocator(comptime config: Config) type {
718718
719719 fn alloc(context: *anyopaque, len: usize, alignment: mem.Alignment, ret_addr: usize) ?[*]u8 {
720720 const self: *Self = @ptrCast(@alignCast(context));
721 if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex);
721 if (have_mutex) std.Io.Threaded.mutexLockUncancelable(&self.mutex);
722722 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);
723723
724724 if (config.enable_memory_limit) {
......@@ -831,7 +831,7 @@ pub fn DebugAllocator(comptime config: Config) type {
831831 return_address: usize,
832832 ) bool {
833833 const self: *Self = @ptrCast(@alignCast(context));
834 if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex);
834 if (have_mutex) std.Io.Threaded.mutexLockUncancelable(&self.mutex);
835835 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);
836836
837837 const size_class_index: usize = @max(@bitSizeOf(usize) - @clz(memory.len - 1), @backingInt(alignment));
......@@ -850,7 +850,7 @@ pub fn DebugAllocator(comptime config: Config) type {
850850 return_address: usize,
851851 ) ?[*]u8 {
852852 const self: *Self = @ptrCast(@alignCast(context));
853 if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex);
853 if (have_mutex) std.Io.Threaded.mutexLockUncancelable(&self.mutex);
854854 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);
855855
856856 const size_class_index: usize = @max(@bitSizeOf(usize) - @clz(memory.len - 1), @backingInt(alignment));
......@@ -868,7 +868,7 @@ pub fn DebugAllocator(comptime config: Config) type {
868868 return_address: usize,
869869 ) void {
870870 const self: *Self = @ptrCast(@alignCast(context));
871 if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex);
871 if (have_mutex) std.Io.Threaded.mutexLockUncancelable(&self.mutex);
872872 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);
873873
874874 const size_class_index: usize = @max(@bitSizeOf(usize) - @clz(old_memory.len - 1), @backingInt(alignment));
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/os/windows.zig+14
......@@ -147,9 +147,23 @@ pub const OBJECT = struct {
147147 pub const Max: @typeInfo(@This()).@"enum".tag_type = @typeInfo(@This()).@"enum".field_names.len;
148148 };
149149
150 pub const BASIC_INFORMATION = extern struct {
151 Attributes: ATTRIBUTES.Flags,
152 GrantedAccess: ACCESS_MASK,
153 HandleCount: ULONG,
154 PointerCount: ULONG,
155 Reserved: [10]ULONG,
156 };
157
150158 pub const NAME_INFORMATION = extern struct {
151159 Name: UNICODE_STRING,
152160 };
161
162 pub const HANDLE_FLAG = packed struct(USHORT) {
163 INHERIT: bool = false,
164 PROTECT_FROM_CLOSE: bool = false,
165 Reserved1: u14 = 0,
166 };
153167};
154168
155169pub const FILE = struct {
lib/std/os/windows/ntdll.zig+7
......@@ -461,6 +461,13 @@ pub extern "ntdll" fn LdrUnregisterDllNotification(
461461 Cookie: LDR.DLL_NOTIFICATION.COOKIE,
462462) callconv(.winapi) NTSTATUS;
463463
464pub extern "ntdll" fn NtSetInformationObject(
465 Handle: HANDLE,
466 ObjectInformationClass: OBJECT.INFORMATION_CLASS,
467 ObjectInformation: *const anyopaque,
468 ObjectInformationLength: ULONG,
469) callconv(.winapi) NTSTATUS;
470
464471pub extern "ntdll" fn NtQueryAttributesFile(
465472 ObjectAttributes: *const OBJECT.ATTRIBUTES,
466473 FileAttributes: *FILE.BASIC_INFORMATION,
lib/std/process.zig+36-30
......@@ -282,37 +282,50 @@ pub const ReplaceError = error{
282282} || Allocator.Error || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
283283
284284pub const ReplaceOptions = struct {
285 exe: Exe = .detect,
285286 argv: []const []const u8,
286 expand_arg0: ArgExpansion = .no_expand,
287
288 /// Set to change the current working directory when spawning the child process.
289 cwd: Child.Cwd = .inherit,
287290 /// Replaces the environment when provided. The PATH value from here is
288291 /// never used to resolve `argv[0]`.
289292 environ_map: ?*const Environ.Map = null,
293 expand_arg0: ArgExpansion = .no_expand,
294
295 inherit_dirs: []const Dir = &.{},
296 inherit_files: []const File = &.{},
297
298 /// Start child process in suspended state.
299 /// For Posix systems it's started as if SIGSTOP was sent.
300 start_suspended: bool = false,
301
302 pub const Exe = union(enum) {
303 /// `argv[0]` is the name of the program to execute. If it is not already a
304 /// file path (i.e. it contains '/'), it is resolved into a file path based on
305 /// PATH from the parent environment.
306 detect,
307 /// `argv[0]` is the name of the program to execute, resolved into a file path
308 /// based on PATH from the parent environment.
309 search,
310 /// `argv[0]` is the file path of the program to execute, relative to `Dir` payload.
311 /// It is *always* treated as a file path, even if it does not contain '/'.
312 path: Dir,
313 /// `File` payload is the program to execute.
314 file: File,
315 /// `path` is the file path of the program to execute, relative to `dir`.
316 /// It is *always* treated as a file path, even if it does not contain '/'.
317 explicit: struct { dir: Dir, path: []const u8 },
318 };
290319};
291320
292321/// Replaces the current process image with the executed process. If this
293322/// function succeeds, it does not return.
294323///
295/// `argv[0]` is the name of the process to replace the current one with. If it
296/// is not already a file path (i.e. it contains '/'), it is resolved into a
297/// file path based on PATH from the parent environment.
298///
299324/// It is illegal to call this function in a fork() child.
300325pub fn replace(io: Io, options: ReplaceOptions) ReplaceError {
301326 return io.vtable.processReplace(io.userdata, options);
302327}
303328
304/// Replaces the current process image with the executed process. If this
305/// function succeeds, it does not return.
306///
307/// `argv[0]` is the file path of the process to replace the current one with,
308/// relative to `dir`. It is *always* treated as a file path, even if it does
309/// not contain '/'.
310///
311/// It is illegal to call this function in a fork() child.
312pub fn replacePath(io: Io, dir: Io.Dir, options: ReplaceOptions) ReplaceError {
313 return io.vtable.processReplacePath(io.userdata, dir, options);
314}
315
316329pub const ArgExpansion = enum { expand, no_expand };
317330
318331/// File name extensions supported natively by `CreateProcess()` on Windows.
......@@ -368,6 +381,7 @@ pub const SpawnError = error{
368381} || Io.File.OpenError || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
369382
370383pub const SpawnOptions = struct {
384 exe: ReplaceOptions.Exe = .detect,
371385 argv: []const []const u8,
372386
373387 /// Set to change the current working directory when spawning the child process.
......@@ -385,11 +399,13 @@ pub const SpawnOptions = struct {
385399 ///
386400 /// The child's progress tree will be grafted into the parent's progress tree,
387401 /// by substituting this node with the child's root node.
388 progress_node: std.Progress.Node = std.Progress.Node.none,
402 progress_node: std.Progress.Node = .none,
389403
390404 stdin: StdIo = .inherit,
391405 stdout: StdIo = .inherit,
392406 stderr: StdIo = .inherit,
407 inherit_dirs: []const Dir = &.{},
408 inherit_files: []const File = &.{},
393409
394410 /// Set to true to obtain rusage information for the child process.
395411 /// Depending on the target platform and implementation status, the
......@@ -443,27 +459,16 @@ pub const SpawnOptions = struct {
443459};
444460
445461/// Creates a child process.
446///
447/// `argv[0]` is the name of the program to execute. If it is not already a
448/// file path (i.e. it contains '/'), it is resolved into a file path based on
449/// PATH from the parent environment.
450462pub fn spawn(io: Io, options: SpawnOptions) SpawnError!Child {
451463 return io.vtable.processSpawn(io.userdata, options);
452464}
453465
454/// Creates a child process.
455///
456/// `argv[0]` is the file path of the program to execute, relative to `dir`. It
457/// is *always* treated as a file path, even if it does not contain '/'.
458pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child {
459 return io.vtable.processSpawnPath(io.userdata, dir, options);
460}
461
462466pub const RunError = error{
463467 StreamTooLong,
464468} || SpawnError || Io.File.MultiReader.UnendingError || Io.Timeout.Error;
465469
466470pub const RunOptions = struct {
471 exe: ReplaceOptions.Exe = .detect,
467472 argv: []const []const u8,
468473 stderr_limit: Io.Limit = .unlimited,
469474 stdout_limit: Io.Limit = .unlimited,
......@@ -501,6 +506,7 @@ pub const RunResult = struct {
501506/// If it succeeds, the caller owns result.stdout and result.stderr memory.
502507pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult {
503508 var child = try spawn(io, .{
509 .exe = options.exe,
504510 .argv = options.argv,
505511 .cwd = options.cwd,
506512 .environ_map = options.environ_map,
lib/std/zig.zig+1-2
......@@ -727,8 +727,7 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *Io.File.Reader) ![
727727}
728728
729729pub fn printAstErrorsToStderr(gpa: Allocator, io: Io, tree: Ast, path: []const u8, color: Color) !void {
730 var wip_errors: ErrorBundle.Wip = undefined;
731 try wip_errors.init(gpa);
730 var wip_errors: ErrorBundle.Wip = try .init(gpa);
732731 defer wip_errors.deinit();
733732
734733 try putAstErrorsIntoBundle(gpa, tree, path, &wip_errors);
lib/std/zig/Client.zig+29
......@@ -60,6 +60,8 @@ pub const Message = struct {
6060 start_fuzzing,
6161 /// The message body has the same format as in Server.
6262 new_fuzz_input,
63 /// Body is a sequence of Arg
64 args,
6365
6466 /// Asks the server to run a list of steps.
6567 /// Body is a `BuildSteps`.
......@@ -69,6 +71,33 @@ pub const Message = struct {
6971 _,
7072 };
7173
74 pub const Arg = enum(u8) {
75 /// Trailing:
76 /// * string: [:0]u8,
77 string,
78 /// Trailing:
79 /// * string: [:0]u8,
80 prefix,
81 /// Trailing:
82 /// * string: [:0]u8,
83 suffix,
84 /// Trailing:
85 /// * handle: Handle,
86 input_file,
87 /// Trailing:
88 /// * handle: Handle,
89 input_dir,
90 /// Trailing:
91 /// * handle: Handle,
92 input_file_content,
93 /// Trailing:
94 /// * handle: Handle,
95 output_file,
96 /// Trailing:
97 /// * handle: Handle,
98 output_dir,
99 };
100
72101 /// Trailing:
73102 /// * step_indices: [step_count]std.Build.Configuration.Step.Index,
74103 pub const BuildSteps = extern struct {
lib/std/zig/ErrorBundle.zig+7-4
......@@ -360,22 +360,25 @@ pub const Wip = struct {
360360 extra: std.ArrayList(u32),
361361 root_list: std.ArrayList(MessageIndex),
362362
363 pub fn init(wip: *Wip, gpa: Allocator) !void {
364 wip.* = .{
363 pub fn init(gpa: Allocator) !Wip {
364 var wip: Wip = .{
365365 .gpa = gpa,
366366 .string_bytes = .empty,
367367 .extra = .empty,
368368 .root_list = .empty,
369369 };
370 errdefer wip.deinit();
370371
371372 // So that 0 can be used to indicate a null string.
372373 try wip.string_bytes.append(gpa, 0);
373374
374 assert(0 == try addExtra(wip, ErrorMessageList{
375 assert(0 == try wip.addExtra(ErrorMessageList{
375376 .len = 0,
376377 .start = 0,
377378 .compile_log_text = 0,
378379 }));
380
381 return wip;
379382 }
380383
381384 pub fn deinit(wip: *Wip) void {
......@@ -773,7 +776,7 @@ pub const Wip = struct {
773776 u32 => @field(extra, field_name),
774777 MessageIndex => @backingInt(@field(extra, field_name)),
775778 SourceLocationIndex => @backingInt(@field(extra, field_name)),
776 else => @compileError("bad field type"),
779 else => @compileError("bad field type: " ++ @typeName(field_type)),
777780 };
778781 i += 1;
779782 }
lib/std/zig/Server.zig+40-2
......@@ -74,6 +74,12 @@ pub const Message = struct {
7474 broadcast_fuzz_input,
7575 /// Body is a TimeReport.
7676 time_report,
77 /// Body is a sequence of:
78 /// - root_dir: InputDir,
79 /// - sub_path: [:0]u8,
80 discovered_inputs,
81 /// Body is a Config.
82 config,
7783
7884 /// The first message sent by the server over the build system protocol.
7985 /// Body is a `Handshake`.
......@@ -112,7 +118,7 @@ pub const Message = struct {
112118
113119 pub const Flags = packed struct(u32) {
114120 file_system_watch_supported: bool,
115 _: u31 = 0,
121 unused: u31 = 0,
116122 };
117123 };
118124
......@@ -198,7 +204,7 @@ pub const Message = struct {
198204 flags: Flags,
199205 pub const Flags = packed struct(u32) {
200206 use_llvm: bool,
201 _: u31 = 0,
207 unused: u31 = 0,
202208 };
203209 };
204210
......@@ -212,6 +218,29 @@ pub const Message = struct {
212218 reserved: u7 = 0,
213219 };
214220 };
221
222 /// A reference to one of the `.input_dir`s from the `.args` message.
223 pub const InputDir = enum(u32) {
224 /// Server cwd relative path, or absolute path.
225 cwd,
226 /// Numbered in the order that `.input_dir`s appear in the `.args` message.
227 _,
228 };
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 };
215244};
216245
217246pub fn receiveMessage(s: *Server) !InMessage.Header {
......@@ -365,3 +394,12 @@ pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void {
365394 try s.out.writeAll(test_metadata.string_bytes);
366395 try s.out.flush();
367396}
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/Compilation.zig+3-6
......@@ -3842,8 +3842,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle {
38423842 const gpa = comp.gpa;
38433843 const io = comp.io;
38443844
3845 var bundle: ErrorBundle.Wip = undefined;
3846 try bundle.init(gpa);
3845 var bundle: ErrorBundle.Wip = try .init(gpa);
38473846 defer bundle.deinit();
38483847
38493848 for (comp.failed_c_objects.values()) |diag_bundle| {
......@@ -5447,8 +5446,7 @@ fn reportRetryableWin32ResourceError(
54475446
54485447 win32_resource.status = .failure_retryable;
54495448
5450 var bundle: ErrorBundle.Wip = undefined;
5451 try bundle.init(comp.gpa);
5449 var bundle: ErrorBundle.Wip = try .init(comp.gpa);
54525450 errdefer bundle.deinit();
54535451 try bundle.addRootErrorMessage(.{
54545452 .msg = try bundle.printString("{s}", .{@errorName(err)}),
......@@ -6835,8 +6833,7 @@ fn failCObjWithOwnedDiagBundle(
68356833
68366834fn failWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, comptime format: []const u8, args: anytype) error{ OutOfMemory, AlreadyReported } {
68376835 @branchHint(.cold);
6838 var bundle: ErrorBundle.Wip = undefined;
6839 try bundle.init(comp.gpa);
6836 var bundle: ErrorBundle.Wip = try .init(comp.gpa);
68406837 errdefer bundle.deinit();
68416838 try bundle.addRootErrorMessage(.{
68426839 .msg = try bundle.printString(format, args),
src/Sema.zig+1-2
......@@ -2705,8 +2705,7 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg
27052705 assert(sema.err == null);
27062706
27072707 if (build_options.enable_debug_extensions and comp.debug_compile_errors) {
2708 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
2709 wip_errors.init(gpa) catch @panic("out of memory");
2708 var wip_errors = std.zig.ErrorBundle.Wip.init(gpa) catch @panic("out of memory");
27102709 Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*, false) catch @panic("out of memory");
27112710 std.debug.print("compile error during Sema:\n", .{});
27122711 var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory");
src/fmt.zig+4-8
......@@ -130,8 +130,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
130130 defer zir.deinit(gpa);
131131
132132 if (zir.hasCompileErrors()) {
133 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
134 try wip_errors.init(gpa);
133 var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa);
135134 defer wip_errors.deinit();
136135 try wip_errors.addZirErrorMessages(zir, tree, source_code, "<stdin>");
137136 var error_bundle = try wip_errors.toOwnedBundle("");
......@@ -144,8 +143,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
144143 defer zoir.deinit(gpa);
145144
146145 if (zoir.hasCompileErrors()) {
147 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
148 try wip_errors.init(gpa);
146 var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa);
149147 defer wip_errors.deinit();
150148 try wip_errors.addZoirErrorMessages(zoir, tree, source_code, "<stdin>");
151149 var error_bundle = try wip_errors.toOwnedBundle("");
......@@ -333,8 +331,7 @@ fn fmtPathFile(
333331 defer zir.deinit(gpa);
334332
335333 if (zir.hasCompileErrors()) {
336 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
337 try wip_errors.init(gpa);
334 var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa);
338335 defer wip_errors.deinit();
339336 try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path);
340337 var error_bundle = try wip_errors.toOwnedBundle("");
......@@ -348,8 +345,7 @@ fn fmtPathFile(
348345 defer zoir.deinit(gpa);
349346
350347 if (zoir.hasCompileErrors()) {
351 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
352 try wip_errors.init(gpa);
348 var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa);
353349 defer wip_errors.deinit();
354350 try wip_errors.addZoirErrorMessages(zoir, tree, source_code, file_path);
355351 var error_bundle = try wip_errors.toOwnedBundle("");
src/link.zig+1-2
......@@ -2891,8 +2891,7 @@ fn resolvePathInputLib(
28912891
28922892 const ld_script_result = LdScript.parse(gpa, &diags, test_path, ld_script_bytes.items);
28932893 if (diags.hasErrors()) {
2894 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
2895 try wip_errors.init(gpa);
2894 var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa);
28962895 defer wip_errors.deinit();
28972896
28982897 try diags.addMessagesToBundle(&wip_errors, null);
src/main.zig+44-36
......@@ -3971,11 +3971,9 @@ fn buildOutputType(
39713971 test_exec_args.items,
39723972 self_exe_path_or_argv0,
39733973 arg_mode,
3974 target,
39753974 &comp_destroyed,
39763975 all_args,
39773976 runtime_args_start,
3978 create_module.resolved_options.link_libc,
39793977 test_execve,
39803978 environ_map,
39813979 );
......@@ -4410,6 +4408,15 @@ fn serve(
44104408 var server: Server = .{ .in = in, .out = out };
44114409 try server.serveStringMessage(.zig_version, build_options.version);
44124410
4411 try server.serveConfig(.{ .flags = .{
4412 .output_mode = comp.config.output_mode,
4413 .link_mode = comp.config.link_mode,
4414 .link_libc = comp.config.link_libc,
4415 .link_libcpp = comp.config.link_libcpp,
4416 .link_libunwind = comp.config.link_libunwind,
4417 .pie = comp.config.pie,
4418 } });
4419
44134420 var child_pid: ?std.process.Child.Id = null;
44144421
44154422 const main_progress_node = std.Progress.start(io, .{});
......@@ -4482,8 +4489,6 @@ fn serve(
44824489 // test_exec_args,
44834490 // self_exe_path.?,
44844491 // arg_mode,
4485 // target,
4486 // true,
44874492 // &comp_destroyed,
44884493 // all_args,
44894494 // runtime_args_start,
......@@ -4634,11 +4639,9 @@ fn runOrTest(
46344639 test_exec_args: []const ?[]const u8,
46354640 self_exe_path: []const u8,
46364641 arg_mode: ArgMode,
4637 target: *const std.Target,
46384642 comp_destroyed: *bool,
46394643 all_args: []const []const u8,
46404644 runtime_args_start: ?usize,
4641 link_libc: bool,
46424645 test_execve: bool,
46434646 environ_map: *process.Environ.Map,
46444647) !void {
......@@ -4686,7 +4689,12 @@ fn runOrTest(
46864689 _ = try io.lockStderr(&.{}, .no_color);
46874690 const err = process.replace(io, .{ .argv = argv.items, .environ_map = environ_map });
46884691 io.unlockStderr();
4689 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);
4692 try warnAboutForeignBinaries(io, arena, .{
4693 .arg_mode = arg_mode,
4694 .target = comp.getTarget(),
4695 .link_mode = comp.config.link_mode,
4696 .link_libc = comp.config.link_libc,
4697 });
46904698 const cmd = try std.mem.join(arena, " ", argv.items);
46914699 fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd });
46924700 } else if (!process.can_spawn) {
......@@ -4716,7 +4724,12 @@ fn runOrTest(
47164724 });
47174725
47184726 const term = term_result catch |err| {
4719 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);
4727 try warnAboutForeignBinaries(io, arena, .{
4728 .arg_mode = arg_mode,
4729 .target = comp.getTarget(),
4730 .link_mode = comp.config.link_mode,
4731 .link_libc = comp.config.link_libc,
4732 });
47204733 const cmd = try std.mem.join(arena, " ", argv.items);
47214734 fatal("the following command failed with {t}:\n{s}", .{ err, cmd });
47224735 };
......@@ -5647,8 +5660,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8, environ_map:
56475660 const zir = try AstGen.generate(arena, tree);
56485661
56495662 if (zir.hasCompileErrors()) {
5650 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
5651 try wip_errors.init(arena);
5663 var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena);
56525664 try wip_errors.addZirErrorMessages(zir, tree, source, display_path);
56535665 var error_bundle = try wip_errors.toOwnedBundle("");
56545666 try error_bundle.renderToStderr(io, .{}, color);
......@@ -5718,8 +5730,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8, environ_map:
57185730 .zon => {
57195731 const zoir = try ZonGen.generate(arena, tree, .{});
57205732 if (zoir.hasCompileErrors()) {
5721 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
5722 try wip_errors.init(arena);
5733 var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena);
57235734 try wip_errors.addZoirErrorMessages(zoir, tree, source, display_path);
57245735 var error_bundle = try wip_errors.toOwnedBundle("");
57255736 error_bundle.renderToStderr(io, .{}, color) catch {};
......@@ -5812,8 +5823,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8, environ_map
58125823 const old_zir = try AstGen.generate(arena, old_tree);
58135824
58145825 if (old_zir.loweringFailed()) {
5815 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
5816 try wip_errors.init(arena);
5826 var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena);
58175827 try wip_errors.addZirErrorMessages(old_zir, old_tree, old_source, old_source_path);
58185828 var error_bundle = try wip_errors.toOwnedBundle("");
58195829 error_bundle.renderToStderr(io, .{}, color) catch {};
......@@ -5824,8 +5834,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8, environ_map
58245834 const new_zir = try AstGen.generate(arena, new_tree);
58255835
58265836 if (new_zir.loweringFailed()) {
5827 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
5828 try wip_errors.init(arena);
5837 var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena);
58295838 try wip_errors.addZirErrorMessages(new_zir, new_tree, new_source, new_source_path);
58305839 var error_bundle = try wip_errors.toOwnedBundle("");
58315840 error_bundle.renderToStderr(io, .{}, color) catch {};
......@@ -5867,33 +5876,32 @@ fn prefixedIntArg(arg: []const u8, prefix: []const u8) ?u64 {
58675876 return std.fmt.parseUnsigned(u64, number, 0) catch |err| fatal("unable to parse {q}: {t}", .{ arg, err });
58685877}
58695878
5870fn warnAboutForeignBinaries(
5871 io: Io,
5872 arena: Allocator,
5879fn warnAboutForeignBinaries(io: Io, arena: Allocator, opts: struct {
58735880 arg_mode: ArgMode,
58745881 target: *const std.Target,
5882 link_mode: std.lang.LinkMode,
58755883 link_libc: bool,
5876) !void {
5877 const host_query: std.Target.Query = .{};
5878 const host_target = std.zig.resolveTargetQueryOrFatal(io, host_query);
5884}) !void {
5885 const host_target = std.zig.resolveTargetQueryOrFatal(io, .{});
58795886
5880 switch (std.zig.system.getExternalExecutor(io, target, .{
5887 switch (std.zig.system.getExternalExecutor(io, opts.target, .{
58815888 .host_cpu_arch = host_target.cpu.arch,
58825889 .host_os_tag = host_target.os.tag,
5883 .link_libc = link_libc,
5890 .link_mode = opts.link_mode,
5891 .link_libc = opts.link_libc,
58845892 })) {
58855893 .native => return,
58865894 .rosetta => {
58875895 const host_name = try host_target.zigTriple(arena);
5888 const foreign_name = try target.zigTriple(arena);
5896 const foreign_name = try opts.target.zigTriple(arena);
58895897 warn("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}). Consider installing Rosetta.", .{
58905898 host_name, foreign_name,
58915899 });
58925900 },
58935901 .qemu => |qemu| {
58945902 const host_name = try host_target.zigTriple(arena);
5895 const foreign_name = try target.zigTriple(arena);
5896 switch (arg_mode) {
5903 const foreign_name = try opts.target.zigTriple(arena);
5904 switch (opts.arg_mode) {
58975905 .zig_test => warn(
58985906 "the host system ({s}) does not appear to be capable of executing binaries " ++
58995907 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
......@@ -5909,8 +5917,8 @@ fn warnAboutForeignBinaries(
59095917 },
59105918 .wine => |wine| {
59115919 const host_name = try host_target.zigTriple(arena);
5912 const foreign_name = try target.zigTriple(arena);
5913 switch (arg_mode) {
5920 const foreign_name = try opts.target.zigTriple(arena);
5921 switch (opts.arg_mode) {
59145922 .zig_test => warn(
59155923 "the host system ({s}) does not appear to be capable of executing binaries " ++
59165924 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
......@@ -5926,8 +5934,8 @@ fn warnAboutForeignBinaries(
59265934 },
59275935 .wasmtime => |wasmtime| {
59285936 const host_name = try host_target.zigTriple(arena);
5929 const foreign_name = try target.zigTriple(arena);
5930 switch (arg_mode) {
5937 const foreign_name = try opts.target.zigTriple(arena);
5938 switch (opts.arg_mode) {
59315939 .zig_test => warn(
59325940 "the host system ({s}) does not appear to be capable of executing binaries " ++
59335941 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
......@@ -5943,8 +5951,8 @@ fn warnAboutForeignBinaries(
59435951 },
59445952 .darling => |darling| {
59455953 const host_name = try host_target.zigTriple(arena);
5946 const foreign_name = try target.zigTriple(arena);
5947 switch (arg_mode) {
5954 const foreign_name = try opts.target.zigTriple(arena);
5955 switch (opts.arg_mode) {
59485956 .zig_test => warn(
59495957 "the host system ({s}) does not appear to be capable of executing binaries " ++
59505958 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
......@@ -5960,7 +5968,7 @@ fn warnAboutForeignBinaries(
59605968 },
59615969 .bad_dl => |foreign_dl| {
59625970 const host_dl = host_target.dynamic_linker.get() orelse "(none)";
5963 const tip_suffix = switch (arg_mode) {
5971 const tip_suffix = switch (opts.arg_mode) {
59645972 .zig_test => ", '--test-no-exec', or '--test-cmd'",
59655973 else => "",
59665974 };
......@@ -5970,8 +5978,8 @@ fn warnAboutForeignBinaries(
59705978 },
59715979 .bad_os_or_cpu => {
59725980 const host_name = try host_target.zigTriple(arena);
5973 const foreign_name = try target.zigTriple(arena);
5974 const tip_suffix = switch (arg_mode) {
5981 const foreign_name = try opts.target.zigTriple(arena);
5982 const tip_suffix = switch (opts.arg_mode) {
59755983 .zig_test => ". Consider using '--test-no-exec' or '--test-cmd'",
59765984 else => "",
59775985 };
test/incremental/add_decl+45-18
......@@ -1,15 +1,21 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const std = @import("std");
47pub fn main() !void {
58 try std.Io.File.stdout().writeStreamingAll(io, foo);
69}
710const foo = "good morning\n";
811const io = std.Io.Threaded.global_single_threaded.io();
9#expect_stdout="good morning\n"
12#}
13#update
14#check stdout {
15good morning
16#}
1017
11#update=add new declaration
12#file=main.zig
18#write main.zig {
1319const std = @import("std");
1420pub fn main() !void {
1521 try std.Io.File.stdout().writeStreamingAll(io, foo);
......@@ -17,10 +23,14 @@ pub fn main() !void {
1723const foo = "good morning\n";
1824const bar = "good evening\n";
1925const io = std.Io.Threaded.global_single_threaded.io();
20#expect_stdout="good morning\n"
26#}
27#update {
28add new declaration#}
29#check stdout {
30good morning
31#}
2132
22#update=reference new declaration
23#file=main.zig
33#write main.zig {
2434const std = @import("std");
2535pub fn main() !void {
2636 try std.Io.File.stdout().writeStreamingAll(io, bar);
......@@ -28,10 +38,14 @@ pub fn main() !void {
2838const foo = "good morning\n";
2939const bar = "good evening\n";
3040const io = std.Io.Threaded.global_single_threaded.io();
31#expect_stdout="good evening\n"
41#}
42#update {
43reference new declaration#}
44#check stdout {
45good evening
46#}
3247
33#update=reference missing declaration
34#file=main.zig
48#write main.zig {
3549const std = @import("std");
3650pub fn main() !void {
3751 try std.Io.File.stdout().writeStreamingAll(io, qux);
......@@ -39,10 +53,14 @@ pub fn main() !void {
3953const foo = "good morning\n";
4054const bar = "good evening\n";
4155const io = std.Io.Threaded.global_single_threaded.io();
42#expect_error=main.zig:3:52: error: use of undeclared identifier 'qux'
56#}
57#update {
58reference missing declaration#}
59#check errors {
60main.zig:3:52: error: use of undeclared identifier 'qux'
61#}
4362
44#update=add missing declaration
45#file=main.zig
63#write main.zig {
4664const std = @import("std");
4765pub fn main() !void {
4866 try std.Io.File.stdout().writeStreamingAll(io, qux);
......@@ -51,14 +69,23 @@ const foo = "good morning\n";
5169const bar = "good evening\n";
5270const qux = "good night\n";
5371const io = std.Io.Threaded.global_single_threaded.io();
54#expect_stdout="good night\n"
72#}
73#update {
74add missing declaration#}
75#check stdout {
76good night
77#}
5578
56#update=remove unused declarations
57#file=main.zig
79#write main.zig {
5880const std = @import("std");
5981pub fn main() !void {
6082 try std.Io.File.stdout().writeStreamingAll(io, qux);
6183}
6284const qux = "good night\n";
6385const io = std.Io.Threaded.global_single_threaded.io();
64#expect_stdout="good night\n"
86#}
87#update {
88remove unused declarations#}
89#check stdout {
90good night
91#}
test/incremental/add_decl_namespaced+46-19
......@@ -1,15 +1,21 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const std = @import("std");
47pub fn main() !void {
58 try std.Io.File.stdout().writeStreamingAll(io, @This().foo);
69}
710const foo = "good morning\n";
811const io = std.Io.Threaded.global_single_threaded.io();
9#expect_stdout="good morning\n"
12#}
13#update
14#check stdout {
15good morning
16#}
1017
11#update=add new declaration
12#file=main.zig
18#write main.zig {
1319const std = @import("std");
1420pub fn main() !void {
1521 try std.Io.File.stdout().writeStreamingAll(io, @This().foo);
......@@ -17,10 +23,14 @@ pub fn main() !void {
1723const foo = "good morning\n";
1824const bar = "good evening\n";
1925const io = std.Io.Threaded.global_single_threaded.io();
20#expect_stdout="good morning\n"
26#}
27#update {
28add new declaration#}
29#check stdout {
30good morning
31#}
2132
22#update=reference new declaration
23#file=main.zig
33#write main.zig {
2434const std = @import("std");
2535pub fn main() !void {
2636 try std.Io.File.stdout().writeStreamingAll(io, @This().bar);
......@@ -28,10 +38,14 @@ pub fn main() !void {
2838const foo = "good morning\n";
2939const bar = "good evening\n";
3040const io = std.Io.Threaded.global_single_threaded.io();
31#expect_stdout="good evening\n"
41#}
42#update {
43reference new declaration#}
44#check stdout {
45good evening
46#}
3247
33#update=reference missing declaration
34#file=main.zig
48#write main.zig {
3549const std = @import("std");
3650pub fn main() !void {
3751 try std.Io.File.stdout().writeStreamingAll(io, @This().qux);
......@@ -39,11 +53,15 @@ pub fn main() !void {
3953const foo = "good morning\n";
4054const bar = "good evening\n";
4155const io = std.Io.Threaded.global_single_threaded.io();
42#expect_error=main.zig:3:59: error: root source file struct 'main' has no member named 'qux'
43#expect_error=main.zig:1:1: note: struct declared here
56#}
57#update {
58reference missing declaration#}
59#check errors {
60main.zig:3:59: error: root source file struct 'main' has no member named 'qux'
61main.zig:1:1: note: struct declared here
62#}
4463
45#update=add missing declaration
46#file=main.zig
64#write main.zig {
4765const std = @import("std");
4866pub fn main() !void {
4967 try std.Io.File.stdout().writeStreamingAll(io, @This().qux);
......@@ -52,14 +70,23 @@ const foo = "good morning\n";
5270const bar = "good evening\n";
5371const qux = "good night\n";
5472const io = std.Io.Threaded.global_single_threaded.io();
55#expect_stdout="good night\n"
73#}
74#update {
75add missing declaration#}
76#check stdout {
77good night
78#}
5679
57#update=remove unused declarations
58#file=main.zig
80#write main.zig {
5981const std = @import("std");
6082pub fn main() !void {
6183 try std.Io.File.stdout().writeStreamingAll(io, @This().qux);
6284}
6385const qux = "good night\n";
6486const io = std.Io.Threaded.global_single_threaded.io();
65#expect_stdout="good night\n"
87#}
88#update {
89remove unused declarations#}
90#check stdout {
91good night
92#}
test/incremental/add_field_and_nested_struct_uses_changed_decl+16-6
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36pub fn main() void {
47 _ = @as(S, undefined);
58}
......@@ -9,9 +12,12 @@ const SomeType = u8;
912const S = struct {
1013 foo: struct { inner: SomeType },
1114};
12#expect_stdout=""
13#update=add field to outer struct, change decl used by inner struct
14#file=main.zig
15#}
16#update
17#check stdout {
18#}
19
20#write main.zig {
1521pub fn main() void {
1622 _ = @as(S, undefined);
1723}
......@@ -22,4 +28,8 @@ const S = struct {
2228 foo: struct { inner: SomeType },
2329 bar: u32,
2430};
25#expect_stdout=""
31#}
32#update {
33add field to outer struct, change decl used by inner struct#}
34#check stdout {
35#}
test/incremental/add_remove_struct_fields deleted-93
......@@ -1,93 +0,0 @@
1#update=initial version
2#file=main.zig
3const S = struct { x: u8 };
4pub fn main(init: std.process.Init) !void {
5 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
6 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
7 error.WriteFailed => return stdout_writer.err.?,
8 };
9 printOneField(&stdout_writer.interface) catch |err| switch (err) {
10 error.WriteFailed => return stdout_writer.err.?,
11 };
12}
13fn printFieldCount(w: *Writer) Writer.Error!void {
14 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
15}
16fn printOneField(w: *Writer) Writer.Error!void {
17 const val: S = .{ .x = 100 };
18 try w.print("{d}\n", .{val.x});
19}
20const std = @import("std");
21const Writer = std.Io.Writer;
22#expect_stdout="1 100\n"
23
24#update=add a field
25#file=main.zig
26const S = struct { x: u8, y: u16 = 200 };
27pub fn main(init: std.process.Init) !void {
28 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
29 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
30 error.WriteFailed => return stdout_writer.err.?,
31 };
32 printOneField(&stdout_writer.interface) catch |err| switch (err) {
33 error.WriteFailed => return stdout_writer.err.?,
34 };
35}
36fn printFieldCount(w: *Writer) Writer.Error!void {
37 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
38}
39fn printOneField(w: *Writer) Writer.Error!void {
40 const val: S = .{ .x = 100 };
41 try w.print("{d}\n", .{val.x});
42}
43const std = @import("std");
44const Writer = std.Io.Writer;
45#expect_stdout="2 100\n"
46
47#update=remove all fields
48#file=main.zig
49const S = struct {};
50pub fn main(init: std.process.Init) !void {
51 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
52 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
53 error.WriteFailed => return stdout_writer.err.?,
54 };
55 printOneField(&stdout_writer.interface) catch |err| switch (err) {
56 error.WriteFailed => return stdout_writer.err.?,
57 };
58}
59fn printFieldCount(w: *Writer) Writer.Error!void {
60 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
61}
62fn printOneField(w: *Writer) Writer.Error!void {
63 const val: S = .{ .x = 100 };
64 try w.print("{d}\n", .{val.x});
65}
66const std = @import("std");
67const Writer = std.Io.Writer;
68#expect_error=main.zig:15:24: error: no field named 'x' in struct 'main.S'
69#expect_error=main.zig:1:11: note: struct declared here
70
71#update=remove reference to non-existent field
72#file=main.zig
73const S = struct {};
74pub fn main(init: std.process.Init) !void {
75 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
76 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
77 error.WriteFailed => return stdout_writer.err.?,
78 };
79 printOneField(&stdout_writer.interface) catch |err| switch (err) {
80 error.WriteFailed => return stdout_writer.err.?,
81 };
82}
83fn printFieldCount(w: *Writer) Writer.Error!void {
84 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
85}
86fn printOneField(w: *Writer) Writer.Error!void {
87 //const val: S = .{ .x = 100 };
88 //try w.print("{d}\n", .{val.x});
89 try w.writeAll("<no fields>\n");
90}
91const std = @import("std");
92const Writer = std.Io.Writer;
93#expect_stdout="0 <no fields>\n"
test/incremental/add_remove_struct_fields/0/main.zig created+19
......@@ -0,0 +1,19 @@
1const S = struct { x: u8 };
2pub fn main(init: std.process.Init) !void {
3 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
4 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
5 error.WriteFailed => return stdout_writer.err.?,
6 };
7 printOneField(&stdout_writer.interface) catch |err| switch (err) {
8 error.WriteFailed => return stdout_writer.err.?,
9 };
10}
11fn printFieldCount(w: *Writer) Writer.Error!void {
12 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
13}
14fn printOneField(w: *Writer) Writer.Error!void {
15 const val: S = .{ .x = 100 };
16 try w.print("{d}\n", .{val.x});
17}
18const std = @import("std");
19const Writer = std.Io.Writer;
test/incremental/add_remove_struct_fields/1/main.zig created+19
......@@ -0,0 +1,19 @@
1const S = struct { x: u8, y: u16 = 200 };
2pub fn main(init: std.process.Init) !void {
3 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
4 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
5 error.WriteFailed => return stdout_writer.err.?,
6 };
7 printOneField(&stdout_writer.interface) catch |err| switch (err) {
8 error.WriteFailed => return stdout_writer.err.?,
9 };
10}
11fn printFieldCount(w: *Writer) Writer.Error!void {
12 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
13}
14fn printOneField(w: *Writer) Writer.Error!void {
15 const val: S = .{ .x = 100 };
16 try w.print("{d}\n", .{val.x});
17}
18const std = @import("std");
19const Writer = std.Io.Writer;
test/incremental/add_remove_struct_fields/2/main.zig created+19
......@@ -0,0 +1,19 @@
1const S = struct {};
2pub fn main(init: std.process.Init) !void {
3 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
4 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
5 error.WriteFailed => return stdout_writer.err.?,
6 };
7 printOneField(&stdout_writer.interface) catch |err| switch (err) {
8 error.WriteFailed => return stdout_writer.err.?,
9 };
10}
11fn printFieldCount(w: *Writer) Writer.Error!void {
12 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
13}
14fn printOneField(w: *Writer) Writer.Error!void {
15 const val: S = .{ .x = 100 };
16 try w.print("{d}\n", .{val.x});
17}
18const std = @import("std");
19const Writer = std.Io.Writer;
test/incremental/add_remove_struct_fields/3/main.zig created+20
......@@ -0,0 +1,20 @@
1const S = struct {};
2pub fn main(init: std.process.Init) !void {
3 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
4 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
5 error.WriteFailed => return stdout_writer.err.?,
6 };
7 printOneField(&stdout_writer.interface) catch |err| switch (err) {
8 error.WriteFailed => return stdout_writer.err.?,
9 };
10}
11fn printFieldCount(w: *Writer) Writer.Error!void {
12 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
13}
14fn printOneField(w: *Writer) Writer.Error!void {
15 //const val: S = .{ .x = 100 };
16 //try w.print("{d}\n", .{val.x});
17 try w.writeAll("<no fields>\n");
18}
19const std = @import("std");
20const Writer = std.Io.Writer;
test/incremental/add_remove_struct_fields/manifest created+31
......@@ -0,0 +1,31 @@
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig 0/main.zig
6#update
7#check stdout {
81 100
9#}
10
11#write main.zig 1/main.zig
12#update {
13add a field#}
14#check stdout {
152 100
16#}
17
18#write main.zig 2/main.zig
19#update {
20remove all fields#}
21#check errors {
22main.zig:15:24: error: no field named 'x' in struct 'main.S'
23main.zig:1:11: note: struct declared here
24#}
25
26#write main.zig 3/main.zig
27#update {
28remove reference to non-existent field#}
29#check stdout {
300 <no fields>
31#}
test/incremental/add_remove_toplevel_fields deleted-96
......@@ -1,96 +0,0 @@
1#update=initial version
2#file=main.zig
3const S = @This();
4x: u8,
5pub fn main(init: std.process.Init) !void {
6 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
7 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
8 error.WriteFailed => return stdout_writer.err.?,
9 };
10 printOneField(&stdout_writer.interface) catch |err| switch (err) {
11 error.WriteFailed => return stdout_writer.err.?,
12 };
13}
14fn printFieldCount(w: *Writer) Writer.Error!void {
15 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
16}
17fn printOneField(w: *Writer) Writer.Error!void {
18 const val: S = .{ .x = 100 };
19 try w.print("{d}\n", .{val.x});
20}
21const std = @import("std");
22const Writer = std.Io.Writer;
23#expect_stdout="1 100\n"
24
25#update=add a field
26#file=main.zig
27const S = @This();
28x: u8,
29y: u16 = 200,
30pub fn main(init: std.process.Init) !void {
31 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
32 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
33 error.WriteFailed => return stdout_writer.err.?,
34 };
35 printOneField(&stdout_writer.interface) catch |err| switch (err) {
36 error.WriteFailed => return stdout_writer.err.?,
37 };
38}
39fn printFieldCount(w: *Writer) Writer.Error!void {
40 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
41}
42fn printOneField(w: *Writer) Writer.Error!void {
43 const val: S = .{ .x = 100 };
44 try w.print("{d}\n", .{val.x});
45}
46const std = @import("std");
47const Writer = std.Io.Writer;
48#expect_stdout="2 100\n"
49
50#update=remove all fields
51#file=main.zig
52const S = @This();
53pub fn main(init: std.process.Init) !void {
54 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
55 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
56 error.WriteFailed => return stdout_writer.err.?,
57 };
58 printOneField(&stdout_writer.interface) catch |err| switch (err) {
59 error.WriteFailed => return stdout_writer.err.?,
60 };
61}
62fn printFieldCount(w: *Writer) Writer.Error!void {
63 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
64}
65fn printOneField(w: *Writer) Writer.Error!void {
66 const val: S = .{ .x = 100 };
67 try w.print("{d}\n", .{val.x});
68}
69const std = @import("std");
70const Writer = std.Io.Writer;
71#expect_error=main.zig:15:24: error: no field named 'x' in struct 'main'
72#expect_error=main.zig:1:1: note: struct declared here
73
74#update=remove reference to non-existent field
75#file=main.zig
76const S = @This();
77pub fn main(init: std.process.Init) !void {
78 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
79 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
80 error.WriteFailed => return stdout_writer.err.?,
81 };
82 printOneField(&stdout_writer.interface) catch |err| switch (err) {
83 error.WriteFailed => return stdout_writer.err.?,
84 };
85}
86fn printFieldCount(w: *Writer) Writer.Error!void {
87 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
88}
89fn printOneField(w: *Writer) Writer.Error!void {
90 //const val: S = .{ .x = 100 };
91 //try w.print("{d}\n", .{val.x});
92 try w.writeAll("<no fields>\n");
93}
94const std = @import("std");
95const Writer = std.Io.Writer;
96#expect_stdout="0 <no fields>\n"
test/incremental/add_remove_toplevel_fields/0/main.zig created+20
......@@ -0,0 +1,20 @@
1const S = @This();
2x: u8,
3pub fn main(init: std.process.Init) !void {
4 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
5 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
6 error.WriteFailed => return stdout_writer.err.?,
7 };
8 printOneField(&stdout_writer.interface) catch |err| switch (err) {
9 error.WriteFailed => return stdout_writer.err.?,
10 };
11}
12fn printFieldCount(w: *Writer) Writer.Error!void {
13 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
14}
15fn printOneField(w: *Writer) Writer.Error!void {
16 const val: S = .{ .x = 100 };
17 try w.print("{d}\n", .{val.x});
18}
19const std = @import("std");
20const Writer = std.Io.Writer;
test/incremental/add_remove_toplevel_fields/1/main.zig created+21
......@@ -0,0 +1,21 @@
1const S = @This();
2x: u8,
3y: u16 = 200,
4pub fn main(init: std.process.Init) !void {
5 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
6 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
7 error.WriteFailed => return stdout_writer.err.?,
8 };
9 printOneField(&stdout_writer.interface) catch |err| switch (err) {
10 error.WriteFailed => return stdout_writer.err.?,
11 };
12}
13fn printFieldCount(w: *Writer) Writer.Error!void {
14 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
15}
16fn printOneField(w: *Writer) Writer.Error!void {
17 const val: S = .{ .x = 100 };
18 try w.print("{d}\n", .{val.x});
19}
20const std = @import("std");
21const Writer = std.Io.Writer;
test/incremental/add_remove_toplevel_fields/2/main.zig created+19
......@@ -0,0 +1,19 @@
1const S = @This();
2pub fn main(init: std.process.Init) !void {
3 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
4 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
5 error.WriteFailed => return stdout_writer.err.?,
6 };
7 printOneField(&stdout_writer.interface) catch |err| switch (err) {
8 error.WriteFailed => return stdout_writer.err.?,
9 };
10}
11fn printFieldCount(w: *Writer) Writer.Error!void {
12 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
13}
14fn printOneField(w: *Writer) Writer.Error!void {
15 const val: S = .{ .x = 100 };
16 try w.print("{d}\n", .{val.x});
17}
18const std = @import("std");
19const Writer = std.Io.Writer;
test/incremental/add_remove_toplevel_fields/3/main.zig created+20
......@@ -0,0 +1,20 @@
1const S = @This();
2pub fn main(init: std.process.Init) !void {
3 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
4 printFieldCount(&stdout_writer.interface) catch |err| switch (err) {
5 error.WriteFailed => return stdout_writer.err.?,
6 };
7 printOneField(&stdout_writer.interface) catch |err| switch (err) {
8 error.WriteFailed => return stdout_writer.err.?,
9 };
10}
11fn printFieldCount(w: *Writer) Writer.Error!void {
12 try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len});
13}
14fn printOneField(w: *Writer) Writer.Error!void {
15 //const val: S = .{ .x = 100 };
16 //try w.print("{d}\n", .{val.x});
17 try w.writeAll("<no fields>\n");
18}
19const std = @import("std");
20const Writer = std.Io.Writer;
test/incremental/add_remove_toplevel_fields/manifest created+31
......@@ -0,0 +1,31 @@
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig 0/main.zig
6#update
7#check stdout {
81 100
9#}
10
11#write main.zig 1/main.zig
12#update {
13add a field#}
14#check stdout {
152 100
16#}
17
18#write main.zig 2/main.zig
19#update {
20remove all fields#}
21#check errors {
22main.zig:15:24: error: no field named 'x' in struct 'main'
23main.zig:1:1: note: struct declared here
24#}
25
26#write main.zig 3/main.zig
27#update {
28remove reference to non-existent field#}
29#check stdout {
300 <no fields>
31#}
test/incremental/analysis_error_and_syntax_error+44-18
......@@ -1,38 +1,64 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36pub fn main() !void {
47 @compileError("uh oh");
58}
6#expect_error=main.zig:2:5: error: uh oh
9#}
10#update
11#check errors {
12main.zig:2:5: error: uh oh
13#}
714
8#update=add parse error
9#file=main.zig
15#write main.zig {
1016pub fn main() !void {
1117 @compileError("uh oh");
12#expect_error=main.zig:3:1: error: expected statement, found 'EOF'
18#}
19#update {
20add parse error#}
21#check errors {
22main.zig:3:1: error: expected statement, found 'EOF'
23#}
1324
14#update=fix parse error
15#file=main.zig
25#write main.zig {
1626pub fn main() !void {
1727 @compileError("uh oh");
1828}
19#expect_error=main.zig:2:5: error: uh oh
29#}
30#update {
31fix parse error#}
32#check errors {
33main.zig:2:5: error: uh oh
34#}
2035
21#update=add parse error again
22#file=main.zig
36#write main.zig {
2337pub fn main() !void {
2438 @compileError("uh oh");
25#expect_error=main.zig:3:1: error: expected statement, found 'EOF'
39#}
40#update {
41add parse error again#}
42#check errors {
43main.zig:3:1: error: expected statement, found 'EOF'
44#}
2645
27#update=comment @compileError call
28#file=main.zig
46#write main.zig {
2947pub fn main() !void {
3048 //@compileError("uh oh");
31#expect_error=main.zig:3:1: error: expected statement, found 'EOF'
49#}
50#update {
51comment @compileError call#}
52#check errors {
53main.zig:3:1: error: expected statement, found 'EOF'
54#}
3255
33#update=fix parse error again
34#file=main.zig
56#write main.zig {
3557pub fn main() !void {
3658 //@compileError("uh oh");
3759}
38#expect_stdout=""
60#}
61#update {
62fix parse error again#}
63#check stdout {
64#}
test/incremental/bad_import+28-12
......@@ -1,32 +1,48 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36pub fn main() !void {
47 _ = @import("foo.zig");
58 try std.Io.File.stdout().writeStreamingAll(io, "success\n");
69}
710const std = @import("std");
811const io = std.Io.Threaded.global_single_threaded.io();
9#file=foo.zig
12#}
13#write foo.zig {
1014comptime {
1115 _ = @import("bad.zig");
1216}
13#expect_error=bad.zig:1:1: error: unable to load "bad.zig": FileNotFound
14#expect_error=foo.zig:2:17: note: file imported here
17#}
18#update
19#check errors {
20bad.zig:1:1: error: unable to load "bad.zig": FileNotFound
21foo.zig:2:17: note: file imported here
22#}
1523
16#update=change bad import
17#file=foo.zig
24#write foo.zig {
1825comptime {
1926 _ = @import("this_is/not_real.zig");
2027}
21#expect_error=this_is/not_real.zig:1:1: error: unable to load "not_real.zig": FileNotFound
22#expect_error=foo.zig:2:17: note: file imported here
28#}
29#update {
30change bad import#}
31#check errors {
32this_is/not_real.zig:1:1: error: unable to load "not_real.zig": FileNotFound
33foo.zig:2:17: note: file imported here
34#}
2335
24#update=remove import of 'foo.zig'
25#file=main.zig
36#write main.zig {
2637pub fn main() !void {
2738 //_ = @import("foo.zig");
2839 try std.Io.File.stdout().writeStreamingAll(io, "success\n");
2940}
3041const std = @import("std");
3142const io = std.Io.Threaded.global_single_threaded.io();
32#expect_stdout="success\n"
43#}
44#update {
45remove import of 'foo.zig'#}
46#check stdout {
47success
48#}
test/incremental/change_bitpack_backing_int+53-30
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const Foo = packed struct(u8) { a: u4, b: i4 };
47const Bar = packed union(u10) { a: u10, b: i10 };
58pub fn main() void {}
......@@ -11,13 +14,18 @@ comptime {
1114}
1215const std = @import("std");
1316const io = std.Io.Threaded.global_single_threaded.io();
14#expect_error=main.zig:5:5: error: found compile log statement
15#expect_error=main.zig:8:5: note: also here
16#expect_compile_log=@as(type, u8)
17#expect_compile_log=@as(comptime_int, 10)
17#}
18#update
19#check errors {
20main.zig:5:5: error: found compile log statement
21main.zig:8:5: note: also here
22
23Compile Log Output:
24@as(type, u8)
25@as(comptime_int, 10)
26#}
1827
19#update=make backing types signed
20#file=main.zig
28#write main.zig {
2129const Foo = packed struct(i8) { a: u4, b: i4 };
2230const Bar = packed union(i10) { a: u10, b: i10 };
2331pub fn main() void {}
......@@ -29,13 +37,19 @@ comptime {
2937}
3038const std = @import("std");
3139const io = std.Io.Threaded.global_single_threaded.io();
32#expect_error=main.zig:5:5: error: found compile log statement
33#expect_error=main.zig:8:5: note: also here
34#expect_compile_log=@as(type, i8)
35#expect_compile_log=@as(comptime_int, 10)
40#}
41#update {
42make backing types signed#}
43#check errors {
44main.zig:5:5: error: found compile log statement
45main.zig:8:5: note: also here
46
47Compile Log Output:
48@as(type, i8)
49@as(comptime_int, 10)
50#}
3651
37#update=make backing types too small
38#file=main.zig
52#write main.zig {
3953const Foo = packed struct(i5) { a: u4, b: i4 };
4054const Bar = packed union(i8) { a: u10, b: i10 };
4155pub fn main() void {}
......@@ -47,16 +61,20 @@ comptime {
4761}
4862const std = @import("std");
4963const io = std.Io.Threaded.global_single_threaded.io();
50#expect_error=main.zig:1:20: error: backing integer bit width does not match total bit width of fields
51#expect_error=main.zig:1:27: note: backing integer 'i5' has bit width '5'
52#expect_error=main.zig:1:20: note: struct fields have total bit width '8'
53#expect_error=main.zig:2:35: error: field bit width does not match backing integer
54#expect_error=main.zig:2:35: note: field type 'u10' has bit width '10'
55#expect_error=main.zig:2:26: note: backing integer 'i8' has bit width '8'
56#expect_error=main.zig:2:35: note: all fields in a packed union must have the same bit width
64#}
65#update {
66make backing types too small#}
67#check errors {
68main.zig:1:20: error: backing integer bit width does not match total bit width of fields
69main.zig:1:27: note: backing integer 'i5' has bit width '5'
70main.zig:1:20: note: struct fields have total bit width '8'
71main.zig:2:35: error: field bit width does not match backing integer
72main.zig:2:35: note: field type 'u10' has bit width '10'
73main.zig:2:26: note: backing integer 'i8' has bit width '8'
74main.zig:2:35: note: all fields in a packed union must have the same bit width
75#}
5776
58#update=make backing types too big
59#file=main.zig
77#write main.zig {
6078const Foo = packed struct(u10) { a: u4, b: i4 };
6179const Bar = packed union(u32) { a: u10, b: i10 };
6280pub fn main() void {}
......@@ -68,10 +86,15 @@ comptime {
6886}
6987const std = @import("std");
7088const io = std.Io.Threaded.global_single_threaded.io();
71#expect_error=main.zig:1:20: error: backing integer bit width does not match total bit width of fields
72#expect_error=main.zig:1:27: note: backing integer 'u10' has bit width '10'
73#expect_error=main.zig:1:20: note: struct fields have total bit width '8'
74#expect_error=main.zig:2:36: error: field bit width does not match backing integer
75#expect_error=main.zig:2:36: note: field type 'u10' has bit width '10'
76#expect_error=main.zig:2:26: note: backing integer 'u32' has bit width '32'
77#expect_error=main.zig:2:36: note: all fields in a packed union must have the same bit width
89#}
90#update {
91make backing types too big#}
92#check errors {
93main.zig:1:20: error: backing integer bit width does not match total bit width of fields
94main.zig:1:27: note: backing integer 'u10' has bit width '10'
95main.zig:1:20: note: struct fields have total bit width '8'
96main.zig:2:36: error: field bit width does not match backing integer
97main.zig:2:36: note: field type 'u10' has bit width '10'
98main.zig:2:26: note: backing integer 'u32' has bit width '32'
99main.zig:2:36: note: all fields in a packed union must have the same bit width
100#}
test/incremental/change_container_kind+94-41
......@@ -1,61 +1,114 @@
1#update=initial version, T is struct
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const T = @import("foo.zig").T;
47pub fn main() void {
58 @compileLog(T);
69 @compileLog(@tagName(@typeInfo(T)));
710}
8#file=foo.zig
11#}
12#write foo.zig {
913pub const T = struct { x: u8 };
10#expect_error=main.zig:3:5: error: found compile log statement
11#expect_compile_log=@as(type, foo.T)
12#expect_compile_log=@as(*const [6:0]u8, "struct")
14#}
15#update {
16T is struct#}
17#check errors {
18main.zig:3:5: error: found compile log statement
19
20Compile Log Output:
21@as(type, foo.T)
22@as(*const [6:0]u8, "struct")
23#}
1324
14#update=T is union
15#file=foo.zig
25#write foo.zig {
1626pub const T = union {};
17#expect_error=main.zig:3:5: error: found compile log statement
18#expect_compile_log=@as(type, foo.T)
19#expect_compile_log=@as(*const [5:0]u8, "union")
27#}
28#update {
29T is union#}
30#check errors {
31main.zig:3:5: error: found compile log statement
32
33Compile Log Output:
34@as(type, foo.T)
35@as(*const [5:0]u8, "union")
36#}
2037
21#update=T is enum
22#file=foo.zig
38#write foo.zig {
2339pub const T = enum {};
24#expect_error=main.zig:3:5: error: found compile log statement
25#expect_compile_log=@as(type, foo.T)
26#expect_compile_log=@as(*const [4:0]u8, "enum")
40#}
41#update {
42T is enum#}
43#check errors {
44main.zig:3:5: error: found compile log statement
2745
28#update=T is opaque
29#file=foo.zig
46Compile Log Output:
47@as(type, foo.T)
48@as(*const [4:0]u8, "enum")
49#}
50
51#write foo.zig {
3052pub const T = opaque {};
31#expect_error=main.zig:3:5: error: found compile log statement
32#expect_compile_log=@as(type, foo.T)
33#expect_compile_log=@as(*const [6:0]u8, "opaque")
53#}
54#update {
55T is opaque#}
56#check errors {
57main.zig:3:5: error: found compile log statement
58
59Compile Log Output:
60@as(type, foo.T)
61@as(*const [6:0]u8, "opaque")
62#}
3463
35#update=T is packed struct
36#file=foo.zig
64#write foo.zig {
3765pub const T = packed struct {};
38#expect_error=main.zig:3:5: error: found compile log statement
39#expect_compile_log=@as(type, foo.T)
40#expect_compile_log=@as(*const [6:0]u8, "struct")
66#}
67#update {
68T is packed struct#}
69#check errors {
70main.zig:3:5: error: found compile log statement
4171
42#update=T is packed union
43#file=foo.zig
72Compile Log Output:
73@as(type, foo.T)
74@as(*const [6:0]u8, "struct")
75#}
76
77#write foo.zig {
4478pub const T = packed union { x: void }; // packed unions need at least 1 field
45#expect_error=main.zig:3:5: error: found compile log statement
46#expect_compile_log=@as(type, foo.T)
47#expect_compile_log=@as(*const [5:0]u8, "union")
79#}
80#update {
81T is packed union#}
82#check errors {
83main.zig:3:5: error: found compile log statement
84
85Compile Log Output:
86@as(type, foo.T)
87@as(*const [5:0]u8, "union")
88#}
4889
49#update=T is extern struct
50#file=foo.zig
90#write foo.zig {
5191pub const T = extern struct {};
52#expect_error=main.zig:3:5: error: found compile log statement
53#expect_compile_log=@as(type, foo.T)
54#expect_compile_log=@as(*const [6:0]u8, "struct")
92#}
93#update {
94T is extern struct#}
95#check errors {
96main.zig:3:5: error: found compile log statement
5597
56#update=T is extern union
57#file=foo.zig
98Compile Log Output:
99@as(type, foo.T)
100@as(*const [6:0]u8, "struct")
101#}
102
103#write foo.zig {
58104pub const T = extern union { x: void }; // extern unions need at least 1 field
59#expect_error=main.zig:3:5: error: found compile log statement
60#expect_compile_log=@as(type, foo.T)
61#expect_compile_log=@as(*const [5:0]u8, "union")
105#}
106#update {
107T is extern union#}
108#check errors {
109main.zig:3:5: error: found compile log statement
110
111Compile Log Output:
112@as(type, foo.T)
113@as(*const [5:0]u8, "union")
114#}
test/incremental/change_embed_file+46-19
......@@ -1,45 +1,72 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const std = @import("std");
47const string = @embedFile("string.txt");
58pub fn main() !void {
69 try std.Io.File.stdout().writeStreamingAll(io, string);
710}
811const io = std.Io.Threaded.global_single_threaded.io();
9#file=string.txt
12#}
13#write string.txt {
14Hello, World!
15#}
16#update
17#check stdout {
1018Hello, World!
11#expect_stdout="Hello, World!\n"
19#}
1220
13#update=change file contents
14#file=string.txt
21#write string.txt {
1522Hello again, World!
16#expect_stdout="Hello again, World!\n"
23#}
24#update {
25change file contents#}
26#check stdout {
27Hello again, World!
28#}
1729
18#update=delete file
19#rm_file=string.txt
20#expect_error=main.zig:2:27: error: failed opening "string.txt": FileNotFound
30#delete string.txt
31#update {
32delete file#}
33#check errors {
34main.zig:2:27: error: failed opening "string.txt": FileNotFound
35#}
2136
22#update=remove reference to file
23#file=main.zig
37#write main.zig {
2438const std = @import("std");
2539const string = @embedFile("string.txt");
2640pub fn main() !void {
2741 try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n");
2842}
2943const io = std.Io.Threaded.global_single_threaded.io();
30#expect_stdout="a hardcoded string\n"
44#}
45#update {
46remove reference to file#}
47#check stdout {
48a hardcoded string
49#}
3150
32#update=re-introduce reference to file
33#file=main.zig
51#write main.zig {
3452const std = @import("std");
3553const string = @embedFile("string.txt");
3654pub fn main() !void {
3755 try std.Io.File.stdout().writeStreamingAll(io, string);
3856}
3957const io = std.Io.Threaded.global_single_threaded.io();
40#expect_error=main.zig:2:27: error: failed opening "string.txt": FileNotFound
58#}
59#update {
60re-introduce reference to file#}
61#check errors {
62main.zig:2:27: error: failed opening "string.txt": FileNotFound
63#}
4164
42#update=recreate file
43#file=string.txt
65#write string.txt {
66We're back, World!
67#}
68#update {
69recreate file#}
70#check stdout {
4471We're back, World!
45#expect_stdout="We're back, World!\n"
72#}
test/incremental/change_enum_tag_type deleted-93
......@@ -1,93 +0,0 @@
1#update=initial version
2#file=main.zig
3const Tag = u2;
4const Foo = enum(Tag) {
5 a,
6 b,
7 c,
8 d,
9};
10pub fn main() !void {
11 var val: Foo = undefined;
12 val = .a;
13 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
14 try stdout_writer.interface.print("{s}\n", .{@tagName(val)});
15}
16const std = @import("std");
17const io = std.Io.Threaded.global_single_threaded.io();
18#expect_stdout="a\n"
19#update=too many enum fields
20#file=main.zig
21const Tag = u2;
22const Foo = enum(Tag) {
23 a,
24 b,
25 c,
26 d,
27 e,
28};
29pub fn main() !void {
30 var val: Foo = undefined;
31 val = .a;
32 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
33 try stdout_writer.interface.print("{s}\n", .{@tagName(val)});
34}
35comptime {
36 // These can't be true at the same time; analysis should stop as soon as it sees `Foo`
37 std.debug.assert(@intFromEnum(Foo.e) == 4);
38 std.debug.assert(@TypeOf(@intFromEnum(Foo.e)) == Tag);
39}
40const std = @import("std");
41const io = std.Io.Threaded.global_single_threaded.io();
42#expect_error=main.zig:7:5: error: enum tag value '4' too large for type 'u2'
43#update=increase tag size
44#file=main.zig
45const Tag = u3;
46const Foo = enum(Tag) {
47 a,
48 b,
49 c,
50 d,
51 e,
52};
53pub fn main() !void {
54 var val: Foo = undefined;
55 val = .a;
56 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
57 try stdout_writer.interface.print("{s}\n", .{@tagName(val)});
58}
59const std = @import("std");
60const io = std.Io.Threaded.global_single_threaded.io();
61#expect_stdout="a\n"
62#update=specify tag directly
63#file=main.zig
64const Foo = enum(u3) {
65 a,
66 b,
67 c,
68 d,
69 e,
70};
71pub fn main() !void {
72 @compileLog(@typeInfo(Foo).@"enum".tag_type);
73}
74const std = @import("std");
75const io = std.Io.Threaded.global_single_threaded.io();
76#expect_error=main.zig:9:5: error: found compile log statement
77#expect_compile_log=@as(type, u3)
78#update=change directly-specified tag type
79#file=main.zig
80const Foo = enum(u8) {
81 a,
82 b,
83 c,
84 d,
85 e,
86};
87pub fn main() !void {
88 @compileLog(@typeInfo(Foo).@"enum".tag_type);
89}
90const std = @import("std");
91const io = std.Io.Threaded.global_single_threaded.io();
92#expect_error=main.zig:9:5: error: found compile log statement
93#expect_compile_log=@as(type, u8)
test/incremental/change_enum_tag_type/0/main.zig created+15
......@@ -0,0 +1,15 @@
1const Tag = u2;
2const Foo = enum(Tag) {
3 a,
4 b,
5 c,
6 d,
7};
8pub fn main() !void {
9 var val: Foo = undefined;
10 val = .a;
11 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
12 try stdout_writer.interface.print("{s}\n", .{@tagName(val)});
13}
14const std = @import("std");
15const io = std.Io.Threaded.global_single_threaded.io();
test/incremental/change_enum_tag_type/1/main.zig created+21
......@@ -0,0 +1,21 @@
1const Tag = u2;
2const Foo = enum(Tag) {
3 a,
4 b,
5 c,
6 d,
7 e,
8};
9pub fn main() !void {
10 var val: Foo = undefined;
11 val = .a;
12 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
13 try stdout_writer.interface.print("{s}\n", .{@tagName(val)});
14}
15comptime {
16 // These can't be true at the same time; analysis should stop as soon as it sees `Foo`
17 std.debug.assert(@backingInt(Foo.e) == 4);
18 std.debug.assert(@TypeOf(@backingInt(Foo.e)) == Tag);
19}
20const std = @import("std");
21const io = std.Io.Threaded.global_single_threaded.io();
test/incremental/change_enum_tag_type/2/main.zig created+16
......@@ -0,0 +1,16 @@
1const Tag = u3;
2const Foo = enum(Tag) {
3 a,
4 b,
5 c,
6 d,
7 e,
8};
9pub fn main() !void {
10 var val: Foo = undefined;
11 val = .a;
12 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
13 try stdout_writer.interface.print("{s}\n", .{@tagName(val)});
14}
15const std = @import("std");
16const io = std.Io.Threaded.global_single_threaded.io();
test/incremental/change_enum_tag_type/3/main.zig created+12
......@@ -0,0 +1,12 @@
1const Foo = enum(u3) {
2 a,
3 b,
4 c,
5 d,
6 e,
7};
8pub fn main() !void {
9 @compileLog(@typeInfo(Foo).@"enum".tag_type);
10}
11const std = @import("std");
12const io = std.Io.Threaded.global_single_threaded.io();
test/incremental/change_enum_tag_type/4/main.zig created+12
......@@ -0,0 +1,12 @@
1const Foo = enum(u8) {
2 a,
3 b,
4 c,
5 d,
6 e,
7};
8pub fn main() !void {
9 @compileLog(@typeInfo(Foo).@"enum".tag_type);
10}
11const std = @import("std");
12const io = std.Io.Threaded.global_single_threaded.io();
test/incremental/change_enum_tag_type/manifest created+43
......@@ -0,0 +1,43 @@
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig 0/main.zig
6#update
7#check stdout {
8a
9#}
10
11#write main.zig 1/main.zig
12#update {
13too many enum fields#}
14#check errors {
15main.zig:7:5: error: enum tag value '4' too large for type 'u2'
16#}
17
18#write main.zig 2/main.zig
19#update {
20increase tag size#}
21#check stdout {
22a
23#}
24
25#write main.zig 3/main.zig
26#update {
27specify tag directly#}
28#check errors {
29main.zig:9:5: error: found compile log statement
30
31Compile Log Output:
32@as(type, u3)
33#}
34
35#write main.zig 4/main.zig
36#update {
37change directly-specified tag type#}
38#check errors {
39main.zig:9:5: error: found compile log statement
40
41Compile Log Output:
42@as(type, u8)
43#}
test/incremental/change_exports deleted-169
......@@ -1,169 +0,0 @@
1#update=initial version
2#file=main.zig
3export fn foo() void {}
4const bar: u32 = 123;
5const other: u32 = 456;
6comptime {
7 @export(&bar, .{ .name = "bar" });
8}
9pub fn main() !void {
10 const S = struct {
11 extern fn foo() void;
12 extern const bar: u32;
13 };
14 S.foo();
15 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
16 try stdout_writer.interface.print("{}\n", .{S.bar});
17}
18const std = @import("std");
19const io = std.Io.Threaded.global_single_threaded.io();
20#expect_stdout="123\n"
21
22#update=add conflict
23#file=main.zig
24export fn foo() void {}
25const bar: u32 = 123;
26const other: u32 = 456;
27comptime {
28 @export(&bar, .{ .name = "bar" });
29 @export(&other, .{ .name = "foo" });
30}
31pub fn main() !void {
32 const S = struct {
33 extern fn foo() void;
34 extern const bar: u32;
35 extern const other: u32;
36 };
37 S.foo();
38 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
39 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
40}
41const std = @import("std");
42const io = std.Io.Threaded.global_single_threaded.io();
43#expect_error=main.zig:6:5: error: exported symbol collision: foo
44#expect_error=main.zig:1:1: note: other symbol here
45
46#update=resolve conflict
47#file=main.zig
48export fn foo() void {}
49const bar: u32 = 123;
50const other: u32 = 456;
51comptime {
52 @export(&bar, .{ .name = "bar" });
53 @export(&other, .{ .name = "other" });
54}
55pub fn main() !void {
56 const S = struct {
57 extern fn foo() void;
58 extern const bar: u32;
59 extern const other: u32;
60 };
61 S.foo();
62 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
63 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
64}
65const std = @import("std");
66const io = std.Io.Threaded.global_single_threaded.io();
67#expect_stdout="123 456\n"
68
69#update=put exports in decl
70#file=main.zig
71export fn foo() void {}
72const bar: u32 = 123;
73const other: u32 = 456;
74const does_exports = {
75 @export(&bar, .{ .name = "bar" });
76 @export(&other, .{ .name = "other" });
77};
78comptime {
79 _ = does_exports;
80}
81pub fn main() !void {
82 const S = struct {
83 extern fn foo() void;
84 extern const bar: u32;
85 extern const other: u32;
86 };
87 S.foo();
88 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
89 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
90}
91const std = @import("std");
92const io = std.Io.Threaded.global_single_threaded.io();
93#expect_stdout="123 456\n"
94
95#update=remove reference to exporting decl
96#file=main.zig
97export fn foo() void {}
98const bar: u32 = 123;
99const other: u32 = 456;
100const does_exports = {
101 @export(&bar, .{ .name = "bar" });
102 @export(&other, .{ .name = "other" });
103};
104comptime {
105 //_ = does_exports;
106}
107pub fn main() !void {
108 const S = struct {
109 extern fn foo() void;
110 };
111 S.foo();
112}
113const std = @import("std");
114#expect_stdout=""
115
116#update=mark consts as export
117#file=main.zig
118export fn foo() void {}
119export const bar: u32 = 123;
120export const other: u32 = 456;
121const does_exports = {
122 @export(&bar, .{ .name = "bar" });
123 @export(&other, .{ .name = "other" });
124};
125comptime {
126 //_ = does_exports;
127}
128pub fn main() !void {
129 const S = struct {
130 extern fn foo() void;
131 extern const bar: u32;
132 extern const other: u32;
133 };
134 S.foo();
135 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
136 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
137}
138const std = @import("std");
139const io = std.Io.Threaded.global_single_threaded.io();
140#expect_stdout="123 456\n"
141
142#update=reintroduce reference to exporting decl, introducing conflict
143#file=main.zig
144export fn foo() void {}
145export const bar: u32 = 123;
146export const other: u32 = 456;
147const does_exports = {
148 @export(&bar, .{ .name = "bar" });
149 @export(&other, .{ .name = "other" });
150};
151comptime {
152 _ = does_exports;
153}
154pub fn main() !void {
155 const S = struct {
156 extern fn foo() void;
157 extern const bar: u32;
158 extern const other: u32;
159 };
160 S.foo();
161 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
162 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
163}
164const std = @import("std");
165const io = std.Io.Threaded.global_single_threaded.io();
166#expect_error=main.zig:5:5: error: exported symbol collision: bar
167#expect_error=main.zig:2:1: note: other symbol here
168#expect_error=main.zig:6:5: error: exported symbol collision: other
169#expect_error=main.zig:3:1: note: other symbol here
test/incremental/change_exports/0/main.zig created+17
......@@ -0,0 +1,17 @@
1export fn foo() void {}
2const bar: u32 = 123;
3const other: u32 = 456;
4comptime {
5 @export(&bar, .{ .name = "bar" });
6}
7pub fn main() !void {
8 const S = struct {
9 extern fn foo() void;
10 extern const bar: u32;
11 };
12 S.foo();
13 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
14 try stdout_writer.interface.print("{}\n", .{S.bar});
15}
16const std = @import("std");
17const io = std.Io.Threaded.global_single_threaded.io();
test/incremental/change_exports/1/main.zig created+19
......@@ -0,0 +1,19 @@
1export fn foo() void {}
2const bar: u32 = 123;
3const other: u32 = 456;
4comptime {
5 @export(&bar, .{ .name = "bar" });
6 @export(&other, .{ .name = "foo" });
7}
8pub fn main() !void {
9 const S = struct {
10 extern fn foo() void;
11 extern const bar: u32;
12 extern const other: u32;
13 };
14 S.foo();
15 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
16 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
17}
18const std = @import("std");
19const io = std.Io.Threaded.global_single_threaded.io();
test/incremental/change_exports/2/main.zig created+19
......@@ -0,0 +1,19 @@
1export fn foo() void {}
2const bar: u32 = 123;
3const other: u32 = 456;
4comptime {
5 @export(&bar, .{ .name = "bar" });
6 @export(&other, .{ .name = "other" });
7}
8pub fn main() !void {
9 const S = struct {
10 extern fn foo() void;
11 extern const bar: u32;
12 extern const other: u32;
13 };
14 S.foo();
15 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
16 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
17}
18const std = @import("std");
19const io = std.Io.Threaded.global_single_threaded.io();
test/incremental/change_exports/3/main.zig created+22
......@@ -0,0 +1,22 @@
1export fn foo() void {}
2const bar: u32 = 123;
3const other: u32 = 456;
4const does_exports = {
5 @export(&bar, .{ .name = "bar" });
6 @export(&other, .{ .name = "other" });
7};
8comptime {
9 _ = does_exports;
10}
11pub fn main() !void {
12 const S = struct {
13 extern fn foo() void;
14 extern const bar: u32;
15 extern const other: u32;
16 };
17 S.foo();
18 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
19 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
20}
21const std = @import("std");
22const io = std.Io.Threaded.global_single_threaded.io();
test/incremental/change_exports/4/main.zig created+17
......@@ -0,0 +1,17 @@
1export fn foo() void {}
2const bar: u32 = 123;
3const other: u32 = 456;
4const does_exports = {
5 @export(&bar, .{ .name = "bar" });
6 @export(&other, .{ .name = "other" });
7};
8comptime {
9 //_ = does_exports;
10}
11pub fn main() !void {
12 const S = struct {
13 extern fn foo() void;
14 };
15 S.foo();
16}
17const std = @import("std");
test/incremental/change_exports/5/main.zig created+22
......@@ -0,0 +1,22 @@
1export fn foo() void {}
2export const bar: u32 = 123;
3export const other: u32 = 456;
4const does_exports = {
5 @export(&bar, .{ .name = "bar" });
6 @export(&other, .{ .name = "other" });
7};
8comptime {
9 //_ = does_exports;
10}
11pub fn main() !void {
12 const S = struct {
13 extern fn foo() void;
14 extern const bar: u32;
15 extern const other: u32;
16 };
17 S.foo();
18 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
19 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
20}
21const std = @import("std");
22const io = std.Io.Threaded.global_single_threaded.io();
test/incremental/change_exports/6/main.zig created+22
......@@ -0,0 +1,22 @@
1export fn foo() void {}
2export const bar: u32 = 123;
3export const other: u32 = 456;
4const does_exports = {
5 @export(&bar, .{ .name = "bar" });
6 @export(&other, .{ .name = "other" });
7};
8comptime {
9 _ = does_exports;
10}
11pub fn main() !void {
12 const S = struct {
13 extern fn foo() void;
14 extern const bar: u32;
15 extern const other: u32;
16 };
17 S.foo();
18 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
19 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
20}
21const std = @import("std");
22const io = std.Io.Threaded.global_single_threaded.io();
test/incremental/change_exports/manifest created+54
......@@ -0,0 +1,54 @@
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig 0/main.zig
6#update
7#check stdout {
8123
9#}
10
11#write main.zig 1/main.zig
12#update {
13add conflict#}
14#check errors {
15main.zig:6:5: error: exported symbol collision: foo
16main.zig:1:1: note: other symbol here
17#}
18
19#write main.zig 2/main.zig
20#update {
21resolve conflict#}
22#check stdout {
23123 456
24#}
25
26#write main.zig 3/main.zig
27#update {
28put exports in decl#}
29#check stdout {
30123 456
31#}
32
33#write main.zig 4/main.zig
34#update {
35remove reference to exporting decl#}
36#check stdout {
37#}
38
39#write main.zig 5/main.zig
40#update {
41mark consts as export#}
42#check stdout {
43123 456
44#}
45
46#write main.zig 6/main.zig
47#update {
48reintroduce reference to exporting decl, introducing conflict#}
49#check errors {
50main.zig:5:5: error: exported symbol collision: bar
51main.zig:2:1: note: other symbol here
52main.zig:6:5: error: exported symbol collision: other
53main.zig:3:1: note: other symbol here
54#}
test/incremental/change_fn_type+24-9
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36pub fn main() !void {
47 try foo(123);
58}
......@@ -9,10 +12,13 @@ fn foo(x: u8) !void {
912}
1013const std = @import("std");
1114const io = std.Io.Threaded.global_single_threaded.io();
12#expect_stdout="123\n"
15#}
16#update
17#check stdout {
18123
19#}
1320
14#update=change function type
15#file=main.zig
21#write main.zig {
1622pub fn main() !void {
1723 try foo(123);
1824}
......@@ -22,10 +28,14 @@ fn foo(x: i64) !void {
2228}
2329const std = @import("std");
2430const io = std.Io.Threaded.global_single_threaded.io();
25#expect_stdout="123\n"
31#}
32#update {
33change function type#}
34#check stdout {
35123
36#}
2637
27#update=change function argument
28#file=main.zig
38#write main.zig {
2939pub fn main() !void {
3040 try foo(-42);
3141}
......@@ -35,4 +45,9 @@ fn foo(x: i64) !void {
3545}
3646const std = @import("std");
3747const io = std.Io.Threaded.global_single_threaded.io();
38#expect_stdout="-42\n"
48#}
49#update {
50change function argument#}
51#check stdout {
52-42
53#}
test/incremental/change_generic_line_number+20-6
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const std = @import("std");
47const io = std.Io.Threaded.global_single_threaded.io();
58fn Printer(message: []const u8) type {
......@@ -13,9 +16,14 @@ pub fn main() !void {
1316 try Printer("foo\n").print();
1417 try Printer("bar\n").print();
1518}
16#expect_stdout="foo\nbar\n"
17#update=change line number
18#file=main.zig
19#}
20#update
21#check stdout {
22foo
23bar
24#}
25
26#write main.zig {
1927const std = @import("std");
2028const io = std.Io.Threaded.global_single_threaded.io();
2129
......@@ -30,4 +38,10 @@ pub fn main() !void {
3038 try Printer("foo\n").print();
3139 try Printer("bar\n").print();
3240}
33#expect_stdout="foo\nbar\n"
41#}
42#update {
43change line number#}
44#check stdout {
45foo
46bar
47#}
test/incremental/change_line_number+18-6
......@@ -1,17 +1,29 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const std = @import("std");
47pub fn main() !void {
58 try std.Io.File.stdout().writeStreamingAll(io, "foo\n");
69}
710const io = std.Io.Threaded.global_single_threaded.io();
8#expect_stdout="foo\n"
9#update=change line number
10#file=main.zig
11#}
12#update
13#check stdout {
14foo
15#}
16
17#write main.zig {
1118const std = @import("std");
1219
1320pub fn main() !void {
1421 try std.Io.File.stdout().writeStreamingAll(io, "foo\n");
1522}
1623const io = std.Io.Threaded.global_single_threaded.io();
17#expect_stdout="foo\n"
24#}
25#update {
26change line number#}
27#check stdout {
28foo
29#}
test/incremental/change_module+49-23
......@@ -1,61 +1,87 @@
1#module=foo=foo.zig
1#exe {
2--dep foo -Mroot=main.zig
3-Mfoo=foo.zig
4#}
25
3#update=initial version
4#file=main.zig
6#write main.zig {
57pub fn main() void {
68 _ = @import("foo");
79 //_ = @import("other.zig");
810}
9#file=foo.zig
11#}
12#write foo.zig {
1013comptime {
1114 _ = @import("other.zig");
1215}
13#file=other.zig
16#}
17#write other.zig {
1418fn f() void {
1519 @compileLog(@src().module);
1620}
1721comptime {
1822 f();
1923}
20#expect_error=other.zig:2:5: error: found compile log statement
21#expect_compile_log=@as([:0]const u8, "foo"[0..3])
24#}
25#update
26#check errors {
27other.zig:2:5: error: found compile log statement
2228
23#update=change module of other.zig
24#file=main.zig
29Compile Log Output:
30@as([:0]const u8, "foo"[0..3])
31#}
32
33#write main.zig {
2534pub fn main() void {
2635 _ = @import("foo");
2736 _ = @import("other.zig");
2837}
29#file=foo.zig
38#}
39#write foo.zig {
3040comptime {
3141 //_ = @import("other.zig");
3242}
33#expect_error=other.zig:2:5: error: found compile log statement
34#expect_compile_log=@as([:0]const u8, "root"[0..4])
43#}
44#update {
45change module of other.zig#}
46#check errors {
47other.zig:2:5: error: found compile log statement
48
49Compile Log Output:
50@as([:0]const u8, "root"[0..4])
51#}
3552
36#update=put other.zig in both modules
37#file=main.zig
53#write main.zig {
3854pub fn main() void {
3955 _ = @import("foo");
4056 _ = @import("other.zig");
4157}
42#file=foo.zig
58#}
59#write foo.zig {
4360comptime {
4461 _ = @import("other.zig");
4562}
46#expect_error=other.zig:1:1: error: file exists in modules 'root' and 'foo'
47#expect_error=other.zig:1:1: note: files must belong to only one module
48#expect_error=main.zig:3:17: note: file is imported here by the root of module 'root'
49#expect_error=foo.zig:2:17: note: file is imported here by the root of module 'foo'
63#}
64#update {
65put other.zig in both modules#}
66#check errors {
67other.zig:1:1: error: file exists in modules 'root' and 'foo'
68other.zig:1:1: note: files must belong to only one module
69main.zig:3:17: note: file is imported here by the root of module 'root'
70foo.zig:2:17: note: file is imported here by the root of module 'foo'
71#}
5072
51#update=put other.zig in no modules
52#file=main.zig
73#write main.zig {
5374pub fn main() void {
5475 _ = @import("foo");
5576 //_ = @import("other.zig");
5677}
57#file=foo.zig
78#}
79#write foo.zig {
5880comptime {
5981 //_ = @import("other.zig");
6082}
61#expect_stdout=""
83#}
84#update {
85put other.zig in no modules#}
86#check stdout {
87#}
test/incremental/change_panic_handler+24-9
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36pub fn main() !u8 {
47 var a: u8 = undefined;
58 a = 255;
......@@ -14,10 +17,13 @@ fn myPanic(msg: []const u8, _: ?usize) noreturn {
1417}
1518const std = @import("std");
1619const io = std.Io.Threaded.global_single_threaded.io();
17#expect_stdout="panic message: integer overflow\n"
20#}
21#update
22#check stdout {
23panic message: integer overflow
24#}
1825
19#update=change the panic handler body
20#file=main.zig
26#write main.zig {
2127pub fn main() !u8 {
2228 var a: u8 = undefined;
2329 a = 255;
......@@ -32,10 +38,14 @@ fn myPanic(msg: []const u8, _: ?usize) noreturn {
3238}
3339const std = @import("std");
3440const io = std.Io.Threaded.global_single_threaded.io();
35#expect_stdout="new panic message: integer overflow\n"
41#}
42#update {
43change the panic handler body#}
44#check stdout {
45new panic message: integer overflow
46#}
3647
37#update=change the panic handler function value
38#file=main.zig
48#write main.zig {
3949pub fn main() !u8 {
4050 var a: u8 = undefined;
4151 a = 255;
......@@ -50,4 +60,9 @@ fn myPanicNew(msg: []const u8, _: ?usize) noreturn {
5060}
5161const std = @import("std");
5262const io = std.Io.Threaded.global_single_threaded.io();
53#expect_stdout="third panic message: integer overflow\n"
63#}
64#update {
65change the panic handler function value#}
66#check stdout {
67third panic message: integer overflow
68#}
test/incremental/change_panic_handler_explicit deleted-149
......@@ -1,149 +0,0 @@
1#update=initial version
2#file=main.zig
3pub fn main() !u8 {
4 var a: u8 = undefined;
5 a = 255;
6 _ = a + 1;
7 return 1;
8}
9const no_panic = std.debug.no_panic;
10pub const panic = struct {
11 pub const call = myPanic;
12 pub fn integerOverflow() noreturn {
13 @panic("integer overflow");
14 }
15 pub const sentinelMismatch = no_panic.sentinelMismatch;
16 pub const unwrapError = no_panic.unwrapError;
17 pub const outOfBounds = no_panic.outOfBounds;
18 pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;
19 pub const inactiveUnionField = no_panic.inactiveUnionField;
20 pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder;
21 pub const reachedUnreachable = no_panic.reachedUnreachable;
22 pub const unwrapNull = no_panic.unwrapNull;
23 pub const castToNull = no_panic.castToNull;
24 pub const incorrectAlignment = no_panic.incorrectAlignment;
25 pub const invalidErrorCode = no_panic.invalidErrorCode;
26 pub const unexpectedErrorCode = no_panic.unexpectedErrorCode;
27 pub const integerOutOfBounds = no_panic.integerOutOfBounds;
28 pub const shlOverflow = no_panic.shlOverflow;
29 pub const shrOverflow = no_panic.shrOverflow;
30 pub const divideByZero = no_panic.divideByZero;
31 pub const exactDivisionRemainder = no_panic.exactDivisionRemainder;
32 pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds;
33 pub const corruptSwitch = no_panic.corruptSwitch;
34 pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
35 pub const invalidEnumValue = no_panic.invalidEnumValue;
36 pub const forLenMismatch = no_panic.forLenMismatch;
37 pub const copyLenMismatch = no_panic.copyLenMismatch;
38 pub const memcpyAlias = no_panic.memcpyAlias;
39 pub const noreturnReturned = no_panic.noreturnReturned;
40 pub const loadUninstantiableType = no_panic.loadUninstantiableType;
41};
42fn myPanic(msg: []const u8, _: ?usize) noreturn {
43 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
44 stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {};
45 std.process.exit(0);
46}
47const std = @import("std");
48const io = std.Io.Threaded.global_single_threaded.io();
49#expect_stdout="panic message: integer overflow\n"
50
51#update=change the panic handler body
52#file=main.zig
53pub fn main() !u8 {
54 var a: u8 = undefined;
55 a = 255;
56 _ = a + 1;
57 return 1;
58}
59const no_panic = std.debug.no_panic;
60pub const panic = struct {
61 pub const call = myPanic;
62 pub fn integerOverflow() noreturn {
63 @panic("integer overflow");
64 }
65 pub const sentinelMismatch = no_panic.sentinelMismatch;
66 pub const unwrapError = no_panic.unwrapError;
67 pub const outOfBounds = no_panic.outOfBounds;
68 pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;
69 pub const inactiveUnionField = no_panic.inactiveUnionField;
70 pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder;
71 pub const reachedUnreachable = no_panic.reachedUnreachable;
72 pub const unwrapNull = no_panic.unwrapNull;
73 pub const castToNull = no_panic.castToNull;
74 pub const incorrectAlignment = no_panic.incorrectAlignment;
75 pub const invalidErrorCode = no_panic.invalidErrorCode;
76 pub const unexpectedErrorCode = no_panic.unexpectedErrorCode;
77 pub const integerOutOfBounds = no_panic.integerOutOfBounds;
78 pub const shlOverflow = no_panic.shlOverflow;
79 pub const shrOverflow = no_panic.shrOverflow;
80 pub const divideByZero = no_panic.divideByZero;
81 pub const exactDivisionRemainder = no_panic.exactDivisionRemainder;
82 pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds;
83 pub const corruptSwitch = no_panic.corruptSwitch;
84 pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
85 pub const invalidEnumValue = no_panic.invalidEnumValue;
86 pub const forLenMismatch = no_panic.forLenMismatch;
87 pub const copyLenMismatch = no_panic.copyLenMismatch;
88 pub const memcpyAlias = no_panic.memcpyAlias;
89 pub const noreturnReturned = no_panic.noreturnReturned;
90 pub const loadUninstantiableType = no_panic.loadUninstantiableType;
91};
92fn myPanic(msg: []const u8, _: ?usize) noreturn {
93 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
94 stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {};
95 std.process.exit(0);
96}
97const std = @import("std");
98const io = std.Io.Threaded.global_single_threaded.io();
99#expect_stdout="new panic message: integer overflow\n"
100
101#update=change the panic handler function value
102#file=main.zig
103pub fn main() !u8 {
104 var a: u8 = undefined;
105 a = 255;
106 _ = a + 1;
107 return 1;
108}
109const no_panic = std.debug.no_panic;
110pub const panic = struct {
111 pub const call = myPanicNew;
112 pub fn integerOverflow() noreturn {
113 @panic("integer overflow");
114 }
115 pub const sentinelMismatch = std.debug.no_panic.sentinelMismatch;
116 pub const unwrapError = std.debug.no_panic.unwrapError;
117 pub const outOfBounds = std.debug.no_panic.outOfBounds;
118 pub const startGreaterThanEnd = std.debug.no_panic.startGreaterThanEnd;
119 pub const inactiveUnionField = std.debug.no_panic.inactiveUnionField;
120 pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder;
121 pub const reachedUnreachable = no_panic.reachedUnreachable;
122 pub const unwrapNull = no_panic.unwrapNull;
123 pub const castToNull = no_panic.castToNull;
124 pub const incorrectAlignment = no_panic.incorrectAlignment;
125 pub const invalidErrorCode = no_panic.invalidErrorCode;
126 pub const unexpectedErrorCode = no_panic.unexpectedErrorCode;
127 pub const integerOutOfBounds = no_panic.integerOutOfBounds;
128 pub const shlOverflow = no_panic.shlOverflow;
129 pub const shrOverflow = no_panic.shrOverflow;
130 pub const divideByZero = no_panic.divideByZero;
131 pub const exactDivisionRemainder = no_panic.exactDivisionRemainder;
132 pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds;
133 pub const corruptSwitch = no_panic.corruptSwitch;
134 pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
135 pub const invalidEnumValue = no_panic.invalidEnumValue;
136 pub const forLenMismatch = no_panic.forLenMismatch;
137 pub const copyLenMismatch = no_panic.copyLenMismatch;
138 pub const memcpyAlias = no_panic.memcpyAlias;
139 pub const noreturnReturned = no_panic.noreturnReturned;
140 pub const loadUninstantiableType = no_panic.loadUninstantiableType;
141};
142fn myPanicNew(msg: []const u8, _: ?usize) noreturn {
143 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
144 stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {};
145 std.process.exit(0);
146}
147const std = @import("std");
148const io = std.Io.Threaded.global_single_threaded.io();
149#expect_stdout="third panic message: integer overflow\n"
test/incremental/change_panic_handler_explicit/0/main.zig created+46
......@@ -0,0 +1,46 @@
1pub fn main() !u8 {
2 var a: u8 = undefined;
3 a = 255;
4 _ = a + 1;
5 return 1;
6}
7const no_panic = std.debug.no_panic;
8pub const panic = struct {
9 pub const call = myPanic;
10 pub fn integerOverflow() noreturn {
11 @panic("integer overflow");
12 }
13 pub const sentinelMismatch = no_panic.sentinelMismatch;
14 pub const unwrapError = no_panic.unwrapError;
15 pub const outOfBounds = no_panic.outOfBounds;
16 pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;
17 pub const inactiveUnionField = no_panic.inactiveUnionField;
18 pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder;
19 pub const reachedUnreachable = no_panic.reachedUnreachable;
20 pub const unwrapNull = no_panic.unwrapNull;
21 pub const castToNull = no_panic.castToNull;
22 pub const incorrectAlignment = no_panic.incorrectAlignment;
23 pub const invalidErrorCode = no_panic.invalidErrorCode;
24 pub const unexpectedErrorCode = no_panic.unexpectedErrorCode;
25 pub const integerOutOfBounds = no_panic.integerOutOfBounds;
26 pub const shlOverflow = no_panic.shlOverflow;
27 pub const shrOverflow = no_panic.shrOverflow;
28 pub const divideByZero = no_panic.divideByZero;
29 pub const exactDivisionRemainder = no_panic.exactDivisionRemainder;
30 pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds;
31 pub const corruptSwitch = no_panic.corruptSwitch;
32 pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
33 pub const invalidEnumValue = no_panic.invalidEnumValue;
34 pub const forLenMismatch = no_panic.forLenMismatch;
35 pub const copyLenMismatch = no_panic.copyLenMismatch;
36 pub const memcpyAlias = no_panic.memcpyAlias;
37 pub const noreturnReturned = no_panic.noreturnReturned;
38 pub const loadUninstantiableType = no_panic.loadUninstantiableType;
39};
40fn myPanic(msg: []const u8, _: ?usize) noreturn {
41 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
42 stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {};
43 std.process.exit(0);
44}
45const std = @import("std");
46const io = std.Io.Threaded.global_single_threaded.io();
test/incremental/change_panic_handler_explicit/1/main.zig created+46
......@@ -0,0 +1,46 @@
1pub fn main() !u8 {
2 var a: u8 = undefined;
3 a = 255;
4 _ = a + 1;
5 return 1;
6}
7const no_panic = std.debug.no_panic;
8pub const panic = struct {
9 pub const call = myPanic;
10 pub fn integerOverflow() noreturn {
11 @panic("integer overflow");
12 }
13 pub const sentinelMismatch = no_panic.sentinelMismatch;
14 pub const unwrapError = no_panic.unwrapError;
15 pub const outOfBounds = no_panic.outOfBounds;
16 pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;
17 pub const inactiveUnionField = no_panic.inactiveUnionField;
18 pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder;
19 pub const reachedUnreachable = no_panic.reachedUnreachable;
20 pub const unwrapNull = no_panic.unwrapNull;
21 pub const castToNull = no_panic.castToNull;
22 pub const incorrectAlignment = no_panic.incorrectAlignment;
23 pub const invalidErrorCode = no_panic.invalidErrorCode;
24 pub const unexpectedErrorCode = no_panic.unexpectedErrorCode;
25 pub const integerOutOfBounds = no_panic.integerOutOfBounds;
26 pub const shlOverflow = no_panic.shlOverflow;
27 pub const shrOverflow = no_panic.shrOverflow;
28 pub const divideByZero = no_panic.divideByZero;
29 pub const exactDivisionRemainder = no_panic.exactDivisionRemainder;
30 pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds;
31 pub const corruptSwitch = no_panic.corruptSwitch;
32 pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
33 pub const invalidEnumValue = no_panic.invalidEnumValue;
34 pub const forLenMismatch = no_panic.forLenMismatch;
35 pub const copyLenMismatch = no_panic.copyLenMismatch;
36 pub const memcpyAlias = no_panic.memcpyAlias;
37 pub const noreturnReturned = no_panic.noreturnReturned;
38 pub const loadUninstantiableType = no_panic.loadUninstantiableType;
39};
40fn myPanic(msg: []const u8, _: ?usize) noreturn {
41 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
42 stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {};
43 std.process.exit(0);
44}
45const std = @import("std");
46const io = std.Io.Threaded.global_single_threaded.io();
test/incremental/change_panic_handler_explicit/2/main.zig created+46
......@@ -0,0 +1,46 @@
1pub fn main() !u8 {
2 var a: u8 = undefined;
3 a = 255;
4 _ = a + 1;
5 return 1;
6}
7const no_panic = std.debug.no_panic;
8pub const panic = struct {
9 pub const call = myPanicNew;
10 pub fn integerOverflow() noreturn {
11 @panic("integer overflow");
12 }
13 pub const sentinelMismatch = no_panic.sentinelMismatch;
14 pub const unwrapError = no_panic.unwrapError;
15 pub const outOfBounds = no_panic.outOfBounds;
16 pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;
17 pub const inactiveUnionField = no_panic.inactiveUnionField;
18 pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder;
19 pub const reachedUnreachable = no_panic.reachedUnreachable;
20 pub const unwrapNull = no_panic.unwrapNull;
21 pub const castToNull = no_panic.castToNull;
22 pub const incorrectAlignment = no_panic.incorrectAlignment;
23 pub const invalidErrorCode = no_panic.invalidErrorCode;
24 pub const unexpectedErrorCode = no_panic.unexpectedErrorCode;
25 pub const integerOutOfBounds = no_panic.integerOutOfBounds;
26 pub const shlOverflow = no_panic.shlOverflow;
27 pub const shrOverflow = no_panic.shrOverflow;
28 pub const divideByZero = no_panic.divideByZero;
29 pub const exactDivisionRemainder = no_panic.exactDivisionRemainder;
30 pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds;
31 pub const corruptSwitch = no_panic.corruptSwitch;
32 pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
33 pub const invalidEnumValue = no_panic.invalidEnumValue;
34 pub const forLenMismatch = no_panic.forLenMismatch;
35 pub const copyLenMismatch = no_panic.copyLenMismatch;
36 pub const memcpyAlias = no_panic.memcpyAlias;
37 pub const noreturnReturned = no_panic.noreturnReturned;
38 pub const loadUninstantiableType = no_panic.loadUninstantiableType;
39};
40fn myPanicNew(msg: []const u8, _: ?usize) noreturn {
41 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
42 stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {};
43 std.process.exit(0);
44}
45const std = @import("std");
46const io = std.Io.Threaded.global_single_threaded.io();
test/incremental/change_panic_handler_explicit/manifest created+23
......@@ -0,0 +1,23 @@
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig 0/main.zig
6#update
7#check stdout {
8panic message: integer overflow
9#}
10
11#write main.zig 1/main.zig
12#update {
13change the panic handler body#}
14#check stdout {
15new panic message: integer overflow
16#}
17
18#write main.zig 2/main.zig
19#update {
20change the panic handler function value#}
21#check stdout {
22third panic message: integer overflow
23#}
test/incremental/change_reify_struct_field_type+16-6
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36fn getEnum(s: @Struct(.auto, null, &.{"field"}, &.{struct { tag: Enum }}, &.{.{}})) Enum {
47 return s.field.tag;
58}
......@@ -8,9 +11,12 @@ pub fn main(init: std.process.Init) !void {
811}
912const Enum = enum { foo, bar };
1013const std = @import("std");
11#expect_stdout="foo"
12#update=change field type
13#file=main.zig
14#}
15#update
16#check stdout {
17foo#}
18
19#write main.zig {
1420fn getEnum(s: @Struct(.auto, null, &.{"field"}, &.{Enum}, &.{.{}})) Enum {
1521 return s.field;
1622}
......@@ -19,4 +25,8 @@ pub fn main(init: std.process.Init) !void {
1925}
2026const Enum = enum { foo, bar };
2127const std = @import("std");
22#expect_stdout="bar"
28#}
29#update {
30change field type#}
31#check stdout {
32bar#}
test/incremental/change_shift_op+18-6
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36pub fn main() !void {
47 try foo(0x1300);
58}
......@@ -9,9 +12,13 @@ fn foo(x: u16) !void {
912}
1013const std = @import("std");
1114const io = std.Io.Threaded.global_single_threaded.io();
12#expect_stdout="0x3000\n"
13#update=change to right shift
14#file=main.zig
15#}
16#update
17#check stdout {
180x3000
19#}
20
21#write main.zig {
1522pub fn main() !void {
1623 try foo(0x1300);
1724}
......@@ -21,4 +28,9 @@ fn foo(x: u16) !void {
2128}
2229const std = @import("std");
2330const io = std.Io.Threaded.global_single_threaded.io();
24#expect_stdout="0x130\n"
31#}
32#update {
33change to right shift#}
34#check stdout {
350x130
36#}
test/incremental/change_struct_same_fields+24-9
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const S = extern struct { x: u8, y: u8 };
47pub fn main() !void {
58 const val: S = .{ .x = 100, .y = 200 };
......@@ -14,10 +17,13 @@ fn foo(val: *const S) !void {
1417}
1518const std = @import("std");
1619const io = std.Io.Threaded.global_single_threaded.io();
17#expect_stdout="100 200\n"
20#}
21#update
22#check stdout {
23100 200
24#}
1825
19#update=change struct layout
20#file=main.zig
26#write main.zig {
2127const S = extern struct { x: u32, y: u32 };
2228pub fn main() !void {
2329 const val: S = .{ .x = 100, .y = 200 };
......@@ -32,10 +38,14 @@ fn foo(val: *const S) !void {
3238}
3339const std = @import("std");
3440const io = std.Io.Threaded.global_single_threaded.io();
35#expect_stdout="100 200\n"
41#}
42#update {
43change struct layout#}
44#check stdout {
45100 200
46#}
3647
37#update=change values
38#file=main.zig
48#write main.zig {
3949const S = extern struct { x: u32, y: u32 };
4050pub fn main() !void {
4151 const val: S = .{ .x = 1234, .y = 5678 };
......@@ -50,4 +60,9 @@ fn foo(val: *const S) !void {
5060}
5161const std = @import("std");
5262const io = std.Io.Threaded.global_single_threaded.io();
53#expect_stdout="1234 5678\n"
63#}
64#update {
65change values#}
66#check stdout {
671234 5678
68#}
test/incremental/change_union_tag_type+25-10
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const A = enum(u8) { a };
47const B = enum(u8) { b };
58const Foo = union(A) { a: u8 };
......@@ -9,10 +12,13 @@ pub fn main(init: std.process.Init) !void {
912 try stdout_writer.interface.print("{s}\n", .{field_name});
1013}
1114const std = @import("std");
12#expect_stdout="a\n"
15#}
16#update
17#check stdout {
18a
19#}
1320
14#update=change tag type
15#file=main.zig
21#write main.zig {
1622const A = enum(u8) { a };
1723const B = enum(u8) { b };
1824const Foo = union(B) { a: u8 };
......@@ -22,11 +28,15 @@ pub fn main(init: std.process.Init) !void {
2228 try stdout_writer.interface.print("{s}\n", .{field_name});
2329}
2430const std = @import("std");
25#expect_error=main.zig:3:24: error: no field named 'a' in enum 'main.B'
26#expect_error=main.zig:2:11: note: enum declared here
31#}
32#update {
33change tag type#}
34#check errors {
35main.zig:3:24: error: no field named 'a' in enum 'main.B'
36main.zig:2:11: note: enum declared here
37#}
2738
28#update=change field name to match new tag type
29#file=main.zig
39#write main.zig {
3040const A = enum(u8) { a };
3141const B = enum(u8) { b };
3242const Foo = union(B) { b: u8 };
......@@ -36,4 +46,9 @@ pub fn main(init: std.process.Init) !void {
3646 try stdout_writer.interface.print("{s}\n", .{field_name});
3747}
3848const std = @import("std");
39#expect_stdout="b\n"
49#}
50#update {
51change field name to match new tag type#}
52#check stdout {
53b
54#}
test/incremental/change_zon_file+48-21
......@@ -1,47 +1,74 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const std = @import("std");
47const message: []const u8 = @import("message.zon");
58pub fn main() !void {
69 try std.Io.File.stdout().writeStreamingAll(io, message);
710}
811const io = std.Io.Threaded.global_single_threaded.io();
9#file=message.zon
12#}
13#write message.zon {
1014"Hello, World!\n"
11#expect_stdout="Hello, World!\n"
15#}
16#update
17#check stdout {
18Hello, World!
19#}
1220
13#update=change ZON file contents
14#file=message.zon
21#write message.zon {
1522"Hello again, World!\n"
16#expect_stdout="Hello again, World!\n"
23#}
24#update {
25change ZON file contents#}
26#check stdout {
27Hello again, World!
28#}
1729
18#update=delete file
19#rm_file=message.zon
20#expect_error=message.zon:1:1: error: unable to load "message.zon": FileNotFound
21#expect_error=main.zig:2:37: note: file imported here
30#delete message.zon
31#update {
32delete file#}
33#check errors {
34message.zon:1:1: error: unable to load "message.zon": FileNotFound
35main.zig:2:37: note: file imported here
36#}
2237
23#update=remove reference to ZON file
24#file=main.zig
38#write main.zig {
2539const std = @import("std");
2640const message: []const u8 = @import("message.zon");
2741pub fn main() !void {
2842 try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n");
2943}
3044const io = std.Io.Threaded.global_single_threaded.io();
31#expect_error=message.zon:1:1: error: unable to load "message.zon": FileNotFound
32#expect_error=main.zig:2:37: note: file imported here
45#}
46#update {
47remove reference to ZON file#}
48#check errors {
49message.zon:1:1: error: unable to load "message.zon": FileNotFound
50main.zig:2:37: note: file imported here
51#}
3352
34#update=recreate ZON file
35#file=message.zon
53#write message.zon {
3654"We're back, World!\n"
37#expect_stdout="a hardcoded string\n"
55#}
56#update {
57recreate ZON file#}
58#check stdout {
59a hardcoded string
60#}
3861
39#update=re-introduce reference to ZON file
40#file=main.zig
62#write main.zig {
4163const std = @import("std");
4264const message: []const u8 = @import("message.zon");
4365pub fn main() !void {
4466 try std.Io.File.stdout().writeStreamingAll(io, message);
4567}
4668const io = std.Io.Threaded.global_single_threaded.io();
47#expect_stdout="We're back, World!\n"
69#}
70#update {
71re-introduce reference to ZON file#}
72#check stdout {
73We're back, World!
74#}
test/incremental/change_zon_file_no_result_type+19-7
......@@ -1,21 +1,33 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const std = @import("std");
47const io = std.Io.Threaded.global_single_threaded.io();
58pub fn main() !void {
69 try std.Io.File.stdout().writeStreamingAll(io, @import("foo.zon").message);
710}
8#file=foo.zon
11#}
12#write foo.zon {
913.{
1014 .message = "Hello, World!\n",
1115 .a_number = 0,
1216}
13#expect_stdout="Hello, World!\n"
17#}
18#update
19#check stdout {
20Hello, World!
21#}
1422
15#update=change ZON file
16#file=foo.zon
23#write foo.zon {
1724.{
1825 .message = "Hello again, World!\n",
1926 .b_number = 0,
2027}
21#expect_stdout="Hello again, World!\n"
28#}
29#update {
30change ZON file#}
31#check stdout {
32Hello again, World!
33#}
test/incremental/compile_error_then_log+24-8
......@@ -1,5 +1,8 @@
1#update=initial version with compile error
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36pub fn main() void {}
47comptime {
58 @compileError("this is an error");
......@@ -7,11 +10,17 @@ comptime {
710comptime {
811 @compileLog("this is a log");
912}
10#expect_error=main.zig:3:5: error: this is an error
11#expect_compile_log=@as(*const [13:0]u8, "this is a log")
13#}
14#update {
15compile error#}
16#check errors {
17main.zig:3:5: error: this is an error
18
19Compile Log Output:
20@as(*const [13:0]u8, "this is a log")
21#}
1222
13#update=remove the compile error
14#file=main.zig
23#write main.zig {
1524pub fn main() void {}
1625comptime {
1726 //@compileError("this is an error");
......@@ -19,5 +28,12 @@ comptime {
1928comptime {
2029 @compileLog("this is a log");
2130}
22#expect_error=main.zig:6:5: error: found compile log statement
23#expect_compile_log=@as(*const [13:0]u8, "this is a log")
31#}
32#update {
33remove the compile error#}
34#check errors {
35main.zig:6:5: error: found compile log statement
36
37Compile Log Output:
38@as(*const [13:0]u8, "this is a log")
39#}
test/incremental/compile_log+28-10
......@@ -1,28 +1,46 @@
1#update=initial version with no compile log
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const std = @import("std");
47pub fn main() !void {
58 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
69}
710const io = std.Io.Threaded.global_single_threaded.io();
8#expect_stdout="Hello, World!\n"
11#}
12#update {
13no compile log#}
14#check stdout {
15Hello, World!
16#}
917
10#update=add compile log
11#file=main.zig
18#write main.zig {
1219const std = @import("std");
1320pub fn main() !void {
1421 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
1522 @compileLog("this is a log");
1623}
1724const io = std.Io.Threaded.global_single_threaded.io();
18#expect_error=main.zig:4:5: error: found compile log statement
19#expect_compile_log=@as(*const [13:0]u8, "this is a log")
25#}
26#update {
27add compile log#}
28#check errors {
29main.zig:4:5: error: found compile log statement
30
31Compile Log Output:
32@as(*const [13:0]u8, "this is a log")
33#}
2034
21#update=remove compile log
22#file=main.zig
35#write main.zig {
2336const std = @import("std");
2437pub fn main() !void {
2538 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
2639}
2740const io = std.Io.Threaded.global_single_threaded.io();
28#expect_stdout="Hello, World!\n"
41#}
42#update {
43remove compile log#}
44#check stdout {
45Hello, World!
46#}
test/incremental/delete_comptime_decls+20-9
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36pub fn main() void {}
47comptime {
58 var array = [_:0]u8{ 1, 2, 3, 4 };
......@@ -22,13 +25,16 @@ comptime {
2225 const slice = array[3..2];
2326 _ = slice;
2427}
25#expect_error=main.zig:5:32: error: end index 6 out of bounds for slice of length 4 +1 (sentinel)
26#expect_error=main.zig:10:28: error: end index 6 out of bounds for array of length 4 +1 (sentinel)
27#expect_error=main.zig:15:28: error: end index 5 out of bounds for array of length 4
28#expect_error=main.zig:20:25: error: start index 3 is larger than end index 2
28#}
29#update
30#check errors {
31main.zig:5:32: error: end index 6 out of bounds for slice of length 4 +1 (sentinel)
32main.zig:10:28: error: end index 6 out of bounds for array of length 4 +1 (sentinel)
33main.zig:15:28: error: end index 5 out of bounds for array of length 4
34main.zig:20:25: error: start index 3 is larger than end index 2
35#}
2936
30#update=delete and modify comptime decls
31#file=main.zig
37#write main.zig {
3238pub fn main() void {}
3339comptime {
3440 const x: [*c]u8 = null;
......@@ -37,4 +43,9 @@ comptime {
3743 const y = x[0..runtime_len];
3844 _ = y;
3945}
40#expect_error=main.zig:6:16: error: slice of null pointer
46#}
47#update {
48delete and modify comptime decls#}
49#check errors {
50main.zig:6:16: error: slice of null pointer
51#}
test/incremental/dependency_on_type_of_inferred_global+17-6
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const foo = @as(u8, 123);
47comptime {
58 // depends on value of `foo`
......@@ -10,9 +13,12 @@ comptime {
1013 if (@TypeOf(&foo) != *const u8) unreachable;
1114}
1215pub fn main() void {}
13#expect_stdout=""
14#update=change the type
15#file=main.zig
16#}
17#update
18#check stdout {
19#}
20
21#write main.zig {
1622const foo = @as(u16, 123);
1723comptime {
1824 // depends on value of `foo`
......@@ -23,4 +29,9 @@ comptime {
2329 if (@TypeOf(&foo) != *const u8) unreachable;
2430}
2531pub fn main() void {}
26#expect_error=main.zig:8:37: error: reached unreachable code
32#}
33#update {
34change the type#}
35#check errors {
36main.zig:8:37: error: reached unreachable code
37#}
test/incremental/do_nothing+19-7
......@@ -1,6 +1,11 @@
1// TODO: it'd be great if we could actually check that no analysis happened!
2#update=initial version
3#file=main.zig
1#todo {
2it'd be great if we could actually check that no analysis happened!
3#}
4#exe {
5-Mroot=main.zig
6#}
7
8#write main.zig {
49pub fn main() void {
510 const ptr: *const O = @ptrFromInt(0x1000);
611 _ = ptr;
......@@ -18,9 +23,12 @@ const O = opaque {
1823 _ = wrapper;
1924 }
2025};
21#expect_stdout=""
22#update=do literally nothing
23#file=main.zig
26#}
27#update
28#check stdout {
29#}
30
31#write main.zig {
2432pub fn main() void {
2533 const ptr: *const O = @ptrFromInt(0x1000);
2634 _ = ptr;
......@@ -38,4 +46,8 @@ const O = opaque {
3846 _ = wrapper;
3947 }
4048};
41#expect_stdout=""
49#}
50#update {
51do literally nothing#}
52#check stdout {
53#}
test/incremental/fix_astgen_failure+37-13
......@@ -1,35 +1,59 @@
1#update=initial version with error
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36pub fn main() !void {
47 try @import("foo.zig").hello();
58}
6#file=foo.zig
9#}
10#write foo.zig {
711pub fn hello() !void {
812 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
913}
10#expect_error=foo.zig:2:9: error: use of undeclared identifier 'std'
11#update=fix the error
12#file=foo.zig
14#}
15#update {
16error#}
17#check errors {
18foo.zig:2:9: error: use of undeclared identifier 'std'
19#}
20
21#write foo.zig {
1322const std = @import("std");
1423pub fn hello() !void {
1524 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
1625}
1726const io = std.Io.Threaded.global_single_threaded.io();
18#expect_stdout="Hello, World!\n"
19#update=add new error
20#file=foo.zig
27#}
28#update {
29fix the error#}
30#check stdout {
31Hello, World!
32#}
33
34#write foo.zig {
2135const std = @import("std");
2236pub fn hello() !void {
2337 try std.Io.File.stdout().writeStreamingAll(io, hello_str);
2438}
2539const io = std.Io.Threaded.global_single_threaded.io();
26#expect_error=foo.zig:3:52: error: use of undeclared identifier 'hello_str'
27#update=fix the new error
28#file=foo.zig
40#}
41#update {
42add new error#}
43#check errors {
44foo.zig:3:52: error: use of undeclared identifier 'hello_str'
45#}
46
47#write foo.zig {
2948const std = @import("std");
3049const hello_str = "Hello, World! Again!\n";
3150pub fn hello() !void {
3251 try std.Io.File.stdout().writeStreamingAll(io, hello_str);
3352}
3453const io = std.Io.Threaded.global_single_threaded.io();
35#expect_stdout="Hello, World! Again!\n"
54#}
55#update {
56fix the new error#}
57#check stdout {
58Hello, World! Again!
59#}
test/incremental/function_becomes_inline+25-9
......@@ -1,5 +1,8 @@
1#update=non-inline version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36pub fn main() !void {
47 try foo();
58}
......@@ -8,10 +11,14 @@ fn foo() !void {
811}
912const std = @import("std");
1013const io = std.Io.Threaded.global_single_threaded.io();
11#expect_stdout="Hello, World!\n"
14#}
15#update {
16non-inline version#}
17#check stdout {
18Hello, World!
19#}
1220
13#update=make function inline
14#file=main.zig
21#write main.zig {
1522pub fn main() !void {
1623 try foo();
1724}
......@@ -20,10 +27,14 @@ inline fn foo() !void {
2027}
2128const std = @import("std");
2229const io = std.Io.Threaded.global_single_threaded.io();
23#expect_stdout="Hello, World!\n"
30#}
31#update {
32make function inline#}
33#check stdout {
34Hello, World!
35#}
2436
25#update=change string
26#file=main.zig
37#write main.zig {
2738pub fn main() !void {
2839 try foo();
2940}
......@@ -32,4 +43,9 @@ inline fn foo() !void {
3243}
3344const std = @import("std");
3445const io = std.Io.Threaded.global_single_threaded.io();
35#expect_stdout="Hello, `inline` World!\n"
46#}
47#update {
48change string#}
49#check stdout {
50Hello, `inline` World!
51#}
test/incremental/hello+18-6
......@@ -1,16 +1,28 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const std = @import("std");
47const io = std.Io.Threaded.global_single_threaded.io();
58pub fn main() !void {
69 try std.Io.File.stdout().writeStreamingAll(io, "good morning\n");
710}
8#expect_stdout="good morning\n"
9#update=change the string
10#file=main.zig
11#}
12#update
13#check stdout {
14good morning
15#}
16
17#write main.zig {
1118const std = @import("std");
1219const io = std.Io.Threaded.global_single_threaded.io();
1320pub fn main() !void {
1421 try std.Io.File.stdout().writeStreamingAll(io, "おはようございます\n");
1522}
16#expect_stdout="おはようございます\n"
23#}
24#update {
25change the string#}
26#check stdout {
27おはようございます
28#}
test/incremental/increase_array_field_length+17-6
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const Big = struct { arr: [10]u8 };
47pub fn main(init: std.process.Init) !void {
58 var big: Big = undefined;
......@@ -12,10 +15,13 @@ pub fn main(init: std.process.Init) !void {
1215 };
1316}
1417const std = @import("std");
15#expect_stdout="arr[9] = 234\n"
18#}
19#update
20#check stdout {
21arr[9] = 234
22#}
1623
17#update=increase array length
18#file=main.zig
24#write main.zig {
1925const Big = struct { arr: [1000]u8 };
2026pub fn main(init: std.process.Init) !void {
2127 var big: Big = undefined;
......@@ -28,4 +34,9 @@ pub fn main(init: std.process.Init) !void {
2834 };
2935}
3036const std = @import("std");
31#expect_stdout="arr[999] = 234\n"
37#}
38#update {
39increase array length#}
40#check stdout {
41arr[999] = 234
42#}
test/incremental/make_decl_pub+20-8
......@@ -1,23 +1,35 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const foo = @import("foo.zig");
47pub fn main() !void {
58 try foo.hello();
69}
7#file=foo.zig
10#}
11#write foo.zig {
812const std = @import("std");
913fn hello() !void {
1014 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
1115}
1216const io = std.Io.Threaded.global_single_threaded.io();
13#expect_error=main.zig:3:12: error: 'hello' is not marked 'pub'
14#expect_error=foo.zig:2:1: note: declared here
17#}
18#update
19#check errors {
20main.zig:3:12: error: 'hello' is not marked 'pub'
21foo.zig:2:1: note: declared here
22#}
1523
16#update=make hello pub
17#file=foo.zig
24#write foo.zig {
1825const std = @import("std");
1926pub fn hello() !void {
2027 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
2128}
2229const io = std.Io.Threaded.global_single_threaded.io();
23#expect_stdout="Hello, World!\n"
30#}
31#update {
32make hello pub#}
33#check stdout {
34Hello, World!
35#}
test/incremental/modify_inline_fn+18-6
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const std = @import("std");
47pub fn main() !void {
58 const str = getStr();
......@@ -9,9 +12,13 @@ inline fn getStr() []const u8 {
912 return "foo\n";
1013}
1114const io = std.Io.Threaded.global_single_threaded.io();
12#expect_stdout="foo\n"
13#update=change the string
14#file=main.zig
15#}
16#update
17#check stdout {
18foo
19#}
20
21#write main.zig {
1522const std = @import("std");
1623pub fn main() !void {
1724 const str = getStr();
......@@ -21,4 +28,9 @@ inline fn getStr() []const u8 {
2128 return "bar\n";
2229}
2330const io = std.Io.Threaded.global_single_threaded.io();
24#expect_stdout="bar\n"
31#}
32#update {
33change the string#}
34#check stdout {
35bar
36#}
test/incremental/move_src+17-6
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const std = @import("std");
47pub fn main() !void {
58 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
......@@ -12,10 +15,13 @@ fn bar() u32 {
1215 return 123;
1316}
1417const io = std.Io.Threaded.global_single_threaded.io();
15#expect_stdout="7 123\n"
18#}
19#update
20#check stdout {
217 123
22#}
1623
17#update=add newline
18#file=main.zig
24#write main.zig {
1925const std = @import("std");
2026pub fn main() !void {
2127 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
......@@ -29,4 +35,9 @@ fn bar() u32 {
2935 return 123;
3036}
3137const io = std.Io.Threaded.global_single_threaded.io();
32#expect_stdout="8 123\n"
38#}
39#update {
40add newline#}
41#check stdout {
428 123
43#}
test/incremental/pointer_to_decl_with_astgen_error+22-9
......@@ -1,23 +1,36 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const foo = {};
47pub fn main() void {
58 _ = &foo;
69}
7#expect_stdout=""
10#}
11#update
12#check stdout {
13#}
814
9#update=introduce use of undeclared identifier
10#file=main.zig
15#write main.zig {
1116const foo = something;
1217pub fn main() void {
1318 _ = &foo;
1419}
15#expect_error=main.zig:1:13: error: use of undeclared identifier 'something'
20#}
21#update {
22introduce use of undeclared identifier#}
23#check errors {
24main.zig:1:13: error: use of undeclared identifier 'something'
25#}
1626
17#update=revert use of undeclared identifier
18#file=main.zig
27#write main.zig {
1928const foo = {};
2029pub fn main() void {
2130 _ = &foo;
2231}
23#expect_stdout=""
32#}
33#update {
34revert use of undeclared identifier#}
35#check stdout {
36#}
test/incremental/recursive_function_becomes_non_recursive+17-6
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36pub fn main() !void {
47 try foo(false);
58}
......@@ -10,10 +13,13 @@ fn foo(recurse: bool) !void {
1013}
1114const std = @import("std");
1215const io = std.Io.Threaded.global_single_threaded.io();
13#expect_stdout="non-recursive path\n"
16#}
17#update
18#check stdout {
19non-recursive path
20#}
1421
15#update=eliminate recursion and change argument
16#file=main.zig
22#write main.zig {
1723pub fn main() !void {
1824 try foo(true);
1925}
......@@ -24,4 +30,9 @@ fn foo(recurse: bool) !void {
2430}
2531const std = @import("std");
2632const io = std.Io.Threaded.global_single_threaded.io();
27#expect_stdout="x==1\n"
33#}
34#update {
35eliminate recursion and change argument#}
36#check stdout {
37x==1
38#}
test/incremental/remove_enum_field+21-9
......@@ -1,27 +1,39 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const MyEnum = enum(u8) {
47 foo = 1,
58 bar = 2,
69};
710pub fn main() !void {
811 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
9 try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)});
12 try stdout_writer.interface.print("{}\n", .{@backingInt(MyEnum.foo)});
1013}
1114const std = @import("std");
1215const io = std.Io.Threaded.global_single_threaded.io();
13#expect_stdout="1\n"
14#update=remove enum field
15#file=main.zig
16#}
17#update
18#check stdout {
191
20#}
21
22#write main.zig {
1623const MyEnum = enum(u8) {
1724 //foo = 1,
1825 bar = 2,
1926};
2027pub fn main() !void {
2128 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
22 try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)});
29 try stdout_writer.interface.print("{}\n", .{@backingInt(MyEnum.foo)});
2330}
2431const std = @import("std");
2532const io = std.Io.Threaded.global_single_threaded.io();
26#expect_error=main.zig:7:69: error: enum 'main.MyEnum' has no member named 'foo'
27#expect_error=main.zig:1:16: note: enum declared here
33#}
34#update {
35remove enum field#}
36#check errors {
37main.zig:7:68: error: enum 'main.MyEnum' has no member named 'foo'
38main.zig:1:16: note: enum declared here
39#}
test/incremental/remove_function_with_changed_callee+16-6
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const std = @import("std");
47const io = std.Io.Threaded.global_single_threaded.io();
58
......@@ -23,9 +26,12 @@ fn bar(a: A) !void {
2326pub fn main() !void {
2427 try foo();
2528}
26#expect_stdout="0"
27#update=remove foo and change bar wasm function type
28#file=main.zig
29#}
30#update
31#check stdout {
320#}
33
34#write main.zig {
2935//! This update must preserve `bar` `InternPool.Index` value, while changing its wasm function type.
3036//! If `foo` code is preserved in the binary without any changes, wasm type checking will fail.
3137const std = @import("std");
......@@ -47,4 +53,8 @@ fn bar(a: A) !void {
4753pub fn main() !void {
4854 try bar(.one);
4955}
50#expect_stdout="1"
56#}
57#update {
58remove foo and change bar wasm function type#}
59#check stdout {
601#}
test/incremental/remove_invalid_union_backing_enum+18-7
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const E = enum { a, b, c };
47const U = union(E) {
58 a: i32,
......@@ -11,10 +14,14 @@ pub fn main() void {
1114 const u: U = .{ .a = 123 };
1215 _ = u;
1316}
14#expect_error=main.zig:6:5: error: no field named 'd' in enum 'main.E'
15#expect_error=main.zig:1:11: note: enum declared here
16#update=remove invalid backing enum
17#file=main.zig
17#}
18#update
19#check errors {
20main.zig:6:5: error: no field named 'd' in enum 'main.E'
21main.zig:1:11: note: enum declared here
22#}
23
24#write main.zig {
1825const U = union {
1926 a: i32,
2027 b: f64,
......@@ -25,4 +32,8 @@ pub fn main() void {
2532 const u: U = .{ .a = 123 };
2633 _ = u;
2734}
28#expect_stdout=""
35#}
36#update {
37remove invalid backing enum#}
38#check stdout {
39#}
test/incremental/struct_reification_becomes_anon_init+17-6
......@@ -1,19 +1,30 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const Foo = @Struct(.auto, null, &.{"reified_field"}, &.{void}, &.{.{}});
47pub fn main(init: std.process.Init) !void {
58 const field_name = @typeInfo(Foo).@"struct".field_names[0];
69 try std.Io.File.stdout().writeStreamingAll(init.io, field_name ++ "\n");
710}
811const std = @import("std");
9#expect_stdout="reified_field\n"
12#}
13#update
14#check stdout {
15reified_field
16#}
1017
11#update=change to `@TypeOf` anonymous init
12#file=main.zig
18#write main.zig {
1319const Foo = @TypeOf(.{ .anon_field = 123 });
1420pub fn main(init: std.process.Init) !void {
1521 const field_name = @typeInfo(Foo).@"struct".field_names[0];
1622 try std.Io.File.stdout().writeStreamingAll(init.io, field_name ++ "\n");
1723}
1824const std = @import("std");
19#expect_stdout="anon_field\n"
25#}
26#update {
27change to `@TypeOf` anonymous init#}
28#check stdout {
29anon_field
30#}
test/incremental/tag_name+23-9
......@@ -1,27 +1,41 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const std = @import("std");
47var some_enum: enum { first, second } = .first;
58const io = std.Io.Threaded.global_single_threaded.io();
69pub fn main() !void {
710 try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum));
811}
9#expect_stdout="first"
10#update=no change
11#file=main.zig
12#}
13#update
14#check stdout {
15first#}
16
17#write main.zig {
1218const std = @import("std");
1319var some_enum: enum { first, second } = .first;
1420const io = std.Io.Threaded.global_single_threaded.io();
1521pub fn main() !void {
1622 try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum));
1723}
18#expect_stdout="first"
19#update=swap fields
20#file=main.zig
24#}
25#update {
26no change#}
27#check stdout {
28first#}
29
30#write main.zig {
2131const std = @import("std");
2232var some_enum: enum { second, first } = .first;
2333const io = std.Io.Threaded.global_single_threaded.io();
2434pub fn main() !void {
2535 try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum));
2636}
27#expect_stdout="first"
37#}
38#update {
39swap fields#}
40#check stdout {
41first#}
test/incremental/temporary_analysis_error_in_generic_signature+25-10
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36//! The original repro here depends on re-analysis order, which depends on
47//! declaration order, so this exact declaration order must be used.
58const Foo = struct { x: u8 };
......@@ -11,10 +14,13 @@ fn bar(comptime x: u8) @This().Foo {
1114 return .{ .x = x };
1215}
1316const std = @import("std");
14#expect_stdout="Z\n"
17#}
18#update
19#check stdout {
20Z
21#}
1522
16#update=change generic signature to use non-existent member
17#file=main.zig
23#write main.zig {
1824//! The original repro here depends on re-analysis order, which depends on
1925//! declaration order, so this exact declaration order must be used.
2026const Foo = struct { x: u8 };
......@@ -26,11 +32,15 @@ fn bar(comptime x: u8) @This().FooAlias {
2632 return .{ .x = x };
2733}
2834const std = @import("std");
29#expect_error=main.zig:8:31: error: root source file struct 'main' has no member named 'FooAlias'
30#expect_error=main.zig:1:1: note: struct declared here
35#}
36#update {
37change generic signature to use non-existent member#}
38#check errors {
39main.zig:8:31: error: root source file struct 'main' has no member named 'FooAlias'
40main.zig:1:1: note: struct declared here
41#}
3142
32#update=add that member, fixing the error
33#file=main.zig
43#write main.zig {
3444//! The original repro here depends on re-analysis order, which depends on
3545//! declaration order, so this exact declaration order must be used.
3646const Foo = struct { x: u8 };
......@@ -43,4 +53,9 @@ fn bar(comptime x: u8) @This().FooAlias {
4353 return .{ .x = x };
4454}
4555const std = @import("std");
46#expect_stdout="Z\n"
56#}
57#update {
58add that member, fixing the error#}
59#check stdout {
60Z
61#}
test/incremental/temporary_parse_error+22-9
......@@ -1,15 +1,28 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const std = @import("std");
47pub fn main() !void {}
5#expect_stdout=""
8#}
9#update
10#check stdout {
11#}
612
7#update=introduce parse error
8#file=main.zig
13#write main.zig {
914pub fn main() !void {
10#expect_error=main.zig:2:1: error: expected statement, found 'EOF'
15#}
16#update {
17introduce parse error#}
18#check errors {
19main.zig:2:1: error: expected statement, found 'EOF'
20#}
1121
12#update=fix parse error
13#file=main.zig
22#write main.zig {
1423pub fn main() !void {}
15#expect_stdout=""
24#}
25#update {
26fix parse error#}
27#check stdout {
28#}
test/incremental/type_becomes_comptime_only+21-9
......@@ -1,5 +1,8 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const SomeType = u32;
47const S = struct {
58 x: SomeType,
......@@ -9,10 +12,12 @@ pub fn main() void {
912 const s: S = .{ .x = 456 };
1013 s.foo();
1114}
12#expect_stdout=""
15#}
16#update
17#check stdout {
18#}
1319
14#update=make S comptime-only
15#file=main.zig
20#write main.zig {
1621const SomeType = comptime_int;
1722const S = struct {
1823 x: SomeType,
......@@ -22,10 +27,13 @@ pub fn main() void {
2227 const s: S = .{ .x = 456 };
2328 s.foo();
2429}
25#expect_stdout=""
30#}
31#update {
32make S comptime-only#}
33#check stdout {
34#}
2635
27#update=make S runtime again
28#file=main.zig
36#write main.zig {
2937const SomeType = u16;
3038const S = struct {
3139 x: SomeType,
......@@ -35,4 +43,8 @@ pub fn main() void {
3543 const s: S = .{ .x = 456 };
3644 s.foo();
3745}
38#expect_stdout=""
46#}
47#update {
48make S runtime again#}
49#check stdout {
50#}
test/incremental/type_dependency_loop+41-21
......@@ -1,50 +1,70 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36pub const A = struct { b: B };
47pub const B = struct { a: A };
58pub fn main() void {
69 _ = @as(B, undefined);
710}
8#expect_error=:error: dependency loop with length 2
9#expect_error=main.zig:2:27: note: type 'main.B' depends on type 'main.A' for field declared here
10#expect_error=main.zig:1:27: note: type 'main.A' depends on type 'main.B' for field declared here
11#expect_error=:note: eliminate any one of these dependencies to break the loop
11#}
12#update
13#check errors {
14error: dependency loop with length 2
15 main.zig:2:27: note: type 'main.B' depends on type 'main.A' for field declared here
16 main.zig:1:27: note: type 'main.A' depends on type 'main.B' for field declared here
17 note: eliminate any one of these dependencies to break the loop
18#}
1219
13#update=remove reference to dependency loop
14#file=main.zig
20#write main.zig {
1521pub const A = struct { b: B };
1622pub const B = struct { a: A };
1723pub fn main() void {
1824 _ = B;
1925}
20#expect_stdout=""
26#}
27#update {
28remove reference to dependency loop#}
29#check stdout {
30#}
2131
22#update=change dependency loop without fixing it
23#file=main.zig
32#write main.zig {
2433pub const A = struct { b: B };
2534pub const B = struct { a: *align(@alignOf(A)) A };
2635pub fn main() void {
2736 _ = B;
2837}
29#expect_stdout=""
38#}
39#update {
40change dependency loop without fixing it#}
41#check stdout {
42#}
3043
31#update=reference dependency loop again
32#file=main.zig
44#write main.zig {
3345pub const A = struct { b: B };
3446pub const B = struct { a: *align(@alignOf(A)) A };
3547pub fn main() void {
3648 _ = @as(B, undefined);
3749}
38#expect_error=:error: dependency loop with length 2
39#expect_error=main.zig:2:43: note: type 'main.B' depends on type 'main.A' for alignment query here
40#expect_error=main.zig:1:27: note: type 'main.A' depends on type 'main.B' for field declared here
41#expect_error=:note: eliminate any one of these dependencies to break the loop
50#}
51#update {
52reference dependency loop again#}
53#check errors {
54error: dependency loop with length 2
55 main.zig:2:43: note: type 'main.B' depends on type 'main.A' for alignment query here
56 main.zig:1:27: note: type 'main.A' depends on type 'main.B' for field declared here
57 note: eliminate any one of these dependencies to break the loop
58#}
4259
43#update=fix dependency loop
44#file=main.zig
60#write main.zig {
4561pub const A = struct { b: B };
4662pub const B = struct { a: *A };
4763pub fn main() void {
4864 _ = @as(B, undefined);
4965}
50#expect_stdout=""
66#}
67#update {
68fix dependency loop#}
69#check stdout {
70#}
test/incremental/unreferenced_error+31-12
......@@ -1,25 +1,35 @@
1#update=initial version
2#file=main.zig
1#exe {
2-Mroot=main.zig
3#}
4
5#write main.zig {
36const std = @import("std");
47pub fn main() !void {
58 try std.Io.File.stdout().writeStreamingAll(io, a);
69}
710const a = "Hello, World!\n";
811const io = std.Io.Threaded.global_single_threaded.io();
9#expect_stdout="Hello, World!\n"
12#}
13#update
14#check stdout {
15Hello, World!
16#}
1017
11#update=introduce compile error
12#file=main.zig
18#write main.zig {
1319const std = @import("std");
1420pub fn main() !void {
1521 try std.Io.File.stdout().writeStreamingAll(io, a);
1622}
1723const a = @compileError("bad a");
1824const io = std.Io.Threaded.global_single_threaded.io();
19#expect_error=main.zig:5:11: error: bad a
25#}
26#update {
27introduce compile error#}
28#check errors {
29main.zig:5:11: error: bad a
30#}
2031
21#update=remove error reference
22#file=main.zig
32#write main.zig {
2333const std = @import("std");
2434pub fn main() !void {
2535 try std.Io.File.stdout().writeStreamingAll(io, b);
......@@ -27,10 +37,14 @@ pub fn main() !void {
2737const a = @compileError("bad a");
2838const b = "Hi there!\n";
2939const io = std.Io.Threaded.global_single_threaded.io();
30#expect_stdout="Hi there!\n"
40#}
41#update {
42remove error reference#}
43#check stdout {
44Hi there!
45#}
3146
32#update=introduce and remove reference to error
33#file=main.zig
47#write main.zig {
3448const std = @import("std");
3549pub fn main() !void {
3650 try std.Io.File.stdout().writeStreamingAll(io, a);
......@@ -38,4 +52,9 @@ pub fn main() !void {
3852const a = "Back to a\n";
3953const b = @compileError("bad b");
4054const io = std.Io.Threaded.global_single_threaded.io();
41#expect_stdout="Back to a\n"
55#}
56#update {
57introduce and remove reference to error#}
58#check stdout {
59Back to a
60#}
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+1858
......@@ -0,0 +1,1858 @@
1const assert = std.debug.assert;
2const std = @import("std");
3const Runner = @This();
4const zig_version_string = @import("builtin").zig_version_string;
5
6gpa: std.mem.Allocator,
7arena: *std.heap.ArenaAllocator,
8io: std.Io,
9prog_node: std.Progress.Node,
10args: Args,
11host: std.Target,
12server: std.zig.Server,
13eb_wip: std.zig.ErrorBundle.Wip,
14
15const Args = struct {
16 @"test": ?struct {
17 input_dir: std.zig.Server.Message.InputDir,
18 dir: std.Io.Dir,
19 },
20 manifest_file: ?std.Io.File,
21 zig_exe: ?std.Io.File,
22 lib_dir: ?std.Io.Dir,
23 src_dir: ?std.Io.Dir,
24 targets: std.ArrayList(u32),
25 target_bytes: std.ArrayList(u8),
26 libc_runtimes_dir: ?std.Io.Dir,
27 enable_darling: bool,
28 enable_qemu: bool,
29 enable_rosetta: bool,
30 enable_wasmtime: bool,
31 enable_wine: bool,
32 quiet: bool,
33
34 fn deinit(args: *Args, gpa: std.mem.Allocator) void {
35 args.targets.deinit(gpa);
36 args.target_bytes.deinit(gpa);
37 args.* = undefined;
38 }
39
40 fn addTarget(args: *Args, gpa: std.mem.Allocator, target: []const u8) std.mem.Allocator.Error!void {
41 try args.target_bytes.ensureUnusedCapacity(gpa, target.len + 1);
42 try args.targets.append(gpa, @intCast(args.target_bytes.items.len));
43 args.target_bytes.appendSliceAssumeCapacity(target);
44 args.target_bytes.appendAssumeCapacity(0);
45 }
46};
47
48fn deinit(runner: *Runner) void {
49 runner.prog_node.end();
50 runner.args.deinit(runner.gpa);
51 runner.eb_wip.deinit();
52 runner.* = undefined;
53}
54
55const FailError = error{AlreadyReported} || std.mem.Allocator.Error || std.Io.Cancelable;
56fn fail(runner: *Runner, comptime fmt: []const u8, args: anytype) FailError {
57 return runner.failString(try runner.eb_wip.printString(fmt, args));
58}
59fn failString(runner: *Runner, msg: std.zig.ErrorBundle.String) FailError {
60 try runner.eb_wip.addRootErrorMessage(.{
61 .msg = msg,
62 });
63 return error.AlreadyReported;
64}
65
66pub const ProtocolError = error{
67 ServerReadFailed,
68 ServerWriteFailed,
69 ServerEndOfStream,
70} || FailError;
71pub fn runServer(runner: *Runner) ProtocolError {
72 runner.server.serveStringMessage(.zig_version, zig_version_string) catch |err| switch (err) {
73 error.WriteFailed => return error.ServerWriteFailed,
74 };
75 while (true) {
76 _ = runner.arena.reset(.retain_capacity);
77 const arena = runner.arena.allocator();
78 const hdr = runner.server.receiveMessage() catch |err| switch (err) {
79 error.ReadFailed => return error.ServerReadFailed,
80 error.EndOfStream => return error.ServerEndOfStream,
81 };
82 switch (hdr.tag) {
83 else => |tag| return runner.fail("unsupported message: {t}", .{tag}),
84 .exit => std.process.exit(0),
85 .args => {
86 const args_body = try arena.alloc(u8, hdr.bytes_len);
87 runner.server.in.readSliceAll(args_body) catch unreachable;
88 const State = enum {
89 positional,
90 @"--zig",
91 @"--lib",
92 @"--src",
93 @"--target",
94 @"--libc-runtimes",
95 };
96 var state: State = .positional;
97 const state_expected: std.enums.EnumArray(State, enum {
98 path,
99 dir,
100 file,
101 string,
102 }) = .init(.{
103 .positional = .path,
104 .@"--zig" = .file,
105 .@"--lib" = .dir,
106 .@"--src" = .dir,
107 .@"--target" = .string,
108 .@"--libc-runtimes" = .dir,
109 });
110 var input_dir: std.zig.Server.Message.InputDir = .cwd;
111 var args_body_offset: usize = 0;
112 while (args_body.len - args_body_offset > 0) {
113 const arg: std.zig.Client.Message.Arg =
114 @fromBackingInt(args_body[args_body_offset]);
115 args_body_offset += 1;
116 arg: switch (arg) {
117 .string => {
118 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
119 const string = args_body[args_body_offset..end];
120 args_body_offset = end + 1;
121 switch (state) {
122 .positional => if (std.mem.eql(u8, string, "--target")) {
123 state = .@"--target";
124 continue;
125 } else if (std.mem.cutPrefix(u8, string, "--target=")) |target| {
126 try runner.args.addTarget(runner.gpa, target);
127 } else if (std.mem.eql(u8, string, "--libc-runtimes")) {
128 state = .@"--libc-runtimes";
129 continue;
130 } else if (std.mem.eql(u8, string, "-fdarling")) {
131 runner.args.enable_darling = true;
132 } else if (std.mem.eql(u8, string, "-fqemu")) {
133 runner.args.enable_qemu = true;
134 } else if (std.mem.eql(u8, string, "-frosetta")) {
135 runner.args.enable_rosetta = true;
136 } else if (std.mem.eql(u8, string, "-fwasmtime")) {
137 runner.args.enable_wasmtime = true;
138 } else if (std.mem.eql(u8, string, "-fwine")) {
139 runner.args.enable_wine = true;
140 } else if (std.mem.eql(u8, string, "--quiet")) {
141 runner.args.quiet = true;
142 },
143 .@"--target" => try runner.args.addTarget(runner.gpa, string),
144 else => return runner.fail(
145 "\"{t}\" expected {t}, got {t}",
146 .{ state, state_expected.get(state), arg },
147 ),
148 }
149 },
150 .prefix => {
151 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
152 const string = args_body[args_body_offset..end];
153 args_body_offset = end + 1;
154 assert(state == .positional);
155 state = if (std.mem.eql(u8, string, "--zig="))
156 .@"--zig"
157 else if (std.mem.eql(u8, string, "--lib="))
158 .@"--lib"
159 else if (std.mem.eql(u8, string, "--src="))
160 .@"--src"
161 else if (std.mem.eql(u8, string, "--libc-runtimes="))
162 .@"--libc-runtimes"
163 else
164 return runner.fail("unsupported arg prefix: {q}", .{string});
165 continue;
166 },
167 .suffix => {
168 const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?;
169 const string = args_body[args_body_offset..end];
170 args_body_offset = end + 1;
171 return runner.fail("unsupported arg suffix: {q}", .{string});
172 },
173 .input_dir => {
174 input_dir = @fromBackingInt(@backingInt(input_dir) + 1);
175 continue :arg .output_dir;
176 },
177 .output_dir => {
178 const dir_handle: *align(1) const std.Io.Dir.Handle = @ptrCast(
179 args_body[args_body_offset..][0..@sizeOf(std.Io.Dir.Handle)],
180 );
181 args_body_offset += @sizeOf(std.Io.Dir.Handle);
182 const dir = runner.io.vtable.inheritParentDir(
183 runner.io.userdata,
184 dir_handle.*,
185 ) catch |err| switch (err) {
186 error.Canceled => |e| return e,
187 else => |e| return runner.fail(
188 "unable to inherit parent dir: {t}",
189 .{e},
190 ),
191 };
192 switch (state) {
193 .positional => {
194 if (runner.args.@"test" != null or
195 runner.args.manifest_file != null) return runner.fail(
196 "{t} specified multiple times",
197 .{state},
198 );
199 runner.args.@"test" = .{ .input_dir = input_dir, .dir = dir };
200 const manifest_path = "manifest";
201 runner.args.manifest_file = dir.openFile(
202 runner.io,
203 manifest_path,
204 .{},
205 ) catch |err| switch (err) {
206 error.Canceled => |e| return e,
207 else => |e| return runner.fail(
208 "unable to open manifest file: {t}",
209 .{e},
210 ),
211 };
212 try runner.discoverInput(input_dir, manifest_path);
213 },
214 .@"--lib" => {
215 if (runner.args.lib_dir != null) return runner.fail(
216 "\"{t}\" specified multiple times",
217 .{state},
218 );
219 runner.args.lib_dir = dir;
220 },
221 .@"--src" => {
222 if (runner.args.src_dir != null) return runner.fail(
223 "\"{t}\" specified multiple times",
224 .{state},
225 );
226 runner.args.src_dir = dir;
227 },
228 .@"--libc-runtimes" => {
229 if (runner.args.libc_runtimes_dir != null) return runner.fail(
230 "\"{t}\" specified multiple times",
231 .{state},
232 );
233 runner.args.libc_runtimes_dir = dir;
234 },
235 else => return runner.fail(
236 "\"{t}\" expected {t}, got {t}",
237 .{ state, state_expected.get(state), arg },
238 ),
239 }
240 },
241 .input_file, .input_file_content, .output_file => {
242 const file_handle: *align(1) const std.Io.File.Handle = @ptrCast(
243 args_body[args_body_offset..][0..@sizeOf(std.Io.File.Handle)],
244 );
245 args_body_offset += @sizeOf(std.Io.File.Handle);
246 const file = runner.io.vtable.inheritParentFile(
247 runner.io.userdata,
248 file_handle.*,
249 .{ .nonblocking = false },
250 ) catch |err| switch (err) {
251 error.Canceled => |e| return e,
252 else => |e| return runner.fail(
253 "unable to inherit parent file: {t}",
254 .{e},
255 ),
256 };
257 switch (state) {
258 .positional => {
259 if (runner.args.@"test" != null or
260 runner.args.manifest_file != null) return runner.fail(
261 "{t} specified multiple times",
262 .{state},
263 );
264 runner.args.manifest_file = file;
265 },
266 .@"--zig" => {
267 if (runner.args.zig_exe != null) return runner.fail(
268 "\"{t}\" specified multiple times",
269 .{state},
270 );
271 runner.args.zig_exe = file;
272 },
273 else => return runner.fail(
274 "\"{t}\" expected {t}, got {t}",
275 .{ state, state_expected.get(state), arg },
276 ),
277 }
278 },
279 }
280 state = .positional;
281 }
282 },
283 .query_test_metadata => {
284 const expected_panic_msgs = try arena.alloc(u32, runner.args.targets.items.len);
285 @memset(expected_panic_msgs, 0);
286 runner.server.serveTestMetadata(.{
287 .names = runner.args.targets.items,
288 .expected_panic_msgs = expected_panic_msgs,
289 .string_bytes = runner.args.target_bytes.items,
290 }) catch |err| switch (err) {
291 error.WriteFailed => return error.ServerWriteFailed,
292 };
293 },
294 .run_test => {
295 const test_index = runner.server.receiveBody_u32() catch unreachable;
296 runner.server.serveBodylessMessage(.test_started) catch |err| switch (err) {
297 error.WriteFailed => return error.ServerWriteFailed,
298 };
299 runner.server.serveTestResults(.{
300 .index = test_index,
301 .flags = .{
302 .status = if (runner.testOne(test_index))
303 .pass
304 else |err| status: switch (err) {
305 error.Canceled,
306 error.OutOfMemory,
307 error.ServerWriteFailed,
308 => |e| return e,
309 error.AlreadyReported => {
310 var eb = try runner.eb_wip.toOwnedBundle("");
311 defer eb.deinit(runner.gpa);
312 assert(eb.errorMessageCount() > 0); // already reported what?
313 runner.server.serveErrorBundle(
314 .error_bundle,
315 eb,
316 ) catch |server_err| switch (server_err) {
317 error.WriteFailed => return error.ServerWriteFailed,
318 };
319 const eb_wip: std.zig.ErrorBundle.Wip = try .init(runner.gpa);
320 runner.eb_wip.deinit();
321 runner.eb_wip = eb_wip;
322 break :status .fail;
323 },
324 error.SkipTest => .skip,
325 },
326 .fuzz = false,
327 .log_err_count = 0,
328 .leak_count = 0,
329 },
330 }) catch |err| switch (err) {
331 error.WriteFailed => return error.ServerWriteFailed,
332 };
333 assert(runner.eb_wip.root_list.items.len == 0); // failed to report
334 },
335 }
336 }
337}
338
339fn discoverInput(
340 runner: *Runner,
341 dir: std.zig.Server.Message.InputDir,
342 sub_path: []const u8,
343) error{ServerWriteFailed}!void {
344 return runner.discoverInputInner(dir, sub_path) catch |err| switch (err) {
345 error.WriteFailed => return error.ServerWriteFailed,
346 };
347}
348fn discoverInputInner(
349 runner: *Runner,
350 dir: std.zig.Server.Message.InputDir,
351 sub_path: []const u8,
352) std.Io.Writer.Error!void {
353 try runner.server.serveMessageHeader(.{
354 .tag = .discovered_inputs,
355 .bytes_len = @intCast(@sizeOf(std.zig.Server.Message.InputDir) +
356 sub_path.len + 1),
357 });
358 try runner.server.out.writeInt(u32, @backingInt(dir), .little);
359 try runner.server.out.writeAll(sub_path);
360 try runner.server.out.writeByte(0);
361 try runner.server.out.flush();
362}
363
364pub const SourceReader = struct {
365 const Io = std.Io;
366
367 backing: *std.Io.Reader,
368 line_aw: std.Io.Writer.Allocating,
369 interface: std.Io.Reader,
370 line: u32,
371 eb_wip: *std.zig.ErrorBundle.Wip,
372 src_path: std.zig.ErrorBundle.String,
373
374 pub fn init(
375 reader: *Io.Reader,
376 gpa: std.mem.Allocator,
377 eb_wip: *std.zig.ErrorBundle.Wip,
378 src_path: std.zig.ErrorBundle.String,
379 ) SourceReader {
380 return .{
381 .backing = reader,
382 .line_aw = .init(gpa),
383 .interface = .{
384 .vtable = &.{
385 .stream = stream,
386 .discard = discard,
387 .readVec = readVec,
388 .rebase = rebase,
389 },
390 .buffer = &.{},
391 .seek = 0,
392 .end = 0,
393 },
394 .line = 0,
395 .eb_wip = eb_wip,
396 .src_path = src_path,
397 };
398 }
399 fn deinit(s: *SourceReader) void {
400 s.line_aw.deinit();
401 }
402 fn stream(r: *Io.Reader, _: *Io.Writer, _: Io.Limit) Io.Reader.StreamError!usize {
403 const s: *SourceReader = @fieldParentPtr("interface", r);
404 try s.bufferLine();
405 return 0;
406 }
407 fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
408 const s: *SourceReader = @fieldParentPtr("interface", r);
409 const remaining = r.buffered();
410 if (remaining.len > 0) {
411 const n = limit.minInt(remaining.len);
412 r.toss(n);
413 return n;
414 }
415 try s.bufferLine();
416 return 0;
417 }
418 fn readVec(r: *Io.Reader, _: [][]u8) Io.Reader.Error!usize {
419 const s: *SourceReader = @fieldParentPtr("interface", r);
420 try s.bufferLine();
421 return 0;
422 }
423 fn rebase(r: *Io.Reader, capacity: usize) Io.Reader.RebaseError!void {
424 const s: *SourceReader = @fieldParentPtr("interface", r);
425 defer s.updateBuffer();
426 s.line_aw.ensureUnusedCapacity(capacity) catch |err| switch (err) {
427 error.OutOfMemory => return error.ReadFailed,
428 };
429 }
430 fn updateBuffer(s: *SourceReader) void {
431 s.interface.buffer = s.line_aw.writer.buffer;
432 s.interface.end = s.line_aw.writer.end;
433 }
434 fn bufferLine(s: *SourceReader) Io.Reader.Error!void {
435 defer s.updateBuffer();
436 {
437 const written = s.line_aw.written();
438 const start = if (std.mem.findScalarLast(u8, written[0..s.interface.seek], '\n')) |newline|
439 newline + 1
440 else
441 0;
442 s.line += @intCast(std.mem.countScalar(u8, written[0..start], '\n'));
443 const preserve = written[start..];
444 @memmove(written[0..preserve.len], preserve);
445 s.line_aw.shrinkRetainingCapacity(preserve.len);
446 s.interface.seek -= start;
447 }
448 const n = s.backing.streamDelimiterEnding(&s.line_aw.writer, '\n') catch |err| switch (err) {
449 error.ReadFailed => |e| return e,
450 error.WriteFailed => return error.ReadFailed,
451 };
452 if (std.mem.endsWith(u8, s.line_aw.written(), "\r")) s.line_aw.writer.end -= 1;
453 const buffered = s.backing.buffered();
454 if (n == 0 and buffered.len == 0) return error.EndOfStream;
455 if (std.mem.startsWith(u8, buffered, "\n")) {
456 s.line_aw.writer.writeByte('\n') catch |err| switch (err) {
457 error.WriteFailed => return error.ReadFailed,
458 };
459 s.backing.toss(1);
460 }
461 }
462 fn fail(
463 s: *SourceReader,
464 src_loc: []const u8,
465 comptime fmt: []const u8,
466 args: anytype,
467 ) FailError {
468 return s.failString(src_loc, try s.eb_wip.printString(fmt, args));
469 }
470 fn failString(
471 s: *SourceReader,
472 src_loc: []const u8,
473 msg: std.zig.ErrorBundle.String,
474 ) FailError {
475 const line: u32, const column: u32, const source_line = src_loc: {
476 const pos = src_loc.ptr - s.interface.buffer.ptr;
477 const end = std.mem.findScalarPos(u8, s.interface.buffer, pos, '\n') orelse
478 s.interface.buffer.len;
479 const start = if (std.mem.findScalarLast(u8, s.interface.buffer[0..end], '\n')) |newline|
480 newline + 1
481 else
482 0;
483 break :src_loc .{
484 @intCast(s.line + std.mem.countScalar(u8, s.interface.buffer[0..start], '\n')),
485 @intCast(pos - start),
486 s.interface.buffer[start..end],
487 };
488 };
489 try s.eb_wip.addRootErrorMessage(.{
490 .msg = msg,
491 .src_loc = try s.eb_wip.addSourceLocation(.{
492 .src_path = s.src_path,
493 .line = line,
494 .column = column,
495 .span_start = column,
496 .span_main = column,
497 .span_end = @intCast(column + src_loc.len),
498 .source_line = try s.eb_wip.addString(source_line),
499 }),
500 });
501 return error.AlreadyReported;
502 }
503};
504pub const DelimitedReader = struct {
505 const Io = std.Io;
506
507 unlimited: *Io.Reader,
508 remaining: Io.Limit,
509 delimiter: []const u8,
510 interface: Io.Reader,
511
512 pub fn init(reader: *Io.Reader, delimiter: []const u8, buffer: []u8) DelimitedReader {
513 return .{
514 .unlimited = reader,
515 .remaining = .nothing,
516 .delimiter = delimiter,
517 .interface = .{
518 .vtable = &.{
519 .stream = stream,
520 .discard = discard,
521 },
522 .buffer = buffer,
523 .seek = 0,
524 .end = 0,
525 },
526 };
527 }
528 fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
529 const d: *DelimitedReader = @fieldParentPtr("interface", r);
530 const block = try d.unlimited.peekGreedy(d.delimiter.len);
531 d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse
532 block.len - d.delimiter.len + 1);
533 if (d.remaining == .nothing) return error.EndOfStream;
534 const n = try d.unlimited.stream(w, limit.min(d.remaining));
535 d.remaining = d.remaining.subtract(n).?;
536 return n;
537 }
538 fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
539 const d: *DelimitedReader = @fieldParentPtr("interface", r);
540 const block = try d.unlimited.peekGreedy(d.delimiter.len);
541 d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse
542 block.len - d.delimiter.len + 1);
543 if (d.remaining == .nothing) return error.EndOfStream;
544 const n = try d.unlimited.discard(limit.min(d.remaining));
545 d.remaining = d.remaining.subtract(n).?;
546 return n;
547 }
548};
549
550const Update = struct {
551 target_query: []const u8,
552 manifest_sr: SourceReader,
553 compiler: ?Compiler,
554 src_dir: std.Io.Dir,
555 prog_node: std.Progress.Node,
556 mtime: std.Io.Timestamp,
557 num: usize,
558 allow_skip: bool,
559};
560
561const TestError = error{
562 SkipTest,
563} || FailError;
564fn testOne(runner: *Runner, test_index: u32) (error{ServerWriteFailed} || TestError)!void {
565 const target_query =
566 std.mem.sliceTo(runner.args.target_bytes.items[runner.args.targets.items[test_index]..], 0);
567 const manifest_file = runner.args.manifest_file orelse
568 return runner.fail("missing \"path/to/test\" arg", .{});
569 var manifest_buffer: [512]u8 = undefined;
570 var manifest_fr = manifest_file.reader(runner.io, &manifest_buffer);
571 var update: Update = .{
572 .target_query = target_query,
573 .manifest_sr = .init(&manifest_fr.interface, runner.gpa, &runner.eb_wip, src_path_string: {
574 var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
575 const src_path = src_path: {
576 break :src_path path_buffer[0 .. manifest_file.realPath(
577 runner.io,
578 &path_buffer,
579 ) catch |err| switch (err) {
580 error.Canceled => |e| return e,
581 else => break :src_path "manifest",
582 }];
583 };
584 break :src_path_string try runner.eb_wip.addString(src_path);
585 }),
586 .compiler = null,
587 .src_dir = (runner.args.src_dir orelse
588 return runner.fail("missing \"--src=path/to/src\" arg", .{})).createDirPathOpen(
589 runner.io,
590 target_query,
591 .{},
592 ) catch |err| switch (err) {
593 error.Canceled => |e| return e,
594 else => |e| return runner.fail("unable to create {q}: {t}", .{ target_query, e }),
595 },
596 .prog_node = .none,
597 .mtime = .now(runner.io, .real),
598 .num = 0,
599 .allow_skip = true,
600 };
601 defer {
602 update.manifest_sr.deinit();
603 update.src_dir.close(runner.io);
604 update.prog_node.end();
605 }
606
607 // hack around `std.Io.Reader` interface bugs by ensuring the
608 // buffer is larger than the longest supported manifest line
609 try update.manifest_sr.line_aw.ensureTotalCapacityPrecise(512);
610 update.manifest_sr.updateBuffer();
611
612 var skip_delimiter = false;
613 while (update.manifest_sr.interface.takeSentinel('\n')) |line| {
614 var line_it = std.mem.tokenizeScalar(u8, line, ' ');
615 const cmd_src_loc = line_it.next() orelse continue;
616 if (skip_delimiter) {
617 assert(std.mem.eql(u8, cmd_src_loc, "#}"));
618 skip_delimiter = false;
619 const unexpected = line_it.rest();
620 if (unexpected.len > 0)
621 return update.manifest_sr.fail(unexpected, "unexpected argument {q}", .{unexpected});
622 continue;
623 }
624 const cmd = std.meta.stringToEnum(Command, std.mem.cutPrefix(u8, cmd_src_loc, "#") orelse
625 return update.manifest_sr.fail(cmd_src_loc, "expected command", .{})) orelse
626 return update.manifest_sr.fail(cmd_src_loc, "unknown command", .{});
627 const arg = switch (cmd) {
628 .todo, .exe, .lib, .obj, .update => null,
629 .skip, .write, .delete, .check => line_it.next() orelse
630 return update.manifest_sr.fail(line_it.rest()[0..0], "missing argument", .{}),
631 };
632 var contents_file: ?std.Io.File = null;
633 defer if (contents_file) |file| file.close(runner.io);
634 var contents_buffer: [512]u8 = undefined;
635 var contents_impl: union(enum) {
636 ending: std.Io.Reader,
637 dr: DelimitedReader,
638 fr: std.Io.File.Reader,
639 } = undefined;
640 const contents_r: *std.Io.Reader = contents_r: switch (cmd) {
641 .skip, .delete => {
642 const unexpected = line_it.rest();
643 if (unexpected.len > 0) return update.manifest_sr.fail(
644 unexpected,
645 "unexpected argument {q}",
646 .{unexpected},
647 );
648 contents_impl = .{ .ending = .ending_instance };
649 break :contents_r &contents_impl.ending;
650 },
651 else => {
652 const contents_path = line_it.rest();
653 if (contents_path.len == 0) {
654 contents_impl = .{ .ending = .ending_instance };
655 break :contents_r &contents_impl.ending;
656 }
657
658 if (std.mem.eql(u8, contents_path, "{")) {
659 contents_impl = .{
660 .dr = .init(&update.manifest_sr.interface, "#}", &contents_buffer),
661 };
662 skip_delimiter = true;
663 break :contents_r &contents_impl.dr.interface;
664 }
665
666 const args_test = runner.args.@"test" orelse return update.manifest_sr.fail(
667 contents_path,
668 "contents file requires directory test",
669 .{},
670 );
671 try runner.discoverInput(args_test.input_dir, contents_path);
672
673 contents_file =
674 args_test.dir.openFile(runner.io, contents_path, .{}) catch |err| switch (err) {
675 error.Canceled => |e| return e,
676 else => |e| return update.manifest_sr.fail(
677 contents_path,
678 "unable to open contents file: {t}",
679 .{e},
680 ),
681 };
682 contents_impl = .{ .fr = contents_file.?.reader(runner.io, &contents_buffer) };
683 break :contents_r &contents_impl.fr.interface;
684 },
685 };
686
687 runner.handleCommand(&update, cmd_src_loc, cmd, arg, contents_r) catch |err| switch (err) {
688 error.Canceled,
689 error.OutOfMemory,
690 error.AlreadyReported,
691 error.SkipTest,
692 => |e| return e,
693 error.ContentsReadFailed => switch (contents_impl) {
694 .ending => unreachable,
695 .dr => switch (manifest_fr.err orelse return error.OutOfMemory) {
696 error.Canceled => |e| return e,
697 else => |e| return runner.fail("unable to read manifest: {t}", .{e}),
698 },
699 .fr => |fr| switch (fr.err.?) {
700 error.Canceled => |e| return e,
701 else => |e| return runner.fail("unable to read contents file: {t}", .{e}),
702 },
703 },
704 };
705 } else |err| switch (err) {
706 error.ReadFailed => switch (manifest_fr.err orelse return error.OutOfMemory) {
707 error.Canceled => |e| return e,
708 else => |e| return runner.fail("unable to read manifest: {t}", .{e}),
709 },
710 error.StreamTooLong => unreachable,
711 error.EndOfStream => if (skip_delimiter)
712 return runner.fail("manifest missing \"#}}\" delimiter", .{}),
713 }
714 if (update.compiler) |*comp| {
715 if (comp.state != .idle) return runner.fail("missing \"#check\"", .{});
716 try comp.exit();
717 }
718}
719const Command = enum { todo, skip, exe, lib, obj, write, delete, update, check };
720fn handleCommand(
721 runner: *Runner,
722 update: *Update,
723 cmd_src_loc: []const u8,
724 cmd: Command,
725 arg: ?[]const u8,
726 contents_r: *std.Io.Reader,
727) (error{ContentsReadFailed} || TestError)!void {
728 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
729 const cmd_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "#{t}{s}{s}", .{
730 cmd, if (arg) |_| " " else "", arg orelse "",
731 }) catch &name_buffer, 0);
732 defer cmd_prog_node.end();
733
734 cmd: switch (cmd) {
735 .todo => _ = contents_r.discardRemaining() catch |err| switch (err) {
736 error.ReadFailed => {},
737 },
738 .skip => if (!update.allow_skip) return update.manifest_sr.fail(
739 cmd_src_loc,
740 "\"#skip\" must appear before other commands",
741 .{},
742 ) else if (std.mem.eql(u8, update.target_query, arg.?)) return error.SkipTest,
743 .exe, .lib, .obj => runner.spawnCompiler(&update.compiler, update.src_dir, switch (cmd) {
744 else => unreachable,
745 .exe => .Exe,
746 .lib => .Lib,
747 .obj => .Obj,
748 }, contents_r, target: {
749 const backend_split = std.mem.findScalarLast(u8, update.target_query, '-') orelse
750 return runner.fail("target {q} missing query", .{update.target_query});
751 const mode_split =
752 std.mem.findScalarLast(u8, update.target_query[0..backend_split], '-') orelse
753 return runner.fail("target {q} missing mode", .{update.target_query});
754 const triple = update.target_query[0..mode_split];
755 const backend_str = update.target_query[backend_split + 1 ..];
756 const backend = std.meta.stringToEnum(Compiler.Target.Backend, backend_str) orelse
757 return runner.fail("target {q} unknown backend {q}", .{
758 update.target_query, backend_str,
759 });
760 const mode_str = update.target_query[mode_split + 1 .. backend_split];
761 const mode = std.meta.stringToEnum(Compiler.Target.Mode, mode_str) orelse
762 return runner.fail("target {q} unknown mode {q}", .{ update.target_query, mode_str });
763 break :target .{
764 .triple = triple,
765 .resolved = std.zig.system.resolveTargetQuery(
766 runner.io,
767 std.Build.parseTargetQuery(.{
768 .arch_os_abi = triple,
769 .object_format = switch (backend) {
770 .sema, .selfhosted, .llvm => null,
771 .cbe => "c",
772 },
773 }) catch |err| switch (err) {
774 error.ParseFailed => return runner.fail("unable to parse triple {q}", .{
775 triple,
776 }),
777 },
778 ) catch |err| switch (err) {
779 error.Canceled => |e| return e,
780 else => |e| return runner.fail("unable to resolve target {q}: {t}", .{ triple, e }),
781 },
782 .mode = mode,
783 .backend = backend,
784 };
785 }) catch |err| switch (err) {
786 error.Canceled, error.OutOfMemory, error.AlreadyReported => |e| return e,
787 error.ReadFailed => return error.ContentsReadFailed,
788 },
789 .write => {
790 const file = update.src_dir.createFile(runner.io, arg.?, .{}) catch |err| switch (err) {
791 error.Canceled => |e| return e,
792 else => |e| return update.manifest_sr.fail(arg.?, "unable to create file: {t}", .{e}),
793 };
794 defer file.close(runner.io);
795 var fw_buffer: [512]u8 = undefined;
796 var fw = file.writer(runner.io, &fw_buffer);
797 _ = contents_r.streamRemaining(&fw.interface) catch |err| switch (err) {
798 error.ReadFailed => return error.ContentsReadFailed,
799 error.WriteFailed => switch (fw.err.?) {
800 error.Canceled => |e| return e,
801 else => |e| return update.manifest_sr.fail(
802 arg.?,
803 "unable to write file: {t}",
804 .{e},
805 ),
806 },
807 };
808 fw.flush() catch |err| switch (err) {
809 error.Canceled => |e| return e,
810 else => |e| return update.manifest_sr.fail(
811 arg.?,
812 "unable to write file: {t}",
813 .{e},
814 ),
815 };
816 file.setTimestamps(runner.io, .{ .modify_timestamp = .{
817 .new = update.mtime,
818 } }) catch |err| switch (err) {
819 error.Canceled => |e| return e,
820 else => |e| return update.manifest_sr.fail(
821 arg.?,
822 "unable to update file timestamps: {t}",
823 .{e},
824 ),
825 };
826 },
827 .delete => update.src_dir.deleteFile(runner.io, arg.?) catch |err| switch (err) {
828 error.Canceled => |e| return e,
829 else => |e| return update.manifest_sr.fail(arg.?, "unable to delete file: {t}", .{e}),
830 },
831 .update => {
832 const comp = &(update.compiler orelse return update.manifest_sr.fail(
833 cmd_src_loc,
834 "missing \"#exe\", \"#lib\", or \"#obj\"",
835 .{},
836 ));
837 update.prog_node.end();
838 update.prog_node = runner.prog_node.start(
839 switch (contents_r.readSliceShort(&name_buffer) catch |err| switch (err) {
840 error.ReadFailed => return error.ContentsReadFailed,
841 }) {
842 0 => switch (update.num) {
843 0 => "initial update",
844 else => |num| std.mem.print(&name_buffer, "update {d}", .{num}) catch
845 &name_buffer,
846 },
847 else => |name_len| name_buffer[0..name_len],
848 },
849 0,
850 );
851 _ = contents_r.discardRemaining() catch |err| switch (err) {
852 error.ReadFailed => return error.ContentsReadFailed,
853 };
854 if (comp.state != .idle)
855 return update.manifest_sr.fail(cmd_src_loc, "missing \"#check\"", .{});
856 comp.client.serveBodylessMessage(.update) catch |err| switch (err) {
857 error.WriteFailed => switch (comp.fw.err.?) {
858 error.Canceled => |e| return e,
859 else => |e| return runner.fail("unable to send message to compiler: {t}", .{e}),
860 },
861 };
862 comp.state = .update;
863 update.mtime = update.mtime.addDuration(.fromSeconds(2));
864 update.num += 1;
865 },
866 .check => {
867 const check = std.meta.stringToEnum(Compiler.Check, arg.?) orelse
868 return update.manifest_sr.fail(arg.?, "unknown check", .{});
869 const expected =
870 contents_r.allocRemaining(runner.gpa, .unlimited) catch |err| switch (err) {
871 error.OutOfMemory => |e| return e,
872 error.ReadFailed => return error.ContentsReadFailed,
873 error.StreamTooLong => unreachable, // .unlimited
874 };
875 defer runner.gpa.free(expected);
876
877 const comp = &(update.compiler orelse return update.manifest_sr.fail(
878 cmd_src_loc,
879 "missing \"#exe\", \"#lib\", or \"#obj\"",
880 .{},
881 ));
882 if (comp.state != .update)
883 return update.manifest_sr.fail(cmd_src_loc, "missing \"#update\"", .{});
884 const stderr = comp.mr.reader(1);
885 while (try comp.receiveMessage()) |message| switch (message.tag) {
886 else => {}, // Ignore other messages,
887 .config => {
888 var body_r: std.Io.Reader = .fixed(message.body);
889 comp.config = body_r.takeStruct(std.zig.Server.Message.Config, .little) catch
890 unreachable;
891 },
892 .emit_digest => {
893 var body_r: std.Io.Reader = .fixed(message.body);
894 _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch
895 unreachable;
896 const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch
897 unreachable;
898 try comp.checkSuccess(check, expected, update.src_dir, digest);
899 },
900 .error_bundle => {
901 const eb = std.zig.Server.allocErrorBundle(
902 runner.arena.allocator(),
903 message.body,
904 ) catch |err| switch (err) {
905 error.OutOfMemory => |e| return e,
906 error.EndOfStream => return runner.fail("error bundle truncated", .{}),
907 };
908 switch (check) {
909 .errors => {
910 var error_aw: std.Io.Writer.Allocating = .init(runner.gpa);
911 defer error_aw.deinit();
912 eb.renderToWriter(.{
913 .include_reference_trace = false,
914 .include_source_line = false,
915 .include_log_text = true,
916 }, &error_aw.writer) catch |err| switch (err) {
917 error.WriteFailed => return error.OutOfMemory,
918 };
919 const actual = error_aw.written();
920 std.mem.replaceScalar(u8, actual, '\\', '/');
921 std.testing.expectEqualStrings(expected, actual) catch |err| switch (err) {
922 error.TestExpectedEqual => return runner.fail(
923 "errors did not match expected",
924 .{},
925 ),
926 };
927 },
928 .stdout, .exit, .lldb => if (eb.errorMessageCount() > 0) {
929 eb.renderToStderr(runner.io, .{}, .auto) catch |err| switch (err) {
930 error.Canceled => {},
931 else => {},
932 };
933 return runner.fail("unexpected compile errors", .{});
934 },
935 }
936 comp.state = .idle;
937 break :cmd;
938 },
939 };
940
941 const buffered_stderr = stderr.buffered();
942 if (buffered_stderr.len > 0) {
943 if (comp.allow_compiler_stderr) {
944 std.log.info("stderr:\n{s}", .{buffered_stderr});
945 } else {
946 return runner.fail("unexpected stderr:\n{s}", .{buffered_stderr});
947 }
948 }
949
950 try comp.exit();
951 return runner.fail("compiler failed to send terminating error_bundle", .{});
952 },
953 }
954 assert(contents_r.discardRemaining() catch |err| switch (err) {
955 error.ReadFailed => return error.ContentsReadFailed,
956 } == 0);
957 switch (cmd) {
958 .todo, .skip => {},
959 else => update.allow_skip = false,
960 }
961}
962
963fn spawnCompiler(
964 runner: *Runner,
965 compiler: *?Compiler,
966 src_dir: std.Io.Dir,
967 output_mode: std.lang.OutputMode,
968 args_r: *std.Io.Reader,
969 target: Compiler.Target,
970) (std.Io.Reader.ShortError || FailError)!void {
971 const gpa = runner.gpa;
972 const arena = runner.arena.allocator();
973 if (compiler.*) |*comp| {
974 try comp.exit();
975 compiler.* = null;
976 }
977
978 var argv: std.ArrayList([]const u8) = .empty;
979 defer argv.deinit(gpa);
980 try argv.appendSlice(gpa, &.{
981 "zig",
982 switch (output_mode) {
983 .Obj => "build-obj",
984 .Exe => "build-exe",
985 .Lib => "build-lib",
986 },
987 "-target",
988 target.triple,
989 "--cache-dir",
990 ".zig-cache",
991 });
992 switch (target.mode) {
993 .whole => {},
994 .incremental => try argv.append(gpa, "-fincremental"),
995 }
996 var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
997 if (target.backend) |b| switch (b) {
998 .sema => try argv.append(gpa, "-fno-emit-bin"),
999 .selfhosted => try argv.append(gpa, "-fno-llvm"),
1000 .llvm => try argv.append(gpa, "-fllvm"),
1001 .cbe => try argv.append(gpa, "-ofmt=c"),
1002 } else try argv.appendSlice(gpa, &.{ "-I", path_buffer[0 .. (runner.args.lib_dir orelse
1003 return runner.fail("\"--lib=path/to/lib\" arg required with cbe", .{}))
1004 .realPath(runner.io, &path_buffer) catch |err| switch (err) {
1005 error.Canceled => |e| return e,
1006 else => |e| return runner.fail("unable to get lib path: {t}", .{e}),
1007 }] });
1008 var need: usize = 1;
1009 var root_name: ?[]const u8 = null;
1010 while (true) {
1011 const done = if (args_r.fill(need)) false else |err| switch (err) {
1012 error.ReadFailed => |e| return e,
1013 error.EndOfStream => true,
1014 };
1015 const buffered = args_r.buffered();
1016 const start = std.mem.findNone(u8, buffered, "\n ") orelse buffered.len;
1017 const end = std.mem.findAnyPos(u8, buffered, start, "\n ") orelse buffered.len;
1018 if (end - start > 0) {
1019 const arg = try arena.dupe(u8, buffered[start..end]);
1020 if (std.mem.cutPrefix(u8, arg, "-M")) |module| {
1021 var module_name_it = std.mem.splitScalar(u8, module, '=');
1022 const module_name = module_name_it.next().?;
1023 root_name = root_name orelse module_name;
1024 }
1025 try argv.append(gpa, arg);
1026 args_r.toss(end);
1027 need = 1;
1028 } else {
1029 args_r.toss(start);
1030 need = buffered.len - start + 1;
1031 }
1032 if (done) break;
1033 }
1034 try argv.append(gpa, "--listen=-");
1035
1036 const out_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{
1037 .root_name = root_name orelse "root",
1038 .cpu_arch = target.resolved.cpu.arch,
1039 .os_tag = target.resolved.os.tag,
1040 .ofmt = target.resolved.ofmt,
1041 .abi = target.resolved.abi,
1042 .output_mode = output_mode,
1043 });
1044
1045 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
1046 const comp_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "compile {s}", .{
1047 out_name,
1048 }) catch &name_buffer, 0);
1049 errdefer comp_prog_node.end();
1050
1051 const child = std.process.spawn(runner.io, .{
1052 .exe = .{ .file = runner.args.zig_exe orelse
1053 return runner.fail("missing \"--zig=path/to/zig\" arg", .{}) },
1054 .argv = argv.items,
1055 .cwd = .{ .dir = src_dir },
1056 .progress_node = comp_prog_node,
1057 .stdin = .pipe,
1058 .stdout = .pipe,
1059 .stderr = .pipe,
1060 }) catch |err| switch (err) {
1061 error.Canceled, error.OutOfMemory => |e| return e,
1062 else => |e| return runner.fail("unable to spawn compiler: {t}", .{e}),
1063 };
1064 compiler.* = .{
1065 .runner = runner,
1066 .prog_node = comp_prog_node,
1067 .allow_compiler_stderr = true,
1068 .output_mode = output_mode,
1069 .state = .idle,
1070 .child = child,
1071 .fw = undefined,
1072 .mr_buffer = undefined,
1073 .mr = undefined,
1074 .client = undefined,
1075 .target = target,
1076 .out_name = out_name,
1077 .config = null,
1078 };
1079 const comp = &compiler.*.?;
1080 comp.fw = child.stdin.?.writerStreaming(runner.io, &.{});
1081 comp.mr.init(gpa, runner.io, comp.mr_buffer.toStreams(), &.{
1082 child.stdout.?, child.stderr.?,
1083 });
1084 comp.client = .{ .in = comp.mr.reader(0), .out = &comp.fw.interface };
1085}
1086const Compiler = struct {
1087 runner: *Runner,
1088 prog_node: std.Progress.Node,
1089 allow_compiler_stderr: bool,
1090 output_mode: std.lang.OutputMode,
1091 state: enum { idle, update },
1092 child: std.process.Child,
1093 fw: std.Io.File.Writer,
1094 mr_buffer: std.Io.File.MultiReader.Buffer(2),
1095 mr: std.Io.File.MultiReader,
1096 client: std.zig.Client,
1097 target: Target,
1098 out_name: []const u8,
1099 config: ?std.zig.Server.Message.Config,
1100
1101 const Target = struct {
1102 triple: []const u8,
1103 resolved: std.Target,
1104 mode: Mode,
1105 backend: ?Backend,
1106
1107 const Mode = enum {
1108 whole,
1109 incremental,
1110 };
1111
1112 const Backend = enum {
1113 /// Run semantic analysis only. Runtime output will not be tested, but we still verify
1114 /// that compilation succeeds. Corresponds to `-fno-emit-bin`.
1115 sema,
1116 /// Use the self-hosted code generation backend for this target.
1117 /// Corresponds to `-fno-llvm -fno-lld`.
1118 selfhosted,
1119 /// Use the LLVM backend.
1120 /// Corresponds to `-fllvm -flld`.
1121 llvm,
1122 /// Use the C backend. The output is compiled with `zig cc`.
1123 /// Corresponds to `-ofmt=c`.
1124 cbe,
1125 };
1126 };
1127
1128 fn receiveMessage(comp: *Compiler) FailError!?struct {
1129 tag: std.zig.Server.Message.Tag,
1130 body: []const u8,
1131 } {
1132 const header = comp.client.receiveMessageWithMultiReader(
1133 &comp.mr,
1134 .none,
1135 ) catch |err| switch (err) {
1136 error.Canceled, error.OutOfMemory => |e| return e,
1137 error.Timeout => unreachable,
1138 error.EndOfStream => return null,
1139 else => |e| return comp.runner.fail("unable to receive message: {t}", .{e}),
1140 };
1141 const body = comp.client.in.take(header.bytes_len) catch unreachable;
1142 const stderr = comp.mr.reader(1);
1143 if (stderr.bufferedLen() > 0) {
1144 if (comp.allow_compiler_stderr) {
1145 std.log.info("{t} stderr:\n{s}", .{ header.tag, stderr.buffered() });
1146 } else {
1147 return comp.runner.fail("{t} unexpected stderr:\n{s}", .{
1148 header.tag, stderr.buffered(),
1149 });
1150 }
1151 stderr.tossBuffered();
1152 }
1153 return .{ .tag = header.tag, .body = body };
1154 }
1155
1156 const Check = enum { errors, stdout, exit, lldb };
1157 fn checkSuccess(
1158 comp: *Compiler,
1159 check: Check,
1160 expected: []const u8,
1161 src_dir: std.Io.Dir,
1162 digest: *const std.Build.Cache.BinDigest,
1163 ) FailError!void {
1164 const runner = comp.runner;
1165 const gpa = runner.gpa;
1166 const arena = runner.arena.allocator();
1167 const io = runner.io;
1168 const target = &comp.target.resolved;
1169 const config = &comp.config.?;
1170 const out_dir = ".zig-cache" ++ std.Io.Dir.path.sep_str ++
1171 "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*);
1172 const out_path = try std.Io.Dir.path.join(arena, &.{ out_dir, comp.out_name });
1173 const bin_path = switch (comp.target.backend.?) {
1174 .sema => return,
1175 .selfhosted, .llvm => out_path,
1176 .cbe => try comp.compileC(src_dir, out_path),
1177 };
1178 switch (check) {
1179 .errors => return,
1180 .stdout, .exit, .lldb => {},
1181 }
1182
1183 var argv: std.ArrayList([]const u8) = .initBuffer(try arena.alloc([]const u8, 4));
1184 var environ_map: std.process.Environ.Map = .init(gpa);
1185 defer environ_map.deinit();
1186 const need_cross_libc =
1187 target.os.tag == .linux and config.flags.link_libc and config.flags.link_mode == .dynamic;
1188 const use_executor = use_executor: switch (std.zig.system.getExternalExecutor(io, target, .{
1189 .host_cpu_arch = runner.host.cpu.arch,
1190 .host_os_tag = runner.host.os.tag,
1191 .qemu_fixes_dl = need_cross_libc and runner.args.libc_runtimes_dir != null,
1192 .link_mode = config.flags.link_mode,
1193 .link_libc = config.flags.link_libc,
1194 })) {
1195 .bad_dl, .bad_os_or_cpu => {
1196 // This binary cannot be executed on this host.
1197 if (!runner.args.quiet) std.log.warn("skipping execution because host {q} cannot " ++
1198 "execute binaries for foreign target {q}", .{
1199 try runner.host.zigTriple(arena), comp.target.triple,
1200 });
1201 return;
1202 },
1203 .native => false,
1204 .darling => |executor| if (runner.args.enable_darling) {
1205 argv.appendAssumeCapacity(executor);
1206 break :use_executor true;
1207 } else continue :use_executor .bad_os_or_cpu,
1208 .qemu => |executor| if (runner.args.enable_qemu) {
1209 argv.appendAssumeCapacity(executor);
1210 if (need_cross_libc) {
1211 const libc_runtimes_dir = runner.args.libc_runtimes_dir orelse
1212 continue :use_executor .bad_os_or_cpu;
1213 var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
1214 const libc_runtimes_path = path_buffer[0 .. libc_runtimes_dir.realPath(
1215 runner.io,
1216 &path_buffer,
1217 ) catch |err| switch (err) {
1218 error.Canceled => |e| return e,
1219 else => |e| return runner.fail("unable to get libc runtimes path: {t}", .{e}),
1220 }];
1221 argv.appendSliceAssumeCapacity(&.{ "-L", try std.Io.Dir.path.join(arena, &.{
1222 libc_runtimes_path,
1223 try if (target.isGnuLibC()) std.zig.target.glibcRuntimeTriple(
1224 arena,
1225 target.cpu.arch,
1226 target.os.tag,
1227 target.abi,
1228 ) else if (target.isMuslLibC()) std.zig.target.muslRuntimeTriple(
1229 arena,
1230 target.cpu.arch,
1231 target.abi,
1232 ) else unreachable,
1233 }) });
1234 }
1235 break :use_executor true;
1236 } else continue :use_executor .bad_os_or_cpu,
1237 .rosetta => if (runner.args.enable_rosetta)
1238 true
1239 else
1240 continue :use_executor .bad_os_or_cpu,
1241 .wine => |executor| if (runner.args.enable_wine) {
1242 if (!environ_map.contains("WINEDEBUG")) try environ_map.put("WINEDEBUG", "-all");
1243 argv.appendAssumeCapacity(executor);
1244 break :use_executor true;
1245 } else continue :use_executor .bad_os_or_cpu,
1246 .wasmtime => |executor| if (runner.args.enable_wasmtime) {
1247 if (!environ_map.contains("WASMTIME_BACKTRACE_DETAILS"))
1248 try environ_map.put("WASMTIME_BACKTRACE_DETAILS", "1");
1249 argv.appendSliceAssumeCapacity(&.{ executor, "--dir=.", "-Sinherit-env" });
1250 break :use_executor true;
1251 } else continue :use_executor .bad_os_or_cpu,
1252 };
1253 argv.appendAssumeCapacity(bin_path);
1254
1255 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
1256 const run_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "run {s}", .{
1257 comp.out_name,
1258 }) catch &name_buffer, 0);
1259 defer run_prog_node.end();
1260
1261 const bin_file = src_dir.openFile(io, bin_path, .{}) catch |err| switch (err) {
1262 error.Canceled => |e| return e,
1263 else => |e| return runner.fail("unable to open {q}: {t}", .{ bin_path, e }),
1264 };
1265 defer bin_file.close(io);
1266 switch (check) {
1267 .errors => unreachable,
1268 .stdout, .exit => {
1269 const result = std.process.run(gpa, io, .{
1270 .exe = if (use_executor) .search else .{ .file = bin_file },
1271 .argv = argv.items,
1272 .cwd = .{ .dir = src_dir },
1273 .environ_map = &environ_map,
1274 .progress_node = run_prog_node,
1275 }) catch |err| if (use_executor) {
1276 // Chances are the foreign executor isn't available. Skip this evaluation.
1277 if (!runner.args.quiet) std.log.warn(
1278 "skipping execution of {q} via executor for foreign target {q}: {t}",
1279 .{ bin_path, comp.target.triple, err },
1280 );
1281 return;
1282 } else return runner.fail("unable to run the generated executable {q}: {t}", .{
1283 bin_path, err,
1284 });
1285 defer {
1286 gpa.free(result.stdout);
1287 gpa.free(result.stderr);
1288 }
1289 switch (result.term) {
1290 .exited => |code| switch (check) {
1291 .errors, .lldb => unreachable,
1292 .stdout => {
1293 if (code != 0) return runner.fail(
1294 "generated executable {q} failed with code {d}",
1295 .{ bin_path, code },
1296 );
1297 std.testing.expectEqualStrings(
1298 expected,
1299 result.stdout,
1300 ) catch |err| switch (err) {
1301 error.TestExpectedEqual => return runner.fail(
1302 "stdout did not match expected",
1303 .{},
1304 ),
1305 };
1306 },
1307 .exit => {
1308 var actual_code_buffer: [std.fmt.count("{d}", .{std.math.maxInt(u8)})]u8 =
1309 undefined;
1310 std.testing.expectEqualStrings(
1311 expected,
1312 std.mem.print(&actual_code_buffer, "{d}", .{code}) catch unreachable,
1313 ) catch |err| switch (err) {
1314 error.TestExpectedEqual => return runner.fail(
1315 "exit code did not match expected",
1316 .{},
1317 ),
1318 };
1319 },
1320 },
1321 .signal => |sig| return runner.fail(
1322 "generated executable {q} terminated with signal {t}",
1323 .{ bin_path, sig },
1324 ),
1325 .stopped => |sig| return runner.fail(
1326 "generated executable {q} stopped with signal {t}",
1327 .{ bin_path, sig },
1328 ),
1329 .unknown => return runner.fail(
1330 "generated executable {q} terminated unexpectedly",
1331 .{bin_path},
1332 ),
1333 }
1334 if (!use_executor and result.stderr.len > 0) {
1335 std.log.err("generated executable {q} had unexpected stderr:\n{s}", .{
1336 bin_path, result.stderr,
1337 });
1338 }
1339 },
1340 .lldb => {},
1341 }
1342 }
1343
1344 fn compileC(parent_comp: *Compiler, src_dir: std.Io.Dir, c_path: []const u8) FailError![]const u8 {
1345 const runner = parent_comp.runner;
1346 const arena = runner.arena.allocator();
1347 var compiler: ?Compiler = null;
1348 var args_r: std.Io.Reader = .fixed(c_path);
1349 parent_comp.runner.spawnCompiler(&compiler, src_dir, parent_comp.output_mode, &args_r, .{
1350 .triple = parent_comp.target.triple,
1351 .resolved = resolved: {
1352 var resolved = parent_comp.target.resolved;
1353 resolved.ofmt = .default(resolved.os.tag, resolved.cpu.arch);
1354 break :resolved resolved;
1355 },
1356 .mode = .whole,
1357 .backend = null,
1358 }) catch |err| switch (err) {
1359 error.Canceled, error.OutOfMemory, error.AlreadyReported => |e| return e,
1360 error.ReadFailed => unreachable, // .fixed
1361 };
1362 const comp = &compiler.?;
1363 comp.client.serveBodylessMessage(.update) catch |err| switch (err) {
1364 error.WriteFailed => switch (comp.fw.err.?) {
1365 error.Canceled => |e| return e,
1366 else => |e| return runner.fail("unable to send message: {t}", .{e}),
1367 },
1368 };
1369 comp.state = .update;
1370 var out_path: ?[]const u8 = null;
1371 while (try comp.receiveMessage()) |message| switch (message.tag) {
1372 else => {}, // Ignore other messages,
1373 .config => {
1374 var body_r: std.Io.Reader = .fixed(message.body);
1375 comp.config =
1376 body_r.takeStruct(std.zig.Server.Message.Config, .little) catch unreachable;
1377 },
1378 .emit_digest => {
1379 var body_r: std.Io.Reader = .fixed(message.body);
1380 _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable;
1381 const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch unreachable;
1382
1383 const out_dir = ".zig-cache" ++ std.Io.Dir.path.sep_str ++
1384 "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*);
1385 out_path = try std.Io.Dir.path.join(arena, &.{ out_dir, comp.out_name });
1386 },
1387 .error_bundle => {
1388 const eb = std.zig.Server.allocErrorBundle(
1389 runner.arena.allocator(),
1390 message.body,
1391 ) catch |err| switch (err) {
1392 error.OutOfMemory => |e| return e,
1393 error.EndOfStream => return runner.fail("error bundle truncated", .{}),
1394 };
1395 if (eb.errorMessageCount() > 0) {
1396 try runner.eb_wip.addBundleAsRoots(eb);
1397 return runner.fail("unexpected compile errors", .{});
1398 }
1399 comp.state = .idle;
1400 break;
1401 },
1402 };
1403 try comp.exit();
1404 return out_path.?;
1405 }
1406
1407 fn exit(comp: *Compiler) FailError!void {
1408 const runner = comp.runner;
1409 comp.client.serveBodylessMessage(.exit) catch |err| switch (err) {
1410 error.WriteFailed => switch (comp.fw.err.?) {
1411 error.Canceled => |e| return e,
1412 error.BrokenPipe => {},
1413 else => |e| return comp.runner.fail("unable to send exit: {t}", .{e}),
1414 },
1415 };
1416 comp.child.stdin.?.close(runner.io);
1417 comp.child.stdin = null;
1418 while (try comp.receiveMessage()) |_| {}
1419 if (comp.client.in.bufferedLen() > 0) return runner.fail("unable to receive messages", .{});
1420 const term = comp.child.wait(runner.io) catch |err|
1421 return runner.fail("child process failed: {t}", .{err});
1422 comp.prog_node.end();
1423 switch (term) {
1424 .exited => |code| if (code != 0)
1425 return runner.fail("compiler failed with code {d}", .{code}),
1426 .signal => |sig| return runner.fail("compiler terminated with signal {t}", .{sig}),
1427 .stopped => |sig| return runner.fail("compiler stopped unexpectedly with signal {t}", .{
1428 sig,
1429 }),
1430 .unknown => return runner.fail("compiler terminated unexpectedly", .{}),
1431 }
1432 }
1433};
1434
1435const ClientArgs = struct {
1436 test_file: std.Io.File,
1437 test_kind: std.Io.File.Kind,
1438 zig_exe: std.Io.File,
1439 lib_dir: std.Io.Dir,
1440 src_dir: std.Io.Dir,
1441 targets: std.ArrayList([]const u8),
1442 keep_src: bool,
1443 libc_runtimes_dir: ?std.Io.Dir,
1444 enable_darling: bool,
1445 enable_qemu: bool,
1446 enable_rosetta: bool,
1447 enable_wine: bool,
1448 enable_wasmtime: bool,
1449 quiet: bool,
1450};
1451pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelable)!u8 {
1452 const fatal = struct {
1453 fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
1454 std.log.err(fmt, args);
1455 std.process.exit(1);
1456 }
1457 }.fatal;
1458 const arena = init.arena.allocator();
1459
1460 var test_path_arg: ?[]const u8 = null;
1461 var zig_path_arg: ?[]const u8 = null;
1462 var lib_path_arg: ?[]const u8 = null;
1463 var libc_runtimes_path_arg: ?[]const u8 = null;
1464 var args: ClientArgs = .{
1465 .test_file = undefined,
1466 .test_kind = undefined,
1467 .zig_exe = undefined,
1468 .lib_dir = undefined,
1469 .src_dir = undefined,
1470 .targets = .empty,
1471 .keep_src = false,
1472 .libc_runtimes_dir = null,
1473 .enable_darling = false,
1474 .enable_qemu = false,
1475 .enable_rosetta = false,
1476 .enable_wine = false,
1477 .enable_wasmtime = false,
1478 .quiet = false,
1479 };
1480 defer args.targets.deinit(init.gpa);
1481
1482 var arg_it = try init.minimal.args.iterateAllocator(arena);
1483 const self = arg_it.next().?;
1484 while (arg_it.next()) |arg| {
1485 if (std.mem.eql(u8, arg, "--listen=-")) {
1486 var stdin_buffer: [512]u8 = undefined;
1487 var stdin: std.Io.File.Reader = .initStreaming(.stdin(), init.io, &stdin_buffer);
1488 var stdout_buffer: [512]u8 = undefined;
1489 var stdout: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer);
1490 var runner: Runner = .{
1491 .gpa = init.gpa,
1492 .arena = init.arena,
1493 .io = init.io,
1494 .prog_node = std.Progress.start(init.io, .{}),
1495 .args = .{
1496 .@"test" = null,
1497 .manifest_file = null,
1498 .zig_exe = null,
1499 .lib_dir = null,
1500 .src_dir = null,
1501 .targets = .empty,
1502 .target_bytes = .empty,
1503 .libc_runtimes_dir = null,
1504 .enable_darling = false,
1505 .enable_qemu = false,
1506 .enable_rosetta = false,
1507 .enable_wasmtime = false,
1508 .enable_wine = false,
1509 .quiet = false,
1510 },
1511 .host = std.zig.system.resolveTargetQuery(init.io, .{}) catch |err| switch (err) {
1512 error.Canceled => |e| return e,
1513 else => |e| fatal("unable to resolve host target: {t}", .{e}),
1514 },
1515 .server = .{
1516 .in = &stdin.interface,
1517 .out = &stdout.interface,
1518 },
1519 .eb_wip = try .init(init.gpa),
1520 };
1521 defer runner.deinit();
1522 switch (runner.runServer()) {
1523 error.Canceled, error.OutOfMemory => |e| return e,
1524 error.ServerReadFailed => switch (stdin.err.?) {
1525 error.Canceled => |e| return e,
1526 else => |e| fatal("unable to receive message: {t}", .{e}),
1527 },
1528 error.ServerWriteFailed => switch (stdout.err.?) {
1529 error.Canceled => |e| return e,
1530 else => |e| fatal("unable to send message: {t}", .{e}),
1531 },
1532 error.ServerEndOfStream => fatal("no more messages", .{}),
1533 error.AlreadyReported => {
1534 var eb = try runner.eb_wip.toOwnedBundle("");
1535 defer eb.deinit(init.gpa);
1536 eb.renderToStderr(runner.io, .{}, .auto) catch |err| switch (err) {
1537 error.Canceled => |e| return e,
1538 else => {},
1539 };
1540 std.process.exit(1);
1541 },
1542 }
1543 } else if (std.mem.eql(u8, arg, "--zig")) {
1544 zig_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
1545 } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| {
1546 zig_path_arg = zig_path;
1547 } else if (std.mem.eql(u8, arg, "--lib")) {
1548 lib_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
1549 } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| {
1550 lib_path_arg = lib_path;
1551 } else if (std.mem.eql(u8, arg, "--target")) {
1552 try args.targets.append(
1553 init.gpa,
1554 try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after {q}", .{arg})),
1555 );
1556 } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| {
1557 try args.targets.append(init.gpa, try arena.dupe(u8, target));
1558 } else if (std.mem.eql(u8, arg, "--keep-src")) {
1559 args.keep_src = true;
1560 } else if (std.mem.eql(u8, arg, "--libc-runtimes")) {
1561 libc_runtimes_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
1562 } else if (std.mem.cutPrefix(u8, arg, "--libc-runtimes=")) |libc_runtimes_path| {
1563 libc_runtimes_path_arg = libc_runtimes_path;
1564 } else if (std.mem.eql(u8, arg, "-fdarling")) {
1565 args.enable_darling = true;
1566 } else if (std.mem.eql(u8, arg, "-fqemu")) {
1567 args.enable_qemu = true;
1568 } else if (std.mem.eql(u8, arg, "-frosetta")) {
1569 args.enable_rosetta = true;
1570 } else if (std.mem.eql(u8, arg, "-fwine")) {
1571 args.enable_wine = true;
1572 } else if (std.mem.eql(u8, arg, "-fwasmtime")) {
1573 args.enable_wasmtime = true;
1574 } else if (std.mem.eql(u8, arg, "--quiet")) {
1575 args.quiet = true;
1576 } else {
1577 if (test_path_arg) |_| fatal("unexpected arg {q}", .{arg});
1578 test_path_arg = arg;
1579 }
1580 }
1581
1582 const test_path = test_path_arg orelse fatal("missing \"path/to/test\" arg", .{});
1583 const zig_path = zig_path_arg orelse fatal("missing \"--zig=path/to/zig\" arg", .{});
1584 const lib_path = lib_path_arg orelse fatal("missing \"--lib=path/to/lib\" arg", .{});
1585
1586 const cwd: std.Io.Dir = .cwd();
1587 args.test_file = cwd.openFile(init.io, test_path, .{
1588 .allow_directory = true,
1589 }) catch |err| switch (err) {
1590 error.Canceled => |e| return e,
1591 else => |e| fatal("unable to open {q}: {t}", .{ test_path, e }),
1592 };
1593 defer args.test_file.close(init.io);
1594 args.test_kind = (args.test_file.stat(init.io) catch |err| switch (err) {
1595 error.Canceled => |e| return e,
1596 else => |e| fatal("unable to stat {q}: {t}", .{ test_path, e }),
1597 }).kind;
1598 args.zig_exe = cwd.openFile(init.io, zig_path, .{}) catch |err| switch (err) {
1599 error.Canceled => |e| return e,
1600 else => |e| fatal("unable to open {q}: {t}", .{ zig_path, e }),
1601 };
1602 defer args.zig_exe.close(init.io);
1603 args.lib_dir = cwd.openDir(init.io, lib_path, .{}) catch |err| switch (err) {
1604 error.Canceled => |e| return e,
1605 else => |e| fatal("unable to open {q}: {t}", .{ lib_path, e }),
1606 };
1607 defer args.lib_dir.close(init.io);
1608 if (libc_runtimes_path_arg) |libc_runtimes_path| args.libc_runtimes_dir = cwd.openDir(
1609 init.io,
1610 libc_runtimes_path,
1611 .{},
1612 ) catch |err| switch (err) {
1613 error.Canceled => |e| return e,
1614 else => |e| fatal("unable to open {q}: {t}", .{ lib_path, e }),
1615 };
1616 defer if (args.libc_runtimes_dir) |libc_runtimes_dir| libc_runtimes_dir.close(init.io);
1617
1618 const src_dir_path = "src_" ++ std.fmt.hex(rand_int: {
1619 var rand_int: u64 = undefined;
1620 init.io.random(@ptrCast(&rand_int));
1621 break :rand_int rand_int;
1622 });
1623 args.src_dir = cwd.createDirPathOpen(init.io, src_dir_path, .{}) catch |err| switch (err) {
1624 error.Canceled => |e| return e,
1625 else => |e| fatal("unable to create {q}: {t}", .{ src_dir_path, e }),
1626 };
1627 defer {
1628 args.src_dir.close(init.io);
1629 if (!args.keep_src) cwd.deleteTree(init.io, src_dir_path) catch |err| {
1630 std.log.warn("unable to delete tree {q}: {t}", .{ src_dir_path, err });
1631 };
1632 }
1633
1634 var child = std.process.spawn(init.io, .{
1635 .argv = &.{ self, "--listen=-" },
1636 .stdin = .pipe,
1637 .stdout = .pipe,
1638 .inherit_dirs = &.{ args.lib_dir, args.src_dir },
1639 .inherit_files = &.{ args.zig_exe, args.test_file },
1640 }) catch |err| switch (err) {
1641 error.Canceled, error.OutOfMemory => |e| return e,
1642 else => |e| fatal("unable to spawn runner: {t}", .{e}),
1643 };
1644
1645 var child_reader_buffer: [512]u8 = undefined;
1646 var child_reader = child.stdout.?.readerStreaming(init.io, &child_reader_buffer);
1647 var child_writer_buffer: [512]u8 = undefined;
1648 var child_writer = child.stdin.?.writerStreaming(init.io, &child_writer_buffer);
1649 var client: std.zig.Client = .{
1650 .in = &child_reader.interface,
1651 .out = &child_writer.interface,
1652 };
1653 const success = runClient(init.gpa, arena, init.io, &client, &args) catch |err| switch (err) {
1654 error.OutOfMemory => |e| return e,
1655 error.ReadFailed => switch (child_reader.err.?) {
1656 error.Canceled => |e| return e,
1657 else => |e| fatal("unable to receive message: {t}", .{e}),
1658 },
1659 error.WriteFailed => switch (child_writer.err.?) {
1660 error.Canceled => |e| return e,
1661 else => |e| fatal("unable to send message: {t}", .{e}),
1662 },
1663 error.EndOfStream => fatal("no more messages", .{}),
1664 };
1665 _ = child.wait(init.io) catch |err| switch (err) {
1666 error.Canceled => |e| return e,
1667 else => |e| fatal("unable to wait for runner: {t}", .{e}),
1668 };
1669 return if (success) 0 else 1;
1670}
1671fn runClient(
1672 gpa: std.mem.Allocator,
1673 arena: std.mem.Allocator,
1674 io: std.Io,
1675 client: *std.zig.Client,
1676 args: *const ClientArgs,
1677) (std.mem.Allocator.Error || std.Io.Reader.Error || std.Io.Writer.Error)!bool {
1678 var stdout_buffer: [512]u8 = undefined;
1679 var stdout_fw: std.Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buffer);
1680 const stdout = &stdout_fw.interface;
1681 var stderr_buffer: [512]u8 = undefined;
1682 var stderr_fw: std.Io.File.Writer = .initStreaming(.stderr(), io, &stderr_buffer);
1683 const stderr = &stderr_fw.interface;
1684
1685 var argv: std.ArrayList(u8) = .empty;
1686 defer argv.deinit(gpa);
1687 const Arg = std.zig.Client.Message.Arg;
1688
1689 try argv.append(gpa, @backingInt(@as(Arg, switch (args.test_kind) {
1690 else => unreachable,
1691 .file => .input_file,
1692 .directory => .input_dir,
1693 })));
1694 try argv.appendSlice(gpa, @ptrCast(&args.test_file.handle));
1695
1696 try argv.append(gpa, @backingInt(Arg.prefix));
1697 try argv.appendSlice(gpa, "--zig=");
1698 try argv.append(gpa, 0);
1699 try argv.append(gpa, @backingInt(Arg.input_file));
1700 try argv.appendSlice(gpa, @ptrCast(&args.zig_exe.handle));
1701
1702 try argv.append(gpa, @backingInt(Arg.prefix));
1703 try argv.appendSlice(gpa, "--lib=");
1704 try argv.append(gpa, 0);
1705 try argv.append(gpa, @backingInt(Arg.input_dir));
1706 try argv.appendSlice(gpa, @ptrCast(&args.lib_dir.handle));
1707
1708 try argv.append(gpa, @backingInt(Arg.prefix));
1709 try argv.appendSlice(gpa, "--src=");
1710 try argv.append(gpa, 0);
1711 try argv.append(gpa, @backingInt(Arg.output_dir));
1712 try argv.appendSlice(gpa, @ptrCast(&args.src_dir.handle));
1713
1714 for (args.targets.items) |target| {
1715 try argv.append(gpa, @backingInt(Arg.string));
1716 try argv.appendSlice(gpa, "--target");
1717 try argv.append(gpa, 0);
1718
1719 try argv.append(gpa, @backingInt(Arg.string));
1720 try argv.appendSlice(gpa, target);
1721 try argv.append(gpa, 0);
1722 }
1723
1724 if (args.libc_runtimes_dir) |libc_runtimes_dir| {
1725 try argv.append(gpa, @backingInt(Arg.prefix));
1726 try argv.appendSlice(gpa, "--libc-runtimes=");
1727 try argv.append(gpa, 0);
1728 try argv.append(gpa, @backingInt(Arg.input_dir));
1729 try argv.appendSlice(gpa, @ptrCast(&libc_runtimes_dir.handle));
1730 }
1731 if (args.enable_darling) {
1732 try argv.append(gpa, @backingInt(Arg.string));
1733 try argv.appendSlice(gpa, "-fdarling");
1734 try argv.append(gpa, 0);
1735 }
1736 if (args.enable_qemu) {
1737 try argv.append(gpa, @backingInt(Arg.string));
1738 try argv.appendSlice(gpa, "-fqemu");
1739 try argv.append(gpa, 0);
1740 }
1741 if (args.enable_rosetta) {
1742 try argv.append(gpa, @backingInt(Arg.string));
1743 try argv.appendSlice(gpa, "-frosetta");
1744 try argv.append(gpa, 0);
1745 }
1746 if (args.enable_wine) {
1747 try argv.append(gpa, @backingInt(Arg.string));
1748 try argv.appendSlice(gpa, "-fwine");
1749 try argv.append(gpa, 0);
1750 }
1751 if (args.enable_wasmtime) {
1752 try argv.append(gpa, @backingInt(Arg.string));
1753 try argv.appendSlice(gpa, "-fwasmtime");
1754 try argv.append(gpa, 0);
1755 }
1756
1757 if (args.quiet) {
1758 try argv.append(gpa, @backingInt(Arg.string));
1759 try argv.appendSlice(gpa, "--quiet");
1760 try argv.append(gpa, 0);
1761 }
1762
1763 try client.serveMessageHeader(.{
1764 .tag = .args,
1765 .bytes_len = @intCast(argv.items.len),
1766 });
1767 try client.out.writeAll(argv.items);
1768
1769 var success = true;
1770 var metadata: struct {
1771 index: u32,
1772 len: u32,
1773 names: []u32,
1774 expected_panic_msgs: []u32,
1775 string_bytes: []u8,
1776
1777 fn name(m: *@This()) []const u8 {
1778 return std.mem.sliceTo(m.string_bytes[m.names[m.index - 1]..], 0);
1779 }
1780
1781 fn expectedPanicMsg(m: *@This()) ?[:0]const u8 {
1782 return switch (m.expected_panic_msgs[m.index - 1]) {
1783 0 => null,
1784 else => |offset| m.string_bytes[offset..std.mem.findScalarPos(
1785 u8,
1786 m.string_bytes,
1787 offset,
1788 0,
1789 ).? :0],
1790 };
1791 }
1792
1793 fn next(m: *@This()) ?u32 {
1794 if (m.len - m.index == 0) return null;
1795 defer m.index += 1;
1796 return m.index;
1797 }
1798 } = .{ .index = 0, .len = 0, .names = &.{}, .expected_panic_msgs = &.{}, .string_bytes = &.{} };
1799 try client.serveBodylessMessage(.query_test_metadata);
1800 while (true) {
1801 const hdr = try client.receiveMessage();
1802 switch (hdr.tag) {
1803 else => try client.in.discardAll(hdr.bytes_len),
1804 .zig_version => {
1805 const actual_version = try client.in.take(hdr.bytes_len);
1806 if (!std.mem.eql(u8, zig_version_string, actual_version)) {
1807 try stderr.print(
1808 "error: zig version mismatch compiler test runner vs compiler: {q} vs {q}\n",
1809 .{ zig_version_string, actual_version },
1810 );
1811 try stderr.flush();
1812 std.process.exit(1);
1813 }
1814 },
1815 .error_bundle => {
1816 const body = try arena.alloc(u8, hdr.bytes_len);
1817 try client.in.readSliceAll(body);
1818 const eb = std.zig.Server.allocErrorBundle(arena, body) catch |err| switch (err) {
1819 error.OutOfMemory => |e| return e,
1820 error.EndOfStream => {
1821 try stderr.writeAll("error bundle truncated\n");
1822 try stderr.flush();
1823 std.process.exit(1);
1824 },
1825 };
1826 success = false;
1827 eb.renderToStderr(io, .{}, .auto) catch {};
1828 },
1829 .test_metadata => {
1830 const tm_hdr =
1831 try client.in.takeStruct(std.zig.Server.Message.TestMetadata, .little);
1832 metadata = .{
1833 .index = 0,
1834 .len = tm_hdr.tests_len,
1835 .names = try arena.alloc(u32, tm_hdr.tests_len),
1836 .expected_panic_msgs = try arena.alloc(u32, tm_hdr.tests_len),
1837 .string_bytes = try arena.alloc(u8, tm_hdr.string_bytes_len),
1838 };
1839 try client.in.readSliceEndian(u32, metadata.names, .little);
1840 try client.in.readSliceEndian(u32, metadata.expected_panic_msgs, .little);
1841 try client.in.readSliceAll(metadata.string_bytes);
1842 try client.serveRunTest(metadata.next() orelse break);
1843 },
1844 .test_results => {
1845 const tr = try client.in.takeStruct(std.zig.Server.Message.TestResults, .little);
1846 switch (tr.flags.status) {
1847 .pass, .skip => {},
1848 .fail => success = false,
1849 }
1850 try stdout.print("[{t}] {s}\n", .{ tr.flags.status, metadata.name() });
1851 try stdout.flush();
1852 try client.serveRunTest(metadata.next() orelse break);
1853 },
1854 }
1855 }
1856 try client.serveBodylessMessage(.exit);
1857 return success;
1858}
test/tests.zig+53-57
......@@ -2214,9 +2214,9 @@ const IncrementalTarget = struct {
22142214
22152215/// These are passed to `incr-check` as `<target>-<backend>` strings.
22162216///
2217/// If only one specific test is failing on a target, instead of entirely disabling the target here,
2218/// you can skip the target for that specific test only by adding a line like this to the manifest:
2219/// #skip_target=x86_64-linux-selfhosted
2217/// If only one specific test is failing on a target, instead of entirely disabling the target here, you
2218/// can skip the target for that specific test only by adding a line like this to top of the manifest:
2219/// #skip x86_64-linux-selfhosted
22202220const incremental_targets = &[_]IncrementalTarget{
22212221 // Avoid adding more CBE or LLVM targets without good reason: they're a lot slower than others
22222222 // to run due to the output (C source code or LLVM IR) being built non-incrementally (by Clang
......@@ -2502,7 +2502,7 @@ pub fn addStandaloneTests(
25022502 .simple_skip_release_small = mem.findScalar(OptimizeMode, optimize_modes, .small) == null,
25032503 });
25042504 const test_cases_dep_step = test_cases_dep.builder.default_step;
2505 test_cases_dep_step.name = b.graph.dupeString(test_cases_dep_name);
2505 test_cases_dep_step.name = test_cases_dep_name;
25062506 step.dependOn(test_cases_dep.builder.default_step);
25072507 return step;
25082508}
......@@ -3344,41 +3344,41 @@ const IncrementalTestOptions = struct {
33443344
33453345pub fn addIncrementalTests(
33463346 b: *std.Build,
3347 test_step: *Step,
3347 runner: *std.Build.Step.Compile,
33483348 options: IncrementalTestOptions,
3349) !void {
3350 const io = b.graph.io;
3351
3352 const incr_check = b.addExecutable(.{
3353 .name = "incr-check",
3354 .root_module = b.createModule(.{
3355 .root_source_file = b.path("tools/incr-check.zig"),
3356 .target = b.graph.host,
3357 .optimize = .debug,
3358 }),
3359 });
3360
3361 b.dependOnDirectoryContents(b.path("test/incremental"));
3362
3363 var dir = try b.root.openDir(io, "test/incremental", .{ .iterate = true });
3364 defer dir.close(io);
3349) !*Step {
3350 const tests_step = b.step("test-incremental", "Run the new incremental compilation test cases");
3351
3352 const tests_path = b.path("test/incremental");
3353 b.dependOnDirectoryContents(tests_path);
3354
3355 var tests_dir = try b.root.openDir(b.graph.io, "test/incremental", .{ .iterate = true });
3356 defer tests_dir.close(b.graph.io);
3357 var test_it = tests_dir.iterate();
3358 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;
33653362
3366 var it = try dir.walk(b.graph.arena);
3367 while (try it.next(io)) |entry| {
3368 if (std.mem.endsWith(u8, entry.basename, ".swp")) continue;
3363 const test_path = tests_path.path(b, @"test".name);
3364 switch (@"test".kind) {
3365 else => continue,
3366 .file => if (isEditorFileName(@"test".name)) continue,
3367 .directory => {},
3368 }
33693369
3370 for (options.test_filters) |test_filter| {
3371 if (std.mem.find(u8, entry.path, test_filter)) |_| break;
3372 } else if (options.test_filters.len > 0) continue;
3370 const run = b.addRunArtifact(runner);
3371 run.setName(@"test".name);
3372 run.enableProtocolMode();
33733373
3374 switch (entry.kind) {
3375 .file => {},
3376 .directory => {
3377 b.dependOnDirectoryContents(b.path(b.pathJoin(&.{ "test", "incremental", entry.path })));
3378 },
3374 switch (@"test".kind) {
33793375 else => continue,
3376 .file => run.addFileArg(test_path),
3377 .directory => run.addDirectoryArg(test_path),
33803378 }
3381 b.dependOnFileContents(b.path(b.pathJoin(&.{ "test", "incremental", entry.path })));
3379 run.addPrefixedFileArg("--zig=", .zig_exe);
3380 run.addPrefixedDirectoryArg("--lib=", .zig_lib);
3381 _ = run.addPrefixedOutputDirectoryArg("--src=", "src");
33823382
33833383 for (incremental_targets) |test_target| {
33843384 const resolved_target = b.resolveTargetQuery(test_target.target);
......@@ -3399,40 +3399,36 @@ pub fn addIncrementalTests(
33993399
34003400 if (options.skip_llvm and test_target.backend == .llvm) continue;
34013401
3402 const target_str = b.fmt("{s}-{t}", .{
3402 const target_str = b.fmt("{s}-incremental-{t}", .{
34033403 resolved_target.query.zigTriple(b.allocator) catch @panic("OOM"),
34043404 test_target.backend,
34053405 });
34063406
3407 if (options.test_target_filters.len > 0) {
3408 for (options.test_target_filters) |filter| {
3409 if (std.mem.find(u8, target_str, filter) != null) break;
3410 } else continue;
3411 }
3412
3413 const run = b.addRunArtifact(incr_check);
3414 run.setName(b.fmt("incr-check {s} '{s}'", .{ target_str, entry.basename }));
3415
3416 run.addArg(b.graph.zig_exe);
3417 run.addFileArg(b.path("test/incremental/").path(b, entry.path));
3418
3419 run.addArg("--zig-lib-dir");
3420 run.addDirectoryArg(.zig_lib);
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;
34213410
34223411 run.addArgs(&.{ "--target", target_str });
3412 }
34233413
3424 run.addArg("--quiet"); // don't fill stderr telling us about skipped tests etc
3414 run.addPrefixedDirectoryArg("--libc-runtimes=", .{ .relative = .{ .base = .libc_runtimes } });
3415 run.addThirdPartyEnabledArgDarling(.{ .enabled = "-fdarling" });
3416 run.addThirdPartyEnabledArgQemu(.{ .enabled = "-fqemu" });
3417 run.addThirdPartyEnabledArgRosetta(.{ .enabled = "-frosetta" });
3418 run.addThirdPartyEnabledArgWasmtime(.{ .enabled = "-fwasmtime" });
3419 run.addThirdPartyEnabledArgWine(.{ .enabled = "-fwine" });
34253420
3426 run.addThirdPartyEnabledArgDarling(.{ .enabled = "-fdarling" });
3427 run.addThirdPartyEnabledArgQemu(.{ .enabled = "-fqemu" });
3428 run.addThirdPartyEnabledArgRosetta(.{ .enabled = "-frosetta" });
3429 run.addThirdPartyEnabledArgWasmtime(.{ .enabled = "-fwasmtime" });
3430 run.addThirdPartyEnabledArgWine(.{ .enabled = "-fwine" });
3421 run.addArg("--quiet"); // don't fill stderr telling us about skipped tests etc
34313422
3432 run.addCheck(.{ .expect_term = .{ .exited = 0 } });
3433 test_step.dependOn(&run.step);
3434 }
3423 tests_step.dependOn(&run.step);
34353424 }
3425
3426 return tests_step;
3427}
3428
3429fn isEditorFileName(name: []const u8) bool {
3430 return (std.mem.startsWith(u8, name, "#") and std.mem.endsWith(u8, name, "#")) or
3431 std.mem.endsWith(u8, name, "~") or std.mem.endsWith(u8, name, ".swp");
34363432}
34373433
34383434pub fn addLlvmIrTests(b: *std.Build, options: LlvmIrContext.Options) ?*Step {
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 => {},
tools/gen_spirv_spec.zig+1-2
......@@ -86,8 +86,7 @@ pub fn main(init: std.process.Init) !void {
8686
8787 var zir = try std.zig.AstGen.generate(arena, tree);
8888 if (zir.hasCompileErrors()) {
89 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
90 try wip_errors.init(arena);
89 var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena);
9190 defer wip_errors.deinit();
9291 try wip_errors.addZirErrorMessages(zir, tree, output, "");
9392 var error_bundle = try wip_errors.toOwnedBundle("");
tools/incr-check.zig deleted-1032
......@@ -1,1032 +0,0 @@
1const std = @import("std");
2const Io = std.Io;
3const Dir = std.Io.Dir;
4const Allocator = std.mem.Allocator;
5const Cache = std.Build.Cache;
6
7const usage =
8 \\Usage: incr-check <zig binary path> <input file> [options]
9 \\Options:
10 \\ --target triple-backend
11 \\ --quiet
12 \\ --zig-lib-dir /path/to/zig/lib
13 \\ --zig-cc-binary /path/to/zig
14 \\ -fqemu
15 \\ -fwine
16 \\ -fwasmtime
17 \\Debug Options:
18 \\ --preserve-tmp
19 \\ --debug-log foo
20 \\ --debug-link-snapshot
21;
22
23pub const std_options: std.Options = .{
24 .logFn = logImpl,
25};
26var log_cur_update: ?*const Case.Update = null;
27fn logImpl(
28 comptime level: std.log.Level,
29 comptime scope: @EnumLiteral(),
30 comptime format: []const u8,
31 args: anytype,
32) void {
33 const update = log_cur_update orelse {
34 return std.log.defaultLog(level, scope, format, args);
35 };
36 std.log.defaultLog(
37 level,
38 scope,
39 "['{s}'] " ++ format,
40 .{update.name} ++ args,
41 );
42}
43
44pub fn main(init: std.process.Init) !void {
45 const gpa = init.gpa;
46 const arena = init.arena.allocator();
47 const io = init.io;
48 const environ_map = init.environ_map;
49 const cwd_path = try std.process.currentPathAlloc(io, arena);
50
51 var opt_zig_exe: ?[]const u8 = null;
52 var opt_input_file_name: ?[]const u8 = null;
53 var opt_lib_dir: ?[]const u8 = null;
54 var opt_cc_zig: ?[]const u8 = null;
55 var opt_target: ?struct { std.Target.Query, Backend } = null;
56 var preserve_tmp = false;
57 var enable_qemu: bool = false;
58 var enable_wine: bool = false;
59 var enable_wasmtime: bool = false;
60 var enable_darling: bool = false;
61 var quiet: bool = false;
62
63 var debug_log_args: std.ArrayList([]const u8) = .empty;
64 var debug_link_snapshot = false;
65
66 var arg_it = try init.minimal.args.iterateAllocator(arena);
67 _ = arg_it.skip();
68 while (arg_it.next()) |arg| {
69 if (arg.len > 0 and arg[0] == '-') {
70 if (std.mem.eql(u8, arg, "--zig-lib-dir")) {
71 opt_lib_dir = arg_it.next() orelse badUsage("expected arg after --zig-lib-dir", .{});
72 } else if (std.mem.eql(u8, arg, "--target")) {
73 const str = arg_it.next() orelse badUsage("expected arg after --zig-cc-binary", .{});
74 opt_target = parseTargetQueryAndBackend(str, "");
75 } else if (std.mem.eql(u8, arg, "--quiet")) {
76 quiet = true;
77 } else if (std.mem.eql(u8, arg, "--debug-log")) {
78 try debug_log_args.append(
79 arena,
80 arg_it.next() orelse badUsage("expected arg after --debug-log", .{}),
81 );
82 } else if (std.mem.eql(u8, arg, "--debug-link-snapshot")) {
83 debug_link_snapshot = true;
84 } else if (std.mem.eql(u8, arg, "--preserve-tmp")) {
85 preserve_tmp = true;
86 } else if (std.mem.eql(u8, arg, "-fqemu")) {
87 enable_qemu = true;
88 } else if (std.mem.eql(u8, arg, "-fwine")) {
89 enable_wine = true;
90 } else if (std.mem.eql(u8, arg, "-fwasmtime")) {
91 enable_wasmtime = true;
92 } else if (std.mem.eql(u8, arg, "-fdarling")) {
93 enable_darling = true;
94 } else if (std.mem.eql(u8, arg, "--zig-cc-binary")) {
95 opt_cc_zig = arg_it.next() orelse badUsage("expected arg after --zig-cc-binary", .{});
96 } else {
97 badUsage("unknown option '{s}'", .{arg});
98 }
99 continue;
100 }
101 if (opt_zig_exe == null) {
102 opt_zig_exe = arg;
103 } else if (opt_input_file_name == null) {
104 opt_input_file_name = arg;
105 } else {
106 badUsage("unknown argument '{s}'\n{s}", .{ arg, usage });
107 }
108 }
109 const zig_exe = opt_zig_exe orelse badUsage("missing path to zig", .{});
110 const input_file_name = opt_input_file_name orelse badUsage("missing input file", .{});
111 const target_query, const backend = opt_target orelse badUsage("missing required option '--target'", .{});
112
113 if (backend == .cbe and opt_lib_dir == null) {
114 std.process.fatal("'--zig-lib-dir' required when using backend 'cbe'", .{});
115 }
116
117 const input_file_bytes = try Dir.cwd().readFileAlloc(io, input_file_name, arena, .limited(std.math.maxInt(u32)));
118 const case: Case = try .parse(arena, input_file_bytes);
119
120 for (case.skip_targets) |skip| {
121 if (target_query.eql(skip.query) and backend == skip.backend) {
122 if (!quiet) std.log.warn("skipping test because of a 'skip_target' match", .{});
123 return;
124 }
125 }
126
127 const target = try std.zig.system.resolveTargetQuery(io, target_query);
128
129 const prog_node = std.Progress.start(io, .{});
130 defer prog_node.end();
131
132 const rand_int = rand64(io);
133 const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int);
134 var tmp_dir = try Dir.cwd().createDirPathOpen(io, tmp_dir_path, .{});
135 defer {
136 tmp_dir.close(io);
137 if (!preserve_tmp) {
138 Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| {
139 std.log.warn("failed to delete tree '{s}': {t}", .{ tmp_dir_path, err });
140 };
141 }
142 }
143
144 // Convert paths to be relative to the cwd of the subprocess.
145 const resolved_zig_exe = try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, zig_exe);
146 const opt_resolved_lib_dir = if (opt_lib_dir) |lib_dir|
147 try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, lib_dir)
148 else
149 null;
150
151 const host = try std.zig.system.resolveTargetQuery(io, .{});
152
153 var child_args: std.ArrayList([]const u8) = .empty;
154 try child_args.appendSlice(arena, &.{
155 resolved_zig_exe, "build-exe",
156 "-fincremental", "-fno-ubsan-rt",
157 "-target", try target_query.zigTriple(arena),
158 "--cache-dir", ".local-cache",
159 "--global-cache-dir", ".global-cache",
160 });
161
162 if (opt_resolved_lib_dir) |resolved_lib_dir| {
163 try child_args.appendSlice(arena, &.{ "--zig-lib-dir", resolved_lib_dir });
164 }
165 switch (backend) {
166 .sema => try child_args.append(arena, "-fno-emit-bin"),
167 .selfhosted => try child_args.appendSlice(arena, &.{ "-fno-llvm", "-fno-lld" }),
168 .llvm => try child_args.appendSlice(arena, &.{ "-fllvm", "-flld" }),
169 .cbe => try child_args.appendSlice(arena, &.{ "-ofmt=c", "-lc" }),
170 }
171 for (debug_log_args.items) |arg| {
172 try child_args.appendSlice(arena, &.{ "--debug-log", arg });
173 }
174 if (debug_link_snapshot) {
175 try child_args.append(arena, "--debug-link-snapshot");
176 }
177 for (case.modules) |mod| {
178 try child_args.appendSlice(arena, &.{ "--dep", mod.name });
179 }
180 try child_args.append(arena, try std.fmt.allocPrint(arena, "-Mroot={s}", .{case.root_source_file}));
181 for (case.modules) |mod| {
182 try child_args.append(arena, try std.fmt.allocPrint(arena, "-M{s}={s}", .{ mod.name, mod.file }));
183 }
184 try child_args.append(arena, "--listen=-");
185
186 const zig_prog_node = prog_node.start("zig", 0);
187 defer zig_prog_node.end();
188
189 var cc_child_args: std.ArrayList([]const u8) = .empty;
190 if (backend == .cbe) {
191 const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe|
192 try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, cc_zig_exe)
193 else
194 resolved_zig_exe;
195
196 try cc_child_args.appendSlice(arena, &.{
197 resolved_cc_zig_exe, "build-exe",
198 "-target", try target_query.zigTriple(arena),
199 "--cache-dir", ".local-cache",
200 "--global-cache-dir", ".global-cache",
201 "-I", opt_resolved_lib_dir.?, // verified earlier
202 "-lc",
203 });
204 }
205
206 const allow_compiler_stderr = debug_log_args.items.len != 0;
207
208 if (allow_compiler_stderr) {
209 const cmd: std.zig.SubprocessCommand = .{
210 .argv = child_args.items,
211 .cwd = tmp_dir_path,
212 };
213 std.log.scoped(.spawn).info("{f}", .{cmd});
214 }
215
216 var child = try std.process.spawn(io, .{
217 .argv = child_args.items,
218 .stdin = .pipe,
219 .stdout = .pipe,
220 .stderr = .pipe,
221 .progress_node = zig_prog_node,
222 .cwd = .{ .path = tmp_dir_path },
223 });
224 defer child.kill(io);
225
226 const updates_prog_node = prog_node.start("updates", case.updates.len);
227 defer updates_prog_node.end();
228
229 var eval: Eval = .{
230 .arena = arena,
231 .io = io,
232 .case = case,
233 .host = host,
234 .target = target,
235 .backend = backend,
236 .tmp_dir = tmp_dir,
237 .tmp_dir_path = tmp_dir_path,
238 .child = &child,
239 .allow_compiler_stderr = allow_compiler_stderr,
240 .quiet = quiet,
241 .preserve_tmp_on_fatal = preserve_tmp,
242 .cc_child_args = &cc_child_args,
243 .enable_qemu = enable_qemu,
244 .enable_wine = enable_wine,
245 .enable_wasmtime = enable_wasmtime,
246 .enable_darling = enable_darling,
247 };
248
249 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
250 var multi_reader: Io.File.MultiReader = undefined;
251 multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? });
252 defer multi_reader.deinit();
253
254 var update_mtime = Io.Clock.real.now(io);
255 for (case.updates) |update| {
256 var update_prog_node = updates_prog_node.start(update.name, 0);
257 defer update_prog_node.end();
258
259 if (debug_log_args.items.len != 0) {
260 // Print a line separating the debug logs from the compiler in the stderr output.
261 std.log.scoped(.status).info("update: '{s}'", .{update.name});
262 }
263
264 log_cur_update = &update;
265 defer log_cur_update = null;
266
267 eval.write(update, update_mtime);
268 update_mtime = update_mtime.addDuration(.fromSeconds(5));
269 try eval.requestUpdate();
270 try eval.check(&multi_reader, update, update_prog_node);
271 }
272
273 try eval.end(&multi_reader);
274
275 waitChild(&child, &eval);
276}
277
278const Eval = struct {
279 arena: Allocator,
280 io: Io,
281 case: Case,
282 host: std.Target,
283 target: std.Target,
284 backend: Backend,
285 tmp_dir: Dir,
286 tmp_dir_path: []const u8,
287 child: *std.process.Child,
288 allow_compiler_stderr: bool,
289 quiet: bool,
290 preserve_tmp_on_fatal: bool,
291 /// When `backend == .cbe`, this contains the first few arguments to `zig cc` to build the generated binary.
292 /// The arguments `out.c in.c` must be appended before spawning the subprocess.
293 cc_child_args: *std.ArrayList([]const u8),
294
295 enable_qemu: bool,
296 enable_wine: bool,
297 enable_wasmtime: bool,
298 enable_darling: bool,
299
300 /// Currently this function assumes the previous updates have already been written.
301 fn write(eval: *Eval, update: Case.Update, mtime: Io.Timestamp) void {
302 const io = eval.io;
303 for (update.changes) |full_contents| {
304 var update_file = eval.tmp_dir.createFile(io, full_contents.name, .{}) catch |err| {
305 eval.fatal("failed to create update '{s}': {t}", .{ full_contents.name, err });
306 };
307 defer update_file.close(io);
308 update_file.writeStreamingAll(io, full_contents.bytes) catch |err| {
309 eval.fatal("failed to write update '{s}': {t}", .{ full_contents.name, err });
310 };
311 update_file.setTimestamps(io, .{ .modify_timestamp = .{ .new = mtime } }) catch |err| {
312 eval.fatal("failed to set mtime for '{s}': {t}", .{ full_contents.name, err });
313 };
314 }
315 for (update.deletes) |doomed_name| {
316 eval.tmp_dir.deleteFile(io, doomed_name) catch |err| {
317 eval.fatal("failed to delete '{s}': {t}", .{ doomed_name, err });
318 };
319 }
320 }
321
322 fn check(eval: *Eval, mr: *Io.File.MultiReader, update: Case.Update, prog_node: std.Progress.Node) !void {
323 const arena = eval.arena;
324 const stdout = mr.reader(0);
325 const stderr = mr.reader(1);
326
327 var client: std.zig.Client = .{
328 .in = stdout,
329 .out = undefined,
330 };
331
332 while (true) {
333 const header = client.receiveMessageWithMultiReader(mr, .none) catch |err| switch (err) {
334 error.Timeout => unreachable,
335 error.EndOfStream => break,
336 else => |e| return e,
337 };
338 const body = client.in.take(header.bytes_len) catch unreachable;
339
340 switch (header.tag) {
341 .error_bundle => {
342 const result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body);
343 if (stderr.bufferedLen() > 0) {
344 if (eval.allow_compiler_stderr) {
345 std.log.info("error_bundle stderr:\n{s}", .{stderr.buffered()});
346 } else {
347 eval.fatal("error_bundle unexpected stderr:\n{s}", .{stderr.buffered()});
348 }
349 stderr.tossBuffered();
350 }
351 if (result_error_bundle.errorMessageCount() != 0) {
352 try eval.checkErrorOutcome(update, result_error_bundle);
353 }
354 // This message indicates the end of the update.
355 return;
356 },
357 .emit_digest => {
358 var r: std.Io.Reader = .fixed(body);
359 _ = r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable;
360
361 if (stderr.bufferedLen() > 0) {
362 if (eval.allow_compiler_stderr) {
363 std.log.info("emit_digest stderr:\n{s}", .{stderr.buffered()});
364 } else {
365 eval.fatal("emit_digest unexpected stderr:\n{s}", .{stderr.buffered()});
366 }
367 stderr.tossBuffered();
368 }
369 if (eval.backend == .sema) {
370 try eval.checkSuccessOutcome(update, null, prog_node);
371 continue;
372 }
373
374 const digest = r.takeArray(Cache.bin_digest_len) catch unreachable;
375 const result_dir = ".local-cache" ++ Dir.path.sep_str ++ "o" ++ Dir.path.sep_str ++ Cache.binToHex(digest.*);
376
377 const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{
378 .root_name = "root", // corresponds to the module name "root"
379 .cpu_arch = eval.target.cpu.arch,
380 .os_tag = eval.target.os.tag,
381 .ofmt = eval.target.ofmt,
382 .abi = eval.target.abi,
383 .output_mode = .Exe,
384 });
385 const bin_path = try Dir.path.join(arena, &.{ result_dir, bin_name });
386
387 try eval.checkSuccessOutcome(update, bin_path, prog_node);
388 },
389 else => {
390 // Ignore other messages.
391 },
392 }
393 }
394
395 const buffered_stderr = stderr.buffered();
396 if (buffered_stderr.len > 0) {
397 if (eval.allow_compiler_stderr) {
398 std.log.info("stderr:\n{s}", .{buffered_stderr});
399 } else {
400 eval.fatal("unexpected stderr:\n{s}", .{buffered_stderr});
401 }
402 }
403
404 waitChild(eval.child, eval);
405 eval.fatal("compiler failed to send terminating error_bundle", .{});
406 }
407
408 fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void {
409 const io = eval.io;
410 const expected = switch (update.outcome) {
411 .unknown => return,
412 .compile_errors => |ce| ce,
413 .stdout, .exit_code => {
414 try error_bundle.renderToStderr(io, .{}, .auto);
415 eval.fatal("unexpected compile errors", .{});
416 },
417 };
418
419 var expected_idx: usize = 0;
420
421 for (error_bundle.getMessages()) |err_idx| {
422 if (expected_idx == expected.errors.len) {
423 try error_bundle.renderToStderr(io, .{}, .auto);
424 eval.fatal("more errors than expected", .{});
425 }
426 try eval.checkOneError(error_bundle, expected.errors[expected_idx], false, err_idx);
427 expected_idx += 1;
428
429 for (error_bundle.getNotes(err_idx)) |note_idx| {
430 if (expected_idx == expected.errors.len) {
431 try error_bundle.renderToStderr(io, .{}, .auto);
432 eval.fatal("more error notes than expected", .{});
433 }
434 try eval.checkOneError(error_bundle, expected.errors[expected_idx], true, note_idx);
435 expected_idx += 1;
436 }
437 }
438
439 if (!std.mem.eql(u8, error_bundle.getCompileLogOutput(), expected.compile_log_output)) {
440 try error_bundle.renderToStderr(io, .{}, .auto);
441 eval.fatal("unexpected compile log output", .{});
442 }
443 }
444
445 fn checkOneError(
446 eval: *Eval,
447 eb: std.zig.ErrorBundle,
448 expected: Case.ExpectedError,
449 is_note: bool,
450 err_idx: std.zig.ErrorBundle.MessageIndex,
451 ) Allocator.Error!void {
452 const io = eval.io;
453 const err = eb.getErrorMessage(err_idx);
454 if (err.count != 1) @panic("TODO error message with count>1");
455 const msg = eb.nullTerminatedString(err.msg);
456 const matches = matches: {
457 if (expected.is_note != is_note) break :matches false;
458 if (!std.mem.eql(u8, expected.msg, msg)) break :matches false;
459 if (err.src_loc == .none) {
460 break :matches expected.src == null;
461 }
462 const expected_src = expected.src orelse break :matches false;
463 const src = eb.getSourceLocation(err.src_loc);
464 const raw_filename = eb.nullTerminatedString(src.src_path);
465 // We need to replace backslashes for consistency between platforms.
466 const filename = name: {
467 if (std.mem.findScalar(u8, raw_filename, '\\') == null) break :name raw_filename;
468 const copied = try eval.arena.dupe(u8, raw_filename);
469 std.mem.replaceScalar(u8, copied, '\\', '/');
470 break :name copied;
471 };
472 if (!std.mem.eql(u8, expected_src.filename, filename)) break :matches false;
473 if (expected_src.line != src.line + 1) break :matches false;
474 if (expected_src.column != src.column + 1) break :matches false;
475 break :matches true;
476 };
477 if (!matches) {
478 eb.renderToStderr(io, .{}, .auto) catch {};
479 eval.fatal("compile error did not match expected error", .{});
480 }
481 }
482
483 fn checkSuccessOutcome(eval: *Eval, update: Case.Update, opt_emitted_path: ?[]const u8, prog_node: std.Progress.Node) !void {
484 switch (update.outcome) {
485 .unknown => return,
486 .compile_errors => eval.fatal("expected compile errors but compilation incorrectly succeeded", .{}),
487 .stdout, .exit_code => {},
488 }
489 const emitted_path = opt_emitted_path orelse {
490 std.debug.assert(eval.backend == .sema);
491 return;
492 };
493 const io = eval.io;
494
495 const binary_path = switch (eval.backend) {
496 .sema => unreachable,
497 .selfhosted, .llvm => emitted_path,
498 .cbe => bin: {
499 const rand_int = rand64(io);
500 const out_bin_name = "./out_" ++ std.fmt.hex(rand_int);
501 try eval.buildCOutput(emitted_path, out_bin_name, prog_node);
502 break :bin out_bin_name;
503 },
504 };
505
506 var argv_buf: [2][]const u8 = undefined;
507 const argv: []const []const u8, const is_foreign: bool = sw: switch (std.zig.system.getExternalExecutor(
508 io,
509 &eval.target,
510 .{
511 .link_libc = eval.backend == .cbe,
512 .host_cpu_arch = eval.host.cpu.arch,
513 .host_os_tag = eval.host.os.tag,
514 },
515 )) {
516 .bad_dl, .bad_os_or_cpu => {
517 // This binary cannot be executed on this host.
518 if (!eval.quiet) {
519 std.log.warn("skipping execution because host '{s}' cannot execute binaries for foreign target '{s}'", .{
520 try eval.host.zigTriple(eval.arena),
521 try eval.target.zigTriple(eval.arena),
522 });
523 }
524 return;
525 },
526 .native, .rosetta => argv: {
527 argv_buf[0] = binary_path;
528 break :argv .{ argv_buf[0..1], false };
529 },
530 .qemu => |executor_cmd| argv: {
531 if (eval.enable_qemu) {
532 argv_buf[0] = executor_cmd;
533 argv_buf[1] = binary_path;
534 break :argv .{ argv_buf[0..2], true };
535 } else {
536 continue :sw .bad_os_or_cpu;
537 }
538 },
539 .wine => |executor_cmd| argv: {
540 if (eval.enable_wine) {
541 argv_buf[0] = executor_cmd;
542 argv_buf[1] = binary_path;
543 break :argv .{ argv_buf[0..2], true };
544 } else {
545 continue :sw .bad_os_or_cpu;
546 }
547 },
548 .wasmtime => |executor_cmd| argv: {
549 if (eval.enable_wasmtime) {
550 argv_buf[0] = executor_cmd;
551 argv_buf[1] = binary_path;
552 break :argv .{ argv_buf[0..2], true };
553 } else {
554 continue :sw .bad_os_or_cpu;
555 }
556 },
557 .darling => |executor_cmd| argv: {
558 if (eval.enable_darling) {
559 argv_buf[0] = executor_cmd;
560 argv_buf[1] = binary_path;
561 break :argv .{ argv_buf[0..2], true };
562 } else {
563 continue :sw .bad_os_or_cpu;
564 }
565 },
566 };
567
568 const run_prog_node = prog_node.start("run generated executable", 0);
569 defer run_prog_node.end();
570
571 const result = std.process.run(eval.arena, io, .{
572 .argv = argv,
573 .cwd = .{ .path = eval.tmp_dir_path },
574 }) catch |err| {
575 if (is_foreign) {
576 // Chances are the foreign executor isn't available. Skip this evaluation.
577 if (!eval.quiet) {
578 std.log.warn("skipping execution of '{s}' via executor for foreign target '{s}': {t}", .{
579 binary_path,
580 try eval.target.zigTriple(eval.arena),
581 err,
582 });
583 }
584 return;
585 }
586 eval.fatal("failed to run the generated executable '{s}': {t}", .{ binary_path, err });
587 };
588
589 // Some executors (looking at you, Wine) like throwing some stderr in, just for fun.
590 // Therefore, we'll ignore stderr when using a foreign executor.
591 if (!is_foreign and result.stderr.len != 0) {
592 std.log.err("generated executable '{s}' had unexpected stderr:\n{s}", .{
593 binary_path, result.stderr,
594 });
595 }
596
597 switch (result.term) {
598 .exited => |code| switch (update.outcome) {
599 .unknown, .compile_errors => unreachable,
600 .stdout => |expected_stdout| {
601 if (code != 0) {
602 eval.fatal("generated executable '{s}' failed with code {d}", .{ binary_path, code });
603 }
604 try std.testing.expectEqualStrings(expected_stdout, result.stdout);
605 },
606 .exit_code => |expected_code| try std.testing.expectEqual(expected_code, code),
607 },
608 .signal => |sig| {
609 eval.fatal("generated executable '{s}' terminated with signal {t}", .{ binary_path, sig });
610 },
611 .stopped => |sig| {
612 eval.fatal("generated executable '{s}' stopped with signal {t}", .{ binary_path, sig });
613 },
614 .unknown => {
615 eval.fatal("generated executable '{s}' terminated unexpectedly", .{binary_path});
616 },
617 }
618
619 if (!is_foreign and result.stderr.len != 0) std.process.exit(1);
620 }
621
622 fn requestUpdate(eval: *Eval) !void {
623 const io = eval.io;
624
625 var w = eval.child.stdin.?.writerStreaming(io, &.{});
626 var client: std.zig.Client = .{
627 .in = undefined,
628 .out = &w.interface,
629 };
630 client.serveBodylessMessage(.update) catch |err| switch (err) {
631 error.WriteFailed => return w.err.?,
632 };
633 }
634
635 fn end(eval: *Eval, mr: *Io.File.MultiReader) !void {
636 requestExit(eval.child, eval);
637
638 var client: std.zig.Client = .{
639 .in = mr.reader(0),
640 .out = undefined,
641 };
642
643 while (true) {
644 const header = client.receiveMessageWithMultiReader(mr, .none) catch |err| switch (err) {
645 error.Timeout => unreachable,
646 error.EndOfStream => |e| {
647 if (client.in.bufferedLen() == 0) break;
648 return e;
649 },
650 else => |e| return e,
651 };
652 try client.in.discardAll(header.bytes_len);
653 }
654
655 const stderr = mr.reader(1).buffered();
656 if (stderr.len > 0) eval.fatal("unexpected stderr:\n{s}", .{stderr});
657 }
658
659 fn buildCOutput(eval: *Eval, c_path: []const u8, out_path: []const u8, prog_node: std.Progress.Node) !void {
660 std.debug.assert(eval.cc_child_args.items.len > 0);
661
662 const child_prog_node = prog_node.start("build cbe output", 0);
663 defer child_prog_node.end();
664
665 try eval.cc_child_args.append(eval.arena, try std.fmt.allocPrint(eval.arena, "-femit-bin={s}", .{out_path}));
666 try eval.cc_child_args.append(eval.arena, c_path);
667 defer eval.cc_child_args.items.len -= 2;
668
669 if (eval.allow_compiler_stderr) {
670 const cmd: std.zig.SubprocessCommand = .{
671 .argv = eval.cc_child_args.items,
672 .cwd = eval.tmp_dir_path,
673 };
674 std.log.scoped(.spawn).info("{f}", .{cmd});
675 }
676
677 const result = std.process.run(eval.arena, eval.io, .{
678 .argv = eval.cc_child_args.items,
679 .cwd = .{ .path = eval.tmp_dir_path },
680 .progress_node = child_prog_node,
681 }) catch |err| {
682 eval.fatal("failed to spawn zig cc for '{s}': {t}", .{ c_path, err });
683 };
684
685 if (result.term == .exited and result.term.exited == 0) return;
686
687 if (result.stderr.len != 0) {
688 std.log.err("zig cc stderr:\n{s}", .{result.stderr});
689 }
690 switch (result.term) {
691 .exited => |code| eval.fatal("zig cc for '{s}' failed with code {d}", .{ c_path, code }),
692 .signal => |sig| eval.fatal("zig cc for '{s}' terminated unexpectedly with signal {t}", .{ c_path, sig }),
693 .stopped => |sig| eval.fatal("zig cc for '{s}' stopped unexpectedly with signal {t}", .{ c_path, sig }),
694 .unknown => eval.fatal("zig cc for '{s}' terminated unexpectedly", .{c_path}),
695 }
696 }
697
698 fn fatal(eval: *Eval, comptime fmt: []const u8, args: anytype) noreturn {
699 const io = eval.io;
700 eval.tmp_dir.close(io);
701 if (!eval.preserve_tmp_on_fatal) {
702 // Kill the child since it holds an open handle to its CWD which is the tmp dir path
703 eval.child.kill(io);
704 Dir.cwd().deleteTree(io, eval.tmp_dir_path) catch |err| {
705 std.log.warn("failed to delete tree '{s}': {t}", .{ eval.tmp_dir_path, err });
706 };
707 }
708 std.process.fatal(fmt, args);
709 }
710};
711
712const Backend = enum {
713 /// Run semantic analysis only. Runtime output will not be tested, but we still verify
714 /// that compilation succeeds. Corresponds to `-fno-emit-bin`.
715 sema,
716 /// Use the self-hosted code generation backend for this target.
717 /// Corresponds to `-fno-llvm -fno-lld`.
718 selfhosted,
719 /// Use the LLVM backend.
720 /// Corresponds to `-fllvm -flld`.
721 llvm,
722 /// Use the C backend. The output is compiled with `zig cc`.
723 /// Corresponds to `-ofmt=c`.
724 cbe,
725};
726
727const Case = struct {
728 updates: []Update,
729 root_source_file: []const u8,
730 skip_targets: []const SkipTarget,
731 modules: []const Module,
732
733 const SkipTarget = struct {
734 query: std.Target.Query,
735 backend: Backend,
736 };
737
738 const Module = struct {
739 name: []const u8,
740 file: []const u8,
741 };
742
743 const Update = struct {
744 name: []const u8,
745 outcome: Outcome,
746 changes: []const FullContents = &.{},
747 deletes: []const []const u8 = &.{},
748 };
749
750 const FullContents = struct {
751 name: []const u8,
752 bytes: []const u8,
753 };
754
755 const Outcome = union(enum) {
756 unknown,
757 compile_errors: struct {
758 errors: []const ExpectedError,
759 compile_log_output: []const u8,
760 },
761 stdout: []const u8,
762 exit_code: u8,
763 };
764
765 const ExpectedError = struct {
766 is_note: bool,
767 msg: []const u8,
768 src: ?struct {
769 filename: []const u8,
770 line: u32,
771 column: u32,
772 },
773 };
774
775 fn parse(arena: Allocator, bytes: []const u8) !Case {
776 const fatal = std.process.fatal;
777
778 var skip_targets: std.ArrayList(SkipTarget) = .empty;
779 var modules: std.ArrayList(Module) = .empty;
780 var updates: std.ArrayList(Update) = .empty;
781 var changes: std.ArrayList(FullContents) = .empty;
782 var deletes: std.ArrayList([]const u8) = .empty;
783 var it = std.mem.splitScalar(u8, bytes, '\n');
784 var line_n: usize = 1;
785 var root_source_file: ?[]const u8 = null;
786 while (it.next()) |line| : (line_n += 1) {
787 if (std.mem.startsWith(u8, line, "#")) {
788 var line_it = std.mem.splitScalar(u8, line, '=');
789 const key = line_it.first()[1..];
790 const val = std.mem.trimEnd(u8, line_it.rest(), "\r"); // windows moment
791 if (val.len == 0) {
792 fatal("line {d}: missing value", .{line_n});
793 } else if (std.mem.eql(u8, key, "skip_target")) {
794 const query, const backend = parseTargetQueryAndBackend(
795 val,
796 try std.fmt.allocPrint(arena, "line {d}: ", .{line_n}),
797 );
798 try skip_targets.append(arena, .{
799 .query = query,
800 .backend = backend,
801 });
802 } else if (std.mem.eql(u8, key, "module")) {
803 const split_idx = std.mem.findScalar(u8, val, '=') orelse
804 fatal("line {d}: module does not include file", .{line_n});
805 const name = val[0..split_idx];
806 const file = val[split_idx + 1 ..];
807 try modules.append(arena, .{
808 .name = name,
809 .file = file,
810 });
811 } else if (std.mem.eql(u8, key, "update")) {
812 if (updates.items.len > 0) {
813 const last_update = &updates.items[updates.items.len - 1];
814 last_update.changes = try changes.toOwnedSlice(arena);
815 last_update.deletes = try deletes.toOwnedSlice(arena);
816 }
817 try updates.append(arena, .{
818 .name = val,
819 .outcome = .unknown,
820 });
821 } else if (std.mem.eql(u8, key, "file")) {
822 if (updates.items.len == 0) fatal("line {d}: file directive before update", .{line_n});
823
824 if (root_source_file == null)
825 root_source_file = val;
826
827 // Because Windows is so excellent, we need to convert CRLF to LF, so
828 // can't just slice into the input here. How delightful!
829 var src: std.ArrayList(u8) = .empty;
830
831 while (true) {
832 const next_line_raw = it.peek() orelse fatal("line {d}: unexpected EOF", .{line_n});
833 const next_line = std.mem.trimEnd(u8, next_line_raw, "\r");
834 if (std.mem.startsWith(u8, next_line, "#")) break;
835
836 _ = it.next();
837 line_n += 1;
838
839 try src.ensureUnusedCapacity(arena, next_line.len + 1);
840 src.appendSliceAssumeCapacity(next_line);
841 src.appendAssumeCapacity('\n');
842 }
843
844 try changes.append(arena, .{
845 .name = val,
846 .bytes = src.items,
847 });
848 } else if (std.mem.eql(u8, key, "rm_file")) {
849 if (updates.items.len == 0) fatal("line {d}: rm_file directive before update", .{line_n});
850 try deletes.append(arena, val);
851 } else if (std.mem.eql(u8, key, "expect_stdout")) {
852 if (updates.items.len == 0) fatal("line {d}: expect directive before update", .{line_n});
853 const last_update = &updates.items[updates.items.len - 1];
854 if (last_update.outcome != .unknown) fatal("line {d}: conflicting expect directive", .{line_n});
855 last_update.outcome = .{
856 .stdout = std.zig.string_literal.parseAlloc(arena, val) catch |err| {
857 fatal("line {d}: bad string literal: {t}", .{ line_n, err });
858 },
859 };
860 } else if (std.mem.eql(u8, key, "expect_error")) {
861 if (updates.items.len == 0) fatal("line {d}: expect directive before update", .{line_n});
862 const last_update = &updates.items[updates.items.len - 1];
863 if (last_update.outcome != .unknown) fatal("line {d}: conflicting expect directive", .{line_n});
864
865 var errors: std.ArrayList(ExpectedError) = .empty;
866 try errors.append(arena, parseExpectedError(val, line_n));
867 while (true) {
868 const next_line = it.peek() orelse break;
869 if (!std.mem.startsWith(u8, next_line, "#")) break;
870 var new_line_it = std.mem.splitScalar(u8, next_line, '=');
871 const new_key = new_line_it.first()[1..];
872 const new_val = std.mem.trimEnd(u8, new_line_it.rest(), "\r");
873 if (new_val.len == 0) break;
874 if (!std.mem.eql(u8, new_key, "expect_error")) break;
875
876 _ = it.next();
877 line_n += 1;
878 try errors.append(arena, parseExpectedError(new_val, line_n));
879 }
880
881 var compile_log_output: std.ArrayList(u8) = .empty;
882 while (true) {
883 const next_line = it.peek() orelse break;
884 if (!std.mem.startsWith(u8, next_line, "#")) break;
885 var new_line_it = std.mem.splitScalar(u8, next_line, '=');
886 const new_key = new_line_it.first()[1..];
887 const new_val = std.mem.trimEnd(u8, new_line_it.rest(), "\r");
888 if (new_val.len == 0) break;
889 if (!std.mem.eql(u8, new_key, "expect_compile_log")) break;
890
891 _ = it.next();
892 line_n += 1;
893 try compile_log_output.ensureUnusedCapacity(arena, new_val.len + 1);
894 compile_log_output.appendSliceAssumeCapacity(new_val);
895 compile_log_output.appendAssumeCapacity('\n');
896 }
897
898 last_update.outcome = .{ .compile_errors = .{
899 .errors = errors.items,
900 .compile_log_output = compile_log_output.items,
901 } };
902 } else if (std.mem.eql(u8, key, "expect_compile_log")) {
903 fatal("line {d}: 'expect_compile_log' must immediately follow 'expect_error'", .{line_n});
904 } else {
905 fatal("line {d}: unrecognized key '{s}'", .{ line_n, key });
906 }
907 }
908 }
909
910 if (changes.items.len > 0) {
911 const last_update = &updates.items[updates.items.len - 1];
912 last_update.changes = changes.items; // arena so no need for toOwnedSlice
913 last_update.deletes = deletes.items;
914 }
915
916 return .{
917 .updates = updates.items,
918 .root_source_file = root_source_file orelse fatal("missing root source file", .{}),
919 .skip_targets = skip_targets.items, // arena so no need for toOwnedSlice
920 .modules = modules.items,
921 };
922 }
923};
924
925fn requestExit(child: *std.process.Child, eval: *Eval) void {
926 if (child.stdin == null) return;
927 const io = eval.io;
928
929 var w = eval.child.stdin.?.writerStreaming(io, &.{});
930 var client: std.zig.Client = .{
931 .in = undefined,
932 .out = &w.interface,
933 };
934 client.serveBodylessMessage(.exit) catch |err| switch (err) {
935 error.WriteFailed => switch (w.err.?) {
936 error.BrokenPipe => {},
937 else => |e| eval.fatal("failed to send exit: {t}", .{e}),
938 },
939 };
940
941 // Send EOF to stdin.
942 child.stdin.?.close(io);
943 child.stdin = null;
944}
945
946fn waitChild(child: *std.process.Child, eval: *Eval) void {
947 const io = eval.io;
948 requestExit(child, eval);
949 const term = child.wait(io) catch |err| eval.fatal("child process failed: {t}", .{err});
950 switch (term) {
951 .exited => |code| if (code != 0) eval.fatal("compiler failed with code {d}", .{code}),
952 .signal => |sig| eval.fatal("compiler terminated with signal {t}", .{sig}),
953 .stopped => |sig| eval.fatal("compiler stopped unexpectedly with signal {t}", .{sig}),
954 .unknown => eval.fatal("compiler terminated unexpectedly", .{}),
955 }
956}
957
958fn parseExpectedError(str: []const u8, l: usize) Case.ExpectedError {
959 // #expect_error=foo.zig:1:2: error: the error message
960 // #expect_error=foo.zig:1:2: note: and a note
961
962 const fatal = std.process.fatal;
963
964 var it = std.mem.splitScalar(u8, str, ':');
965 const filename = it.first();
966 const line_str, const column_str = if (filename.len > 0) .{
967 it.next() orelse fatal("line {d}: incomplete error specification", .{l}),
968 it.next() orelse fatal("line {d}: incomplete error specification", .{l}),
969 } else .{ undefined, undefined };
970 const error_or_note_str = std.mem.trim(
971 u8,
972 it.next() orelse fatal("line {d}: incomplete error specification", .{l}),
973 " ",
974 );
975
976 const is_note = if (std.mem.eql(u8, error_or_note_str, "error"))
977 false
978 else if (std.mem.eql(u8, error_or_note_str, "note"))
979 true
980 else
981 fatal("line {d}: expeted 'error' or 'note', found '{s}'", .{ l, error_or_note_str });
982
983 const message = std.mem.trim(u8, it.rest(), " ");
984 if (message.len == 0) fatal("line {d}: empty error message", .{l});
985
986 return .{
987 .is_note = is_note,
988 .msg = message,
989 .src = if (filename.len == 0) null else .{
990 .filename = filename,
991 .line = std.fmt.parseInt(u32, line_str, 10) catch
992 fatal("line {d}: invalid line number '{s}'", .{ l, line_str }),
993 .column = std.fmt.parseInt(u32, column_str, 10) catch
994 fatal("line {d}: invalid column number '{s}'", .{ l, column_str }),
995 },
996 };
997}
998
999fn rand64(io: Io) u64 {
1000 var x: u64 = undefined;
1001 io.random(@ptrCast(&x));
1002 return x;
1003}
1004
1005/// Calls `std.process.fatal` on error. The error messages are prefixed with `err_prefix`.
1006fn parseTargetQueryAndBackend(input_str: []const u8, err_prefix: []const u8) struct { std.Target.Query, Backend } {
1007 const fatal = std.process.fatal;
1008
1009 const split_idx = std.mem.findScalarLast(u8, input_str, '-') orelse
1010 fatal("{s}target does not include backend", .{err_prefix});
1011
1012 const query = input_str[0..split_idx];
1013
1014 const backend_str = input_str[split_idx + 1 ..];
1015 const backend: Backend = std.meta.stringToEnum(Backend, backend_str) orelse
1016 fatal("{s}invalid backend '{s}'", .{ err_prefix, backend_str });
1017
1018 const parsed_query = std.Build.parseTargetQuery(.{
1019 .arch_os_abi = query,
1020 .object_format = switch (backend) {
1021 .sema, .selfhosted, .llvm => null,
1022 .cbe => "c",
1023 },
1024 }) catch fatal("{s}invalid target query '{s}'", .{ err_prefix, query });
1025
1026 return .{ parsed_query, backend };
1027}
1028
1029fn badUsage(comptime fmt: []const u8, args: anytype) noreturn {
1030 std.log.err(fmt ++ "\n{s}", args ++ .{usage});
1031 std.process.exit(1);
1032}