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 {...@@ -125,7 +125,6 @@ pub fn build(b: *std.Build) !void {
125 const skip_darwin = b.option(bool, "skip-darwin", "Main test suite skips targets with darwin OSs") orelse false;125 const skip_darwin = b.option(bool, "skip-darwin", "Main test suite skips targets with darwin OSs") orelse false;
126 const skip_linux = b.option(bool, "skip-linux", "Main test suite skips targets with linux OS") orelse false;126 const skip_linux = b.option(bool, "skip-linux", "Main test suite skips targets with linux OS") orelse false;
127 const skip_llvm = b.option(bool, "skip-llvm", "Main test suite skips targets that use LLVM backend") orelse false;127 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
130 const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false;129 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 {...@@ -781,8 +780,22 @@ pub fn build(b: *std.Build) !void {
781 test_step.dependOn(check_oracle_step);780 test_step.dependOn(check_oracle_step);
782 }781 }
783782
784 const test_incremental_step = b.step("test-incremental", "Run the incremental compilation test cases");783 if (tests.addLibcTestNszTests(b, .{
785 try tests.addIncrementalTests(b, test_incremental_step, .{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, .{
786 .test_filters = test_filters,799 .test_filters = test_filters,
787 .test_target_filters = test_target_filters,800 .test_target_filters = test_target_filters,
788 .skip_non_native = skip_non_native,801 .skip_non_native = skip_non_native,
...@@ -794,16 +807,7 @@ pub fn build(b: *std.Build) !void {...@@ -794,16 +807,7 @@ pub fn build(b: *std.Build) !void {
794 .skip_darwin = skip_darwin,807 .skip_darwin = skip_darwin,
795 .skip_linux = skip_linux,808 .skip_linux = skip_linux,
796 .skip_llvm = skip_llvm,809 .skip_llvm = skip_llvm,
797 });810 }));
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);
807}811}
808812
809fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {813fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
lib/compiler/Maker.zig+27-13
...@@ -3631,7 +3631,7 @@ pub fn resolveLazyPath(...@@ -3631,7 +3631,7 @@ pub fn resolveLazyPath(
3631 const c = &maker.scanned_config.configuration;3631 const c = &maker.scanned_config.configuration;
3632 return switch (lazy_path) {3632 return switch (lazy_path) {
3633 .source_path => |sp| try packagePath(maker, arena, sp.owner, sp.sub_path.slice(c)),3633 .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)),
3635 .generated => |gen| {3635 .generated => |gen| {
3636 const base = generatedPath(maker, gen.index);3636 const base = generatedPath(maker, gen.index);
3637 var file_path = base;3637 var file_path = base;
...@@ -3768,7 +3768,12 @@ pub fn packagePath(...@@ -3768,7 +3768,12 @@ pub fn packagePath(
3768 };3768 };
3769}3769}
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 {
3772 const graph = maker.graph;3777 const graph = maker.graph;
3773 const c = &maker.scanned_config.configuration;3778 const c = &maker.scanned_config.configuration;
3774 const sub_path = relative.sub_path.slice(c);3779 const sub_path = relative.sub_path.slice(c);
...@@ -3789,12 +3794,12 @@ pub fn relativePath(maker: *const Maker, arena: Allocator, relative: Configurati...@@ -3789,12 +3794,12 @@ pub fn relativePath(maker: *const Maker, arena: Allocator, relative: Configurati
3789 .root_dir = graph.build_root_directory,3794 .root_dir = graph.build_root_directory,
3790 .sub_path = sub_path,3795 .sub_path = sub_path,
3791 },3796 },
3792 .zig_exe => .{3797 .zig_exe => {
3793 .root_dir = .cwd(),3798 assert(sub_path.len == 0);
3794 .sub_path = if (sub_path.len == 0)3799 return .{
3795 graph.zig_exe3800 .root_dir = .cwd(),
3796 else3801 .sub_path = graph.zig_exe,
3797 try Io.Dir.path.join(arena, &.{ graph.zig_exe, sub_path }),3802 };
3798 },3803 },
3799 .zig_lib => .{3804 .zig_lib => .{
3800 .root_dir = graph.zig_lib_directory,3805 .root_dir = graph.zig_lib_directory,
...@@ -3804,6 +3809,10 @@ pub fn relativePath(maker: *const Maker, arena: Allocator, relative: Configurati...@@ -3804,6 +3809,10 @@ pub fn relativePath(maker: *const Maker, arena: Allocator, relative: Configurati
3804 .install_lib => try maker.install_paths.lib.join(arena, sub_path),3809 .install_lib => try maker.install_paths.lib.join(arena, sub_path),
3805 .install_bin => try maker.install_paths.bin.join(arena, sub_path),3810 .install_bin => try maker.install_paths.bin.join(arena, sub_path),
3806 .install_include => try maker.install_paths.include.join(arena, sub_path),3811 .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", .{}),
3807 };3816 };
3808}3817}
38093818
...@@ -4125,8 +4134,7 @@ const Fork = struct {...@@ -4125,8 +4134,7 @@ const Fork = struct {
4125 fork.arena_allocator = .init(gpa);4134 fork.arena_allocator = .init(gpa);
4126 const arena = fork.arena_allocator.allocator();4135 const arena = fork.arena_allocator.allocator();
41274136
4128 var error_bundle: std.zig.ErrorBundle.Wip = undefined;4137 var error_bundle: std.zig.ErrorBundle.Wip = try .init(gpa);
4129 try error_bundle.init(gpa);
4130 defer error_bundle.deinit();4138 defer error_bundle.deinit();
41314139
4132 const manifest_path = try fork.path.join(arena, Package.Manifest.basename);4140 const manifest_path = try fork.path.join(arena, Package.Manifest.basename);
...@@ -4227,8 +4235,7 @@ fn loadManifest(...@@ -4227,8 +4235,7 @@ fn loadManifest(
4227 errdefer manifest.deinit(gpa);4235 errdefer manifest.deinit(gpa);
42284236
4229 if (manifest.errors.len > 0) {4237 if (manifest.errors.len > 0) {
4230 var wip_errors: std.zig.ErrorBundle.Wip = undefined;4238 var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa);
4231 try wip_errors.init(gpa);
4232 defer wip_errors.deinit();4239 defer wip_errors.deinit();
42334240
4234 const src_path = try wip_errors.addString(Package.Manifest.basename);4241 const src_path = try wip_errors.addString(Package.Manifest.basename);
...@@ -4393,15 +4400,22 @@ fn confPathDepToCachePath(...@@ -4393,15 +4400,22 @@ fn confPathDepToCachePath(
4393 else => |index| try Dir.path.join(arena, &.{ index.get(c).?.root_path.slice(c), sub_path }),4400 else => |index| try Dir.path.join(arena, &.{ index.get(c).?.root_path.slice(c), sub_path }),
4394 },4401 },
4395 },4402 },
4403 .zig_exe => {
4404 assert(sub_path.len == 0);
4405 return .{
4406 .root_dir = .cwd(),
4407 .sub_path = graph.zig_exe,
4408 };
4409 },
4396 .zig_lib => .{4410 .zig_lib => .{
4397 .root_dir = graph.zig_lib_directory,4411 .root_dir = graph.zig_lib_directory,
4398 .sub_path = sub_path,4412 .sub_path = sub_path,
4399 },4413 },
4400 .zig_exe => @panic("TODO"),
4401 .install_prefix => @panic("TODO"),4414 .install_prefix => @panic("TODO"),
4402 .install_lib => @panic("TODO"),4415 .install_lib => @panic("TODO"),
4403 .install_bin => @panic("TODO"),4416 .install_bin => @panic("TODO"),
4404 .install_include => @panic("TODO"),4417 .install_include => @panic("TODO"),
4418 .libc_runtimes => @panic("TODO"),
4405 };4419 };
4406}4420}
44074421
lib/compiler/Maker/Fetch.zig+1-1
...@@ -527,7 +527,7 @@ pub fn run(f: *Fetch) RunError!void {...@@ -527,7 +527,7 @@ pub fn run(f: *Fetch) RunError!void {
527 const arena = f.arena.allocator();527 const arena = f.arena.allocator();
528 const gpa = f.arena.child_allocator;528 const gpa = f.arena.child_allocator;
529529
530 try eb.init(gpa);530 eb.* = try .init(gpa);
531531
532 // Check the global zig package cache to see if the hash already exists. If532 // Check the global zig package cache to see if the hash already exists. If
533 // so, load, parse, and validate the build.zig.zon file therein, and skip533 // 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(...@@ -270,7 +270,10 @@ pub fn make(
270 .compile => break :t null,270 .compile => break :t null,
271 .run => {271 .run => {
272 const run_flags: Configuration.Step.Run.Flags = @bitCast(flags);272 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 }
274 },277 },
275 else => {},278 else => {},
276 }279 }
...@@ -621,6 +624,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi...@@ -621,6 +624,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi
621 else => |e| return e,624 else => |e| return e,
622 };625 };
623 const body = client.in.take(header.bytes_len) catch unreachable;626 const body = client.in.take(header.bytes_len) catch unreachable;
627 var body_r: std.Io.Reader = .fixed(body);
624628
625 switch (header.tag) {629 switch (header.tag) {
626 .zig_version => {630 .zig_version => {
...@@ -632,16 +636,22 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi...@@ -632,16 +636,22 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi
632 );636 );
633 }637 }
634 },638 },
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 },
635 .error_bundle => {645 .error_bundle => {
636 s.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body);646 s.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body);
637 // This message indicates the end of the update.647 // This message indicates the end of the update.
638 if (watch) break;648 if (watch) break;
639 },649 },
640 .emit_digest => {650 .emit_digest => {
641 const EmitDigest = std.zig.Server.Message.EmitDigest;651 const emit_digest = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable;
642 const emit_digest: *align(1) const EmitDigest = @ptrCast(body);652 const digest = body_r.takeArray(Cache.bin_digest_len) catch unreachable;
643 s.result_cached = emit_digest.flags.cache_hit;653 s.result_cached = emit_digest.flags.cache_hit;
644 result = .{ .bin = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len].* };654 result = .{ .bin = digest.* };
645 },655 },
646 .file_system_inputs => {656 .file_system_inputs => {
647 clearWatchInputs(s, maker);657 clearWatchInputs(s, maker);
...@@ -701,8 +711,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi...@@ -701,8 +711,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi
701 }711 }
702 },712 },
703 .time_report => if (maker.web_server) |ws| {713 .time_report => if (maker.web_server) |ws| {
704 const TimeReport = std.zig.Server.Message.TimeReport;714 const tr = body_r.takeStruct(std.zig.Server.Message.TimeReport, .little) catch unreachable;
705 const tr: *align(1) const TimeReport = @ptrCast(body[0..@sizeOf(TimeReport)]);
706 ws.updateTimeReportCompile(.{715 ws.updateTimeReportCompile(.{
707 .compile_step = step_index,716 .compile_step = step_index,
708 .use_llvm = tr.flags.use_llvm,717 .use_llvm = tr.flags.use_llvm,
...@@ -711,7 +720,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi...@@ -711,7 +720,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi
711 .llvm_pass_timings_len = tr.llvm_pass_timings_len,720 .llvm_pass_timings_len = tr.llvm_pass_timings_len,
712 .files_len = tr.files_len,721 .files_len = tr.files_len,
713 .decls_len = tr.decls_len,722 .decls_len = tr.decls_len,
714 .trailing = body[@sizeOf(TimeReport)..],723 .trailing = body_r.buffered(),
715 });724 });
716 },725 },
717 else => {}, // ignore other messages726 else => {}, // ignore other messages
...@@ -848,7 +857,7 @@ pub fn setWatchInputsFromManifestFiles(...@@ -848,7 +857,7 @@ pub fn setWatchInputsFromManifestFiles(
848}857}
849858
850/// For steps that have a single input that never changes when re-running `make`.859/// 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 {
852 if (!step.inputs.populated()) try step.addWatchInput(maker, arena, lazy_path);861 if (!step.inputs.populated()) try step.addWatchInput(maker, arena, lazy_path);
853}862}
854863
...@@ -857,7 +866,7 @@ pub fn clearWatchInputs(step: *Step, maker: *Maker) void {...@@ -857,7 +866,7 @@ pub fn clearWatchInputs(step: *Step, maker: *Maker) void {
857}866}
858867
859/// Places a *file* dependency on the path.868/// 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 {
861 const conf = &maker.scanned_config.configuration;870 const conf = &maker.scanned_config.configuration;
862 switch (lazy_file) {871 switch (lazy_file) {
863 .source_path => |source_path| {872 .source_path => |source_path| {
...@@ -866,7 +875,7 @@ pub fn addWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_file: La...@@ -866,7 +875,7 @@ pub fn addWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_file: La
866 try addWatchInputPath(step, maker, pkg_path);875 try addWatchInputPath(step, maker, pkg_path);
867 },876 },
868 .relative => |relative| {877 .relative => |relative| {
869 const resolved_path = try maker.relativePath(arena, relative);878 const resolved_path = try maker.relativePath(arena, relative, step);
870 try addWatchInputPath(step, maker, resolved_path);879 try addWatchInputPath(step, maker, resolved_path);
871 },880 },
872 // Nothing to watch because this dependency edge is modeled instead via `dependants`.881 // 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...@@ -881,7 +890,7 @@ pub fn addWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_file: La
881/// Paths derived from this directory should also be manually added via890/// Paths derived from this directory should also be manually added via
882/// `addDirectoryWatchInputFromPath` if and only if this function returns891/// `addDirectoryWatchInputFromPath` if and only if this function returns
883/// `true`.892/// `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 {
885 const graph = maker.graph;894 const graph = maker.graph;
886 const arena = graph.arena; // TODO don't leak into the process arena895 const arena = graph.arena; // TODO don't leak into the process arena
887 switch (lazy_directory) {896 switch (lazy_directory) {
...@@ -892,7 +901,7 @@ pub fn addDirectoryWatchInput(step: *Step, maker: *Maker, lazy_directory: LazyPa...@@ -892,7 +901,7 @@ pub fn addDirectoryWatchInput(step: *Step, maker: *Maker, lazy_directory: LazyPa
892 try addDirectoryWatchInputFromPath(step, maker, pkg_path);901 try addDirectoryWatchInputFromPath(step, maker, pkg_path);
893 },902 },
894 .relative => |relative| {903 .relative => |relative| {
895 const resolved_path = try maker.relativePath(arena, relative);904 const resolved_path = try maker.relativePath(arena, relative, step);
896 try addDirectoryWatchInputFromPath(step, maker, resolved_path);905 try addDirectoryWatchInputFromPath(step, maker, resolved_path);
897 },906 },
898 // Nothing to watch because this dependency edge is modeled instead via `dependants`.907 // 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,...@@ -21,7 +21,7 @@ zig_process: ?*Step.ZigProcess = null,
21/// Populated by InstallArtifact.21/// Populated by InstallArtifact.
22installed_path: ?Path = null,22installed_path: ?Path = null,
23/// Populated by `make`, used by `Run`.23/// Populated by `make`, used by `Run`.
24is_linking_libc: bool = false,24config: ?std.zig.Server.Message.Config = null,
2525
26pub fn make(26pub fn make(
27 compile: *Compile,27 compile: *Compile,
...@@ -29,6 +29,7 @@ pub fn make(...@@ -29,6 +29,7 @@ pub fn make(
29 maker: *Maker,29 maker: *Maker,
30 progress_node: std.Progress.Node,30 progress_node: std.Progress.Node,
31) Step.ExtendedMakeError!void {31) Step.ExtendedMakeError!void {
32 _ = compile; // only accessed by `Step.evalZigProcess`.
32 const graph = maker.graph;33 const graph = maker.graph;
33 const gpa = maker.gpa;34 const gpa = maker.gpa;
34 const conf = &maker.scanned_config.configuration;35 const conf = &maker.scanned_config.configuration;
...@@ -42,7 +43,7 @@ pub fn make(...@@ -42,7 +43,7 @@ pub fn make(
42 var argv: std.ArrayList([]const u8) = .empty;43 var argv: std.ArrayList([]const u8) = .empty;
43 defer argv.deinit(gpa);44 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
47 const incremental = conf_comp.flags4.incremental.toBool() orelse graph.incremental == true;48 const incremental = conf_comp.flags4.incremental.toBool() orelse graph.incremental == true;
4849
...@@ -158,7 +159,6 @@ const ModuleListContext = struct {...@@ -158,7 +159,6 @@ const ModuleListContext = struct {
158159
159fn lowerZigArgs(160fn lowerZigArgs(
160 arena: Allocator,161 arena: Allocator,
161 compile: *Compile,
162 compile_index: Configuration.Step.Index,162 compile_index: Configuration.Step.Index,
163 maker: *Maker,163 maker: *Maker,
164 progress_node: std.Progress.Node,164 progress_node: std.Progress.Node,
...@@ -575,8 +575,6 @@ fn lowerZigArgs(...@@ -575,8 +575,6 @@ fn lowerZigArgs(
575 try zig_args.ensureUnusedCapacity(gpa, 2);575 try zig_args.ensureUnusedCapacity(gpa, 2);
576 if (is_linking_libcpp) zig_args.appendAssumeCapacity("-lc++");576 if (is_linking_libcpp) zig_args.appendAssumeCapacity("-lc++");
577 if (is_linking_libc) zig_args.appendAssumeCapacity("-lc");577 if (is_linking_libc) zig_args.appendAssumeCapacity("-lc");
578
579 compile.is_linking_libc = is_linking_libc;
580 }578 }
581579
582 if (conf_comp.win32_manifest.value) |manifest_file| {580 if (conf_comp.win32_manifest.value) |manifest_file| {
...@@ -944,6 +942,7 @@ pub fn rebuildInFuzzMode(...@@ -944,6 +942,7 @@ pub fn rebuildInFuzzMode(
944 compile_index: Configuration.Step.Index,942 compile_index: Configuration.Step.Index,
945 progress_node: std.Progress.Node,943 progress_node: std.Progress.Node,
946) !Step.OptCacheDigest {944) !Step.OptCacheDigest {
945 _ = compile;
947 const gpa = maker.gpa;946 const gpa = maker.gpa;
948 const step = maker.stepByIndex(compile_index);947 const step = maker.stepByIndex(compile_index);
949948
...@@ -962,7 +961,7 @@ pub fn rebuildInFuzzMode(...@@ -962,7 +961,7 @@ pub fn rebuildInFuzzMode(
962 var argv: std.ArrayList([]const u8) = .empty;961 var argv: std.ArrayList([]const u8) = .empty;
963 defer argv.deinit(gpa);962 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);
966 return Step.evalZigProcess(compile_index, maker, argv.items, progress_node, false);965 return Step.evalZigProcess(compile_index, maker, argv.items, progress_node, false);
967}966}
968967
lib/compiler/Maker/Step/Run.zig+821-326
...@@ -55,237 +55,598 @@ pub fn make(...@@ -55,237 +55,598 @@ pub fn make(
55 var man = graph.cache.obtain();55 var man = graph.cache.obtain();
56 defer man.deinit();56 defer man.deinit();
5757
58 if (conf_run.environ_map.value) |environ_map_index| {58 var tmp_dir_path: ["tmp".len + Dir.path.sep_str.len + std.fmt.hex(@as(u64, 0)).len]u8 = undefined;
59 const environ_map = environ_map_index.get(conf);59
60 for (environ_map.keys.slice(conf), environ_map.values.slice(conf)) |key, value| {60 const has_side_effects = has_side_effects: {
61 man.hash.addBytesZ(key.slice(conf));61 if (conf_run.environ_map.value) |environ_map_index| {
62 man.hash.addBytesZ(value.slice(conf));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 }
63 }67 }
64 }
6568
66 for (conf_run.preopens.slice) |preopen| {69 for (conf_run.preopens.slice) |preopen| {
67 man.hash.addBytesZ(preopen.name.slice(conf));70 man.hash.addBytesZ(preopen.name.slice(conf));
68 const cwd_path = try maker.resolveLazyPathIndex(arena, preopen.path, run_index);71 const cwd_path = try maker.resolveLazyPathIndex(arena, preopen.path, run_index);
69 man.hash.addBytes(try cwd_path.toString(arena));72 man.hash.addBytes(try cwd_path.toString(arena));
70 }73 }
7174
72 man.hash.add(graph.fuzzing);75 man.hash.add(graph.fuzzing);
73 man.hash.add(conf_run.flags.color);76 man.hash.add(conf_run.flags.color);
74 man.hash.add(conf_run.flags.disable_zig_progress);77 man.hash.add(conf_run.flags.disable_zig_progress);
7578
76 var any_dep_files = false;79 var any_discovered_inputs = switch (conf_run.flags.stdio) {
77 var any_output_args = false;80 .infer_from_args, .inherit, .check, .zig_test => false,
78 var any_cli_positionals = 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| {208 var owned_dirs: std.bit_set.Dynamic = .{};
81 const arg = arg_index.get(conf);209 var inherit_dirs: std.ArrayList(Io.Dir) = .empty;
82 try argv_list.ensureUnusedCapacity(gpa, 1);210 var owned_files: std.bit_set.Dynamic = .{};
83 switch (arg.flags.tag) {211 var inherit_files: std.ArrayList(Io.File) = .empty;
84 .string => {212 var protocol_args: std.ArrayList(u8) = .empty;
85 const prefix = arg.prefix.value.?.slice(conf);213 var input_dirs: std.ArrayList(Cache.Path) = .empty;
86 argv_list.appendAssumeCapacity(prefix);214 defer {
87 man.hash.addBytesZ(prefix);215 for (inherit_dirs.items, 0..) |inherit_dir, inherit_dir_index|
88 },216 if (owned_dirs.isSet(inherit_dir_index)) inherit_dir.close(io);
89 .path_file => {217 for (inherit_files.items, 0..) |inherit_file, inherit_file_index|
90 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";218 if (owned_files.isSet(inherit_file_index)) inherit_file.close(io);
91 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";219 owned_dirs.deinit(gpa);
92 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);220 inherit_dirs.deinit(gpa);
93 argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{221 owned_files.deinit(gpa);
94 prefix, try convertPathArg(arena, run_index, maker, file_path, arg.flags.make_absolute), suffix,222 inherit_files.deinit(gpa);
95 }));223 protocol_args.deinit(gpa);
96 man.hash.add(arg.flags.make_absolute);224 input_dirs.deinit(gpa);
97 man.hash.addBytesZ(prefix);225 }
98 man.hash.addBytesZ(suffix);226 switch (conf_run.flags.stdio) {
99 _ = try man.addInputPath(file_path, .{});227 .infer_from_args, .inherit, .check => {},
100 },228 .zig_test => {
101 .path_directory => {229 const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false);
102 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";230
103 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";231 try argv_list.ensureUnusedCapacity(gpa, 3);
104 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);232 argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string}));
105 const resolved_arg = try mem.concat(arena, u8, &.{233 argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed}));
106 prefix, try convertPathArg(arena, run_index, maker, file_path, arg.flags.make_absolute), suffix,234 argv_list.appendAssumeCapacity("--listen=-");
107 });
108 argv_list.appendAssumeCapacity(resolved_arg);
109 man.hash.add(arg.flags.make_absolute);
110 man.hash.addBytes(resolved_arg);
111 },235 },
112 .file_content => {236 .protocol => {
113 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";237 argv_list.appendAssumeCapacity("--listen=-");
114 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";238 try input_dirs.append(gpa, if (conf_run.cwd.value) |lazy_cwd|
115 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);239 try maker.resolveLazyPathIndex(arena, lazy_cwd, run_index)
116240 else
117 var result: std.Io.Writer.Allocating = .init(arena);241 .cwd());
118 result.writer.writeAll(prefix) catch return error.OutOfMemory;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|265 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
121 return step.fail(maker, "unable to open input file {f}: {t}", .{ file_path, err });266 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
122 defer file.close(io);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, &.{});307 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
125 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {308 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
126 error.ReadFailed => switch (file_reader.err.?) {309 const dir_path = try maker.resolveLazyPath(arena, lazy_path, run_index);
127 error.Canceled => |e| return e,310 try input_dirs.append(gpa, dir_path);
128 else => |e| return step.fail(maker, "failed to read from {f}: {t}", .{ file_path, e }),311
129 },312 const has_sub_path = dir_path.sub_path.len > 0;
130 error.WriteFailed => return error.OutOfMemory,313 try owned_dirs.resize(gpa, owned_dirs.bit_length + 1, has_sub_path);
131 };314 try inherit_dirs.ensureUnusedCapacity(gpa, 1);
132 result.writer.writeAll(suffix) catch return error.OutOfMemory;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());517 switch (conf_run.stdin.u) {
135 man.hash.addBytesZ(prefix);518 .bytes => |bytes| {
136 man.hash.addBytesZ(suffix);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);
137 _ = try man.addInputPath(file_path, .{});523 _ = try man.addInputPath(file_path, .{});
138 },524 },
139 .artifact => {525 .none => {},
140 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";526 }
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;
147527
148 const file_path = producer_make_comp.installed_path orelse528 if (conf_run.captured_stdout.value) |captured| {
149 maker.generatedPath(producer.generated_bin.value.?);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, &.{533 if (conf_run.captured_stderr.value) |captured| {
152 prefix, try convertPathArg(arena, run_index, maker, file_path, arg.flags.make_absolute), suffix,534 man.hash.addBytes(captured.basename.slice(conf));
153 }));535 man.hash.add(conf_run.flags.stderr_trim_whitespace);
536 }
154537
155 man.hash.add(arg.flags.make_absolute);538 switch (conf_run.flags.stdio) {
156 man.hash.addBytesZ(prefix);539 .infer_from_args, .inherit, .zig_test, .protocol => {},
157 man.hash.addBytesZ(suffix);540 .check => {
158 _ = try man.addInputPath(file_path, .{});541 man.hash.addBytes(if (conf_run.expect_stderr_exact.value) |bytes| bytes.slice(conf) else "");
159 },542 man.hash.addBytes(if (conf_run.expect_stdout_exact.value) |bytes| bytes.slice(conf) else "");
160 .output_file, .output_directory => {543 for (conf_run.expect_stderr_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf));
161 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";544 for (conf_run.expect_stdout_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf));
162 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";545 man.hash.add(conf_run.flags2.expect_term_status);
163 const basename = arg.basename.value.?.slice(conf);546 man.hash.addOptional(conf_run.expect_term_value.value);
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 }
189 },547 },
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),
195 }548 }
196 }
197549
198 man.hash.add(conf_run.flags.test_runner_mode);550 for (conf_run.file_inputs.slice) |lazy_path| {
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| {
213 const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);551 const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);
214 _ = try man.addInputPath(file_path, .{});552 _ = try man.addInputPath(file_path, .{});
215 },553 }
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 }
223554
224 if (conf_run.captured_stderr.value) |captured| {555 if (conf_run.cwd.value) |lazy_path| {
225 man.hash.addBytes(captured.basename.slice(conf));556 const cwd_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);
226 man.hash.add(conf_run.flags.stderr_trim_whitespace);557 _ = man.hash.addBytes(try cwd_path.toString(arena));
227 }558 }
228559
229 switch (conf_run.flags.stdio) {560 // Whether the Run step has side effects *other than* updating the output arguments.
230 .infer_from_args, .inherit, .zig_test => {},561 // When fuzzing we need to always run the test runner to populate fuzz_tests.
231 .check => {562 const has_side_effects = graph.fuzzing or conf_run.flags.has_side_effects or any_cli_positionals or
232 man.hash.addBytes(if (conf_run.expect_stderr_exact.value) |bytes| bytes.slice(conf) else "");563 switch (conf_run.flags.stdio) {
233 man.hash.addBytes(if (conf_run.expect_stdout_exact.value) |bytes| bytes.slice(conf) else "");564 .infer_from_args => !any_output_args and
234 for (conf_run.expect_stderr_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf));565 conf_run.captured_stdout.value == null and
235 for (conf_run.expect_stdout_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf));566 conf_run.captured_stderr.value == null,
236 man.hash.add(conf_run.flags2.expect_term_status);567 .inherit => true,
237 man.hash.addOptional(conf_run.expect_term_value.value);568 .check, .zig_test, .protocol => false,
238 },569 };
239 }
240570
241 for (conf_run.file_inputs.slice) |lazy_path| {571 if (!has_side_effects and try step.cacheHitWatched(maker, &man, progress_node)) {
242 const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);572 // Cache hit; skip running command.
243 _ = try man.addInputPath(file_path, .{});573 const digest = man.hitDigestHex();
244 }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| {580 if (!any_discovered_inputs) {
247 const cwd_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);581 // We already know the final output paths; use them directly.
248 _ = man.hash.addBytes(try cwd_path.toString(arena));582 const digest = if (has_side_effects) man.hash.final() else man.missDigestHex();
249 }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.610 // We do not know the final output paths yet; use temporary directory to run the command.
252 // When fuzzing we need to always run the test runner to populate fuzz_tests.611 var rand_int: u64 = undefined;
253 const has_side_effects = graph.fuzzing or conf_run.flags.has_side_effects or any_cli_positionals or612 io.random(@ptrCast(&rand_int));
254 switch (conf_run.flags.stdio) {613 tmp_dir_path = ("tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int)).*;
255 .infer_from_args => !any_output_args and614
256 conf_run.captured_stdout.value == null and615 try populateGeneratedPathsCreateDirs(arena, run_index, maker, &tmp_dir_path, output_placeholders.items, switch (conf_run.flags.stdio) {
257 conf_run.captured_stderr.value == null,616 .infer_from_args, .inherit, .check, .zig_test => .{ .argv = argv_list.items },
258 .inherit => true,617 .protocol => .{ .protocol = .{
259 .check, .zig_test => false,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 },
260 };646 };
261647
262 if (!has_side_effects and try step.cacheHitWatched(maker, &man, progress_node)) {648 break :has_side_effects has_side_effects;
263 // Cache hit; skip running command.649 };
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);
289650
290 for (output_placeholders.items) |placeholder| {651 for (output_placeholders.items) |placeholder| {
291 const arg = placeholder.arg_index.get(conf);652 const arg = placeholder.arg_index.get(conf);
...@@ -296,10 +657,10 @@ pub fn make(...@@ -296,10 +657,10 @@ pub fn make(
296 var diagnostic: Cache.DepTokenizer.Token = undefined;657 var diagnostic: Cache.DepTokenizer.Token = undefined;
297 man.addInputDepFile(generated_path, &diagnostic) catch |err| switch (err) {658 man.addInputDepFile(generated_path, &diagnostic) catch |err| switch (err) {
298 error.OutOfMemory, error.Canceled => |e| return e,659 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}", .{
300 generated_path, diagnostic,661 generated_path, diagnostic,
301 }),662 }),
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}", .{
303 generated_path, e,664 generated_path, e,
304 }),665 }),
305 };666 };
...@@ -307,13 +668,13 @@ pub fn make(...@@ -307,13 +668,13 @@ pub fn make(
307 var diagnostic: Cache.Manifest.AddDiscoveredDepFileDiagnostic = undefined;668 var diagnostic: Cache.Manifest.AddDiscoveredDepFileDiagnostic = undefined;
308 man.addDiscoveredDepFile(generated_path, &diagnostic) catch |err| switch (err) {669 man.addDiscoveredDepFile(generated_path, &diagnostic) catch |err| switch (err) {
309 error.OutOfMemory, error.Canceled => |e| return e,670 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}", .{
311 generated_path, diagnostic.dep_tokenizer,672 generated_path, diagnostic.dep_tokenizer,
312 }),673 }),
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}", .{
314 generated_path, diagnostic.add_discovered_path,675 generated_path, diagnostic.add_discovered_path,
315 }),676 }),
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}", .{
317 generated_path, e,678 generated_path, e,
318 }),679 }),
319 };680 };
...@@ -331,7 +692,7 @@ pub fn make(...@@ -331,7 +692,7 @@ pub fn make(
331692
332 if (any_output) {693 if (any_output) {
333 // Rename into place.694 // 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 };
335 const dst_path: Path = .{ .root_dir = cache_root, .sub_path = "o" ++ Dir.path.sep_str ++ &digest };696 const dst_path: Path = .{ .root_dir = cache_root, .sub_path = "o" ++ Dir.path.sep_str ++ &digest };
336 Dir.rename(697 Dir.rename(
337 tmp_path.root_dir.handle,698 tmp_path.root_dir.handle,
...@@ -342,7 +703,7 @@ pub fn make(...@@ -342,7 +703,7 @@ pub fn make(
342 ) catch |err| switch (err) {703 ) catch |err| switch (err) {
343 error.DirNotEmpty => {704 error.DirNotEmpty => {
344 dst_path.root_dir.handle.deleteTree(io, dst_path.sub_path) catch |del_err|705 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
347 Dir.rename(708 Dir.rename(
348 tmp_path.root_dir.handle,709 tmp_path.root_dir.handle,
...@@ -350,17 +711,21 @@ pub fn make(...@@ -350,17 +711,21 @@ pub fn make(
350 dst_path.root_dir.handle,711 dst_path.root_dir.handle,
351 dst_path.sub_path,712 dst_path.sub_path,
352 io,713 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}", .{
354 tmp_path, dst_path, retry_err,715 tmp_path, dst_path, retry_err,
355 });716 });
356 },717 },
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}", .{
358 tmp_path, dst_path, err,719 tmp_path, dst_path, err,
359 }),720 }),
360 };721 };
361 }722 }
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
365 try populateGeneratedStdIo(maker, &conf_run, &digest);730 try populateGeneratedStdIo(maker, &conf_run, &digest);
366 try populateGeneratedPaths(maker, output_placeholders.items, &digest);731 try populateGeneratedPaths(maker, output_placeholders.items, &digest);
...@@ -386,19 +751,30 @@ fn thirdPartyToggle(...@@ -386,19 +751,30 @@ fn thirdPartyToggle(
386 enable: ?Configuration.String,751 enable: ?Configuration.String,
387 disable: ?Configuration.String,752 disable: ?Configuration.String,
388) void {753) void {
389 if (setting) {754 const string = (if (setting) enable else disable) orelse return;
390 if (enable) |string| {755 const slice = string.slice(conf);
391 const slice = string.slice(conf);756 if (man_hash) |h| h.addBytesZ(slice);
392 if (man_hash) |h| h.addBytesZ(slice);757 argv_list.appendAssumeCapacity(slice);
393 argv_list.appendAssumeCapacity(slice);758}
394 }759
395 } else {760fn thirdPartyToggleProtocol(
396 if (disable) |string| {761 gpa: Allocator,
397 const slice = string.slice(conf);762 man_hash: *Cache.HashHelper,
398 if (man_hash) |h| h.addBytesZ(slice);763 protocol_args: *std.ArrayList(u8),
399 argv_list.appendAssumeCapacity(slice);764 conf: *const Configuration,
400 }765 setting: bool,
401 }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);
402}778}
403779
404/// Reads stdout of a Zig test process until a termination condition is reached:780/// Reads stdout of a Zig test process until a termination condition is reached:
...@@ -411,7 +787,11 @@ fn waitZigTest(...@@ -411,7 +787,11 @@ fn waitZigTest(
411 run_index: Configuration.Step.Index,787 run_index: Configuration.Step.Index,
412 maker: *Maker,788 maker: *Maker,
413 child: *process.Child,789 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,
415 multi_reader: *Io.File.MultiReader,795 multi_reader: *Io.File.MultiReader,
416 opt_metadata: *?TestMetadata,796 opt_metadata: *?TestMetadata,
417 results: *Step.TestResults,797 results: *Step.TestResults,
...@@ -431,9 +811,6 @@ fn waitZigTest(...@@ -431,9 +811,6 @@ fn waitZigTest(
431 const io = graph.io;811 const io = graph.io;
432 const step = maker.stepByIndex(run_index);812 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
437 const stdout = multi_reader.reader(0);814 const stdout = multi_reader.reader(0);
438 const stderr = multi_reader.reader(1);815 const stderr = multi_reader.reader(1);
439816
...@@ -444,9 +821,17 @@ fn waitZigTest(...@@ -444,9 +821,17 @@ fn waitZigTest(
444 .out = &stdin_writer.interface,821 .out = &stdin_writer.interface,
445 };822 };
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
447 if (opt_metadata.*) |*md| {832 if (opt_metadata.*) |*md| {
448 // Previous unit test process died or was killed; we're continuing where it left off833 // 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 };
450 } else {835 } else {
451 // Running unit tests normally836 // Running unit tests normally
452 run.fuzz_tests.clearRetainingCapacity();837 run.fuzz_tests.clearRetainingCapacity();
...@@ -492,6 +877,7 @@ fn waitZigTest(...@@ -492,6 +877,7 @@ fn waitZigTest(
492 var body_r: std.Io.Reader = .fixed(body);877 var body_r: std.Io.Reader = .fixed(body);
493878
494 switch (header.tag) {879 switch (header.tag) {
880 else => {}, // ignore other messages
495 .zig_version => {881 .zig_version => {
496 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail(882 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail(
497 maker,883 maker,
...@@ -499,6 +885,7 @@ fn waitZigTest(...@@ -499,6 +885,7 @@ fn waitZigTest(
499 .{ builtin.zig_version_string, body },885 .{ builtin.zig_version_string, body },
500 );886 );
501 },887 },
888 .error_bundle => step.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body),
502 .test_metadata => {889 .test_metadata => {
503 // `metadata` would only be populated if we'd already seen a `test_metadata`, but we890 // `metadata` would only be populated if we'd already seen a `test_metadata`, but we
504 // only request it once (and importantly, we don't re-request it if we kill and891 // only request it once (and importantly, we don't re-request it if we kill and
...@@ -516,27 +903,27 @@ fn waitZigTest(...@@ -516,27 +903,27 @@ fn waitZigTest(
516903
517 const string_bytes = body_r.take(tm_hdr.string_bytes_len) catch unreachable;904 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);
520 opt_metadata.* = .{907 opt_metadata.* = .{
521 .string_bytes = try arena.dupe(u8, string_bytes),908 .string_bytes = try arena.dupe(u8, string_bytes),
522 .ns_per_test = try arena.alloc(u64, results.test_count),909 .ns_per_test = try arena.alloc(u64, results.test_count),
523 .names = names,910 .names = names,
524 .expected_panic_msgs = expected_panic_msgs,911 .expected_panic_msgs = expected_panic_msgs,
525 .next_index = 0,912 .next_index = 0,
526 .prog_node = progress_node,
527 };913 };
528 @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64));914 @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64));
529915
530 active_test_index = null;916 active_test_index = null;
531 last_update = .now(io, .awake);917 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 };
534 },920 },
535 .test_started => {921 .test_started => {
536 active_test_index = opt_metadata.*.?.next_index - 1;922 active_test_index = opt_metadata.*.?.next_index - 1;
537 last_update = .now(io, .awake);923 last_update = .now(io, .awake);
538 },924 },
539 .test_results => {925 .test_results => {
926 tests_prog_node.completeOne();
540 const md = &opt_metadata.*.?;927 const md = &opt_metadata.*.?;
541928
542 const tr_hdr = body_r.takeStruct(std.zig.Server.Message.TestResults, .little) catch unreachable;929 const tr_hdr = body_r.takeStruct(std.zig.Server.Message.TestResults, .little) catch unreachable;
...@@ -558,10 +945,12 @@ fn waitZigTest(...@@ -558,10 +945,12 @@ fn waitZigTest(
558 const name = md.testName(tr_hdr.index);945 const name = md.testName(tr_hdr.index);
559 const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");946 const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");
560 stderr.tossBuffered();947 stderr.tossBuffered();
561 if (stderr_bytes.len == 0) {948 if (stderr_bytes.len > 0) {
562 try step.addError(maker, "'{s}' failed without output", .{name});
563 } else {
564 try step.addError(maker, "'{s}' failed:\n{s}", .{ name, stderr_bytes });949 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});
565 }954 }
566 } else if (leak_count > 0) {955 } else if (leak_count > 0) {
567 const name = md.testName(tr_hdr.index);956 const name = md.testName(tr_hdr.index);
...@@ -581,9 +970,18 @@ fn waitZigTest(...@@ -581,9 +970,18 @@ fn waitZigTest(
581 md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds);970 md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds);
582 last_update = now;971 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 => {},
585 },984 },
586 else => {}, // ignore other messages
587 }985 }
588 }986 }
589}987}
...@@ -820,6 +1218,7 @@ const FuzzTestRunner = struct {...@@ -820,6 +1218,7 @@ const FuzzTestRunner = struct {
820 }1218 }
8211219
822 switch (header.tag) {1220 switch (header.tag) {
1221 else => {}, // ignore other messages
823 .zig_version => {1222 .zig_version => {
824 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail(1223 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail(
825 maker,1224 maker,
...@@ -882,7 +1281,6 @@ const FuzzTestRunner = struct {...@@ -882,7 +1281,6 @@ const FuzzTestRunner = struct {
882 f.pending_broadcasts.appendSliceAssumeCapacity(@ptrCast(&footer));1281 f.pending_broadcasts.appendSliceAssumeCapacity(@ptrCast(&footer));
883 }1282 }
884 },1283 },
885 else => {}, // ignore other messages
886 }1284 }
8871285
888 instance.message.clearRetainingCapacity();1286 instance.message.clearRetainingCapacity();
...@@ -1146,6 +1544,9 @@ fn evalZigTest(...@@ -1146,6 +1544,9 @@ fn evalZigTest(
1146 maker: *Maker,1544 maker: *Maker,
1147 progress_node: std.Progress.Node,1545 progress_node: std.Progress.Node,
1148 spawn_options: process.SpawnOptions,1546 spawn_options: process.SpawnOptions,
1547 protocol_args: []const u8,
1548 input_dirs: []const Cache.Path,
1549 man: ?*Cache.Manifest,
1149 fuzz_context: ?FuzzContext,1550 fuzz_context: ?FuzzContext,
1150) !void {1551) !void {
1151 if (fuzz_context != null) {1552 if (fuzz_context != null) {
...@@ -1194,6 +1595,10 @@ fn evalZigTest(...@@ -1194,6 +1595,10 @@ fn evalZigTest(
1194 maker,1595 maker,
1195 &child,1596 &child,
1196 progress_node,1597 progress_node,
1598 spawn_options.progress_node,
1599 protocol_args,
1600 input_dirs,
1601 man,
1197 &multi_reader,1602 &multi_reader,
1198 &test_metadata,1603 &test_metadata,
1199 &test_results,1604 &test_results,
...@@ -1323,7 +1728,6 @@ const TestMetadata = struct {...@@ -1323,7 +1728,6 @@ const TestMetadata = struct {
1323 expected_panic_msgs: []const u32,1728 expected_panic_msgs: []const u32,
1324 string_bytes: []const u8,1729 string_bytes: []const u8,
1325 next_index: u32,1730 next_index: u32,
1326 prog_node: std.Progress.Node,
13271731
1328 fn toCachedTestMetadata(tm: TestMetadata) CachedTestMetadata {1732 fn toCachedTestMetadata(tm: TestMetadata) CachedTestMetadata {
1329 return .{1733 return .{
...@@ -1346,16 +1750,14 @@ pub const CachedTestMetadata = struct {...@@ -1346,16 +1750,14 @@ pub const CachedTestMetadata = struct {
1346 }1750 }
1347};1751};
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 {
1350 while (metadata.next_index < metadata.names.len) {1754 while (metadata.next_index < metadata.names.len) {
1351 const i = metadata.next_index;1755 const i = metadata.next_index;
1352 metadata.next_index += 1;1756 metadata.next_index += 1;
13531757
1354 if (metadata.expected_panic_msgs[i] != 0) continue;1758 if (metadata.expected_panic_msgs[i] != 0) continue;
13551759
1356 const name = metadata.testName(i);1760 test_prog_node.setName(metadata.testName(i));
1357 if (sub_prog_node.*) |n| n.end();
1358 sub_prog_node.* = metadata.prog_node.start(name, 0);
13591761
1360 try client.serveRunTest(i);1762 try client.serveRunTest(i);
1361 return;1763 return;
...@@ -1402,7 +1804,7 @@ fn evalGeneric(...@@ -1402,7 +1804,7 @@ fn evalGeneric(
1402 var write_buffer: [1024]u8 = undefined;1804 var write_buffer: [1024]u8 = undefined;
1403 var stdin_writer = child.stdin.?.writerStreaming(io, &write_buffer);1805 var stdin_writer = child.stdin.?.writerStreaming(io, &write_buffer);
1404 _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {1806 _ = 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}", .{
1406 path, file_reader.err.?,1808 path, file_reader.err.?,
1407 }),1809 }),
1408 error.WriteFailed => return step.fail(maker, "failed to write to stdin: {t}", .{1810 error.WriteFailed => return step.fail(maker, "failed to write to stdin: {t}", .{
...@@ -1490,6 +1892,7 @@ fn evalGeneric(...@@ -1490,6 +1892,7 @@ fn evalGeneric(
14901892
1491const IndexedOutput = struct {1893const IndexedOutput = struct {
1492 index: u32,1894 index: u32,
1895 offset: u32,
1493 arg_index: Configuration.Step.Run.Arg.Index,1896 arg_index: Configuration.Step.Run.Arg.Index,
1494};1897};
14951898
...@@ -1516,13 +1919,16 @@ pub fn rerunInFuzzMode(...@@ -1516,13 +1919,16 @@ pub fn rerunInFuzzMode(
1516 var argv_list: std.ArrayList([]const u8) = .empty;1919 var argv_list: std.ArrayList([]const u8) = .empty;
1517 defer argv_list.deinit(gpa);1920 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| {
1520 const arg = arg_index.get(conf);1926 const arg = arg_index.get(conf);
1521 try argv_list.ensureUnusedCapacity(gpa, 1);1927 try argv_list.ensureUnusedCapacity(gpa, 1);
1522 switch (arg.flags.tag) {1928 switch (arg.flags.tag) {
1523 .string => {1929 .string => {
1524 const prefix = arg.prefix.value.?.slice(conf);1930 const string = arg.prefix.value.?.slice(conf);
1525 argv_list.appendAssumeCapacity(prefix);1931 argv_list.appendAssumeCapacity(string);
1526 },1932 },
1527 .path_file => {1933 .path_file => {
1528 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";1934 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
...@@ -1550,14 +1956,14 @@ pub fn rerunInFuzzMode(...@@ -1550,14 +1956,14 @@ pub fn rerunInFuzzMode(
1550 result.writer.writeAll(prefix) catch return error.OutOfMemory;1956 result.writer.writeAll(prefix) catch return error.OutOfMemory;
15511957
1552 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|1958 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 });
1554 defer file.close(io);1960 defer file.close(io);
15551961
1556 var file_reader = file.reader(io, &.{});1962 var file_reader = file.reader(io, &.{});
1557 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {1963 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
1558 error.ReadFailed => switch (file_reader.err.?) {1964 error.ReadFailed => switch (file_reader.err.?) {
1559 error.Canceled => |e| return e,1965 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 }),
1561 },1967 },
1562 error.WriteFailed => return error.OutOfMemory,1968 error.WriteFailed => return error.OutOfMemory,
1563 };1969 };
...@@ -1591,14 +1997,17 @@ pub fn rerunInFuzzMode(...@@ -1591,14 +1997,17 @@ pub fn rerunInFuzzMode(
1591 .enable_wine => thirdPartyToggle(null, &argv_list, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value),1997 .enable_wine => thirdPartyToggle(null, &argv_list, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value),
1592 }1998 }
1593 }1999 }
15942000 switch (conf_run.flags.stdio) {
1595 if (conf_run.flags.test_runner_mode) {2001 .infer_from_args, .inherit, .check => {},
1596 const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false);2002 .zig_test => {
15972003 const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false);
1598 try argv_list.ensureUnusedCapacity(gpa, 3);2004
1599 argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string}));2005 try argv_list.ensureUnusedCapacity(gpa, 3);
1600 argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed}));2006 argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string}));
1601 argv_list.appendAssumeCapacity("--listen=-");2007 argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed}));
2008 argv_list.appendAssumeCapacity("--listen=-");
2009 },
2010 .protocol => unreachable,
1602 }2011 }
16032012
1604 step.clearFailedCommand(gpa);2013 step.clearFailedCommand(gpa);
...@@ -1607,7 +2016,7 @@ pub fn rerunInFuzzMode(...@@ -1607,7 +2016,7 @@ pub fn rerunInFuzzMode(
1607 var rand_int: u64 = undefined;2016 var rand_int: u64 = undefined;
1608 io.random(@ptrCast(&rand_int));2017 io.random(@ptrCast(&rand_int));
1609 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);2018 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, .{
1611 .fuzz = fuzz,2020 .fuzz = fuzz,
1612 });2021 });
1613}2022}
...@@ -1633,7 +2042,16 @@ fn populateGeneratedPathsCreateDirs(...@@ -1633,7 +2042,16 @@ fn populateGeneratedPathsCreateDirs(
1633 maker: *Maker,2042 maker: *Maker,
1634 output_dir_path: []const u8,2043 output_dir_path: []const u8,
1635 output_placeholders: []const IndexedOutput,2044 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 },
1637) !void {2055) !void {
1638 const step = maker.stepByIndex(run_index);2056 const step = maker.stepByIndex(run_index);
1639 const conf = &maker.scanned_config.configuration;2057 const conf = &maker.scanned_config.configuration;
...@@ -1653,16 +2071,52 @@ fn populateGeneratedPathsCreateDirs(...@@ -1653,16 +2071,52 @@ fn populateGeneratedPathsCreateDirs(
1653 const create_path: Path = .{2071 const create_path: Path = .{
1654 .root_dir = cache_root,2072 .root_dir = cache_root,
1655 .sub_path = switch (arg.flags.tag) {2073 .sub_path = switch (arg.flags.tag) {
2074 else => unreachable,
1656 .output_file => Dir.path.dirname(generated_path.sub_path).?,2075 .output_file => Dir.path.dirname(generated_path.sub_path).?,
1657 .output_directory => generated_path.sub_path,2076 .output_directory => generated_path.sub_path,
1658 else => unreachable,
1659 },2077 },
1660 };2078 };
1661 create_path.root_dir.handle.createDirPath(io, create_path.sub_path) catch |err|2079 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);2082 switch (populate) {
1665 argv[placeholder.index] = try mem.concat(arena, u8, &.{ prefix, arg_output_path, suffix });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 }
1666 }2120 }
1667}2121}
16682122
...@@ -1711,6 +2165,11 @@ fn runCommand(...@@ -1711,6 +2165,11 @@ fn runCommand(
1711 maker: *Maker,2165 maker: *Maker,
1712 progress_node: std.Progress.Node,2166 progress_node: std.Progress.Node,
1713 argv: []const []const u8,2167 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,
1714 has_side_effects: bool,2173 has_side_effects: bool,
1715 output_dir_path: []const u8,2174 output_dir_path: []const u8,
1716 fuzz_context: ?FuzzContext,2175 fuzz_context: ?FuzzContext,
...@@ -1777,10 +2236,16 @@ fn runCommand(...@@ -1777,10 +2236,16 @@ fn runCommand(
1777 progress_node,2236 progress_node,
1778 argv,2237 argv,
1779 &environ_map,2238 &environ_map,
2239 inherit_dirs,
2240 inherit_files,
2241 protocol_args,
2242 input_dirs,
2243 man,
1780 has_side_effects,2244 has_side_effects,
1781 fuzz_context,2245 fuzz_context,
1782 ) catch |err| term: {2246 ) catch |err| term: {
1783 switch (err) {2247 switch (err) {
2248 else => {},
1784 error.InvalidExe, // cpu arch mismatch2249 error.InvalidExe, // cpu arch mismatch
1785 error.FileNotFound, // can happen with a wrong dynamic linker path2250 error.FileNotFound, // can happen with a wrong dynamic linker path
1786 => interpret: {2251 => interpret: {
...@@ -1794,24 +2259,21 @@ fn runCommand(...@@ -1794,24 +2259,21 @@ fn runCommand(
1794 const root_module = producer.root_module.get(conf);2259 const root_module = producer.root_module.get(conf);
1795 const root_module_target = root_module.resolved_target.get(conf).?.result.get(conf);2260 const root_module_target = root_module.resolved_target.get(conf).?.result.get(conf);
1796 const root_target = root_module_target.unwrapTarget(conf);2261 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
1799 const host: std.Target = std.zig.system.resolveTargetQuery(io, .{}) catch |he| switch (he) {2264 const host: std.Target = std.zig.system.resolveTargetQuery(io, .{}) catch |he| switch (he) {
1800 error.Canceled => |e| return e,2265 error.Canceled => |e| return e,
1801 else => builtin.target,2266 else => builtin.target,
1802 };2267 };
18032268
1804 const need_cross_libc = link_libc and root_target.os.tag == .linux and2269 const need_cross_libc = root_target.os.tag == .linux and
1805 switch (producer.flags2.linkage) {2270 config.flags.link_libc and config.flags.link_mode == .dynamic;
1806 .static => false,
1807 .dynamic => true,
1808 .default => root_target.isGnuLibC(),
1809 };
1810 switch (std.zig.system.getExternalExecutor(io, &root_target, .{2271 switch (std.zig.system.getExternalExecutor(io, &root_target, .{
1811 .host_cpu_arch = host.cpu.arch,2272 .host_cpu_arch = host.cpu.arch,
1812 .host_os_tag = host.os.tag,2273 .host_os_tag = host.os.tag,
1813 .qemu_fixes_dl = need_cross_libc and graph.libc_runtimes_dir != null,2274 .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,
1815 })) {2277 })) {
1816 .native, .rosetta => {2278 .native, .rosetta => {
1817 if (allow_skip) return error.MakeSkipped;2279 if (allow_skip) return error.MakeSkipped;
...@@ -1874,7 +2336,7 @@ fn runCommand(...@@ -1874,7 +2336,7 @@ fn runCommand(
1874 interp_argv.appendAssumeCapacity(bin_name);2336 interp_argv.appendAssumeCapacity(bin_name);
1875 interp_argv.appendAssumeCapacity("--dir=.");2337 interp_argv.appendAssumeCapacity("--dir=.");
1876 for (conf_run.preopens.slice) |preopen| {2338 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);
1878 path.root_dir.handle.createDirPath(io, path.subPathOrDot()) catch |e|2340 path.root_dir.handle.createDirPath(io, path.subPathOrDot()) catch |e|
1879 return step.fail(maker, "failed creating directory {f}: {t}", .{ path, e });2341 return step.fail(maker, "failed creating directory {f}: {t}", .{ path, e });
1880 interp_argv.appendAssumeCapacity(try arena.print("--dir={f}::{s}", .{ path, preopen.name.slice(conf) }));2342 interp_argv.appendAssumeCapacity(try arena.print("--dir={f}::{s}", .{ path, preopen.name.slice(conf) }));
...@@ -1928,6 +2390,11 @@ fn runCommand(...@@ -1928,6 +2390,11 @@ fn runCommand(
1928 progress_node,2390 progress_node,
1929 interp_argv.items,2391 interp_argv.items,
1930 &environ_map,2392 &environ_map,
2393 inherit_dirs,
2394 inherit_files,
2395 protocol_args,
2396 input_dirs,
2397 man,
1931 has_side_effects,2398 has_side_effects,
1932 fuzz_context,2399 fuzz_context,
1933 ) catch |e| {2400 ) catch |e| {
...@@ -1937,21 +2404,21 @@ fn runCommand(...@@ -1937,21 +2404,21 @@ fn runCommand(
1937 };2404 };
1938 },2405 },
1939 error.MakeFailed, error.OutOfMemory, error.Canceled => |e| return e,2406 error.MakeFailed, error.OutOfMemory, error.Canceled => |e| return e,
1940 else => {},
1941 }2407 }
1942 return step.fail(maker, "failed to spawn and capture stdio from {s}: {t}", .{ argv[0], err });2408 return step.fail(maker, "failed to spawn and capture stdio from {s}: {t}", .{ argv[0], err });
1943 };2409 };
19442410
1945 const generic_result = opt_generic_result orelse {2411 const generic_result = switch (conf_run.flags.stdio) {
1946 assert(conf_run.flags.stdio == .zig_test);2412 .infer_from_args, .inherit, .check => opt_generic_result.?,
1947 // Specific errors have already been reported, and test results are populated. All we need2413 .zig_test, .protocol => {
1948 // to do is report step failure if any test failed.2414 assert(opt_generic_result == null);
1949 if (!step.test_results.isSuccess()) return error.MakeFailed;2415 // Specific errors have already been reported, and test results are populated. All we need
1950 return;2416 // to do is report step failure if any test failed.
2417 if (!step.test_results.isSuccess()) return error.MakeFailed;
2418 return;
2419 },
1951 };2420 };
1952
1953 assert(fuzz_context == null);2421 assert(fuzz_context == null);
1954 assert(conf_run.flags.stdio != .zig_test);
19552422
1956 // Capture stdout and stderr to GeneratedFile objects.2423 // Capture stdout and stderr to GeneratedFile objects.
1957 const Stream = struct {2424 const Stream = struct {
...@@ -1978,7 +2445,7 @@ fn runCommand(...@@ -1978,7 +2445,7 @@ fn runCommand(
19782445
1979 const sub_path_parent = output_path.dirname().?;2446 const sub_path_parent = output_path.dirname().?;
1980 sub_path_parent.root_dir.handle.createDirPath(io, sub_path_parent.sub_path) catch |err|2447 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
1983 const data = switch (stream.trim_whitespace) {2450 const data = switch (stream.trim_whitespace) {
1984 .none => stream.bytes.?,2451 .none => stream.bytes.?,
...@@ -1989,12 +2456,12 @@ fn runCommand(...@@ -1989,12 +2456,12 @@ fn runCommand(
1989 output_path.root_dir.handle.writeFile(io, .{2456 output_path.root_dir.handle.writeFile(io, .{
1990 .sub_path = output_path.sub_path,2457 .sub_path = output_path.sub_path,
1991 .data = data,2458 .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 });
1993 }2460 }
1994 }2461 }
19952462
1996 switch (conf_run.flags.stdio) {2463 switch (conf_run.flags.stdio) {
1997 .zig_test => unreachable,2464 .zig_test, .protocol => unreachable,
1998 .check => {2465 .check => {
1999 if (conf_run.expect_stderr_exact.value) |bytes| {2466 if (conf_run.expect_stderr_exact.value) |bytes| {
2000 const expected_bytes = bytes.slice(conf);2467 const expected_bytes = bytes.slice(conf);
...@@ -2186,6 +2653,11 @@ fn spawnChildAndCollect(...@@ -2186,6 +2653,11 @@ fn spawnChildAndCollect(
2186 progress_node: std.Progress.Node,2653 progress_node: std.Progress.Node,
2187 argv: []const []const u8,2654 argv: []const []const u8,
2188 environ_map: *EnvMap,2655 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,
2189 has_side_effects: bool,2661 has_side_effects: bool,
2190 fuzz_context: ?FuzzContext,2662 fuzz_context: ?FuzzContext,
2191) !?EvalGenericResult {2663) !?EvalGenericResult {
...@@ -2199,7 +2671,10 @@ fn spawnChildAndCollect(...@@ -2199,7 +2671,10 @@ fn spawnChildAndCollect(
21992671
2200 if (fuzz_context != null) {2672 if (fuzz_context != null) {
2201 assert(!has_side_effects);2673 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 }
2203 }2678 }
22042679
2205 const child_cwd: process.Child.Cwd = if (conf_run.cwd.value) |lazy_cwd|2680 const child_cwd: process.Child.Cwd = if (conf_run.cwd.value) |lazy_cwd|
...@@ -2230,68 +2705,88 @@ fn spawnChildAndCollect(...@@ -2230,68 +2705,88 @@ fn spawnChildAndCollect(
2230 .cwd = child_cwd,2705 .cwd = child_cwd,
2231 .environ_map = environ_map,2706 .environ_map = environ_map,
2232 .request_resource_usage_statistics = true,2707 .request_resource_usage_statistics = true,
2233 .stdin = if (conf_run.stdin.u != .none) s: {2708 .stdin = if (conf_run.stdin.u != .none) stdin: {
2234 assert(conf_run.flags.stdio != .inherit);2709 assert(conf_run.flags.stdio != .inherit);
2235 break :s .pipe;2710 break :stdin .pipe;
2236 } else switch (conf_run.flags.stdio) {2711 } else switch (conf_run.flags.stdio) {
2237 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore,2712 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore,
2238 .inherit => .inherit,2713 .inherit => .inherit,
2239 .check => .ignore,2714 .check => .ignore,
2240 .zig_test => .pipe,2715 .zig_test, .protocol => .pipe,
2241 },2716 },
2242 .stdout = if (conf_run.captured_stdout.value != null) .pipe else switch (conf_run.flags.stdio) {2717 .stdout = if (conf_run.captured_stdout.value != null) .pipe else switch (conf_run.flags.stdio) {
2243 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore,2718 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore,
2244 .inherit => .inherit,2719 .inherit => .inherit,
2245 .check => if (checksContainStdout(&conf_run)) .pipe else .ignore,2720 .check => if (checksContainStdout(&conf_run)) .pipe else .ignore,
2246 .zig_test => .pipe,2721 .zig_test, .protocol => .pipe,
2247 },2722 },
2248 .stderr = if (conf_run.captured_stderr.value != null) .pipe else switch (conf_run.flags.stdio) {2723 .stderr = if (conf_run.captured_stderr.value != null) .pipe else switch (conf_run.flags.stdio) {
2249 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .pipe,2724 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .pipe,
2250 .inherit => if (maker.protocol_server == null) .inherit else .pipe,2725 .inherit => if (maker.protocol_server == null) .inherit else .pipe,
2251 .check => .pipe,2726 .check => .pipe,
2252 .zig_test => .pipe,2727 .zig_test, .protocol => .pipe,
2253 },2728 },
2729 .inherit_dirs = inherit_dirs,
2730 .inherit_files = inherit_files,
2254 };2731 };
22552732
2256 if (maker.protocol_server != null) {2733 if (maker.protocol_server != null) {
2257 if (spawn_options.stdin == .inherit) {2734 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", .{});
2259 }2736 }
2260 if (spawn_options.stdout == .inherit) {2737 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", .{});
2262 }2739 }
2263 assert(spawn_options.stderr != .inherit);2740 assert(spawn_options.stderr != .inherit);
2264 }2741 }
22652742
2266 if (conf_run.flags.stdio == .zig_test) {2743 switch (conf_run.flags.stdio) {
2267 try setColorEnvironmentVariables(&conf_run, environ_map, graph.stderr_mode.?);2744 .infer_from_args, .inherit, .check => {
2268 const started: Io.Clock.Timestamp = .now(io, .awake);2745 const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit;
2269 const result = evalZigTest(run, run_index, maker, progress_node, spawn_options, fuzz_context) catch |err| switch (err) {2746 if (!conf_run.flags.disable_zig_progress and !inherit) {
2270 error.Canceled => |e| return e,2747 spawn_options.progress_node = progress_node;
2271 else => |e| e,2748 }
2272 };2749 const terminal_mode: Io.Terminal.Mode = if (inherit) m: {
2273 step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);2750 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
2274 try result;2751 break :m stderr.terminal_mode;
2275 return null;2752 } else .no_color;
2276 } else {2753 defer if (inherit) io.unlockStderr();
2277 const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit;2754 try setColorEnvironmentVariables(&conf_run, environ_map, terminal_mode);
2278 if (!conf_run.flags.disable_zig_progress and !inherit) {2755
2279 spawn_options.progress_node = progress_node;2756 const started: Io.Clock.Timestamp = .now(io, .awake);
2280 }2757 const result = evalGeneric(arena, run_index, maker, spawn_options) catch |err| switch (err) {
2281 const terminal_mode: Io.Terminal.Mode = if (inherit) m: {2758 error.Canceled => |e| return e,
2282 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);2759 else => |e| e,
2283 break :m stderr.terminal_mode;2760 };
2284 } else .no_color;2761 step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);
2285 defer if (inherit) io.unlockStderr();2762 return try result;
2286 try setColorEnvironmentVariables(&conf_run, environ_map, terminal_mode);2763 },
22872764 .zig_test, .protocol => |stdio| {
2288 const started: Io.Clock.Timestamp = .now(io, .awake);2765 spawn_options.progress_node = if (conf_run.flags.disable_zig_progress or fuzz_context != null)
2289 const result = evalGeneric(arena, run_index, maker, spawn_options) catch |err| switch (err) {2766 .none
2290 error.Canceled => |e| return e,2767 else
2291 else => |e| e,2768 progress_node.start(@tagName(stdio), 0);
2292 };2769 defer spawn_options.progress_node.end();
2293 step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);2770 try setColorEnvironmentVariables(&conf_run, environ_map, graph.stderr_mode.?);
2294 return try result;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 },
2295 }2790 }
2296}2791}
22972792
...@@ -2341,7 +2836,7 @@ fn setColorEnvironmentVariables(...@@ -2341,7 +2836,7 @@ fn setColorEnvironmentVariables(
2341 .auto => {2836 .auto => {
2342 const capture_stderr = conf_run.captured_stderr.value != null or switch (conf_run.flags.stdio) {2837 const capture_stderr = conf_run.captured_stderr.value != null or switch (conf_run.flags.stdio) {
2343 .check => checksContainStderr(conf_run),2838 .check => checksContainStderr(conf_run),
2344 .infer_from_args, .inherit, .zig_test => false,2839 .infer_from_args, .inherit, .zig_test, .protocol => false,
2345 };2840 };
2346 if (capture_stderr) {2841 if (capture_stderr) {
2347 continue :color .disable;2842 continue :color .disable;
lib/compiler/resinator/main.zig+3-6
...@@ -790,8 +790,7 @@ fn cliDiagnosticsToErrorBundle(...@@ -790,8 +790,7 @@ fn cliDiagnosticsToErrorBundle(
790) !ErrorBundle {790) !ErrorBundle {
791 @branchHint(.cold);791 @branchHint(.cold);
792792
793 var bundle: ErrorBundle.Wip = undefined;793 var bundle: ErrorBundle.Wip = try .init(gpa);
794 try bundle.init(gpa);
795 errdefer bundle.deinit();794 errdefer bundle.deinit();
796795
797 try bundle.addRootErrorMessage(.{796 try bundle.addRootErrorMessage(.{
...@@ -837,8 +836,7 @@ fn diagnosticsToErrorBundle(...@@ -837,8 +836,7 @@ fn diagnosticsToErrorBundle(
837) !ErrorBundle {836) !ErrorBundle {
838 @branchHint(.cold);837 @branchHint(.cold);
839838
840 var bundle: ErrorBundle.Wip = undefined;839 var bundle: ErrorBundle.Wip = try .init(gpa);
841 try bundle.init(gpa);
842 errdefer bundle.deinit();840 errdefer bundle.deinit();
843841
844 var msg_buf: std.Io.Writer.Allocating = .init(gpa);842 var msg_buf: std.Io.Writer.Allocating = .init(gpa);
...@@ -919,8 +917,7 @@ fn diagnosticsToErrorBundle(...@@ -919,8 +917,7 @@ fn diagnosticsToErrorBundle(
919917
920fn errorStringToErrorBundle(allocator: Allocator, comptime format: []const u8, args: anytype) !ErrorBundle {918fn errorStringToErrorBundle(allocator: Allocator, comptime format: []const u8, args: anytype) !ErrorBundle {
921 @branchHint(.cold);919 @branchHint(.cold);
922 var bundle: ErrorBundle.Wip = undefined;920 var bundle: ErrorBundle.Wip = try .init(allocator);
923 try bundle.init(allocator);
924 errdefer bundle.deinit();921 errdefer bundle.deinit();
925 try bundle.addRootErrorMessage(.{922 try bundle.addRootErrorMessage(.{
926 .msg = try bundle.printString(format, args),923 .msg = try bundle.printString(format, args),
lib/compiler/util.zig+1-2
...@@ -10,8 +10,7 @@ pub fn aroDiagnosticsToErrorBundle(...@@ -10,8 +10,7 @@ pub fn aroDiagnosticsToErrorBundle(
10) !ErrorBundle {10) !ErrorBundle {
11 @branchHint(.cold);11 @branchHint(.cold);
1212
13 var bundle: ErrorBundle.Wip = undefined;13 var bundle: ErrorBundle.Wip = try .init(gpa);
14 try bundle.init(gpa);
15 errdefer bundle.deinit();14 errdefer bundle.deinit();
1615
17 if (fail_msg) |msg| {16 if (fail_msg) |msg| {
lib/std/Build.zig+5-5
...@@ -768,9 +768,8 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {...@@ -768,9 +768,8 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
768768
769 const run_step = Step.Run.create(b, step_name);769 const run_step = Step.Run.create(b, step_name);
770 run_step.producer = exe;770 run_step.producer = exe;
771 run_step.addArtifactArg(exe);
771 if (exe.kind == .@"test") {772 if (exe.kind == .@"test") {
772 run_step.addArtifactArg(exe);
773
774 const test_server_mode: bool = s: {773 const test_server_mode: bool = s: {
775 if (exe.test_runner) |r| break :s r.mode == .server;774 if (exe.test_runner) |r| break :s r.mode == .server;
776 if (exe.use_llvm == false) {775 if (exe.use_llvm == false) {
...@@ -802,8 +801,6 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {...@@ -802,8 +801,6 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
802 // communicate failure via its exit code.801 // communicate failure via its exit code.
803 run_step.expectExitCode(0);802 run_step.expectExitCode(0);
804 }803 }
805 } else {
806 run_step.addArtifactArg(exe);
807 }804 }
808805
809 return run_step;806 return run_step;
...@@ -2696,12 +2693,15 @@ fn validateConfigureDependency(lazy_path: LazyPath) void {...@@ -2696,12 +2693,15 @@ fn validateConfigureDependency(lazy_path: LazyPath) void {
2696 .src_path, .cwd_relative, .dependency => {}, // OK2693 .src_path, .cwd_relative, .dependency => {}, // OK
2697 .generated => @panic("configure phase cannot depend on files generated during make phase"),2694 .generated => @panic("configure phase cannot depend on files generated during make phase"),
2698 .relative => |relative| switch (relative.base) {2695 .relative => |relative| switch (relative.base) {
2699 .cwd, .build_root, .local_cache, .global_cache, .zig_exe, .zig_lib => {}, // OK2696 .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"),
2700 .install_prefix,2698 .install_prefix,
2701 .install_lib,2699 .install_lib,
2702 .install_bin,2700 .install_bin,
2703 .install_include,2701 .install_include,
2704 => @panic("configure phase cannot depend on files installed during make phase"),2702 => @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"),
2705 },2705 },
2706 }2706 }
2707}2707}
lib/std/Build/Configuration.zig+2-2
...@@ -665,7 +665,7 @@ pub const Step = extern struct {...@@ -665,7 +665,7 @@ pub const Step = extern struct {
665 pub const Tag = enum(u2) { none, bytes, lazy_path };665 pub const Tag = enum(u2) { none, bytes, lazy_path };
666 };666 };
667 pub const TrimWhitespace = enum(u2) { none, all, leading, trailing };667 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
670 pub const ExpectTermStatus = enum(u2) { exited, signal, stopped, unknown };670 pub const ExpectTermStatus = enum(u2) { exited, signal, stopped, unknown };
671671
...@@ -675,7 +675,6 @@ pub const Step = extern struct {...@@ -675,7 +675,6 @@ pub const Step = extern struct {
675 skip_foreign_checks: bool,675 skip_foreign_checks: bool,
676 failing_to_execute_foreign_is_an_error: bool,676 failing_to_execute_foreign_is_an_error: bool,
677 has_side_effects: bool,677 has_side_effects: bool,
678 test_runner_mode: bool,
679 color: Color,678 color: Color,
680 stdin: StdIn.Tag,679 stdin: StdIn.Tag,
681 stdio: StdIo,680 stdio: StdIo,
...@@ -1576,6 +1575,7 @@ pub const LazyPath = union(@This().Tag) {...@@ -1576,6 +1575,7 @@ pub const LazyPath = union(@This().Tag) {
1576 install_lib,1575 install_lib,
1577 install_bin,1576 install_bin,
1578 install_include,1577 install_include,
1578 libc_runtimes,
1579 };1579 };
1580 };1580 };
1581};1581};
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...@@ -462,13 +462,13 @@ pub fn write(b: *std.Build, wc: *Configuration.Wip, writer: *std.Io.Writer) !voi
462 .skip_foreign_checks = run.skip_foreign_checks,462 .skip_foreign_checks = run.skip_foreign_checks,
463 .failing_to_execute_foreign_is_an_error = run.failing_to_execute_foreign_is_an_error,463 .failing_to_execute_foreign_is_an_error = run.failing_to_execute_foreign_is_an_error,
464 .has_side_effects = run.has_side_effects,464 .has_side_effects = run.has_side_effects,
465 .test_runner_mode = run.test_runner_mode,
466 .color = run.color,465 .color = run.color,
467 .stdio = switch (run.stdio) {466 .stdio = switch (run.stdio) {
468 .infer_from_args => .infer_from_args,467 .infer_from_args => .infer_from_args,
469 .inherit => .inherit,468 .inherit => .inherit,
470 .check => .check,469 .check => .check,
471 .zig_test => .zig_test,470 .zig_test => .zig_test,
471 .protocol => .protocol,
472 },472 },
473 .stdin = switch (run.stdin) {473 .stdin = switch (run.stdin) {
474 .none => .none,474 .none => .none,
lib/std/Build/Step.zig+1-1
...@@ -95,7 +95,7 @@ pub fn init(options: StepOptions) Step {...@@ -95,7 +95,7 @@ pub fn init(options: StepOptions) Step {
9595
96 return .{96 return .{
97 .tag = options.tag,97 .tag = options.tag,
98 .name = arena.dupe(u8, options.name) catch @panic("OOM"),98 .name = options.owner.graph.dupeString(options.name),
99 .owner = options.owner,99 .owner = options.owner,
100 .dependencies = .empty,100 .dependencies = .empty,
101 .max_rss = options.max_rss,101 .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...@@ -41,5 +41,5 @@ pub fn create(owner: *std.Build, file: std.Build.LazyPath, options: Options) *Ch
41}41}
4242
43pub fn setName(check_file: *CheckFile, name: []const u8) void {43pub 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);
45}45}
lib/std/Build/Step/Run.zig+17-8
...@@ -92,7 +92,6 @@ captured_stdout: ?*CapturedStdIo,...@@ -92,7 +92,6 @@ captured_stdout: ?*CapturedStdIo,
92captured_stderr: ?*CapturedStdIo,92captured_stderr: ?*CapturedStdIo,
9393
94has_side_effects: bool,94has_side_effects: bool,
95test_runner_mode: bool = false,
9695
97/// If this Run step was produced by a Compile step, it is tracked here.96/// If this Run step was produced by a Compile step, it is tracked here.
98producer: ?*Step.Compile,97producer: ?*Step.Compile,
...@@ -127,7 +126,10 @@ pub const StdIo = union(enum) {...@@ -127,7 +126,10 @@ pub const StdIo = union(enum) {
127 check: std.ArrayList(Check),126 check: std.ArrayList(Check),
128 /// This Run step is running a zig unit test binary and will communicate127 /// This Run step is running a zig unit test binary and will communicate
129 /// extra metadata over the IPC protocol.128 /// extra metadata over the IPC protocol.
129 /// Deprecated, use `.protocol`.
130 zig_test,130 zig_test,
131 /// This Run step is running a binary that will communicate over the IPC protocol.
132 protocol,
131133
132 pub const Check = union(enum) {134 pub const Check = union(enum) {
133 expect_stderr_exact: []const u8,135 expect_stderr_exact: []const u8,
...@@ -239,14 +241,17 @@ pub fn create(owner: *std.Build, name: []const u8) *Run {...@@ -239,14 +241,17 @@ pub fn create(owner: *std.Build, name: []const u8) *Run {
239}241}
240242
241pub fn setName(run: *Run, name: []const u8) void {243pub fn setName(run: *Run, name: []const u8) void {
242 run.step.name = name;244 run.step.name = run.step.owner.graph.dupeString(name);
243 run.rename_step_with_output_arg = false;245 run.rename_step_with_output_arg = false;
244}246}
245247
248/// Deprecated, use `enableProtocolMode`.
246pub fn enableTestRunnerMode(run: *Run) void {249pub fn enableTestRunnerMode(run: *Run) void {
247 if (run.test_runner_mode) return;
248 run.stdio = .zig_test;250 run.stdio = .zig_test;
249 run.test_runner_mode = true;251}
252
253pub fn enableProtocolMode(run: *Run) void {
254 run.stdio = .protocol;
250}255}
251256
252pub const ArgOptions = struct {257pub const ArgOptions = struct {
...@@ -750,8 +755,10 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void {...@@ -750,8 +755,10 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void {
750}755}
751756
752pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {757pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {
753 assert(run.stdio != .inherit);758 switch (run.stdio) {
754 assert(run.stdio != .zig_test);759 .infer_from_args, .check => {},
760 .inherit, .zig_test, .protocol => unreachable,
761 }
755762
756 const b = run.step.owner;763 const b = run.step.owner;
757 const graph = b.graph;764 const graph = b.graph;
...@@ -771,8 +778,10 @@ pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPa...@@ -771,8 +778,10 @@ pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPa
771}778}
772779
773pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {780pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {
774 assert(run.stdio != .inherit);781 switch (run.stdio) {
775 assert(run.stdio != .zig_test);782 .infer_from_args, .check => {},
783 .inherit, .zig_test, .protocol => unreachable,
784 }
776785
777 const b = run.step.owner;786 const b = run.step.owner;
778 const graph = b.graph;787 const graph = b.graph;
lib/std/Io.zig+21-18
...@@ -213,13 +213,13 @@ pub const VTable = struct {...@@ -213,13 +213,13 @@ pub const VTable = struct {
213 processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void,213 processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void,
214 processSetCurrentPath: *const fn (?*anyopaque, []const u8) std.process.SetCurrentPathError!void,214 processSetCurrentPath: *const fn (?*anyopaque, []const u8) std.process.SetCurrentPathError!void,
215 processReplace: *const fn (?*anyopaque, std.process.ReplaceOptions) std.process.ReplaceError,215 processReplace: *const fn (?*anyopaque, std.process.ReplaceOptions) std.process.ReplaceError,
216 processReplacePath: *const fn (?*anyopaque, Dir, std.process.ReplaceOptions) std.process.ReplaceError,
217 processSpawn: *const fn (?*anyopaque, std.process.SpawnOptions) std.process.SpawnError!std.process.Child,216 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,
219 childWait: *const fn (?*anyopaque, *std.process.Child) std.process.Child.WaitError!std.process.Child.Term,217 childWait: *const fn (?*anyopaque, *std.process.Child) std.process.Child.WaitError!std.process.Child.Term,
220 childKill: *const fn (?*anyopaque, *std.process.Child) void,218 childKill: *const fn (?*anyopaque, *std.process.Child) void,
221219
222 progressParentFile: *const fn (?*anyopaque) std.Progress.ParentFileError!File,220 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
224 now: *const fn (?*anyopaque, Clock) Timestamp,224 now: *const fn (?*anyopaque, Clock) Timestamp,
225 clockResolution: *const fn (?*anyopaque, Clock) Clock.ResolutionError!Duration,225 clockResolution: *const fn (?*anyopaque, Clock) Clock.ResolutionError!Duration,
...@@ -827,6 +827,10 @@ pub const UnexpectedError = error{...@@ -827,6 +827,10 @@ pub const UnexpectedError = error{
827 Unexpected,827 Unexpected,
828};828};
829829
830pub const InheritParentHandleError = error{
831 UnsupportedOperation,
832} || Cancelable || UnexpectedError;
833
830pub const Clock = enum {834pub const Clock = enum {
831 /// A settable system-wide clock that measures real (i.e. wall-clock)835 /// A settable system-wide clock that measures real (i.e. wall-clock)
832 /// time. This clock is affected by discontinuous jumps in the system836 /// time. This clock is affected by discontinuous jumps in the system
...@@ -2797,13 +2801,13 @@ pub const failing: std.Io = .{...@@ -2797,13 +2801,13 @@ pub const failing: std.Io = .{
2797 .processSetCurrentDir = failingProcessSetCurrentDir,2801 .processSetCurrentDir = failingProcessSetCurrentDir,
2798 .processSetCurrentPath = failingProcessSetCurrentPath,2802 .processSetCurrentPath = failingProcessSetCurrentPath,
2799 .processReplace = failingProcessReplace,2803 .processReplace = failingProcessReplace,
2800 .processReplacePath = failingProcessReplacePath,
2801 .processSpawn = failingProcessSpawn,2804 .processSpawn = failingProcessSpawn,
2802 .processSpawnPath = failingProcessSpawnPath,
2803 .childWait = unreachableChildWait,2805 .childWait = unreachableChildWait,
2804 .childKill = unreachableChildKill,2806 .childKill = unreachableChildKill,
28052807
2806 .progressParentFile = failingProgressParentFile,2808 .progressParentFile = failingProgressParentFile,
2809 .inheritParentDir = failingInheritParentDir,
2810 .inheritParentFile = failingInheritParentFile,
28072811
2808 .random = noRandom,2812 .random = noRandom,
2809 .randomSecure = failingRandomSecure,2813 .randomSecure = failingRandomSecure,
...@@ -3451,26 +3455,12 @@ pub fn failingProcessReplace(userdata: ?*anyopaque, options: std.process.Replace...@@ -3451,26 +3455,12 @@ pub fn failingProcessReplace(userdata: ?*anyopaque, options: std.process.Replace
3451 return error.OperationUnsupported;3455 return error.OperationUnsupported;
3452}3456}
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
3461pub fn failingProcessSpawn(userdata: ?*anyopaque, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child {3458pub fn failingProcessSpawn(userdata: ?*anyopaque, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child {
3462 _ = userdata;3459 _ = userdata;
3463 _ = options;3460 _ = options;
3464 return error.OperationUnsupported;3461 return error.OperationUnsupported;
3465}3462}
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
3474pub fn unreachableChildWait(userdata: ?*anyopaque, child: *std.process.Child) std.process.Child.WaitError!std.process.Child.Term {3464pub fn unreachableChildWait(userdata: ?*anyopaque, child: *std.process.Child) std.process.Child.WaitError!std.process.Child.Term {
3475 _ = userdata;3465 _ = userdata;
3476 _ = child;3466 _ = child;
...@@ -3488,6 +3478,19 @@ pub fn failingProgressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileE...@@ -3488,6 +3478,19 @@ pub fn failingProgressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileE
3488 return error.UnsupportedOperation;3478 return error.UnsupportedOperation;
3489}3479}
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
3491pub fn noRandom(userdata: ?*anyopaque, buffer: []u8) void {3494pub fn noRandom(userdata: ?*anyopaque, buffer: []u8) void {
3492 _ = userdata;3495 _ = userdata;
3493 @memset(buffer, 0);3496 @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 {...@@ -28,8 +28,7 @@ pub fn init(reader: *Reader, limit: Limit, buffer: []u8) Limited {
28fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {28fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {
29 const l: *Limited = @fieldParentPtr("interface", r);29 const l: *Limited = @fieldParentPtr("interface", r);
30 if (l.remaining == .nothing) return error.EndOfStream;30 if (l.remaining == .nothing) return error.EndOfStream;
31 const combined_limit = limit.min(l.remaining);31 const n = try l.unlimited.stream(w, limit.min(l.remaining));
32 const n = try l.unlimited.stream(w, combined_limit);
33 l.remaining = l.remaining.subtract(n).?;32 l.remaining = l.remaining.subtract(n).?;
34 return n;33 return n;
35}34}
...@@ -53,8 +52,7 @@ test stream {...@@ -53,8 +52,7 @@ test stream {
53fn discard(r: *Reader, limit: Limit) Reader.Error!usize {52fn discard(r: *Reader, limit: Limit) Reader.Error!usize {
54 const l: *Limited = @fieldParentPtr("interface", r);53 const l: *Limited = @fieldParentPtr("interface", r);
55 if (l.remaining == .nothing) return error.EndOfStream;54 if (l.remaining == .nothing) return error.EndOfStream;
56 const combined_limit = limit.min(l.remaining);55 const n = try l.unlimited.discard(limit.min(l.remaining));
57 const n = try l.unlimited.discard(combined_limit);
58 l.remaining = l.remaining.subtract(n).?;56 l.remaining = l.remaining.subtract(n).?;
59 return n;57 return n;
60}58}
lib/std/Io/Threaded.zig+872-226
...@@ -85,6 +85,8 @@ csprng: Csprng = .uninitialized,...@@ -85,6 +85,8 @@ csprng: Csprng = .uninitialized,
8585
86system_basic_information: SystemBasicInformation = .{},86system_basic_information: SystemBasicInformation = .{},
8787
88process_spawn_mutex: if (is_windows) Io.Mutex else void = if (is_windows) .init else {},
89
88const SystemBasicInformation = if (!is_windows) struct {} else struct {90const SystemBasicInformation = if (!is_windows) struct {} else struct {
89 buffer: windows.SYSTEM.BASIC_INFORMATION = undefined,91 buffer: windows.SYSTEM.BASIC_INFORMATION = undefined,
90 initialized: std.atomic.Value(bool) = .{ .raw = false },92 initialized: std.atomic.Value(bool) = .{ .raw = false },
...@@ -1567,6 +1569,10 @@ const poll_buffer_len = 64;...@@ -1567,6 +1569,10 @@ const poll_buffer_len = 64;
1567pub const default_PATH = "/usr/local/bin:/bin:/usr/bin";1569pub const default_PATH = "/usr/local/bin:/bin:/usr/bin";
1568/// There are multiple kernel bugs being worked around with retries.1570/// There are multiple kernel bugs being worked around with retries.
1569const max_windows_kernel_bug_retries = 13;1571const 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
1571comptime {1577comptime {
1572 if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX);1578 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;...@@ -1706,7 +1712,7 @@ var global_single_threaded_instance: Threaded = .init_single_threaded;
1706pub const global_single_threaded: *Threaded = &global_single_threaded_instance;1712pub const global_single_threaded: *Threaded = &global_single_threaded_instance;
17071713
1708pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void {1714pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void {
1709 mutexLock(&t.mutex);1715 mutexLockUncancelable(&t.mutex);
1710 defer mutexUnlock(&t.mutex);1716 defer mutexUnlock(&t.mutex);
1711 t.async_limit = new_limit;1717 t.async_limit = new_limit;
1712}1718}
...@@ -1727,7 +1733,7 @@ pub fn deinit(t: *Threaded) void {...@@ -1727,7 +1733,7 @@ pub fn deinit(t: *Threaded) void {
1727fn join(t: *Threaded) void {1733fn join(t: *Threaded) void {
1728 if (builtin.single_threaded) return;1734 if (builtin.single_threaded) return;
1729 {1735 {
1730 mutexLock(&t.mutex);1736 mutexLockUncancelable(&t.mutex);
1731 defer mutexUnlock(&t.mutex);1737 defer mutexUnlock(&t.mutex);
1732 t.join_requested = true;1738 t.join_requested = true;
1733 }1739 }
...@@ -1789,7 +1795,7 @@ fn worker(t: *Threaded) void {...@@ -1789,7 +1795,7 @@ fn worker(t: *Threaded) void {
17891795
1790 defer t.wait_group.finish();1796 defer t.wait_group.finish();
17911797
1792 mutexLock(&t.mutex);1798 mutexLockUncancelable(&t.mutex);
1793 defer mutexUnlock(&t.mutex);1799 defer mutexUnlock(&t.mutex);
17941800
1795 while (true) {1801 while (true) {
...@@ -1798,7 +1804,7 @@ fn worker(t: *Threaded) void {...@@ -1798,7 +1804,7 @@ fn worker(t: *Threaded) void {
1798 thread.cancel_protection = .unblocked;1804 thread.cancel_protection = .unblocked;
1799 const runnable: *Runnable = @fieldParentPtr("node", runnable_node);1805 const runnable: *Runnable = @fieldParentPtr("node", runnable_node);
1800 runnable.startFn(runnable, &thread, t);1806 runnable.startFn(runnable, &thread, t);
1801 mutexLock(&t.mutex);1807 mutexLockUncancelable(&t.mutex);
1802 t.busy_count -= 1;1808 t.busy_count -= 1;
1803 }1809 }
1804 if (t.join_requested) break;1810 if (t.join_requested) break;
...@@ -1901,13 +1907,13 @@ pub fn io(t: *Threaded) Io {...@@ -1901,13 +1907,13 @@ pub fn io(t: *Threaded) Io {
1901 .processSetCurrentDir = processSetCurrentDir,1907 .processSetCurrentDir = processSetCurrentDir,
1902 .processSetCurrentPath = processSetCurrentPath,1908 .processSetCurrentPath = processSetCurrentPath,
1903 .processReplace = processReplace,1909 .processReplace = processReplace,
1904 .processReplacePath = processReplacePath,
1905 .processSpawn = processSpawn,1910 .processSpawn = processSpawn,
1906 .processSpawnPath = processSpawnPath,
1907 .childWait = childWait,1911 .childWait = childWait,
1908 .childKill = childKill,1912 .childKill = childKill,
19091913
1910 .progressParentFile = progressParentFile,1914 .progressParentFile = progressParentFile,
1915 .inheritParentDir = inheritParentDir,
1916 .inheritParentFile = inheritParentFile,
19111917
1912 .now = now,1918 .now = now,
1913 .clockResolution = clockResolution,1919 .clockResolution = clockResolution,
...@@ -1975,6 +1981,10 @@ const have_copy_file_range = switch (native_os) {...@@ -1975,6 +1981,10 @@ const have_copy_file_range = switch (native_os) {
1975 .linux, .freebsd => true,1981 .linux, .freebsd => true,
1976 else => false,1982 else => false,
1977};1983};
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};
1978const have_fcopyfile = is_darwin;1988const have_fcopyfile = is_darwin;
1979const have_fchmodat2 = native_os == .linux and1989const have_fchmodat2 = native_os == .linux and
1980 (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse true) and1990 (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse true) and
...@@ -2084,7 +2094,7 @@ fn async(...@@ -2084,7 +2094,7 @@ fn async(
2084 },2094 },
2085 };2095 };
20862096
2087 mutexLock(&t.mutex);2097 mutexLockUncancelable(&t.mutex);
20882098
2089 const busy_count = t.busy_count;2099 const busy_count = t.busy_count;
20902100
...@@ -2136,7 +2146,7 @@ fn concurrent(...@@ -2136,7 +2146,7 @@ fn concurrent(
2136 };2146 };
2137 errdefer future.destroy(gpa);2147 errdefer future.destroy(gpa);
21382148
2139 mutexLock(&t.mutex);2149 mutexLockUncancelable(&t.mutex);
2140 defer mutexUnlock(&t.mutex);2150 defer mutexUnlock(&t.mutex);
21412151
2142 const busy_count = t.busy_count;2152 const busy_count = t.busy_count;
...@@ -2181,7 +2191,7 @@ fn groupAsync(...@@ -2181,7 +2191,7 @@ fn groupAsync(
2181 error.OutOfMemory => return groupAsyncEager(start, context.ptr),2191 error.OutOfMemory => return groupAsyncEager(start, context.ptr),
2182 };2192 };
21832193
2184 mutexLock(&t.mutex);2194 mutexLockUncancelable(&t.mutex);
21852195
2186 const busy_count = t.busy_count;2196 const busy_count = t.busy_count;
21872197
...@@ -2244,7 +2254,7 @@ fn groupConcurrent(...@@ -2244,7 +2254,7 @@ fn groupConcurrent(
2244 };2254 };
2245 errdefer task.destroy(gpa);2255 errdefer task.destroy(gpa);
22462256
2247 mutexLock(&t.mutex);2257 mutexLockUncancelable(&t.mutex);
2248 defer mutexUnlock(&t.mutex);2258 defer mutexUnlock(&t.mutex);
22492259
2250 const busy_count = t.busy_count;2260 const busy_count = t.busy_count;
...@@ -4114,7 +4124,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {...@@ -4114,7 +4124,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
41144124
4115fn systemBasicInformation(t: *Threaded) ?*const windows.SYSTEM.BASIC_INFORMATION {4125fn systemBasicInformation(t: *Threaded) ?*const windows.SYSTEM.BASIC_INFORMATION {
4116 if (!t.system_basic_information.initialized.load(.acquire)) {4126 if (!t.system_basic_information.initialized.load(.acquire)) {
4117 mutexLock(&t.mutex);4127 mutexLockUncancelable(&t.mutex);
4118 defer mutexUnlock(&t.mutex);4128 defer mutexUnlock(&t.mutex);
41194129
4120 switch (windows.ntdll.NtQuerySystemInformation(4130 switch (windows.ntdll.NtQuerySystemInformation(
...@@ -4462,7 +4472,7 @@ fn dirCreateFilePosix(...@@ -4462,7 +4472,7 @@ fn dirCreateFilePosix(
4462 var fl_flags: usize = fl: {4472 var fl_flags: usize = fl: {
4463 const syscall: Syscall = try .start();4473 const syscall: Syscall = try .start();
4464 while (true) {4474 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));
4466 switch (posix.errno(rc)) {4476 switch (posix.errno(rc)) {
4467 .SUCCESS => {4477 .SUCCESS => {
4468 syscall.finish();4478 syscall.finish();
...@@ -5058,7 +5068,7 @@ fn dirOpenFilePosix(...@@ -5058,7 +5068,7 @@ fn dirOpenFilePosix(
5058 var fl_flags: usize = fl: {5068 var fl_flags: usize = fl: {
5059 const syscall: Syscall = try .start();5069 const syscall: Syscall = try .start();
5060 while (true) {5070 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));
5062 switch (posix.errno(rc)) {5072 switch (posix.errno(rc)) {
5063 .SUCCESS => {5073 .SUCCESS => {
5064 syscall.finish();5074 syscall.finish();
...@@ -7030,6 +7040,11 @@ fn fileRealPathPosix(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.R...@@ -7030,6 +7040,11 @@ fn fileRealPathPosix(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.R
7030}7040}
70317041
7032fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize {7042fn 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 };
7033 switch (native_os) {7048 switch (native_os) {
7034 .dragonfly, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {7049 .dragonfly, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
7035 var sufficient_buffer: [posix.PATH_MAX]u8 = undefined;7050 var sufficient_buffer: [posix.PATH_MAX]u8 = undefined;
...@@ -7674,6 +7689,8 @@ fn dirRenameWindowsInner(...@@ -7674,6 +7689,8 @@ fn dirRenameWindowsInner(
7674 defer w.CloseHandle(src_fd);7689 defer w.CloseHandle(src_fd);
76757690
7676 var rc: w.NTSTATUS = undefined;7691 var rc: w.NTSTATUS = undefined;
7692 var attempt: u5 = 0;
7693
7677 // FileRenameInformationEx has varying levels of support:7694 // FileRenameInformationEx has varying levels of support:
7678 // - FILE_RENAME_INFORMATION_EX requires >= win10_rs17695 // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1
7679 // (INVALID_INFO_CLASS is returned if not supported)7696 // (INVALID_INFO_CLASS is returned if not supported)
...@@ -7697,24 +7714,43 @@ fn dirRenameWindowsInner(...@@ -7697,24 +7714,43 @@ fn dirRenameWindowsInner(
7697 });7714 });
7698 var io_status_block: w.IO_STATUS_BLOCK = undefined;7715 var io_status_block: w.IO_STATUS_BLOCK = undefined;
7699 const rename_info_buf = rename_info.toBuffer();7716 const rename_info_buf = rename_info.toBuffer();
7700 rc = w.ntdll.NtSetInformationFile(7717 var syscall: Syscall = try .start();
7701 src_fd,7718 while (true) {
7702 &io_status_block,7719 rc = w.ntdll.NtSetInformationFile(
7703 rename_info_buf.ptr,7720 src_fd,
7704 @intCast(rename_info_buf.len),7721 &io_status_block,
7705 .RenameEx,7722 rename_info_buf.ptr,
7706 );7723 @intCast(rename_info_buf.len),
7707 switch (rc) {7724 .RenameEx,
7708 .SUCCESS => return,7725 );
7709 // The filesystem does not support FileDispositionInformationEx7726 switch (rc) {
7710 .INVALID_PARAMETER,7727 // For all other statuses, fall down to the switch below to handle them.
7711 // The operating system does not support FileDispositionInformationEx7728 else => {
7712 .INVALID_INFO_CLASS,7729 syscall.finish();
7713 // The operating system does not support one of the flags7730 break :need_fallback false;
7714 .NOT_SUPPORTED,7731 },
7715 => break :need_fallback true,7732 .CANCELLED => try syscall.checkCancel(),
7716 // For all other statuses, fall down to the switch below to handle them.7733 // The filesystem does not support FileDispositionInformationEx
7717 else => break :need_fallback false,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 }
7718 }7754 }
7719 };7755 };
77207756
...@@ -7726,13 +7762,33 @@ fn dirRenameWindowsInner(...@@ -7726,13 +7762,33 @@ fn dirRenameWindowsInner(
7726 });7762 });
7727 var io_status_block: w.IO_STATUS_BLOCK = undefined;7763 var io_status_block: w.IO_STATUS_BLOCK = undefined;
7728 const rename_info_buf = rename_info.toBuffer();7764 const rename_info_buf = rename_info.toBuffer();
7729 rc = w.ntdll.NtSetInformationFile(7765 var syscall: Syscall = try .start();
7730 src_fd,7766 while (true) {
7731 &io_status_block,7767 rc = w.ntdll.NtSetInformationFile(
7732 rename_info_buf.ptr,7768 src_fd,
7733 @intCast(rename_info_buf.len),7769 &io_status_block,
7734 .Rename,7770 rename_info_buf.ptr,
7735 );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 }
7736 }7792 }
77377793
7738 switch (rc) {7794 switch (rc) {
...@@ -10002,7 +10058,7 @@ fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize {...@@ -10002,7 +10058,7 @@ fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize {
10002 .PENDING => unreachable,10058 .PENDING => unreachable,
10003 .CANCELLED => unreachable,10059 .CANCELLED => unreachable,
10004 .SUCCESS => return io_status_block.Information,10060 .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,
10006 .INVALID_HANDLE => return error.NotOpenForReading,10062 .INVALID_HANDLE => return error.NotOpenForReading,
10007 .INVALID_DEVICE_REQUEST => return error.IsDir,10063 .INVALID_DEVICE_REQUEST => return error.IsDir,
10008 .FILE_LOCK_CONFLICT => return error.LockViolation,10064 .FILE_LOCK_CONFLICT => return error.LockViolation,
...@@ -10019,7 +10075,7 @@ fn ntWriteFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize {...@@ -10019,7 +10075,7 @@ fn ntWriteFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize {
10019 .INVALID_USER_BUFFER => return error.SystemResources,10075 .INVALID_USER_BUFFER => return error.SystemResources,
10020 .NO_MEMORY => return error.SystemResources,10076 .NO_MEMORY => return error.SystemResources,
10021 .QUOTA_EXCEEDED => return error.SystemResources,10077 .QUOTA_EXCEEDED => return error.SystemResources,
10022 .PIPE_BROKEN => return error.BrokenPipe,10078 .PIPE_BROKEN, .PIPE_CLOSING => return error.BrokenPipe,
10023 .INVALID_HANDLE => return error.NotOpenForWriting,10079 .INVALID_HANDLE => return error.NotOpenForWriting,
10024 .FILE_LOCK_CONFLICT => return error.LockViolation,10080 .FILE_LOCK_CONFLICT => return error.LockViolation,
10025 .ACCESS_DENIED => return error.AccessDenied,10081 .ACCESS_DENIED => return error.AccessDenied,
...@@ -14082,7 +14138,7 @@ fn lockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancel...@@ -14082,7 +14138,7 @@ fn lockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancel
14082 const current_thread_id = Thread.currentId();14138 const current_thread_id = Thread.currentId();
1408314139
14084 if (@atomicLoad(std.Thread.Id, &t.stderr_mutex_locker, .unordered) != current_thread_id) {14140 if (@atomicLoad(std.Thread.Id, &t.stderr_mutex_locker, .unordered) != current_thread_id) {
14085 mutexLock(&t.stderr_mutex);14141 try mutexLock(&t.stderr_mutex);
14086 assert(t.stderr_mutex_lock_count == 0);14142 assert(t.stderr_mutex_lock_count == 0);
14087 @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, current_thread_id, .unordered);14143 @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, current_thread_id, .unordered);
14088 }14144 }
...@@ -14145,6 +14201,7 @@ fn unlockStderr(userdata: ?*anyopaque) void {...@@ -14145,6 +14201,7 @@ fn unlockStderr(userdata: ?*anyopaque) void {
14145fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize {14201fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize {
14146 const t: *Threaded = @ptrCast(@alignCast(userdata));14202 const t: *Threaded = @ptrCast(@alignCast(userdata));
14147 _ = t;14203 _ = t;
14204
14148 if (is_windows) {14205 if (is_windows) {
14149 var wtf16le_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined;14206 var wtf16le_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined;
14150 const n = windows.ntdll.RtlGetCurrentDirectory_U(wtf16le_buf.len * 2 + 2, &wtf16le_buf) / 2;14207 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...@@ -14160,12 +14217,18 @@ fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathEr
14160 end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable;14217 end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable;
14161 }14218 }
14162 return end_index;14219 return end_index;
14163 } else if (native_os == .wasi and !builtin.link_libc) {14220 }
14221
14222 if (native_os == .wasi and !builtin.link_libc) {
14164 if (buffer.len == 0) return error.NameTooLong;14223 if (buffer.len == 0) return error.NameTooLong;
14165 buffer[0] = '.';14224 buffer[0] = '.';
14166 return 1;14225 return 1;
14167 }14226 }
1416814227
14228 return processCurrentPathPosix(buffer);
14229}
14230
14231fn processCurrentPathPosix(buffer: []u8) process.CurrentPathError!usize {
14169 const err: posix.E = if (builtin.link_libc) err: {14232 const err: posix.E = if (builtin.link_libc) err: {
14170 const c_err = if (std.c.getcwd(buffer.ptr, buffer.len)) |_| 0 else std.c._errno().*;14233 const c_err = if (std.c.getcwd(buffer.ptr, buffer.len)) |_| 0 else std.c._errno().*;
14171 break :err @fromBackingInt(@intCast(c_err));14234 break :err @fromBackingInt(@intCast(c_err));
...@@ -15101,7 +15164,7 @@ const WindowsEnvironStrings = struct {...@@ -15101,7 +15164,7 @@ const WindowsEnvironStrings = struct {
15101};15164};
1510215165
15103fn scanEnviron(t: *Threaded) void {15166fn scanEnviron(t: *Threaded) void {
15104 mutexLock(&t.mutex);15167 mutexLockUncancelable(&t.mutex);
15105 defer mutexUnlock(&t.mutex);15168 defer mutexUnlock(&t.mutex);
15106 if (t.environ_initialized) return;15169 if (t.environ_initialized) return;
15107 t.environ.scan(t.allocator);15170 t.environ.scan(t.allocator);
...@@ -15133,28 +15196,54 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces...@@ -15133,28 +15196,54 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
15133 });15196 });
15134 };15197 };
1513515198
15136 return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);15199 if (is_darwin) if (t.spawnDarwin(
15137}15200 .replace,
1513815201 options.exe,
15139fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError {15202 argv_buf.ptr,
15140 if (!process.can_replace) return error.OperationUnsupported;15203 options.cwd,
15141 _ = userdata;15204 env_block,
15142 _ = dir;15205 options.expand_arg0,
15143 _ = options;15206 .none,
15144 @panic("TODO processReplacePath");15207 .inherit,
15145}15208 .inherit,
1514615209 .inherit,
15147fn processSpawnPath(userdata: ?*anyopaque, dir: Dir, options: process.SpawnOptions) process.SpawnError!process.Child {15210 options.inherit_dirs,
15148 if (!process.can_spawn) return error.OperationUnsupported;15211 options.inherit_files,
15149 _ = userdata;15212 null,
15150 _ = dir;15213 options.start_suspended,
15151 _ = options;15214 PATH,
15152 @panic("TODO processSpawnPath");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);
15153}15241}
1515415242
15155const processSpawn = switch (native_os) {15243const processSpawn = switch (native_os) {
15156 .wasi, .emscripten, .ios, .tvos, .visionos, .watchos => processSpawnUnsupported,15244 .wasi, .emscripten, .ios, .tvos, .visionos, .watchos => processSpawnUnsupported,
15157 .windows => processSpawnWindows,15245 .windows => processSpawnWindows,
15246 .driverkit, .maccatalyst, .macos => processSpawnDarwin,
15158 else => processSpawnPosix,15247 else => processSpawnPosix,
15159};15248};
1516015249
...@@ -15230,16 +15319,12 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -15230,16 +15319,12 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
15230 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);15319 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
15231 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr;15320 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
15236 const env_block = env_block: {15322 const env_block = env_block: {
15237 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
15238 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{15323 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],
15240 });15325 });
15241 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{15326 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
15242 .zig_progress_fd = prog_fd,15327 .zig_progress_fd = prog_pipe[1],
15243 });15328 });
15244 };15329 };
1524515330
...@@ -15267,23 +15352,42 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -15267,23 +15352,42 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
15267 if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked;15352 if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked;
15268 const ep1 = err_pipe[1];15353 const ep1 = err_pipe[1];
1526915354
15270 setUpChildIo(options.stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);15355 // Must happen before clobbering fds below.
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
15274 switch (options.cwd) {15356 switch (options.cwd) {
15275 .inherit => {},15357 .inherit => {},
15276 .dir => |cwd| {15358 .dir => |cwd| fchdir(cwd.handle) catch |err| forkBail(ep1, err),
15277 fchdir(cwd.handle) catch |err| forkBail(ep1, err);15359 .path => |cwd| chdir(cwd) catch |err| forkBail(ep1, err),
15278 },
15279 .path => |cwd| {
15280 chdir(cwd) catch |err| forkBail(ep1, err);
15281 },
15282 }15360 }
1528315361
15284 // Must happen after fchdir above, the cwd file descriptor might be15362 for (options.inherit_dirs) |inherit_dir| switch (posix.errno(
15285 // equal to prog_fileno and be clobbered by this dup2 call.15363 posix.system.fcntl(inherit_dir.handle, posix.F.SETFD, @as(u32, 0)),
15286 if (prog_pipe[1] != -1) dup2(prog_pipe[1], prog_fileno) catch |err| forkBail(ep1, err);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
15288 if (options.gid) |gid| {15392 if (options.gid) |gid| {
15289 switch (posix.errno(posix.system.setregid(gid, gid))) {15393 switch (posix.errno(posix.system.setregid(gid, gid))) {
...@@ -15316,14 +15420,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -15316,14 +15420,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
15316 }15420 }
1531715421
15318 if (options.start_suspended) {15422 if (options.start_suspended) {
15319 switch (posix.errno(posix.system.kill(0, .STOP))) {15423 switch (posix.errno(posix.system.kill(posix.system.getpid(), .STOP))) {
15320 .SUCCESS => {},15424 .SUCCESS => {},
15321 .PERM => forkBail(ep1, error.PermissionDenied),15425 .PERM => forkBail(ep1, error.PermissionDenied),
15322 else => forkBail(ep1, error.Unexpected),15426 else => forkBail(ep1, error.Unexpected),
15323 }15427 }
15324 }15428 }
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);
15327 forkBail(ep1, err);15431 forkBail(ep1, err);
15328 }15432 }
1532915433
...@@ -15359,7 +15463,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -15359,7 +15463,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1535915463
15360fn getDevNullFd(t: *Threaded) !posix.fd_t {15464fn getDevNullFd(t: *Threaded) !posix.fd_t {
15361 {15465 {
15362 mutexLock(&t.mutex);15466 try mutexLock(&t.mutex);
15363 defer mutexUnlock(&t.mutex);15467 defer mutexUnlock(&t.mutex);
15364 if (t.null_file.fd != -1) return t.null_file.fd;15468 if (t.null_file.fd != -1) return t.null_file.fd;
15365 }15469 }
...@@ -15371,7 +15475,7 @@ fn getDevNullFd(t: *Threaded) !posix.fd_t {...@@ -15371,7 +15475,7 @@ fn getDevNullFd(t: *Threaded) !posix.fd_t {
15371 .SUCCESS => {15475 .SUCCESS => {
15372 syscall.finish();15476 syscall.finish();
15373 const fresh_fd: posix.fd_t = @intCast(rc);15477 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.
15375 defer mutexUnlock(&t.mutex);15479 defer mutexUnlock(&t.mutex);
15376 if (t.null_file.fd != -1) {15480 if (t.null_file.fd != -1) {
15377 closeFd(fresh_fd);15481 closeFd(fresh_fd);
...@@ -15746,19 +15850,137 @@ fn destroyPipe(pipe: [2]posix.fd_t) void {...@@ -15746,19 +15850,137 @@ fn destroyPipe(pipe: [2]posix.fd_t) void {
15746 if (pipe[0] != pipe[1]) closeFd(pipe[1]);15850 if (pipe[0] != pipe[1]) closeFd(pipe[1]);
15747}15851}
1574815852
15749fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {15853fn processSpawnDarwin(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
15750 switch (stdio) {15854 const t: *Threaded = @ptrCast(@alignCast(userdata));
15751 .pipe => try dup2(pipe_fd, std_fileno),15855
15752 .close => closeFd(std_fileno),15856 t.scanEnviron(); // for PATH
15753 .inherit => {},15857 const PATH = t.environ.string.PATH orelse default_PATH;
15754 .ignore => try dup2(dev_null_fd, std_fileno),15858
15755 .file => |file| try dup2(file.handle, std_fileno),15859 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
15756 }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 };
15757}15901}
1575815902
15759fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {15903fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
15760 const t: *Threaded = @ptrCast(@alignCast(userdata));15904 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
15762 const any_ignore =15984 const any_ignore =
15763 options.stdin == .ignore or15985 options.stdin == .ignore or
15764 options.stdout == .ignore or15986 options.stdout == .ignore or
...@@ -15800,6 +16022,43 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15800,6 +16022,43 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
15800 }) else undefined;16022 }) else undefined;
15801 errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle);16023 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
15803 var siStartInfo: windows.STARTUPINFOW = .{16062 var siStartInfo: windows.STARTUPINFOW = .{
15804 .cb = @sizeOf(windows.STARTUPINFOW),16063 .cb = @sizeOf(windows.STARTUPINFOW),
15805 .dwFlags = windows.STARTF_USESTDHANDLES,16064 .dwFlags = windows.STARTF_USESTDHANDLES,
...@@ -15877,32 +16136,6 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15877,32 +16136,6 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
15877 };16136 };
15878 var piProcInfo: windows.PROCESS.INFORMATION = undefined;16137 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
15906 const env_block = env_block: {16139 const env_block = env_block: {
15907 const prog_handle = if (options.progress_node.index != .none)16140 const prog_handle = if (options.progress_node.index != .none)
15908 prog_pipe[1]16141 prog_pipe[1]
...@@ -15916,45 +16149,6 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15916,45 +16149,6 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
15916 });16149 });
15917 };16150 };
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
15958 run: {16152 run: {
15959 // We have to scan each time because the PEB environment pointer is not stable.16153 // We have to scan each time because the PEB environment pointer is not stable.
15960 const env_strings: WindowsEnvironStrings = .scan();16154 const env_strings: WindowsEnvironStrings = .scan();
...@@ -15975,10 +16169,8 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15975,10 +16169,8 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1597516169
15976 var dir_buf: std.ArrayList(u16) = .empty;16170 var dir_buf: std.ArrayList(u16) = .empty;
1597716171
15978 if (cwd_path_w.len > 0) {16172 if (app_path_w) |app_path| try dir_buf.appendSlice(arena, app_path);
15979 try dir_buf.appendSlice(arena, cwd_path_w);16173 if (app_dir_w) |app_dir| {
15980 }
15981 if (app_dirname_w) |app_dir| {
15982 if (dir_buf.items.len > 0) try dir_buf.append(arena, Dir.path.sep);16174 if (dir_buf.items.len > 0) try dir_buf.append(arena, Dir.path.sep);
15983 try dir_buf.appendSlice(arena, app_dir);16175 try dir_buf.appendSlice(arena, app_dir);
15984 }16176 }
...@@ -16003,15 +16195,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -16003,15 +16195,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
16003 else => |e| return e,16195 else => |e| return e,
16004 };16196 };
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.
16008 // We still search the path if the cwd is absolute because of the16198 // We still search the path if the cwd is absolute because of the
16009 // "cwd provided by options is in effect when choosing the executable path16199 // "cwd provided by options is in effect when choosing the executable path
16010 // to match posix semantics" behavior--we don't want to skip searching16200 // to match posix semantics" behavior--we don't want to skip searching
16011 // the PATH just because we were trying to set the cwd of the child process.16201 // 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) {16202 if (switch (options.exe) {
16013 return original_err;16203 // If the app name had path separators, that disallows PATH searching.
16014 }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
16016 var it = std.mem.tokenizeScalar(u16, PATH, ';');16211 var it = std.mem.tokenizeScalar(u16, PATH, ';');
16017 while (it.next()) |search_path| {16212 while (it.next()) |search_path| {
...@@ -16089,11 +16284,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -16089,11 +16284,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
16089 };16284 };
16090}16285}
1609116286
16092fn inheritFile() windows.HANDLE {}
16093
16094fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {16287fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
16095 {16288 {
16096 mutexLock(&t.mutex);16289 try mutexLock(&t.mutex);
16097 defer mutexUnlock(&t.mutex);16290 defer mutexUnlock(&t.mutex);
16098 if (t.random_file.handle) |handle| return handle;16291 if (t.random_file.handle) |handle| return handle;
16099 }16292 }
...@@ -16116,7 +16309,7 @@ fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {...@@ -16116,7 +16309,7 @@ fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
16116 )) {16309 )) {
16117 .SUCCESS => {16310 .SUCCESS => {
16118 syscall.finish();16311 syscall.finish();
16119 mutexLock(&t.mutex); // Another thread might have won the race.16312 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
16120 defer mutexUnlock(&t.mutex);16313 defer mutexUnlock(&t.mutex);
16121 if (t.random_file.handle) |prev_handle| {16314 if (t.random_file.handle) |prev_handle| {
16122 windows.CloseHandle(fresh_handle);16315 windows.CloseHandle(fresh_handle);
...@@ -16137,7 +16330,7 @@ fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {...@@ -16137,7 +16330,7 @@ fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1613716330
16138fn getNulDevice(t: *Threaded) !windows.HANDLE {16331fn getNulDevice(t: *Threaded) !windows.HANDLE {
16139 {16332 {
16140 mutexLock(&t.mutex);16333 try mutexLock(&t.mutex);
16141 defer mutexUnlock(&t.mutex);16334 defer mutexUnlock(&t.mutex);
16142 if (t.null_file.handle) |handle| return handle;16335 if (t.null_file.handle) |handle| return handle;
16143 }16336 }
...@@ -16163,7 +16356,7 @@ fn getNulDevice(t: *Threaded) !windows.HANDLE {...@@ -16163,7 +16356,7 @@ fn getNulDevice(t: *Threaded) !windows.HANDLE {
16163 )) {16356 )) {
16164 .SUCCESS => {16357 .SUCCESS => {
16165 syscall.finish();16358 syscall.finish();
16166 mutexLock(&t.mutex); // Another thread might have won the race.16359 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
16167 defer mutexUnlock(&t.mutex);16360 defer mutexUnlock(&t.mutex);
16168 if (t.null_file.handle) |prev_handle| {16361 if (t.null_file.handle) |prev_handle| {
16169 windows.CloseHandle(fresh_handle);16362 windows.CloseHandle(fresh_handle);
...@@ -16196,7 +16389,7 @@ fn getNulDevice(t: *Threaded) !windows.HANDLE {...@@ -16196,7 +16389,7 @@ fn getNulDevice(t: *Threaded) !windows.HANDLE {
1619616389
16197fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE {16390fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE {
16198 {16391 {
16199 mutexLock(&t.mutex);16392 try mutexLock(&t.mutex);
16200 defer mutexUnlock(&t.mutex);16393 defer mutexUnlock(&t.mutex);
16201 if (t.pipe_file.handle) |handle| return handle;16394 if (t.pipe_file.handle) |handle| return handle;
16202 }16395 }
...@@ -16218,7 +16411,7 @@ fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE {...@@ -16218,7 +16411,7 @@ fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE {
16218 )) {16411 )) {
16219 .SUCCESS => {16412 .SUCCESS => {
16220 syscall.finish();16413 syscall.finish();
16221 mutexLock(&t.mutex); // Another thread might have won the race.16414 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
16222 defer mutexUnlock(&t.mutex);16415 defer mutexUnlock(&t.mutex);
16223 if (t.pipe_file.handle) |prev_handle| {16416 if (t.pipe_file.handle) |prev_handle| {
16224 windows.CloseHandle(fresh_handle);16417 windows.CloseHandle(fresh_handle);
...@@ -16503,7 +16696,15 @@ fn windowsCreateProcessPathExt(...@@ -16503,7 +16696,15 @@ fn windowsCreateProcessPathExt(
16503 else16696 else
16504 full_app_name;16697 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 )) |_| {
16507 return;16708 return;
16508 } else |err| switch (err) {16709 } else |err| switch (err) {
16509 error.FileNotFound => continue,16710 error.FileNotFound => continue,
...@@ -17037,61 +17238,112 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c...@@ -17037,61 +17238,112 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c
17037 try std.testing.expectEqualStrings(expected_cmd_line, cmd_line);17238 try std.testing.expectEqualStrings(expected_cmd_line, cmd_line);
17038}17239}
1703917240
17040fn posixExecv(17241fn posixExec(
17041 arg0_expand: process.ArgExpansion,17242 exe: process.ReplaceOptions.Exe,
17042 file: [*:0]const u8,17243 argv: [*:null]?[*:0]const u8,
17043 child_argv: [*:null]?[*:0]const u8,17244 expand_arg0: process.ArgExpansion,
17044 env_block: process.Environ.PosixBlock,17245 env_block: process.Environ.PosixBlock,
17045 PATH: []const u8,17246 PATH: []const u8,
17046) process.ReplaceError {17247) process.ReplaceError {
17047 const file_slice = std.mem.sliceTo(file, 0);17248 const arg0_slice = std.mem.span(argv[0].?);
17048 if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, env_block);17249 exe: switch (exe) {
1704917250 .detect => continue :exe if (std.mem.findScalar(u8, arg0_slice, '/')) |_| .{
17050 // Use of PATH_MAX here is valid as the path_buf will be passed17251 .path = .cwd(),
17051 // directly to the operating system in posixExecvPath.17252 } else .search,
17052 var path_buf: [posix.PATH_MAX]u8 = undefined;17253 .search => {
17053 var it = std.mem.tokenizeScalar(u8, PATH, ':');17254 if (Dir.path.isAbsolute(arg0_slice)) continue :exe .{ .path = .cwd() };
17054 var seen_eacces = false;17255 // Use of PATH_MAX here is valid as the path_buf will be passed
17055 var err: process.ReplaceError = error.FileNotFound;17256 // directly to the operating system in posixExecveat.
1705617257 var path_buf: [posix.PATH_MAX]u8 = undefined;
17057 // In case of expanding arg0 we must put it back if we return with an error.17258 var it = std.mem.tokenizeScalar(u8, PATH, ':');
17058 const prev_arg0 = child_argv[0];17259 var err: error{ AccessDenied, FileNotFound, NotDir } = error.FileNotFound;
17059 defer switch (arg0_expand) {
17060 .expand => child_argv[0] = prev_arg0,
17061 .no_expand => {},
17062 };
1706317260
17064 while (it.next()) |search_path| {17261 while (it.next()) |search_path| {
17065 const path_len = search_path.len + file_slice.len + 1;17262 const path_len = search_path.len + arg0_slice.len + 1;
17066 if (path_buf.len < path_len + 1) return error.NameTooLong;17263 if (path_buf.len < path_len + 1) return error.NameTooLong;
17067 @memcpy(path_buf[0..search_path.len], search_path);17264 @memcpy(path_buf[0..search_path.len], search_path);
17068 path_buf[search_path.len] = '/';17265 path_buf[search_path.len] = Dir.path.sep;
17069 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);17266 @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice);
17070 path_buf[path_len] = 0;17267 path_buf[path_len] = 0;
17071 const full_path = path_buf[0..path_len :0].ptr;17268 const full_path = path_buf[0..path_len :0].ptr;
17072 switch (arg0_expand) {17269 switch (expand_arg0) {
17073 .expand => child_argv[0] = full_path,17270 .expand => argv[0] = full_path,
17074 .no_expand => {},17271 .no_expand => {},
17075 }17272 }
17076 err = posixExecvPath(full_path, child_argv, env_block);17273 switch (posixExecveat(posix.AT.FDCWD, full_path, argv, env_block)) {
17077 switch (err) {17274 error.AccessDenied, error.FileNotFound, error.NotDir => |e| switch (err) {
17078 error.AccessDenied => seen_eacces = true,17275 error.AccessDenied => {},
17079 error.FileNotFound, error.NotDir => {},17276 error.FileNotFound, error.NotDir => err = e,
17080 else => |e| return e,17277 },
17081 }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 },
17082 }17290 }
17083 if (seen_eacces) return error.AccessDenied;
17084 return err;
17085}17291}
1708617292
17087/// This function ignores PATH environment variable.17293/// This function ignores PATH environment variable.
17088pub fn posixExecvPath(17294pub fn posixExecveat(
17089 path: [*:0]const u8,17295 dir: posix.fd_t,
17090 child_argv: [*:null]const ?[*:0]const u8,17296 path: ?[*:0]const u8,
17297 argv: [*:null]const ?[*:0]const u8,
17091 env_block: process.Environ.PosixBlock,17298 env_block: process.Environ.PosixBlock,
17092) process.ReplaceError {17299) process.ReplaceError {
17093 try Thread.checkCancel();17300 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 }) {
17095 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.17347 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
17096 .@"2BIG" => return error.SystemResources,17348 .@"2BIG" => return error.SystemResources,
17097 .MFILE => return error.ProcessFdQuotaExceeded,17349 .MFILE => return error.ProcessFdQuotaExceeded,
...@@ -17123,6 +17375,334 @@ pub fn posixExecvPath(...@@ -17123,6 +17375,334 @@ pub fn posixExecvPath(
17123 }17375 }
17124}17376}
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
17126pub const CreatePipeOptions = struct {17706pub const CreatePipeOptions = struct {
17127 server: End,17707 server: End,
17128 client: End,17708 client: End,
...@@ -17224,6 +17804,53 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {...@@ -17224,6 +17804,53 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
17224 return t.environ.zig_progress_file;17804 return t.environ.zig_progress_file;
17225}17805}
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
17227pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {17854pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {
17228 t.scanEnviron();17855 t.scanEnviron();
17229 return @field(t.environ.string, name);17856 return @field(t.environ.string, name);
...@@ -17242,7 +17869,7 @@ fn random(userdata: ?*anyopaque, buffer: []u8) void {...@@ -17242,7 +17869,7 @@ fn random(userdata: ?*anyopaque, buffer: []u8) void {
17242}17869}
1724317870
17244fn randomMainThread(t: *Threaded, buffer: []u8) void {17871fn randomMainThread(t: *Threaded, buffer: []u8) void {
17245 mutexLock(&t.mutex);17872 mutexLockUncancelable(&t.mutex);
17246 defer mutexUnlock(&t.mutex);17873 defer mutexUnlock(&t.mutex);
1724717874
17248 if (!t.csprng.isInitialized()) {17875 if (!t.csprng.isInitialized()) {
...@@ -17250,7 +17877,7 @@ fn randomMainThread(t: *Threaded, buffer: []u8) void {...@@ -17250,7 +17877,7 @@ fn randomMainThread(t: *Threaded, buffer: []u8) void {
17250 var seed: [Csprng.seed_len]u8 = undefined;17877 var seed: [Csprng.seed_len]u8 = undefined;
17251 {17878 {
17252 mutexUnlock(&t.mutex);17879 mutexUnlock(&t.mutex);
17253 defer mutexLock(&t.mutex);17880 defer mutexLockUncancelable(&t.mutex);
1725417881
17255 const prev = swapCancelProtection(t, .blocked);17882 const prev = swapCancelProtection(t, .blocked);
17256 defer _ = swapCancelProtection(t, prev);17883 defer _ = swapCancelProtection(t, prev);
...@@ -17437,7 +18064,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {...@@ -17437,7 +18064,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {
1743718064
17438fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {18065fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {
17439 {18066 {
17440 mutexLock(&t.mutex);18067 try mutexLock(&t.mutex);
17441 defer mutexUnlock(&t.mutex);18068 defer mutexUnlock(&t.mutex);
1744218069
17443 if (t.random_file.fd == -2) return error.EntropyUnavailable;18070 if (t.random_file.fd == -2) return error.EntropyUnavailable;
...@@ -17478,7 +18105,7 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {...@@ -17478,7 +18105,7 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {
17478 .SUCCESS => {18105 .SUCCESS => {
17479 syscall.finish();18106 syscall.finish();
17480 if (!statx.mask.TYPE) return error.EntropyUnavailable;18107 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.
17482 defer mutexUnlock(&t.mutex);18109 defer mutexUnlock(&t.mutex);
17483 if (t.random_file.fd >= 0) {18110 if (t.random_file.fd >= 0) {
17484 closeFd(fd);18111 closeFd(fd);
...@@ -17506,7 +18133,7 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {...@@ -17506,7 +18133,7 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {
17506 switch (posix.errno(fstat_sym(fd, &stat))) {18133 switch (posix.errno(fstat_sym(fd, &stat))) {
17507 .SUCCESS => {18134 .SUCCESS => {
17508 syscall.finish();18135 syscall.finish();
17509 mutexLock(&t.mutex); // Another thread might have won the race.18136 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
17510 defer mutexUnlock(&t.mutex);18137 defer mutexUnlock(&t.mutex);
17511 if (t.random_file.fd >= 0) {18138 if (t.random_file.fd >= 0) {
17512 closeFd(fd);18139 closeFd(fd);
...@@ -18434,7 +19061,7 @@ const CreateFileMapError = error{...@@ -18434,7 +19061,7 @@ const CreateFileMapError = error{
1843419061
18435fn createFileMap(19062fn createFileMap(
18436 file: File,19063 file: File,
18437 protection: std.process.MemoryProtection,19064 protection: process.MemoryProtection,
18438 offset: u64,19065 offset: u64,
18439 populate: bool,19066 populate: bool,
18440 len: usize,19067 len: usize,
...@@ -19062,7 +19689,7 @@ fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void {...@@ -19062,7 +19689,7 @@ fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void {
19062 }19689 }
1906319690
19064 mutexUnlock(mutex);19691 mutexUnlock(mutex);
19065 defer mutexLock(mutex);19692 defer mutexLockUncancelable(mutex);
1906619693
19067 while (true) {19694 while (true) {
19068 Thread.futexWaitUncancelable(&cond.epoch.raw, epoch, null);19695 Thread.futexWaitUncancelable(&cond.epoch.raw, epoch, null);
...@@ -19082,8 +19709,27 @@ fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void {...@@ -19082,8 +19709,27 @@ fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void {
19082 }19709 }
19083}19710}
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
19085/// Same as `Io.Mutex.lockUncancelable` but avoids the VTable.19731/// Same as `Io.Mutex.lockUncancelable` but avoids the VTable.
19086pub fn mutexLock(m: *Io.Mutex) void {19732pub fn mutexLockUncancelable(m: *Io.Mutex) void {
19087 const initial_state = m.state.cmpxchgStrong(19733 const initial_state = m.state.cmpxchgStrong(
19088 .unlocked,19734 .unlocked,
19089 .locked_once,19735 .locked_once,
lib/std/bit_set.zig+2-7
...@@ -723,13 +723,8 @@ pub const Dynamic = struct {...@@ -723,13 +723,8 @@ pub const Dynamic = struct {
723 // That slot holds the size of the true allocation, which723 // That slot holds the size of the true allocation, which
724 // is needed by Zig's allocator interface in case a shrink724 // is needed by Zig's allocator interface in case a shrink
725 // fails.725 // fails.
726726 const empty_masks_data = [_]MaskInt{ 0, undefined };
727 // Don't modify this value. Ideally it would go in const data so727 const empty_masks_ptr = @constCast(empty_masks_data[1..2]);
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];
733728
734 /// Creates a bit set with no elements present.729 /// Creates a bit set with no elements present.
735 /// If bit_length is not zero, deinit must eventually be called.730 /// 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) {...@@ -8809,6 +8809,11 @@ pub const O = switch (native_os) {
8809 else => void,8809 else => void,
8810};8810};
88118811
8812pub const EXECVEAT = switch (native_os) {
8813 .linux => linux.EXECVEAT,
8814 else => void,
8815};
8816
8812pub const MAP = switch (native_os) {8817pub const MAP = switch (native_os) {
8813 .linux => linux.MAP,8818 .linux => linux.MAP,
8814 .emscripten => packed struct(u32) {8819 .emscripten => packed struct(u32) {
...@@ -11045,6 +11050,7 @@ pub extern "c" fn renameat(olddirfd: fd_t, old: [*:0]const u8, newdirfd: fd_t, n...@@ -11045,6 +11050,7 @@ pub extern "c" fn renameat(olddirfd: fd_t, old: [*:0]const u8, newdirfd: fd_t, n
11045pub extern "c" fn chdir(path: [*:0]const u8) c_int;11050pub extern "c" fn chdir(path: [*:0]const u8) c_int;
11046pub extern "c" fn fchdir(fd: fd_t) c_int;11051pub extern "c" fn fchdir(fd: fd_t) c_int;
11047pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int;11052pub 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;
11048pub extern "c" fn dup(fd: fd_t) c_int;11054pub extern "c" fn dup(fd: fd_t) c_int;
11049pub extern "c" fn dup2(old_fd: fd_t, new_fd: fd_t) c_int;11055pub extern "c" fn dup2(old_fd: fd_t, new_fd: fd_t) c_int;
11050pub extern "c" fn dup3(old: c_int, new: c_int, flags: c_uint) c_int;11056pub 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;...@@ -11779,8 +11785,10 @@ pub const posix_spawn_file_actions_init = darwin.posix_spawn_file_actions_init;
11779pub const posix_spawn_file_actions_t = darwin.posix_spawn_file_actions_t;11785pub const posix_spawn_file_actions_t = darwin.posix_spawn_file_actions_t;
11780pub const posix_spawnattr_destroy = darwin.posix_spawnattr_destroy;11786pub const posix_spawnattr_destroy = darwin.posix_spawnattr_destroy;
11781pub const posix_spawnattr_getflags = darwin.posix_spawnattr_getflags;11787pub const posix_spawnattr_getflags = darwin.posix_spawnattr_getflags;
11788pub const posix_spawnattr_getpgroup = darwin.posix_spawnattr_getpgroup;
11782pub const posix_spawnattr_init = darwin.posix_spawnattr_init;11789pub const posix_spawnattr_init = darwin.posix_spawnattr_init;
11783pub const posix_spawnattr_setflags = darwin.posix_spawnattr_setflags;11790pub const posix_spawnattr_setflags = darwin.posix_spawnattr_setflags;
11791pub const posix_spawnattr_setpgroup = darwin.posix_spawnattr_setpgroup;
11784pub const posix_spawnattr_t = darwin.posix_spawnattr_t;11792pub const posix_spawnattr_t = darwin.posix_spawnattr_t;
11785pub const posix_spawnp = darwin.posix_spawnp;11793pub const posix_spawnp = darwin.posix_spawnp;
11786pub const pthread_attr_get_qos_class_np = darwin.pthread_attr_get_qos_class_np;11794pub 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 {};...@@ -1253,8 +1253,10 @@ pub const posix_spawnattr_t = *opaque {};
1253pub const posix_spawn_file_actions_t = *opaque {};1253pub const posix_spawn_file_actions_t = *opaque {};
1254pub extern "c" fn posix_spawnattr_init(attr: *posix_spawnattr_t) c_int;1254pub extern "c" fn posix_spawnattr_init(attr: *posix_spawnattr_t) c_int;
1255pub extern "c" fn posix_spawnattr_destroy(attr: *posix_spawnattr_t) c_int;1255pub 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;
1257pub extern "c" fn posix_spawnattr_getflags(attr: *const posix_spawnattr_t, flags: *POSIX_SPAWN) c_int;1256pub 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;
1258pub extern "c" fn posix_spawn_file_actions_init(actions: *posix_spawn_file_actions_t) c_int;1260pub extern "c" fn posix_spawn_file_actions_init(actions: *posix_spawn_file_actions_t) c_int;
1259pub extern "c" fn posix_spawn_file_actions_destroy(actions: *posix_spawn_file_actions_t) c_int;1261pub extern "c" fn posix_spawn_file_actions_destroy(actions: *posix_spawn_file_actions_t) c_int;
1260pub extern "c" fn posix_spawn_file_actions_addclose(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int;1262pub 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 {...@@ -1438,7 +1438,7 @@ pub fn dirnamePosix(path: []const u8) ?[]const u8 {
1438}1438}
14391439
1440fn dirnameInner(comptime path_type: PathType, path: []const u8) ?[]const u8 {1440fn 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);
1442 _ = it.last() orelse return null;1442 _ = it.last() orelse return null;
1443 const up = it.previous() orelse return it.root();1443 const up = it.previous() orelse return it.root();
1444 return up.path;1444 return up.path;
...@@ -1539,7 +1539,7 @@ pub fn basenameWindows(path: []const u8) []const u8 {...@@ -1539,7 +1539,7 @@ pub fn basenameWindows(path: []const u8) []const u8 {
1539}1539}
15401540
1541fn basenameInner(comptime path_type: PathType, path: []const u8) []const u8 {1541fn 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);
1543 const last = it.last() orelse return &[_]u8{};1543 const last = it.last() orelse return &[_]u8{};
1544 return last.name;1544 return last.name;
1545}1545}
...@@ -2338,14 +2338,14 @@ pub const NativeComponentIterator = ComponentIterator(switch (native_os) {...@@ -2338,14 +2338,14 @@ pub const NativeComponentIterator = ComponentIterator(switch (native_os) {
2338}, u8);2338}, u8);
23392339
2340pub fn componentIterator(path: []const u8) NativeComponentIterator {2340pub fn componentIterator(path: []const u8) NativeComponentIterator {
2341 return NativeComponentIterator.init(path);2341 return .init(path);
2342}2342}
23432343
2344test "ComponentIterator posix" {2344test "ComponentIterator posix" {
2345 const PosixComponentIterator = ComponentIterator(.posix, u8);2345 const PosixComponentIterator = ComponentIterator(.posix, u8);
2346 {2346 {
2347 const path = "a/b/c/";2347 const path = "a/b/c/";
2348 var it = PosixComponentIterator.init(path);2348 var it: PosixComponentIterator = .init(path);
2349 try std.testing.expectEqual(0, it.root_len);2349 try std.testing.expectEqual(0, it.root_len);
2350 try std.testing.expectEqual(0, it.root_end_index);2350 try std.testing.expectEqual(0, it.root_end_index);
2351 try std.testing.expect(null == it.root());2351 try std.testing.expect(null == it.root());
...@@ -2393,7 +2393,7 @@ test "ComponentIterator posix" {...@@ -2393,7 +2393,7 @@ test "ComponentIterator posix" {
23932393
2394 {2394 {
2395 const path = "/a/b/c/";2395 const path = "/a/b/c/";
2396 var it = PosixComponentIterator.init(path);2396 var it: PosixComponentIterator = .init(path);
2397 try std.testing.expectEqual(1, it.root_len);2397 try std.testing.expectEqual(1, it.root_len);
2398 try std.testing.expectEqual(1, it.root_end_index);2398 try std.testing.expectEqual(1, it.root_end_index);
2399 try std.testing.expectEqualStrings("/", it.root().?);2399 try std.testing.expectEqualStrings("/", it.root().?);
...@@ -2441,7 +2441,7 @@ test "ComponentIterator posix" {...@@ -2441,7 +2441,7 @@ test "ComponentIterator posix" {
24412441
2442 {2442 {
2443 const path = "////a///b///c////";2443 const path = "////a///b///c////";
2444 var it = PosixComponentIterator.init(path);2444 var it: PosixComponentIterator = .init(path);
2445 try std.testing.expectEqual(1, it.root_len);2445 try std.testing.expectEqual(1, it.root_len);
2446 try std.testing.expectEqual(4, it.root_end_index);2446 try std.testing.expectEqual(4, it.root_end_index);
2447 try std.testing.expectEqualStrings("/", it.root().?);2447 try std.testing.expectEqualStrings("/", it.root().?);
...@@ -2489,7 +2489,7 @@ test "ComponentIterator posix" {...@@ -2489,7 +2489,7 @@ test "ComponentIterator posix" {
24892489
2490 {2490 {
2491 const path = "/";2491 const path = "/";
2492 var it = PosixComponentIterator.init(path);2492 var it: PosixComponentIterator = .init(path);
2493 try std.testing.expectEqual(1, it.root_len);2493 try std.testing.expectEqual(1, it.root_len);
2494 try std.testing.expectEqual(1, it.root_end_index);2494 try std.testing.expectEqual(1, it.root_end_index);
2495 try std.testing.expectEqualStrings("/", it.root().?);2495 try std.testing.expectEqualStrings("/", it.root().?);
...@@ -2507,7 +2507,7 @@ test "ComponentIterator posix" {...@@ -2507,7 +2507,7 @@ test "ComponentIterator posix" {
25072507
2508 {2508 {
2509 const path = "";2509 const path = "";
2510 var it = PosixComponentIterator.init(path);2510 var it: PosixComponentIterator = .init(path);
2511 try std.testing.expectEqual(0, it.root_len);2511 try std.testing.expectEqual(0, it.root_len);
2512 try std.testing.expectEqual(0, it.root_end_index);2512 try std.testing.expectEqual(0, it.root_end_index);
2513 try std.testing.expect(null == it.root());2513 try std.testing.expect(null == it.root());
...@@ -2528,7 +2528,7 @@ test "ComponentIterator windows" {...@@ -2528,7 +2528,7 @@ test "ComponentIterator windows" {
2528 const WindowsComponentIterator = ComponentIterator(.windows, u8);2528 const WindowsComponentIterator = ComponentIterator(.windows, u8);
2529 {2529 {
2530 const path = "a/b\\c//";2530 const path = "a/b\\c//";
2531 var it = WindowsComponentIterator.init(path);2531 var it: WindowsComponentIterator = .init(path);
2532 try std.testing.expectEqual(0, it.root_len);2532 try std.testing.expectEqual(0, it.root_len);
2533 try std.testing.expectEqual(0, it.root_end_index);2533 try std.testing.expectEqual(0, it.root_end_index);
2534 try std.testing.expect(null == it.root());2534 try std.testing.expect(null == it.root());
...@@ -2576,7 +2576,7 @@ test "ComponentIterator windows" {...@@ -2576,7 +2576,7 @@ test "ComponentIterator windows" {
25762576
2577 {2577 {
2578 const path = "C:\\a/b/c/";2578 const path = "C:\\a/b/c/";
2579 var it = WindowsComponentIterator.init(path);2579 var it: WindowsComponentIterator = .init(path);
2580 try std.testing.expectEqual(3, it.root_len);2580 try std.testing.expectEqual(3, it.root_len);
2581 try std.testing.expectEqual(3, it.root_end_index);2581 try std.testing.expectEqual(3, it.root_end_index);
2582 try std.testing.expectEqualStrings("C:\\", it.root().?);2582 try std.testing.expectEqualStrings("C:\\", it.root().?);
...@@ -2614,7 +2614,7 @@ test "ComponentIterator windows" {...@@ -2614,7 +2614,7 @@ test "ComponentIterator windows" {
26142614
2615 {2615 {
2616 const path = "C:\\\\//a/\\/\\b///c////";2616 const path = "C:\\\\//a/\\/\\b///c////";
2617 var it = WindowsComponentIterator.init(path);2617 var it: WindowsComponentIterator = .init(path);
2618 try std.testing.expectEqual(3, it.root_len);2618 try std.testing.expectEqual(3, it.root_len);
2619 try std.testing.expectEqual(6, it.root_end_index);2619 try std.testing.expectEqual(6, it.root_end_index);
2620 try std.testing.expectEqualStrings("C:\\", it.root().?);2620 try std.testing.expectEqualStrings("C:\\", it.root().?);
...@@ -2652,7 +2652,7 @@ test "ComponentIterator windows" {...@@ -2652,7 +2652,7 @@ test "ComponentIterator windows" {
26522652
2653 {2653 {
2654 const path = "/";2654 const path = "/";
2655 var it = WindowsComponentIterator.init(path);2655 var it: WindowsComponentIterator = .init(path);
2656 try std.testing.expectEqual(1, it.root_len);2656 try std.testing.expectEqual(1, it.root_len);
2657 try std.testing.expectEqual(1, it.root_end_index);2657 try std.testing.expectEqual(1, it.root_end_index);
2658 try std.testing.expectEqualStrings("/", it.root().?);2658 try std.testing.expectEqualStrings("/", it.root().?);
...@@ -2670,7 +2670,7 @@ test "ComponentIterator windows" {...@@ -2670,7 +2670,7 @@ test "ComponentIterator windows" {
26702670
2671 {2671 {
2672 const path = "";2672 const path = "";
2673 var it = WindowsComponentIterator.init(path);2673 var it: WindowsComponentIterator = .init(path);
2674 try std.testing.expectEqual(0, it.root_len);2674 try std.testing.expectEqual(0, it.root_len);
2675 try std.testing.expectEqual(0, it.root_end_index);2675 try std.testing.expectEqual(0, it.root_end_index);
2676 try std.testing.expect(null == it.root());2676 try std.testing.expect(null == it.root());
...@@ -2692,7 +2692,7 @@ test "ComponentIterator windows WTF-16" {...@@ -2692,7 +2692,7 @@ test "ComponentIterator windows WTF-16" {
2692 const L = std.unicode.utf8ToUtf16LeStringLiteral;2692 const L = std.unicode.utf8ToUtf16LeStringLiteral;
26932693
2694 const path = L("C:\\a/b/c/");2694 const path = L("C:\\a/b/c/");
2695 var it = WindowsComponentIterator.init(path);2695 var it: WindowsComponentIterator = .init(path);
2696 try std.testing.expectEqual(3, it.root_len);2696 try std.testing.expectEqual(3, it.root_len);
2697 try std.testing.expectEqual(3, it.root_end_index);2697 try std.testing.expectEqual(3, it.root_end_index);
2698 try std.testing.expectEqualSlices(u16, L("C:\\"), it.root().?);2698 try std.testing.expectEqualSlices(u16, L("C:\\"), it.root().?);
...@@ -2731,59 +2731,59 @@ test "ComponentIterator windows WTF-16" {...@@ -2731,59 +2731,59 @@ test "ComponentIterator windows WTF-16" {
2731test "ComponentIterator roots" {2731test "ComponentIterator roots" {
2732 // UEFI2732 // UEFI
2733 {2733 {
2734 var it = ComponentIterator(.uefi, u8).init("\\\\a");2734 var it: ComponentIterator(.uefi, u8) = .init("\\\\a");
2735 try std.testing.expectEqualStrings("\\", it.root().?);2735 try std.testing.expectEqualStrings("\\", it.root().?);
27362736
2737 it = ComponentIterator(.uefi, u8).init("//a");2737 it = .init("//a");
2738 try std.testing.expect(null == it.root());2738 try std.testing.expect(null == it.root());
2739 }2739 }
2740 // POSIX2740 // POSIX
2741 {2741 {
2742 var it = ComponentIterator(.posix, u8).init("//a");2742 var it: ComponentIterator(.posix, u8) = .init("//a");
2743 try std.testing.expectEqualStrings("/", it.root().?);2743 try std.testing.expectEqualStrings("/", it.root().?);
27442744
2745 it = ComponentIterator(.posix, u8).init("\\\\a");2745 it = .init("\\\\a");
2746 try std.testing.expect(null == it.root());2746 try std.testing.expect(null == it.root());
2747 }2747 }
2748 // Windows2748 // Windows
2749 {2749 {
2750 // Drive relative2750 // Drive relative
2751 var it = ComponentIterator(.windows, u8).init("C:a");2751 var it: ComponentIterator(.windows, u8) = .init("C:a");
2752 try std.testing.expectEqualStrings("C:", it.root().?);2752 try std.testing.expectEqualStrings("C:", it.root().?);
27532753
2754 // Drive absolute2754 // Drive absolute
2755 it = ComponentIterator(.windows, u8).init("C:/a");2755 it = .init("C:/a");
2756 try std.testing.expectEqualStrings("C:/", it.root().?);2756 try std.testing.expectEqualStrings("C:/", it.root().?);
2757 it = ComponentIterator(.windows, u8).init("C:\\a");2757 it = .init("C:\\a");
2758 try std.testing.expectEqualStrings("C:\\", it.root().?);2758 try std.testing.expectEqualStrings("C:\\", it.root().?);
2759 it = ComponentIterator(.windows, u8).init("C:///a");2759 it = .init("C:///a");
2760 try std.testing.expectEqualStrings("C:/", it.root().?);2760 try std.testing.expectEqualStrings("C:/", it.root().?);
27612761
2762 // Rooted2762 // Rooted
2763 it = ComponentIterator(.windows, u8).init("\\a");2763 it = .init("\\a");
2764 try std.testing.expectEqualStrings("\\", it.root().?);2764 try std.testing.expectEqualStrings("\\", it.root().?);
2765 it = ComponentIterator(.windows, u8).init("/a");2765 it = .init("/a");
2766 try std.testing.expectEqualStrings("/", it.root().?);2766 try std.testing.expectEqualStrings("/", it.root().?);
27672767
2768 // Root local device2768 // Root local device
2769 it = ComponentIterator(.windows, u8).init("\\\\.");2769 it = .init("\\\\.");
2770 try std.testing.expectEqualStrings("\\\\.", it.root().?);2770 try std.testing.expectEqualStrings("\\\\.", it.root().?);
2771 it = ComponentIterator(.windows, u8).init("//?");2771 it = .init("//?");
2772 try std.testing.expectEqualStrings("//?", it.root().?);2772 try std.testing.expectEqualStrings("//?", it.root().?);
27732773
2774 // UNC absolute2774 // UNC absolute
2775 it = ComponentIterator(.windows, u8).init("//");2775 it = .init("//");
2776 try std.testing.expectEqualStrings("//", it.root().?);2776 try std.testing.expectEqualStrings("//", it.root().?);
2777 it = ComponentIterator(.windows, u8).init("\\\\a");2777 it = .init("\\\\a");
2778 try std.testing.expectEqualStrings("\\\\a", it.root().?);2778 try std.testing.expectEqualStrings("\\\\a", it.root().?);
2779 it = ComponentIterator(.windows, u8).init("\\\\a\\b\\\\c");2779 it = .init("\\\\a\\b\\\\c");
2780 try std.testing.expectEqualStrings("\\\\a\\b\\", it.root().?);2780 try std.testing.expectEqualStrings("\\\\a\\b\\", it.root().?);
2781 it = ComponentIterator(.windows, u8).init("//a");2781 it = .init("//a");
2782 try std.testing.expectEqualStrings("//a", it.root().?);2782 try std.testing.expectEqualStrings("//a", it.root().?);
2783 it = ComponentIterator(.windows, u8).init("//a/b//c");2783 it = .init("//a/b//c");
2784 try std.testing.expectEqualStrings("//a/b/", it.root().?);2784 try std.testing.expectEqualStrings("//a/b/", it.root().?);
2785 // Malformed UNC path with empty server name2785 // Malformed UNC path with empty server name
2786 it = ComponentIterator(.windows, u8).init("\\\\\\a\\b\\c");2786 it = .init("\\\\\\a\\b\\c");
2787 try std.testing.expectEqualStrings("\\\\\\a\\", it.root().?);2787 try std.testing.expectEqualStrings("\\\\\\a\\", it.root().?);
2788 }2788 }
2789}2789}
lib/std/heap/debug_allocator.zig+4-4
...@@ -718,7 +718,7 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -718,7 +718,7 @@ pub fn DebugAllocator(comptime config: Config) type {
718718
719 fn alloc(context: *anyopaque, len: usize, alignment: mem.Alignment, ret_addr: usize) ?[*]u8 {719 fn alloc(context: *anyopaque, len: usize, alignment: mem.Alignment, ret_addr: usize) ?[*]u8 {
720 const self: *Self = @ptrCast(@alignCast(context));720 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);
722 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);722 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);
723723
724 if (config.enable_memory_limit) {724 if (config.enable_memory_limit) {
...@@ -831,7 +831,7 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -831,7 +831,7 @@ pub fn DebugAllocator(comptime config: Config) type {
831 return_address: usize,831 return_address: usize,
832 ) bool {832 ) bool {
833 const self: *Self = @ptrCast(@alignCast(context));833 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);
835 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);835 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);
836836
837 const size_class_index: usize = @max(@bitSizeOf(usize) - @clz(memory.len - 1), @backingInt(alignment));837 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 {...@@ -850,7 +850,7 @@ pub fn DebugAllocator(comptime config: Config) type {
850 return_address: usize,850 return_address: usize,
851 ) ?[*]u8 {851 ) ?[*]u8 {
852 const self: *Self = @ptrCast(@alignCast(context));852 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);
854 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);854 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);
855855
856 const size_class_index: usize = @max(@bitSizeOf(usize) - @clz(memory.len - 1), @backingInt(alignment));856 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 {...@@ -868,7 +868,7 @@ pub fn DebugAllocator(comptime config: Config) type {
868 return_address: usize,868 return_address: usize,
869 ) void {869 ) void {
870 const self: *Self = @ptrCast(@alignCast(context));870 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);
872 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);872 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);
873873
874 const size_class_index: usize = @max(@bitSizeOf(usize) - @clz(old_memory.len - 1), @backingInt(alignment));874 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) {...@@ -971,7 +971,7 @@ pub const Signedness = enum(u1) {
971971
972/// This data structure is used by the Zig language code generation and972/// This data structure is used by the Zig language code generation and
973/// therefore must be kept in sync with the compiler implementation.973/// therefore must be kept in sync with the compiler implementation.
974pub const OutputMode = enum {974pub const OutputMode = enum(u2) {
975 Exe,975 Exe,
976 Lib,976 Lib,
977 Obj,977 Obj,
lib/std/os/windows.zig+14
...@@ -147,9 +147,23 @@ pub const OBJECT = struct {...@@ -147,9 +147,23 @@ pub const OBJECT = struct {
147 pub const Max: @typeInfo(@This()).@"enum".tag_type = @typeInfo(@This()).@"enum".field_names.len;147 pub const Max: @typeInfo(@This()).@"enum".tag_type = @typeInfo(@This()).@"enum".field_names.len;
148 };148 };
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
150 pub const NAME_INFORMATION = extern struct {158 pub const NAME_INFORMATION = extern struct {
151 Name: UNICODE_STRING,159 Name: UNICODE_STRING,
152 };160 };
161
162 pub const HANDLE_FLAG = packed struct(USHORT) {
163 INHERIT: bool = false,
164 PROTECT_FROM_CLOSE: bool = false,
165 Reserved1: u14 = 0,
166 };
153};167};
154168
155pub const FILE = struct {169pub const FILE = struct {
lib/std/os/windows/ntdll.zig+7
...@@ -461,6 +461,13 @@ pub extern "ntdll" fn LdrUnregisterDllNotification(...@@ -461,6 +461,13 @@ pub extern "ntdll" fn LdrUnregisterDllNotification(
461 Cookie: LDR.DLL_NOTIFICATION.COOKIE,461 Cookie: LDR.DLL_NOTIFICATION.COOKIE,
462) callconv(.winapi) NTSTATUS;462) 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
464pub extern "ntdll" fn NtQueryAttributesFile(471pub extern "ntdll" fn NtQueryAttributesFile(
465 ObjectAttributes: *const OBJECT.ATTRIBUTES,472 ObjectAttributes: *const OBJECT.ATTRIBUTES,
466 FileAttributes: *FILE.BASIC_INFORMATION,473 FileAttributes: *FILE.BASIC_INFORMATION,
lib/std/process.zig+36-30
...@@ -282,37 +282,50 @@ pub const ReplaceError = error{...@@ -282,37 +282,50 @@ pub const ReplaceError = error{
282} || Allocator.Error || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;282} || Allocator.Error || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
283283
284pub const ReplaceOptions = struct {284pub const ReplaceOptions = struct {
285 exe: Exe = .detect,
285 argv: []const []const u8,286 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,
287 /// Replaces the environment when provided. The PATH value from here is290 /// Replaces the environment when provided. The PATH value from here is
288 /// never used to resolve `argv[0]`.291 /// never used to resolve `argv[0]`.
289 environ_map: ?*const Environ.Map = null,292 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 };
290};319};
291320
292/// Replaces the current process image with the executed process. If this321/// Replaces the current process image with the executed process. If this
293/// function succeeds, it does not return.322/// function succeeds, it does not return.
294///323///
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///
299/// It is illegal to call this function in a fork() child.324/// It is illegal to call this function in a fork() child.
300pub fn replace(io: Io, options: ReplaceOptions) ReplaceError {325pub fn replace(io: Io, options: ReplaceOptions) ReplaceError {
301 return io.vtable.processReplace(io.userdata, options);326 return io.vtable.processReplace(io.userdata, options);
302}327}
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
316pub const ArgExpansion = enum { expand, no_expand };329pub const ArgExpansion = enum { expand, no_expand };
317330
318/// File name extensions supported natively by `CreateProcess()` on Windows.331/// File name extensions supported natively by `CreateProcess()` on Windows.
...@@ -368,6 +381,7 @@ pub const SpawnError = error{...@@ -368,6 +381,7 @@ pub const SpawnError = error{
368} || Io.File.OpenError || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;381} || Io.File.OpenError || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
369382
370pub const SpawnOptions = struct {383pub const SpawnOptions = struct {
384 exe: ReplaceOptions.Exe = .detect,
371 argv: []const []const u8,385 argv: []const []const u8,
372386
373 /// Set to change the current working directory when spawning the child process.387 /// Set to change the current working directory when spawning the child process.
...@@ -385,11 +399,13 @@ pub const SpawnOptions = struct {...@@ -385,11 +399,13 @@ pub const SpawnOptions = struct {
385 ///399 ///
386 /// The child's progress tree will be grafted into the parent's progress tree,400 /// The child's progress tree will be grafted into the parent's progress tree,
387 /// by substituting this node with the child's root node.401 /// 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
390 stdin: StdIo = .inherit,404 stdin: StdIo = .inherit,
391 stdout: StdIo = .inherit,405 stdout: StdIo = .inherit,
392 stderr: StdIo = .inherit,406 stderr: StdIo = .inherit,
407 inherit_dirs: []const Dir = &.{},
408 inherit_files: []const File = &.{},
393409
394 /// Set to true to obtain rusage information for the child process.410 /// Set to true to obtain rusage information for the child process.
395 /// Depending on the target platform and implementation status, the411 /// Depending on the target platform and implementation status, the
...@@ -443,27 +459,16 @@ pub const SpawnOptions = struct {...@@ -443,27 +459,16 @@ pub const SpawnOptions = struct {
443};459};
444460
445/// Creates a child process.461/// 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.
450pub fn spawn(io: Io, options: SpawnOptions) SpawnError!Child {462pub fn spawn(io: Io, options: SpawnOptions) SpawnError!Child {
451 return io.vtable.processSpawn(io.userdata, options);463 return io.vtable.processSpawn(io.userdata, options);
452}464}
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
462pub const RunError = error{466pub const RunError = error{
463 StreamTooLong,467 StreamTooLong,
464} || SpawnError || Io.File.MultiReader.UnendingError || Io.Timeout.Error;468} || SpawnError || Io.File.MultiReader.UnendingError || Io.Timeout.Error;
465469
466pub const RunOptions = struct {470pub const RunOptions = struct {
471 exe: ReplaceOptions.Exe = .detect,
467 argv: []const []const u8,472 argv: []const []const u8,
468 stderr_limit: Io.Limit = .unlimited,473 stderr_limit: Io.Limit = .unlimited,
469 stdout_limit: Io.Limit = .unlimited,474 stdout_limit: Io.Limit = .unlimited,
...@@ -501,6 +506,7 @@ pub const RunResult = struct {...@@ -501,6 +506,7 @@ pub const RunResult = struct {
501/// If it succeeds, the caller owns result.stdout and result.stderr memory.506/// If it succeeds, the caller owns result.stdout and result.stderr memory.
502pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult {507pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult {
503 var child = try spawn(io, .{508 var child = try spawn(io, .{
509 .exe = options.exe,
504 .argv = options.argv,510 .argv = options.argv,
505 .cwd = options.cwd,511 .cwd = options.cwd,
506 .environ_map = options.environ_map,512 .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) ![...@@ -727,8 +727,7 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *Io.File.Reader) ![
727}727}
728728
729pub fn printAstErrorsToStderr(gpa: Allocator, io: Io, tree: Ast, path: []const u8, color: Color) !void {729pub fn printAstErrorsToStderr(gpa: Allocator, io: Io, tree: Ast, path: []const u8, color: Color) !void {
730 var wip_errors: ErrorBundle.Wip = undefined;730 var wip_errors: ErrorBundle.Wip = try .init(gpa);
731 try wip_errors.init(gpa);
732 defer wip_errors.deinit();731 defer wip_errors.deinit();
733732
734 try putAstErrorsIntoBundle(gpa, tree, path, &wip_errors);733 try putAstErrorsIntoBundle(gpa, tree, path, &wip_errors);
lib/std/zig/Client.zig+29
...@@ -60,6 +60,8 @@ pub const Message = struct {...@@ -60,6 +60,8 @@ pub const Message = struct {
60 start_fuzzing,60 start_fuzzing,
61 /// The message body has the same format as in Server.61 /// The message body has the same format as in Server.
62 new_fuzz_input,62 new_fuzz_input,
63 /// Body is a sequence of Arg
64 args,
6365
64 /// Asks the server to run a list of steps.66 /// Asks the server to run a list of steps.
65 /// Body is a `BuildSteps`.67 /// Body is a `BuildSteps`.
...@@ -69,6 +71,33 @@ pub const Message = struct {...@@ -69,6 +71,33 @@ pub const Message = struct {
69 _,71 _,
70 };72 };
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
72 /// Trailing:101 /// Trailing:
73 /// * step_indices: [step_count]std.Build.Configuration.Step.Index,102 /// * step_indices: [step_count]std.Build.Configuration.Step.Index,
74 pub const BuildSteps = extern struct {103 pub const BuildSteps = extern struct {
lib/std/zig/ErrorBundle.zig+7-4
...@@ -360,22 +360,25 @@ pub const Wip = struct {...@@ -360,22 +360,25 @@ pub const Wip = struct {
360 extra: std.ArrayList(u32),360 extra: std.ArrayList(u32),
361 root_list: std.ArrayList(MessageIndex),361 root_list: std.ArrayList(MessageIndex),
362362
363 pub fn init(wip: *Wip, gpa: Allocator) !void {363 pub fn init(gpa: Allocator) !Wip {
364 wip.* = .{364 var wip: Wip = .{
365 .gpa = gpa,365 .gpa = gpa,
366 .string_bytes = .empty,366 .string_bytes = .empty,
367 .extra = .empty,367 .extra = .empty,
368 .root_list = .empty,368 .root_list = .empty,
369 };369 };
370 errdefer wip.deinit();
370371
371 // So that 0 can be used to indicate a null string.372 // So that 0 can be used to indicate a null string.
372 try wip.string_bytes.append(gpa, 0);373 try wip.string_bytes.append(gpa, 0);
373374
374 assert(0 == try addExtra(wip, ErrorMessageList{375 assert(0 == try wip.addExtra(ErrorMessageList{
375 .len = 0,376 .len = 0,
376 .start = 0,377 .start = 0,
377 .compile_log_text = 0,378 .compile_log_text = 0,
378 }));379 }));
380
381 return wip;
379 }382 }
380383
381 pub fn deinit(wip: *Wip) void {384 pub fn deinit(wip: *Wip) void {
...@@ -773,7 +776,7 @@ pub const Wip = struct {...@@ -773,7 +776,7 @@ pub const Wip = struct {
773 u32 => @field(extra, field_name),776 u32 => @field(extra, field_name),
774 MessageIndex => @backingInt(@field(extra, field_name)),777 MessageIndex => @backingInt(@field(extra, field_name)),
775 SourceLocationIndex => @backingInt(@field(extra, field_name)),778 SourceLocationIndex => @backingInt(@field(extra, field_name)),
776 else => @compileError("bad field type"),779 else => @compileError("bad field type: " ++ @typeName(field_type)),
777 };780 };
778 i += 1;781 i += 1;
779 }782 }
lib/std/zig/Server.zig+40-2
...@@ -74,6 +74,12 @@ pub const Message = struct {...@@ -74,6 +74,12 @@ pub const Message = struct {
74 broadcast_fuzz_input,74 broadcast_fuzz_input,
75 /// Body is a TimeReport.75 /// Body is a TimeReport.
76 time_report,76 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
78 /// The first message sent by the server over the build system protocol.84 /// The first message sent by the server over the build system protocol.
79 /// Body is a `Handshake`.85 /// Body is a `Handshake`.
...@@ -112,7 +118,7 @@ pub const Message = struct {...@@ -112,7 +118,7 @@ pub const Message = struct {
112118
113 pub const Flags = packed struct(u32) {119 pub const Flags = packed struct(u32) {
114 file_system_watch_supported: bool,120 file_system_watch_supported: bool,
115 _: u31 = 0,121 unused: u31 = 0,
116 };122 };
117 };123 };
118124
...@@ -198,7 +204,7 @@ pub const Message = struct {...@@ -198,7 +204,7 @@ pub const Message = struct {
198 flags: Flags,204 flags: Flags,
199 pub const Flags = packed struct(u32) {205 pub const Flags = packed struct(u32) {
200 use_llvm: bool,206 use_llvm: bool,
201 _: u31 = 0,207 unused: u31 = 0,
202 };208 };
203 };209 };
204210
...@@ -212,6 +218,29 @@ pub const Message = struct {...@@ -212,6 +218,29 @@ pub const Message = struct {
212 reserved: u7 = 0,218 reserved: u7 = 0,
213 };219 };
214 };220 };
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 };
215};244};
216245
217pub fn receiveMessage(s: *Server) !InMessage.Header {246pub fn receiveMessage(s: *Server) !InMessage.Header {
...@@ -365,3 +394,12 @@ pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void {...@@ -365,3 +394,12 @@ pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void {
365 try s.out.writeAll(test_metadata.string_bytes);394 try s.out.writeAll(test_metadata.string_bytes);
366 try s.out.flush();395 try s.out.flush();
367}396}
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) {...@@ -33,13 +33,9 @@ pub const Executor = union(enum) {
33pub const GetExternalExecutorOptions = struct {33pub const GetExternalExecutorOptions = struct {
34 host_cpu_arch: std.Target.Cpu.Arch,34 host_cpu_arch: std.Target.Cpu.Arch,
35 host_os_tag: std.Target.Os.Tag,35 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,
41 qemu_fixes_dl: bool = false,36 qemu_fixes_dl: bool = false,
42 link_libc: bool = false,37 link_mode: std.lang.LinkMode,
38 link_libc: bool,
43};39};
4440
45/// Return whether or not the given host is capable of running executables of41/// 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...@@ -71,7 +67,7 @@ pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExt
71 var bad_result: Executor = .bad_os_or_cpu;67 var bad_result: Executor = .bad_os_or_cpu;
7268
73 if (os_match and cpu_ok) native: {69 if (os_match and cpu_ok) native: {
74 if (options.link_libc) {70 if (options.link_libc and options.link_mode == .dynamic) {
75 if (candidate.dynamic_linker.get()) |candidate_dl| {71 if (candidate.dynamic_linker.get()) |candidate_dl| {
76 Io.Dir.cwd().access(io, candidate_dl, .{}) catch {72 Io.Dir.cwd().access(io, candidate_dl, .{}) catch {
77 bad_result = .{ .bad_dl = candidate_dl };73 bad_result = .{ .bad_dl = candidate_dl };
...@@ -84,9 +80,7 @@ pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExt...@@ -84,9 +80,7 @@ pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExt
8480
85 // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 281 // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2
86 // to emulate the foreign architecture.82 // to emulate the foreign architecture.
87 if (options.allow_rosetta and os_match and83 if (os_match and (host_os_tag == .maccatalyst or host_os_tag == .macos) and host_cpu_arch == .aarch64) {
88 (host_os_tag == .maccatalyst or host_os_tag == .macos) and host_cpu_arch == .aarch64)
89 {
90 switch (candidate.cpu.arch) {84 switch (candidate.cpu.arch) {
91 .x86_64 => return .rosetta,85 .x86_64 => return .rosetta,
92 else => return bad_result,86 else => return bad_result,
...@@ -94,7 +88,7 @@ pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExt...@@ -94,7 +88,7 @@ pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExt
94 }88 }
9589
96 // If the OS matches, we can use QEMU to emulate a foreign architecture.90 // 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)) {
98 return switch (candidate.cpu.arch) {92 return switch (candidate.cpu.arch) {
99 inline .aarch64,93 inline .aarch64,
100 .arm,94 .arm,
...@@ -167,38 +161,32 @@ pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExt...@@ -167,38 +161,32 @@ pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExt
167 };161 };
168 }162 }
169163
170 if (options.allow_wasmtime and candidate.cpu.arch.isWasm()) {164 if (candidate.cpu.arch.isWasm()) {
171 return .{ .wasmtime = "wasmtime" };165 return .{ .wasmtime = "wasmtime" };
172 }166 }
173167
174 switch (candidate.os.tag) {168 switch (candidate.os.tag) {
175 .windows => {169 .windows => {
176 if (options.allow_wine) {170 const wine_supported = switch (candidate.cpu.arch) {
177 const wine_supported = switch (candidate.cpu.arch) {171 .thumb => switch (host_cpu_arch) {
178 .thumb => switch (host_cpu_arch) {172 .arm, .thumb, .aarch64 => true,
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,
185 else => false,173 else => false,
186 };174 },
187 return if (wine_supported) .{ .wine = "wine" } else bad_result;175 .aarch64 => host_cpu_arch == .aarch64,
188 }176 .x86 => host_cpu_arch.isX86(),
189 return bad_result;177 .x86_64 => host_cpu_arch == .x86_64,
178 else => false,
179 };
180 return if (wine_supported) .{ .wine = "wine" } else bad_result;
190 },181 },
191 .driverkit, .macos => {182 .driverkit, .macos => {
192 if (options.allow_darling) {183 // This check can be loosened once darling adds a QEMU-based emulation
193 // This check can be loosened once darling adds a QEMU-based emulation184 // layer for non-host architectures:
194 // layer for non-host architectures:185 // https://github.com/darlinghq/darling/issues/863
195 // https://github.com/darlinghq/darling/issues/863186 if (candidate.cpu.arch != host_cpu_arch) {
196 if (candidate.cpu.arch != host_cpu_arch) {187 return bad_result;
197 return bad_result;
198 }
199 return .{ .darling = "darling" };
200 }188 }
201 return bad_result;189 return .{ .darling = "darling" };
202 },190 },
203 else => return bad_result,191 else => return bad_result,
204 }192 }
src/Compilation.zig+3-6
...@@ -3842,8 +3842,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle {...@@ -3842,8 +3842,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle {
3842 const gpa = comp.gpa;3842 const gpa = comp.gpa;
3843 const io = comp.io;3843 const io = comp.io;
38443844
3845 var bundle: ErrorBundle.Wip = undefined;3845 var bundle: ErrorBundle.Wip = try .init(gpa);
3846 try bundle.init(gpa);
3847 defer bundle.deinit();3846 defer bundle.deinit();
38483847
3849 for (comp.failed_c_objects.values()) |diag_bundle| {3848 for (comp.failed_c_objects.values()) |diag_bundle| {
...@@ -5447,8 +5446,7 @@ fn reportRetryableWin32ResourceError(...@@ -5447,8 +5446,7 @@ fn reportRetryableWin32ResourceError(
54475446
5448 win32_resource.status = .failure_retryable;5447 win32_resource.status = .failure_retryable;
54495448
5450 var bundle: ErrorBundle.Wip = undefined;5449 var bundle: ErrorBundle.Wip = try .init(comp.gpa);
5451 try bundle.init(comp.gpa);
5452 errdefer bundle.deinit();5450 errdefer bundle.deinit();
5453 try bundle.addRootErrorMessage(.{5451 try bundle.addRootErrorMessage(.{
5454 .msg = try bundle.printString("{s}", .{@errorName(err)}),5452 .msg = try bundle.printString("{s}", .{@errorName(err)}),
...@@ -6835,8 +6833,7 @@ fn failCObjWithOwnedDiagBundle(...@@ -6835,8 +6833,7 @@ fn failCObjWithOwnedDiagBundle(
68356833
6836fn failWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, comptime format: []const u8, args: anytype) error{ OutOfMemory, AlreadyReported } {6834fn failWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, comptime format: []const u8, args: anytype) error{ OutOfMemory, AlreadyReported } {
6837 @branchHint(.cold);6835 @branchHint(.cold);
6838 var bundle: ErrorBundle.Wip = undefined;6836 var bundle: ErrorBundle.Wip = try .init(comp.gpa);
6839 try bundle.init(comp.gpa);
6840 errdefer bundle.deinit();6837 errdefer bundle.deinit();
6841 try bundle.addRootErrorMessage(.{6838 try bundle.addRootErrorMessage(.{
6842 .msg = try bundle.printString(format, args),6839 .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...@@ -2705,8 +2705,7 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg
2705 assert(sema.err == null);2705 assert(sema.err == null);
27062706
2707 if (build_options.enable_debug_extensions and comp.debug_compile_errors) {2707 if (build_options.enable_debug_extensions and comp.debug_compile_errors) {
2708 var wip_errors: std.zig.ErrorBundle.Wip = undefined;2708 var wip_errors = std.zig.ErrorBundle.Wip.init(gpa) catch @panic("out of memory");
2709 wip_errors.init(gpa) catch @panic("out of memory");
2710 Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*, false) catch @panic("out of memory");2709 Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*, false) catch @panic("out of memory");
2711 std.debug.print("compile error during Sema:\n", .{});2710 std.debug.print("compile error during Sema:\n", .{});
2712 var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory");2711 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) !...@@ -130,8 +130,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
130 defer zir.deinit(gpa);130 defer zir.deinit(gpa);
131131
132 if (zir.hasCompileErrors()) {132 if (zir.hasCompileErrors()) {
133 var wip_errors: std.zig.ErrorBundle.Wip = undefined;133 var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa);
134 try wip_errors.init(gpa);
135 defer wip_errors.deinit();134 defer wip_errors.deinit();
136 try wip_errors.addZirErrorMessages(zir, tree, source_code, "<stdin>");135 try wip_errors.addZirErrorMessages(zir, tree, source_code, "<stdin>");
137 var error_bundle = try wip_errors.toOwnedBundle("");136 var error_bundle = try wip_errors.toOwnedBundle("");
...@@ -144,8 +143,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !...@@ -144,8 +143,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
144 defer zoir.deinit(gpa);143 defer zoir.deinit(gpa);
145144
146 if (zoir.hasCompileErrors()) {145 if (zoir.hasCompileErrors()) {
147 var wip_errors: std.zig.ErrorBundle.Wip = undefined;146 var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa);
148 try wip_errors.init(gpa);
149 defer wip_errors.deinit();147 defer wip_errors.deinit();
150 try wip_errors.addZoirErrorMessages(zoir, tree, source_code, "<stdin>");148 try wip_errors.addZoirErrorMessages(zoir, tree, source_code, "<stdin>");
151 var error_bundle = try wip_errors.toOwnedBundle("");149 var error_bundle = try wip_errors.toOwnedBundle("");
...@@ -333,8 +331,7 @@ fn fmtPathFile(...@@ -333,8 +331,7 @@ fn fmtPathFile(
333 defer zir.deinit(gpa);331 defer zir.deinit(gpa);
334332
335 if (zir.hasCompileErrors()) {333 if (zir.hasCompileErrors()) {
336 var wip_errors: std.zig.ErrorBundle.Wip = undefined;334 var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa);
337 try wip_errors.init(gpa);
338 defer wip_errors.deinit();335 defer wip_errors.deinit();
339 try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path);336 try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path);
340 var error_bundle = try wip_errors.toOwnedBundle("");337 var error_bundle = try wip_errors.toOwnedBundle("");
...@@ -348,8 +345,7 @@ fn fmtPathFile(...@@ -348,8 +345,7 @@ fn fmtPathFile(
348 defer zoir.deinit(gpa);345 defer zoir.deinit(gpa);
349346
350 if (zoir.hasCompileErrors()) {347 if (zoir.hasCompileErrors()) {
351 var wip_errors: std.zig.ErrorBundle.Wip = undefined;348 var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa);
352 try wip_errors.init(gpa);
353 defer wip_errors.deinit();349 defer wip_errors.deinit();
354 try wip_errors.addZoirErrorMessages(zoir, tree, source_code, file_path);350 try wip_errors.addZoirErrorMessages(zoir, tree, source_code, file_path);
355 var error_bundle = try wip_errors.toOwnedBundle("");351 var error_bundle = try wip_errors.toOwnedBundle("");
src/link.zig+1-2
...@@ -2891,8 +2891,7 @@ fn resolvePathInputLib(...@@ -2891,8 +2891,7 @@ fn resolvePathInputLib(
28912891
2892 const ld_script_result = LdScript.parse(gpa, &diags, test_path, ld_script_bytes.items);2892 const ld_script_result = LdScript.parse(gpa, &diags, test_path, ld_script_bytes.items);
2893 if (diags.hasErrors()) {2893 if (diags.hasErrors()) {
2894 var wip_errors: std.zig.ErrorBundle.Wip = undefined;2894 var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa);
2895 try wip_errors.init(gpa);
2896 defer wip_errors.deinit();2895 defer wip_errors.deinit();
28972896
2898 try diags.addMessagesToBundle(&wip_errors, null);2897 try diags.addMessagesToBundle(&wip_errors, null);
src/main.zig+44-36
...@@ -3971,11 +3971,9 @@ fn buildOutputType(...@@ -3971,11 +3971,9 @@ fn buildOutputType(
3971 test_exec_args.items,3971 test_exec_args.items,
3972 self_exe_path_or_argv0,3972 self_exe_path_or_argv0,
3973 arg_mode,3973 arg_mode,
3974 target,
3975 &comp_destroyed,3974 &comp_destroyed,
3976 all_args,3975 all_args,
3977 runtime_args_start,3976 runtime_args_start,
3978 create_module.resolved_options.link_libc,
3979 test_execve,3977 test_execve,
3980 environ_map,3978 environ_map,
3981 );3979 );
...@@ -4410,6 +4408,15 @@ fn serve(...@@ -4410,6 +4408,15 @@ fn serve(
4410 var server: Server = .{ .in = in, .out = out };4408 var server: Server = .{ .in = in, .out = out };
4411 try server.serveStringMessage(.zig_version, build_options.version);4409 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
4413 var child_pid: ?std.process.Child.Id = null;4420 var child_pid: ?std.process.Child.Id = null;
44144421
4415 const main_progress_node = std.Progress.start(io, .{});4422 const main_progress_node = std.Progress.start(io, .{});
...@@ -4482,8 +4489,6 @@ fn serve(...@@ -4482,8 +4489,6 @@ fn serve(
4482 // test_exec_args,4489 // test_exec_args,
4483 // self_exe_path.?,4490 // self_exe_path.?,
4484 // arg_mode,4491 // arg_mode,
4485 // target,
4486 // true,
4487 // &comp_destroyed,4492 // &comp_destroyed,
4488 // all_args,4493 // all_args,
4489 // runtime_args_start,4494 // runtime_args_start,
...@@ -4634,11 +4639,9 @@ fn runOrTest(...@@ -4634,11 +4639,9 @@ fn runOrTest(
4634 test_exec_args: []const ?[]const u8,4639 test_exec_args: []const ?[]const u8,
4635 self_exe_path: []const u8,4640 self_exe_path: []const u8,
4636 arg_mode: ArgMode,4641 arg_mode: ArgMode,
4637 target: *const std.Target,
4638 comp_destroyed: *bool,4642 comp_destroyed: *bool,
4639 all_args: []const []const u8,4643 all_args: []const []const u8,
4640 runtime_args_start: ?usize,4644 runtime_args_start: ?usize,
4641 link_libc: bool,
4642 test_execve: bool,4645 test_execve: bool,
4643 environ_map: *process.Environ.Map,4646 environ_map: *process.Environ.Map,
4644) !void {4647) !void {
...@@ -4686,7 +4689,12 @@ fn runOrTest(...@@ -4686,7 +4689,12 @@ fn runOrTest(
4686 _ = try io.lockStderr(&.{}, .no_color);4689 _ = try io.lockStderr(&.{}, .no_color);
4687 const err = process.replace(io, .{ .argv = argv.items, .environ_map = environ_map });4690 const err = process.replace(io, .{ .argv = argv.items, .environ_map = environ_map });
4688 io.unlockStderr();4691 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 });
4690 const cmd = try std.mem.join(arena, " ", argv.items);4698 const cmd = try std.mem.join(arena, " ", argv.items);
4691 fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd });4699 fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd });
4692 } else if (!process.can_spawn) {4700 } else if (!process.can_spawn) {
...@@ -4716,7 +4724,12 @@ fn runOrTest(...@@ -4716,7 +4724,12 @@ fn runOrTest(
4716 });4724 });
47174725
4718 const term = term_result catch |err| {4726 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 });
4720 const cmd = try std.mem.join(arena, " ", argv.items);4733 const cmd = try std.mem.join(arena, " ", argv.items);
4721 fatal("the following command failed with {t}:\n{s}", .{ err, cmd });4734 fatal("the following command failed with {t}:\n{s}", .{ err, cmd });
4722 };4735 };
...@@ -5647,8 +5660,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8, environ_map:...@@ -5647,8 +5660,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8, environ_map:
5647 const zir = try AstGen.generate(arena, tree);5660 const zir = try AstGen.generate(arena, tree);
56485661
5649 if (zir.hasCompileErrors()) {5662 if (zir.hasCompileErrors()) {
5650 var wip_errors: std.zig.ErrorBundle.Wip = undefined;5663 var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena);
5651 try wip_errors.init(arena);
5652 try wip_errors.addZirErrorMessages(zir, tree, source, display_path);5664 try wip_errors.addZirErrorMessages(zir, tree, source, display_path);
5653 var error_bundle = try wip_errors.toOwnedBundle("");5665 var error_bundle = try wip_errors.toOwnedBundle("");
5654 try error_bundle.renderToStderr(io, .{}, color);5666 try error_bundle.renderToStderr(io, .{}, color);
...@@ -5718,8 +5730,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8, environ_map:...@@ -5718,8 +5730,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8, environ_map:
5718 .zon => {5730 .zon => {
5719 const zoir = try ZonGen.generate(arena, tree, .{});5731 const zoir = try ZonGen.generate(arena, tree, .{});
5720 if (zoir.hasCompileErrors()) {5732 if (zoir.hasCompileErrors()) {
5721 var wip_errors: std.zig.ErrorBundle.Wip = undefined;5733 var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena);
5722 try wip_errors.init(arena);
5723 try wip_errors.addZoirErrorMessages(zoir, tree, source, display_path);5734 try wip_errors.addZoirErrorMessages(zoir, tree, source, display_path);
5724 var error_bundle = try wip_errors.toOwnedBundle("");5735 var error_bundle = try wip_errors.toOwnedBundle("");
5725 error_bundle.renderToStderr(io, .{}, color) catch {};5736 error_bundle.renderToStderr(io, .{}, color) catch {};
...@@ -5812,8 +5823,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8, environ_map...@@ -5812,8 +5823,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8, environ_map
5812 const old_zir = try AstGen.generate(arena, old_tree);5823 const old_zir = try AstGen.generate(arena, old_tree);
58135824
5814 if (old_zir.loweringFailed()) {5825 if (old_zir.loweringFailed()) {
5815 var wip_errors: std.zig.ErrorBundle.Wip = undefined;5826 var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena);
5816 try wip_errors.init(arena);
5817 try wip_errors.addZirErrorMessages(old_zir, old_tree, old_source, old_source_path);5827 try wip_errors.addZirErrorMessages(old_zir, old_tree, old_source, old_source_path);
5818 var error_bundle = try wip_errors.toOwnedBundle("");5828 var error_bundle = try wip_errors.toOwnedBundle("");
5819 error_bundle.renderToStderr(io, .{}, color) catch {};5829 error_bundle.renderToStderr(io, .{}, color) catch {};
...@@ -5824,8 +5834,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8, environ_map...@@ -5824,8 +5834,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8, environ_map
5824 const new_zir = try AstGen.generate(arena, new_tree);5834 const new_zir = try AstGen.generate(arena, new_tree);
58255835
5826 if (new_zir.loweringFailed()) {5836 if (new_zir.loweringFailed()) {
5827 var wip_errors: std.zig.ErrorBundle.Wip = undefined;5837 var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena);
5828 try wip_errors.init(arena);
5829 try wip_errors.addZirErrorMessages(new_zir, new_tree, new_source, new_source_path);5838 try wip_errors.addZirErrorMessages(new_zir, new_tree, new_source, new_source_path);
5830 var error_bundle = try wip_errors.toOwnedBundle("");5839 var error_bundle = try wip_errors.toOwnedBundle("");
5831 error_bundle.renderToStderr(io, .{}, color) catch {};5840 error_bundle.renderToStderr(io, .{}, color) catch {};
...@@ -5867,33 +5876,32 @@ fn prefixedIntArg(arg: []const u8, prefix: []const u8) ?u64 {...@@ -5867,33 +5876,32 @@ fn prefixedIntArg(arg: []const u8, prefix: []const u8) ?u64 {
5867 return std.fmt.parseUnsigned(u64, number, 0) catch |err| fatal("unable to parse {q}: {t}", .{ arg, err });5876 return std.fmt.parseUnsigned(u64, number, 0) catch |err| fatal("unable to parse {q}: {t}", .{ arg, err });
5868}5877}
58695878
5870fn warnAboutForeignBinaries(5879fn warnAboutForeignBinaries(io: Io, arena: Allocator, opts: struct {
5871 io: Io,
5872 arena: Allocator,
5873 arg_mode: ArgMode,5880 arg_mode: ArgMode,
5874 target: *const std.Target,5881 target: *const std.Target,
5882 link_mode: std.lang.LinkMode,
5875 link_libc: bool,5883 link_libc: bool,
5876) !void {5884}) !void {
5877 const host_query: std.Target.Query = .{};5885 const host_target = std.zig.resolveTargetQueryOrFatal(io, .{});
5878 const host_target = std.zig.resolveTargetQueryOrFatal(io, host_query);
58795886
5880 switch (std.zig.system.getExternalExecutor(io, target, .{5887 switch (std.zig.system.getExternalExecutor(io, opts.target, .{
5881 .host_cpu_arch = host_target.cpu.arch,5888 .host_cpu_arch = host_target.cpu.arch,
5882 .host_os_tag = host_target.os.tag,5889 .host_os_tag = host_target.os.tag,
5883 .link_libc = link_libc,5890 .link_mode = opts.link_mode,
5891 .link_libc = opts.link_libc,
5884 })) {5892 })) {
5885 .native => return,5893 .native => return,
5886 .rosetta => {5894 .rosetta => {
5887 const host_name = try host_target.zigTriple(arena);5895 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);
5889 warn("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}). Consider installing Rosetta.", .{5897 warn("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}). Consider installing Rosetta.", .{
5890 host_name, foreign_name,5898 host_name, foreign_name,
5891 });5899 });
5892 },5900 },
5893 .qemu => |qemu| {5901 .qemu => |qemu| {
5894 const host_name = try host_target.zigTriple(arena);5902 const host_name = try host_target.zigTriple(arena);
5895 const foreign_name = try target.zigTriple(arena);5903 const foreign_name = try opts.target.zigTriple(arena);
5896 switch (arg_mode) {5904 switch (opts.arg_mode) {
5897 .zig_test => warn(5905 .zig_test => warn(
5898 "the host system ({s}) does not appear to be capable of executing binaries " ++5906 "the host system ({s}) does not appear to be capable of executing binaries " ++
5899 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++5907 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
...@@ -5909,8 +5917,8 @@ fn warnAboutForeignBinaries(...@@ -5909,8 +5917,8 @@ fn warnAboutForeignBinaries(
5909 },5917 },
5910 .wine => |wine| {5918 .wine => |wine| {
5911 const host_name = try host_target.zigTriple(arena);5919 const host_name = try host_target.zigTriple(arena);
5912 const foreign_name = try target.zigTriple(arena);5920 const foreign_name = try opts.target.zigTriple(arena);
5913 switch (arg_mode) {5921 switch (opts.arg_mode) {
5914 .zig_test => warn(5922 .zig_test => warn(
5915 "the host system ({s}) does not appear to be capable of executing binaries " ++5923 "the host system ({s}) does not appear to be capable of executing binaries " ++
5916 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++5924 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
...@@ -5926,8 +5934,8 @@ fn warnAboutForeignBinaries(...@@ -5926,8 +5934,8 @@ fn warnAboutForeignBinaries(
5926 },5934 },
5927 .wasmtime => |wasmtime| {5935 .wasmtime => |wasmtime| {
5928 const host_name = try host_target.zigTriple(arena);5936 const host_name = try host_target.zigTriple(arena);
5929 const foreign_name = try target.zigTriple(arena);5937 const foreign_name = try opts.target.zigTriple(arena);
5930 switch (arg_mode) {5938 switch (opts.arg_mode) {
5931 .zig_test => warn(5939 .zig_test => warn(
5932 "the host system ({s}) does not appear to be capable of executing binaries " ++5940 "the host system ({s}) does not appear to be capable of executing binaries " ++
5933 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++5941 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
...@@ -5943,8 +5951,8 @@ fn warnAboutForeignBinaries(...@@ -5943,8 +5951,8 @@ fn warnAboutForeignBinaries(
5943 },5951 },
5944 .darling => |darling| {5952 .darling => |darling| {
5945 const host_name = try host_target.zigTriple(arena);5953 const host_name = try host_target.zigTriple(arena);
5946 const foreign_name = try target.zigTriple(arena);5954 const foreign_name = try opts.target.zigTriple(arena);
5947 switch (arg_mode) {5955 switch (opts.arg_mode) {
5948 .zig_test => warn(5956 .zig_test => warn(
5949 "the host system ({s}) does not appear to be capable of executing binaries " ++5957 "the host system ({s}) does not appear to be capable of executing binaries " ++
5950 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++5958 "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++
...@@ -5960,7 +5968,7 @@ fn warnAboutForeignBinaries(...@@ -5960,7 +5968,7 @@ fn warnAboutForeignBinaries(
5960 },5968 },
5961 .bad_dl => |foreign_dl| {5969 .bad_dl => |foreign_dl| {
5962 const host_dl = host_target.dynamic_linker.get() orelse "(none)";5970 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) {
5964 .zig_test => ", '--test-no-exec', or '--test-cmd'",5972 .zig_test => ", '--test-no-exec', or '--test-cmd'",
5965 else => "",5973 else => "",
5966 };5974 };
...@@ -5970,8 +5978,8 @@ fn warnAboutForeignBinaries(...@@ -5970,8 +5978,8 @@ fn warnAboutForeignBinaries(
5970 },5978 },
5971 .bad_os_or_cpu => {5979 .bad_os_or_cpu => {
5972 const host_name = try host_target.zigTriple(arena);5980 const host_name = try host_target.zigTriple(arena);
5973 const foreign_name = try target.zigTriple(arena);5981 const foreign_name = try opts.target.zigTriple(arena);
5974 const tip_suffix = switch (arg_mode) {5982 const tip_suffix = switch (opts.arg_mode) {
5975 .zig_test => ". Consider using '--test-no-exec' or '--test-cmd'",5983 .zig_test => ". Consider using '--test-no-exec' or '--test-cmd'",
5976 else => "",5984 else => "",
5977 };5985 };
test/incremental/add_decl+45-18
...@@ -1,15 +1,21 @@...@@ -1,15 +1,21 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const std = @import("std");6const std = @import("std");
4pub fn main() !void {7pub fn main() !void {
5 try std.Io.File.stdout().writeStreamingAll(io, foo);8 try std.Io.File.stdout().writeStreamingAll(io, foo);
6}9}
7const foo = "good morning\n";10const foo = "good morning\n";
8const io = std.Io.Threaded.global_single_threaded.io();11const 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 declaration18#write main.zig {
12#file=main.zig
13const std = @import("std");19const std = @import("std");
14pub fn main() !void {20pub fn main() !void {
15 try std.Io.File.stdout().writeStreamingAll(io, foo);21 try std.Io.File.stdout().writeStreamingAll(io, foo);
...@@ -17,10 +23,14 @@ pub fn main() !void {...@@ -17,10 +23,14 @@ pub fn main() !void {
17const foo = "good morning\n";23const foo = "good morning\n";
18const bar = "good evening\n";24const bar = "good evening\n";
19const io = std.Io.Threaded.global_single_threaded.io();25const 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 declaration33#write main.zig {
23#file=main.zig
24const std = @import("std");34const std = @import("std");
25pub fn main() !void {35pub fn main() !void {
26 try std.Io.File.stdout().writeStreamingAll(io, bar);36 try std.Io.File.stdout().writeStreamingAll(io, bar);
...@@ -28,10 +38,14 @@ pub fn main() !void {...@@ -28,10 +38,14 @@ pub fn main() !void {
28const foo = "good morning\n";38const foo = "good morning\n";
29const bar = "good evening\n";39const bar = "good evening\n";
30const io = std.Io.Threaded.global_single_threaded.io();40const 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 declaration48#write main.zig {
34#file=main.zig
35const std = @import("std");49const std = @import("std");
36pub fn main() !void {50pub fn main() !void {
37 try std.Io.File.stdout().writeStreamingAll(io, qux);51 try std.Io.File.stdout().writeStreamingAll(io, qux);
...@@ -39,10 +53,14 @@ pub fn main() !void {...@@ -39,10 +53,14 @@ pub fn main() !void {
39const foo = "good morning\n";53const foo = "good morning\n";
40const bar = "good evening\n";54const bar = "good evening\n";
41const io = std.Io.Threaded.global_single_threaded.io();55const 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 declaration63#write main.zig {
45#file=main.zig
46const std = @import("std");64const std = @import("std");
47pub fn main() !void {65pub fn main() !void {
48 try std.Io.File.stdout().writeStreamingAll(io, qux);66 try std.Io.File.stdout().writeStreamingAll(io, qux);
...@@ -51,14 +69,23 @@ const foo = "good morning\n";...@@ -51,14 +69,23 @@ const foo = "good morning\n";
51const bar = "good evening\n";69const bar = "good evening\n";
52const qux = "good night\n";70const qux = "good night\n";
53const io = std.Io.Threaded.global_single_threaded.io();71const 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 declarations79#write main.zig {
57#file=main.zig
58const std = @import("std");80const std = @import("std");
59pub fn main() !void {81pub fn main() !void {
60 try std.Io.File.stdout().writeStreamingAll(io, qux);82 try std.Io.File.stdout().writeStreamingAll(io, qux);
61}83}
62const qux = "good night\n";84const qux = "good night\n";
63const io = std.Io.Threaded.global_single_threaded.io();85const 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,15 +1,21 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const std = @import("std");6const std = @import("std");
4pub fn main() !void {7pub fn main() !void {
5 try std.Io.File.stdout().writeStreamingAll(io, @This().foo);8 try std.Io.File.stdout().writeStreamingAll(io, @This().foo);
6}9}
7const foo = "good morning\n";10const foo = "good morning\n";
8const io = std.Io.Threaded.global_single_threaded.io();11const 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 declaration18#write main.zig {
12#file=main.zig
13const std = @import("std");19const std = @import("std");
14pub fn main() !void {20pub fn main() !void {
15 try std.Io.File.stdout().writeStreamingAll(io, @This().foo);21 try std.Io.File.stdout().writeStreamingAll(io, @This().foo);
...@@ -17,10 +23,14 @@ pub fn main() !void {...@@ -17,10 +23,14 @@ pub fn main() !void {
17const foo = "good morning\n";23const foo = "good morning\n";
18const bar = "good evening\n";24const bar = "good evening\n";
19const io = std.Io.Threaded.global_single_threaded.io();25const 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 declaration33#write main.zig {
23#file=main.zig
24const std = @import("std");34const std = @import("std");
25pub fn main() !void {35pub fn main() !void {
26 try std.Io.File.stdout().writeStreamingAll(io, @This().bar);36 try std.Io.File.stdout().writeStreamingAll(io, @This().bar);
...@@ -28,10 +38,14 @@ pub fn main() !void {...@@ -28,10 +38,14 @@ pub fn main() !void {
28const foo = "good morning\n";38const foo = "good morning\n";
29const bar = "good evening\n";39const bar = "good evening\n";
30const io = std.Io.Threaded.global_single_threaded.io();40const 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 declaration48#write main.zig {
34#file=main.zig
35const std = @import("std");49const std = @import("std");
36pub fn main() !void {50pub fn main() !void {
37 try std.Io.File.stdout().writeStreamingAll(io, @This().qux);51 try std.Io.File.stdout().writeStreamingAll(io, @This().qux);
...@@ -39,11 +53,15 @@ pub fn main() !void {...@@ -39,11 +53,15 @@ pub fn main() !void {
39const foo = "good morning\n";53const foo = "good morning\n";
40const bar = "good evening\n";54const bar = "good evening\n";
41const io = std.Io.Threaded.global_single_threaded.io();55const 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'56#}
43#expect_error=main.zig:1:1: note: struct declared here57#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 declaration64#write main.zig {
46#file=main.zig
47const std = @import("std");65const std = @import("std");
48pub fn main() !void {66pub fn main() !void {
49 try std.Io.File.stdout().writeStreamingAll(io, @This().qux);67 try std.Io.File.stdout().writeStreamingAll(io, @This().qux);
...@@ -52,14 +70,23 @@ const foo = "good morning\n";...@@ -52,14 +70,23 @@ const foo = "good morning\n";
52const bar = "good evening\n";70const bar = "good evening\n";
53const qux = "good night\n";71const qux = "good night\n";
54const io = std.Io.Threaded.global_single_threaded.io();72const 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 declarations80#write main.zig {
58#file=main.zig
59const std = @import("std");81const std = @import("std");
60pub fn main() !void {82pub fn main() !void {
61 try std.Io.File.stdout().writeStreamingAll(io, @This().qux);83 try std.Io.File.stdout().writeStreamingAll(io, @This().qux);
62}84}
63const qux = "good night\n";85const qux = "good night\n";
64const io = std.Io.Threaded.global_single_threaded.io();86const 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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3pub fn main() void {6pub fn main() void {
4 _ = @as(S, undefined);7 _ = @as(S, undefined);
5}8}
...@@ -9,9 +12,12 @@ const SomeType = u8;...@@ -9,9 +12,12 @@ const SomeType = u8;
9const S = struct {12const S = struct {
10 foo: struct { inner: SomeType },13 foo: struct { inner: SomeType },
11};14};
12#expect_stdout=""15#}
13#update=add field to outer struct, change decl used by inner struct16#update
14#file=main.zig17#check stdout {
18#}
19
20#write main.zig {
15pub fn main() void {21pub fn main() void {
16 _ = @as(S, undefined);22 _ = @as(S, undefined);
17}23}
...@@ -22,4 +28,8 @@ const S = struct {...@@ -22,4 +28,8 @@ const S = struct {
22 foo: struct { inner: SomeType },28 foo: struct { inner: SomeType },
23 bar: u32,29 bar: u32,
24};30};
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,38 +1,64 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3pub fn main() !void {6pub fn main() !void {
4 @compileError("uh oh");7 @compileError("uh oh");
5}8}
6#expect_error=main.zig:2:5: error: uh oh9#}
10#update
11#check errors {
12main.zig:2:5: error: uh oh
13#}
714
8#update=add parse error15#write main.zig {
9#file=main.zig
10pub fn main() !void {16pub fn main() !void {
11 @compileError("uh oh");17 @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 error25#write main.zig {
15#file=main.zig
16pub fn main() !void {26pub fn main() !void {
17 @compileError("uh oh");27 @compileError("uh oh");
18}28}
19#expect_error=main.zig:2:5: error: uh oh29#}
30#update {
31fix parse error#}
32#check errors {
33main.zig:2:5: error: uh oh
34#}
2035
21#update=add parse error again36#write main.zig {
22#file=main.zig
23pub fn main() !void {37pub fn main() !void {
24 @compileError("uh oh");38 @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 call46#write main.zig {
28#file=main.zig
29pub fn main() !void {47pub fn main() !void {
30 //@compileError("uh oh");48 //@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 again56#write main.zig {
34#file=main.zig
35pub fn main() !void {57pub fn main() !void {
36 //@compileError("uh oh");58 //@compileError("uh oh");
37}59}
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,32 +1,48 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3pub fn main() !void {6pub fn main() !void {
4 _ = @import("foo.zig");7 _ = @import("foo.zig");
5 try std.Io.File.stdout().writeStreamingAll(io, "success\n");8 try std.Io.File.stdout().writeStreamingAll(io, "success\n");
6}9}
7const std = @import("std");10const std = @import("std");
8const io = std.Io.Threaded.global_single_threaded.io();11const io = std.Io.Threaded.global_single_threaded.io();
9#file=foo.zig12#}
13#write foo.zig {
10comptime {14comptime {
11 _ = @import("bad.zig");15 _ = @import("bad.zig");
12}16}
13#expect_error=bad.zig:1:1: error: unable to load "bad.zig": FileNotFound17#}
14#expect_error=foo.zig:2:17: note: file imported here18#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 import24#write foo.zig {
17#file=foo.zig
18comptime {25comptime {
19 _ = @import("this_is/not_real.zig");26 _ = @import("this_is/not_real.zig");
20}27}
21#expect_error=this_is/not_real.zig:1:1: error: unable to load "not_real.zig": FileNotFound28#}
22#expect_error=foo.zig:2:17: note: file imported here29#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'36#write main.zig {
25#file=main.zig
26pub fn main() !void {37pub fn main() !void {
27 //_ = @import("foo.zig");38 //_ = @import("foo.zig");
28 try std.Io.File.stdout().writeStreamingAll(io, "success\n");39 try std.Io.File.stdout().writeStreamingAll(io, "success\n");
29}40}
30const std = @import("std");41const std = @import("std");
31const io = std.Io.Threaded.global_single_threaded.io();42const 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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const Foo = packed struct(u8) { a: u4, b: i4 };6const Foo = packed struct(u8) { a: u4, b: i4 };
4const Bar = packed union(u10) { a: u10, b: i10 };7const Bar = packed union(u10) { a: u10, b: i10 };
5pub fn main() void {}8pub fn main() void {}
...@@ -11,13 +14,18 @@ comptime {...@@ -11,13 +14,18 @@ comptime {
11}14}
12const std = @import("std");15const std = @import("std");
13const io = std.Io.Threaded.global_single_threaded.io();16const io = std.Io.Threaded.global_single_threaded.io();
14#expect_error=main.zig:5:5: error: found compile log statement17#}
15#expect_error=main.zig:8:5: note: also here18#update
16#expect_compile_log=@as(type, u8)19#check errors {
17#expect_compile_log=@as(comptime_int, 10)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 signed28#write main.zig {
20#file=main.zig
21const Foo = packed struct(i8) { a: u4, b: i4 };29const Foo = packed struct(i8) { a: u4, b: i4 };
22const Bar = packed union(i10) { a: u10, b: i10 };30const Bar = packed union(i10) { a: u10, b: i10 };
23pub fn main() void {}31pub fn main() void {}
...@@ -29,13 +37,19 @@ comptime {...@@ -29,13 +37,19 @@ comptime {
29}37}
30const std = @import("std");38const std = @import("std");
31const io = std.Io.Threaded.global_single_threaded.io();39const io = std.Io.Threaded.global_single_threaded.io();
32#expect_error=main.zig:5:5: error: found compile log statement40#}
33#expect_error=main.zig:8:5: note: also here41#update {
34#expect_compile_log=@as(type, i8)42make backing types signed#}
35#expect_compile_log=@as(comptime_int, 10)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 small52#write main.zig {
38#file=main.zig
39const Foo = packed struct(i5) { a: u4, b: i4 };53const Foo = packed struct(i5) { a: u4, b: i4 };
40const Bar = packed union(i8) { a: u10, b: i10 };54const Bar = packed union(i8) { a: u10, b: i10 };
41pub fn main() void {}55pub fn main() void {}
...@@ -47,16 +61,20 @@ comptime {...@@ -47,16 +61,20 @@ comptime {
47}61}
48const std = @import("std");62const std = @import("std");
49const io = std.Io.Threaded.global_single_threaded.io();63const 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 fields64#}
51#expect_error=main.zig:1:27: note: backing integer 'i5' has bit width '5'65#update {
52#expect_error=main.zig:1:20: note: struct fields have total bit width '8'66make backing types too small#}
53#expect_error=main.zig:2:35: error: field bit width does not match backing integer67#check errors {
54#expect_error=main.zig:2:35: note: field type 'u10' has bit width '10'68main.zig:1:20: error: backing integer bit width does not match total bit width of fields
55#expect_error=main.zig:2:26: note: backing integer 'i8' has bit width '8'69main.zig:1:27: note: backing integer 'i5' has bit width '5'
56#expect_error=main.zig:2:35: note: all fields in a packed union must have the same bit width70main.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 big77#write main.zig {
59#file=main.zig
60const Foo = packed struct(u10) { a: u4, b: i4 };78const Foo = packed struct(u10) { a: u4, b: i4 };
61const Bar = packed union(u32) { a: u10, b: i10 };79const Bar = packed union(u32) { a: u10, b: i10 };
62pub fn main() void {}80pub fn main() void {}
...@@ -68,10 +86,15 @@ comptime {...@@ -68,10 +86,15 @@ comptime {
68}86}
69const std = @import("std");87const std = @import("std");
70const io = std.Io.Threaded.global_single_threaded.io();88const 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 fields89#}
72#expect_error=main.zig:1:27: note: backing integer 'u10' has bit width '10'90#update {
73#expect_error=main.zig:1:20: note: struct fields have total bit width '8'91make backing types too big#}
74#expect_error=main.zig:2:36: error: field bit width does not match backing integer92#check errors {
75#expect_error=main.zig:2:36: note: field type 'u10' has bit width '10'93main.zig:1:20: error: backing integer bit width does not match total bit width of fields
76#expect_error=main.zig:2:26: note: backing integer 'u32' has bit width '32'94main.zig:1:27: note: backing integer 'u10' has bit width '10'
77#expect_error=main.zig:2:36: note: all fields in a packed union must have the same bit width95main.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,61 +1,114 @@
1#update=initial version, T is struct1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const T = @import("foo.zig").T;6const T = @import("foo.zig").T;
4pub fn main() void {7pub fn main() void {
5 @compileLog(T);8 @compileLog(T);
6 @compileLog(@tagName(@typeInfo(T)));9 @compileLog(@tagName(@typeInfo(T)));
7}10}
8#file=foo.zig11#}
12#write foo.zig {
9pub const T = struct { x: u8 };13pub const T = struct { x: u8 };
10#expect_error=main.zig:3:5: error: found compile log statement14#}
11#expect_compile_log=@as(type, foo.T)15#update {
12#expect_compile_log=@as(*const [6:0]u8, "struct")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 union25#write foo.zig {
15#file=foo.zig
16pub const T = union {};26pub const T = union {};
17#expect_error=main.zig:3:5: error: found compile log statement27#}
18#expect_compile_log=@as(type, foo.T)28#update {
19#expect_compile_log=@as(*const [5:0]u8, "union")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 enum38#write foo.zig {
22#file=foo.zig
23pub const T = enum {};39pub const T = enum {};
24#expect_error=main.zig:3:5: error: found compile log statement40#}
25#expect_compile_log=@as(type, foo.T)41#update {
26#expect_compile_log=@as(*const [4:0]u8, "enum")42T is enum#}
43#check errors {
44main.zig:3:5: error: found compile log statement
2745
28#update=T is opaque46Compile Log Output:
29#file=foo.zig47@as(type, foo.T)
48@as(*const [4:0]u8, "enum")
49#}
50
51#write foo.zig {
30pub const T = opaque {};52pub const T = opaque {};
31#expect_error=main.zig:3:5: error: found compile log statement53#}
32#expect_compile_log=@as(type, foo.T)54#update {
33#expect_compile_log=@as(*const [6:0]u8, "opaque")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 struct64#write foo.zig {
36#file=foo.zig
37pub const T = packed struct {};65pub const T = packed struct {};
38#expect_error=main.zig:3:5: error: found compile log statement66#}
39#expect_compile_log=@as(type, foo.T)67#update {
40#expect_compile_log=@as(*const [6:0]u8, "struct")68T is packed struct#}
69#check errors {
70main.zig:3:5: error: found compile log statement
4171
42#update=T is packed union72Compile Log Output:
43#file=foo.zig73@as(type, foo.T)
74@as(*const [6:0]u8, "struct")
75#}
76
77#write foo.zig {
44pub const T = packed union { x: void }; // packed unions need at least 1 field78pub const T = packed union { x: void }; // packed unions need at least 1 field
45#expect_error=main.zig:3:5: error: found compile log statement79#}
46#expect_compile_log=@as(type, foo.T)80#update {
47#expect_compile_log=@as(*const [5:0]u8, "union")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 struct90#write foo.zig {
50#file=foo.zig
51pub const T = extern struct {};91pub const T = extern struct {};
52#expect_error=main.zig:3:5: error: found compile log statement92#}
53#expect_compile_log=@as(type, foo.T)93#update {
54#expect_compile_log=@as(*const [6:0]u8, "struct")94T is extern struct#}
95#check errors {
96main.zig:3:5: error: found compile log statement
5597
56#update=T is extern union98Compile Log Output:
57#file=foo.zig99@as(type, foo.T)
100@as(*const [6:0]u8, "struct")
101#}
102
103#write foo.zig {
58pub const T = extern union { x: void }; // extern unions need at least 1 field104pub const T = extern union { x: void }; // extern unions need at least 1 field
59#expect_error=main.zig:3:5: error: found compile log statement105#}
60#expect_compile_log=@as(type, foo.T)106#update {
61#expect_compile_log=@as(*const [5:0]u8, "union")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,45 +1,72 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const std = @import("std");6const std = @import("std");
4const string = @embedFile("string.txt");7const string = @embedFile("string.txt");
5pub fn main() !void {8pub fn main() !void {
6 try std.Io.File.stdout().writeStreamingAll(io, string);9 try std.Io.File.stdout().writeStreamingAll(io, string);
7}10}
8const io = std.Io.Threaded.global_single_threaded.io();11const io = std.Io.Threaded.global_single_threaded.io();
9#file=string.txt12#}
13#write string.txt {
14Hello, World!
15#}
16#update
17#check stdout {
10Hello, World!18Hello, World!
11#expect_stdout="Hello, World!\n"19#}
1220
13#update=change file contents21#write string.txt {
14#file=string.txt
15Hello again, World!22Hello 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 file30#delete string.txt
19#rm_file=string.txt31#update {
20#expect_error=main.zig:2:27: error: failed opening "string.txt": FileNotFound32delete file#}
33#check errors {
34main.zig:2:27: error: failed opening "string.txt": FileNotFound
35#}
2136
22#update=remove reference to file37#write main.zig {
23#file=main.zig
24const std = @import("std");38const std = @import("std");
25const string = @embedFile("string.txt");39const string = @embedFile("string.txt");
26pub fn main() !void {40pub fn main() !void {
27 try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n");41 try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n");
28}42}
29const io = std.Io.Threaded.global_single_threaded.io();43const 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 file51#write main.zig {
33#file=main.zig
34const std = @import("std");52const std = @import("std");
35const string = @embedFile("string.txt");53const string = @embedFile("string.txt");
36pub fn main() !void {54pub fn main() !void {
37 try std.Io.File.stdout().writeStreamingAll(io, string);55 try std.Io.File.stdout().writeStreamingAll(io, string);
38}56}
39const io = std.Io.Threaded.global_single_threaded.io();57const io = std.Io.Threaded.global_single_threaded.io();
40#expect_error=main.zig:2:27: error: failed opening "string.txt": FileNotFound58#}
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 file65#write string.txt {
43#file=string.txt66We're back, World!
67#}
68#update {
69recreate file#}
70#check stdout {
44We're back, World!71We'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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3pub fn main() !void {6pub fn main() !void {
4 try foo(123);7 try foo(123);
5}8}
...@@ -9,10 +12,13 @@ fn foo(x: u8) !void {...@@ -9,10 +12,13 @@ fn foo(x: u8) !void {
9}12}
10const std = @import("std");13const std = @import("std");
11const io = std.Io.Threaded.global_single_threaded.io();14const 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 type21#write main.zig {
15#file=main.zig
16pub fn main() !void {22pub fn main() !void {
17 try foo(123);23 try foo(123);
18}24}
...@@ -22,10 +28,14 @@ fn foo(x: i64) !void {...@@ -22,10 +28,14 @@ fn foo(x: i64) !void {
22}28}
23const std = @import("std");29const std = @import("std");
24const io = std.Io.Threaded.global_single_threaded.io();30const 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 argument38#write main.zig {
28#file=main.zig
29pub fn main() !void {39pub fn main() !void {
30 try foo(-42);40 try foo(-42);
31}41}
...@@ -35,4 +45,9 @@ fn foo(x: i64) !void {...@@ -35,4 +45,9 @@ fn foo(x: i64) !void {
35}45}
36const std = @import("std");46const std = @import("std");
37const io = std.Io.Threaded.global_single_threaded.io();47const 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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const std = @import("std");6const std = @import("std");
4const io = std.Io.Threaded.global_single_threaded.io();7const io = std.Io.Threaded.global_single_threaded.io();
5fn Printer(message: []const u8) type {8fn Printer(message: []const u8) type {
...@@ -13,9 +16,14 @@ pub fn main() !void {...@@ -13,9 +16,14 @@ pub fn main() !void {
13 try Printer("foo\n").print();16 try Printer("foo\n").print();
14 try Printer("bar\n").print();17 try Printer("bar\n").print();
15}18}
16#expect_stdout="foo\nbar\n"19#}
17#update=change line number20#update
18#file=main.zig21#check stdout {
22foo
23bar
24#}
25
26#write main.zig {
19const std = @import("std");27const std = @import("std");
20const io = std.Io.Threaded.global_single_threaded.io();28const io = std.Io.Threaded.global_single_threaded.io();
2129
...@@ -30,4 +38,10 @@ pub fn main() !void {...@@ -30,4 +38,10 @@ pub fn main() !void {
30 try Printer("foo\n").print();38 try Printer("foo\n").print();
31 try Printer("bar\n").print();39 try Printer("bar\n").print();
32}40}
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,17 +1,29 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const std = @import("std");6const std = @import("std");
4pub fn main() !void {7pub fn main() !void {
5 try std.Io.File.stdout().writeStreamingAll(io, "foo\n");8 try std.Io.File.stdout().writeStreamingAll(io, "foo\n");
6}9}
7const io = std.Io.Threaded.global_single_threaded.io();10const io = std.Io.Threaded.global_single_threaded.io();
8#expect_stdout="foo\n"11#}
9#update=change line number12#update
10#file=main.zig13#check stdout {
14foo
15#}
16
17#write main.zig {
11const std = @import("std");18const std = @import("std");
1219
13pub fn main() !void {20pub fn main() !void {
14 try std.Io.File.stdout().writeStreamingAll(io, "foo\n");21 try std.Io.File.stdout().writeStreamingAll(io, "foo\n");
15}22}
16const io = std.Io.Threaded.global_single_threaded.io();23const 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,61 +1,87 @@
1#module=foo=foo.zig1#exe {
2--dep foo -Mroot=main.zig
3-Mfoo=foo.zig
4#}
25
3#update=initial version6#write main.zig {
4#file=main.zig
5pub fn main() void {7pub fn main() void {
6 _ = @import("foo");8 _ = @import("foo");
7 //_ = @import("other.zig");9 //_ = @import("other.zig");
8}10}
9#file=foo.zig11#}
12#write foo.zig {
10comptime {13comptime {
11 _ = @import("other.zig");14 _ = @import("other.zig");
12}15}
13#file=other.zig16#}
17#write other.zig {
14fn f() void {18fn f() void {
15 @compileLog(@src().module);19 @compileLog(@src().module);
16}20}
17comptime {21comptime {
18 f();22 f();
19}23}
20#expect_error=other.zig:2:5: error: found compile log statement24#}
21#expect_compile_log=@as([:0]const u8, "foo"[0..3])25#update
26#check errors {
27other.zig:2:5: error: found compile log statement
2228
23#update=change module of other.zig29Compile Log Output:
24#file=main.zig30@as([:0]const u8, "foo"[0..3])
31#}
32
33#write main.zig {
25pub fn main() void {34pub fn main() void {
26 _ = @import("foo");35 _ = @import("foo");
27 _ = @import("other.zig");36 _ = @import("other.zig");
28}37}
29#file=foo.zig38#}
39#write foo.zig {
30comptime {40comptime {
31 //_ = @import("other.zig");41 //_ = @import("other.zig");
32}42}
33#expect_error=other.zig:2:5: error: found compile log statement43#}
34#expect_compile_log=@as([:0]const u8, "root"[0..4])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 modules53#write main.zig {
37#file=main.zig
38pub fn main() void {54pub fn main() void {
39 _ = @import("foo");55 _ = @import("foo");
40 _ = @import("other.zig");56 _ = @import("other.zig");
41}57}
42#file=foo.zig58#}
59#write foo.zig {
43comptime {60comptime {
44 _ = @import("other.zig");61 _ = @import("other.zig");
45}62}
46#expect_error=other.zig:1:1: error: file exists in modules 'root' and 'foo'63#}
47#expect_error=other.zig:1:1: note: files must belong to only one module64#update {
48#expect_error=main.zig:3:17: note: file is imported here by the root of module 'root'65put other.zig in both modules#}
49#expect_error=foo.zig:2:17: note: file is imported here by the root of module 'foo'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 modules73#write main.zig {
52#file=main.zig
53pub fn main() void {74pub fn main() void {
54 _ = @import("foo");75 _ = @import("foo");
55 //_ = @import("other.zig");76 //_ = @import("other.zig");
56}77}
57#file=foo.zig78#}
79#write foo.zig {
58comptime {80comptime {
59 //_ = @import("other.zig");81 //_ = @import("other.zig");
60}82}
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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3pub fn main() !u8 {6pub fn main() !u8 {
4 var a: u8 = undefined;7 var a: u8 = undefined;
5 a = 255;8 a = 255;
...@@ -14,10 +17,13 @@ fn myPanic(msg: []const u8, _: ?usize) noreturn {...@@ -14,10 +17,13 @@ fn myPanic(msg: []const u8, _: ?usize) noreturn {
14}17}
15const std = @import("std");18const std = @import("std");
16const io = std.Io.Threaded.global_single_threaded.io();19const 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 body26#write main.zig {
20#file=main.zig
21pub fn main() !u8 {27pub fn main() !u8 {
22 var a: u8 = undefined;28 var a: u8 = undefined;
23 a = 255;29 a = 255;
...@@ -32,10 +38,14 @@ fn myPanic(msg: []const u8, _: ?usize) noreturn {...@@ -32,10 +38,14 @@ fn myPanic(msg: []const u8, _: ?usize) noreturn {
32}38}
33const std = @import("std");39const std = @import("std");
34const io = std.Io.Threaded.global_single_threaded.io();40const 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 value48#write main.zig {
38#file=main.zig
39pub fn main() !u8 {49pub fn main() !u8 {
40 var a: u8 = undefined;50 var a: u8 = undefined;
41 a = 255;51 a = 255;
...@@ -50,4 +60,9 @@ fn myPanicNew(msg: []const u8, _: ?usize) noreturn {...@@ -50,4 +60,9 @@ fn myPanicNew(msg: []const u8, _: ?usize) noreturn {
50}60}
51const std = @import("std");61const std = @import("std");
52const io = std.Io.Threaded.global_single_threaded.io();62const 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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3fn getEnum(s: @Struct(.auto, null, &.{"field"}, &.{struct { tag: Enum }}, &.{.{}})) Enum {6fn getEnum(s: @Struct(.auto, null, &.{"field"}, &.{struct { tag: Enum }}, &.{.{}})) Enum {
4 return s.field.tag;7 return s.field.tag;
5}8}
...@@ -8,9 +11,12 @@ pub fn main(init: std.process.Init) !void {...@@ -8,9 +11,12 @@ pub fn main(init: std.process.Init) !void {
8}11}
9const Enum = enum { foo, bar };12const Enum = enum { foo, bar };
10const std = @import("std");13const std = @import("std");
11#expect_stdout="foo"14#}
12#update=change field type15#update
13#file=main.zig16#check stdout {
17foo#}
18
19#write main.zig {
14fn getEnum(s: @Struct(.auto, null, &.{"field"}, &.{Enum}, &.{.{}})) Enum {20fn getEnum(s: @Struct(.auto, null, &.{"field"}, &.{Enum}, &.{.{}})) Enum {
15 return s.field;21 return s.field;
16}22}
...@@ -19,4 +25,8 @@ pub fn main(init: std.process.Init) !void {...@@ -19,4 +25,8 @@ pub fn main(init: std.process.Init) !void {
19}25}
20const Enum = enum { foo, bar };26const Enum = enum { foo, bar };
21const std = @import("std");27const 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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3pub fn main() !void {6pub fn main() !void {
4 try foo(0x1300);7 try foo(0x1300);
5}8}
...@@ -9,9 +12,13 @@ fn foo(x: u16) !void {...@@ -9,9 +12,13 @@ fn foo(x: u16) !void {
9}12}
10const std = @import("std");13const std = @import("std");
11const io = std.Io.Threaded.global_single_threaded.io();14const io = std.Io.Threaded.global_single_threaded.io();
12#expect_stdout="0x3000\n"15#}
13#update=change to right shift16#update
14#file=main.zig17#check stdout {
180x3000
19#}
20
21#write main.zig {
15pub fn main() !void {22pub fn main() !void {
16 try foo(0x1300);23 try foo(0x1300);
17}24}
...@@ -21,4 +28,9 @@ fn foo(x: u16) !void {...@@ -21,4 +28,9 @@ fn foo(x: u16) !void {
21}28}
22const std = @import("std");29const std = @import("std");
23const io = std.Io.Threaded.global_single_threaded.io();30const 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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const S = extern struct { x: u8, y: u8 };6const S = extern struct { x: u8, y: u8 };
4pub fn main() !void {7pub fn main() !void {
5 const val: S = .{ .x = 100, .y = 200 };8 const val: S = .{ .x = 100, .y = 200 };
...@@ -14,10 +17,13 @@ fn foo(val: *const S) !void {...@@ -14,10 +17,13 @@ fn foo(val: *const S) !void {
14}17}
15const std = @import("std");18const std = @import("std");
16const io = std.Io.Threaded.global_single_threaded.io();19const 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 layout26#write main.zig {
20#file=main.zig
21const S = extern struct { x: u32, y: u32 };27const S = extern struct { x: u32, y: u32 };
22pub fn main() !void {28pub fn main() !void {
23 const val: S = .{ .x = 100, .y = 200 };29 const val: S = .{ .x = 100, .y = 200 };
...@@ -32,10 +38,14 @@ fn foo(val: *const S) !void {...@@ -32,10 +38,14 @@ fn foo(val: *const S) !void {
32}38}
33const std = @import("std");39const std = @import("std");
34const io = std.Io.Threaded.global_single_threaded.io();40const 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 values48#write main.zig {
38#file=main.zig
39const S = extern struct { x: u32, y: u32 };49const S = extern struct { x: u32, y: u32 };
40pub fn main() !void {50pub fn main() !void {
41 const val: S = .{ .x = 1234, .y = 5678 };51 const val: S = .{ .x = 1234, .y = 5678 };
...@@ -50,4 +60,9 @@ fn foo(val: *const S) !void {...@@ -50,4 +60,9 @@ fn foo(val: *const S) !void {
50}60}
51const std = @import("std");61const std = @import("std");
52const io = std.Io.Threaded.global_single_threaded.io();62const 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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const A = enum(u8) { a };6const A = enum(u8) { a };
4const B = enum(u8) { b };7const B = enum(u8) { b };
5const Foo = union(A) { a: u8 };8const Foo = union(A) { a: u8 };
...@@ -9,10 +12,13 @@ pub fn main(init: std.process.Init) !void {...@@ -9,10 +12,13 @@ pub fn main(init: std.process.Init) !void {
9 try stdout_writer.interface.print("{s}\n", .{field_name});12 try stdout_writer.interface.print("{s}\n", .{field_name});
10}13}
11const std = @import("std");14const std = @import("std");
12#expect_stdout="a\n"15#}
16#update
17#check stdout {
18a
19#}
1320
14#update=change tag type21#write main.zig {
15#file=main.zig
16const A = enum(u8) { a };22const A = enum(u8) { a };
17const B = enum(u8) { b };23const B = enum(u8) { b };
18const Foo = union(B) { a: u8 };24const Foo = union(B) { a: u8 };
...@@ -22,11 +28,15 @@ pub fn main(init: std.process.Init) !void {...@@ -22,11 +28,15 @@ pub fn main(init: std.process.Init) !void {
22 try stdout_writer.interface.print("{s}\n", .{field_name});28 try stdout_writer.interface.print("{s}\n", .{field_name});
23}29}
24const std = @import("std");30const std = @import("std");
25#expect_error=main.zig:3:24: error: no field named 'a' in enum 'main.B'31#}
26#expect_error=main.zig:2:11: note: enum declared here32#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 type39#write main.zig {
29#file=main.zig
30const A = enum(u8) { a };40const A = enum(u8) { a };
31const B = enum(u8) { b };41const B = enum(u8) { b };
32const Foo = union(B) { b: u8 };42const Foo = union(B) { b: u8 };
...@@ -36,4 +46,9 @@ pub fn main(init: std.process.Init) !void {...@@ -36,4 +46,9 @@ pub fn main(init: std.process.Init) !void {
36 try stdout_writer.interface.print("{s}\n", .{field_name});46 try stdout_writer.interface.print("{s}\n", .{field_name});
37}47}
38const std = @import("std");48const 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,47 +1,74 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const std = @import("std");6const std = @import("std");
4const message: []const u8 = @import("message.zon");7const message: []const u8 = @import("message.zon");
5pub fn main() !void {8pub fn main() !void {
6 try std.Io.File.stdout().writeStreamingAll(io, message);9 try std.Io.File.stdout().writeStreamingAll(io, message);
7}10}
8const io = std.Io.Threaded.global_single_threaded.io();11const io = std.Io.Threaded.global_single_threaded.io();
9#file=message.zon12#}
13#write message.zon {
10"Hello, World!\n"14"Hello, World!\n"
11#expect_stdout="Hello, World!\n"15#}
16#update
17#check stdout {
18Hello, World!
19#}
1220
13#update=change ZON file contents21#write message.zon {
14#file=message.zon
15"Hello again, World!\n"22"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 file30#delete message.zon
19#rm_file=message.zon31#update {
20#expect_error=message.zon:1:1: error: unable to load "message.zon": FileNotFound32delete file#}
21#expect_error=main.zig:2:37: note: file imported here33#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 file38#write main.zig {
24#file=main.zig
25const std = @import("std");39const std = @import("std");
26const message: []const u8 = @import("message.zon");40const message: []const u8 = @import("message.zon");
27pub fn main() !void {41pub fn main() !void {
28 try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n");42 try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n");
29}43}
30const io = std.Io.Threaded.global_single_threaded.io();44const io = std.Io.Threaded.global_single_threaded.io();
31#expect_error=message.zon:1:1: error: unable to load "message.zon": FileNotFound45#}
32#expect_error=main.zig:2:37: note: file imported here46#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 file53#write message.zon {
35#file=message.zon
36"We're back, World!\n"54"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 file62#write main.zig {
40#file=main.zig
41const std = @import("std");63const std = @import("std");
42const message: []const u8 = @import("message.zon");64const message: []const u8 = @import("message.zon");
43pub fn main() !void {65pub fn main() !void {
44 try std.Io.File.stdout().writeStreamingAll(io, message);66 try std.Io.File.stdout().writeStreamingAll(io, message);
45}67}
46const io = std.Io.Threaded.global_single_threaded.io();68const 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,21 +1,33 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const std = @import("std");6const std = @import("std");
4const io = std.Io.Threaded.global_single_threaded.io();7const io = std.Io.Threaded.global_single_threaded.io();
5pub fn main() !void {8pub fn main() !void {
6 try std.Io.File.stdout().writeStreamingAll(io, @import("foo.zon").message);9 try std.Io.File.stdout().writeStreamingAll(io, @import("foo.zon").message);
7}10}
8#file=foo.zon11#}
12#write foo.zon {
9.{13.{
10 .message = "Hello, World!\n",14 .message = "Hello, World!\n",
11 .a_number = 0,15 .a_number = 0,
12}16}
13#expect_stdout="Hello, World!\n"17#}
18#update
19#check stdout {
20Hello, World!
21#}
1422
15#update=change ZON file23#write foo.zon {
16#file=foo.zon
17.{24.{
18 .message = "Hello again, World!\n",25 .message = "Hello again, World!\n",
19 .b_number = 0,26 .b_number = 0,
20}27}
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,5 +1,8 @@
1#update=initial version with compile error1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3pub fn main() void {}6pub fn main() void {}
4comptime {7comptime {
5 @compileError("this is an error");8 @compileError("this is an error");
...@@ -7,11 +10,17 @@ comptime {...@@ -7,11 +10,17 @@ comptime {
7comptime {10comptime {
8 @compileLog("this is a log");11 @compileLog("this is a log");
9}12}
10#expect_error=main.zig:3:5: error: this is an error13#}
11#expect_compile_log=@as(*const [13:0]u8, "this is a log")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 error23#write main.zig {
14#file=main.zig
15pub fn main() void {}24pub fn main() void {}
16comptime {25comptime {
17 //@compileError("this is an error");26 //@compileError("this is an error");
...@@ -19,5 +28,12 @@ comptime {...@@ -19,5 +28,12 @@ comptime {
19comptime {28comptime {
20 @compileLog("this is a log");29 @compileLog("this is a log");
21}30}
22#expect_error=main.zig:6:5: error: found compile log statement31#}
23#expect_compile_log=@as(*const [13:0]u8, "this is a log")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,28 +1,46 @@
1#update=initial version with no compile log1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const std = @import("std");6const std = @import("std");
4pub fn main() !void {7pub fn main() !void {
5 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");8 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
6}9}
7const io = std.Io.Threaded.global_single_threaded.io();10const 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 log18#write main.zig {
11#file=main.zig
12const std = @import("std");19const std = @import("std");
13pub fn main() !void {20pub fn main() !void {
14 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");21 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
15 @compileLog("this is a log");22 @compileLog("this is a log");
16}23}
17const io = std.Io.Threaded.global_single_threaded.io();24const io = std.Io.Threaded.global_single_threaded.io();
18#expect_error=main.zig:4:5: error: found compile log statement25#}
19#expect_compile_log=@as(*const [13:0]u8, "this is a log")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 log35#write main.zig {
22#file=main.zig
23const std = @import("std");36const std = @import("std");
24pub fn main() !void {37pub fn main() !void {
25 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");38 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
26}39}
27const io = std.Io.Threaded.global_single_threaded.io();40const 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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3pub fn main() void {}6pub fn main() void {}
4comptime {7comptime {
5 var array = [_:0]u8{ 1, 2, 3, 4 };8 var array = [_:0]u8{ 1, 2, 3, 4 };
...@@ -22,13 +25,16 @@ comptime {...@@ -22,13 +25,16 @@ comptime {
22 const slice = array[3..2];25 const slice = array[3..2];
23 _ = slice;26 _ = slice;
24}27}
25#expect_error=main.zig:5:32: error: end index 6 out of bounds for slice of length 4 +1 (sentinel)28#}
26#expect_error=main.zig:10:28: error: end index 6 out of bounds for array of length 4 +1 (sentinel)29#update
27#expect_error=main.zig:15:28: error: end index 5 out of bounds for array of length 430#check errors {
28#expect_error=main.zig:20:25: error: start index 3 is larger than end index 231main.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 decls37#write main.zig {
31#file=main.zig
32pub fn main() void {}38pub fn main() void {}
33comptime {39comptime {
34 const x: [*c]u8 = null;40 const x: [*c]u8 = null;
...@@ -37,4 +43,9 @@ comptime {...@@ -37,4 +43,9 @@ comptime {
37 const y = x[0..runtime_len];43 const y = x[0..runtime_len];
38 _ = y;44 _ = y;
39}45}
40#expect_error=main.zig:6:16: error: slice of null pointer46#}
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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const foo = @as(u8, 123);6const foo = @as(u8, 123);
4comptime {7comptime {
5 // depends on value of `foo`8 // depends on value of `foo`
...@@ -10,9 +13,12 @@ comptime {...@@ -10,9 +13,12 @@ comptime {
10 if (@TypeOf(&foo) != *const u8) unreachable;13 if (@TypeOf(&foo) != *const u8) unreachable;
11}14}
12pub fn main() void {}15pub fn main() void {}
13#expect_stdout=""16#}
14#update=change the type17#update
15#file=main.zig18#check stdout {
19#}
20
21#write main.zig {
16const foo = @as(u16, 123);22const foo = @as(u16, 123);
17comptime {23comptime {
18 // depends on value of `foo`24 // depends on value of `foo`
...@@ -23,4 +29,9 @@ comptime {...@@ -23,4 +29,9 @@ comptime {
23 if (@TypeOf(&foo) != *const u8) unreachable;29 if (@TypeOf(&foo) != *const u8) unreachable;
24}30}
25pub fn main() void {}31pub fn main() void {}
26#expect_error=main.zig:8:37: error: reached unreachable code32#}
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,6 +1,11 @@
1// TODO: it'd be great if we could actually check that no analysis happened!1#todo {
2#update=initial version2it'd be great if we could actually check that no analysis happened!
3#file=main.zig3#}
4#exe {
5-Mroot=main.zig
6#}
7
8#write main.zig {
4pub fn main() void {9pub fn main() void {
5 const ptr: *const O = @ptrFromInt(0x1000);10 const ptr: *const O = @ptrFromInt(0x1000);
6 _ = ptr;11 _ = ptr;
...@@ -18,9 +23,12 @@ const O = opaque {...@@ -18,9 +23,12 @@ const O = opaque {
18 _ = wrapper;23 _ = wrapper;
19 }24 }
20};25};
21#expect_stdout=""26#}
22#update=do literally nothing27#update
23#file=main.zig28#check stdout {
29#}
30
31#write main.zig {
24pub fn main() void {32pub fn main() void {
25 const ptr: *const O = @ptrFromInt(0x1000);33 const ptr: *const O = @ptrFromInt(0x1000);
26 _ = ptr;34 _ = ptr;
...@@ -38,4 +46,8 @@ const O = opaque {...@@ -38,4 +46,8 @@ const O = opaque {
38 _ = wrapper;46 _ = wrapper;
39 }47 }
40};48};
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,35 +1,59 @@
1#update=initial version with error1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3pub fn main() !void {6pub fn main() !void {
4 try @import("foo.zig").hello();7 try @import("foo.zig").hello();
5}8}
6#file=foo.zig9#}
10#write foo.zig {
7pub fn hello() !void {11pub fn hello() !void {
8 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");12 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
9}13}
10#expect_error=foo.zig:2:9: error: use of undeclared identifier 'std'14#}
11#update=fix the error15#update {
12#file=foo.zig16error#}
17#check errors {
18foo.zig:2:9: error: use of undeclared identifier 'std'
19#}
20
21#write foo.zig {
13const std = @import("std");22const std = @import("std");
14pub fn hello() !void {23pub fn hello() !void {
15 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");24 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
16}25}
17const io = std.Io.Threaded.global_single_threaded.io();26const io = std.Io.Threaded.global_single_threaded.io();
18#expect_stdout="Hello, World!\n"27#}
19#update=add new error28#update {
20#file=foo.zig29fix the error#}
30#check stdout {
31Hello, World!
32#}
33
34#write foo.zig {
21const std = @import("std");35const std = @import("std");
22pub fn hello() !void {36pub fn hello() !void {
23 try std.Io.File.stdout().writeStreamingAll(io, hello_str);37 try std.Io.File.stdout().writeStreamingAll(io, hello_str);
24}38}
25const io = std.Io.Threaded.global_single_threaded.io();39const io = std.Io.Threaded.global_single_threaded.io();
26#expect_error=foo.zig:3:52: error: use of undeclared identifier 'hello_str'40#}
27#update=fix the new error41#update {
28#file=foo.zig42add new error#}
43#check errors {
44foo.zig:3:52: error: use of undeclared identifier 'hello_str'
45#}
46
47#write foo.zig {
29const std = @import("std");48const std = @import("std");
30const hello_str = "Hello, World! Again!\n";49const hello_str = "Hello, World! Again!\n";
31pub fn hello() !void {50pub fn hello() !void {
32 try std.Io.File.stdout().writeStreamingAll(io, hello_str);51 try std.Io.File.stdout().writeStreamingAll(io, hello_str);
33}52}
34const io = std.Io.Threaded.global_single_threaded.io();53const 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,5 +1,8 @@
1#update=non-inline version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3pub fn main() !void {6pub fn main() !void {
4 try foo();7 try foo();
5}8}
...@@ -8,10 +11,14 @@ fn foo() !void {...@@ -8,10 +11,14 @@ fn foo() !void {
8}11}
9const std = @import("std");12const std = @import("std");
10const io = std.Io.Threaded.global_single_threaded.io();13const 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 inline21#write main.zig {
14#file=main.zig
15pub fn main() !void {22pub fn main() !void {
16 try foo();23 try foo();
17}24}
...@@ -20,10 +27,14 @@ inline fn foo() !void {...@@ -20,10 +27,14 @@ inline fn foo() !void {
20}27}
21const std = @import("std");28const std = @import("std");
22const io = std.Io.Threaded.global_single_threaded.io();29const 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 string37#write main.zig {
26#file=main.zig
27pub fn main() !void {38pub fn main() !void {
28 try foo();39 try foo();
29}40}
...@@ -32,4 +43,9 @@ inline fn foo() !void {...@@ -32,4 +43,9 @@ inline fn foo() !void {
32}43}
33const std = @import("std");44const std = @import("std");
34const io = std.Io.Threaded.global_single_threaded.io();45const 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,16 +1,28 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const std = @import("std");6const std = @import("std");
4const io = std.Io.Threaded.global_single_threaded.io();7const io = std.Io.Threaded.global_single_threaded.io();
5pub fn main() !void {8pub fn main() !void {
6 try std.Io.File.stdout().writeStreamingAll(io, "good morning\n");9 try std.Io.File.stdout().writeStreamingAll(io, "good morning\n");
7}10}
8#expect_stdout="good morning\n"11#}
9#update=change the string12#update
10#file=main.zig13#check stdout {
14good morning
15#}
16
17#write main.zig {
11const std = @import("std");18const std = @import("std");
12const io = std.Io.Threaded.global_single_threaded.io();19const io = std.Io.Threaded.global_single_threaded.io();
13pub fn main() !void {20pub fn main() !void {
14 try std.Io.File.stdout().writeStreamingAll(io, "おはようございます\n");21 try std.Io.File.stdout().writeStreamingAll(io, "おはようございます\n");
15}22}
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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const Big = struct { arr: [10]u8 };6const Big = struct { arr: [10]u8 };
4pub fn main(init: std.process.Init) !void {7pub fn main(init: std.process.Init) !void {
5 var big: Big = undefined;8 var big: Big = undefined;
...@@ -12,10 +15,13 @@ pub fn main(init: std.process.Init) !void {...@@ -12,10 +15,13 @@ pub fn main(init: std.process.Init) !void {
12 };15 };
13}16}
14const std = @import("std");17const 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 length24#write main.zig {
18#file=main.zig
19const Big = struct { arr: [1000]u8 };25const Big = struct { arr: [1000]u8 };
20pub fn main(init: std.process.Init) !void {26pub fn main(init: std.process.Init) !void {
21 var big: Big = undefined;27 var big: Big = undefined;
...@@ -28,4 +34,9 @@ pub fn main(init: std.process.Init) !void {...@@ -28,4 +34,9 @@ pub fn main(init: std.process.Init) !void {
28 };34 };
29}35}
30const std = @import("std");36const 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,23 +1,35 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const foo = @import("foo.zig");6const foo = @import("foo.zig");
4pub fn main() !void {7pub fn main() !void {
5 try foo.hello();8 try foo.hello();
6}9}
7#file=foo.zig10#}
11#write foo.zig {
8const std = @import("std");12const std = @import("std");
9fn hello() !void {13fn hello() !void {
10 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");14 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
11}15}
12const io = std.Io.Threaded.global_single_threaded.io();16const io = std.Io.Threaded.global_single_threaded.io();
13#expect_error=main.zig:3:12: error: 'hello' is not marked 'pub'17#}
14#expect_error=foo.zig:2:1: note: declared here18#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 pub24#write foo.zig {
17#file=foo.zig
18const std = @import("std");25const std = @import("std");
19pub fn hello() !void {26pub fn hello() !void {
20 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");27 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
21}28}
22const io = std.Io.Threaded.global_single_threaded.io();29const 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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const std = @import("std");6const std = @import("std");
4pub fn main() !void {7pub fn main() !void {
5 const str = getStr();8 const str = getStr();
...@@ -9,9 +12,13 @@ inline fn getStr() []const u8 {...@@ -9,9 +12,13 @@ inline fn getStr() []const u8 {
9 return "foo\n";12 return "foo\n";
10}13}
11const io = std.Io.Threaded.global_single_threaded.io();14const io = std.Io.Threaded.global_single_threaded.io();
12#expect_stdout="foo\n"15#}
13#update=change the string16#update
14#file=main.zig17#check stdout {
18foo
19#}
20
21#write main.zig {
15const std = @import("std");22const std = @import("std");
16pub fn main() !void {23pub fn main() !void {
17 const str = getStr();24 const str = getStr();
...@@ -21,4 +28,9 @@ inline fn getStr() []const u8 {...@@ -21,4 +28,9 @@ inline fn getStr() []const u8 {
21 return "bar\n";28 return "bar\n";
22}29}
23const io = std.Io.Threaded.global_single_threaded.io();30const 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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const std = @import("std");6const std = @import("std");
4pub fn main() !void {7pub fn main() !void {
5 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});8 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
...@@ -12,10 +15,13 @@ fn bar() u32 {...@@ -12,10 +15,13 @@ fn bar() u32 {
12 return 123;15 return 123;
13}16}
14const io = std.Io.Threaded.global_single_threaded.io();17const 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 newline24#write main.zig {
18#file=main.zig
19const std = @import("std");25const std = @import("std");
20pub fn main() !void {26pub fn main() !void {
21 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});27 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
...@@ -29,4 +35,9 @@ fn bar() u32 {...@@ -29,4 +35,9 @@ fn bar() u32 {
29 return 123;35 return 123;
30}36}
31const io = std.Io.Threaded.global_single_threaded.io();37const 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,23 +1,36 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const foo = {};6const foo = {};
4pub fn main() void {7pub fn main() void {
5 _ = &foo;8 _ = &foo;
6}9}
7#expect_stdout=""10#}
11#update
12#check stdout {
13#}
814
9#update=introduce use of undeclared identifier15#write main.zig {
10#file=main.zig
11const foo = something;16const foo = something;
12pub fn main() void {17pub fn main() void {
13 _ = &foo;18 _ = &foo;
14}19}
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 identifier27#write main.zig {
18#file=main.zig
19const foo = {};28const foo = {};
20pub fn main() void {29pub fn main() void {
21 _ = &foo;30 _ = &foo;
22}31}
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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3pub fn main() !void {6pub fn main() !void {
4 try foo(false);7 try foo(false);
5}8}
...@@ -10,10 +13,13 @@ fn foo(recurse: bool) !void {...@@ -10,10 +13,13 @@ fn foo(recurse: bool) !void {
10}13}
11const std = @import("std");14const std = @import("std");
12const io = std.Io.Threaded.global_single_threaded.io();15const 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 argument22#write main.zig {
16#file=main.zig
17pub fn main() !void {23pub fn main() !void {
18 try foo(true);24 try foo(true);
19}25}
...@@ -24,4 +30,9 @@ fn foo(recurse: bool) !void {...@@ -24,4 +30,9 @@ fn foo(recurse: bool) !void {
24}30}
25const std = @import("std");31const std = @import("std");
26const io = std.Io.Threaded.global_single_threaded.io();32const 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,27 +1,39 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const MyEnum = enum(u8) {6const MyEnum = enum(u8) {
4 foo = 1,7 foo = 1,
5 bar = 2,8 bar = 2,
6};9};
7pub fn main() !void {10pub fn main() !void {
8 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});11 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)});
10}13}
11const std = @import("std");14const std = @import("std");
12const io = std.Io.Threaded.global_single_threaded.io();15const io = std.Io.Threaded.global_single_threaded.io();
13#expect_stdout="1\n"16#}
14#update=remove enum field17#update
15#file=main.zig18#check stdout {
191
20#}
21
22#write main.zig {
16const MyEnum = enum(u8) {23const MyEnum = enum(u8) {
17 //foo = 1,24 //foo = 1,
18 bar = 2,25 bar = 2,
19};26};
20pub fn main() !void {27pub fn main() !void {
21 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});28 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)});
23}30}
24const std = @import("std");31const std = @import("std");
25const io = std.Io.Threaded.global_single_threaded.io();32const io = std.Io.Threaded.global_single_threaded.io();
26#expect_error=main.zig:7:69: error: enum 'main.MyEnum' has no member named 'foo'33#}
27#expect_error=main.zig:1:16: note: enum declared here34#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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const std = @import("std");6const std = @import("std");
4const io = std.Io.Threaded.global_single_threaded.io();7const io = std.Io.Threaded.global_single_threaded.io();
58
...@@ -23,9 +26,12 @@ fn bar(a: A) !void {...@@ -23,9 +26,12 @@ fn bar(a: A) !void {
23pub fn main() !void {26pub fn main() !void {
24 try foo();27 try foo();
25}28}
26#expect_stdout="0"29#}
27#update=remove foo and change bar wasm function type30#update
28#file=main.zig31#check stdout {
320#}
33
34#write main.zig {
29//! This update must preserve `bar` `InternPool.Index` value, while changing its wasm function type.35//! This update must preserve `bar` `InternPool.Index` value, while changing its wasm function type.
30//! If `foo` code is preserved in the binary without any changes, wasm type checking will fail.36//! If `foo` code is preserved in the binary without any changes, wasm type checking will fail.
31const std = @import("std");37const std = @import("std");
...@@ -47,4 +53,8 @@ fn bar(a: A) !void {...@@ -47,4 +53,8 @@ fn bar(a: A) !void {
47pub fn main() !void {53pub fn main() !void {
48 try bar(.one);54 try bar(.one);
49}55}
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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const E = enum { a, b, c };6const E = enum { a, b, c };
4const U = union(E) {7const U = union(E) {
5 a: i32,8 a: i32,
...@@ -11,10 +14,14 @@ pub fn main() void {...@@ -11,10 +14,14 @@ pub fn main() void {
11 const u: U = .{ .a = 123 };14 const u: U = .{ .a = 123 };
12 _ = u;15 _ = u;
13}16}
14#expect_error=main.zig:6:5: error: no field named 'd' in enum 'main.E'17#}
15#expect_error=main.zig:1:11: note: enum declared here18#update
16#update=remove invalid backing enum19#check errors {
17#file=main.zig20main.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 {
18const U = union {25const U = union {
19 a: i32,26 a: i32,
20 b: f64,27 b: f64,
...@@ -25,4 +32,8 @@ pub fn main() void {...@@ -25,4 +32,8 @@ pub fn main() void {
25 const u: U = .{ .a = 123 };32 const u: U = .{ .a = 123 };
26 _ = u;33 _ = u;
27}34}
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,19 +1,30 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const Foo = @Struct(.auto, null, &.{"reified_field"}, &.{void}, &.{.{}});6const Foo = @Struct(.auto, null, &.{"reified_field"}, &.{void}, &.{.{}});
4pub fn main(init: std.process.Init) !void {7pub fn main(init: std.process.Init) !void {
5 const field_name = @typeInfo(Foo).@"struct".field_names[0];8 const field_name = @typeInfo(Foo).@"struct".field_names[0];
6 try std.Io.File.stdout().writeStreamingAll(init.io, field_name ++ "\n");9 try std.Io.File.stdout().writeStreamingAll(init.io, field_name ++ "\n");
7}10}
8const std = @import("std");11const 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 init18#write main.zig {
12#file=main.zig
13const Foo = @TypeOf(.{ .anon_field = 123 });19const Foo = @TypeOf(.{ .anon_field = 123 });
14pub fn main(init: std.process.Init) !void {20pub fn main(init: std.process.Init) !void {
15 const field_name = @typeInfo(Foo).@"struct".field_names[0];21 const field_name = @typeInfo(Foo).@"struct".field_names[0];
16 try std.Io.File.stdout().writeStreamingAll(init.io, field_name ++ "\n");22 try std.Io.File.stdout().writeStreamingAll(init.io, field_name ++ "\n");
17}23}
18const std = @import("std");24const 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,27 +1,41 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const std = @import("std");6const std = @import("std");
4var some_enum: enum { first, second } = .first;7var some_enum: enum { first, second } = .first;
5const io = std.Io.Threaded.global_single_threaded.io();8const io = std.Io.Threaded.global_single_threaded.io();
6pub fn main() !void {9pub fn main() !void {
7 try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum));10 try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum));
8}11}
9#expect_stdout="first"12#}
10#update=no change13#update
11#file=main.zig14#check stdout {
15first#}
16
17#write main.zig {
12const std = @import("std");18const std = @import("std");
13var some_enum: enum { first, second } = .first;19var some_enum: enum { first, second } = .first;
14const io = std.Io.Threaded.global_single_threaded.io();20const io = std.Io.Threaded.global_single_threaded.io();
15pub fn main() !void {21pub fn main() !void {
16 try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum));22 try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum));
17}23}
18#expect_stdout="first"24#}
19#update=swap fields25#update {
20#file=main.zig26no change#}
27#check stdout {
28first#}
29
30#write main.zig {
21const std = @import("std");31const std = @import("std");
22var some_enum: enum { second, first } = .first;32var some_enum: enum { second, first } = .first;
23const io = std.Io.Threaded.global_single_threaded.io();33const io = std.Io.Threaded.global_single_threaded.io();
24pub fn main() !void {34pub fn main() !void {
25 try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum));35 try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum));
26}36}
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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3//! The original repro here depends on re-analysis order, which depends on6//! The original repro here depends on re-analysis order, which depends on
4//! declaration order, so this exact declaration order must be used.7//! declaration order, so this exact declaration order must be used.
5const Foo = struct { x: u8 };8const Foo = struct { x: u8 };
...@@ -11,10 +14,13 @@ fn bar(comptime x: u8) @This().Foo {...@@ -11,10 +14,13 @@ fn bar(comptime x: u8) @This().Foo {
11 return .{ .x = x };14 return .{ .x = x };
12}15}
13const std = @import("std");16const 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 member23#write main.zig {
17#file=main.zig
18//! The original repro here depends on re-analysis order, which depends on24//! The original repro here depends on re-analysis order, which depends on
19//! declaration order, so this exact declaration order must be used.25//! declaration order, so this exact declaration order must be used.
20const Foo = struct { x: u8 };26const Foo = struct { x: u8 };
...@@ -26,11 +32,15 @@ fn bar(comptime x: u8) @This().FooAlias {...@@ -26,11 +32,15 @@ fn bar(comptime x: u8) @This().FooAlias {
26 return .{ .x = x };32 return .{ .x = x };
27}33}
28const std = @import("std");34const std = @import("std");
29#expect_error=main.zig:8:31: error: root source file struct 'main' has no member named 'FooAlias'35#}
30#expect_error=main.zig:1:1: note: struct declared here36#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 error43#write main.zig {
33#file=main.zig
34//! The original repro here depends on re-analysis order, which depends on44//! The original repro here depends on re-analysis order, which depends on
35//! declaration order, so this exact declaration order must be used.45//! declaration order, so this exact declaration order must be used.
36const Foo = struct { x: u8 };46const Foo = struct { x: u8 };
...@@ -43,4 +53,9 @@ fn bar(comptime x: u8) @This().FooAlias {...@@ -43,4 +53,9 @@ fn bar(comptime x: u8) @This().FooAlias {
43 return .{ .x = x };53 return .{ .x = x };
44}54}
45const std = @import("std");55const 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,15 +1,28 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const std = @import("std");6const std = @import("std");
4pub fn main() !void {}7pub fn main() !void {}
5#expect_stdout=""8#}
9#update
10#check stdout {
11#}
612
7#update=introduce parse error13#write main.zig {
8#file=main.zig
9pub fn main() !void {14pub 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 error22#write main.zig {
13#file=main.zig
14pub fn main() !void {}23pub 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,5 +1,8 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const SomeType = u32;6const SomeType = u32;
4const S = struct {7const S = struct {
5 x: SomeType,8 x: SomeType,
...@@ -9,10 +12,12 @@ pub fn main() void {...@@ -9,10 +12,12 @@ pub fn main() void {
9 const s: S = .{ .x = 456 };12 const s: S = .{ .x = 456 };
10 s.foo();13 s.foo();
11}14}
12#expect_stdout=""15#}
16#update
17#check stdout {
18#}
1319
14#update=make S comptime-only20#write main.zig {
15#file=main.zig
16const SomeType = comptime_int;21const SomeType = comptime_int;
17const S = struct {22const S = struct {
18 x: SomeType,23 x: SomeType,
...@@ -22,10 +27,13 @@ pub fn main() void {...@@ -22,10 +27,13 @@ pub fn main() void {
22 const s: S = .{ .x = 456 };27 const s: S = .{ .x = 456 };
23 s.foo();28 s.foo();
24}29}
25#expect_stdout=""30#}
31#update {
32make S comptime-only#}
33#check stdout {
34#}
2635
27#update=make S runtime again36#write main.zig {
28#file=main.zig
29const SomeType = u16;37const SomeType = u16;
30const S = struct {38const S = struct {
31 x: SomeType,39 x: SomeType,
...@@ -35,4 +43,8 @@ pub fn main() void {...@@ -35,4 +43,8 @@ pub fn main() void {
35 const s: S = .{ .x = 456 };43 const s: S = .{ .x = 456 };
36 s.foo();44 s.foo();
37}45}
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,50 +1,70 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3pub const A = struct { b: B };6pub const A = struct { b: B };
4pub const B = struct { a: A };7pub const B = struct { a: A };
5pub fn main() void {8pub fn main() void {
6 _ = @as(B, undefined);9 _ = @as(B, undefined);
7}10}
8#expect_error=:error: dependency loop with length 211#}
9#expect_error=main.zig:2:27: note: type 'main.B' depends on type 'main.A' for field declared here12#update
10#expect_error=main.zig:1:27: note: type 'main.A' depends on type 'main.B' for field declared here13#check errors {
11#expect_error=:note: eliminate any one of these dependencies to break the loop14error: 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 loop20#write main.zig {
14#file=main.zig
15pub const A = struct { b: B };21pub const A = struct { b: B };
16pub const B = struct { a: A };22pub const B = struct { a: A };
17pub fn main() void {23pub fn main() void {
18 _ = B;24 _ = B;
19}25}
20#expect_stdout=""26#}
27#update {
28remove reference to dependency loop#}
29#check stdout {
30#}
2131
22#update=change dependency loop without fixing it32#write main.zig {
23#file=main.zig
24pub const A = struct { b: B };33pub const A = struct { b: B };
25pub const B = struct { a: *align(@alignOf(A)) A };34pub const B = struct { a: *align(@alignOf(A)) A };
26pub fn main() void {35pub fn main() void {
27 _ = B;36 _ = B;
28}37}
29#expect_stdout=""38#}
39#update {
40change dependency loop without fixing it#}
41#check stdout {
42#}
3043
31#update=reference dependency loop again44#write main.zig {
32#file=main.zig
33pub const A = struct { b: B };45pub const A = struct { b: B };
34pub const B = struct { a: *align(@alignOf(A)) A };46pub const B = struct { a: *align(@alignOf(A)) A };
35pub fn main() void {47pub fn main() void {
36 _ = @as(B, undefined);48 _ = @as(B, undefined);
37}49}
38#expect_error=:error: dependency loop with length 250#}
39#expect_error=main.zig:2:43: note: type 'main.B' depends on type 'main.A' for alignment query here51#update {
40#expect_error=main.zig:1:27: note: type 'main.A' depends on type 'main.B' for field declared here52reference dependency loop again#}
41#expect_error=:note: eliminate any one of these dependencies to break the loop53#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 loop60#write main.zig {
44#file=main.zig
45pub const A = struct { b: B };61pub const A = struct { b: B };
46pub const B = struct { a: *A };62pub const B = struct { a: *A };
47pub fn main() void {63pub fn main() void {
48 _ = @as(B, undefined);64 _ = @as(B, undefined);
49}65}
50#expect_stdout=""66#}
67#update {
68fix dependency loop#}
69#check stdout {
70#}
test/incremental/unreferenced_error+31-12
...@@ -1,25 +1,35 @@...@@ -1,25 +1,35 @@
1#update=initial version1#exe {
2#file=main.zig2-Mroot=main.zig
3#}
4
5#write main.zig {
3const std = @import("std");6const std = @import("std");
4pub fn main() !void {7pub fn main() !void {
5 try std.Io.File.stdout().writeStreamingAll(io, a);8 try std.Io.File.stdout().writeStreamingAll(io, a);
6}9}
7const a = "Hello, World!\n";10const a = "Hello, World!\n";
8const io = std.Io.Threaded.global_single_threaded.io();11const 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 error18#write main.zig {
12#file=main.zig
13const std = @import("std");19const std = @import("std");
14pub fn main() !void {20pub fn main() !void {
15 try std.Io.File.stdout().writeStreamingAll(io, a);21 try std.Io.File.stdout().writeStreamingAll(io, a);
16}22}
17const a = @compileError("bad a");23const a = @compileError("bad a");
18const io = std.Io.Threaded.global_single_threaded.io();24const io = std.Io.Threaded.global_single_threaded.io();
19#expect_error=main.zig:5:11: error: bad a25#}
26#update {
27introduce compile error#}
28#check errors {
29main.zig:5:11: error: bad a
30#}
2031
21#update=remove error reference32#write main.zig {
22#file=main.zig
23const std = @import("std");33const std = @import("std");
24pub fn main() !void {34pub fn main() !void {
25 try std.Io.File.stdout().writeStreamingAll(io, b);35 try std.Io.File.stdout().writeStreamingAll(io, b);
...@@ -27,10 +37,14 @@ pub fn main() !void {...@@ -27,10 +37,14 @@ pub fn main() !void {
27const a = @compileError("bad a");37const a = @compileError("bad a");
28const b = "Hi there!\n";38const b = "Hi there!\n";
29const io = std.Io.Threaded.global_single_threaded.io();39const 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 error47#write main.zig {
33#file=main.zig
34const std = @import("std");48const std = @import("std");
35pub fn main() !void {49pub fn main() !void {
36 try std.Io.File.stdout().writeStreamingAll(io, a);50 try std.Io.File.stdout().writeStreamingAll(io, a);
...@@ -38,4 +52,9 @@ pub fn main() !void {...@@ -38,4 +52,9 @@ pub fn main() !void {
38const a = "Back to a\n";52const a = "Back to a\n";
39const b = @compileError("bad b");53const b = @compileError("bad b");
40const io = std.Io.Threaded.global_single_threaded.io();54const 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(...@@ -626,6 +626,7 @@ pub fn lowerToBuildSteps(
626 if (getExternalExecutor(io, &case.target.result, .{626 if (getExternalExecutor(io, &case.target.result, .{
627 .host_cpu_arch = host.result.cpu.arch,627 .host_cpu_arch = host.result.cpu.arch,
628 .host_os_tag = host.result.os.tag,628 .host_os_tag = host.result.os.tag,
629 .link_mode = .dynamic, // TODO: this emulates old behavior until this file is deleted
629 .link_libc = true,630 .link_libc = true,
630 }) != .native) {631 }) != .native) {
631 // We wouldn't be able to run the compiled C code.632 // 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 {...@@ -2214,9 +2214,9 @@ const IncrementalTarget = struct {
22142214
2215/// These are passed to `incr-check` as `<target>-<backend>` strings.2215/// These are passed to `incr-check` as `<target>-<backend>` strings.
2216///2216///
2217/// If only one specific test is failing on a target, instead of entirely disabling the target here,2217/// If only one specific test is failing on a target, instead of entirely disabling the target here, you
2218/// you can skip the target for that specific test only by adding a line like this to the manifest:2218/// can skip the target for that specific test only by adding a line like this to top of the manifest:
2219/// #skip_target=x86_64-linux-selfhosted2219/// #skip x86_64-linux-selfhosted
2220const incremental_targets = &[_]IncrementalTarget{2220const incremental_targets = &[_]IncrementalTarget{
2221 // Avoid adding more CBE or LLVM targets without good reason: they're a lot slower than others2221 // Avoid adding more CBE or LLVM targets without good reason: they're a lot slower than others
2222 // to run due to the output (C source code or LLVM IR) being built non-incrementally (by Clang2222 // 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(...@@ -2502,7 +2502,7 @@ pub fn addStandaloneTests(
2502 .simple_skip_release_small = mem.findScalar(OptimizeMode, optimize_modes, .small) == null,2502 .simple_skip_release_small = mem.findScalar(OptimizeMode, optimize_modes, .small) == null,
2503 });2503 });
2504 const test_cases_dep_step = test_cases_dep.builder.default_step;2504 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;
2506 step.dependOn(test_cases_dep.builder.default_step);2506 step.dependOn(test_cases_dep.builder.default_step);
2507 return step;2507 return step;
2508}2508}
...@@ -3344,41 +3344,41 @@ const IncrementalTestOptions = struct {...@@ -3344,41 +3344,41 @@ const IncrementalTestOptions = struct {
33443344
3345pub fn addIncrementalTests(3345pub fn addIncrementalTests(
3346 b: *std.Build,3346 b: *std.Build,
3347 test_step: *Step,3347 runner: *std.Build.Step.Compile,
3348 options: IncrementalTestOptions,3348 options: IncrementalTestOptions,
3349) !void {3349) !*Step {
3350 const io = b.graph.io;3350 const tests_step = b.step("test-incremental", "Run the new incremental compilation test cases");
33513351
3352 const incr_check = b.addExecutable(.{3352 const tests_path = b.path("test/incremental");
3353 .name = "incr-check",3353 b.dependOnDirectoryContents(tests_path);
3354 .root_module = b.createModule(.{3354
3355 .root_source_file = b.path("tools/incr-check.zig"),3355 var tests_dir = try b.root.openDir(b.graph.io, "test/incremental", .{ .iterate = true });
3356 .target = b.graph.host,3356 defer tests_dir.close(b.graph.io);
3357 .optimize = .debug,3357 var test_it = tests_dir.iterate();
3358 }),3358 while (try test_it.next(b.graph.io)) |@"test"| {
3359 });3359 for (options.test_filters) |filter| {
33603360 if (std.mem.find(u8, @"test".name, filter) != null) break;
3361 b.dependOnDirectoryContents(b.path("test/incremental"));3361 } else if (options.test_filters.len > 0) continue;
3362
3363 var dir = try b.root.openDir(io, "test/incremental", .{ .iterate = true });
3364 defer dir.close(io);
33653362
3366 var it = try dir.walk(b.graph.arena);3363 const test_path = tests_path.path(b, @"test".name);
3367 while (try it.next(io)) |entry| {3364 switch (@"test".kind) {
3368 if (std.mem.endsWith(u8, entry.basename, ".swp")) continue;3365 else => continue,
3366 .file => if (isEditorFileName(@"test".name)) continue,
3367 .directory => {},
3368 }
33693369
3370 for (options.test_filters) |test_filter| {3370 const run = b.addRunArtifact(runner);
3371 if (std.mem.find(u8, entry.path, test_filter)) |_| break;3371 run.setName(@"test".name);
3372 } else if (options.test_filters.len > 0) continue;3372 run.enableProtocolMode();
33733373
3374 switch (entry.kind) {3374 switch (@"test".kind) {
3375 .file => {},
3376 .directory => {
3377 b.dependOnDirectoryContents(b.path(b.pathJoin(&.{ "test", "incremental", entry.path })));
3378 },
3379 else => continue,3375 else => continue,
3376 .file => run.addFileArg(test_path),
3377 .directory => run.addDirectoryArg(test_path),
3380 }3378 }
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
3383 for (incremental_targets) |test_target| {3383 for (incremental_targets) |test_target| {
3384 const resolved_target = b.resolveTargetQuery(test_target.target);3384 const resolved_target = b.resolveTargetQuery(test_target.target);
...@@ -3399,40 +3399,36 @@ pub fn addIncrementalTests(...@@ -3399,40 +3399,36 @@ pub fn addIncrementalTests(
33993399
3400 if (options.skip_llvm and test_target.backend == .llvm) continue;3400 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}", .{
3403 resolved_target.query.zigTriple(b.allocator) catch @panic("OOM"),3403 resolved_target.query.zigTriple(b.allocator) catch @panic("OOM"),
3404 test_target.backend,3404 test_target.backend,
3405 });3405 });
34063406
3407 if (options.test_target_filters.len > 0) {3407 for (options.test_target_filters) |filter| {
3408 for (options.test_target_filters) |filter| {3408 if (std.mem.find(u8, target_str, filter) != null) break;
3409 if (std.mem.find(u8, target_str, filter) != null) break;3409 } else if (options.test_target_filters.len > 0) continue;
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);
34213410
3422 run.addArgs(&.{ "--target", target_str });3411 run.addArgs(&.{ "--target", target_str });
3412 }
34233413
3424 run.addArg("--quiet"); // don't fill stderr telling us about skipped tests etc3414 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" });3421 run.addArg("--quiet"); // don't fill stderr telling us about skipped tests etc
3427 run.addThirdPartyEnabledArgQemu(.{ .enabled = "-fqemu" });
3428 run.addThirdPartyEnabledArgRosetta(.{ .enabled = "-frosetta" });
3429 run.addThirdPartyEnabledArgWasmtime(.{ .enabled = "-fwasmtime" });
3430 run.addThirdPartyEnabledArgWine(.{ .enabled = "-fwine" });
34313422
3432 run.addCheck(.{ .expect_term = .{ .exited = 0 } });3423 tests_step.dependOn(&run.step);
3433 test_step.dependOn(&run.step);
3434 }
3435 }3424 }
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");
3436}3432}
34373433
3438pub fn addLlvmIrTests(b: *std.Build, options: LlvmIrContext.Options) ?*Step {3434pub fn addLlvmIrTests(b: *std.Build, options: LlvmIrContext.Options) ?*Step {
tools/doctest.zig+1
...@@ -314,6 +314,7 @@ fn printOutput(...@@ -314,6 +314,7 @@ fn printOutput(
314 switch (getExternalExecutor(io, &target, .{314 switch (getExternalExecutor(io, &target, .{
315 .host_cpu_arch = host.cpu.arch,315 .host_cpu_arch = host.cpu.arch,
316 .host_os_tag = host.os.tag,316 .host_os_tag = host.os.tag,
317 .link_mode = code.link_mode orelse .dynamic,
317 .link_libc = code.link_libc,318 .link_libc = code.link_libc,
318 })) {319 })) {
319 .native => {},320 .native => {},
tools/gen_spirv_spec.zig+1-2
...@@ -86,8 +86,7 @@ pub fn main(init: std.process.Init) !void {...@@ -86,8 +86,7 @@ pub fn main(init: std.process.Init) !void {
8686
87 var zir = try std.zig.AstGen.generate(arena, tree);87 var zir = try std.zig.AstGen.generate(arena, tree);
88 if (zir.hasCompileErrors()) {88 if (zir.hasCompileErrors()) {
89 var wip_errors: std.zig.ErrorBundle.Wip = undefined;89 var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena);
90 try wip_errors.init(arena);
91 defer wip_errors.deinit();90 defer wip_errors.deinit();
92 try wip_errors.addZirErrorMessages(zir, tree, output, "");91 try wip_errors.addZirErrorMessages(zir, tree, output, "");
93 var error_bundle = try wip_errors.toOwnedBundle("");92 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}