diff --git a/build.zig b/build.zig index 6b94dd1d195da678e7ef1335d68464e8fd9dcadc..aad5a316a9ab7036a4a283582253eb36dfa055c0 100644 --- a/build.zig +++ b/build.zig @@ -125,7 +125,6 @@ pub fn build(b: *std.Build) !void { const skip_darwin = b.option(bool, "skip-darwin", "Main test suite skips targets with darwin OSs") orelse false; const skip_linux = b.option(bool, "skip-linux", "Main test suite skips targets with linux OS") orelse false; const skip_llvm = b.option(bool, "skip-llvm", "Main test suite skips targets that use LLVM backend") orelse false; - const skip_test_incremental = b.option(bool, "skip-test-incremental", "Main test step omits dependency on test-incremental step") orelse false; const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false; @@ -781,22 +780,6 @@ pub fn build(b: *std.Build) !void { test_step.dependOn(check_oracle_step); } - const test_incremental_step = b.step("test-incremental", "Run the incremental compilation test cases"); - try tests.addIncrementalTests(b, test_incremental_step, .{ - .test_filters = test_filters, - .test_target_filters = test_target_filters, - .skip_non_native = skip_non_native, - .skip_wasm = skip_wasm, - .skip_freebsd = skip_freebsd, - .skip_netbsd = skip_netbsd, - .skip_openbsd = skip_openbsd, - .skip_windows = skip_windows, - .skip_darwin = skip_darwin, - .skip_linux = skip_linux, - .skip_llvm = skip_llvm, - }); - if (!skip_test_incremental) test_step.dependOn(test_incremental_step); - if (tests.addLibcTestNszTests(b, .{ .optimize_modes = optimize_modes, .test_filters = test_filters, @@ -804,6 +787,27 @@ pub fn build(b: *std.Build) !void { .skip_wasm = skip_wasm, .max_rss = 4_300_000_000, })) |test_libc_nsz_step| test_step.dependOn(test_libc_nsz_step); + + const runner = b.addExecutable(.{ + .name = "runner", + .root_module = b.createModule(.{ + .root_source_file = b.path("test/src/Runner.zig"), + .target = b.graph.host, + }), + }); + test_step.dependOn(try tests.addIncrementalTests(b, runner, .{ + .test_filters = test_filters, + .test_target_filters = test_target_filters, + .skip_non_native = skip_non_native, + .skip_wasm = skip_wasm, + .skip_freebsd = skip_freebsd, + .skip_netbsd = skip_netbsd, + .skip_openbsd = skip_openbsd, + .skip_windows = skip_windows, + .skip_darwin = skip_darwin, + .skip_linux = skip_linux, + .skip_llvm = skip_llvm, + })); } fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index 739ddd0bb8183c8d9040f73fc11c1341054ff828..12df472dea24af6477265bbd9e922542db8a8757 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -3631,7 +3631,7 @@ pub fn resolveLazyPath( const c = &maker.scanned_config.configuration; return switch (lazy_path) { .source_path => |sp| try packagePath(maker, arena, sp.owner, sp.sub_path.slice(c)), - .relative => |relative| relativePath(maker, arena, relative), + .relative => |relative| try relativePath(maker, arena, relative, stepByIndex(maker, asking_step_index)), .generated => |gen| { const base = generatedPath(maker, gen.index); var file_path = base; @@ -3768,7 +3768,12 @@ pub fn packagePath( }; } -pub fn relativePath(maker: *const Maker, arena: Allocator, relative: Configuration.LazyPath.Relative) Allocator.Error!Path { +pub fn relativePath( + maker: *const Maker, + arena: Allocator, + relative: Configuration.LazyPath.Relative, + asking_step: *Step, +) error{ OutOfMemory, MakeFailed }!Path { const graph = maker.graph; const c = &maker.scanned_config.configuration; const sub_path = relative.sub_path.slice(c); @@ -3789,12 +3794,12 @@ pub fn relativePath(maker: *const Maker, arena: Allocator, relative: Configurati .root_dir = graph.build_root_directory, .sub_path = sub_path, }, - .zig_exe => .{ - .root_dir = .cwd(), - .sub_path = if (sub_path.len == 0) - graph.zig_exe - else - try Io.Dir.path.join(arena, &.{ graph.zig_exe, sub_path }), + .zig_exe => { + assert(sub_path.len == 0); + return .{ + .root_dir = .cwd(), + .sub_path = graph.zig_exe, + }; }, .zig_lib => .{ .root_dir = graph.zig_lib_directory, @@ -3804,6 +3809,10 @@ pub fn relativePath(maker: *const Maker, arena: Allocator, relative: Configurati .install_lib => try maker.install_paths.lib.join(arena, sub_path), .install_bin => try maker.install_paths.bin.join(arena, sub_path), .install_include => try maker.install_paths.include.join(arena, sub_path), + .libc_runtimes => if (maker.graph.libc_runtimes_dir) |libc_runtimes_dir| .{ + .root_dir = .cwd(), + .sub_path = try Dir.path.join(arena, &.{ libc_runtimes_dir, sub_path }), + } else asking_step.fail(maker, "unknown LazyPath: \"--libc-runtimes\" not specified", .{}), }; } @@ -4125,8 +4134,7 @@ const Fork = struct { fork.arena_allocator = .init(gpa); const arena = fork.arena_allocator.allocator(); - var error_bundle: std.zig.ErrorBundle.Wip = undefined; - try error_bundle.init(gpa); + var error_bundle: std.zig.ErrorBundle.Wip = try .init(gpa); defer error_bundle.deinit(); const manifest_path = try fork.path.join(arena, Package.Manifest.basename); @@ -4227,8 +4235,7 @@ fn loadManifest( errdefer manifest.deinit(gpa); if (manifest.errors.len > 0) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); const src_path = try wip_errors.addString(Package.Manifest.basename); @@ -4393,15 +4400,22 @@ fn confPathDepToCachePath( else => |index| try Dir.path.join(arena, &.{ index.get(c).?.root_path.slice(c), sub_path }), }, }, + .zig_exe => { + assert(sub_path.len == 0); + return .{ + .root_dir = .cwd(), + .sub_path = graph.zig_exe, + }; + }, .zig_lib => .{ .root_dir = graph.zig_lib_directory, .sub_path = sub_path, }, - .zig_exe => @panic("TODO"), .install_prefix => @panic("TODO"), .install_lib => @panic("TODO"), .install_bin => @panic("TODO"), .install_include => @panic("TODO"), + .libc_runtimes => @panic("TODO"), }; } diff --git a/lib/compiler/Maker/Fetch.zig b/lib/compiler/Maker/Fetch.zig index 5d3e80e25ee3becf1b308aa4917fe6a5c92781b2..21b7b2597c3e33cf7f10e31c82c82bbf052d2785 100644 --- a/lib/compiler/Maker/Fetch.zig +++ b/lib/compiler/Maker/Fetch.zig @@ -527,7 +527,7 @@ pub fn run(f: *Fetch) RunError!void { const arena = f.arena.allocator(); const gpa = f.arena.child_allocator; - try eb.init(gpa); + eb.* = try .init(gpa); // Check the global zig package cache to see if the hash already exists. If // so, load, parse, and validate the build.zig.zon file therein, and skip diff --git a/lib/compiler/Maker/Step.zig b/lib/compiler/Maker/Step.zig index f52640d0a490cb148467672db759d1051765a12a..37212902592f2dfd5d2ea0d760b861d1e7fe03e2 100644 --- a/lib/compiler/Maker/Step.zig +++ b/lib/compiler/Maker/Step.zig @@ -270,7 +270,10 @@ pub fn make( .compile => break :t null, .run => { const run_flags: Configuration.Step.Run.Flags = @bitCast(flags); - if (run_flags.stdio == .zig_test) break :t null; + switch (run_flags.stdio) { + .infer_from_args, .inherit, .check => {}, + .zig_test, .protocol => break :t null, + } }, else => {}, } @@ -621,6 +624,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi else => |e| return e, }; const body = client.in.take(header.bytes_len) catch unreachable; + var body_r: std.Io.Reader = .fixed(body); switch (header.tag) { .zig_version => { @@ -632,16 +636,22 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi ); } }, + .config => switch (s.extended) { + else => unreachable, + .compile => |*compile| compile.config = + body_r.takeStruct(std.zig.Server.Message.Config, .little) catch unreachable, + .translate_c => {}, + }, .error_bundle => { s.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body); // This message indicates the end of the update. if (watch) break; }, .emit_digest => { - const EmitDigest = std.zig.Server.Message.EmitDigest; - const emit_digest: *align(1) const EmitDigest = @ptrCast(body); + const emit_digest = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable; + const digest = body_r.takeArray(Cache.bin_digest_len) catch unreachable; s.result_cached = emit_digest.flags.cache_hit; - result = .{ .bin = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len].* }; + result = .{ .bin = digest.* }; }, .file_system_inputs => { clearWatchInputs(s, maker); @@ -701,8 +711,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi } }, .time_report => if (maker.web_server) |ws| { - const TimeReport = std.zig.Server.Message.TimeReport; - const tr: *align(1) const TimeReport = @ptrCast(body[0..@sizeOf(TimeReport)]); + const tr = body_r.takeStruct(std.zig.Server.Message.TimeReport, .little) catch unreachable; ws.updateTimeReportCompile(.{ .compile_step = step_index, .use_llvm = tr.flags.use_llvm, @@ -711,7 +720,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi .llvm_pass_timings_len = tr.llvm_pass_timings_len, .files_len = tr.files_len, .decls_len = tr.decls_len, - .trailing = body[@sizeOf(TimeReport)..], + .trailing = body_r.buffered(), }); }, else => {}, // ignore other messages @@ -848,7 +857,7 @@ pub fn setWatchInputsFromManifestFiles( } /// For steps that have a single input that never changes when re-running `make`. -pub fn singleUnchangingWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_path: LazyPath) Allocator.Error!void { +pub fn singleUnchangingWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_path: LazyPath) FailError!void { if (!step.inputs.populated()) try step.addWatchInput(maker, arena, lazy_path); } @@ -857,7 +866,7 @@ pub fn clearWatchInputs(step: *Step, maker: *Maker) void { } /// Places a *file* dependency on the path. -pub fn addWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_file: LazyPath) Allocator.Error!void { +pub fn addWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_file: LazyPath) FailError!void { const conf = &maker.scanned_config.configuration; switch (lazy_file) { .source_path => |source_path| { @@ -866,7 +875,7 @@ pub fn addWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_file: La try addWatchInputPath(step, maker, pkg_path); }, .relative => |relative| { - const resolved_path = try maker.relativePath(arena, relative); + const resolved_path = try maker.relativePath(arena, relative, step); try addWatchInputPath(step, maker, resolved_path); }, // 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 /// Paths derived from this directory should also be manually added via /// `addDirectoryWatchInputFromPath` if and only if this function returns /// `true`. -pub fn addDirectoryWatchInput(step: *Step, maker: *Maker, lazy_directory: LazyPath) Allocator.Error!bool { +pub fn addDirectoryWatchInput(step: *Step, maker: *Maker, lazy_directory: LazyPath) FailError!bool { const graph = maker.graph; const arena = graph.arena; // TODO don't leak into the process arena switch (lazy_directory) { @@ -892,7 +901,7 @@ pub fn addDirectoryWatchInput(step: *Step, maker: *Maker, lazy_directory: LazyPa try addDirectoryWatchInputFromPath(step, maker, pkg_path); }, .relative => |relative| { - const resolved_path = try maker.relativePath(arena, relative); + const resolved_path = try maker.relativePath(arena, relative, step); try addDirectoryWatchInputFromPath(step, maker, resolved_path); }, // Nothing to watch because this dependency edge is modeled instead via `dependants`. diff --git a/lib/compiler/Maker/Step/Compile.zig b/lib/compiler/Maker/Step/Compile.zig index b65019ed6e41890671ab9ea232e0df6317e13dec..1c8cdc9db4c94aae76542ef3e43d36f1fc6c190c 100644 --- a/lib/compiler/Maker/Step/Compile.zig +++ b/lib/compiler/Maker/Step/Compile.zig @@ -21,7 +21,7 @@ zig_process: ?*Step.ZigProcess = null, /// Populated by InstallArtifact. installed_path: ?Path = null, /// Populated by `make`, used by `Run`. -is_linking_libc: bool = false, +config: ?std.zig.Server.Message.Config = null, pub fn make( compile: *Compile, @@ -29,6 +29,7 @@ pub fn make( maker: *Maker, progress_node: std.Progress.Node, ) Step.ExtendedMakeError!void { + _ = compile; // only accessed by `Step.evalZigProcess`. const graph = maker.graph; const gpa = maker.gpa; const conf = &maker.scanned_config.configuration; @@ -42,7 +43,7 @@ pub fn make( var argv: std.ArrayList([]const u8) = .empty; defer argv.deinit(gpa); - try lowerZigArgs(arena, compile, compile_index, maker, progress_node, &argv, false); + try lowerZigArgs(arena, compile_index, maker, progress_node, &argv, false); const incremental = conf_comp.flags4.incremental.toBool() orelse graph.incremental == true; @@ -158,7 +159,6 @@ const ModuleListContext = struct { fn lowerZigArgs( arena: Allocator, - compile: *Compile, compile_index: Configuration.Step.Index, maker: *Maker, progress_node: std.Progress.Node, @@ -575,8 +575,6 @@ fn lowerZigArgs( try zig_args.ensureUnusedCapacity(gpa, 2); if (is_linking_libcpp) zig_args.appendAssumeCapacity("-lc++"); if (is_linking_libc) zig_args.appendAssumeCapacity("-lc"); - - compile.is_linking_libc = is_linking_libc; } if (conf_comp.win32_manifest.value) |manifest_file| { @@ -944,6 +942,7 @@ pub fn rebuildInFuzzMode( compile_index: Configuration.Step.Index, progress_node: std.Progress.Node, ) !Step.OptCacheDigest { + _ = compile; const gpa = maker.gpa; const step = maker.stepByIndex(compile_index); @@ -962,7 +961,7 @@ pub fn rebuildInFuzzMode( var argv: std.ArrayList([]const u8) = .empty; defer argv.deinit(gpa); - try lowerZigArgs(arena, compile, compile_index, maker, progress_node, &argv, true); + try lowerZigArgs(arena, compile_index, maker, progress_node, &argv, true); return Step.evalZigProcess(compile_index, maker, argv.items, progress_node, false); } diff --git a/lib/compiler/Maker/Step/Run.zig b/lib/compiler/Maker/Step/Run.zig index 7f5afff5cbb1968bb25537a716a715a9efb20506..a1fbb989aee08ef463efa44cd8511ec0337a79f4 100644 --- a/lib/compiler/Maker/Step/Run.zig +++ b/lib/compiler/Maker/Step/Run.zig @@ -55,237 +55,598 @@ pub fn make( var man = graph.cache.obtain(); defer man.deinit(); - if (conf_run.environ_map.value) |environ_map_index| { - const environ_map = environ_map_index.get(conf); - for (environ_map.keys.slice(conf), environ_map.values.slice(conf)) |key, value| { - man.hash.addBytesZ(key.slice(conf)); - man.hash.addBytesZ(value.slice(conf)); + var tmp_dir_path: ["tmp".len + Dir.path.sep_str.len + std.fmt.hex(@as(u64, 0)).len]u8 = undefined; + + const has_side_effects = has_side_effects: { + if (conf_run.environ_map.value) |environ_map_index| { + const environ_map = environ_map_index.get(conf); + for (environ_map.keys.slice(conf), environ_map.values.slice(conf)) |key, value| { + man.hash.addBytesZ(key.slice(conf)); + man.hash.addBytesZ(value.slice(conf)); + } } - } - - for (conf_run.preopens.slice) |preopen| { - man.hash.addBytesZ(preopen.name.slice(conf)); - const cwd_path = try maker.resolveLazyPathIndex(arena, preopen.path, run_index); - man.hash.addBytes(try cwd_path.toString(arena)); - } - - man.hash.add(graph.fuzzing); - man.hash.add(conf_run.flags.color); - man.hash.add(conf_run.flags.disable_zig_progress); - - var any_dep_files = false; - var any_output_args = false; - var any_cli_positionals = false; - - for (conf_run.args.slice) |arg_index| { - const arg = arg_index.get(conf); - try argv_list.ensureUnusedCapacity(gpa, 1); - switch (arg.flags.tag) { - .string => { - const prefix = arg.prefix.value.?.slice(conf); - argv_list.appendAssumeCapacity(prefix); - man.hash.addBytesZ(prefix); - }, - .path_file => { - const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; - const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; - const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); - argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{ - prefix, try convertPathArg(arena, run_index, maker, file_path, arg.flags.make_absolute), suffix, - })); - man.hash.add(arg.flags.make_absolute); - man.hash.addBytesZ(prefix); - man.hash.addBytesZ(suffix); - _ = try man.addInputPath(file_path, .{}); - }, - .path_directory => { - const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; - const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; - const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); - const resolved_arg = try mem.concat(arena, u8, &.{ - prefix, try convertPathArg(arena, run_index, maker, file_path, arg.flags.make_absolute), suffix, - }); - argv_list.appendAssumeCapacity(resolved_arg); - man.hash.add(arg.flags.make_absolute); - man.hash.addBytes(resolved_arg); - }, - .file_content => { - const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; - const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; - const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); - - var result: std.Io.Writer.Allocating = .init(arena); - result.writer.writeAll(prefix) catch return error.OutOfMemory; - - const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err| - return step.fail(maker, "unable to open input file {f}: {t}", .{ file_path, err }); - defer file.close(io); - - var file_reader = file.reader(io, &.{}); - _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) { - error.ReadFailed => switch (file_reader.err.?) { - error.Canceled => |e| return e, - else => |e| return step.fail(maker, "failed to read from {f}: {t}", .{ file_path, e }), - }, - error.WriteFailed => return error.OutOfMemory, - }; - result.writer.writeAll(suffix) catch return error.OutOfMemory; - - argv_list.appendAssumeCapacity(result.written()); - man.hash.addBytesZ(prefix); - man.hash.addBytesZ(suffix); - _ = try man.addInputPath(file_path, .{}); - }, - .artifact => { - const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; - const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; - const producer_index = arg.producer.value.?; - const producer_step = producer_index.ptr(conf); - const producer = producer_step.extended.get(conf.extra).compile; - const producer_make_comp_step = maker.stepByIndex(producer_index); - const producer_make_comp = &producer_make_comp_step.extended.compile; - - const file_path = producer_make_comp.installed_path orelse - maker.generatedPath(producer.generated_bin.value.?); - - argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{ - prefix, try convertPathArg(arena, run_index, maker, file_path, arg.flags.make_absolute), suffix, - })); - - man.hash.add(arg.flags.make_absolute); - man.hash.addBytesZ(prefix); - man.hash.addBytesZ(suffix); - _ = try man.addInputPath(file_path, .{}); - }, - .output_file, .output_directory => { - const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; - const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; - const basename = arg.basename.value.?.slice(conf); - - man.hash.add(arg.flags.make_absolute); - man.hash.addBytesZ(prefix); - man.hash.addBytesZ(basename); - man.hash.addBytesZ(suffix); - man.hash.add(arg.flags.dep_file); - - any_dep_files = any_dep_files or arg.flags.dep_file; - any_output_args = true; - - // Add a placeholder into the argument list because we need the - // manifest hash to be updated with all arguments before the - // object directory is computed. - try output_placeholders.append(gpa, .{ - .index = @intCast(argv_list.items.len), - .arg_index = arg_index, - }); - argv_list.items.len += 1; + + for (conf_run.preopens.slice) |preopen| { + man.hash.addBytesZ(preopen.name.slice(conf)); + const cwd_path = try maker.resolveLazyPathIndex(arena, preopen.path, run_index); + man.hash.addBytes(try cwd_path.toString(arena)); + } + + man.hash.add(graph.fuzzing); + man.hash.add(conf_run.flags.color); + man.hash.add(conf_run.flags.disable_zig_progress); + + var any_discovered_inputs = switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check, .zig_test => false, + .protocol => true, + }; + var any_output_args = false; + var any_cli_positionals = false; + + for (switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check, .zig_test => conf_run.args.slice, + .protocol => conf_run.args.slice[0..1], // arguments communicated over protocol + }) |arg_index| { + const arg = arg_index.get(conf); + try argv_list.ensureUnusedCapacity(gpa, 1); + switch (arg.flags.tag) { + .string => { + const string = arg.prefix.value.?.slice(conf); + argv_list.appendAssumeCapacity(string); + man.hash.addBytesZ(string); + }, + .path_file => { + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); + argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{ + prefix, try convertPathArg(arena, run_index, maker, file_path, arg.flags.make_absolute), suffix, + })); + man.hash.add(arg.flags.make_absolute); + man.hash.addBytesZ(prefix); + man.hash.addBytesZ(suffix); + _ = try man.addInputPath(file_path, .{}); + }, + .path_directory => { + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); + const resolved_arg = try mem.concat(arena, u8, &.{ + prefix, try convertPathArg(arena, run_index, maker, file_path, arg.flags.make_absolute), suffix, + }); + argv_list.appendAssumeCapacity(resolved_arg); + man.hash.add(arg.flags.make_absolute); + man.hash.addBytes(resolved_arg); + }, + .file_content => { + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); + + var result: std.Io.Writer.Allocating = .init(arena); + result.writer.writeAll(prefix) catch return error.OutOfMemory; + + const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err| + return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err }); + defer file.close(io); + + var file_reader = file.reader(io, &.{}); + _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) { + error.ReadFailed => switch (file_reader.err.?) { + error.Canceled => |e| return e, + else => |e| return step.fail(maker, "failed to read from {qf}: {t}", .{ file_path, e }), + }, + error.WriteFailed => return error.OutOfMemory, + }; + result.writer.writeAll(suffix) catch return error.OutOfMemory; + + argv_list.appendAssumeCapacity(result.written()); + man.hash.addBytesZ(prefix); + _ = try man.addInputPath(file_path, .{}); + man.hash.addBytesZ(suffix); + }, + .artifact => { + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const producer_index = arg.producer.value.?; + const producer_step = producer_index.ptr(conf); + const producer = producer_step.extended.get(conf.extra).compile; + const producer_make_comp_step = maker.stepByIndex(producer_index); + const producer_make_comp = &producer_make_comp_step.extended.compile; + + const file_path = producer_make_comp.installed_path orelse + maker.generatedPath(producer.generated_bin.value.?); + + argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{ + prefix, try convertPathArg(arena, run_index, maker, file_path, arg.flags.make_absolute), suffix, + })); + + man.hash.add(arg.flags.make_absolute); + man.hash.addBytesZ(prefix); + man.hash.addBytesZ(suffix); + _ = try man.addInputPath(file_path, .{}); + }, + .output_file, .output_directory => { + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const basename = arg.basename.value.?.slice(conf); + + man.hash.add(arg.flags.make_absolute); + man.hash.addBytesZ(prefix); + man.hash.addBytesZ(basename); + man.hash.addBytesZ(suffix); + man.hash.add(arg.flags.dep_file); + + if (arg.flags.dep_file) any_discovered_inputs = true; + any_output_args = true; + + // Add a placeholder into the argument list because we need the + // manifest hash to be updated with all arguments before the + // object directory is computed. + try output_placeholders.append(gpa, .{ + .index = @intCast(argv_list.items.len), + .offset = 0, + .arg_index = arg_index, + }); + _ = argv_list.addOneAssumeCapacity(); + }, + .passthru => { + any_cli_positionals = true; + if (maker.run_args) |run_args| { + try argv_list.appendSlice(gpa, run_args); + man.hash.addListOfBytes(run_args); + } + }, + .enable_darling => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_darling, arg.prefix.value, arg.suffix.value), + .enable_qemu => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_qemu, arg.prefix.value, arg.suffix.value), + .enable_rosetta => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_rosetta, arg.prefix.value, arg.suffix.value), + .enable_wasmtime => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_wasmtime, arg.prefix.value, arg.suffix.value), + .enable_wine => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value), + } + } + + var owned_dirs: std.bit_set.Dynamic = .{}; + var inherit_dirs: std.ArrayList(Io.Dir) = .empty; + var owned_files: std.bit_set.Dynamic = .{}; + var inherit_files: std.ArrayList(Io.File) = .empty; + var protocol_args: std.ArrayList(u8) = .empty; + var input_dirs: std.ArrayList(Cache.Path) = .empty; + defer { + for (inherit_dirs.items, 0..) |inherit_dir, inherit_dir_index| + if (owned_dirs.isSet(inherit_dir_index)) inherit_dir.close(io); + for (inherit_files.items, 0..) |inherit_file, inherit_file_index| + if (owned_files.isSet(inherit_file_index)) inherit_file.close(io); + owned_dirs.deinit(gpa); + inherit_dirs.deinit(gpa); + owned_files.deinit(gpa); + inherit_files.deinit(gpa); + protocol_args.deinit(gpa); + input_dirs.deinit(gpa); + } + switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check => {}, + .zig_test => { + const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false); + + try argv_list.ensureUnusedCapacity(gpa, 3); + argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string})); + argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed})); + argv_list.appendAssumeCapacity("--listen=-"); }, - .passthru => { - any_cli_positionals = true; - if (maker.run_args) |run_args| { - try argv_list.appendSlice(gpa, run_args); - man.hash.addListOfBytes(run_args); + .protocol => { + argv_list.appendAssumeCapacity("--listen=-"); + try input_dirs.append(gpa, if (conf_run.cwd.value) |lazy_cwd| + try maker.resolveLazyPathIndex(arena, lazy_cwd, run_index) + else + .cwd()); + for (conf_run.args.slice[1..]) |arg_index| { + const arg = arg_index.get(conf); + switch (arg.flags.tag) { + .string => { + const string = arg.prefix.value.?.slice(conf); + + man.hash.addBytesZ(string); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + string.len + 1); + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.string)); + protocol_args.appendSliceAssumeCapacity(string); + protocol_args.appendAssumeCapacity(0); + }, + .path_file => { + const lazy_path = arg.path.value.?.get(conf); + switch (lazy_path) { + else => {}, + .relative => |relative| switch (relative.flags.base) { + else => {}, + .libc_runtimes => if (graph.libc_runtimes_dir == null) continue, + }, + } + + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const file_path = try maker.resolveLazyPath(arena, lazy_path, run_index); + + try owned_files.resize(gpa, owned_files.bit_length + 1, true); + try inherit_files.ensureUnusedCapacity(gpa, 1); + const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err| + return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err }); + inherit_files.appendAssumeCapacity(file); + + man.hash.addBytesZ(prefix); + _ = try man.addInputPath(file_path, .{}); + man.hash.addBytesZ(suffix); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 + + 1 + @sizeOf(Io.File.Handle) + + 1 + suffix.len + 1); + if (prefix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_file)); + const file_handle: *align(1) Io.File.Handle = + @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle))); + file_handle.* = file.handle; + if (suffix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + }, + .path_directory => { + const lazy_path = arg.path.value.?.get(conf); + switch (lazy_path) { + else => {}, + .relative => |relative| switch (relative.flags.base) { + else => {}, + .libc_runtimes => if (graph.libc_runtimes_dir == null) continue, + }, + } + + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const dir_path = try maker.resolveLazyPath(arena, lazy_path, run_index); + try input_dirs.append(gpa, dir_path); + + const has_sub_path = dir_path.sub_path.len > 0; + try owned_dirs.resize(gpa, owned_dirs.bit_length + 1, has_sub_path); + try inherit_dirs.ensureUnusedCapacity(gpa, 1); + const dir = if (has_sub_path) + dir_path.root_dir.handle.openDir(io, dir_path.sub_path, .{ + .iterate = true, + }) catch |err| return step.fail(maker, "unable to open input dir {qf}: {t}", .{ dir_path, err }) + else + dir_path.root_dir.handle; + inherit_dirs.appendAssumeCapacity(dir); + + man.hash.addBytesZ(prefix); + man.hash.addOptionalBytes(dir_path.root_dir.path); + man.hash.addBytes(dir_path.sub_path); + man.hash.addBytesZ(suffix); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 + + 1 + @sizeOf(Io.Dir.Handle) + + 1 + suffix.len + 1); + if (prefix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_dir)); + const dir_handle: *align(1) Io.Dir.Handle = + @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.Dir.Handle))); + dir_handle.* = dir.handle; + if (suffix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + }, + .file_content => { + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); + + try owned_files.resize(gpa, owned_files.bit_length + 1, true); + try inherit_files.ensureUnusedCapacity(gpa, 1); + const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err| + return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err }); + inherit_files.appendAssumeCapacity(file); + + man.hash.addBytesZ(prefix); + _ = try man.addInputPath(file_path, .{}); + man.hash.addBytesZ(suffix); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 + + 1 + @sizeOf(Io.File.Handle) + + 1 + suffix.len + 1); + if (prefix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_file_content)); + const file_handle: *align(1) Io.File.Handle = + @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle))); + file_handle.* = file.handle; + if (suffix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + }, + .artifact => { + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const producer_index = arg.producer.value.?; + const producer_step = producer_index.ptr(conf); + const producer = producer_step.extended.get(conf.extra).compile; + const producer_make_comp_step = maker.stepByIndex(producer_index); + const producer_make_comp = &producer_make_comp_step.extended.compile; + + const file_path = producer_make_comp.installed_path orelse + maker.generatedPath(producer.generated_bin.value.?); + + try owned_files.resize(gpa, owned_files.bit_length + 1, true); + try inherit_files.ensureUnusedCapacity(gpa, 1); + const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err| + return step.fail(maker, "unable to open input artifact {qf}: {t}", .{ file_path, err }); + inherit_files.appendAssumeCapacity(file); + + man.hash.addBytesZ(prefix); + _ = try man.addInputPath(file_path, .{}); + man.hash.addBytesZ(suffix); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 + + 1 + @sizeOf(Io.File.Handle) + + 1 + suffix.len + 1); + if (prefix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_file)); + const file_handle: *align(1) Io.File.Handle = + @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle))); + file_handle.* = file.handle; + if (suffix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + }, + .output_file => { + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const basename = arg.basename.value.?.slice(conf); + + man.hash.addBytesZ(prefix); + man.hash.addBytesZ(basename); + man.hash.addBytesZ(suffix); + assert(!arg.flags.dep_file); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 + + 1 + @sizeOf(Io.File.Handle) + + 1 + suffix.len + 1); + if (prefix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.output_file)); + + // Add a placeholder into the argument list because we need the + // manifest hash to be updated with all arguments before the + // object directory is computed. + try owned_files.resize(gpa, owned_files.bit_length + 1, false); + try inherit_files.ensureUnusedCapacity(gpa, 1); + try output_placeholders.append(gpa, .{ + .index = @intCast(inherit_files.items.len), + .offset = @intCast(protocol_args.items.len), + .arg_index = arg_index, + }); + _ = inherit_files.addOneAssumeCapacity(); + + _ = protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle)); + if (suffix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + }, + .output_directory => { + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const basename = arg.basename.value.?.slice(conf); + + man.hash.addBytesZ(prefix); + man.hash.addBytesZ(basename); + man.hash.addBytesZ(suffix); + assert(!arg.flags.dep_file); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 + + 1 + @sizeOf(Io.Dir.Handle) + + 1 + suffix.len + 1); + if (prefix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.output_dir)); + + // Add a placeholder into the argument list because we need the + // manifest hash to be updated with all arguments before the + // object directory is computed. + try owned_dirs.resize(gpa, owned_dirs.bit_length + 1, false); + try inherit_dirs.ensureUnusedCapacity(gpa, 1); + try output_placeholders.append(gpa, .{ + .index = @intCast(inherit_dirs.items.len), + .offset = @intCast(protocol_args.items.len), + .arg_index = arg_index, + }); + _ = inherit_dirs.addOneAssumeCapacity(); + + _ = protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.Dir.Handle)); + if (suffix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + }, + .passthru => if (maker.run_args) |run_args| { + man.hash.addListOfBytes(run_args); + + for (run_args) |run_arg| { + try protocol_args.ensureUnusedCapacity(gpa, 1 + run_arg.len + 1); + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.string)); + protocol_args.appendSliceAssumeCapacity(run_arg); + protocol_args.appendAssumeCapacity(0); + } + }, + .enable_darling => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_darling, arg.prefix.value, arg.suffix.value), + .enable_qemu => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_qemu, arg.prefix.value, arg.suffix.value), + .enable_rosetta => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_rosetta, arg.prefix.value, arg.suffix.value), + .enable_wasmtime => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_wasmtime, arg.prefix.value, arg.suffix.value), + .enable_wine => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value), + } } }, - .enable_darling => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_darling, arg.prefix.value, arg.suffix.value), - .enable_qemu => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_qemu, arg.prefix.value, arg.suffix.value), - .enable_rosetta => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_rosetta, arg.prefix.value, arg.suffix.value), - .enable_wasmtime => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_wasmtime, arg.prefix.value, arg.suffix.value), - .enable_wine => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value), } - } - man.hash.add(conf_run.flags.test_runner_mode); - if (conf_run.flags.test_runner_mode) { - const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false); + switch (conf_run.stdin.u) { + .bytes => |bytes| { + man.hash.addBytes(bytes.slice(conf)); + }, + .lazy_path => |lazy_path| { + const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index); + _ = try man.addInputPath(file_path, .{}); + }, + .none => {}, + } + + if (conf_run.captured_stdout.value) |captured| { + man.hash.addBytes(captured.basename.slice(conf)); + man.hash.add(conf_run.flags.stdout_trim_whitespace); + } - try argv_list.ensureUnusedCapacity(gpa, 3); - argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string})); - argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed})); - argv_list.appendAssumeCapacity("--listen=-"); - } + if (conf_run.captured_stderr.value) |captured| { + man.hash.addBytes(captured.basename.slice(conf)); + man.hash.add(conf_run.flags.stderr_trim_whitespace); + } + + switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .zig_test, .protocol => {}, + .check => { + man.hash.addBytes(if (conf_run.expect_stderr_exact.value) |bytes| bytes.slice(conf) else ""); + man.hash.addBytes(if (conf_run.expect_stdout_exact.value) |bytes| bytes.slice(conf) else ""); + for (conf_run.expect_stderr_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf)); + for (conf_run.expect_stdout_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf)); + man.hash.add(conf_run.flags2.expect_term_status); + man.hash.addOptional(conf_run.expect_term_value.value); + }, + } - switch (conf_run.stdin.u) { - .bytes => |bytes| { - man.hash.addBytes(bytes.slice(conf)); - }, - .lazy_path => |lazy_path| { + for (conf_run.file_inputs.slice) |lazy_path| { const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index); _ = try man.addInputPath(file_path, .{}); - }, - .none => {}, - } + } - if (conf_run.captured_stdout.value) |captured| { - man.hash.addBytes(captured.basename.slice(conf)); - man.hash.add(conf_run.flags.stdout_trim_whitespace); - } + if (conf_run.cwd.value) |lazy_path| { + const cwd_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index); + _ = man.hash.addBytes(try cwd_path.toString(arena)); + } - if (conf_run.captured_stderr.value) |captured| { - man.hash.addBytes(captured.basename.slice(conf)); - man.hash.add(conf_run.flags.stderr_trim_whitespace); - } + // Whether the Run step has side effects *other than* updating the output arguments. + // When fuzzing we need to always run the test runner to populate fuzz_tests. + const has_side_effects = graph.fuzzing or conf_run.flags.has_side_effects or any_cli_positionals or + switch (conf_run.flags.stdio) { + .infer_from_args => !any_output_args and + conf_run.captured_stdout.value == null and + conf_run.captured_stderr.value == null, + .inherit => true, + .check, .zig_test, .protocol => false, + }; - switch (conf_run.flags.stdio) { - .infer_from_args, .inherit, .zig_test => {}, - .check => { - man.hash.addBytes(if (conf_run.expect_stderr_exact.value) |bytes| bytes.slice(conf) else ""); - man.hash.addBytes(if (conf_run.expect_stdout_exact.value) |bytes| bytes.slice(conf) else ""); - for (conf_run.expect_stderr_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf)); - for (conf_run.expect_stdout_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf)); - man.hash.add(conf_run.flags2.expect_term_status); - man.hash.addOptional(conf_run.expect_term_value.value); - }, - } + if (!has_side_effects and try step.cacheHitWatched(maker, &man, progress_node)) { + // Cache hit; skip running command. + const digest = man.hitDigestHex(); + try populateGeneratedStdIo(maker, &conf_run, &digest); + try populateGeneratedPaths(maker, output_placeholders.items, &digest); + step.result_cached = true; + return; + } - for (conf_run.file_inputs.slice) |lazy_path| { - const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index); - _ = try man.addInputPath(file_path, .{}); - } + if (!any_discovered_inputs) { + // We already know the final output paths; use them directly. + const digest = if (has_side_effects) man.hash.final() else man.missDigestHex(); + const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest; + try populateGeneratedStdIo(maker, &conf_run, &digest); + try populateGeneratedPathsCreateDirs(arena, run_index, maker, output_dir_path, output_placeholders.items, switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check, .zig_test => .{ .argv = argv_list.items }, + .protocol => .{ .protocol = .{ + .owned_dirs = &owned_dirs, + .inherit_dirs = inherit_dirs.items, + .owned_files = &owned_files, + .inherit_files = inherit_files.items, + .args = protocol_args.items, + } }, + }); + runCommand(arena, run, run_index, maker, progress_node, argv_list.items, &.{}, &.{}, &.{}, &.{}, null, has_side_effects, output_dir_path, null) catch |err| switch (err) { + else => |e| return e, + error.MakeFailed, error.MakeSkipped => |e| { + try step.setWatchInputsFromManifest(maker, &man); + return e; + }, + }; + if (has_side_effects) { + try step.setWatchInputsFromManifest(maker, &man); + } else { + try step.finalizeManifestAndWatch(maker, &man); + } + return; + } - if (conf_run.cwd.value) |lazy_path| { - const cwd_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index); - _ = man.hash.addBytes(try cwd_path.toString(arena)); - } + // We do not know the final output paths yet; use temporary directory to run the command. + var rand_int: u64 = undefined; + io.random(@ptrCast(&rand_int)); + tmp_dir_path = ("tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int)).*; - // Whether the Run step has side effects *other than* updating the output arguments. - // When fuzzing we need to always run the test runner to populate fuzz_tests. - const has_side_effects = graph.fuzzing or conf_run.flags.has_side_effects or any_cli_positionals or - switch (conf_run.flags.stdio) { - .infer_from_args => !any_output_args and - conf_run.captured_stdout.value == null and - conf_run.captured_stderr.value == null, - .inherit => true, - .check, .zig_test => false, + try populateGeneratedPathsCreateDirs(arena, run_index, maker, &tmp_dir_path, output_placeholders.items, switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check, .zig_test => .{ .argv = argv_list.items }, + .protocol => .{ .protocol = .{ + .owned_dirs = &owned_dirs, + .inherit_dirs = inherit_dirs.items, + .owned_files = &owned_files, + .inherit_files = inherit_files.items, + .args = protocol_args.items, + } }, + }); + runCommand( + arena, + run, + run_index, + maker, + progress_node, + argv_list.items, + inherit_dirs.items, + inherit_files.items, + protocol_args.items, + input_dirs.items, + &man, + has_side_effects, + &tmp_dir_path, + null, + ) catch |err| switch (err) { + else => |e| return e, + error.MakeFailed, error.MakeSkipped => |e| { + try step.setWatchInputsFromManifest(maker, &man); + return e; + }, }; - if (!has_side_effects and try step.cacheHitWatched(maker, &man, progress_node)) { - // Cache hit; skip running command. - const digest = man.hitDigestHex(); - try populateGeneratedStdIo(maker, &conf_run, &digest); - try populateGeneratedPaths(maker, output_placeholders.items, &digest); - step.result_cached = true; - return; - } - - if (!any_dep_files) { - // We already know the final output paths; use them directly. - const digest = if (has_side_effects) man.hash.final() else man.missDigestHex(); - const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest; - try populateGeneratedStdIo(maker, &conf_run, &digest); - try populateGeneratedPathsCreateDirs(arena, run_index, maker, output_dir_path, output_placeholders.items, argv_list.items); - try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, has_side_effects, output_dir_path, null); - if (!has_side_effects) try step.finalizeManifestAndWatch(maker, &man); - return; - } - - // We do not know the final output paths yet; use temporary directory to run the command. - var rand_int: u64 = undefined; - io.random(@ptrCast(&rand_int)); - const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); - - try populateGeneratedPathsCreateDirs(arena, run_index, maker, tmp_dir_path, output_placeholders.items, argv_list.items); - try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, has_side_effects, tmp_dir_path, null); + break :has_side_effects has_side_effects; + }; for (output_placeholders.items) |placeholder| { const arg = placeholder.arg_index.get(conf); @@ -296,10 +657,10 @@ pub fn make( var diagnostic: Cache.DepTokenizer.Token = undefined; man.addInputDepFile(generated_path, &diagnostic) catch |err| switch (err) { error.OutOfMemory, error.Canceled => |e| return e, - error.InvalidDepFile => return step.fail(maker, "failed adding dep file {f} to cache: {f}", .{ + error.InvalidDepFile => return step.fail(maker, "failed adding dep file {qf} to cache: {f}", .{ generated_path, diagnostic, }), - else => |e| return step.fail(maker, "failed adding dep file {f} to cache: {t}", .{ + else => |e| return step.fail(maker, "failed adding dep file {qf} to cache: {t}", .{ generated_path, e, }), }; @@ -307,13 +668,13 @@ pub fn make( var diagnostic: Cache.Manifest.AddDiscoveredDepFileDiagnostic = undefined; man.addDiscoveredDepFile(generated_path, &diagnostic) catch |err| switch (err) { error.OutOfMemory, error.Canceled => |e| return e, - error.InvalidDepFile => return step.fail(maker, "failed adding dep file {f} to cache: {f}", .{ + error.InvalidDepFile => return step.fail(maker, "failed adding dep file {qf} to cache: {f}", .{ generated_path, diagnostic.dep_tokenizer, }), - error.FileSystemFailure => return step.fail(maker, "failed adding a path from dep file {f} to cache: {f}", .{ + error.FileSystemFailure => return step.fail(maker, "failed adding a path from dep file {qf} to cache: {f}", .{ generated_path, diagnostic.add_discovered_path, }), - else => |e| return step.fail(maker, "failed adding dep file {f} to cache: {t}", .{ + else => |e| return step.fail(maker, "failed adding dep file {qf} to cache: {t}", .{ generated_path, e, }), }; @@ -331,7 +692,7 @@ pub fn make( if (any_output) { // Rename into place. - const tmp_path: Path = .{ .root_dir = cache_root, .sub_path = tmp_dir_path }; + const tmp_path: Path = .{ .root_dir = cache_root, .sub_path = &tmp_dir_path }; const dst_path: Path = .{ .root_dir = cache_root, .sub_path = "o" ++ Dir.path.sep_str ++ &digest }; Dir.rename( tmp_path.root_dir.handle, @@ -342,7 +703,7 @@ pub fn make( ) catch |err| switch (err) { error.DirNotEmpty => { dst_path.root_dir.handle.deleteTree(io, dst_path.sub_path) catch |del_err| - return step.fail(maker, "failed to remove tree {f}: {t}", .{ dst_path, del_err }); + return step.fail(maker, "failed to remove tree {qf}: {t}", .{ dst_path, del_err }); Dir.rename( tmp_path.root_dir.handle, @@ -350,17 +711,21 @@ pub fn make( dst_path.root_dir.handle, dst_path.sub_path, io, - ) catch |retry_err| return step.fail(maker, "failed to rename directory {f} to {f}: {t}", .{ + ) catch |retry_err| return step.fail(maker, "failed to rename directory {qf} to {qf}: {t}", .{ tmp_path, dst_path, retry_err, }); }, - else => return step.fail(maker, "failed to rename directory {f} to {f}: {t}", .{ + else => return step.fail(maker, "failed to rename directory {qf} to {qf}: {t}", .{ tmp_path, dst_path, err, }), }; } - if (!has_side_effects) try step.finalizeManifestAndWatch(maker, &man); + if (has_side_effects) { + try step.setWatchInputsFromManifest(maker, &man); + } else { + try step.finalizeManifestAndWatch(maker, &man); + } try populateGeneratedStdIo(maker, &conf_run, &digest); try populateGeneratedPaths(maker, output_placeholders.items, &digest); @@ -386,19 +751,30 @@ fn thirdPartyToggle( enable: ?Configuration.String, disable: ?Configuration.String, ) void { - if (setting) { - if (enable) |string| { - const slice = string.slice(conf); - if (man_hash) |h| h.addBytesZ(slice); - argv_list.appendAssumeCapacity(slice); - } - } else { - if (disable) |string| { - const slice = string.slice(conf); - if (man_hash) |h| h.addBytesZ(slice); - argv_list.appendAssumeCapacity(slice); - } - } + const string = (if (setting) enable else disable) orelse return; + const slice = string.slice(conf); + if (man_hash) |h| h.addBytesZ(slice); + argv_list.appendAssumeCapacity(slice); +} + +fn thirdPartyToggleProtocol( + gpa: Allocator, + man_hash: *Cache.HashHelper, + protocol_args: *std.ArrayList(u8), + conf: *const Configuration, + setting: bool, + enable: ?Configuration.String, + disable: ?Configuration.String, +) Allocator.Error!void { + const string = (if (setting) enable else disable) orelse return; + const slice = string.slice(conf); + + man_hash.addBytesZ(slice); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + slice.len + 1); + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.string)); + protocol_args.appendSliceAssumeCapacity(slice); + protocol_args.appendAssumeCapacity(0); } /// Reads stdout of a Zig test process until a termination condition is reached: @@ -411,7 +787,11 @@ fn waitZigTest( run_index: Configuration.Step.Index, maker: *Maker, child: *process.Child, - progress_node: std.Progress.Node, + tests_prog_node: std.Progress.Node, + test_prog_node: std.Progress.Node, + protocol_args: []const u8, + input_dirs: []const Cache.Path, + man: ?*Cache.Manifest, multi_reader: *Io.File.MultiReader, opt_metadata: *?TestMetadata, results: *Step.TestResults, @@ -431,9 +811,6 @@ fn waitZigTest( const io = graph.io; const step = maker.stepByIndex(run_index); - var sub_prog_node: ?std.Progress.Node = null; - defer if (sub_prog_node) |n| n.end(); - const stdout = multi_reader.reader(0); const stderr = multi_reader.reader(1); @@ -444,9 +821,17 @@ fn waitZigTest( .out = &stdin_writer.interface, }; + if (protocol_args.len > 0) { + try client.serveMessageHeader(.{ + .tag = .args, + .bytes_len = @intCast(protocol_args.len), + }); + try client.out.writeAll(protocol_args); + } + if (opt_metadata.*) |*md| { // Previous unit test process died or was killed; we're continuing where it left off - requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; + requestNextTest(&client, md, test_prog_node) catch |err| return .{ .write_failed = err }; } else { // Running unit tests normally run.fuzz_tests.clearRetainingCapacity(); @@ -492,6 +877,7 @@ fn waitZigTest( var body_r: std.Io.Reader = .fixed(body); switch (header.tag) { + else => {}, // ignore other messages .zig_version => { if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail( maker, @@ -499,6 +885,7 @@ fn waitZigTest( .{ builtin.zig_version_string, body }, ); }, + .error_bundle => step.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body), .test_metadata => { // `metadata` would only be populated if we'd already seen a `test_metadata`, but we // only request it once (and importantly, we don't re-request it if we kill and @@ -516,27 +903,27 @@ fn waitZigTest( const string_bytes = body_r.take(tm_hdr.string_bytes_len) catch unreachable; - progress_node.setEstimatedTotalItems(names.len); + tests_prog_node.setEstimatedTotalItems(names.len); opt_metadata.* = .{ .string_bytes = try arena.dupe(u8, string_bytes), .ns_per_test = try arena.alloc(u64, results.test_count), .names = names, .expected_panic_msgs = expected_panic_msgs, .next_index = 0, - .prog_node = progress_node, }; @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64)); active_test_index = null; last_update = .now(io, .awake); - requestNextTest(&client, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err }; + requestNextTest(&client, &opt_metadata.*.?, test_prog_node) catch |err| return .{ .write_failed = err }; }, .test_started => { active_test_index = opt_metadata.*.?.next_index - 1; last_update = .now(io, .awake); }, .test_results => { + tests_prog_node.completeOne(); const md = &opt_metadata.*.?; const tr_hdr = body_r.takeStruct(std.zig.Server.Message.TestResults, .little) catch unreachable; @@ -558,10 +945,12 @@ fn waitZigTest( const name = md.testName(tr_hdr.index); const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n"); stderr.tossBuffered(); - if (stderr_bytes.len == 0) { - try step.addError(maker, "'{s}' failed without output", .{name}); - } else { + if (stderr_bytes.len > 0) { try step.addError(maker, "'{s}' failed:\n{s}", .{ name, stderr_bytes }); + } else if (step.result_error_bundle.errorMessageCount() > 0) { + try step.addError(maker, "'{s}' errored", .{name}); + } else { + try step.addError(maker, "'{s}' failed without output", .{name}); } } else if (leak_count > 0) { const name = md.testName(tr_hdr.index); @@ -581,9 +970,18 @@ fn waitZigTest( md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds); last_update = now; - requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; + requestNextTest(&client, md, test_prog_node) catch |err| return .{ .write_failed = err }; + }, + .discovered_inputs => while (body_r.takeEnum( + std.zig.Server.Message.InputDir, + .little, + )) |input_dir| try man.?.addDiscoveredPath(.{ .discovered_path = .{ + .unresolved = try input_dirs[@backingInt(input_dir)] + .join(arena, try body_r.takeSentinel(0)), + } }) else |err| switch (err) { + else => |e| return e, + error.EndOfStream => {}, }, - else => {}, // ignore other messages } } } @@ -820,6 +1218,7 @@ const FuzzTestRunner = struct { } switch (header.tag) { + else => {}, // ignore other messages .zig_version => { if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail( maker, @@ -882,7 +1281,6 @@ const FuzzTestRunner = struct { f.pending_broadcasts.appendSliceAssumeCapacity(@ptrCast(&footer)); } }, - else => {}, // ignore other messages } instance.message.clearRetainingCapacity(); @@ -1146,6 +1544,9 @@ fn evalZigTest( maker: *Maker, progress_node: std.Progress.Node, spawn_options: process.SpawnOptions, + protocol_args: []const u8, + input_dirs: []const Cache.Path, + man: ?*Cache.Manifest, fuzz_context: ?FuzzContext, ) !void { if (fuzz_context != null) { @@ -1194,6 +1595,10 @@ fn evalZigTest( maker, &child, progress_node, + spawn_options.progress_node, + protocol_args, + input_dirs, + man, &multi_reader, &test_metadata, &test_results, @@ -1323,7 +1728,6 @@ const TestMetadata = struct { expected_panic_msgs: []const u32, string_bytes: []const u8, next_index: u32, - prog_node: std.Progress.Node, fn toCachedTestMetadata(tm: TestMetadata) CachedTestMetadata { return .{ @@ -1346,16 +1750,14 @@ pub const CachedTestMetadata = struct { } }; -fn requestNextTest(client: *std.zig.Client, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { +fn requestNextTest(client: *std.zig.Client, metadata: *TestMetadata, test_prog_node: std.Progress.Node) !void { while (metadata.next_index < metadata.names.len) { const i = metadata.next_index; metadata.next_index += 1; if (metadata.expected_panic_msgs[i] != 0) continue; - const name = metadata.testName(i); - if (sub_prog_node.*) |n| n.end(); - sub_prog_node.* = metadata.prog_node.start(name, 0); + test_prog_node.setName(metadata.testName(i)); try client.serveRunTest(i); return; @@ -1402,7 +1804,7 @@ fn evalGeneric( var write_buffer: [1024]u8 = undefined; var stdin_writer = child.stdin.?.writerStreaming(io, &write_buffer); _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) { - error.ReadFailed => return step.fail(maker, "failed to read from {f}: {t}", .{ + error.ReadFailed => return step.fail(maker, "failed to read from {qf}: {t}", .{ path, file_reader.err.?, }), error.WriteFailed => return step.fail(maker, "failed to write to stdin: {t}", .{ @@ -1490,6 +1892,7 @@ fn evalGeneric( const IndexedOutput = struct { index: u32, + offset: u32, arg_index: Configuration.Step.Run.Arg.Index, }; @@ -1516,13 +1919,16 @@ pub fn rerunInFuzzMode( var argv_list: std.ArrayList([]const u8) = .empty; defer argv_list.deinit(gpa); - for (conf_run.args.slice) |arg_index| { + for (switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check, .zig_test => conf_run.args.slice, + .protocol => @panic("protocol fuzz unimplemented"), + }) |arg_index| { const arg = arg_index.get(conf); try argv_list.ensureUnusedCapacity(gpa, 1); switch (arg.flags.tag) { .string => { - const prefix = arg.prefix.value.?.slice(conf); - argv_list.appendAssumeCapacity(prefix); + const string = arg.prefix.value.?.slice(conf); + argv_list.appendAssumeCapacity(string); }, .path_file => { const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; @@ -1550,14 +1956,14 @@ pub fn rerunInFuzzMode( result.writer.writeAll(prefix) catch return error.OutOfMemory; const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err| - return step.fail(maker, "unable to open input file {f}: {t}", .{ file_path, err }); + return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err }); defer file.close(io); var file_reader = file.reader(io, &.{}); _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) { error.ReadFailed => switch (file_reader.err.?) { error.Canceled => |e| return e, - else => |e| return step.fail(maker, "failed to read from {f}: {t}", .{ file_path, e }), + else => |e| return step.fail(maker, "failed to read from {qf}: {t}", .{ file_path, e }), }, error.WriteFailed => return error.OutOfMemory, }; @@ -1591,14 +1997,17 @@ pub fn rerunInFuzzMode( .enable_wine => thirdPartyToggle(null, &argv_list, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value), } } + switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check => {}, + .zig_test => { + const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false); - if (conf_run.flags.test_runner_mode) { - const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false); - - try argv_list.ensureUnusedCapacity(gpa, 3); - argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string})); - argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed})); - argv_list.appendAssumeCapacity("--listen=-"); + try argv_list.ensureUnusedCapacity(gpa, 3); + argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string})); + argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed})); + argv_list.appendAssumeCapacity("--listen=-"); + }, + .protocol => unreachable, } step.clearFailedCommand(gpa); @@ -1607,7 +2016,7 @@ pub fn rerunInFuzzMode( var rand_int: u64 = undefined; io.random(@ptrCast(&rand_int)); const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); - try runCommand(arena, run, run_index, maker, prog_node, argv_list.items, has_side_effects, tmp_dir_path, .{ + try runCommand(arena, run, run_index, maker, prog_node, argv_list.items, &.{}, &.{}, &.{}, &.{}, null, has_side_effects, tmp_dir_path, .{ .fuzz = fuzz, }); } @@ -1633,7 +2042,16 @@ fn populateGeneratedPathsCreateDirs( maker: *Maker, output_dir_path: []const u8, output_placeholders: []const IndexedOutput, - argv: [][]const u8, + populate: union(enum) { + argv: [][]const u8, + protocol: struct { + owned_dirs: *std.bit_set.Dynamic, + inherit_dirs: []Io.Dir, + owned_files: *std.bit_set.Dynamic, + inherit_files: []Io.File, + args: []u8, + }, + }, ) !void { const step = maker.stepByIndex(run_index); const conf = &maker.scanned_config.configuration; @@ -1653,16 +2071,52 @@ fn populateGeneratedPathsCreateDirs( const create_path: Path = .{ .root_dir = cache_root, .sub_path = switch (arg.flags.tag) { + else => unreachable, .output_file => Dir.path.dirname(generated_path.sub_path).?, .output_directory => generated_path.sub_path, - else => unreachable, }, }; create_path.root_dir.handle.createDirPath(io, create_path.sub_path) catch |err| - return step.fail(maker, "unable to make path {f}: {t}", .{ create_path, err }); + return step.fail(maker, "unable to make path {qf}: {t}", .{ create_path, err }); - const arg_output_path = try convertPathArg(arena, run_index, maker, generated_path, arg.flags.make_absolute); - argv[placeholder.index] = try mem.concat(arena, u8, &.{ prefix, arg_output_path, suffix }); + switch (populate) { + .argv => |argv| { + const arg_output_path = try convertPathArg(arena, run_index, maker, generated_path, arg.flags.make_absolute); + argv[placeholder.index] = try mem.concat(arena, u8, &.{ prefix, arg_output_path, suffix }); + }, + .protocol => |protocol| switch (arg.flags.tag) { + else => unreachable, + .output_file => { + const file = generated_path.root_dir.handle.createFile( + io, + generated_path.sub_path, + .{ .read = true }, + ) catch |err| return step.fail(maker, "unable to create output file {qf}: {t}", .{ + generated_path, err, + }); + protocol.inherit_files[placeholder.index] = file; + protocol.owned_files.set(placeholder.index); + const file_handle: *align(1) Io.File.Handle = + @ptrCast(protocol.args[placeholder.offset..][0..@sizeOf(Io.File.Handle)]); + file_handle.* = file.handle; + }, + .output_directory => { + assert(generated_path.sub_path.len > 0); + const dir = generated_path.root_dir.handle.openDir( + io, + generated_path.sub_path, + .{ .iterate = true }, + ) catch |err| return step.fail(maker, "unable to open output dir {qf}: {t}", .{ + generated_path, err, + }); + protocol.inherit_dirs[placeholder.index] = dir; + protocol.owned_dirs.set(placeholder.index); + const dir_handle: *align(1) Io.Dir.Handle = + @ptrCast(protocol.args[placeholder.offset..][0..@sizeOf(Io.Dir.Handle)]); + dir_handle.* = dir.handle; + }, + }, + } } } @@ -1711,6 +2165,11 @@ fn runCommand( maker: *Maker, progress_node: std.Progress.Node, argv: []const []const u8, + inherit_dirs: []const Io.Dir, + inherit_files: []const Io.File, + protocol_args: []const u8, + input_dirs: []const Cache.Path, + man: ?*Cache.Manifest, has_side_effects: bool, output_dir_path: []const u8, fuzz_context: ?FuzzContext, @@ -1777,10 +2236,16 @@ fn runCommand( progress_node, argv, &environ_map, + inherit_dirs, + inherit_files, + protocol_args, + input_dirs, + man, has_side_effects, fuzz_context, ) catch |err| term: { switch (err) { + else => {}, error.InvalidExe, // cpu arch mismatch error.FileNotFound, // can happen with a wrong dynamic linker path => interpret: { @@ -1794,24 +2259,21 @@ fn runCommand( const root_module = producer.root_module.get(conf); const root_module_target = root_module.resolved_target.get(conf).?.result.get(conf); const root_target = root_module_target.unwrapTarget(conf); - const link_libc = maker.stepByIndex(producer_index).extended.compile.is_linking_libc; + const config = maker.stepByIndex(producer_index).extended.compile.config.?; const host: std.Target = std.zig.system.resolveTargetQuery(io, .{}) catch |he| switch (he) { error.Canceled => |e| return e, else => builtin.target, }; - const need_cross_libc = link_libc and root_target.os.tag == .linux and - switch (producer.flags2.linkage) { - .static => false, - .dynamic => true, - .default => root_target.isGnuLibC(), - }; + const need_cross_libc = root_target.os.tag == .linux and + config.flags.link_libc and config.flags.link_mode == .dynamic; switch (std.zig.system.getExternalExecutor(io, &root_target, .{ .host_cpu_arch = host.cpu.arch, .host_os_tag = host.os.tag, .qemu_fixes_dl = need_cross_libc and graph.libc_runtimes_dir != null, - .link_libc = link_libc, + .link_mode = config.flags.link_mode, + .link_libc = config.flags.link_libc, })) { .native, .rosetta => { if (allow_skip) return error.MakeSkipped; @@ -1874,7 +2336,7 @@ fn runCommand( interp_argv.appendAssumeCapacity(bin_name); interp_argv.appendAssumeCapacity("--dir=."); for (conf_run.preopens.slice) |preopen| { - const path = try maker.resolveLazyPath(arena, preopen.path.get(conf), run_index); + const path = try maker.resolveLazyPathIndex(arena, preopen.path, run_index); path.root_dir.handle.createDirPath(io, path.subPathOrDot()) catch |e| return step.fail(maker, "failed creating directory {f}: {t}", .{ path, e }); interp_argv.appendAssumeCapacity(try arena.print("--dir={f}::{s}", .{ path, preopen.name.slice(conf) })); @@ -1928,6 +2390,11 @@ fn runCommand( progress_node, interp_argv.items, &environ_map, + inherit_dirs, + inherit_files, + protocol_args, + input_dirs, + man, has_side_effects, fuzz_context, ) catch |e| { @@ -1937,21 +2404,21 @@ fn runCommand( }; }, error.MakeFailed, error.OutOfMemory, error.Canceled => |e| return e, - else => {}, } return step.fail(maker, "failed to spawn and capture stdio from {s}: {t}", .{ argv[0], err }); }; - const generic_result = opt_generic_result orelse { - assert(conf_run.flags.stdio == .zig_test); - // Specific errors have already been reported, and test results are populated. All we need - // to do is report step failure if any test failed. - if (!step.test_results.isSuccess()) return error.MakeFailed; - return; + const generic_result = switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check => opt_generic_result.?, + .zig_test, .protocol => { + assert(opt_generic_result == null); + // Specific errors have already been reported, and test results are populated. All we need + // to do is report step failure if any test failed. + if (!step.test_results.isSuccess()) return error.MakeFailed; + return; + }, }; - assert(fuzz_context == null); - assert(conf_run.flags.stdio != .zig_test); // Capture stdout and stderr to GeneratedFile objects. const Stream = struct { @@ -1978,7 +2445,7 @@ fn runCommand( const sub_path_parent = output_path.dirname().?; sub_path_parent.root_dir.handle.createDirPath(io, sub_path_parent.sub_path) catch |err| - return step.fail(maker, "unable to make path {f}: {t}", .{ sub_path_parent, err }); + return step.fail(maker, "unable to make path {qf}: {t}", .{ sub_path_parent, err }); const data = switch (stream.trim_whitespace) { .none => stream.bytes.?, @@ -1989,12 +2456,12 @@ fn runCommand( output_path.root_dir.handle.writeFile(io, .{ .sub_path = output_path.sub_path, .data = data, - }) catch |err| return step.fail(maker, "unable to write file {f}: {t}", .{ output_path, err }); + }) catch |err| return step.fail(maker, "unable to write file {qf}: {t}", .{ output_path, err }); } } switch (conf_run.flags.stdio) { - .zig_test => unreachable, + .zig_test, .protocol => unreachable, .check => { if (conf_run.expect_stderr_exact.value) |bytes| { const expected_bytes = bytes.slice(conf); @@ -2186,6 +2653,11 @@ fn spawnChildAndCollect( progress_node: std.Progress.Node, argv: []const []const u8, environ_map: *EnvMap, + inherit_dirs: []const Io.Dir, + inherit_files: []const Io.File, + protocol_args: []const u8, + input_dirs: []const Cache.Path, + man: ?*Cache.Manifest, has_side_effects: bool, fuzz_context: ?FuzzContext, ) !?EvalGenericResult { @@ -2199,7 +2671,10 @@ fn spawnChildAndCollect( if (fuzz_context != null) { assert(!has_side_effects); - assert(conf_run.flags.stdio == .zig_test); + switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check => unreachable, + .zig_test, .protocol => {}, + } } const child_cwd: process.Child.Cwd = if (conf_run.cwd.value) |lazy_cwd| @@ -2230,68 +2705,88 @@ fn spawnChildAndCollect( .cwd = child_cwd, .environ_map = environ_map, .request_resource_usage_statistics = true, - .stdin = if (conf_run.stdin.u != .none) s: { + .stdin = if (conf_run.stdin.u != .none) stdin: { assert(conf_run.flags.stdio != .inherit); - break :s .pipe; + break :stdin .pipe; } else switch (conf_run.flags.stdio) { .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore, .inherit => .inherit, .check => .ignore, - .zig_test => .pipe, + .zig_test, .protocol => .pipe, }, .stdout = if (conf_run.captured_stdout.value != null) .pipe else switch (conf_run.flags.stdio) { .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore, .inherit => .inherit, .check => if (checksContainStdout(&conf_run)) .pipe else .ignore, - .zig_test => .pipe, + .zig_test, .protocol => .pipe, }, .stderr = if (conf_run.captured_stderr.value != null) .pipe else switch (conf_run.flags.stdio) { .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .pipe, .inherit => if (maker.protocol_server == null) .inherit else .pipe, .check => .pipe, - .zig_test => .pipe, + .zig_test, .protocol => .pipe, }, + .inherit_dirs = inherit_dirs, + .inherit_files = inherit_files, }; if (maker.protocol_server != null) { if (spawn_options.stdin == .inherit) { - return step.fail(maker, "Cannot inherit stdin when running through over the build system protocol", .{}); + return step.fail(maker, "Cannot inherit stdin when running over the build system protocol", .{}); } if (spawn_options.stdout == .inherit) { - return step.fail(maker, "Cannot inherit stdout when running through over the build system protocol", .{}); + return step.fail(maker, "Cannot inherit stdout when running over the build system protocol", .{}); } assert(spawn_options.stderr != .inherit); } - if (conf_run.flags.stdio == .zig_test) { - try setColorEnvironmentVariables(&conf_run, environ_map, graph.stderr_mode.?); - const started: Io.Clock.Timestamp = .now(io, .awake); - const result = evalZigTest(run, run_index, maker, progress_node, spawn_options, fuzz_context) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| e, - }; - step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); - try result; - return null; - } else { - const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit; - if (!conf_run.flags.disable_zig_progress and !inherit) { - spawn_options.progress_node = progress_node; - } - const terminal_mode: Io.Terminal.Mode = if (inherit) m: { - const stderr = try io.lockStderr(&.{}, graph.stderr_mode); - break :m stderr.terminal_mode; - } else .no_color; - defer if (inherit) io.unlockStderr(); - try setColorEnvironmentVariables(&conf_run, environ_map, terminal_mode); + switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check => { + const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit; + if (!conf_run.flags.disable_zig_progress and !inherit) { + spawn_options.progress_node = progress_node; + } + const terminal_mode: Io.Terminal.Mode = if (inherit) m: { + const stderr = try io.lockStderr(&.{}, graph.stderr_mode); + break :m stderr.terminal_mode; + } else .no_color; + defer if (inherit) io.unlockStderr(); + try setColorEnvironmentVariables(&conf_run, environ_map, terminal_mode); - const started: Io.Clock.Timestamp = .now(io, .awake); - const result = evalGeneric(arena, run_index, maker, spawn_options) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| e, - }; - step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); - return try result; + const started: Io.Clock.Timestamp = .now(io, .awake); + const result = evalGeneric(arena, run_index, maker, spawn_options) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| e, + }; + step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); + return try result; + }, + .zig_test, .protocol => |stdio| { + spawn_options.progress_node = if (conf_run.flags.disable_zig_progress or fuzz_context != null) + .none + else + progress_node.start(@tagName(stdio), 0); + defer spawn_options.progress_node.end(); + try setColorEnvironmentVariables(&conf_run, environ_map, graph.stderr_mode.?); + const started: Io.Clock.Timestamp = .now(io, .awake); + const result = evalZigTest( + run, + run_index, + maker, + progress_node, + spawn_options, + protocol_args, + input_dirs, + man, + fuzz_context, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| e, + }; + step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); + try result; + return null; + }, } } @@ -2341,7 +2836,7 @@ fn setColorEnvironmentVariables( .auto => { const capture_stderr = conf_run.captured_stderr.value != null or switch (conf_run.flags.stdio) { .check => checksContainStderr(conf_run), - .infer_from_args, .inherit, .zig_test => false, + .infer_from_args, .inherit, .zig_test, .protocol => false, }; if (capture_stderr) { continue :color .disable; diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index 7ba7f7445cdb930783a3aa2bac9307b0367c4b6d..1331c68aa8ae193b6bfab3e20c3ce88228e3a525 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -790,8 +790,7 @@ fn cliDiagnosticsToErrorBundle( ) !ErrorBundle { @branchHint(.cold); - var bundle: ErrorBundle.Wip = undefined; - try bundle.init(gpa); + var bundle: ErrorBundle.Wip = try .init(gpa); errdefer bundle.deinit(); try bundle.addRootErrorMessage(.{ @@ -837,8 +836,7 @@ fn diagnosticsToErrorBundle( ) !ErrorBundle { @branchHint(.cold); - var bundle: ErrorBundle.Wip = undefined; - try bundle.init(gpa); + var bundle: ErrorBundle.Wip = try .init(gpa); errdefer bundle.deinit(); var msg_buf: std.Io.Writer.Allocating = .init(gpa); @@ -919,8 +917,7 @@ fn diagnosticsToErrorBundle( fn errorStringToErrorBundle(allocator: Allocator, comptime format: []const u8, args: anytype) !ErrorBundle { @branchHint(.cold); - var bundle: ErrorBundle.Wip = undefined; - try bundle.init(allocator); + var bundle: ErrorBundle.Wip = try .init(allocator); errdefer bundle.deinit(); try bundle.addRootErrorMessage(.{ .msg = try bundle.printString(format, args), diff --git a/lib/compiler/util.zig b/lib/compiler/util.zig index 05570abc3bfd9cfc1c9c3afaf602ee0b615bb78b..9e17e996a47fe572f121788a7c0c6f4868e8192f 100644 --- a/lib/compiler/util.zig +++ b/lib/compiler/util.zig @@ -10,8 +10,7 @@ pub fn aroDiagnosticsToErrorBundle( ) !ErrorBundle { @branchHint(.cold); - var bundle: ErrorBundle.Wip = undefined; - try bundle.init(gpa); + var bundle: ErrorBundle.Wip = try .init(gpa); errdefer bundle.deinit(); if (fail_msg) |msg| { diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 47993f66d4edb8905c6c497288825c103e80ba9a..36b48d62cf8075024dddc8dc21b339b929d34c5e 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -768,9 +768,8 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run { const run_step = Step.Run.create(b, step_name); run_step.producer = exe; + run_step.addArtifactArg(exe); if (exe.kind == .@"test") { - run_step.addArtifactArg(exe); - const test_server_mode: bool = s: { if (exe.test_runner) |r| break :s r.mode == .server; if (exe.use_llvm == false) { @@ -802,8 +801,6 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run { // communicate failure via its exit code. run_step.expectExitCode(0); } - } else { - run_step.addArtifactArg(exe); } return run_step; @@ -2696,12 +2693,15 @@ fn validateConfigureDependency(lazy_path: LazyPath) void { .src_path, .cwd_relative, .dependency => {}, // OK .generated => @panic("configure phase cannot depend on files generated during make phase"), .relative => |relative| switch (relative.base) { - .cwd, .build_root, .local_cache, .global_cache, .zig_exe, .zig_lib => {}, // OK + .cwd, .build_root, .local_cache, .global_cache, .zig_lib => {}, // OK + .zig_exe => if (relative.sub_path.len > 0) @panic("file base cannot have a sub path"), .install_prefix, .install_lib, .install_bin, .install_include, => @panic("configure phase cannot depend on files installed during make phase"), + .libc_runtimes, + => @panic("configure phase cannot depend on directory known only during make phase"), }, } } diff --git a/lib/std/Build/Configuration.zig b/lib/std/Build/Configuration.zig index efcd9d86ed32f16d6cb1b24427f098fbcbf94747..53bd11500b0d70d4448bd461cfcd924fec6482a2 100644 --- a/lib/std/Build/Configuration.zig +++ b/lib/std/Build/Configuration.zig @@ -665,7 +665,7 @@ pub const Step = extern struct { pub const Tag = enum(u2) { none, bytes, lazy_path }; }; pub const TrimWhitespace = enum(u2) { none, all, leading, trailing }; - pub const StdIo = enum(u2) { infer_from_args, inherit, check, zig_test }; + pub const StdIo = enum(u3) { infer_from_args, inherit, check, zig_test, protocol }; pub const ExpectTermStatus = enum(u2) { exited, signal, stopped, unknown }; @@ -675,7 +675,6 @@ pub const Step = extern struct { skip_foreign_checks: bool, failing_to_execute_foreign_is_an_error: bool, has_side_effects: bool, - test_runner_mode: bool, color: Color, stdin: StdIn.Tag, stdio: StdIo, @@ -1576,6 +1575,7 @@ pub const LazyPath = union(@This().Tag) { install_lib, install_bin, install_include, + libc_runtimes, }; }; }; diff --git a/lib/std/Build/Serialize.zig b/lib/std/Build/Serialize.zig index eae93e66e46515fc5d5f3e33634966cc6cc9bbaf..802bf4cbcff33db51ca5c406828e5325bb707895 100644 --- a/lib/std/Build/Serialize.zig +++ b/lib/std/Build/Serialize.zig @@ -462,13 +462,13 @@ pub fn write(b: *std.Build, wc: *Configuration.Wip, writer: *std.Io.Writer) !voi .skip_foreign_checks = run.skip_foreign_checks, .failing_to_execute_foreign_is_an_error = run.failing_to_execute_foreign_is_an_error, .has_side_effects = run.has_side_effects, - .test_runner_mode = run.test_runner_mode, .color = run.color, .stdio = switch (run.stdio) { .infer_from_args => .infer_from_args, .inherit => .inherit, .check => .check, .zig_test => .zig_test, + .protocol => .protocol, }, .stdin = switch (run.stdin) { .none => .none, diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 78c1e3343c23acec72985f2b9d357e52dc3b977f..a4d6987126ac87113fb73ce63dca3b75c89ffe9b 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -95,7 +95,7 @@ pub fn init(options: StepOptions) Step { return .{ .tag = options.tag, - .name = arena.dupe(u8, options.name) catch @panic("OOM"), + .name = options.owner.graph.dupeString(options.name), .owner = options.owner, .dependencies = .empty, .max_rss = options.max_rss, diff --git a/lib/std/Build/Step/CheckFile.zig b/lib/std/Build/Step/CheckFile.zig index 21ebbd4621c51319de1cc69579c0c59a4ec1df0d..44c0494c1e544eb9a92c36bdadfc06b9a75ed45f 100644 --- a/lib/std/Build/Step/CheckFile.zig +++ b/lib/std/Build/Step/CheckFile.zig @@ -41,5 +41,5 @@ pub fn create(owner: *std.Build, file: std.Build.LazyPath, options: Options) *Ch } pub fn setName(check_file: *CheckFile, name: []const u8) void { - check_file.step.name = name; + check_file.step.name = check_file.step.owner.graph.dupeString(name); } diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 7d271e10e1461606574b96a6966332975b39b706..8662d5258a3553e349e0f5682c733f2b4678e8c0 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -92,7 +92,6 @@ captured_stdout: ?*CapturedStdIo, captured_stderr: ?*CapturedStdIo, has_side_effects: bool, -test_runner_mode: bool = false, /// If this Run step was produced by a Compile step, it is tracked here. producer: ?*Step.Compile, @@ -127,7 +126,10 @@ pub const StdIo = union(enum) { check: std.ArrayList(Check), /// This Run step is running a zig unit test binary and will communicate /// extra metadata over the IPC protocol. + /// Deprecated, use `.protocol`. zig_test, + /// This Run step is running a binary that will communicate over the IPC protocol. + protocol, pub const Check = union(enum) { expect_stderr_exact: []const u8, @@ -239,14 +241,17 @@ pub fn create(owner: *std.Build, name: []const u8) *Run { } pub fn setName(run: *Run, name: []const u8) void { - run.step.name = name; + run.step.name = run.step.owner.graph.dupeString(name); run.rename_step_with_output_arg = false; } +/// Deprecated, use `enableProtocolMode`. pub fn enableTestRunnerMode(run: *Run) void { - if (run.test_runner_mode) return; run.stdio = .zig_test; - run.test_runner_mode = true; +} + +pub fn enableProtocolMode(run: *Run) void { + run.stdio = .protocol; } pub const ArgOptions = struct { @@ -750,8 +755,10 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void { } pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath { - assert(run.stdio != .inherit); - assert(run.stdio != .zig_test); + switch (run.stdio) { + .infer_from_args, .check => {}, + .inherit, .zig_test, .protocol => unreachable, + } const b = run.step.owner; const graph = b.graph; @@ -771,8 +778,10 @@ pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPa } pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath { - assert(run.stdio != .inherit); - assert(run.stdio != .zig_test); + switch (run.stdio) { + .infer_from_args, .check => {}, + .inherit, .zig_test, .protocol => unreachable, + } const b = run.step.owner; const graph = b.graph; diff --git a/lib/std/Io.zig b/lib/std/Io.zig index b1fefc704ab6a21cfa03af9c6fefcb235b22db7b..1f26d2bf7ea49c898b81d7921e5520a6d6d033e0 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -213,13 +213,13 @@ pub const VTable = struct { processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void, processSetCurrentPath: *const fn (?*anyopaque, []const u8) std.process.SetCurrentPathError!void, processReplace: *const fn (?*anyopaque, std.process.ReplaceOptions) std.process.ReplaceError, - processReplacePath: *const fn (?*anyopaque, Dir, std.process.ReplaceOptions) std.process.ReplaceError, processSpawn: *const fn (?*anyopaque, std.process.SpawnOptions) std.process.SpawnError!std.process.Child, - processSpawnPath: *const fn (?*anyopaque, Dir, std.process.SpawnOptions) std.process.SpawnError!std.process.Child, childWait: *const fn (?*anyopaque, *std.process.Child) std.process.Child.WaitError!std.process.Child.Term, childKill: *const fn (?*anyopaque, *std.process.Child) void, progressParentFile: *const fn (?*anyopaque) std.Progress.ParentFileError!File, + inheritParentDir: *const fn (?*anyopaque, handle: Dir.Handle) InheritParentHandleError!Dir, + inheritParentFile: *const fn (?*anyopaque, handle: File.Handle, flags: File.Flags) InheritParentHandleError!File, now: *const fn (?*anyopaque, Clock) Timestamp, clockResolution: *const fn (?*anyopaque, Clock) Clock.ResolutionError!Duration, @@ -827,6 +827,10 @@ pub const UnexpectedError = error{ Unexpected, }; +pub const InheritParentHandleError = error{ + UnsupportedOperation, +} || Cancelable || UnexpectedError; + pub const Clock = enum { /// A settable system-wide clock that measures real (i.e. wall-clock) /// time. This clock is affected by discontinuous jumps in the system @@ -2797,13 +2801,13 @@ pub const failing: std.Io = .{ .processSetCurrentDir = failingProcessSetCurrentDir, .processSetCurrentPath = failingProcessSetCurrentPath, .processReplace = failingProcessReplace, - .processReplacePath = failingProcessReplacePath, .processSpawn = failingProcessSpawn, - .processSpawnPath = failingProcessSpawnPath, .childWait = unreachableChildWait, .childKill = unreachableChildKill, .progressParentFile = failingProgressParentFile, + .inheritParentDir = failingInheritParentDir, + .inheritParentFile = failingInheritParentFile, .random = noRandom, .randomSecure = failingRandomSecure, @@ -3451,26 +3455,12 @@ pub fn failingProcessReplace(userdata: ?*anyopaque, options: std.process.Replace return error.OperationUnsupported; } -pub fn failingProcessReplacePath(userdata: ?*anyopaque, dir: Dir, options: std.process.ReplaceOptions) std.process.ReplaceError { - _ = userdata; - _ = dir; - _ = options; - return error.OperationUnsupported; -} - pub fn failingProcessSpawn(userdata: ?*anyopaque, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child { _ = userdata; _ = options; return error.OperationUnsupported; } -pub fn failingProcessSpawnPath(userdata: ?*anyopaque, dir: Dir, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child { - _ = userdata; - _ = dir; - _ = options; - return error.OperationUnsupported; -} - pub fn unreachableChildWait(userdata: ?*anyopaque, child: *std.process.Child) std.process.Child.WaitError!std.process.Child.Term { _ = userdata; _ = child; @@ -3488,6 +3478,19 @@ pub fn failingProgressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileE return error.UnsupportedOperation; } +pub fn failingInheritParentDir(userdata: ?*anyopaque, handle: Dir.Handle) InheritParentHandleError!Dir { + _ = userdata; + _ = handle; + return error.UnsupportedOperation; +} + +pub fn failingInheritParentFile(userdata: ?*anyopaque, handle: File.Handle, flags: File.Flags) InheritParentHandleError!File { + _ = userdata; + _ = handle; + _ = flags; + return error.UnsupportedOperation; +} + pub fn noRandom(userdata: ?*anyopaque, buffer: []u8) void { _ = userdata; @memset(buffer, 0); diff --git a/lib/std/Io/Reader/Limited.zig b/lib/std/Io/Reader/Limited.zig index bc372de06b1e023638f648c8cbd7533f3fd7dec3..fc1e219176dfa8b5ffb37ba6d2759a34f3d2f69b 100644 --- a/lib/std/Io/Reader/Limited.zig +++ b/lib/std/Io/Reader/Limited.zig @@ -28,8 +28,7 @@ pub fn init(reader: *Reader, limit: Limit, buffer: []u8) Limited { fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize { const l: *Limited = @fieldParentPtr("interface", r); if (l.remaining == .nothing) return error.EndOfStream; - const combined_limit = limit.min(l.remaining); - const n = try l.unlimited.stream(w, combined_limit); + const n = try l.unlimited.stream(w, limit.min(l.remaining)); l.remaining = l.remaining.subtract(n).?; return n; } @@ -53,8 +52,7 @@ test stream { fn discard(r: *Reader, limit: Limit) Reader.Error!usize { const l: *Limited = @fieldParentPtr("interface", r); if (l.remaining == .nothing) return error.EndOfStream; - const combined_limit = limit.min(l.remaining); - const n = try l.unlimited.discard(combined_limit); + const n = try l.unlimited.discard(limit.min(l.remaining)); l.remaining = l.remaining.subtract(n).?; return n; } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 3b688ecc93c36631fa0c0bad423155abb8020eeb..ab888c5e6ee58558a7aa65d1cac8bb263b8a4d2a 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -85,6 +85,8 @@ csprng: Csprng = .uninitialized, system_basic_information: SystemBasicInformation = .{}, +process_spawn_mutex: if (is_windows) Io.Mutex else void = if (is_windows) .init else {}, + const SystemBasicInformation = if (!is_windows) struct {} else struct { buffer: windows.SYSTEM.BASIC_INFORMATION = undefined, initialized: std.atomic.Value(bool) = .{ .raw = false }, @@ -1567,6 +1569,10 @@ const poll_buffer_len = 64; pub const default_PATH = "/usr/local/bin:/bin:/usr/bin"; /// There are multiple kernel bugs being worked around with retries. const max_windows_kernel_bug_retries = 13; +/// Similar in nature to `max_windows_kernel_bug_retries`, but much more likely to +/// be a real error that won't go away after a retry, so the number of retries +/// is lower. +const max_windows_rename_retries = 3; comptime { 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; pub const global_single_threaded: *Threaded = &global_single_threaded_instance; pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { - mutexLock(&t.mutex); + mutexLockUncancelable(&t.mutex); defer mutexUnlock(&t.mutex); t.async_limit = new_limit; } @@ -1727,7 +1733,7 @@ pub fn deinit(t: *Threaded) void { fn join(t: *Threaded) void { if (builtin.single_threaded) return; { - mutexLock(&t.mutex); + mutexLockUncancelable(&t.mutex); defer mutexUnlock(&t.mutex); t.join_requested = true; } @@ -1789,7 +1795,7 @@ fn worker(t: *Threaded) void { defer t.wait_group.finish(); - mutexLock(&t.mutex); + mutexLockUncancelable(&t.mutex); defer mutexUnlock(&t.mutex); while (true) { @@ -1798,7 +1804,7 @@ fn worker(t: *Threaded) void { thread.cancel_protection = .unblocked; const runnable: *Runnable = @fieldParentPtr("node", runnable_node); runnable.startFn(runnable, &thread, t); - mutexLock(&t.mutex); + mutexLockUncancelable(&t.mutex); t.busy_count -= 1; } if (t.join_requested) break; @@ -1901,13 +1907,13 @@ pub fn io(t: *Threaded) Io { .processSetCurrentDir = processSetCurrentDir, .processSetCurrentPath = processSetCurrentPath, .processReplace = processReplace, - .processReplacePath = processReplacePath, .processSpawn = processSpawn, - .processSpawnPath = processSpawnPath, .childWait = childWait, .childKill = childKill, .progressParentFile = progressParentFile, + .inheritParentDir = inheritParentDir, + .inheritParentFile = inheritParentFile, .now = now, .clockResolution = clockResolution, @@ -1975,6 +1981,10 @@ const have_copy_file_range = switch (native_os) { .linux, .freebsd => true, else => false, }; +const have_fcntl = switch (native_os) { + .linux, .dragonfly, .freebsd, .netbsd, .openbsd, .illumos, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => true, + else => false, +}; const have_fcopyfile = is_darwin; const have_fchmodat2 = native_os == .linux and (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse true) and @@ -2084,7 +2094,7 @@ fn async( }, }; - mutexLock(&t.mutex); + mutexLockUncancelable(&t.mutex); const busy_count = t.busy_count; @@ -2136,7 +2146,7 @@ fn concurrent( }; errdefer future.destroy(gpa); - mutexLock(&t.mutex); + mutexLockUncancelable(&t.mutex); defer mutexUnlock(&t.mutex); const busy_count = t.busy_count; @@ -2181,7 +2191,7 @@ fn groupAsync( error.OutOfMemory => return groupAsyncEager(start, context.ptr), }; - mutexLock(&t.mutex); + mutexLockUncancelable(&t.mutex); const busy_count = t.busy_count; @@ -2244,7 +2254,7 @@ fn groupConcurrent( }; errdefer task.destroy(gpa); - mutexLock(&t.mutex); + mutexLockUncancelable(&t.mutex); defer mutexUnlock(&t.mutex); const busy_count = t.busy_count; @@ -4114,7 +4124,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { fn systemBasicInformation(t: *Threaded) ?*const windows.SYSTEM.BASIC_INFORMATION { if (!t.system_basic_information.initialized.load(.acquire)) { - mutexLock(&t.mutex); + mutexLockUncancelable(&t.mutex); defer mutexUnlock(&t.mutex); switch (windows.ntdll.NtQuerySystemInformation( @@ -4462,7 +4472,7 @@ fn dirCreateFilePosix( var fl_flags: usize = fl: { const syscall: Syscall = try .start(); while (true) { - const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); + const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(u32, 0)); switch (posix.errno(rc)) { .SUCCESS => { syscall.finish(); @@ -5058,7 +5068,7 @@ fn dirOpenFilePosix( var fl_flags: usize = fl: { const syscall: Syscall = try .start(); while (true) { - const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); + const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(u32, 0)); switch (posix.errno(rc)) { .SUCCESS => { syscall.finish(); @@ -7030,6 +7040,11 @@ fn fileRealPathPosix(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.R } fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize { + if (fd == posix.AT.FDCWD) return processCurrentPathPosix(out_buffer) catch |err| switch (err) { + else => |e| return e, + error.CurrentDirUnlinked => return error.FileNotFound, + error.PathExceedsLimit => return error.NameTooLong, + }; switch (native_os) { .dragonfly, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { var sufficient_buffer: [posix.PATH_MAX]u8 = undefined; @@ -7674,6 +7689,8 @@ fn dirRenameWindowsInner( defer w.CloseHandle(src_fd); var rc: w.NTSTATUS = undefined; + var attempt: u5 = 0; + // FileRenameInformationEx has varying levels of support: // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1 // (INVALID_INFO_CLASS is returned if not supported) @@ -7697,24 +7714,43 @@ fn dirRenameWindowsInner( }); var io_status_block: w.IO_STATUS_BLOCK = undefined; const rename_info_buf = rename_info.toBuffer(); - rc = w.ntdll.NtSetInformationFile( - src_fd, - &io_status_block, - rename_info_buf.ptr, - @intCast(rename_info_buf.len), - .RenameEx, - ); - switch (rc) { - .SUCCESS => return, - // The filesystem does not support FileDispositionInformationEx - .INVALID_PARAMETER, - // The operating system does not support FileDispositionInformationEx - .INVALID_INFO_CLASS, - // The operating system does not support one of the flags - .NOT_SUPPORTED, - => break :need_fallback true, - // For all other statuses, fall down to the switch below to handle them. - else => break :need_fallback false, + var syscall: Syscall = try .start(); + while (true) { + rc = w.ntdll.NtSetInformationFile( + src_fd, + &io_status_block, + rename_info_buf.ptr, + @intCast(rename_info_buf.len), + .RenameEx, + ); + switch (rc) { + // For all other statuses, fall down to the switch below to handle them. + else => { + syscall.finish(); + break :need_fallback false; + }, + .CANCELLED => try syscall.checkCancel(), + // The filesystem does not support FileDispositionInformationEx + .INVALID_PARAMETER, + // The operating system does not support FileDispositionInformationEx + .INVALID_INFO_CLASS, + // The operating system does not support one of the flags + .NOT_SUPPORTED, + => { + syscall.finish(); + break :need_fallback true; + }, + .ACCESS_DENIED => { + syscall.finish(); + if (max_windows_rename_retries - attempt == 0) break :need_fallback false; + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), + .clock = .awake, + } }); + attempt += 1; + syscall = try .start(); + }, + } } }; @@ -7726,13 +7762,33 @@ fn dirRenameWindowsInner( }); var io_status_block: w.IO_STATUS_BLOCK = undefined; const rename_info_buf = rename_info.toBuffer(); - rc = w.ntdll.NtSetInformationFile( - src_fd, - &io_status_block, - rename_info_buf.ptr, - @intCast(rename_info_buf.len), - .Rename, - ); + var syscall: Syscall = try .start(); + while (true) { + rc = w.ntdll.NtSetInformationFile( + src_fd, + &io_status_block, + rename_info_buf.ptr, + @intCast(rename_info_buf.len), + .Rename, + ); + switch (rc) { + else => { + syscall.finish(); + break; + }, + .CANCELLED => try syscall.checkCancel(), + .ACCESS_DENIED => { + syscall.finish(); + if (max_windows_rename_retries - attempt == 0) break; + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), + .clock = .awake, + } }); + attempt += 1; + syscall = try .start(); + }, + } + } } switch (rc) { @@ -10002,7 +10058,7 @@ fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { .PENDING => unreachable, .CANCELLED => unreachable, .SUCCESS => return io_status_block.Information, - .END_OF_FILE, .PIPE_BROKEN => return error.EndOfStream, + .END_OF_FILE, .PIPE_BROKEN, .PIPE_CLOSING => return error.EndOfStream, .INVALID_HANDLE => return error.NotOpenForReading, .INVALID_DEVICE_REQUEST => return error.IsDir, .FILE_LOCK_CONFLICT => return error.LockViolation, @@ -10019,7 +10075,7 @@ fn ntWriteFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { .INVALID_USER_BUFFER => return error.SystemResources, .NO_MEMORY => return error.SystemResources, .QUOTA_EXCEEDED => return error.SystemResources, - .PIPE_BROKEN => return error.BrokenPipe, + .PIPE_BROKEN, .PIPE_CLOSING => return error.BrokenPipe, .INVALID_HANDLE => return error.NotOpenForWriting, .FILE_LOCK_CONFLICT => return error.LockViolation, .ACCESS_DENIED => return error.AccessDenied, @@ -14082,7 +14138,7 @@ fn lockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancel const current_thread_id = Thread.currentId(); if (@atomicLoad(std.Thread.Id, &t.stderr_mutex_locker, .unordered) != current_thread_id) { - mutexLock(&t.stderr_mutex); + try mutexLock(&t.stderr_mutex); assert(t.stderr_mutex_lock_count == 0); @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, current_thread_id, .unordered); } @@ -14145,6 +14201,7 @@ fn unlockStderr(userdata: ?*anyopaque) void { fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; + if (is_windows) { var wtf16le_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined; 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 end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable; } return end_index; - } else if (native_os == .wasi and !builtin.link_libc) { + } + + if (native_os == .wasi and !builtin.link_libc) { if (buffer.len == 0) return error.NameTooLong; buffer[0] = '.'; return 1; } + return processCurrentPathPosix(buffer); +} + +fn processCurrentPathPosix(buffer: []u8) process.CurrentPathError!usize { const err: posix.E = if (builtin.link_libc) err: { const c_err = if (std.c.getcwd(buffer.ptr, buffer.len)) |_| 0 else std.c._errno().*; break :err @fromBackingInt(@intCast(c_err)); @@ -15101,7 +15164,7 @@ const WindowsEnvironStrings = struct { }; fn scanEnviron(t: *Threaded) void { - mutexLock(&t.mutex); + mutexLockUncancelable(&t.mutex); defer mutexUnlock(&t.mutex); if (t.environ_initialized) return; t.environ.scan(t.allocator); @@ -15133,28 +15196,54 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces }); }; - return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH); -} - -fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError { - if (!process.can_replace) return error.OperationUnsupported; - _ = userdata; - _ = dir; - _ = options; - @panic("TODO processReplacePath"); -} - -fn processSpawnPath(userdata: ?*anyopaque, dir: Dir, options: process.SpawnOptions) process.SpawnError!process.Child { - if (!process.can_spawn) return error.OperationUnsupported; - _ = userdata; - _ = dir; - _ = options; - @panic("TODO processSpawnPath"); + if (is_darwin) if (t.spawnDarwin( + .replace, + options.exe, + argv_buf.ptr, + options.cwd, + env_block, + options.expand_arg0, + .none, + .inherit, + .inherit, + .inherit, + options.inherit_dirs, + options.inherit_files, + null, + options.start_suspended, + PATH, + )) |_| unreachable else |err| switch (err) { + else => |e| return e, + error.WouldBlock => return error.SystemResources, + error.FileTooBig, + error.NoSpaceLeft, + error.PathAlreadyExists, + error.SymLinkLoop, + error.NetworkNotFound, + error.PipeBusy, + error.AntivirusInterference, + => return error.FileNotFound, + error.DeviceBusy, + error.NoDevice, + error.UnrecognizedVolume, + error.ReadOnlyFileSystem, + => return error.FileSystem, + error.ResourceLimitReached => return error.SystemResources, + error.FileLocksUnsupported => unreachable, // lock not requested + error.InvalidWtf8 => unreachable, // windows only + error.InvalidBatchScriptArg => unreachable, // windows only + error.InvalidUserId => unreachable, // not supported + error.InvalidProcessGroupId => unreachable, // passed null + error.InvalidName => unreachable, // windows only + error.ProcessAlreadyExec => unreachable, // linux only + }; + return posixExec(options.exe, argv_buf.ptr, options.expand_arg0, env_block, PATH); } const processSpawn = switch (native_os) { .wasi, .emscripten, .ios, .tvos, .visionos, .watchos => processSpawnUnsupported, .windows => processSpawnWindows, + .driverkit, .maccatalyst, .macos => processSpawnDarwin, else => processSpawnPosix, }; @@ -15230,16 +15319,12 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null); for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr; - const prog_fileno = 3; - comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno); - const env_block = env_block: { - const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{ - .zig_progress_fd = prog_fd, + .zig_progress_fd = prog_pipe[1], }); break :env_block try t.environ.process_environ.createPosixBlock(arena, .{ - .zig_progress_fd = prog_fd, + .zig_progress_fd = prog_pipe[1], }); }; @@ -15267,23 +15352,42 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked; const ep1 = err_pipe[1]; - setUpChildIo(options.stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); - setUpChildIo(options.stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); - setUpChildIo(options.stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); - + // Must happen before clobbering fds below. switch (options.cwd) { .inherit => {}, - .dir => |cwd| { - fchdir(cwd.handle) catch |err| forkBail(ep1, err); - }, - .path => |cwd| { - chdir(cwd) catch |err| forkBail(ep1, err); - }, + .dir => |cwd| fchdir(cwd.handle) catch |err| forkBail(ep1, err), + .path => |cwd| chdir(cwd) catch |err| forkBail(ep1, err), } - // Must happen after fchdir above, the cwd file descriptor might be - // equal to prog_fileno and be clobbered by this dup2 call. - if (prog_pipe[1] != -1) dup2(prog_pipe[1], prog_fileno) catch |err| forkBail(ep1, err); + for (options.inherit_dirs) |inherit_dir| switch (posix.errno( + posix.system.fcntl(inherit_dir.handle, posix.F.SETFD, @as(u32, 0)), + )) { + .SUCCESS => {}, + else => |err| forkBail(ep1, posix.unexpectedErrno(err)), + }; + for (options.inherit_files) |file| switch (posix.errno( + posix.system.fcntl(file.handle, posix.F.SETFD, @as(u32, 0)), + )) { + .SUCCESS => {}, + else => |err| forkBail(ep1, posix.unexpectedErrno(err)), + }; + if (prog_pipe[1] != -1) switch (posix.errno( + posix.system.fcntl(prog_pipe[1], posix.F.SETFD, @as(u32, 0)), + )) { + .SUCCESS => {}, + else => |err| forkBail(ep1, posix.unexpectedErrno(err)), + }; + for ( + [_]process.SpawnOptions.StdIo{ options.stdin, options.stdout, options.stderr }, + [_]posix.fd_t{ stdin_pipe[0], stdout_pipe[1], stderr_pipe[1] }, + [_]posix.fd_t{ posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO }, + ) |stdio, pipe_fd, std_fd| switch (stdio) { + .pipe => dup2(pipe_fd, std_fd) catch |err| forkBail(ep1, err), + .close => closeFd(std_fd), + .inherit => {}, + .ignore => dup2(dev_null_fd, std_fd) catch |err| forkBail(ep1, err), + .file => |file| dup2(file.handle, std_fd) catch |err| forkBail(ep1, err), + }; if (options.gid) |gid| { switch (posix.errno(posix.system.setregid(gid, gid))) { @@ -15316,14 +15420,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp } if (options.start_suspended) { - switch (posix.errno(posix.system.kill(0, .STOP))) { + switch (posix.errno(posix.system.kill(posix.system.getpid(), .STOP))) { .SUCCESS => {}, .PERM => forkBail(ep1, error.PermissionDenied), else => forkBail(ep1, error.Unexpected), } } - const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH); + const err = posixExec(options.exe, argv_buf.ptr, options.expand_arg0, env_block, PATH); forkBail(ep1, err); } @@ -15359,7 +15463,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp fn getDevNullFd(t: *Threaded) !posix.fd_t { { - mutexLock(&t.mutex); + try mutexLock(&t.mutex); defer mutexUnlock(&t.mutex); if (t.null_file.fd != -1) return t.null_file.fd; } @@ -15371,7 +15475,7 @@ fn getDevNullFd(t: *Threaded) !posix.fd_t { .SUCCESS => { syscall.finish(); const fresh_fd: posix.fd_t = @intCast(rc); - mutexLock(&t.mutex); // Another thread might have won the race. + mutexLockUncancelable(&t.mutex); // Another thread might have won the race. defer mutexUnlock(&t.mutex); if (t.null_file.fd != -1) { closeFd(fresh_fd); @@ -15746,19 +15850,137 @@ fn destroyPipe(pipe: [2]posix.fd_t) void { if (pipe[0] != pipe[1]) closeFd(pipe[1]); } -fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void { - switch (stdio) { - .pipe => try dup2(pipe_fd, std_fileno), - .close => closeFd(std_fileno), - .inherit => {}, - .ignore => try dup2(dev_null_fd, std_fileno), - .file => |file| try dup2(file.handle, std_fileno), - } +fn processSpawnDarwin(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + + t.scanEnviron(); // for PATH + const PATH = t.environ.string.PATH orelse default_PATH; + + var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null); + for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr; + + const env_block = env_block: { + const prog_fd: i32 = -1; + if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{ + .zig_progress_fd = prog_fd, + }); + break :env_block try t.environ.process_environ.createPosixBlock(arena, .{ + .zig_progress_fd = prog_fd, + }); + }; + + const spawned = try t.spawnDarwin( + .spawn, + options.exe, + argv_buf.ptr, + options.cwd, + env_block, + options.expand_arg0, + options.progress_node, + options.stdin, + options.stdout, + options.stderr, + options.inherit_dirs, + options.inherit_files, + options.pgid, + options.start_suspended, + PATH, + ); + return .{ + .id = spawned.pid, + .thread_handle = {}, + .stdin = spawned.stdin, + .stdout = spawned.stdout, + .stderr = spawned.stderr, + .request_resource_usage_statistics = options.request_resource_usage_statistics, + }; } fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { const t: *Threaded = @ptrCast(@alignCast(userdata)); + var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const cwd_w = cwd_w: { + switch (options.cwd) { + .inherit => break :cwd_w null, + .dir => |cwd_dir| { + var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1); + const dir_path = try GetFinalPathNameByHandle( + cwd_dir.handle, + .{}, + dir_path_buffer[0..windows.PATH_MAX_WIDE], + ); + dir_path_buffer[dir_path.len] = 0; + // Shrink the allocation down to just the path buffer + sentinel + dir_path_buffer = try arena.realloc(dir_path_buffer, dir_path.len + 1); + break :cwd_w dir_path_buffer[0..dir_path.len :0]; + }, + .path => |cwd| { + break :cwd_w try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd); + }, + } + }; + const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; + + // If the app name has more than just a filename, then we need to separate + // that into the basename and dirname and use the dirname as an addition to + // the cwd path. This is because NtQueryDirectoryFile cannot accept + // FileName params with path separators. + var app_w_buf: [windows.PATH_MAX_WIDE]u16 = undefined; + // If the app name is absolute, then the cwd will already have the app's dirname in it, + // so only populate app_dir if app name is a relative path with > 0 path separators. + const app_path_w, const app_dir_w, const app_name_w = app: { + const path = switch (options.exe) { + .detect, .search, .path => options.argv[0], + .file => |file| { + const path_w = try GetFinalPathNameByHandle(file.handle, .{}, &app_w_buf); + var path_it: Dir.path.ComponentIterator(.windows, u16) = .init(path_w); + const base = path_it.last().?; + break :app .{ + null, + if (path_it.previous()) |dir| dir.path else path_it.root(), + base.name, + }; + }, + .explicit => |explicit| explicit.path, + }; + break :app .{ + if (Dir.path.isAbsolute(path)) null else switch (options.exe) { + // The cwd provided by options is in effect when choosing the executable + // path to match POSIX semantics. + .detect, .search => cwd_w, + .path => |dir| try GetFinalPathNameByHandle(dir.handle, .{}, &app_w_buf), + .file => unreachable, + .explicit => |explicit| try GetFinalPathNameByHandle( + explicit.dir.handle, + .{}, + &app_w_buf, + ), + }, + if (Dir.path.dirname(path)) |app_dir| + try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_dir) + else + null, + try std.unicode.wtf8ToWtf16LeAllocZ(arena, Dir.path.basename(path)), + }; + }; + + const flags: windows.CreateProcessFlags = .{ + .create_suspended = options.start_suspended, + .create_unicode_environment = true, + .create_no_window = options.create_no_window, + }; + + try mutexLock(&t.process_spawn_mutex); + defer mutexUnlock(&t.process_spawn_mutex); + const any_ignore = options.stdin == .ignore or options.stdout == .ignore or @@ -15800,6 +16022,43 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro }) else undefined; errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle); + defer for (options.inherit_dirs) |inherit_dir| switch (windows.ntdll.NtSetInformationObject( + inherit_dir.handle, + .HandleFlag, + &windows.OBJECT.HANDLE_FLAG{ .INHERIT = false }, + @sizeOf(windows.OBJECT.HANDLE_FLAG), + )) { + .SUCCESS => {}, + else => |status| windows.unexpectedStatus(status) catch {}, + }; + for (options.inherit_dirs) |inherit_dir| switch (windows.ntdll.NtSetInformationObject( + inherit_dir.handle, + .HandleFlag, + &windows.OBJECT.HANDLE_FLAG{ .INHERIT = true }, + @sizeOf(windows.OBJECT.HANDLE_FLAG), + )) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), + }; + defer for (options.inherit_files) |inherit_file| switch (windows.ntdll.NtSetInformationObject( + inherit_file.handle, + .HandleFlag, + &windows.OBJECT.HANDLE_FLAG{ .INHERIT = false }, + @sizeOf(windows.OBJECT.HANDLE_FLAG), + )) { + .SUCCESS => {}, + else => |status| windows.unexpectedStatus(status) catch {}, + }; + for (options.inherit_files) |inherit_file| switch (windows.ntdll.NtSetInformationObject( + inherit_file.handle, + .HandleFlag, + &windows.OBJECT.HANDLE_FLAG{ .INHERIT = true }, + @sizeOf(windows.OBJECT.HANDLE_FLAG), + )) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), + }; + var siStartInfo: windows.STARTUPINFOW = .{ .cb = @sizeOf(windows.STARTUPINFOW), .dwFlags = windows.STARTF_USESTDHANDLES, @@ -15877,32 +16136,6 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro }; var piProcInfo: windows.PROCESS.INFORMATION = undefined; - var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - const cwd_w = cwd_w: { - switch (options.cwd) { - .inherit => break :cwd_w null, - .dir => |cwd_dir| { - var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1); - const dir_path = try GetFinalPathNameByHandle( - cwd_dir.handle, - .{}, - dir_path_buffer[0..windows.PATH_MAX_WIDE], - ); - dir_path_buffer[dir_path.len] = 0; - // Shrink the allocation down to just the path buffer + sentinel - dir_path_buffer = try arena.realloc(dir_path_buffer, dir_path.len + 1); - break :cwd_w dir_path_buffer[0..dir_path.len :0]; - }, - .path => |cwd| { - break :cwd_w try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd); - }, - } - }; - const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; - const env_block = env_block: { const prog_handle = if (options.progress_node.index != .none) prog_pipe[1] @@ -15916,45 +16149,6 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro }); }; - const app_name_wtf8 = options.argv[0]; - const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8); - - // The cwd provided by options is in effect when choosing the executable - // path to match POSIX semantics. - const cwd_path_w = x: { - // If the app name is absolute, then we need to use its dirname as the cwd - if (app_name_is_absolute) { - const dir = Dir.path.dirname(app_name_wtf8).?; - break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, dir); - } else if (cwd_w) |cwd| { - break :x cwd; - } else { - break :x &[_:0]u16{}; // empty for cwd - } - }; - - // If the app name has more than just a filename, then we need to separate - // that into the basename and dirname and use the dirname as an addition to - // the cwd path. This is because NtQueryDirectoryFile cannot accept - // FileName params with path separators. - const app_basename_wtf8 = Dir.path.basename(app_name_wtf8); - // If the app name is absolute, then the cwd will already have the app's dirname in it, - // so only populate app_dirname if app name is a relative path with > 0 path separators. - const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) Dir.path.dirname(app_name_wtf8) else null; - const app_dirname_w: ?[:0]u16 = x: { - if (maybe_app_dirname_wtf8) |app_dirname_wtf8| { - break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_dirname_wtf8); - } - break :x null; - }; - const app_name_w = try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_basename_wtf8); - - const flags: windows.CreateProcessFlags = .{ - .create_suspended = options.start_suspended, - .create_unicode_environment = true, - .create_no_window = options.create_no_window, - }; - run: { // We have to scan each time because the PEB environment pointer is not stable. const env_strings: WindowsEnvironStrings = .scan(); @@ -15975,10 +16169,8 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro var dir_buf: std.ArrayList(u16) = .empty; - if (cwd_path_w.len > 0) { - try dir_buf.appendSlice(arena, cwd_path_w); - } - if (app_dirname_w) |app_dir| { + if (app_path_w) |app_path| try dir_buf.appendSlice(arena, app_path); + if (app_dir_w) |app_dir| { if (dir_buf.items.len > 0) try dir_buf.append(arena, Dir.path.sep); try dir_buf.appendSlice(arena, app_dir); } @@ -16003,15 +16195,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro else => |e| return e, }; - // If the app name had path separators, that disallows PATH searching, - // and there's no need to search the PATH if the app name is absolute. // We still search the path if the cwd is absolute because of the // "cwd provided by options is in effect when choosing the executable path // to match posix semantics" behavior--we don't want to skip searching // the PATH just because we were trying to set the cwd of the child process. - if (app_dirname_w != null or app_name_is_absolute) { - return original_err; - } + if (switch (options.exe) { + // If the app name had path separators, that disallows PATH searching. + .detect => app_dir_w != null, + // There's no need to search the PATH if the app name is absolute. + .search => app_path_w == null, + // PATH searching is disallowed. + .path, .file, .explicit => true, + }) return original_err; var it = std.mem.tokenizeScalar(u16, PATH, ';'); while (it.next()) |search_path| { @@ -16089,11 +16284,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro }; } -fn inheritFile() windows.HANDLE {} - fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE { { - mutexLock(&t.mutex); + try mutexLock(&t.mutex); defer mutexUnlock(&t.mutex); if (t.random_file.handle) |handle| return handle; } @@ -16116,7 +16309,7 @@ fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE { )) { .SUCCESS => { syscall.finish(); - mutexLock(&t.mutex); // Another thread might have won the race. + mutexLockUncancelable(&t.mutex); // Another thread might have won the race. defer mutexUnlock(&t.mutex); if (t.random_file.handle) |prev_handle| { windows.CloseHandle(fresh_handle); @@ -16137,7 +16330,7 @@ fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE { fn getNulDevice(t: *Threaded) !windows.HANDLE { { - mutexLock(&t.mutex); + try mutexLock(&t.mutex); defer mutexUnlock(&t.mutex); if (t.null_file.handle) |handle| return handle; } @@ -16163,7 +16356,7 @@ fn getNulDevice(t: *Threaded) !windows.HANDLE { )) { .SUCCESS => { syscall.finish(); - mutexLock(&t.mutex); // Another thread might have won the race. + mutexLockUncancelable(&t.mutex); // Another thread might have won the race. defer mutexUnlock(&t.mutex); if (t.null_file.handle) |prev_handle| { windows.CloseHandle(fresh_handle); @@ -16196,7 +16389,7 @@ fn getNulDevice(t: *Threaded) !windows.HANDLE { fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE { { - mutexLock(&t.mutex); + try mutexLock(&t.mutex); defer mutexUnlock(&t.mutex); if (t.pipe_file.handle) |handle| return handle; } @@ -16218,7 +16411,7 @@ fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE { )) { .SUCCESS => { syscall.finish(); - mutexLock(&t.mutex); // Another thread might have won the race. + mutexLockUncancelable(&t.mutex); // Another thread might have won the race. defer mutexUnlock(&t.mutex); if (t.pipe_file.handle) |prev_handle| { windows.CloseHandle(fresh_handle); @@ -16503,7 +16696,15 @@ fn windowsCreateProcessPathExt( else full_app_name; - if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, env_block, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| { + if (windowsCreateProcess( + app_name_w.ptr, + cmd_line_w.ptr, + env_block, + cwd_ptr, + flags, + lpStartupInfo, + lpProcessInformation, + )) |_| { return; } else |err| switch (err) { error.FileNotFound => continue, @@ -17037,61 +17238,112 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c try std.testing.expectEqualStrings(expected_cmd_line, cmd_line); } -fn posixExecv( - arg0_expand: process.ArgExpansion, - file: [*:0]const u8, - child_argv: [*:null]?[*:0]const u8, +fn posixExec( + exe: process.ReplaceOptions.Exe, + argv: [*:null]?[*:0]const u8, + expand_arg0: process.ArgExpansion, env_block: process.Environ.PosixBlock, PATH: []const u8, ) process.ReplaceError { - const file_slice = std.mem.sliceTo(file, 0); - if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, env_block); + const arg0_slice = std.mem.span(argv[0].?); + exe: switch (exe) { + .detect => continue :exe if (std.mem.findScalar(u8, arg0_slice, '/')) |_| .{ + .path = .cwd(), + } else .search, + .search => { + if (Dir.path.isAbsolute(arg0_slice)) continue :exe .{ .path = .cwd() }; + // Use of PATH_MAX here is valid as the path_buf will be passed + // directly to the operating system in posixExecveat. + var path_buf: [posix.PATH_MAX]u8 = undefined; + var it = std.mem.tokenizeScalar(u8, PATH, ':'); + var err: error{ AccessDenied, FileNotFound, NotDir } = error.FileNotFound; - // Use of PATH_MAX here is valid as the path_buf will be passed - // directly to the operating system in posixExecvPath. - var path_buf: [posix.PATH_MAX]u8 = undefined; - var it = std.mem.tokenizeScalar(u8, PATH, ':'); - var seen_eacces = false; - var err: process.ReplaceError = error.FileNotFound; - - // In case of expanding arg0 we must put it back if we return with an error. - const prev_arg0 = child_argv[0]; - defer switch (arg0_expand) { - .expand => child_argv[0] = prev_arg0, - .no_expand => {}, - }; - - while (it.next()) |search_path| { - const path_len = search_path.len + file_slice.len + 1; - if (path_buf.len < path_len + 1) return error.NameTooLong; - @memcpy(path_buf[0..search_path.len], search_path); - path_buf[search_path.len] = '/'; - @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice); - path_buf[path_len] = 0; - const full_path = path_buf[0..path_len :0].ptr; - switch (arg0_expand) { - .expand => child_argv[0] = full_path, - .no_expand => {}, - } - err = posixExecvPath(full_path, child_argv, env_block); - switch (err) { - error.AccessDenied => seen_eacces = true, - error.FileNotFound, error.NotDir => {}, - else => |e| return e, - } + while (it.next()) |search_path| { + const path_len = search_path.len + arg0_slice.len + 1; + if (path_buf.len < path_len + 1) return error.NameTooLong; + @memcpy(path_buf[0..search_path.len], search_path); + path_buf[search_path.len] = Dir.path.sep; + @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice); + path_buf[path_len] = 0; + const full_path = path_buf[0..path_len :0].ptr; + switch (expand_arg0) { + .expand => argv[0] = full_path, + .no_expand => {}, + } + switch (posixExecveat(posix.AT.FDCWD, full_path, argv, env_block)) { + error.AccessDenied, error.FileNotFound, error.NotDir => |e| switch (err) { + error.AccessDenied => {}, + error.FileNotFound, error.NotDir => err = e, + }, + else => |e| return e, + } + } + return err; + }, + .path => |path| return posixExecveat(path.handle, arg0_slice, argv, env_block), + .file => |file| return posixExecveat(file.handle, null, argv, env_block), + .explicit => |explicit| { + var path_buffer: [posix.PATH_MAX]u8 = undefined; + const path_posix = try pathToPosix(explicit.path, &path_buffer); + return posixExecveat(explicit.dir.handle, path_posix, argv, env_block); + }, } - if (seen_eacces) return error.AccessDenied; - return err; } /// This function ignores PATH environment variable. -pub fn posixExecvPath( - path: [*:0]const u8, - child_argv: [*:null]const ?[*:0]const u8, +pub fn posixExecveat( + dir: posix.fd_t, + path: ?[*:0]const u8, + argv: [*:null]const ?[*:0]const u8, env_block: process.Environ.PosixBlock, ) process.ReplaceError { try Thread.checkCancel(); - switch (posix.errno(posix.system.execve(path, child_argv, env_block.slice.ptr))) { + switch (err: { + if (path) |p| if (dir == posix.AT.FDCWD or Dir.path.isAbsoluteZ(p)) { + break :err posix.errno(posix.system.execve(p, argv, env_block.slice.ptr)); + }; + if (comptime native_os == .linux and (!builtin.link_libc or + (builtin.target.isGnuLibC() and builtin.target.os.version_range.linux.glibc.order( + .{ .major = 2, .minor = 34, .patch = 0 }, + ).compare(.gte)))) + { + switch (posix.errno(posix.system.execveat(dir, path orelse "", argv, env_block.slice.ptr, .{ + .EMPTY_PATH = path == null, + .SYMLINK_NOFOLLOW = false, + }))) { + else => |err| break :err err, + .NOSYS => {}, + } + } + var path_buffer: [posix.PATH_MAX]u8 = undefined; + var path_len = realPathPosix(dir, &path_buffer) catch |err| switch (err) { + else => |e| return e, + error.InputOutput, + error.FileTooBig, + error.NoSpaceLeft, + error.PathAlreadyExists, + error.SymLinkLoop, + error.NetworkNotFound, + error.PipeBusy, + error.AntivirusInterference, + => return error.FileNotFound, + error.DeviceBusy, + error.NoDevice, + error.UnrecognizedVolume, + => return error.FileSystem, + }; + if (path) |p| { + const path_slice = std.mem.span(p); + path_buffer[path_len] = Dir.path.sep; + path_len += 1; + @memcpy(path_buffer[path_len..][0..path_slice.len], path_slice); + path_len += path_slice.len; + } + path_buffer[path_len] = 0; + break :err posix.errno( + posix.system.execve(path_buffer[0..path_len :0], argv, env_block.slice.ptr), + ); + }) { .FAULT => |err| return errnoBug(err), // Bad pointer parameter. .@"2BIG" => return error.SystemResources, .MFILE => return error.ProcessFdQuotaExceeded, @@ -17123,6 +17375,334 @@ pub fn posixExecvPath( } } +fn spawnDarwin( + t: *Threaded, + mode: enum { spawn, replace }, + exe: process.ReplaceOptions.Exe, + argv: [*:null]?[*:0]const u8, + cwd: process.Child.Cwd, + env_block: process.Environ.PosixBlock, + expand_arg0: process.ArgExpansion, + progress_node: std.Progress.Node, + stdin: process.SpawnOptions.StdIo, + stdout: process.SpawnOptions.StdIo, + stderr: process.SpawnOptions.StdIo, + inherit_dirs: []const Dir, + inherit_files: []const File, + pgid: ?posix.pid_t, + start_suspended: bool, + PATH: []const u8, +) (process.SpawnError || process.ReplaceError)!Spawned { + var fa: std.c.posix_spawn_file_actions_t = undefined; + switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawn_file_actions_init(&fa))))) { + .SUCCESS => {}, + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + defer switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawn_file_actions_destroy(&fa), + )))) { + .SUCCESS => {}, + .INVAL => recoverableOsBugDetected(), + else => recoverableOsBugDetected(), + }; + + var attr: std.c.posix_spawnattr_t = undefined; + switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawnattr_init(&attr))))) { + .SUCCESS => {}, + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + defer switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawnattr_destroy(&attr))))) { + .SUCCESS => {}, + .INVAL => recoverableOsBugDetected(), + else => recoverableOsBugDetected(), + }; + + // The child process does need to access (one end of) these pipes. However, + // we must initially set CLOEXEC to avoid a race condition. If another thread + // is racing to spawn a different child process, we don't want it to inherit + // these FDs in any scenario; that would mean that, for instance, calls to + // `poll` from the parent would not report the child's stdout as closing when + // expected, since the other child may retain a reference to the write end of + // the pipe. So, we create the pipes with CLOEXEC initially. After fork, we + // need to do something in the new child to make sure we preserve the reference + // we want. We could use `fcntl` to remove CLOEXEC from the FD, but as it + // turns out, we `dup2` everything anyway, so there's no need! + const pipe_flags: posix.O = .{ .CLOEXEC = true }; + + const stdin_pipe = if (stdin == .pipe) try pipe2(pipe_flags) else undefined; + errdefer if (stdin == .pipe) { + destroyPipe(stdin_pipe); + }; + + const stdout_pipe = if (stdout == .pipe) try pipe2(pipe_flags) else undefined; + errdefer if (stdout == .pipe) { + destroyPipe(stdout_pipe); + }; + + const stderr_pipe = if (stderr == .pipe) try pipe2(pipe_flags) else undefined; + errdefer if (stderr == .pipe) { + destroyPipe(stderr_pipe); + }; + + const any_ignore = (stdin == .ignore or stdout == .ignore or stderr == .ignore); + const dev_null_fd = if (any_ignore) try t.getDevNullFd() else undefined; + + const prog_pipe: [2]posix.fd_t = if (progress_node.index != .none) pipe: { + // We use CLOEXEC for the same reason as in `pipe_flags`. + break :pipe try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }); + } else .{ -1, -1 }; + errdefer destroyPipe(prog_pipe); + + var cwd_buffer: [posix.PATH_MAX]u8 = undefined; + switch (cwd) { + .inherit => {}, + .dir => |dir| switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawn_file_actions_addfchdir_np(&fa, dir.handle), + )))) { + .SUCCESS => {}, + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + }, + .path => |path| switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawn_file_actions_addchdir_np(&fa, try pathToPosix(path, &cwd_buffer)), + )))) { + .SUCCESS => {}, + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .NAMETOOLONG => return error.NameTooLong, + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + }, + } + + for (inherit_dirs) |inherit_dir| switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawn_file_actions_addinherit_np(&fa, inherit_dir.handle), + )))) { + .SUCCESS => {}, + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + }; + for (inherit_files) |inherit_file| switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawn_file_actions_addinherit_np(&fa, inherit_file.handle), + )))) { + .SUCCESS => {}, + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + }; + if (prog_pipe[1] != -1) switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawn_file_actions_addinherit_np(&fa, prog_pipe[1]), + )))) { + .SUCCESS => {}, + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + }; + for ( + [_]process.SpawnOptions.StdIo{ stdin, stdout, stderr }, + [_]posix.fd_t{ stdin_pipe[0], stdout_pipe[1], stderr_pipe[1] }, + [_]posix.fd_t{ posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO }, + ) |stdio, pipe_fd, std_fd| switch (@as(std.c.E, @fromBackingInt(@intCast(switch (stdio) { + .pipe => std.c.posix_spawn_file_actions_adddup2(&fa, pipe_fd, std_fd), + .close => std.c.posix_spawn_file_actions_addclose(&fa, std_fd), + .inherit => std.c.posix_spawn_file_actions_addinherit_np(&fa, std_fd), + .ignore => std.c.posix_spawn_file_actions_adddup2(&fa, dev_null_fd, std_fd), + .file => |file| std.c.posix_spawn_file_actions_adddup2(&fa, file.handle, std_fd), + })))) { + .SUCCESS => {}, + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + }; + + if (pgid) |pid| switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawnattr_setpgroup(&attr, pid), + )))) { + .SUCCESS => {}, + .INVAL => return error.InvalidProcessGroupId, + else => |err| return posix.unexpectedErrno(err), + }; + + switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawnattr_setflags(&attr, .{ + .SETPGROUP = pgid != null, + .SETEXEC = switch (mode) { + .spawn => false, + .replace => true, + }, + .START_SUSPENDED = start_suspended, + }))))) { + .SUCCESS => {}, + .INVAL => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + + var pid: posix.pid_t = undefined; + const arg0_slice = std.mem.span(argv[0].?); + var path_buffer: [posix.PATH_MAX]u8 = undefined; + switch (err: { + break :err @as(std.c.E, @fromBackingInt(@intCast(exe: switch (exe) { + .detect => if (std.mem.findScalar(u8, arg0_slice, '/')) |_| + std.c.posix_spawn(&pid, arg0_slice, &fa, &attr, argv, env_block.slice.ptr) + else + continue :exe .search, + .search => if (!Dir.path.isAbsolute(arg0_slice)) { + // Use of PATH_MAX here is valid as the path_buf will be passed + // directly to the operating system in posixExecveat. + var path_buf: [posix.PATH_MAX]u8 = undefined; + var it = std.mem.tokenizeScalar(u8, PATH, ':'); + var err: error{ AccessDenied, FileNotFound, NotDir } = error.FileNotFound; + + while (it.next()) |search_path| { + const path_len = search_path.len + arg0_slice.len + 1; + if (path_buf.len < path_len + 1) return error.NameTooLong; + @memcpy(path_buf[0..search_path.len], search_path); + path_buf[search_path.len] = Dir.path.sep; + @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice); + path_buf[path_len] = 0; + const full_path = path_buf[0..path_len :0].ptr; + switch (expand_arg0) { + .expand => argv[0] = full_path, + .no_expand => {}, + } + switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawn(&pid, full_path, &fa, &attr, argv, env_block.slice.ptr), + )))) { + else => |e| break :err e, + .ACCES, .NOENT, .NOTDIR => |e| switch (err) { + error.AccessDenied => {}, + error.FileNotFound, error.NotDir => err = switch (e) { + else => unreachable, + .ACCES => error.AccessDenied, + .NOENT => error.FileNotFound, + .NOTDIR => error.NotDir, + }, + }, + } + } + return err; + } else std.c.posix_spawn(&pid, arg0_slice, &fa, &attr, argv, env_block.slice.ptr), + .path => |path| if (path.handle != posix.AT.FDCWD and !Dir.path.isAbsolute(arg0_slice)) { + var path_len = realPathPosix(path.handle, &path_buffer) catch |err| switch (err) { + else => |e| return e, + error.InputOutput => return error.FileNotFound, + }; + path_buffer[path_len] = Dir.path.sep; + path_len += 1; + @memcpy(path_buffer[path_len..][0..arg0_slice.len], arg0_slice); + path_len += arg0_slice.len; + path_buffer[path_len] = 0; + break :exe std.c.posix_spawn( + &pid, + path_buffer[0..path_len :0], + &fa, + &attr, + argv, + env_block.slice.ptr, + ); + } else std.c.posix_spawn(&pid, arg0_slice, &fa, &attr, argv, env_block.slice.ptr), + .file => |file| { + const path_len = realPathPosix(file.handle, &path_buffer) catch |err| switch (err) { + else => |e| return e, + error.InputOutput => return error.FileNotFound, + }; + path_buffer[path_len] = 0; + break :exe std.c.posix_spawn( + &pid, + path_buffer[0..path_len :0], + &fa, + &attr, + argv, + env_block.slice.ptr, + ); + }, + .explicit => |explicit| if (explicit.dir.handle != posix.AT.FDCWD and + !Dir.path.isAbsolute(explicit.path)) + { + var path_len = realPathPosix( + explicit.dir.handle, + &path_buffer, + ) catch |err| switch (err) { + else => |e| return e, + error.InputOutput => return error.FileNotFound, + }; + path_buffer[path_len] = Dir.path.sep; + path_len += 1; + @memcpy(path_buffer[path_len..][0..explicit.path.len], explicit.path); + path_len += explicit.path.len; + path_buffer[path_len] = 0; + break :exe std.c.posix_spawn( + &pid, + path_buffer[0..path_len :0], + &fa, + &attr, + argv, + env_block.slice.ptr, + ); + } else std.c.posix_spawn( + &pid, + try pathToPosix(explicit.path, &path_buffer), + &fa, + &attr, + argv, + env_block.slice.ptr, + ), + }))); + }) { + .SUCCESS => {}, + .AGAIN => return error.SystemResources, + .INVAL => |err| return errnoBug(err), + .@"2BIG" => return error.SystemResources, + .ACCES => return error.AccessDenied, + .FAULT => |err| return errnoBug(err), + .IO => return error.FileSystem, + .LOOP => return error.FileSystem, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOEXEC => return error.InvalidExe, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .TXTBSY => return error.FileBusy, + .BADARCH => return error.InvalidExe, + .BADF => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + + errdefer comptime unreachable; // The child is spawned; we must not error from now on + + if (stdin == .pipe) closeFd(stdin_pipe[0]); + if (stdout == .pipe) closeFd(stdout_pipe[1]); + if (stderr == .pipe) closeFd(stderr_pipe[1]); + + if (prog_pipe[1] != -1) closeFd(prog_pipe[1]); + progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } }); + + return .{ + .pid = pid, + .err_fd = -1, + .stdin = switch (stdin) { + .pipe => .{ .handle = stdin_pipe[1], .flags = .{ .nonblocking = false } }, + else => null, + }, + .stdout = switch (stdout) { + .pipe => .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = false } }, + else => null, + }, + .stderr = switch (stderr) { + .pipe => .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = false } }, + else => null, + }, + }; +} + pub const CreatePipeOptions = struct { server: End, client: End, @@ -17224,6 +17804,53 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { return t.environ.zig_progress_file; } +fn inheritParentHandle(handle: posix.fd_t) Io.InheritParentHandleError!void { + switch (native_os) { + .linux, + .dragonfly, + .freebsd, + .netbsd, + .openbsd, + .illumos, + .driverkit, + .ios, + .maccatalyst, + .macos, + .tvos, + .visionos, + .watchos, + => return setCloexec(handle), + .windows => switch (windows.ntdll.NtSetInformationObject( + handle, + .HandleFlag, + &windows.OBJECT.HANDLE_FLAG{ .INHERIT = false }, + @sizeOf(windows.OBJECT.HANDLE_FLAG), + )) { + .SUCCESS => return, + else => |status| return windows.unexpectedStatus(status), + }, + else => return error.UnsupportedOperation, + } +} + +fn inheritParentDir(userdata: ?*anyopaque, handle: Dir.Handle) Io.InheritParentHandleError!Dir { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; + try inheritParentHandle(handle); + return .{ .handle = handle }; +} + +fn inheritParentFile( + userdata: ?*anyopaque, + handle: File.Handle, + flags: File.Flags, +) Io.InheritParentHandleError!File { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; + try inheritParentHandle(handle); + return .{ .handle = handle, .flags = flags }; +} + pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 { t.scanEnviron(); return @field(t.environ.string, name); @@ -17242,7 +17869,7 @@ fn random(userdata: ?*anyopaque, buffer: []u8) void { } fn randomMainThread(t: *Threaded, buffer: []u8) void { - mutexLock(&t.mutex); + mutexLockUncancelable(&t.mutex); defer mutexUnlock(&t.mutex); if (!t.csprng.isInitialized()) { @@ -17250,7 +17877,7 @@ fn randomMainThread(t: *Threaded, buffer: []u8) void { var seed: [Csprng.seed_len]u8 = undefined; { mutexUnlock(&t.mutex); - defer mutexLock(&t.mutex); + defer mutexLockUncancelable(&t.mutex); const prev = swapCancelProtection(t, .blocked); defer _ = swapCancelProtection(t, prev); @@ -17437,7 +18064,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void { fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { { - mutexLock(&t.mutex); + try mutexLock(&t.mutex); defer mutexUnlock(&t.mutex); if (t.random_file.fd == -2) return error.EntropyUnavailable; @@ -17478,7 +18105,7 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { .SUCCESS => { syscall.finish(); if (!statx.mask.TYPE) return error.EntropyUnavailable; - mutexLock(&t.mutex); // Another thread might have won the race. + mutexLockUncancelable(&t.mutex); // Another thread might have won the race. defer mutexUnlock(&t.mutex); if (t.random_file.fd >= 0) { closeFd(fd); @@ -17506,7 +18133,7 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { switch (posix.errno(fstat_sym(fd, &stat))) { .SUCCESS => { syscall.finish(); - mutexLock(&t.mutex); // Another thread might have won the race. + mutexLockUncancelable(&t.mutex); // Another thread might have won the race. defer mutexUnlock(&t.mutex); if (t.random_file.fd >= 0) { closeFd(fd); @@ -18434,7 +19061,7 @@ const CreateFileMapError = error{ fn createFileMap( file: File, - protection: std.process.MemoryProtection, + protection: process.MemoryProtection, offset: u64, populate: bool, len: usize, @@ -19062,7 +19689,7 @@ fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void { } mutexUnlock(mutex); - defer mutexLock(mutex); + defer mutexLockUncancelable(mutex); while (true) { Thread.futexWaitUncancelable(&cond.epoch.raw, epoch, null); @@ -19082,8 +19709,27 @@ fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void { } } +/// Same as `Io.Mutex.lock` but avoids the VTable. +pub fn mutexLock(m: *Io.Mutex) Io.Cancelable!void { + const initial_state = m.state.cmpxchgStrong( + .unlocked, + .locked_once, + .acquire, + .monotonic, + ) orelse { + @branchHint(.likely); + return; + }; + if (initial_state == .contended) { + try Thread.futexWait(@ptrCast(&m.state.raw), @backingInt(Io.Mutex.State.contended), null); + } + while (m.state.swap(.contended, .acquire) != .unlocked) { + try Thread.futexWait(@ptrCast(&m.state.raw), @backingInt(Io.Mutex.State.contended), null); + } +} + /// Same as `Io.Mutex.lockUncancelable` but avoids the VTable. -pub fn mutexLock(m: *Io.Mutex) void { +pub fn mutexLockUncancelable(m: *Io.Mutex) void { const initial_state = m.state.cmpxchgStrong( .unlocked, .locked_once, diff --git a/lib/std/bit_set.zig b/lib/std/bit_set.zig index 72478ec895abe6b5e8ddef32a09a045eb3063698..b74cb49f0659b0693ee9f1b1cd3ba8298a22f319 100644 --- a/lib/std/bit_set.zig +++ b/lib/std/bit_set.zig @@ -723,13 +723,8 @@ pub const Dynamic = struct { // That slot holds the size of the true allocation, which // is needed by Zig's allocator interface in case a shrink // fails. - - // Don't modify this value. Ideally it would go in const data so - // modifications would cause a bus error, but the only way - // to discard a const qualifier is through intFromPtr, which - // cannot currently round trip at comptime. - var empty_masks_data = [_]MaskInt{ 0, undefined }; - const empty_masks_ptr = empty_masks_data[1..2]; + const empty_masks_data = [_]MaskInt{ 0, undefined }; + const empty_masks_ptr = @constCast(empty_masks_data[1..2]); /// Creates a bit set with no elements present. /// If bit_length is not zero, deinit must eventually be called. diff --git a/lib/std/c.zig b/lib/std/c.zig index abadb78976db809b025e6b08e30b5963b9cd95bc..51c6656d4f21dc23717f757faddc76f109049e2a 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -8809,6 +8809,11 @@ pub const O = switch (native_os) { else => void, }; +pub const EXECVEAT = switch (native_os) { + .linux => linux.EXECVEAT, + else => void, +}; + pub const MAP = switch (native_os) { .linux => linux.MAP, .emscripten => packed struct(u32) { @@ -11045,6 +11050,7 @@ pub extern "c" fn renameat(olddirfd: fd_t, old: [*:0]const u8, newdirfd: fd_t, n pub extern "c" fn chdir(path: [*:0]const u8) c_int; pub extern "c" fn fchdir(fd: fd_t) c_int; pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int; +pub 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; pub extern "c" fn dup(fd: fd_t) c_int; pub extern "c" fn dup2(old_fd: fd_t, new_fd: fd_t) c_int; pub 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; pub const posix_spawn_file_actions_t = darwin.posix_spawn_file_actions_t; pub const posix_spawnattr_destroy = darwin.posix_spawnattr_destroy; pub const posix_spawnattr_getflags = darwin.posix_spawnattr_getflags; +pub const posix_spawnattr_getpgroup = darwin.posix_spawnattr_getpgroup; pub const posix_spawnattr_init = darwin.posix_spawnattr_init; pub const posix_spawnattr_setflags = darwin.posix_spawnattr_setflags; +pub const posix_spawnattr_setpgroup = darwin.posix_spawnattr_setpgroup; pub const posix_spawnattr_t = darwin.posix_spawnattr_t; pub const posix_spawnp = darwin.posix_spawnp; pub const pthread_attr_get_qos_class_np = darwin.pthread_attr_get_qos_class_np; diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index 19ff7897d8a4297dedcd565d4f01b5f903f60667..fa8c1901cd74954ece2856487cf4eb6afcd90beb 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -1253,8 +1253,10 @@ pub const posix_spawnattr_t = *opaque {}; pub const posix_spawn_file_actions_t = *opaque {}; pub extern "c" fn posix_spawnattr_init(attr: *posix_spawnattr_t) c_int; pub extern "c" fn posix_spawnattr_destroy(attr: *posix_spawnattr_t) c_int; -pub extern "c" fn posix_spawnattr_setflags(attr: *posix_spawnattr_t, flags: POSIX_SPAWN) c_int; pub extern "c" fn posix_spawnattr_getflags(attr: *const posix_spawnattr_t, flags: *POSIX_SPAWN) c_int; +pub extern "c" fn posix_spawnattr_getpgroup(attr: *const posix_spawnattr_t, pgroup: *pid_t) c_int; +pub extern "c" fn posix_spawnattr_setflags(attr: *posix_spawnattr_t, flags: POSIX_SPAWN) c_int; +pub extern "c" fn posix_spawnattr_setpgroup(attr: *posix_spawnattr_t, pgroup: pid_t) c_int; pub extern "c" fn posix_spawn_file_actions_init(actions: *posix_spawn_file_actions_t) c_int; pub extern "c" fn posix_spawn_file_actions_destroy(actions: *posix_spawn_file_actions_t) c_int; pub extern "c" fn posix_spawn_file_actions_addclose(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int; diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index 01d6f0236c1c2b9ade0034cf6d0f35435dd19f73..58813c48753f92b63b4571161ae172c0f525e70b 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -1438,7 +1438,7 @@ pub fn dirnamePosix(path: []const u8) ?[]const u8 { } fn dirnameInner(comptime path_type: PathType, path: []const u8) ?[]const u8 { - var it = ComponentIterator(path_type, u8).init(path); + var it: ComponentIterator(path_type, u8) = .init(path); _ = it.last() orelse return null; const up = it.previous() orelse return it.root(); return up.path; @@ -1539,7 +1539,7 @@ pub fn basenameWindows(path: []const u8) []const u8 { } fn basenameInner(comptime path_type: PathType, path: []const u8) []const u8 { - var it = ComponentIterator(path_type, u8).init(path); + var it: ComponentIterator(path_type, u8) = .init(path); const last = it.last() orelse return &[_]u8{}; return last.name; } @@ -2338,14 +2338,14 @@ pub const NativeComponentIterator = ComponentIterator(switch (native_os) { }, u8); pub fn componentIterator(path: []const u8) NativeComponentIterator { - return NativeComponentIterator.init(path); + return .init(path); } test "ComponentIterator posix" { const PosixComponentIterator = ComponentIterator(.posix, u8); { const path = "a/b/c/"; - var it = PosixComponentIterator.init(path); + var it: PosixComponentIterator = .init(path); try std.testing.expectEqual(0, it.root_len); try std.testing.expectEqual(0, it.root_end_index); try std.testing.expect(null == it.root()); @@ -2393,7 +2393,7 @@ test "ComponentIterator posix" { { const path = "/a/b/c/"; - var it = PosixComponentIterator.init(path); + var it: PosixComponentIterator = .init(path); try std.testing.expectEqual(1, it.root_len); try std.testing.expectEqual(1, it.root_end_index); try std.testing.expectEqualStrings("/", it.root().?); @@ -2441,7 +2441,7 @@ test "ComponentIterator posix" { { const path = "////a///b///c////"; - var it = PosixComponentIterator.init(path); + var it: PosixComponentIterator = .init(path); try std.testing.expectEqual(1, it.root_len); try std.testing.expectEqual(4, it.root_end_index); try std.testing.expectEqualStrings("/", it.root().?); @@ -2489,7 +2489,7 @@ test "ComponentIterator posix" { { const path = "/"; - var it = PosixComponentIterator.init(path); + var it: PosixComponentIterator = .init(path); try std.testing.expectEqual(1, it.root_len); try std.testing.expectEqual(1, it.root_end_index); try std.testing.expectEqualStrings("/", it.root().?); @@ -2507,7 +2507,7 @@ test "ComponentIterator posix" { { const path = ""; - var it = PosixComponentIterator.init(path); + var it: PosixComponentIterator = .init(path); try std.testing.expectEqual(0, it.root_len); try std.testing.expectEqual(0, it.root_end_index); try std.testing.expect(null == it.root()); @@ -2528,7 +2528,7 @@ test "ComponentIterator windows" { const WindowsComponentIterator = ComponentIterator(.windows, u8); { const path = "a/b\\c//"; - var it = WindowsComponentIterator.init(path); + var it: WindowsComponentIterator = .init(path); try std.testing.expectEqual(0, it.root_len); try std.testing.expectEqual(0, it.root_end_index); try std.testing.expect(null == it.root()); @@ -2576,7 +2576,7 @@ test "ComponentIterator windows" { { const path = "C:\\a/b/c/"; - var it = WindowsComponentIterator.init(path); + var it: WindowsComponentIterator = .init(path); try std.testing.expectEqual(3, it.root_len); try std.testing.expectEqual(3, it.root_end_index); try std.testing.expectEqualStrings("C:\\", it.root().?); @@ -2614,7 +2614,7 @@ test "ComponentIterator windows" { { const path = "C:\\\\//a/\\/\\b///c////"; - var it = WindowsComponentIterator.init(path); + var it: WindowsComponentIterator = .init(path); try std.testing.expectEqual(3, it.root_len); try std.testing.expectEqual(6, it.root_end_index); try std.testing.expectEqualStrings("C:\\", it.root().?); @@ -2652,7 +2652,7 @@ test "ComponentIterator windows" { { const path = "/"; - var it = WindowsComponentIterator.init(path); + var it: WindowsComponentIterator = .init(path); try std.testing.expectEqual(1, it.root_len); try std.testing.expectEqual(1, it.root_end_index); try std.testing.expectEqualStrings("/", it.root().?); @@ -2670,7 +2670,7 @@ test "ComponentIterator windows" { { const path = ""; - var it = WindowsComponentIterator.init(path); + var it: WindowsComponentIterator = .init(path); try std.testing.expectEqual(0, it.root_len); try std.testing.expectEqual(0, it.root_end_index); try std.testing.expect(null == it.root()); @@ -2692,7 +2692,7 @@ test "ComponentIterator windows WTF-16" { const L = std.unicode.utf8ToUtf16LeStringLiteral; const path = L("C:\\a/b/c/"); - var it = WindowsComponentIterator.init(path); + var it: WindowsComponentIterator = .init(path); try std.testing.expectEqual(3, it.root_len); try std.testing.expectEqual(3, it.root_end_index); try std.testing.expectEqualSlices(u16, L("C:\\"), it.root().?); @@ -2731,59 +2731,59 @@ test "ComponentIterator windows WTF-16" { test "ComponentIterator roots" { // UEFI { - var it = ComponentIterator(.uefi, u8).init("\\\\a"); + var it: ComponentIterator(.uefi, u8) = .init("\\\\a"); try std.testing.expectEqualStrings("\\", it.root().?); - it = ComponentIterator(.uefi, u8).init("//a"); + it = .init("//a"); try std.testing.expect(null == it.root()); } // POSIX { - var it = ComponentIterator(.posix, u8).init("//a"); + var it: ComponentIterator(.posix, u8) = .init("//a"); try std.testing.expectEqualStrings("/", it.root().?); - it = ComponentIterator(.posix, u8).init("\\\\a"); + it = .init("\\\\a"); try std.testing.expect(null == it.root()); } // Windows { // Drive relative - var it = ComponentIterator(.windows, u8).init("C:a"); + var it: ComponentIterator(.windows, u8) = .init("C:a"); try std.testing.expectEqualStrings("C:", it.root().?); // Drive absolute - it = ComponentIterator(.windows, u8).init("C:/a"); + it = .init("C:/a"); try std.testing.expectEqualStrings("C:/", it.root().?); - it = ComponentIterator(.windows, u8).init("C:\\a"); + it = .init("C:\\a"); try std.testing.expectEqualStrings("C:\\", it.root().?); - it = ComponentIterator(.windows, u8).init("C:///a"); + it = .init("C:///a"); try std.testing.expectEqualStrings("C:/", it.root().?); // Rooted - it = ComponentIterator(.windows, u8).init("\\a"); + it = .init("\\a"); try std.testing.expectEqualStrings("\\", it.root().?); - it = ComponentIterator(.windows, u8).init("/a"); + it = .init("/a"); try std.testing.expectEqualStrings("/", it.root().?); // Root local device - it = ComponentIterator(.windows, u8).init("\\\\."); + it = .init("\\\\."); try std.testing.expectEqualStrings("\\\\.", it.root().?); - it = ComponentIterator(.windows, u8).init("//?"); + it = .init("//?"); try std.testing.expectEqualStrings("//?", it.root().?); // UNC absolute - it = ComponentIterator(.windows, u8).init("//"); + it = .init("//"); try std.testing.expectEqualStrings("//", it.root().?); - it = ComponentIterator(.windows, u8).init("\\\\a"); + it = .init("\\\\a"); try std.testing.expectEqualStrings("\\\\a", it.root().?); - it = ComponentIterator(.windows, u8).init("\\\\a\\b\\\\c"); + it = .init("\\\\a\\b\\\\c"); try std.testing.expectEqualStrings("\\\\a\\b\\", it.root().?); - it = ComponentIterator(.windows, u8).init("//a"); + it = .init("//a"); try std.testing.expectEqualStrings("//a", it.root().?); - it = ComponentIterator(.windows, u8).init("//a/b//c"); + it = .init("//a/b//c"); try std.testing.expectEqualStrings("//a/b/", it.root().?); // Malformed UNC path with empty server name - it = ComponentIterator(.windows, u8).init("\\\\\\a\\b\\c"); + it = .init("\\\\\\a\\b\\c"); try std.testing.expectEqualStrings("\\\\\\a\\", it.root().?); } } diff --git a/lib/std/heap/debug_allocator.zig b/lib/std/heap/debug_allocator.zig index 5d410fe92647d389304da9655537ae86911981ea..ccba352bb55e0e12bfd596d3a1f49458407a1c98 100644 --- a/lib/std/heap/debug_allocator.zig +++ b/lib/std/heap/debug_allocator.zig @@ -718,7 +718,7 @@ pub fn DebugAllocator(comptime config: Config) type { fn alloc(context: *anyopaque, len: usize, alignment: mem.Alignment, ret_addr: usize) ?[*]u8 { const self: *Self = @ptrCast(@alignCast(context)); - if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + if (have_mutex) std.Io.Threaded.mutexLockUncancelable(&self.mutex); defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); if (config.enable_memory_limit) { @@ -831,7 +831,7 @@ pub fn DebugAllocator(comptime config: Config) type { return_address: usize, ) bool { const self: *Self = @ptrCast(@alignCast(context)); - if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + if (have_mutex) std.Io.Threaded.mutexLockUncancelable(&self.mutex); defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); 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 { return_address: usize, ) ?[*]u8 { const self: *Self = @ptrCast(@alignCast(context)); - if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + if (have_mutex) std.Io.Threaded.mutexLockUncancelable(&self.mutex); defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); 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 { return_address: usize, ) void { const self: *Self = @ptrCast(@alignCast(context)); - if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + if (have_mutex) std.Io.Threaded.mutexLockUncancelable(&self.mutex); defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); const size_class_index: usize = @max(@bitSizeOf(usize) - @clz(old_memory.len - 1), @backingInt(alignment)); diff --git a/lib/std/lang.zig b/lib/std/lang.zig index fbe36760bca652bf37909ed033be562cdb2da489..b8dba4354d5d4dfa4fe05bf7786806e3e01dddbd 100644 --- a/lib/std/lang.zig +++ b/lib/std/lang.zig @@ -971,7 +971,7 @@ pub const Signedness = enum(u1) { /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. -pub const OutputMode = enum { +pub const OutputMode = enum(u2) { Exe, Lib, Obj, diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 0f6eaae0122b2dc59d2dcc1d0cd40a262686938b..5476c17147677692bbe2422a99f21456f11c88a1 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -147,9 +147,23 @@ pub const OBJECT = struct { pub const Max: @typeInfo(@This()).@"enum".tag_type = @typeInfo(@This()).@"enum".field_names.len; }; + pub const BASIC_INFORMATION = extern struct { + Attributes: ATTRIBUTES.Flags, + GrantedAccess: ACCESS_MASK, + HandleCount: ULONG, + PointerCount: ULONG, + Reserved: [10]ULONG, + }; + pub const NAME_INFORMATION = extern struct { Name: UNICODE_STRING, }; + + pub const HANDLE_FLAG = packed struct(USHORT) { + INHERIT: bool = false, + PROTECT_FROM_CLOSE: bool = false, + Reserved1: u14 = 0, + }; }; pub const FILE = struct { diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index 1f6151308ce389b7eed1686892a01cb5b668a69d..16b3f57f21a7f55f2ddb3188d180673022e7bee7 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -461,6 +461,13 @@ pub extern "ntdll" fn LdrUnregisterDllNotification( Cookie: LDR.DLL_NOTIFICATION.COOKIE, ) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtSetInformationObject( + Handle: HANDLE, + ObjectInformationClass: OBJECT.INFORMATION_CLASS, + ObjectInformation: *const anyopaque, + ObjectInformationLength: ULONG, +) callconv(.winapi) NTSTATUS; + pub extern "ntdll" fn NtQueryAttributesFile( ObjectAttributes: *const OBJECT.ATTRIBUTES, FileAttributes: *FILE.BASIC_INFORMATION, diff --git a/lib/std/process.zig b/lib/std/process.zig index 651d854edb623b25437e418640fa54f7f4e6c67a..ed4dceabdde0abee586683e90daddf144d78fff9 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -282,37 +282,50 @@ pub const ReplaceError = error{ } || Allocator.Error || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; pub const ReplaceOptions = struct { + exe: Exe = .detect, argv: []const []const u8, - expand_arg0: ArgExpansion = .no_expand, + + /// Set to change the current working directory when spawning the child process. + cwd: Child.Cwd = .inherit, /// Replaces the environment when provided. The PATH value from here is /// never used to resolve `argv[0]`. environ_map: ?*const Environ.Map = null, + expand_arg0: ArgExpansion = .no_expand, + + inherit_dirs: []const Dir = &.{}, + inherit_files: []const File = &.{}, + + /// Start child process in suspended state. + /// For Posix systems it's started as if SIGSTOP was sent. + start_suspended: bool = false, + + pub const Exe = union(enum) { + /// `argv[0]` is the name of the program to execute. If it is not already a + /// file path (i.e. it contains '/'), it is resolved into a file path based on + /// PATH from the parent environment. + detect, + /// `argv[0]` is the name of the program to execute, resolved into a file path + /// based on PATH from the parent environment. + search, + /// `argv[0]` is the file path of the program to execute, relative to `Dir` payload. + /// It is *always* treated as a file path, even if it does not contain '/'. + path: Dir, + /// `File` payload is the program to execute. + file: File, + /// `path` is the file path of the program to execute, relative to `dir`. + /// It is *always* treated as a file path, even if it does not contain '/'. + explicit: struct { dir: Dir, path: []const u8 }, + }; }; /// Replaces the current process image with the executed process. If this /// function succeeds, it does not return. /// -/// `argv[0]` is the name of the process to replace the current one with. If it -/// is not already a file path (i.e. it contains '/'), it is resolved into a -/// file path based on PATH from the parent environment. -/// /// It is illegal to call this function in a fork() child. pub fn replace(io: Io, options: ReplaceOptions) ReplaceError { return io.vtable.processReplace(io.userdata, options); } -/// Replaces the current process image with the executed process. If this -/// function succeeds, it does not return. -/// -/// `argv[0]` is the file path of the process to replace the current one with, -/// relative to `dir`. It is *always* treated as a file path, even if it does -/// not contain '/'. -/// -/// It is illegal to call this function in a fork() child. -pub fn replacePath(io: Io, dir: Io.Dir, options: ReplaceOptions) ReplaceError { - return io.vtable.processReplacePath(io.userdata, dir, options); -} - pub const ArgExpansion = enum { expand, no_expand }; /// File name extensions supported natively by `CreateProcess()` on Windows. @@ -368,6 +381,7 @@ pub const SpawnError = error{ } || Io.File.OpenError || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; pub const SpawnOptions = struct { + exe: ReplaceOptions.Exe = .detect, argv: []const []const u8, /// Set to change the current working directory when spawning the child process. @@ -385,11 +399,13 @@ pub const SpawnOptions = struct { /// /// The child's progress tree will be grafted into the parent's progress tree, /// by substituting this node with the child's root node. - progress_node: std.Progress.Node = std.Progress.Node.none, + progress_node: std.Progress.Node = .none, stdin: StdIo = .inherit, stdout: StdIo = .inherit, stderr: StdIo = .inherit, + inherit_dirs: []const Dir = &.{}, + inherit_files: []const File = &.{}, /// Set to true to obtain rusage information for the child process. /// Depending on the target platform and implementation status, the @@ -443,27 +459,16 @@ pub const SpawnOptions = struct { }; /// Creates a child process. -/// -/// `argv[0]` is the name of the program to execute. If it is not already a -/// file path (i.e. it contains '/'), it is resolved into a file path based on -/// PATH from the parent environment. pub fn spawn(io: Io, options: SpawnOptions) SpawnError!Child { return io.vtable.processSpawn(io.userdata, options); } -/// Creates a child process. -/// -/// `argv[0]` is the file path of the program to execute, relative to `dir`. It -/// is *always* treated as a file path, even if it does not contain '/'. -pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { - return io.vtable.processSpawnPath(io.userdata, dir, options); -} - pub const RunError = error{ StreamTooLong, } || SpawnError || Io.File.MultiReader.UnendingError || Io.Timeout.Error; pub const RunOptions = struct { + exe: ReplaceOptions.Exe = .detect, argv: []const []const u8, stderr_limit: Io.Limit = .unlimited, stdout_limit: Io.Limit = .unlimited, @@ -501,6 +506,7 @@ pub const RunResult = struct { /// If it succeeds, the caller owns result.stdout and result.stderr memory. pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { var child = try spawn(io, .{ + .exe = options.exe, .argv = options.argv, .cwd = options.cwd, .environ_map = options.environ_map, diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 286654f1bd123b8fc6732cc70a5017e970d00f68..5c6bfa52f700385cf2be4894b3f870d9b34cff81 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -727,8 +727,7 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *Io.File.Reader) ![ } pub fn printAstErrorsToStderr(gpa: Allocator, io: Io, tree: Ast, path: []const u8, color: Color) !void { - var wip_errors: ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); + var wip_errors: ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); try putAstErrorsIntoBundle(gpa, tree, path, &wip_errors); diff --git a/lib/std/zig/Client.zig b/lib/std/zig/Client.zig index cedda191b99042affaba76eec838fd9f332abdbb..786f23479de219f3fe4c2096af40bbb4cab63e60 100644 --- a/lib/std/zig/Client.zig +++ b/lib/std/zig/Client.zig @@ -60,6 +60,8 @@ pub const Message = struct { start_fuzzing, /// The message body has the same format as in Server. new_fuzz_input, + /// Body is a sequence of Arg + args, /// Asks the server to run a list of steps. /// Body is a `BuildSteps`. @@ -69,6 +71,33 @@ pub const Message = struct { _, }; + pub const Arg = enum(u8) { + /// Trailing: + /// * string: [:0]u8, + string, + /// Trailing: + /// * string: [:0]u8, + prefix, + /// Trailing: + /// * string: [:0]u8, + suffix, + /// Trailing: + /// * handle: Handle, + input_file, + /// Trailing: + /// * handle: Handle, + input_dir, + /// Trailing: + /// * handle: Handle, + input_file_content, + /// Trailing: + /// * handle: Handle, + output_file, + /// Trailing: + /// * handle: Handle, + output_dir, + }; + /// Trailing: /// * step_indices: [step_count]std.Build.Configuration.Step.Index, pub const BuildSteps = extern struct { diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index d6fde276f1e9fb328d262b90a91f0112c45afb77..4ce1af8fdee40685a819298333d4248ddb9abfa0 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -360,22 +360,25 @@ pub const Wip = struct { extra: std.ArrayList(u32), root_list: std.ArrayList(MessageIndex), - pub fn init(wip: *Wip, gpa: Allocator) !void { - wip.* = .{ + pub fn init(gpa: Allocator) !Wip { + var wip: Wip = .{ .gpa = gpa, .string_bytes = .empty, .extra = .empty, .root_list = .empty, }; + errdefer wip.deinit(); // So that 0 can be used to indicate a null string. try wip.string_bytes.append(gpa, 0); - assert(0 == try addExtra(wip, ErrorMessageList{ + assert(0 == try wip.addExtra(ErrorMessageList{ .len = 0, .start = 0, .compile_log_text = 0, })); + + return wip; } pub fn deinit(wip: *Wip) void { @@ -773,7 +776,7 @@ pub const Wip = struct { u32 => @field(extra, field_name), MessageIndex => @backingInt(@field(extra, field_name)), SourceLocationIndex => @backingInt(@field(extra, field_name)), - else => @compileError("bad field type"), + else => @compileError("bad field type: " ++ @typeName(field_type)), }; i += 1; } diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index 4c7e3a85441554dba1a6fe6d92b530d90edcbf39..7fb59ac5a45a762d5bfc38f979e883f765a01c9c 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -74,6 +74,12 @@ pub const Message = struct { broadcast_fuzz_input, /// Body is a TimeReport. time_report, + /// Body is a sequence of: + /// - root_dir: InputDir, + /// - sub_path: [:0]u8, + discovered_inputs, + /// Body is a Config. + config, /// The first message sent by the server over the build system protocol. /// Body is a `Handshake`. @@ -112,7 +118,7 @@ pub const Message = struct { pub const Flags = packed struct(u32) { file_system_watch_supported: bool, - _: u31 = 0, + unused: u31 = 0, }; }; @@ -198,7 +204,7 @@ pub const Message = struct { flags: Flags, pub const Flags = packed struct(u32) { use_llvm: bool, - _: u31 = 0, + unused: u31 = 0, }; }; @@ -212,6 +218,29 @@ pub const Message = struct { reserved: u7 = 0, }; }; + + /// A reference to one of the `.input_dir`s from the `.args` message. + pub const InputDir = enum(u32) { + /// Server cwd relative path, or absolute path. + cwd, + /// Numbered in the order that `.input_dir`s appear in the `.args` message. + _, + }; + + /// Properties of the output binary target that are decided by the compiler. + pub const Config = extern struct { + flags: Flags, + + pub const Flags = packed struct(u8) { + output_mode: std.lang.OutputMode, + link_mode: std.lang.LinkMode, + link_libc: bool, + link_libcpp: bool, + link_libunwind: bool, + pie: bool, + unused: u1 = 0, + }; + }; }; pub fn receiveMessage(s: *Server) !InMessage.Header { @@ -365,3 +394,12 @@ pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void { try s.out.writeAll(test_metadata.string_bytes); try s.out.flush(); } + +pub fn serveConfig(s: *Server, config: OutMessage.Config) !void { + try s.serveMessageHeader(.{ + .tag = .config, + .bytes_len = @intCast(@sizeOf(OutMessage.Config)), + }); + try s.out.writeStruct(config, .little); + try s.out.flush(); +} diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 857ae1d2ff1c35feac45eb5b2f40cca4986d74f3..41de483284a972e2f6e87668b739fba5592415ae 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -33,13 +33,9 @@ pub const Executor = union(enum) { pub const GetExternalExecutorOptions = struct { host_cpu_arch: std.Target.Cpu.Arch, host_os_tag: std.Target.Os.Tag, - allow_darling: bool = true, - allow_qemu: bool = true, - allow_rosetta: bool = true, - allow_wasmtime: bool = true, - allow_wine: bool = true, qemu_fixes_dl: bool = false, - link_libc: bool = false, + link_mode: std.lang.LinkMode, + link_libc: bool, }; /// 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 var bad_result: Executor = .bad_os_or_cpu; if (os_match and cpu_ok) native: { - if (options.link_libc) { + if (options.link_libc and options.link_mode == .dynamic) { if (candidate.dynamic_linker.get()) |candidate_dl| { Io.Dir.cwd().access(io, candidate_dl, .{}) catch { bad_result = .{ .bad_dl = candidate_dl }; @@ -84,9 +80,7 @@ pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExt // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2 // to emulate the foreign architecture. - if (options.allow_rosetta and os_match and - (host_os_tag == .maccatalyst or host_os_tag == .macos) and host_cpu_arch == .aarch64) - { + if (os_match and (host_os_tag == .maccatalyst or host_os_tag == .macos) and host_cpu_arch == .aarch64) { switch (candidate.cpu.arch) { .x86_64 => return .rosetta, else => return bad_result, @@ -94,7 +88,7 @@ pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExt } // If the OS matches, we can use QEMU to emulate a foreign architecture. - if (options.allow_qemu and os_match and (!cpu_ok or options.qemu_fixes_dl)) { + if (os_match and (!cpu_ok or options.qemu_fixes_dl)) { return switch (candidate.cpu.arch) { inline .aarch64, .arm, @@ -167,38 +161,32 @@ pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExt }; } - if (options.allow_wasmtime and candidate.cpu.arch.isWasm()) { + if (candidate.cpu.arch.isWasm()) { return .{ .wasmtime = "wasmtime" }; } switch (candidate.os.tag) { .windows => { - if (options.allow_wine) { - const wine_supported = switch (candidate.cpu.arch) { - .thumb => switch (host_cpu_arch) { - .arm, .thumb, .aarch64 => true, - else => false, - }, - .aarch64 => host_cpu_arch == .aarch64, - .x86 => host_cpu_arch.isX86(), - .x86_64 => host_cpu_arch == .x86_64, + const wine_supported = switch (candidate.cpu.arch) { + .thumb => switch (host_cpu_arch) { + .arm, .thumb, .aarch64 => true, else => false, - }; - return if (wine_supported) .{ .wine = "wine" } else bad_result; - } - return bad_result; + }, + .aarch64 => host_cpu_arch == .aarch64, + .x86 => host_cpu_arch.isX86(), + .x86_64 => host_cpu_arch == .x86_64, + else => false, + }; + return if (wine_supported) .{ .wine = "wine" } else bad_result; }, .driverkit, .macos => { - if (options.allow_darling) { - // This check can be loosened once darling adds a QEMU-based emulation - // layer for non-host architectures: - // https://github.com/darlinghq/darling/issues/863 - if (candidate.cpu.arch != host_cpu_arch) { - return bad_result; - } - return .{ .darling = "darling" }; + // This check can be loosened once darling adds a QEMU-based emulation + // layer for non-host architectures: + // https://github.com/darlinghq/darling/issues/863 + if (candidate.cpu.arch != host_cpu_arch) { + return bad_result; } - return bad_result; + return .{ .darling = "darling" }; }, else => return bad_result, } diff --git a/src/Compilation.zig b/src/Compilation.zig index 823763cef6f0af734eb86fcd1de4a1dd316e0025..34aa59e063d05bf2b0676b6462ca470fb9b02407 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3842,8 +3842,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle { const gpa = comp.gpa; const io = comp.io; - var bundle: ErrorBundle.Wip = undefined; - try bundle.init(gpa); + var bundle: ErrorBundle.Wip = try .init(gpa); defer bundle.deinit(); for (comp.failed_c_objects.values()) |diag_bundle| { @@ -5447,8 +5446,7 @@ fn reportRetryableWin32ResourceError( win32_resource.status = .failure_retryable; - var bundle: ErrorBundle.Wip = undefined; - try bundle.init(comp.gpa); + var bundle: ErrorBundle.Wip = try .init(comp.gpa); errdefer bundle.deinit(); try bundle.addRootErrorMessage(.{ .msg = try bundle.printString("{s}", .{@errorName(err)}), @@ -6835,8 +6833,7 @@ fn failCObjWithOwnedDiagBundle( fn failWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, comptime format: []const u8, args: anytype) error{ OutOfMemory, AlreadyReported } { @branchHint(.cold); - var bundle: ErrorBundle.Wip = undefined; - try bundle.init(comp.gpa); + var bundle: ErrorBundle.Wip = try .init(comp.gpa); errdefer bundle.deinit(); try bundle.addRootErrorMessage(.{ .msg = try bundle.printString(format, args), diff --git a/src/Sema.zig b/src/Sema.zig index a1d30147758234ed15ba84cbe71a85494ac574aa..e209f453cae9ceb95b5d222fb281d5e3bff7da2e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2705,8 +2705,7 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg assert(sema.err == null); if (build_options.enable_debug_extensions and comp.debug_compile_errors) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - wip_errors.init(gpa) catch @panic("out of memory"); + var wip_errors = std.zig.ErrorBundle.Wip.init(gpa) catch @panic("out of memory"); Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*, false) catch @panic("out of memory"); std.debug.print("compile error during Sema:\n", .{}); var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory"); diff --git a/src/fmt.zig b/src/fmt.zig index 769e9ab3fc1e007b70d413868f103d386f4e0419..8bb5582cbea0e506e66a6b1c52f381b81222bc2e 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -130,8 +130,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! defer zir.deinit(gpa); if (zir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); try wip_errors.addZirErrorMessages(zir, tree, source_code, ""); var error_bundle = try wip_errors.toOwnedBundle(""); @@ -144,8 +143,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! defer zoir.deinit(gpa); if (zoir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); try wip_errors.addZoirErrorMessages(zoir, tree, source_code, ""); var error_bundle = try wip_errors.toOwnedBundle(""); @@ -333,8 +331,7 @@ fn fmtPathFile( defer zir.deinit(gpa); if (zir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path); var error_bundle = try wip_errors.toOwnedBundle(""); @@ -348,8 +345,7 @@ fn fmtPathFile( defer zoir.deinit(gpa); if (zoir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); try wip_errors.addZoirErrorMessages(zoir, tree, source_code, file_path); var error_bundle = try wip_errors.toOwnedBundle(""); diff --git a/src/link.zig b/src/link.zig index c1ad89e817611968fa3c61b12cd5b19ad317fdfe..ab03353da50b05e487370c70bf3b564431f83893 100644 --- a/src/link.zig +++ b/src/link.zig @@ -2891,8 +2891,7 @@ fn resolvePathInputLib( const ld_script_result = LdScript.parse(gpa, &diags, test_path, ld_script_bytes.items); if (diags.hasErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); try diags.addMessagesToBundle(&wip_errors, null); diff --git a/src/main.zig b/src/main.zig index 34ff983b0328f7d603ae0ad8e456ab5d0b993634..7d9d7e8ef506177405d5b2e1c859ad9afbc0d66d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3971,11 +3971,9 @@ fn buildOutputType( test_exec_args.items, self_exe_path_or_argv0, arg_mode, - target, &comp_destroyed, all_args, runtime_args_start, - create_module.resolved_options.link_libc, test_execve, environ_map, ); @@ -4410,6 +4408,15 @@ fn serve( var server: Server = .{ .in = in, .out = out }; try server.serveStringMessage(.zig_version, build_options.version); + try server.serveConfig(.{ .flags = .{ + .output_mode = comp.config.output_mode, + .link_mode = comp.config.link_mode, + .link_libc = comp.config.link_libc, + .link_libcpp = comp.config.link_libcpp, + .link_libunwind = comp.config.link_libunwind, + .pie = comp.config.pie, + } }); + var child_pid: ?std.process.Child.Id = null; const main_progress_node = std.Progress.start(io, .{}); @@ -4482,8 +4489,6 @@ fn serve( // test_exec_args, // self_exe_path.?, // arg_mode, - // target, - // true, // &comp_destroyed, // all_args, // runtime_args_start, @@ -4634,11 +4639,9 @@ fn runOrTest( test_exec_args: []const ?[]const u8, self_exe_path: []const u8, arg_mode: ArgMode, - target: *const std.Target, comp_destroyed: *bool, all_args: []const []const u8, runtime_args_start: ?usize, - link_libc: bool, test_execve: bool, environ_map: *process.Environ.Map, ) !void { @@ -4686,7 +4689,12 @@ fn runOrTest( _ = try io.lockStderr(&.{}, .no_color); const err = process.replace(io, .{ .argv = argv.items, .environ_map = environ_map }); io.unlockStderr(); - try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc); + try warnAboutForeignBinaries(io, arena, .{ + .arg_mode = arg_mode, + .target = comp.getTarget(), + .link_mode = comp.config.link_mode, + .link_libc = comp.config.link_libc, + }); const cmd = try std.mem.join(arena, " ", argv.items); fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd }); } else if (!process.can_spawn) { @@ -4716,7 +4724,12 @@ fn runOrTest( }); const term = term_result catch |err| { - try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc); + try warnAboutForeignBinaries(io, arena, .{ + .arg_mode = arg_mode, + .target = comp.getTarget(), + .link_mode = comp.config.link_mode, + .link_libc = comp.config.link_libc, + }); const cmd = try std.mem.join(arena, " ", argv.items); fatal("the following command failed with {t}:\n{s}", .{ err, cmd }); }; @@ -5647,8 +5660,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8, environ_map: const zir = try AstGen.generate(arena, tree); if (zir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(arena); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena); try wip_errors.addZirErrorMessages(zir, tree, source, display_path); var error_bundle = try wip_errors.toOwnedBundle(""); try error_bundle.renderToStderr(io, .{}, color); @@ -5718,8 +5730,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8, environ_map: .zon => { const zoir = try ZonGen.generate(arena, tree, .{}); if (zoir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(arena); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena); try wip_errors.addZoirErrorMessages(zoir, tree, source, display_path); var error_bundle = try wip_errors.toOwnedBundle(""); error_bundle.renderToStderr(io, .{}, color) catch {}; @@ -5812,8 +5823,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8, environ_map const old_zir = try AstGen.generate(arena, old_tree); if (old_zir.loweringFailed()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(arena); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena); try wip_errors.addZirErrorMessages(old_zir, old_tree, old_source, old_source_path); var error_bundle = try wip_errors.toOwnedBundle(""); error_bundle.renderToStderr(io, .{}, color) catch {}; @@ -5824,8 +5834,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8, environ_map const new_zir = try AstGen.generate(arena, new_tree); if (new_zir.loweringFailed()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(arena); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena); try wip_errors.addZirErrorMessages(new_zir, new_tree, new_source, new_source_path); var error_bundle = try wip_errors.toOwnedBundle(""); error_bundle.renderToStderr(io, .{}, color) catch {}; @@ -5867,33 +5876,32 @@ fn prefixedIntArg(arg: []const u8, prefix: []const u8) ?u64 { return std.fmt.parseUnsigned(u64, number, 0) catch |err| fatal("unable to parse {q}: {t}", .{ arg, err }); } -fn warnAboutForeignBinaries( - io: Io, - arena: Allocator, +fn warnAboutForeignBinaries(io: Io, arena: Allocator, opts: struct { arg_mode: ArgMode, target: *const std.Target, + link_mode: std.lang.LinkMode, link_libc: bool, -) !void { - const host_query: std.Target.Query = .{}; - const host_target = std.zig.resolveTargetQueryOrFatal(io, host_query); +}) !void { + const host_target = std.zig.resolveTargetQueryOrFatal(io, .{}); - switch (std.zig.system.getExternalExecutor(io, target, .{ + switch (std.zig.system.getExternalExecutor(io, opts.target, .{ .host_cpu_arch = host_target.cpu.arch, .host_os_tag = host_target.os.tag, - .link_libc = link_libc, + .link_mode = opts.link_mode, + .link_libc = opts.link_libc, })) { .native => return, .rosetta => { const host_name = try host_target.zigTriple(arena); - const foreign_name = try target.zigTriple(arena); + const foreign_name = try opts.target.zigTriple(arena); warn("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}). Consider installing Rosetta.", .{ host_name, foreign_name, }); }, .qemu => |qemu| { const host_name = try host_target.zigTriple(arena); - const foreign_name = try target.zigTriple(arena); - switch (arg_mode) { + const foreign_name = try opts.target.zigTriple(arena); + switch (opts.arg_mode) { .zig_test => warn( "the host system ({s}) does not appear to be capable of executing binaries " ++ "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++ @@ -5909,8 +5917,8 @@ fn warnAboutForeignBinaries( }, .wine => |wine| { const host_name = try host_target.zigTriple(arena); - const foreign_name = try target.zigTriple(arena); - switch (arg_mode) { + const foreign_name = try opts.target.zigTriple(arena); + switch (opts.arg_mode) { .zig_test => warn( "the host system ({s}) does not appear to be capable of executing binaries " ++ "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++ @@ -5926,8 +5934,8 @@ fn warnAboutForeignBinaries( }, .wasmtime => |wasmtime| { const host_name = try host_target.zigTriple(arena); - const foreign_name = try target.zigTriple(arena); - switch (arg_mode) { + const foreign_name = try opts.target.zigTriple(arena); + switch (opts.arg_mode) { .zig_test => warn( "the host system ({s}) does not appear to be capable of executing binaries " ++ "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++ @@ -5943,8 +5951,8 @@ fn warnAboutForeignBinaries( }, .darling => |darling| { const host_name = try host_target.zigTriple(arena); - const foreign_name = try target.zigTriple(arena); - switch (arg_mode) { + const foreign_name = try opts.target.zigTriple(arena); + switch (opts.arg_mode) { .zig_test => warn( "the host system ({s}) does not appear to be capable of executing binaries " ++ "from the target ({s}). Consider using '--test-cmd {s} --test-cmd-bin' " ++ @@ -5960,7 +5968,7 @@ fn warnAboutForeignBinaries( }, .bad_dl => |foreign_dl| { const host_dl = host_target.dynamic_linker.get() orelse "(none)"; - const tip_suffix = switch (arg_mode) { + const tip_suffix = switch (opts.arg_mode) { .zig_test => ", '--test-no-exec', or '--test-cmd'", else => "", }; @@ -5970,8 +5978,8 @@ fn warnAboutForeignBinaries( }, .bad_os_or_cpu => { const host_name = try host_target.zigTriple(arena); - const foreign_name = try target.zigTriple(arena); - const tip_suffix = switch (arg_mode) { + const foreign_name = try opts.target.zigTriple(arena); + const tip_suffix = switch (opts.arg_mode) { .zig_test => ". Consider using '--test-no-exec' or '--test-cmd'", else => "", }; diff --git a/test/incremental/add_decl b/test/incremental/add_decl index 50d961da7b88f00b78b2895ebfeae18e76219d6a..5832015217c571a79a2bc5ca0ab45abf91d7a156 100644 --- a/test/incremental/add_decl +++ b/test/incremental/add_decl @@ -1,15 +1,21 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, foo); } const foo = "good morning\n"; const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="good morning\n" +#} +#update +#check stdout { +good morning +#} -#update=add new declaration -#file=main.zig +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, foo); @@ -17,10 +23,14 @@ pub fn main() !void { const foo = "good morning\n"; const bar = "good evening\n"; const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="good morning\n" +#} +#update { +add new declaration#} +#check stdout { +good morning +#} -#update=reference new declaration -#file=main.zig +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, bar); @@ -28,10 +38,14 @@ pub fn main() !void { const foo = "good morning\n"; const bar = "good evening\n"; const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="good evening\n" +#} +#update { +reference new declaration#} +#check stdout { +good evening +#} -#update=reference missing declaration -#file=main.zig +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, qux); @@ -39,10 +53,14 @@ pub fn main() !void { const foo = "good morning\n"; const bar = "good evening\n"; const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:3:52: error: use of undeclared identifier 'qux' +#} +#update { +reference missing declaration#} +#check errors { +main.zig:3:52: error: use of undeclared identifier 'qux' +#} -#update=add missing declaration -#file=main.zig +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, qux); @@ -51,14 +69,23 @@ const foo = "good morning\n"; const bar = "good evening\n"; const qux = "good night\n"; const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="good night\n" +#} +#update { +add missing declaration#} +#check stdout { +good night +#} -#update=remove unused declarations -#file=main.zig +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, qux); } const qux = "good night\n"; const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="good night\n" +#} +#update { +remove unused declarations#} +#check stdout { +good night +#} diff --git a/test/incremental/add_decl_namespaced b/test/incremental/add_decl_namespaced index e1fa74a3ad7ecd6bb70b481f73d6db0702c27d22..30bf6a81e9abb3449ad1575a20022b7d6f0d279f 100644 --- a/test/incremental/add_decl_namespaced +++ b/test/incremental/add_decl_namespaced @@ -1,15 +1,21 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, @This().foo); } const foo = "good morning\n"; const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="good morning\n" +#} +#update +#check stdout { +good morning +#} -#update=add new declaration -#file=main.zig +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, @This().foo); @@ -17,10 +23,14 @@ pub fn main() !void { const foo = "good morning\n"; const bar = "good evening\n"; const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="good morning\n" +#} +#update { +add new declaration#} +#check stdout { +good morning +#} -#update=reference new declaration -#file=main.zig +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, @This().bar); @@ -28,10 +38,14 @@ pub fn main() !void { const foo = "good morning\n"; const bar = "good evening\n"; const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="good evening\n" +#} +#update { +reference new declaration#} +#check stdout { +good evening +#} -#update=reference missing declaration -#file=main.zig +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, @This().qux); @@ -39,11 +53,15 @@ pub fn main() !void { const foo = "good morning\n"; const bar = "good evening\n"; const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:3:59: error: root source file struct 'main' has no member named 'qux' -#expect_error=main.zig:1:1: note: struct declared here +#} +#update { +reference missing declaration#} +#check errors { +main.zig:3:59: error: root source file struct 'main' has no member named 'qux' +main.zig:1:1: note: struct declared here +#} -#update=add missing declaration -#file=main.zig +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, @This().qux); @@ -52,14 +70,23 @@ const foo = "good morning\n"; const bar = "good evening\n"; const qux = "good night\n"; const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="good night\n" +#} +#update { +add missing declaration#} +#check stdout { +good night +#} -#update=remove unused declarations -#file=main.zig +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, @This().qux); } const qux = "good night\n"; const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="good night\n" +#} +#update { +remove unused declarations#} +#check stdout { +good night +#} diff --git a/test/incremental/add_field_and_nested_struct_uses_changed_decl b/test/incremental/add_field_and_nested_struct_uses_changed_decl index 35882c10a3fcda7d06a2ca968226e3c545d29c12..731d34837bc82b9b3ffac774943f53ffad77d468 100644 --- a/test/incremental/add_field_and_nested_struct_uses_changed_decl +++ b/test/incremental/add_field_and_nested_struct_uses_changed_decl @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { pub fn main() void { _ = @as(S, undefined); } @@ -9,9 +12,12 @@ const SomeType = u8; const S = struct { foo: struct { inner: SomeType }, }; -#expect_stdout="" -#update=add field to outer struct, change decl used by inner struct -#file=main.zig +#} +#update +#check stdout { +#} + +#write main.zig { pub fn main() void { _ = @as(S, undefined); } @@ -22,4 +28,8 @@ const S = struct { foo: struct { inner: SomeType }, bar: u32, }; -#expect_stdout="" +#} +#update { +add field to outer struct, change decl used by inner struct#} +#check stdout { +#} diff --git a/test/incremental/add_remove_struct_fields b/test/incremental/add_remove_struct_fields deleted file mode 100644 index f5e62fad787f9c2e96a2cdcf476173732cfe32f6..0000000000000000000000000000000000000000 --- a/test/incremental/add_remove_struct_fields +++ /dev/null @@ -1,93 +0,0 @@ -#update=initial version -#file=main.zig -const S = struct { x: u8 }; -pub fn main(init: std.process.Init) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); - printFieldCount(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; - printOneField(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; -} -fn printFieldCount(w: *Writer) Writer.Error!void { - try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); -} -fn printOneField(w: *Writer) Writer.Error!void { - const val: S = .{ .x = 100 }; - try w.print("{d}\n", .{val.x}); -} -const std = @import("std"); -const Writer = std.Io.Writer; -#expect_stdout="1 100\n" - -#update=add a field -#file=main.zig -const S = struct { x: u8, y: u16 = 200 }; -pub fn main(init: std.process.Init) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); - printFieldCount(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; - printOneField(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; -} -fn printFieldCount(w: *Writer) Writer.Error!void { - try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); -} -fn printOneField(w: *Writer) Writer.Error!void { - const val: S = .{ .x = 100 }; - try w.print("{d}\n", .{val.x}); -} -const std = @import("std"); -const Writer = std.Io.Writer; -#expect_stdout="2 100\n" - -#update=remove all fields -#file=main.zig -const S = struct {}; -pub fn main(init: std.process.Init) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); - printFieldCount(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; - printOneField(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; -} -fn printFieldCount(w: *Writer) Writer.Error!void { - try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); -} -fn printOneField(w: *Writer) Writer.Error!void { - const val: S = .{ .x = 100 }; - try w.print("{d}\n", .{val.x}); -} -const std = @import("std"); -const Writer = std.Io.Writer; -#expect_error=main.zig:15:24: error: no field named 'x' in struct 'main.S' -#expect_error=main.zig:1:11: note: struct declared here - -#update=remove reference to non-existent field -#file=main.zig -const S = struct {}; -pub fn main(init: std.process.Init) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); - printFieldCount(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; - printOneField(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; -} -fn printFieldCount(w: *Writer) Writer.Error!void { - try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); -} -fn printOneField(w: *Writer) Writer.Error!void { - //const val: S = .{ .x = 100 }; - //try w.print("{d}\n", .{val.x}); - try w.writeAll("\n"); -} -const std = @import("std"); -const Writer = std.Io.Writer; -#expect_stdout="0 \n" diff --git a/test/incremental/add_remove_struct_fields/0/main.zig b/test/incremental/add_remove_struct_fields/0/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..0481e7418f5889de45d38cb7033e498831d8305a --- /dev/null +++ b/test/incremental/add_remove_struct_fields/0/main.zig @@ -0,0 +1,19 @@ +const S = struct { x: u8 }; +pub fn main(init: std.process.Init) !void { + var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); + printFieldCount(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; + printOneField(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; +} +fn printFieldCount(w: *Writer) Writer.Error!void { + try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); +} +fn printOneField(w: *Writer) Writer.Error!void { + const val: S = .{ .x = 100 }; + try w.print("{d}\n", .{val.x}); +} +const std = @import("std"); +const Writer = std.Io.Writer; diff --git a/test/incremental/add_remove_struct_fields/1/main.zig b/test/incremental/add_remove_struct_fields/1/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..82305cdf3a00d0d56681a196b94e8cfff72710bd --- /dev/null +++ b/test/incremental/add_remove_struct_fields/1/main.zig @@ -0,0 +1,19 @@ +const S = struct { x: u8, y: u16 = 200 }; +pub fn main(init: std.process.Init) !void { + var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); + printFieldCount(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; + printOneField(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; +} +fn printFieldCount(w: *Writer) Writer.Error!void { + try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); +} +fn printOneField(w: *Writer) Writer.Error!void { + const val: S = .{ .x = 100 }; + try w.print("{d}\n", .{val.x}); +} +const std = @import("std"); +const Writer = std.Io.Writer; diff --git a/test/incremental/add_remove_struct_fields/2/main.zig b/test/incremental/add_remove_struct_fields/2/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..b490c06b2315fce7bbbd8c9c24729adcb6e12084 --- /dev/null +++ b/test/incremental/add_remove_struct_fields/2/main.zig @@ -0,0 +1,19 @@ +const S = struct {}; +pub fn main(init: std.process.Init) !void { + var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); + printFieldCount(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; + printOneField(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; +} +fn printFieldCount(w: *Writer) Writer.Error!void { + try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); +} +fn printOneField(w: *Writer) Writer.Error!void { + const val: S = .{ .x = 100 }; + try w.print("{d}\n", .{val.x}); +} +const std = @import("std"); +const Writer = std.Io.Writer; diff --git a/test/incremental/add_remove_struct_fields/3/main.zig b/test/incremental/add_remove_struct_fields/3/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..62ca3461d6ee668ee6dbd046e127b8fafead8f8b --- /dev/null +++ b/test/incremental/add_remove_struct_fields/3/main.zig @@ -0,0 +1,20 @@ +const S = struct {}; +pub fn main(init: std.process.Init) !void { + var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); + printFieldCount(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; + printOneField(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; +} +fn printFieldCount(w: *Writer) Writer.Error!void { + try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); +} +fn printOneField(w: *Writer) Writer.Error!void { + //const val: S = .{ .x = 100 }; + //try w.print("{d}\n", .{val.x}); + try w.writeAll("\n"); +} +const std = @import("std"); +const Writer = std.Io.Writer; diff --git a/test/incremental/add_remove_struct_fields/manifest b/test/incremental/add_remove_struct_fields/manifest new file mode 100644 index 0000000000000000000000000000000000000000..2099e73cef2d89db7446cfe1c9f3a82057332248 --- /dev/null +++ b/test/incremental/add_remove_struct_fields/manifest @@ -0,0 +1,31 @@ +#exe { +-Mroot=main.zig +#} + +#write main.zig 0/main.zig +#update +#check stdout { +1 100 +#} + +#write main.zig 1/main.zig +#update { +add a field#} +#check stdout { +2 100 +#} + +#write main.zig 2/main.zig +#update { +remove all fields#} +#check errors { +main.zig:15:24: error: no field named 'x' in struct 'main.S' +main.zig:1:11: note: struct declared here +#} + +#write main.zig 3/main.zig +#update { +remove reference to non-existent field#} +#check stdout { +0 +#} diff --git a/test/incremental/add_remove_toplevel_fields b/test/incremental/add_remove_toplevel_fields deleted file mode 100644 index e7ba9a2e892d343b08f90016a545252d04b04d5c..0000000000000000000000000000000000000000 --- a/test/incremental/add_remove_toplevel_fields +++ /dev/null @@ -1,96 +0,0 @@ -#update=initial version -#file=main.zig -const S = @This(); -x: u8, -pub fn main(init: std.process.Init) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); - printFieldCount(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; - printOneField(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; -} -fn printFieldCount(w: *Writer) Writer.Error!void { - try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); -} -fn printOneField(w: *Writer) Writer.Error!void { - const val: S = .{ .x = 100 }; - try w.print("{d}\n", .{val.x}); -} -const std = @import("std"); -const Writer = std.Io.Writer; -#expect_stdout="1 100\n" - -#update=add a field -#file=main.zig -const S = @This(); -x: u8, -y: u16 = 200, -pub fn main(init: std.process.Init) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); - printFieldCount(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; - printOneField(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; -} -fn printFieldCount(w: *Writer) Writer.Error!void { - try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); -} -fn printOneField(w: *Writer) Writer.Error!void { - const val: S = .{ .x = 100 }; - try w.print("{d}\n", .{val.x}); -} -const std = @import("std"); -const Writer = std.Io.Writer; -#expect_stdout="2 100\n" - -#update=remove all fields -#file=main.zig -const S = @This(); -pub fn main(init: std.process.Init) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); - printFieldCount(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; - printOneField(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; -} -fn printFieldCount(w: *Writer) Writer.Error!void { - try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); -} -fn printOneField(w: *Writer) Writer.Error!void { - const val: S = .{ .x = 100 }; - try w.print("{d}\n", .{val.x}); -} -const std = @import("std"); -const Writer = std.Io.Writer; -#expect_error=main.zig:15:24: error: no field named 'x' in struct 'main' -#expect_error=main.zig:1:1: note: struct declared here - -#update=remove reference to non-existent field -#file=main.zig -const S = @This(); -pub fn main(init: std.process.Init) !void { - var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); - printFieldCount(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; - printOneField(&stdout_writer.interface) catch |err| switch (err) { - error.WriteFailed => return stdout_writer.err.?, - }; -} -fn printFieldCount(w: *Writer) Writer.Error!void { - try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); -} -fn printOneField(w: *Writer) Writer.Error!void { - //const val: S = .{ .x = 100 }; - //try w.print("{d}\n", .{val.x}); - try w.writeAll("\n"); -} -const std = @import("std"); -const Writer = std.Io.Writer; -#expect_stdout="0 \n" diff --git a/test/incremental/add_remove_toplevel_fields/0/main.zig b/test/incremental/add_remove_toplevel_fields/0/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..72823f713cab4244b581f51ca442a7ae57ece762 --- /dev/null +++ b/test/incremental/add_remove_toplevel_fields/0/main.zig @@ -0,0 +1,20 @@ +const S = @This(); +x: u8, +pub fn main(init: std.process.Init) !void { + var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); + printFieldCount(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; + printOneField(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; +} +fn printFieldCount(w: *Writer) Writer.Error!void { + try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); +} +fn printOneField(w: *Writer) Writer.Error!void { + const val: S = .{ .x = 100 }; + try w.print("{d}\n", .{val.x}); +} +const std = @import("std"); +const Writer = std.Io.Writer; diff --git a/test/incremental/add_remove_toplevel_fields/1/main.zig b/test/incremental/add_remove_toplevel_fields/1/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..7fecd0bb563d9962f8cbc4830f714259e34d393b --- /dev/null +++ b/test/incremental/add_remove_toplevel_fields/1/main.zig @@ -0,0 +1,21 @@ +const S = @This(); +x: u8, +y: u16 = 200, +pub fn main(init: std.process.Init) !void { + var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); + printFieldCount(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; + printOneField(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; +} +fn printFieldCount(w: *Writer) Writer.Error!void { + try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); +} +fn printOneField(w: *Writer) Writer.Error!void { + const val: S = .{ .x = 100 }; + try w.print("{d}\n", .{val.x}); +} +const std = @import("std"); +const Writer = std.Io.Writer; diff --git a/test/incremental/add_remove_toplevel_fields/2/main.zig b/test/incremental/add_remove_toplevel_fields/2/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..cdba934cc767e1ab7e9ed7e5ff9a4a43db164981 --- /dev/null +++ b/test/incremental/add_remove_toplevel_fields/2/main.zig @@ -0,0 +1,19 @@ +const S = @This(); +pub fn main(init: std.process.Init) !void { + var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); + printFieldCount(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; + printOneField(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; +} +fn printFieldCount(w: *Writer) Writer.Error!void { + try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); +} +fn printOneField(w: *Writer) Writer.Error!void { + const val: S = .{ .x = 100 }; + try w.print("{d}\n", .{val.x}); +} +const std = @import("std"); +const Writer = std.Io.Writer; diff --git a/test/incremental/add_remove_toplevel_fields/3/main.zig b/test/incremental/add_remove_toplevel_fields/3/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..495c87c2b1aeeefe9628072bb2e2f325f8c82325 --- /dev/null +++ b/test/incremental/add_remove_toplevel_fields/3/main.zig @@ -0,0 +1,20 @@ +const S = @This(); +pub fn main(init: std.process.Init) !void { + var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); + printFieldCount(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; + printOneField(&stdout_writer.interface) catch |err| switch (err) { + error.WriteFailed => return stdout_writer.err.?, + }; +} +fn printFieldCount(w: *Writer) Writer.Error!void { + try w.print("{d} ", .{@typeInfo(S).@"struct".field_names.len}); +} +fn printOneField(w: *Writer) Writer.Error!void { + //const val: S = .{ .x = 100 }; + //try w.print("{d}\n", .{val.x}); + try w.writeAll("\n"); +} +const std = @import("std"); +const Writer = std.Io.Writer; diff --git a/test/incremental/add_remove_toplevel_fields/manifest b/test/incremental/add_remove_toplevel_fields/manifest new file mode 100644 index 0000000000000000000000000000000000000000..569ee7527b51e75d6a214d260b4206b1c5dd2974 --- /dev/null +++ b/test/incremental/add_remove_toplevel_fields/manifest @@ -0,0 +1,31 @@ +#exe { +-Mroot=main.zig +#} + +#write main.zig 0/main.zig +#update +#check stdout { +1 100 +#} + +#write main.zig 1/main.zig +#update { +add a field#} +#check stdout { +2 100 +#} + +#write main.zig 2/main.zig +#update { +remove all fields#} +#check errors { +main.zig:15:24: error: no field named 'x' in struct 'main' +main.zig:1:1: note: struct declared here +#} + +#write main.zig 3/main.zig +#update { +remove reference to non-existent field#} +#check stdout { +0 +#} diff --git a/test/incremental/analysis_error_and_syntax_error b/test/incremental/analysis_error_and_syntax_error index 6c703db7545fd51153794b18ab696e25851bf8a5..5a2246ee8a8cbc2da6512e70830d8927a676d75b 100644 --- a/test/incremental/analysis_error_and_syntax_error +++ b/test/incremental/analysis_error_and_syntax_error @@ -1,38 +1,64 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { pub fn main() !void { @compileError("uh oh"); } -#expect_error=main.zig:2:5: error: uh oh +#} +#update +#check errors { +main.zig:2:5: error: uh oh +#} -#update=add parse error -#file=main.zig +#write main.zig { pub fn main() !void { @compileError("uh oh"); -#expect_error=main.zig:3:1: error: expected statement, found 'EOF' +#} +#update { +add parse error#} +#check errors { +main.zig:3:1: error: expected statement, found 'EOF' +#} -#update=fix parse error -#file=main.zig +#write main.zig { pub fn main() !void { @compileError("uh oh"); } -#expect_error=main.zig:2:5: error: uh oh +#} +#update { +fix parse error#} +#check errors { +main.zig:2:5: error: uh oh +#} -#update=add parse error again -#file=main.zig +#write main.zig { pub fn main() !void { @compileError("uh oh"); -#expect_error=main.zig:3:1: error: expected statement, found 'EOF' +#} +#update { +add parse error again#} +#check errors { +main.zig:3:1: error: expected statement, found 'EOF' +#} -#update=comment @compileError call -#file=main.zig +#write main.zig { pub fn main() !void { //@compileError("uh oh"); -#expect_error=main.zig:3:1: error: expected statement, found 'EOF' +#} +#update { +comment @compileError call#} +#check errors { +main.zig:3:1: error: expected statement, found 'EOF' +#} -#update=fix parse error again -#file=main.zig +#write main.zig { pub fn main() !void { //@compileError("uh oh"); } -#expect_stdout="" +#} +#update { +fix parse error again#} +#check stdout { +#} diff --git a/test/incremental/bad_import b/test/incremental/bad_import index fd91fa66050e2ae3cb1407382d5f173bdb2fe884..d93a9ed6309f963c310a82f0813d0905a564cc10 100644 --- a/test/incremental/bad_import +++ b/test/incremental/bad_import @@ -1,32 +1,48 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { pub fn main() !void { _ = @import("foo.zig"); try std.Io.File.stdout().writeStreamingAll(io, "success\n"); } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#file=foo.zig +#} +#write foo.zig { comptime { _ = @import("bad.zig"); } -#expect_error=bad.zig:1:1: error: unable to load "bad.zig": FileNotFound -#expect_error=foo.zig:2:17: note: file imported here +#} +#update +#check errors { +bad.zig:1:1: error: unable to load "bad.zig": FileNotFound +foo.zig:2:17: note: file imported here +#} -#update=change bad import -#file=foo.zig +#write foo.zig { comptime { _ = @import("this_is/not_real.zig"); } -#expect_error=this_is/not_real.zig:1:1: error: unable to load "not_real.zig": FileNotFound -#expect_error=foo.zig:2:17: note: file imported here +#} +#update { +change bad import#} +#check errors { +this_is/not_real.zig:1:1: error: unable to load "not_real.zig": FileNotFound +foo.zig:2:17: note: file imported here +#} -#update=remove import of 'foo.zig' -#file=main.zig +#write main.zig { pub fn main() !void { //_ = @import("foo.zig"); try std.Io.File.stdout().writeStreamingAll(io, "success\n"); } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="success\n" +#} +#update { +remove import of 'foo.zig'#} +#check stdout { +success +#} diff --git a/test/incremental/change_bitpack_backing_int b/test/incremental/change_bitpack_backing_int index 0af16aa43e092554bc22c09053b22b54e87a32e4..a5a2b81ce3c3df699043da2fdbd06e7718627ec8 100644 --- a/test/incremental/change_bitpack_backing_int +++ b/test/incremental/change_bitpack_backing_int @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const Foo = packed struct(u8) { a: u4, b: i4 }; const Bar = packed union(u10) { a: u10, b: i10 }; pub fn main() void {} @@ -11,13 +14,18 @@ comptime { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:5:5: error: found compile log statement -#expect_error=main.zig:8:5: note: also here -#expect_compile_log=@as(type, u8) -#expect_compile_log=@as(comptime_int, 10) +#} +#update +#check errors { +main.zig:5:5: error: found compile log statement +main.zig:8:5: note: also here -#update=make backing types signed -#file=main.zig +Compile Log Output: +@as(type, u8) +@as(comptime_int, 10) +#} + +#write main.zig { const Foo = packed struct(i8) { a: u4, b: i4 }; const Bar = packed union(i10) { a: u10, b: i10 }; pub fn main() void {} @@ -29,13 +37,19 @@ comptime { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:5:5: error: found compile log statement -#expect_error=main.zig:8:5: note: also here -#expect_compile_log=@as(type, i8) -#expect_compile_log=@as(comptime_int, 10) +#} +#update { +make backing types signed#} +#check errors { +main.zig:5:5: error: found compile log statement +main.zig:8:5: note: also here -#update=make backing types too small -#file=main.zig +Compile Log Output: +@as(type, i8) +@as(comptime_int, 10) +#} + +#write main.zig { const Foo = packed struct(i5) { a: u4, b: i4 }; const Bar = packed union(i8) { a: u10, b: i10 }; pub fn main() void {} @@ -47,16 +61,20 @@ comptime { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:1:20: error: backing integer bit width does not match total bit width of fields -#expect_error=main.zig:1:27: note: backing integer 'i5' has bit width '5' -#expect_error=main.zig:1:20: note: struct fields have total bit width '8' -#expect_error=main.zig:2:35: error: field bit width does not match backing integer -#expect_error=main.zig:2:35: note: field type 'u10' has bit width '10' -#expect_error=main.zig:2:26: note: backing integer 'i8' has bit width '8' -#expect_error=main.zig:2:35: note: all fields in a packed union must have the same bit width +#} +#update { +make backing types too small#} +#check errors { +main.zig:1:20: error: backing integer bit width does not match total bit width of fields +main.zig:1:27: note: backing integer 'i5' has bit width '5' +main.zig:1:20: note: struct fields have total bit width '8' +main.zig:2:35: error: field bit width does not match backing integer +main.zig:2:35: note: field type 'u10' has bit width '10' +main.zig:2:26: note: backing integer 'i8' has bit width '8' +main.zig:2:35: note: all fields in a packed union must have the same bit width +#} -#update=make backing types too big -#file=main.zig +#write main.zig { const Foo = packed struct(u10) { a: u4, b: i4 }; const Bar = packed union(u32) { a: u10, b: i10 }; pub fn main() void {} @@ -68,10 +86,15 @@ comptime { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:1:20: error: backing integer bit width does not match total bit width of fields -#expect_error=main.zig:1:27: note: backing integer 'u10' has bit width '10' -#expect_error=main.zig:1:20: note: struct fields have total bit width '8' -#expect_error=main.zig:2:36: error: field bit width does not match backing integer -#expect_error=main.zig:2:36: note: field type 'u10' has bit width '10' -#expect_error=main.zig:2:26: note: backing integer 'u32' has bit width '32' -#expect_error=main.zig:2:36: note: all fields in a packed union must have the same bit width +#} +#update { +make backing types too big#} +#check errors { +main.zig:1:20: error: backing integer bit width does not match total bit width of fields +main.zig:1:27: note: backing integer 'u10' has bit width '10' +main.zig:1:20: note: struct fields have total bit width '8' +main.zig:2:36: error: field bit width does not match backing integer +main.zig:2:36: note: field type 'u10' has bit width '10' +main.zig:2:26: note: backing integer 'u32' has bit width '32' +main.zig:2:36: note: all fields in a packed union must have the same bit width +#} diff --git a/test/incremental/change_container_kind b/test/incremental/change_container_kind index cf6b5a3689cdb51f037db505ad5fdd9ebebdb48b..b83abc6ed79414180e28bf393af94cb708bb377e 100644 --- a/test/incremental/change_container_kind +++ b/test/incremental/change_container_kind @@ -1,61 +1,114 @@ -#update=initial version, T is struct -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const T = @import("foo.zig").T; pub fn main() void { @compileLog(T); @compileLog(@tagName(@typeInfo(T))); } -#file=foo.zig +#} +#write foo.zig { pub const T = struct { x: u8 }; -#expect_error=main.zig:3:5: error: found compile log statement -#expect_compile_log=@as(type, foo.T) -#expect_compile_log=@as(*const [6:0]u8, "struct") +#} +#update { +T is struct#} +#check errors { +main.zig:3:5: error: found compile log statement -#update=T is union -#file=foo.zig +Compile Log Output: +@as(type, foo.T) +@as(*const [6:0]u8, "struct") +#} + +#write foo.zig { pub const T = union {}; -#expect_error=main.zig:3:5: error: found compile log statement -#expect_compile_log=@as(type, foo.T) -#expect_compile_log=@as(*const [5:0]u8, "union") +#} +#update { +T is union#} +#check errors { +main.zig:3:5: error: found compile log statement -#update=T is enum -#file=foo.zig +Compile Log Output: +@as(type, foo.T) +@as(*const [5:0]u8, "union") +#} + +#write foo.zig { pub const T = enum {}; -#expect_error=main.zig:3:5: error: found compile log statement -#expect_compile_log=@as(type, foo.T) -#expect_compile_log=@as(*const [4:0]u8, "enum") +#} +#update { +T is enum#} +#check errors { +main.zig:3:5: error: found compile log statement -#update=T is opaque -#file=foo.zig +Compile Log Output: +@as(type, foo.T) +@as(*const [4:0]u8, "enum") +#} + +#write foo.zig { pub const T = opaque {}; -#expect_error=main.zig:3:5: error: found compile log statement -#expect_compile_log=@as(type, foo.T) -#expect_compile_log=@as(*const [6:0]u8, "opaque") +#} +#update { +T is opaque#} +#check errors { +main.zig:3:5: error: found compile log statement -#update=T is packed struct -#file=foo.zig +Compile Log Output: +@as(type, foo.T) +@as(*const [6:0]u8, "opaque") +#} + +#write foo.zig { pub const T = packed struct {}; -#expect_error=main.zig:3:5: error: found compile log statement -#expect_compile_log=@as(type, foo.T) -#expect_compile_log=@as(*const [6:0]u8, "struct") +#} +#update { +T is packed struct#} +#check errors { +main.zig:3:5: error: found compile log statement -#update=T is packed union -#file=foo.zig +Compile Log Output: +@as(type, foo.T) +@as(*const [6:0]u8, "struct") +#} + +#write foo.zig { pub const T = packed union { x: void }; // packed unions need at least 1 field -#expect_error=main.zig:3:5: error: found compile log statement -#expect_compile_log=@as(type, foo.T) -#expect_compile_log=@as(*const [5:0]u8, "union") +#} +#update { +T is packed union#} +#check errors { +main.zig:3:5: error: found compile log statement -#update=T is extern struct -#file=foo.zig +Compile Log Output: +@as(type, foo.T) +@as(*const [5:0]u8, "union") +#} + +#write foo.zig { pub const T = extern struct {}; -#expect_error=main.zig:3:5: error: found compile log statement -#expect_compile_log=@as(type, foo.T) -#expect_compile_log=@as(*const [6:0]u8, "struct") +#} +#update { +T is extern struct#} +#check errors { +main.zig:3:5: error: found compile log statement -#update=T is extern union -#file=foo.zig +Compile Log Output: +@as(type, foo.T) +@as(*const [6:0]u8, "struct") +#} + +#write foo.zig { pub const T = extern union { x: void }; // extern unions need at least 1 field -#expect_error=main.zig:3:5: error: found compile log statement -#expect_compile_log=@as(type, foo.T) -#expect_compile_log=@as(*const [5:0]u8, "union") +#} +#update { +T is extern union#} +#check errors { +main.zig:3:5: error: found compile log statement + +Compile Log Output: +@as(type, foo.T) +@as(*const [5:0]u8, "union") +#} diff --git a/test/incremental/change_embed_file b/test/incremental/change_embed_file index 8b515d2d980c9ee68da186c8fa149b5b19c2ed20..d50101dc476973ab1974d4ae82fca3cebffed359 100644 --- a/test/incremental/change_embed_file +++ b/test/incremental/change_embed_file @@ -1,45 +1,72 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const std = @import("std"); const string = @embedFile("string.txt"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, string); } const io = std.Io.Threaded.global_single_threaded.io(); -#file=string.txt +#} +#write string.txt { Hello, World! -#expect_stdout="Hello, World!\n" +#} +#update +#check stdout { +Hello, World! +#} -#update=change file contents -#file=string.txt +#write string.txt { +Hello again, World! +#} +#update { +change file contents#} +#check stdout { Hello again, World! -#expect_stdout="Hello again, World!\n" +#} -#update=delete file -#rm_file=string.txt -#expect_error=main.zig:2:27: error: failed opening "string.txt": FileNotFound +#delete string.txt +#update { +delete file#} +#check errors { +main.zig:2:27: error: failed opening "string.txt": FileNotFound +#} -#update=remove reference to file -#file=main.zig +#write main.zig { const std = @import("std"); const string = @embedFile("string.txt"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n"); } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="a hardcoded string\n" +#} +#update { +remove reference to file#} +#check stdout { +a hardcoded string +#} -#update=re-introduce reference to file -#file=main.zig +#write main.zig { const std = @import("std"); const string = @embedFile("string.txt"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, string); } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:2:27: error: failed opening "string.txt": FileNotFound +#} +#update { +re-introduce reference to file#} +#check errors { +main.zig:2:27: error: failed opening "string.txt": FileNotFound +#} -#update=recreate file -#file=string.txt +#write string.txt { We're back, World! -#expect_stdout="We're back, World!\n" +#} +#update { +recreate file#} +#check stdout { +We're back, World! +#} diff --git a/test/incremental/change_enum_tag_type b/test/incremental/change_enum_tag_type deleted file mode 100644 index 6ff8a1424d0c695145e7e136b9989c7413fda5a2..0000000000000000000000000000000000000000 --- a/test/incremental/change_enum_tag_type +++ /dev/null @@ -1,93 +0,0 @@ -#update=initial version -#file=main.zig -const Tag = u2; -const Foo = enum(Tag) { - a, - b, - c, - d, -}; -pub fn main() !void { - var val: Foo = undefined; - val = .a; - var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); - try stdout_writer.interface.print("{s}\n", .{@tagName(val)}); -} -const std = @import("std"); -const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="a\n" -#update=too many enum fields -#file=main.zig -const Tag = u2; -const Foo = enum(Tag) { - a, - b, - c, - d, - e, -}; -pub fn main() !void { - var val: Foo = undefined; - val = .a; - var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); - try stdout_writer.interface.print("{s}\n", .{@tagName(val)}); -} -comptime { - // These can't be true at the same time; analysis should stop as soon as it sees `Foo` - std.debug.assert(@intFromEnum(Foo.e) == 4); - std.debug.assert(@TypeOf(@intFromEnum(Foo.e)) == Tag); -} -const std = @import("std"); -const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:7:5: error: enum tag value '4' too large for type 'u2' -#update=increase tag size -#file=main.zig -const Tag = u3; -const Foo = enum(Tag) { - a, - b, - c, - d, - e, -}; -pub fn main() !void { - var val: Foo = undefined; - val = .a; - var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); - try stdout_writer.interface.print("{s}\n", .{@tagName(val)}); -} -const std = @import("std"); -const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="a\n" -#update=specify tag directly -#file=main.zig -const Foo = enum(u3) { - a, - b, - c, - d, - e, -}; -pub fn main() !void { - @compileLog(@typeInfo(Foo).@"enum".tag_type); -} -const std = @import("std"); -const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:9:5: error: found compile log statement -#expect_compile_log=@as(type, u3) -#update=change directly-specified tag type -#file=main.zig -const Foo = enum(u8) { - a, - b, - c, - d, - e, -}; -pub fn main() !void { - @compileLog(@typeInfo(Foo).@"enum".tag_type); -} -const std = @import("std"); -const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:9:5: error: found compile log statement -#expect_compile_log=@as(type, u8) diff --git a/test/incremental/change_enum_tag_type/0/main.zig b/test/incremental/change_enum_tag_type/0/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..391e354224b13f84fd0e172d9a2c5a07074cdcd8 --- /dev/null +++ b/test/incremental/change_enum_tag_type/0/main.zig @@ -0,0 +1,15 @@ +const Tag = u2; +const Foo = enum(Tag) { + a, + b, + c, + d, +}; +pub fn main() !void { + var val: Foo = undefined; + val = .a; + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); + try stdout_writer.interface.print("{s}\n", .{@tagName(val)}); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental/change_enum_tag_type/1/main.zig b/test/incremental/change_enum_tag_type/1/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..ea4ff6f7a9097e4f4982ae4598a40b0e1f023e80 --- /dev/null +++ b/test/incremental/change_enum_tag_type/1/main.zig @@ -0,0 +1,21 @@ +const Tag = u2; +const Foo = enum(Tag) { + a, + b, + c, + d, + e, +}; +pub fn main() !void { + var val: Foo = undefined; + val = .a; + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); + try stdout_writer.interface.print("{s}\n", .{@tagName(val)}); +} +comptime { + // These can't be true at the same time; analysis should stop as soon as it sees `Foo` + std.debug.assert(@backingInt(Foo.e) == 4); + std.debug.assert(@TypeOf(@backingInt(Foo.e)) == Tag); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental/change_enum_tag_type/2/main.zig b/test/incremental/change_enum_tag_type/2/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..2a1eee0ba91d59ed1d5df6adfc5ae2de2d29a5d6 --- /dev/null +++ b/test/incremental/change_enum_tag_type/2/main.zig @@ -0,0 +1,16 @@ +const Tag = u3; +const Foo = enum(Tag) { + a, + b, + c, + d, + e, +}; +pub fn main() !void { + var val: Foo = undefined; + val = .a; + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); + try stdout_writer.interface.print("{s}\n", .{@tagName(val)}); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental/change_enum_tag_type/3/main.zig b/test/incremental/change_enum_tag_type/3/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..c4e213660e09f90523a3c8914d0f09dd5266cdf4 --- /dev/null +++ b/test/incremental/change_enum_tag_type/3/main.zig @@ -0,0 +1,12 @@ +const Foo = enum(u3) { + a, + b, + c, + d, + e, +}; +pub fn main() !void { + @compileLog(@typeInfo(Foo).@"enum".tag_type); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental/change_enum_tag_type/4/main.zig b/test/incremental/change_enum_tag_type/4/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..729f2501dd4e1ff7260c7234a1b204052fe5f92c --- /dev/null +++ b/test/incremental/change_enum_tag_type/4/main.zig @@ -0,0 +1,12 @@ +const Foo = enum(u8) { + a, + b, + c, + d, + e, +}; +pub fn main() !void { + @compileLog(@typeInfo(Foo).@"enum".tag_type); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental/change_enum_tag_type/manifest b/test/incremental/change_enum_tag_type/manifest new file mode 100644 index 0000000000000000000000000000000000000000..682c83a67ed4a3d9ced7ba413b3c8e2ddfab4559 --- /dev/null +++ b/test/incremental/change_enum_tag_type/manifest @@ -0,0 +1,43 @@ +#exe { +-Mroot=main.zig +#} + +#write main.zig 0/main.zig +#update +#check stdout { +a +#} + +#write main.zig 1/main.zig +#update { +too many enum fields#} +#check errors { +main.zig:7:5: error: enum tag value '4' too large for type 'u2' +#} + +#write main.zig 2/main.zig +#update { +increase tag size#} +#check stdout { +a +#} + +#write main.zig 3/main.zig +#update { +specify tag directly#} +#check errors { +main.zig:9:5: error: found compile log statement + +Compile Log Output: +@as(type, u3) +#} + +#write main.zig 4/main.zig +#update { +change directly-specified tag type#} +#check errors { +main.zig:9:5: error: found compile log statement + +Compile Log Output: +@as(type, u8) +#} diff --git a/test/incremental/change_exports b/test/incremental/change_exports deleted file mode 100644 index 64aec0288cbbf82420b8eaf5345245bea34d72b0..0000000000000000000000000000000000000000 --- a/test/incremental/change_exports +++ /dev/null @@ -1,169 +0,0 @@ -#update=initial version -#file=main.zig -export fn foo() void {} -const bar: u32 = 123; -const other: u32 = 456; -comptime { - @export(&bar, .{ .name = "bar" }); -} -pub fn main() !void { - const S = struct { - extern fn foo() void; - extern const bar: u32; - }; - S.foo(); - var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); - try stdout_writer.interface.print("{}\n", .{S.bar}); -} -const std = @import("std"); -const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="123\n" - -#update=add conflict -#file=main.zig -export fn foo() void {} -const bar: u32 = 123; -const other: u32 = 456; -comptime { - @export(&bar, .{ .name = "bar" }); - @export(&other, .{ .name = "foo" }); -} -pub fn main() !void { - const S = struct { - extern fn foo() void; - extern const bar: u32; - extern const other: u32; - }; - S.foo(); - var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); - try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); -} -const std = @import("std"); -const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:6:5: error: exported symbol collision: foo -#expect_error=main.zig:1:1: note: other symbol here - -#update=resolve conflict -#file=main.zig -export fn foo() void {} -const bar: u32 = 123; -const other: u32 = 456; -comptime { - @export(&bar, .{ .name = "bar" }); - @export(&other, .{ .name = "other" }); -} -pub fn main() !void { - const S = struct { - extern fn foo() void; - extern const bar: u32; - extern const other: u32; - }; - S.foo(); - var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); - try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); -} -const std = @import("std"); -const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="123 456\n" - -#update=put exports in decl -#file=main.zig -export fn foo() void {} -const bar: u32 = 123; -const other: u32 = 456; -const does_exports = { - @export(&bar, .{ .name = "bar" }); - @export(&other, .{ .name = "other" }); -}; -comptime { - _ = does_exports; -} -pub fn main() !void { - const S = struct { - extern fn foo() void; - extern const bar: u32; - extern const other: u32; - }; - S.foo(); - var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); - try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); -} -const std = @import("std"); -const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="123 456\n" - -#update=remove reference to exporting decl -#file=main.zig -export fn foo() void {} -const bar: u32 = 123; -const other: u32 = 456; -const does_exports = { - @export(&bar, .{ .name = "bar" }); - @export(&other, .{ .name = "other" }); -}; -comptime { - //_ = does_exports; -} -pub fn main() !void { - const S = struct { - extern fn foo() void; - }; - S.foo(); -} -const std = @import("std"); -#expect_stdout="" - -#update=mark consts as export -#file=main.zig -export fn foo() void {} -export const bar: u32 = 123; -export const other: u32 = 456; -const does_exports = { - @export(&bar, .{ .name = "bar" }); - @export(&other, .{ .name = "other" }); -}; -comptime { - //_ = does_exports; -} -pub fn main() !void { - const S = struct { - extern fn foo() void; - extern const bar: u32; - extern const other: u32; - }; - S.foo(); - var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); - try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); -} -const std = @import("std"); -const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="123 456\n" - -#update=reintroduce reference to exporting decl, introducing conflict -#file=main.zig -export fn foo() void {} -export const bar: u32 = 123; -export const other: u32 = 456; -const does_exports = { - @export(&bar, .{ .name = "bar" }); - @export(&other, .{ .name = "other" }); -}; -comptime { - _ = does_exports; -} -pub fn main() !void { - const S = struct { - extern fn foo() void; - extern const bar: u32; - extern const other: u32; - }; - S.foo(); - var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); - try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); -} -const std = @import("std"); -const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:5:5: error: exported symbol collision: bar -#expect_error=main.zig:2:1: note: other symbol here -#expect_error=main.zig:6:5: error: exported symbol collision: other -#expect_error=main.zig:3:1: note: other symbol here diff --git a/test/incremental/change_exports/0/main.zig b/test/incremental/change_exports/0/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..79f3265c558830ea94304c71feb5eeb39b96de33 --- /dev/null +++ b/test/incremental/change_exports/0/main.zig @@ -0,0 +1,17 @@ +export fn foo() void {} +const bar: u32 = 123; +const other: u32 = 456; +comptime { + @export(&bar, .{ .name = "bar" }); +} +pub fn main() !void { + const S = struct { + extern fn foo() void; + extern const bar: u32; + }; + S.foo(); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); + try stdout_writer.interface.print("{}\n", .{S.bar}); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental/change_exports/1/main.zig b/test/incremental/change_exports/1/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..af1dd07028368824ec0bc5a81d0c7b43e99ad521 --- /dev/null +++ b/test/incremental/change_exports/1/main.zig @@ -0,0 +1,19 @@ +export fn foo() void {} +const bar: u32 = 123; +const other: u32 = 456; +comptime { + @export(&bar, .{ .name = "bar" }); + @export(&other, .{ .name = "foo" }); +} +pub fn main() !void { + const S = struct { + extern fn foo() void; + extern const bar: u32; + extern const other: u32; + }; + S.foo(); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); + try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental/change_exports/2/main.zig b/test/incremental/change_exports/2/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..e2737aea7e73d589b7525d06085a79f0b8a11f0f --- /dev/null +++ b/test/incremental/change_exports/2/main.zig @@ -0,0 +1,19 @@ +export fn foo() void {} +const bar: u32 = 123; +const other: u32 = 456; +comptime { + @export(&bar, .{ .name = "bar" }); + @export(&other, .{ .name = "other" }); +} +pub fn main() !void { + const S = struct { + extern fn foo() void; + extern const bar: u32; + extern const other: u32; + }; + S.foo(); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); + try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental/change_exports/3/main.zig b/test/incremental/change_exports/3/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..20662c64435cc85c232ec1a18ffec0982b31fc58 --- /dev/null +++ b/test/incremental/change_exports/3/main.zig @@ -0,0 +1,22 @@ +export fn foo() void {} +const bar: u32 = 123; +const other: u32 = 456; +const does_exports = { + @export(&bar, .{ .name = "bar" }); + @export(&other, .{ .name = "other" }); +}; +comptime { + _ = does_exports; +} +pub fn main() !void { + const S = struct { + extern fn foo() void; + extern const bar: u32; + extern const other: u32; + }; + S.foo(); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); + try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental/change_exports/4/main.zig b/test/incremental/change_exports/4/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..f53a371119987978b64e3cac638dc3630b179a0a --- /dev/null +++ b/test/incremental/change_exports/4/main.zig @@ -0,0 +1,17 @@ +export fn foo() void {} +const bar: u32 = 123; +const other: u32 = 456; +const does_exports = { + @export(&bar, .{ .name = "bar" }); + @export(&other, .{ .name = "other" }); +}; +comptime { + //_ = does_exports; +} +pub fn main() !void { + const S = struct { + extern fn foo() void; + }; + S.foo(); +} +const std = @import("std"); diff --git a/test/incremental/change_exports/5/main.zig b/test/incremental/change_exports/5/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..ad9ccddf989a962083c351a2badc6fd7a5777b92 --- /dev/null +++ b/test/incremental/change_exports/5/main.zig @@ -0,0 +1,22 @@ +export fn foo() void {} +export const bar: u32 = 123; +export const other: u32 = 456; +const does_exports = { + @export(&bar, .{ .name = "bar" }); + @export(&other, .{ .name = "other" }); +}; +comptime { + //_ = does_exports; +} +pub fn main() !void { + const S = struct { + extern fn foo() void; + extern const bar: u32; + extern const other: u32; + }; + S.foo(); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); + try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental/change_exports/6/main.zig b/test/incremental/change_exports/6/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..2019185e6046252901faf087291720f93895d514 --- /dev/null +++ b/test/incremental/change_exports/6/main.zig @@ -0,0 +1,22 @@ +export fn foo() void {} +export const bar: u32 = 123; +export const other: u32 = 456; +const does_exports = { + @export(&bar, .{ .name = "bar" }); + @export(&other, .{ .name = "other" }); +}; +comptime { + _ = does_exports; +} +pub fn main() !void { + const S = struct { + extern fn foo() void; + extern const bar: u32; + extern const other: u32; + }; + S.foo(); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); + try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other }); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental/change_exports/manifest b/test/incremental/change_exports/manifest new file mode 100644 index 0000000000000000000000000000000000000000..41716cd32cadf13dca51971fbab9703bc1530ade --- /dev/null +++ b/test/incremental/change_exports/manifest @@ -0,0 +1,54 @@ +#exe { +-Mroot=main.zig +#} + +#write main.zig 0/main.zig +#update +#check stdout { +123 +#} + +#write main.zig 1/main.zig +#update { +add conflict#} +#check errors { +main.zig:6:5: error: exported symbol collision: foo +main.zig:1:1: note: other symbol here +#} + +#write main.zig 2/main.zig +#update { +resolve conflict#} +#check stdout { +123 456 +#} + +#write main.zig 3/main.zig +#update { +put exports in decl#} +#check stdout { +123 456 +#} + +#write main.zig 4/main.zig +#update { +remove reference to exporting decl#} +#check stdout { +#} + +#write main.zig 5/main.zig +#update { +mark consts as export#} +#check stdout { +123 456 +#} + +#write main.zig 6/main.zig +#update { +reintroduce reference to exporting decl, introducing conflict#} +#check errors { +main.zig:5:5: error: exported symbol collision: bar +main.zig:2:1: note: other symbol here +main.zig:6:5: error: exported symbol collision: other +main.zig:3:1: note: other symbol here +#} diff --git a/test/incremental/change_fn_type b/test/incremental/change_fn_type index ca0667d8041e05c363a9c975b865c6e1a7640904..b16811fbc937aa183fd80bfe7b0eb59690e089b7 100644 --- a/test/incremental/change_fn_type +++ b/test/incremental/change_fn_type @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { pub fn main() !void { try foo(123); } @@ -9,10 +12,13 @@ fn foo(x: u8) !void { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="123\n" +#} +#update +#check stdout { +123 +#} -#update=change function type -#file=main.zig +#write main.zig { pub fn main() !void { try foo(123); } @@ -22,10 +28,14 @@ fn foo(x: i64) !void { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="123\n" +#} +#update { +change function type#} +#check stdout { +123 +#} -#update=change function argument -#file=main.zig +#write main.zig { pub fn main() !void { try foo(-42); } @@ -35,4 +45,9 @@ fn foo(x: i64) !void { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="-42\n" +#} +#update { +change function argument#} +#check stdout { +-42 +#} diff --git a/test/incremental/change_generic_line_number b/test/incremental/change_generic_line_number index 23b3f2c0190a47c9c5e8a124af324b308fc100a3..cda09859f9c849649b5b40c66d9d16e94101cf34 100644 --- a/test/incremental/change_generic_line_number +++ b/test/incremental/change_generic_line_number @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); fn Printer(message: []const u8) type { @@ -13,9 +16,14 @@ pub fn main() !void { try Printer("foo\n").print(); try Printer("bar\n").print(); } -#expect_stdout="foo\nbar\n" -#update=change line number -#file=main.zig +#} +#update +#check stdout { +foo +bar +#} + +#write main.zig { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); @@ -30,4 +38,10 @@ pub fn main() !void { try Printer("foo\n").print(); try Printer("bar\n").print(); } -#expect_stdout="foo\nbar\n" +#} +#update { +change line number#} +#check stdout { +foo +bar +#} diff --git a/test/incremental/change_line_number b/test/incremental/change_line_number index 90a1edb95c924143792363c8b14c6414f0fbac13..ed8e24b009356b452dd71d17d7ca510e0df285d2 100644 --- a/test/incremental/change_line_number +++ b/test/incremental/change_line_number @@ -1,17 +1,29 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, "foo\n"); } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="foo\n" -#update=change line number -#file=main.zig +#} +#update +#check stdout { +foo +#} + +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, "foo\n"); } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="foo\n" +#} +#update { +change line number#} +#check stdout { +foo +#} diff --git a/test/incremental/change_module b/test/incremental/change_module index 73129f9eec6db02e446ff663c5600ffac2345994..4c24df6e4dd2c3f410d2d0a555e5a05a2d94d592 100644 --- a/test/incremental/change_module +++ b/test/incremental/change_module @@ -1,61 +1,87 @@ -#module=foo=foo.zig +#exe { +--dep foo -Mroot=main.zig +-Mfoo=foo.zig +#} -#update=initial version -#file=main.zig +#write main.zig { pub fn main() void { _ = @import("foo"); //_ = @import("other.zig"); } -#file=foo.zig +#} +#write foo.zig { comptime { _ = @import("other.zig"); } -#file=other.zig +#} +#write other.zig { fn f() void { @compileLog(@src().module); } comptime { f(); } -#expect_error=other.zig:2:5: error: found compile log statement -#expect_compile_log=@as([:0]const u8, "foo"[0..3]) +#} +#update +#check errors { +other.zig:2:5: error: found compile log statement -#update=change module of other.zig -#file=main.zig +Compile Log Output: +@as([:0]const u8, "foo"[0..3]) +#} + +#write main.zig { pub fn main() void { _ = @import("foo"); _ = @import("other.zig"); } -#file=foo.zig +#} +#write foo.zig { comptime { //_ = @import("other.zig"); } -#expect_error=other.zig:2:5: error: found compile log statement -#expect_compile_log=@as([:0]const u8, "root"[0..4]) +#} +#update { +change module of other.zig#} +#check errors { +other.zig:2:5: error: found compile log statement + +Compile Log Output: +@as([:0]const u8, "root"[0..4]) +#} -#update=put other.zig in both modules -#file=main.zig +#write main.zig { pub fn main() void { _ = @import("foo"); _ = @import("other.zig"); } -#file=foo.zig +#} +#write foo.zig { comptime { _ = @import("other.zig"); } -#expect_error=other.zig:1:1: error: file exists in modules 'root' and 'foo' -#expect_error=other.zig:1:1: note: files must belong to only one module -#expect_error=main.zig:3:17: note: file is imported here by the root of module 'root' -#expect_error=foo.zig:2:17: note: file is imported here by the root of module 'foo' +#} +#update { +put other.zig in both modules#} +#check errors { +other.zig:1:1: error: file exists in modules 'root' and 'foo' +other.zig:1:1: note: files must belong to only one module +main.zig:3:17: note: file is imported here by the root of module 'root' +foo.zig:2:17: note: file is imported here by the root of module 'foo' +#} -#update=put other.zig in no modules -#file=main.zig +#write main.zig { pub fn main() void { _ = @import("foo"); //_ = @import("other.zig"); } -#file=foo.zig +#} +#write foo.zig { comptime { //_ = @import("other.zig"); } -#expect_stdout="" +#} +#update { +put other.zig in no modules#} +#check stdout { +#} diff --git a/test/incremental/change_panic_handler b/test/incremental/change_panic_handler index b250c9867985a683a209279835f366703f2f5161..3407de9bade926e20542b9342c91e2d223a019da 100644 --- a/test/incremental/change_panic_handler +++ b/test/incremental/change_panic_handler @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { pub fn main() !u8 { var a: u8 = undefined; a = 255; @@ -14,10 +17,13 @@ fn myPanic(msg: []const u8, _: ?usize) noreturn { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="panic message: integer overflow\n" +#} +#update +#check stdout { +panic message: integer overflow +#} -#update=change the panic handler body -#file=main.zig +#write main.zig { pub fn main() !u8 { var a: u8 = undefined; a = 255; @@ -32,10 +38,14 @@ fn myPanic(msg: []const u8, _: ?usize) noreturn { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="new panic message: integer overflow\n" +#} +#update { +change the panic handler body#} +#check stdout { +new panic message: integer overflow +#} -#update=change the panic handler function value -#file=main.zig +#write main.zig { pub fn main() !u8 { var a: u8 = undefined; a = 255; @@ -50,4 +60,9 @@ fn myPanicNew(msg: []const u8, _: ?usize) noreturn { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="third panic message: integer overflow\n" +#} +#update { +change the panic handler function value#} +#check stdout { +third panic message: integer overflow +#} diff --git a/test/incremental/change_panic_handler_explicit b/test/incremental/change_panic_handler_explicit deleted file mode 100644 index 3735cfd86c0116ce130481c56f60d6816174d00f..0000000000000000000000000000000000000000 --- a/test/incremental/change_panic_handler_explicit +++ /dev/null @@ -1,149 +0,0 @@ -#update=initial version -#file=main.zig -pub fn main() !u8 { - var a: u8 = undefined; - a = 255; - _ = a + 1; - return 1; -} -const no_panic = std.debug.no_panic; -pub const panic = struct { - pub const call = myPanic; - pub fn integerOverflow() noreturn { - @panic("integer overflow"); - } - pub const sentinelMismatch = no_panic.sentinelMismatch; - pub const unwrapError = no_panic.unwrapError; - pub const outOfBounds = no_panic.outOfBounds; - pub const startGreaterThanEnd = no_panic.startGreaterThanEnd; - pub const inactiveUnionField = no_panic.inactiveUnionField; - pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder; - pub const reachedUnreachable = no_panic.reachedUnreachable; - pub const unwrapNull = no_panic.unwrapNull; - pub const castToNull = no_panic.castToNull; - pub const incorrectAlignment = no_panic.incorrectAlignment; - pub const invalidErrorCode = no_panic.invalidErrorCode; - pub const unexpectedErrorCode = no_panic.unexpectedErrorCode; - pub const integerOutOfBounds = no_panic.integerOutOfBounds; - pub const shlOverflow = no_panic.shlOverflow; - pub const shrOverflow = no_panic.shrOverflow; - pub const divideByZero = no_panic.divideByZero; - pub const exactDivisionRemainder = no_panic.exactDivisionRemainder; - pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds; - pub const corruptSwitch = no_panic.corruptSwitch; - pub const shiftRhsTooBig = no_panic.shiftRhsTooBig; - pub const invalidEnumValue = no_panic.invalidEnumValue; - pub const forLenMismatch = no_panic.forLenMismatch; - pub const copyLenMismatch = no_panic.copyLenMismatch; - pub const memcpyAlias = no_panic.memcpyAlias; - pub const noreturnReturned = no_panic.noreturnReturned; - pub const loadUninstantiableType = no_panic.loadUninstantiableType; -}; -fn myPanic(msg: []const u8, _: ?usize) noreturn { - var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); - stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {}; - std.process.exit(0); -} -const std = @import("std"); -const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="panic message: integer overflow\n" - -#update=change the panic handler body -#file=main.zig -pub fn main() !u8 { - var a: u8 = undefined; - a = 255; - _ = a + 1; - return 1; -} -const no_panic = std.debug.no_panic; -pub const panic = struct { - pub const call = myPanic; - pub fn integerOverflow() noreturn { - @panic("integer overflow"); - } - pub const sentinelMismatch = no_panic.sentinelMismatch; - pub const unwrapError = no_panic.unwrapError; - pub const outOfBounds = no_panic.outOfBounds; - pub const startGreaterThanEnd = no_panic.startGreaterThanEnd; - pub const inactiveUnionField = no_panic.inactiveUnionField; - pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder; - pub const reachedUnreachable = no_panic.reachedUnreachable; - pub const unwrapNull = no_panic.unwrapNull; - pub const castToNull = no_panic.castToNull; - pub const incorrectAlignment = no_panic.incorrectAlignment; - pub const invalidErrorCode = no_panic.invalidErrorCode; - pub const unexpectedErrorCode = no_panic.unexpectedErrorCode; - pub const integerOutOfBounds = no_panic.integerOutOfBounds; - pub const shlOverflow = no_panic.shlOverflow; - pub const shrOverflow = no_panic.shrOverflow; - pub const divideByZero = no_panic.divideByZero; - pub const exactDivisionRemainder = no_panic.exactDivisionRemainder; - pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds; - pub const corruptSwitch = no_panic.corruptSwitch; - pub const shiftRhsTooBig = no_panic.shiftRhsTooBig; - pub const invalidEnumValue = no_panic.invalidEnumValue; - pub const forLenMismatch = no_panic.forLenMismatch; - pub const copyLenMismatch = no_panic.copyLenMismatch; - pub const memcpyAlias = no_panic.memcpyAlias; - pub const noreturnReturned = no_panic.noreturnReturned; - pub const loadUninstantiableType = no_panic.loadUninstantiableType; -}; -fn myPanic(msg: []const u8, _: ?usize) noreturn { - var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); - stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {}; - std.process.exit(0); -} -const std = @import("std"); -const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="new panic message: integer overflow\n" - -#update=change the panic handler function value -#file=main.zig -pub fn main() !u8 { - var a: u8 = undefined; - a = 255; - _ = a + 1; - return 1; -} -const no_panic = std.debug.no_panic; -pub const panic = struct { - pub const call = myPanicNew; - pub fn integerOverflow() noreturn { - @panic("integer overflow"); - } - pub const sentinelMismatch = std.debug.no_panic.sentinelMismatch; - pub const unwrapError = std.debug.no_panic.unwrapError; - pub const outOfBounds = std.debug.no_panic.outOfBounds; - pub const startGreaterThanEnd = std.debug.no_panic.startGreaterThanEnd; - pub const inactiveUnionField = std.debug.no_panic.inactiveUnionField; - pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder; - pub const reachedUnreachable = no_panic.reachedUnreachable; - pub const unwrapNull = no_panic.unwrapNull; - pub const castToNull = no_panic.castToNull; - pub const incorrectAlignment = no_panic.incorrectAlignment; - pub const invalidErrorCode = no_panic.invalidErrorCode; - pub const unexpectedErrorCode = no_panic.unexpectedErrorCode; - pub const integerOutOfBounds = no_panic.integerOutOfBounds; - pub const shlOverflow = no_panic.shlOverflow; - pub const shrOverflow = no_panic.shrOverflow; - pub const divideByZero = no_panic.divideByZero; - pub const exactDivisionRemainder = no_panic.exactDivisionRemainder; - pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds; - pub const corruptSwitch = no_panic.corruptSwitch; - pub const shiftRhsTooBig = no_panic.shiftRhsTooBig; - pub const invalidEnumValue = no_panic.invalidEnumValue; - pub const forLenMismatch = no_panic.forLenMismatch; - pub const copyLenMismatch = no_panic.copyLenMismatch; - pub const memcpyAlias = no_panic.memcpyAlias; - pub const noreturnReturned = no_panic.noreturnReturned; - pub const loadUninstantiableType = no_panic.loadUninstantiableType; -}; -fn myPanicNew(msg: []const u8, _: ?usize) noreturn { - var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); - stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {}; - std.process.exit(0); -} -const std = @import("std"); -const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="third panic message: integer overflow\n" diff --git a/test/incremental/change_panic_handler_explicit/0/main.zig b/test/incremental/change_panic_handler_explicit/0/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..608b4a8f1beac504fd564bc89705d2c676076b7b --- /dev/null +++ b/test/incremental/change_panic_handler_explicit/0/main.zig @@ -0,0 +1,46 @@ +pub fn main() !u8 { + var a: u8 = undefined; + a = 255; + _ = a + 1; + return 1; +} +const no_panic = std.debug.no_panic; +pub const panic = struct { + pub const call = myPanic; + pub fn integerOverflow() noreturn { + @panic("integer overflow"); + } + pub const sentinelMismatch = no_panic.sentinelMismatch; + pub const unwrapError = no_panic.unwrapError; + pub const outOfBounds = no_panic.outOfBounds; + pub const startGreaterThanEnd = no_panic.startGreaterThanEnd; + pub const inactiveUnionField = no_panic.inactiveUnionField; + pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder; + pub const reachedUnreachable = no_panic.reachedUnreachable; + pub const unwrapNull = no_panic.unwrapNull; + pub const castToNull = no_panic.castToNull; + pub const incorrectAlignment = no_panic.incorrectAlignment; + pub const invalidErrorCode = no_panic.invalidErrorCode; + pub const unexpectedErrorCode = no_panic.unexpectedErrorCode; + pub const integerOutOfBounds = no_panic.integerOutOfBounds; + pub const shlOverflow = no_panic.shlOverflow; + pub const shrOverflow = no_panic.shrOverflow; + pub const divideByZero = no_panic.divideByZero; + pub const exactDivisionRemainder = no_panic.exactDivisionRemainder; + pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds; + pub const corruptSwitch = no_panic.corruptSwitch; + pub const shiftRhsTooBig = no_panic.shiftRhsTooBig; + pub const invalidEnumValue = no_panic.invalidEnumValue; + pub const forLenMismatch = no_panic.forLenMismatch; + pub const copyLenMismatch = no_panic.copyLenMismatch; + pub const memcpyAlias = no_panic.memcpyAlias; + pub const noreturnReturned = no_panic.noreturnReturned; + pub const loadUninstantiableType = no_panic.loadUninstantiableType; +}; +fn myPanic(msg: []const u8, _: ?usize) noreturn { + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); + stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {}; + std.process.exit(0); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental/change_panic_handler_explicit/1/main.zig b/test/incremental/change_panic_handler_explicit/1/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..a1a8742a597b78f18e449dbc60348c4974bb5874 --- /dev/null +++ b/test/incremental/change_panic_handler_explicit/1/main.zig @@ -0,0 +1,46 @@ +pub fn main() !u8 { + var a: u8 = undefined; + a = 255; + _ = a + 1; + return 1; +} +const no_panic = std.debug.no_panic; +pub const panic = struct { + pub const call = myPanic; + pub fn integerOverflow() noreturn { + @panic("integer overflow"); + } + pub const sentinelMismatch = no_panic.sentinelMismatch; + pub const unwrapError = no_panic.unwrapError; + pub const outOfBounds = no_panic.outOfBounds; + pub const startGreaterThanEnd = no_panic.startGreaterThanEnd; + pub const inactiveUnionField = no_panic.inactiveUnionField; + pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder; + pub const reachedUnreachable = no_panic.reachedUnreachable; + pub const unwrapNull = no_panic.unwrapNull; + pub const castToNull = no_panic.castToNull; + pub const incorrectAlignment = no_panic.incorrectAlignment; + pub const invalidErrorCode = no_panic.invalidErrorCode; + pub const unexpectedErrorCode = no_panic.unexpectedErrorCode; + pub const integerOutOfBounds = no_panic.integerOutOfBounds; + pub const shlOverflow = no_panic.shlOverflow; + pub const shrOverflow = no_panic.shrOverflow; + pub const divideByZero = no_panic.divideByZero; + pub const exactDivisionRemainder = no_panic.exactDivisionRemainder; + pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds; + pub const corruptSwitch = no_panic.corruptSwitch; + pub const shiftRhsTooBig = no_panic.shiftRhsTooBig; + pub const invalidEnumValue = no_panic.invalidEnumValue; + pub const forLenMismatch = no_panic.forLenMismatch; + pub const copyLenMismatch = no_panic.copyLenMismatch; + pub const memcpyAlias = no_panic.memcpyAlias; + pub const noreturnReturned = no_panic.noreturnReturned; + pub const loadUninstantiableType = no_panic.loadUninstantiableType; +}; +fn myPanic(msg: []const u8, _: ?usize) noreturn { + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); + stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {}; + std.process.exit(0); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental/change_panic_handler_explicit/2/main.zig b/test/incremental/change_panic_handler_explicit/2/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..9b6143b2fa2dc39db42a6bdccc82acaf428986b7 --- /dev/null +++ b/test/incremental/change_panic_handler_explicit/2/main.zig @@ -0,0 +1,46 @@ +pub fn main() !u8 { + var a: u8 = undefined; + a = 255; + _ = a + 1; + return 1; +} +const no_panic = std.debug.no_panic; +pub const panic = struct { + pub const call = myPanicNew; + pub fn integerOverflow() noreturn { + @panic("integer overflow"); + } + pub const sentinelMismatch = no_panic.sentinelMismatch; + pub const unwrapError = no_panic.unwrapError; + pub const outOfBounds = no_panic.outOfBounds; + pub const startGreaterThanEnd = no_panic.startGreaterThanEnd; + pub const inactiveUnionField = no_panic.inactiveUnionField; + pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder; + pub const reachedUnreachable = no_panic.reachedUnreachable; + pub const unwrapNull = no_panic.unwrapNull; + pub const castToNull = no_panic.castToNull; + pub const incorrectAlignment = no_panic.incorrectAlignment; + pub const invalidErrorCode = no_panic.invalidErrorCode; + pub const unexpectedErrorCode = no_panic.unexpectedErrorCode; + pub const integerOutOfBounds = no_panic.integerOutOfBounds; + pub const shlOverflow = no_panic.shlOverflow; + pub const shrOverflow = no_panic.shrOverflow; + pub const divideByZero = no_panic.divideByZero; + pub const exactDivisionRemainder = no_panic.exactDivisionRemainder; + pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds; + pub const corruptSwitch = no_panic.corruptSwitch; + pub const shiftRhsTooBig = no_panic.shiftRhsTooBig; + pub const invalidEnumValue = no_panic.invalidEnumValue; + pub const forLenMismatch = no_panic.forLenMismatch; + pub const copyLenMismatch = no_panic.copyLenMismatch; + pub const memcpyAlias = no_panic.memcpyAlias; + pub const noreturnReturned = no_panic.noreturnReturned; + pub const loadUninstantiableType = no_panic.loadUninstantiableType; +}; +fn myPanicNew(msg: []const u8, _: ?usize) noreturn { + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); + stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {}; + std.process.exit(0); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental/change_panic_handler_explicit/manifest b/test/incremental/change_panic_handler_explicit/manifest new file mode 100644 index 0000000000000000000000000000000000000000..3388f912ec3c0f6f111bf0750283c4c94a2b720f --- /dev/null +++ b/test/incremental/change_panic_handler_explicit/manifest @@ -0,0 +1,23 @@ +#exe { +-Mroot=main.zig +#} + +#write main.zig 0/main.zig +#update +#check stdout { +panic message: integer overflow +#} + +#write main.zig 1/main.zig +#update { +change the panic handler body#} +#check stdout { +new panic message: integer overflow +#} + +#write main.zig 2/main.zig +#update { +change the panic handler function value#} +#check stdout { +third panic message: integer overflow +#} diff --git a/test/incremental/change_reify_struct_field_type b/test/incremental/change_reify_struct_field_type index bc157bd68364af5eda2d806a2d83b24479bdb843..92a41faee42808ef4b2338a1bdac241507e659fe 100644 --- a/test/incremental/change_reify_struct_field_type +++ b/test/incremental/change_reify_struct_field_type @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { fn getEnum(s: @Struct(.auto, null, &.{"field"}, &.{struct { tag: Enum }}, &.{.{}})) Enum { return s.field.tag; } @@ -8,9 +11,12 @@ pub fn main(init: std.process.Init) !void { } const Enum = enum { foo, bar }; const std = @import("std"); -#expect_stdout="foo" -#update=change field type -#file=main.zig +#} +#update +#check stdout { +foo#} + +#write main.zig { fn getEnum(s: @Struct(.auto, null, &.{"field"}, &.{Enum}, &.{.{}})) Enum { return s.field; } @@ -19,4 +25,8 @@ pub fn main(init: std.process.Init) !void { } const Enum = enum { foo, bar }; const std = @import("std"); -#expect_stdout="bar" +#} +#update { +change field type#} +#check stdout { +bar#} diff --git a/test/incremental/change_shift_op b/test/incremental/change_shift_op index d1fa6e747e125af9506bdff9766bbb5fa0dcd368..bcd0ff1d2f61b4aeb305a350bc64a8b64b76fbed 100644 --- a/test/incremental/change_shift_op +++ b/test/incremental/change_shift_op @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { pub fn main() !void { try foo(0x1300); } @@ -9,9 +12,13 @@ fn foo(x: u16) !void { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="0x3000\n" -#update=change to right shift -#file=main.zig +#} +#update +#check stdout { +0x3000 +#} + +#write main.zig { pub fn main() !void { try foo(0x1300); } @@ -21,4 +28,9 @@ fn foo(x: u16) !void { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="0x130\n" +#} +#update { +change to right shift#} +#check stdout { +0x130 +#} diff --git a/test/incremental/change_struct_same_fields b/test/incremental/change_struct_same_fields index b94f03778b21467cf255a13c48c95cce59f99329..348a443dd555223482c96f6ae0ccd7ed5a48c459 100644 --- a/test/incremental/change_struct_same_fields +++ b/test/incremental/change_struct_same_fields @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const S = extern struct { x: u8, y: u8 }; pub fn main() !void { const val: S = .{ .x = 100, .y = 200 }; @@ -14,10 +17,13 @@ fn foo(val: *const S) !void { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="100 200\n" +#} +#update +#check stdout { +100 200 +#} -#update=change struct layout -#file=main.zig +#write main.zig { const S = extern struct { x: u32, y: u32 }; pub fn main() !void { const val: S = .{ .x = 100, .y = 200 }; @@ -32,10 +38,14 @@ fn foo(val: *const S) !void { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="100 200\n" +#} +#update { +change struct layout#} +#check stdout { +100 200 +#} -#update=change values -#file=main.zig +#write main.zig { const S = extern struct { x: u32, y: u32 }; pub fn main() !void { const val: S = .{ .x = 1234, .y = 5678 }; @@ -50,4 +60,9 @@ fn foo(val: *const S) !void { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="1234 5678\n" +#} +#update { +change values#} +#check stdout { +1234 5678 +#} diff --git a/test/incremental/change_union_tag_type b/test/incremental/change_union_tag_type index 5f754de42ab27224a99c30cff9c645c60fbbf1cb..dbbbc94c1e9bebbfdd1b98743de8a85763e725a5 100644 --- a/test/incremental/change_union_tag_type +++ b/test/incremental/change_union_tag_type @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const A = enum(u8) { a }; const B = enum(u8) { b }; const Foo = union(A) { a: u8 }; @@ -9,10 +12,13 @@ pub fn main(init: std.process.Init) !void { try stdout_writer.interface.print("{s}\n", .{field_name}); } const std = @import("std"); -#expect_stdout="a\n" +#} +#update +#check stdout { +a +#} -#update=change tag type -#file=main.zig +#write main.zig { const A = enum(u8) { a }; const B = enum(u8) { b }; const Foo = union(B) { a: u8 }; @@ -22,11 +28,15 @@ pub fn main(init: std.process.Init) !void { try stdout_writer.interface.print("{s}\n", .{field_name}); } const std = @import("std"); -#expect_error=main.zig:3:24: error: no field named 'a' in enum 'main.B' -#expect_error=main.zig:2:11: note: enum declared here +#} +#update { +change tag type#} +#check errors { +main.zig:3:24: error: no field named 'a' in enum 'main.B' +main.zig:2:11: note: enum declared here +#} -#update=change field name to match new tag type -#file=main.zig +#write main.zig { const A = enum(u8) { a }; const B = enum(u8) { b }; const Foo = union(B) { b: u8 }; @@ -36,4 +46,9 @@ pub fn main(init: std.process.Init) !void { try stdout_writer.interface.print("{s}\n", .{field_name}); } const std = @import("std"); -#expect_stdout="b\n" +#} +#update { +change field name to match new tag type#} +#check stdout { +b +#} diff --git a/test/incremental/change_zon_file b/test/incremental/change_zon_file index 7616c53ea80094d746102df13d51e387a3d1bc40..53b7c82c84703475b71d4495a716041858d27cee 100644 --- a/test/incremental/change_zon_file +++ b/test/incremental/change_zon_file @@ -1,47 +1,74 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const std = @import("std"); const message: []const u8 = @import("message.zon"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, message); } const io = std.Io.Threaded.global_single_threaded.io(); -#file=message.zon +#} +#write message.zon { "Hello, World!\n" -#expect_stdout="Hello, World!\n" +#} +#update +#check stdout { +Hello, World! +#} -#update=change ZON file contents -#file=message.zon +#write message.zon { "Hello again, World!\n" -#expect_stdout="Hello again, World!\n" +#} +#update { +change ZON file contents#} +#check stdout { +Hello again, World! +#} -#update=delete file -#rm_file=message.zon -#expect_error=message.zon:1:1: error: unable to load "message.zon": FileNotFound -#expect_error=main.zig:2:37: note: file imported here +#delete message.zon +#update { +delete file#} +#check errors { +message.zon:1:1: error: unable to load "message.zon": FileNotFound +main.zig:2:37: note: file imported here +#} -#update=remove reference to ZON file -#file=main.zig +#write main.zig { const std = @import("std"); const message: []const u8 = @import("message.zon"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n"); } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=message.zon:1:1: error: unable to load "message.zon": FileNotFound -#expect_error=main.zig:2:37: note: file imported here +#} +#update { +remove reference to ZON file#} +#check errors { +message.zon:1:1: error: unable to load "message.zon": FileNotFound +main.zig:2:37: note: file imported here +#} -#update=recreate ZON file -#file=message.zon +#write message.zon { "We're back, World!\n" -#expect_stdout="a hardcoded string\n" +#} +#update { +recreate ZON file#} +#check stdout { +a hardcoded string +#} -#update=re-introduce reference to ZON file -#file=main.zig +#write main.zig { const std = @import("std"); const message: []const u8 = @import("message.zon"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, message); } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="We're back, World!\n" +#} +#update { +re-introduce reference to ZON file#} +#check stdout { +We're back, World! +#} diff --git a/test/incremental/change_zon_file_no_result_type b/test/incremental/change_zon_file_no_result_type index 5deca8c7fda5e77995ec385bea0ed4e58839dd2e..dd63226cbaa21d113d2ff5f7f43484d616a5d956 100644 --- a/test/incremental/change_zon_file_no_result_type +++ b/test/incremental/change_zon_file_no_result_type @@ -1,21 +1,33 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, @import("foo.zon").message); } -#file=foo.zon +#} +#write foo.zon { .{ .message = "Hello, World!\n", .a_number = 0, } -#expect_stdout="Hello, World!\n" +#} +#update +#check stdout { +Hello, World! +#} -#update=change ZON file -#file=foo.zon +#write foo.zon { .{ .message = "Hello again, World!\n", .b_number = 0, } -#expect_stdout="Hello again, World!\n" +#} +#update { +change ZON file#} +#check stdout { +Hello again, World! +#} diff --git a/test/incremental/compile_error_then_log b/test/incremental/compile_error_then_log index a19430f7cd2a2e45a7fadb4c6b8b57a3a9927608..44dd98a1734dc9fdc2c5618fa58d8fdcceea8ce9 100644 --- a/test/incremental/compile_error_then_log +++ b/test/incremental/compile_error_then_log @@ -1,5 +1,8 @@ -#update=initial version with compile error -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { pub fn main() void {} comptime { @compileError("this is an error"); @@ -7,11 +10,17 @@ comptime { comptime { @compileLog("this is a log"); } -#expect_error=main.zig:3:5: error: this is an error -#expect_compile_log=@as(*const [13:0]u8, "this is a log") +#} +#update { +compile error#} +#check errors { +main.zig:3:5: error: this is an error -#update=remove the compile error -#file=main.zig +Compile Log Output: +@as(*const [13:0]u8, "this is a log") +#} + +#write main.zig { pub fn main() void {} comptime { //@compileError("this is an error"); @@ -19,5 +28,12 @@ comptime { comptime { @compileLog("this is a log"); } -#expect_error=main.zig:6:5: error: found compile log statement -#expect_compile_log=@as(*const [13:0]u8, "this is a log") +#} +#update { +remove the compile error#} +#check errors { +main.zig:6:5: error: found compile log statement + +Compile Log Output: +@as(*const [13:0]u8, "this is a log") +#} diff --git a/test/incremental/compile_log b/test/incremental/compile_log index cc4c6368391979358ac215f6b9c73a080cd39ff0..d4445f3ca80aaac2262e70b94ec2f8acaf751603 100644 --- a/test/incremental/compile_log +++ b/test/incremental/compile_log @@ -1,28 +1,46 @@ -#update=initial version with no compile log -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="Hello, World!\n" +#} +#update { +no compile log#} +#check stdout { +Hello, World! +#} -#update=add compile log -#file=main.zig +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); @compileLog("this is a log"); } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:4:5: error: found compile log statement -#expect_compile_log=@as(*const [13:0]u8, "this is a log") +#} +#update { +add compile log#} +#check errors { +main.zig:4:5: error: found compile log statement -#update=remove compile log -#file=main.zig +Compile Log Output: +@as(*const [13:0]u8, "this is a log") +#} + +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="Hello, World!\n" +#} +#update { +remove compile log#} +#check stdout { +Hello, World! +#} diff --git a/test/incremental/delete_comptime_decls b/test/incremental/delete_comptime_decls index 3d653d6d7dea5296437618569edb701d43bbf3ba..ffae08f3d8d7a26173aefdb4c398895eb26d420a 100644 --- a/test/incremental/delete_comptime_decls +++ b/test/incremental/delete_comptime_decls @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { pub fn main() void {} comptime { var array = [_:0]u8{ 1, 2, 3, 4 }; @@ -22,13 +25,16 @@ comptime { const slice = array[3..2]; _ = slice; } -#expect_error=main.zig:5:32: error: end index 6 out of bounds for slice of length 4 +1 (sentinel) -#expect_error=main.zig:10:28: error: end index 6 out of bounds for array of length 4 +1 (sentinel) -#expect_error=main.zig:15:28: error: end index 5 out of bounds for array of length 4 -#expect_error=main.zig:20:25: error: start index 3 is larger than end index 2 +#} +#update +#check errors { +main.zig:5:32: error: end index 6 out of bounds for slice of length 4 +1 (sentinel) +main.zig:10:28: error: end index 6 out of bounds for array of length 4 +1 (sentinel) +main.zig:15:28: error: end index 5 out of bounds for array of length 4 +main.zig:20:25: error: start index 3 is larger than end index 2 +#} -#update=delete and modify comptime decls -#file=main.zig +#write main.zig { pub fn main() void {} comptime { const x: [*c]u8 = null; @@ -37,4 +43,9 @@ comptime { const y = x[0..runtime_len]; _ = y; } -#expect_error=main.zig:6:16: error: slice of null pointer +#} +#update { +delete and modify comptime decls#} +#check errors { +main.zig:6:16: error: slice of null pointer +#} diff --git a/test/incremental/dependency_on_type_of_inferred_global b/test/incremental/dependency_on_type_of_inferred_global index 66e52d58394bec7274dec9b0e1767a72338109ac..73d36a7254759c0f98720617aaf3848cc8703070 100644 --- a/test/incremental/dependency_on_type_of_inferred_global +++ b/test/incremental/dependency_on_type_of_inferred_global @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const foo = @as(u8, 123); comptime { // depends on value of `foo` @@ -10,9 +13,12 @@ comptime { if (@TypeOf(&foo) != *const u8) unreachable; } pub fn main() void {} -#expect_stdout="" -#update=change the type -#file=main.zig +#} +#update +#check stdout { +#} + +#write main.zig { const foo = @as(u16, 123); comptime { // depends on value of `foo` @@ -23,4 +29,9 @@ comptime { if (@TypeOf(&foo) != *const u8) unreachable; } pub fn main() void {} -#expect_error=main.zig:8:37: error: reached unreachable code +#} +#update { +change the type#} +#check errors { +main.zig:8:37: error: reached unreachable code +#} diff --git a/test/incremental/do_nothing b/test/incremental/do_nothing index bc50b4bc03836fe9fd93317c60c3b703c7368c0b..213f7f1892d8fa96dbb9313054d862513956ce66 100644 --- a/test/incremental/do_nothing +++ b/test/incremental/do_nothing @@ -1,6 +1,11 @@ -// TODO: it'd be great if we could actually check that no analysis happened! -#update=initial version -#file=main.zig +#todo { +it'd be great if we could actually check that no analysis happened! +#} +#exe { +-Mroot=main.zig +#} + +#write main.zig { pub fn main() void { const ptr: *const O = @ptrFromInt(0x1000); _ = ptr; @@ -18,9 +23,12 @@ const O = opaque { _ = wrapper; } }; -#expect_stdout="" -#update=do literally nothing -#file=main.zig +#} +#update +#check stdout { +#} + +#write main.zig { pub fn main() void { const ptr: *const O = @ptrFromInt(0x1000); _ = ptr; @@ -38,4 +46,8 @@ const O = opaque { _ = wrapper; } }; -#expect_stdout="" +#} +#update { +do literally nothing#} +#check stdout { +#} diff --git a/test/incremental/fix_astgen_failure b/test/incremental/fix_astgen_failure index adddc9ac054c2d95709467018ef70af6a71d2a8a..feb078359a513becff249d9e5e89a88a2e243352 100644 --- a/test/incremental/fix_astgen_failure +++ b/test/incremental/fix_astgen_failure @@ -1,35 +1,59 @@ -#update=initial version with error -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { pub fn main() !void { try @import("foo.zig").hello(); } -#file=foo.zig +#} +#write foo.zig { pub fn hello() !void { try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } -#expect_error=foo.zig:2:9: error: use of undeclared identifier 'std' -#update=fix the error -#file=foo.zig +#} +#update { +error#} +#check errors { +foo.zig:2:9: error: use of undeclared identifier 'std' +#} + +#write foo.zig { const std = @import("std"); pub fn hello() !void { try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="Hello, World!\n" -#update=add new error -#file=foo.zig +#} +#update { +fix the error#} +#check stdout { +Hello, World! +#} + +#write foo.zig { const std = @import("std"); pub fn hello() !void { try std.Io.File.stdout().writeStreamingAll(io, hello_str); } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=foo.zig:3:52: error: use of undeclared identifier 'hello_str' -#update=fix the new error -#file=foo.zig +#} +#update { +add new error#} +#check errors { +foo.zig:3:52: error: use of undeclared identifier 'hello_str' +#} + +#write foo.zig { const std = @import("std"); const hello_str = "Hello, World! Again!\n"; pub fn hello() !void { try std.Io.File.stdout().writeStreamingAll(io, hello_str); } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="Hello, World! Again!\n" +#} +#update { +fix the new error#} +#check stdout { +Hello, World! Again! +#} diff --git a/test/incremental/function_becomes_inline b/test/incremental/function_becomes_inline index 91afe196ab4d1c28fb041a5f9dd8f0b6956ea463..7d64be329579442c284c149e6342bcc0d3105c36 100644 --- a/test/incremental/function_becomes_inline +++ b/test/incremental/function_becomes_inline @@ -1,5 +1,8 @@ -#update=non-inline version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { pub fn main() !void { try foo(); } @@ -8,10 +11,14 @@ fn foo() !void { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="Hello, World!\n" +#} +#update { +non-inline version#} +#check stdout { +Hello, World! +#} -#update=make function inline -#file=main.zig +#write main.zig { pub fn main() !void { try foo(); } @@ -20,10 +27,14 @@ inline fn foo() !void { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="Hello, World!\n" +#} +#update { +make function inline#} +#check stdout { +Hello, World! +#} -#update=change string -#file=main.zig +#write main.zig { pub fn main() !void { try foo(); } @@ -32,4 +43,9 @@ inline fn foo() !void { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="Hello, `inline` World!\n" +#} +#update { +change string#} +#check stdout { +Hello, `inline` World! +#} diff --git a/test/incremental/hello b/test/incremental/hello index 6988323d3dbb504cad158ad134fccc4ed4023087..74a45d2a8b81afebe334968569949b6c82d4c397 100644 --- a/test/incremental/hello +++ b/test/incremental/hello @@ -1,16 +1,28 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, "good morning\n"); } -#expect_stdout="good morning\n" -#update=change the string -#file=main.zig +#} +#update +#check stdout { +good morning +#} + +#write main.zig { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, "おはようございます\n"); } -#expect_stdout="おはようございます\n" +#} +#update { +change the string#} +#check stdout { +おはようございます +#} diff --git a/test/incremental/increase_array_field_length b/test/incremental/increase_array_field_length index 714473dd8302d57b0b2644a90f73442d47e9db2d..7b0eb9c165ff1a1b43206624569303b820d658ac 100644 --- a/test/incremental/increase_array_field_length +++ b/test/incremental/increase_array_field_length @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const Big = struct { arr: [10]u8 }; pub fn main(init: std.process.Init) !void { var big: Big = undefined; @@ -12,10 +15,13 @@ pub fn main(init: std.process.Init) !void { }; } const std = @import("std"); -#expect_stdout="arr[9] = 234\n" +#} +#update +#check stdout { +arr[9] = 234 +#} -#update=increase array length -#file=main.zig +#write main.zig { const Big = struct { arr: [1000]u8 }; pub fn main(init: std.process.Init) !void { var big: Big = undefined; @@ -28,4 +34,9 @@ pub fn main(init: std.process.Init) !void { }; } const std = @import("std"); -#expect_stdout="arr[999] = 234\n" +#} +#update { +increase array length#} +#check stdout { +arr[999] = 234 +#} diff --git a/test/incremental/make_decl_pub b/test/incremental/make_decl_pub index cdb057a7dc1fbc0f72d5ed935d47f47c91855f9e..59a54ece02b3850083fdd6c86c63d901cd346157 100644 --- a/test/incremental/make_decl_pub +++ b/test/incremental/make_decl_pub @@ -1,23 +1,35 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const foo = @import("foo.zig"); pub fn main() !void { try foo.hello(); } -#file=foo.zig +#} +#write foo.zig { const std = @import("std"); fn hello() !void { try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:3:12: error: 'hello' is not marked 'pub' -#expect_error=foo.zig:2:1: note: declared here +#} +#update +#check errors { +main.zig:3:12: error: 'hello' is not marked 'pub' +foo.zig:2:1: note: declared here +#} -#update=make hello pub -#file=foo.zig +#write foo.zig { const std = @import("std"); pub fn hello() !void { try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="Hello, World!\n" +#} +#update { +make hello pub#} +#check stdout { +Hello, World! +#} diff --git a/test/incremental/modify_inline_fn b/test/incremental/modify_inline_fn index aa7223f6ace49b27eaadfbc92bebffbe6affcc44..956a47a6da82a984edfb3cc9ebf788759c0c61a5 100644 --- a/test/incremental/modify_inline_fn +++ b/test/incremental/modify_inline_fn @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const std = @import("std"); pub fn main() !void { const str = getStr(); @@ -9,9 +12,13 @@ inline fn getStr() []const u8 { return "foo\n"; } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="foo\n" -#update=change the string -#file=main.zig +#} +#update +#check stdout { +foo +#} + +#write main.zig { const std = @import("std"); pub fn main() !void { const str = getStr(); @@ -21,4 +28,9 @@ inline fn getStr() []const u8 { return "bar\n"; } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="bar\n" +#} +#update { +change the string#} +#check stdout { +bar +#} diff --git a/test/incremental/move_src b/test/incremental/move_src index 20134267d1244fefb659ffc3d204f55c5db347d3..b385a312dbb5d55b6f7605aee556abbda5f9ade4 100644 --- a/test/incremental/move_src +++ b/test/incremental/move_src @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const std = @import("std"); pub fn main() !void { var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); @@ -12,10 +15,13 @@ fn bar() u32 { return 123; } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="7 123\n" +#} +#update +#check stdout { +7 123 +#} -#update=add newline -#file=main.zig +#write main.zig { const std = @import("std"); pub fn main() !void { var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); @@ -29,4 +35,9 @@ fn bar() u32 { return 123; } const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="8 123\n" +#} +#update { +add newline#} +#check stdout { +8 123 +#} diff --git a/test/incremental/pointer_to_decl_with_astgen_error b/test/incremental/pointer_to_decl_with_astgen_error index 6c9359209b7fa7e83de855a3e4680f31c566fa5c..87d07ca1abe476a8bc0be5a2abfa2b127736127f 100644 --- a/test/incremental/pointer_to_decl_with_astgen_error +++ b/test/incremental/pointer_to_decl_with_astgen_error @@ -1,23 +1,36 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const foo = {}; pub fn main() void { _ = &foo; } -#expect_stdout="" +#} +#update +#check stdout { +#} -#update=introduce use of undeclared identifier -#file=main.zig +#write main.zig { const foo = something; pub fn main() void { _ = &foo; } -#expect_error=main.zig:1:13: error: use of undeclared identifier 'something' +#} +#update { +introduce use of undeclared identifier#} +#check errors { +main.zig:1:13: error: use of undeclared identifier 'something' +#} -#update=revert use of undeclared identifier -#file=main.zig +#write main.zig { const foo = {}; pub fn main() void { _ = &foo; } -#expect_stdout="" +#} +#update { +revert use of undeclared identifier#} +#check stdout { +#} diff --git a/test/incremental/recursive_function_becomes_non_recursive b/test/incremental/recursive_function_becomes_non_recursive index e789f3860de557247e84240918773ada0362feaa..442748a079c7bc0ca8a97fc726a661e86546acd1 100644 --- a/test/incremental/recursive_function_becomes_non_recursive +++ b/test/incremental/recursive_function_becomes_non_recursive @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { pub fn main() !void { try foo(false); } @@ -10,10 +13,13 @@ fn foo(recurse: bool) !void { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="non-recursive path\n" +#} +#update +#check stdout { +non-recursive path +#} -#update=eliminate recursion and change argument -#file=main.zig +#write main.zig { pub fn main() !void { try foo(true); } @@ -24,4 +30,9 @@ fn foo(recurse: bool) !void { } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="x==1\n" +#} +#update { +eliminate recursion and change argument#} +#check stdout { +x==1 +#} diff --git a/test/incremental/remove_enum_field b/test/incremental/remove_enum_field index f478ea7f5528410cb20c367cb01aa0157a34fc12..67b0db55b9d597270216eee728189fbd0a638833 100644 --- a/test/incremental/remove_enum_field +++ b/test/incremental/remove_enum_field @@ -1,27 +1,39 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const MyEnum = enum(u8) { foo = 1, bar = 2, }; pub fn main() !void { var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); - try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)}); + try stdout_writer.interface.print("{}\n", .{@backingInt(MyEnum.foo)}); } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="1\n" -#update=remove enum field -#file=main.zig +#} +#update +#check stdout { +1 +#} + +#write main.zig { const MyEnum = enum(u8) { //foo = 1, bar = 2, }; pub fn main() !void { var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); - try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)}); + try stdout_writer.interface.print("{}\n", .{@backingInt(MyEnum.foo)}); } const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:7:69: error: enum 'main.MyEnum' has no member named 'foo' -#expect_error=main.zig:1:16: note: enum declared here +#} +#update { +remove enum field#} +#check errors { +main.zig:7:68: error: enum 'main.MyEnum' has no member named 'foo' +main.zig:1:16: note: enum declared here +#} diff --git a/test/incremental/remove_function_with_changed_callee b/test/incremental/remove_function_with_changed_callee index 31e962124dfb10374fd9b0dd70bc743edde143bd..d0a04678922d9ef841393ad2903469baf6ed7ca6 100644 --- a/test/incremental/remove_function_with_changed_callee +++ b/test/incremental/remove_function_with_changed_callee @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); @@ -23,9 +26,12 @@ fn bar(a: A) !void { pub fn main() !void { try foo(); } -#expect_stdout="0" -#update=remove foo and change bar wasm function type -#file=main.zig +#} +#update +#check stdout { +0#} + +#write main.zig { //! This update must preserve `bar` `InternPool.Index` value, while changing its wasm function type. //! If `foo` code is preserved in the binary without any changes, wasm type checking will fail. const std = @import("std"); @@ -47,4 +53,8 @@ fn bar(a: A) !void { pub fn main() !void { try bar(.one); } -#expect_stdout="1" +#} +#update { +remove foo and change bar wasm function type#} +#check stdout { +1#} diff --git a/test/incremental/remove_invalid_union_backing_enum b/test/incremental/remove_invalid_union_backing_enum index 04970380fb621c4e2e9cb2d3c5069d6448c5b989..8a2099afb4ebe92697ff76f773b53abb4eb8b871 100644 --- a/test/incremental/remove_invalid_union_backing_enum +++ b/test/incremental/remove_invalid_union_backing_enum @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const E = enum { a, b, c }; const U = union(E) { a: i32, @@ -11,10 +14,14 @@ pub fn main() void { const u: U = .{ .a = 123 }; _ = u; } -#expect_error=main.zig:6:5: error: no field named 'd' in enum 'main.E' -#expect_error=main.zig:1:11: note: enum declared here -#update=remove invalid backing enum -#file=main.zig +#} +#update +#check errors { +main.zig:6:5: error: no field named 'd' in enum 'main.E' +main.zig:1:11: note: enum declared here +#} + +#write main.zig { const U = union { a: i32, b: f64, @@ -25,4 +32,8 @@ pub fn main() void { const u: U = .{ .a = 123 }; _ = u; } -#expect_stdout="" +#} +#update { +remove invalid backing enum#} +#check stdout { +#} diff --git a/test/incremental/struct_reification_becomes_anon_init b/test/incremental/struct_reification_becomes_anon_init index 7099685ba2c2e464607039c5eb461fa500422d69..bd790cdeb9445aab9834c64f0a5fa683974b6d95 100644 --- a/test/incremental/struct_reification_becomes_anon_init +++ b/test/incremental/struct_reification_becomes_anon_init @@ -1,19 +1,30 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const Foo = @Struct(.auto, null, &.{"reified_field"}, &.{void}, &.{.{}}); pub fn main(init: std.process.Init) !void { const field_name = @typeInfo(Foo).@"struct".field_names[0]; try std.Io.File.stdout().writeStreamingAll(init.io, field_name ++ "\n"); } const std = @import("std"); -#expect_stdout="reified_field\n" +#} +#update +#check stdout { +reified_field +#} -#update=change to `@TypeOf` anonymous init -#file=main.zig +#write main.zig { const Foo = @TypeOf(.{ .anon_field = 123 }); pub fn main(init: std.process.Init) !void { const field_name = @typeInfo(Foo).@"struct".field_names[0]; try std.Io.File.stdout().writeStreamingAll(init.io, field_name ++ "\n"); } const std = @import("std"); -#expect_stdout="anon_field\n" +#} +#update { +change to `@TypeOf` anonymous init#} +#check stdout { +anon_field +#} diff --git a/test/incremental/tag_name b/test/incremental/tag_name index 17954435c15786ddf98bbd576c1a2ae346ab14aa..05991657cdbfd920ee24d094bb8db93ada706d17 100644 --- a/test/incremental/tag_name +++ b/test/incremental/tag_name @@ -1,27 +1,41 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const std = @import("std"); var some_enum: enum { first, second } = .first; const io = std.Io.Threaded.global_single_threaded.io(); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum)); } -#expect_stdout="first" -#update=no change -#file=main.zig +#} +#update +#check stdout { +first#} + +#write main.zig { const std = @import("std"); var some_enum: enum { first, second } = .first; const io = std.Io.Threaded.global_single_threaded.io(); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum)); } -#expect_stdout="first" -#update=swap fields -#file=main.zig +#} +#update { +no change#} +#check stdout { +first#} + +#write main.zig { const std = @import("std"); var some_enum: enum { second, first } = .first; const io = std.Io.Threaded.global_single_threaded.io(); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum)); } -#expect_stdout="first" +#} +#update { +swap fields#} +#check stdout { +first#} diff --git a/test/incremental/temporary_analysis_error_in_generic_signature b/test/incremental/temporary_analysis_error_in_generic_signature index d8db72050b3af1164a312e1e7a1ed1f89bfc3dae..dbb3b6407eba276b9654775979353b53d9cea452 100644 --- a/test/incremental/temporary_analysis_error_in_generic_signature +++ b/test/incremental/temporary_analysis_error_in_generic_signature @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { //! The original repro here depends on re-analysis order, which depends on //! declaration order, so this exact declaration order must be used. const Foo = struct { x: u8 }; @@ -11,10 +14,13 @@ fn bar(comptime x: u8) @This().Foo { return .{ .x = x }; } const std = @import("std"); -#expect_stdout="Z\n" +#} +#update +#check stdout { +Z +#} -#update=change generic signature to use non-existent member -#file=main.zig +#write main.zig { //! The original repro here depends on re-analysis order, which depends on //! declaration order, so this exact declaration order must be used. const Foo = struct { x: u8 }; @@ -26,11 +32,15 @@ fn bar(comptime x: u8) @This().FooAlias { return .{ .x = x }; } const std = @import("std"); -#expect_error=main.zig:8:31: error: root source file struct 'main' has no member named 'FooAlias' -#expect_error=main.zig:1:1: note: struct declared here +#} +#update { +change generic signature to use non-existent member#} +#check errors { +main.zig:8:31: error: root source file struct 'main' has no member named 'FooAlias' +main.zig:1:1: note: struct declared here +#} -#update=add that member, fixing the error -#file=main.zig +#write main.zig { //! The original repro here depends on re-analysis order, which depends on //! declaration order, so this exact declaration order must be used. const Foo = struct { x: u8 }; @@ -43,4 +53,9 @@ fn bar(comptime x: u8) @This().FooAlias { return .{ .x = x }; } const std = @import("std"); -#expect_stdout="Z\n" +#} +#update { +add that member, fixing the error#} +#check stdout { +Z +#} diff --git a/test/incremental/temporary_parse_error b/test/incremental/temporary_parse_error index b19636e4455a8c19a028372276b96560c5c523da..9687ac4844b79133391f85addd09e71a44c8f6cd 100644 --- a/test/incremental/temporary_parse_error +++ b/test/incremental/temporary_parse_error @@ -1,15 +1,28 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const std = @import("std"); pub fn main() !void {} -#expect_stdout="" +#} +#update +#check stdout { +#} -#update=introduce parse error -#file=main.zig +#write main.zig { pub fn main() !void { -#expect_error=main.zig:2:1: error: expected statement, found 'EOF' +#} +#update { +introduce parse error#} +#check errors { +main.zig:2:1: error: expected statement, found 'EOF' +#} -#update=fix parse error -#file=main.zig +#write main.zig { pub fn main() !void {} -#expect_stdout="" +#} +#update { +fix parse error#} +#check stdout { +#} diff --git a/test/incremental/type_becomes_comptime_only b/test/incremental/type_becomes_comptime_only index f5b0f9b5786a684d7e2dc72f78438d18ffad84e5..f174faacd788e3e5d4720c9badb1cb2e5f91c9ea 100644 --- a/test/incremental/type_becomes_comptime_only +++ b/test/incremental/type_becomes_comptime_only @@ -1,5 +1,8 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const SomeType = u32; const S = struct { x: SomeType, @@ -9,10 +12,12 @@ pub fn main() void { const s: S = .{ .x = 456 }; s.foo(); } -#expect_stdout="" +#} +#update +#check stdout { +#} -#update=make S comptime-only -#file=main.zig +#write main.zig { const SomeType = comptime_int; const S = struct { x: SomeType, @@ -22,10 +27,13 @@ pub fn main() void { const s: S = .{ .x = 456 }; s.foo(); } -#expect_stdout="" +#} +#update { +make S comptime-only#} +#check stdout { +#} -#update=make S runtime again -#file=main.zig +#write main.zig { const SomeType = u16; const S = struct { x: SomeType, @@ -35,4 +43,8 @@ pub fn main() void { const s: S = .{ .x = 456 }; s.foo(); } -#expect_stdout="" +#} +#update { +make S runtime again#} +#check stdout { +#} diff --git a/test/incremental/type_dependency_loop b/test/incremental/type_dependency_loop index b645d4266215f712826dee9291dc3150c397108f..85de8ae8391015b85194357423522046466cc042 100644 --- a/test/incremental/type_dependency_loop +++ b/test/incremental/type_dependency_loop @@ -1,50 +1,70 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { pub const A = struct { b: B }; pub const B = struct { a: A }; pub fn main() void { _ = @as(B, undefined); } -#expect_error=:error: dependency loop with length 2 -#expect_error=main.zig:2:27: note: type 'main.B' depends on type 'main.A' for field declared here -#expect_error=main.zig:1:27: note: type 'main.A' depends on type 'main.B' for field declared here -#expect_error=:note: eliminate any one of these dependencies to break the loop +#} +#update +#check errors { +error: dependency loop with length 2 + main.zig:2:27: note: type 'main.B' depends on type 'main.A' for field declared here + main.zig:1:27: note: type 'main.A' depends on type 'main.B' for field declared here + note: eliminate any one of these dependencies to break the loop +#} -#update=remove reference to dependency loop -#file=main.zig +#write main.zig { pub const A = struct { b: B }; pub const B = struct { a: A }; pub fn main() void { _ = B; } -#expect_stdout="" +#} +#update { +remove reference to dependency loop#} +#check stdout { +#} -#update=change dependency loop without fixing it -#file=main.zig +#write main.zig { pub const A = struct { b: B }; pub const B = struct { a: *align(@alignOf(A)) A }; pub fn main() void { _ = B; } -#expect_stdout="" +#} +#update { +change dependency loop without fixing it#} +#check stdout { +#} -#update=reference dependency loop again -#file=main.zig +#write main.zig { pub const A = struct { b: B }; pub const B = struct { a: *align(@alignOf(A)) A }; pub fn main() void { _ = @as(B, undefined); } -#expect_error=:error: dependency loop with length 2 -#expect_error=main.zig:2:43: note: type 'main.B' depends on type 'main.A' for alignment query here -#expect_error=main.zig:1:27: note: type 'main.A' depends on type 'main.B' for field declared here -#expect_error=:note: eliminate any one of these dependencies to break the loop +#} +#update { +reference dependency loop again#} +#check errors { +error: dependency loop with length 2 + main.zig:2:43: note: type 'main.B' depends on type 'main.A' for alignment query here + main.zig:1:27: note: type 'main.A' depends on type 'main.B' for field declared here + note: eliminate any one of these dependencies to break the loop +#} -#update=fix dependency loop -#file=main.zig +#write main.zig { pub const A = struct { b: B }; pub const B = struct { a: *A }; pub fn main() void { _ = @as(B, undefined); } -#expect_stdout="" +#} +#update { +fix dependency loop#} +#check stdout { +#} diff --git a/test/incremental/unreferenced_error b/test/incremental/unreferenced_error index f0ab8062594912c7b990472104877bd61a3d6680..0f5f65feab5c98985d105d06db1e2086cd6f0470 100644 --- a/test/incremental/unreferenced_error +++ b/test/incremental/unreferenced_error @@ -1,25 +1,35 @@ -#update=initial version -#file=main.zig +#exe { +-Mroot=main.zig +#} + +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, a); } const a = "Hello, World!\n"; const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="Hello, World!\n" +#} +#update +#check stdout { +Hello, World! +#} -#update=introduce compile error -#file=main.zig +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, a); } const a = @compileError("bad a"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_error=main.zig:5:11: error: bad a +#} +#update { +introduce compile error#} +#check errors { +main.zig:5:11: error: bad a +#} -#update=remove error reference -#file=main.zig +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, b); @@ -27,10 +37,14 @@ pub fn main() !void { const a = @compileError("bad a"); const b = "Hi there!\n"; const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="Hi there!\n" +#} +#update { +remove error reference#} +#check stdout { +Hi there! +#} -#update=introduce and remove reference to error -#file=main.zig +#write main.zig { const std = @import("std"); pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, a); @@ -38,4 +52,9 @@ pub fn main() !void { const a = "Back to a\n"; const b = @compileError("bad b"); const io = std.Io.Threaded.global_single_threaded.io(); -#expect_stdout="Back to a\n" +#} +#update { +introduce and remove reference to error#} +#check stdout { +Back to a +#} diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 5dbff266e78f9b3f585bdbd87b99ac8e2f27f09f..b4fe4e65337b070c8ae99e867c418657b607b0d8 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -626,6 +626,7 @@ pub fn lowerToBuildSteps( if (getExternalExecutor(io, &case.target.result, .{ .host_cpu_arch = host.result.cpu.arch, .host_os_tag = host.result.os.tag, + .link_mode = .dynamic, // TODO: this emulates old behavior until this file is deleted .link_libc = true, }) != .native) { // We wouldn't be able to run the compiled C code. diff --git a/test/src/Runner.zig b/test/src/Runner.zig new file mode 100644 index 0000000000000000000000000000000000000000..f917fccc32541d0395512e332d9d5317536921c1 --- /dev/null +++ b/test/src/Runner.zig @@ -0,0 +1,1858 @@ +const assert = std.debug.assert; +const std = @import("std"); +const Runner = @This(); +const zig_version_string = @import("builtin").zig_version_string; + +gpa: std.mem.Allocator, +arena: *std.heap.ArenaAllocator, +io: std.Io, +prog_node: std.Progress.Node, +args: Args, +host: std.Target, +server: std.zig.Server, +eb_wip: std.zig.ErrorBundle.Wip, + +const Args = struct { + @"test": ?struct { + input_dir: std.zig.Server.Message.InputDir, + dir: std.Io.Dir, + }, + manifest_file: ?std.Io.File, + zig_exe: ?std.Io.File, + lib_dir: ?std.Io.Dir, + src_dir: ?std.Io.Dir, + targets: std.ArrayList(u32), + target_bytes: std.ArrayList(u8), + libc_runtimes_dir: ?std.Io.Dir, + enable_darling: bool, + enable_qemu: bool, + enable_rosetta: bool, + enable_wasmtime: bool, + enable_wine: bool, + quiet: bool, + + fn deinit(args: *Args, gpa: std.mem.Allocator) void { + args.targets.deinit(gpa); + args.target_bytes.deinit(gpa); + args.* = undefined; + } + + fn addTarget(args: *Args, gpa: std.mem.Allocator, target: []const u8) std.mem.Allocator.Error!void { + try args.target_bytes.ensureUnusedCapacity(gpa, target.len + 1); + try args.targets.append(gpa, @intCast(args.target_bytes.items.len)); + args.target_bytes.appendSliceAssumeCapacity(target); + args.target_bytes.appendAssumeCapacity(0); + } +}; + +fn deinit(runner: *Runner) void { + runner.prog_node.end(); + runner.args.deinit(runner.gpa); + runner.eb_wip.deinit(); + runner.* = undefined; +} + +const FailError = error{AlreadyReported} || std.mem.Allocator.Error || std.Io.Cancelable; +fn fail(runner: *Runner, comptime fmt: []const u8, args: anytype) FailError { + return runner.failString(try runner.eb_wip.printString(fmt, args)); +} +fn failString(runner: *Runner, msg: std.zig.ErrorBundle.String) FailError { + try runner.eb_wip.addRootErrorMessage(.{ + .msg = msg, + }); + return error.AlreadyReported; +} + +pub const ProtocolError = error{ + ServerReadFailed, + ServerWriteFailed, + ServerEndOfStream, +} || FailError; +pub fn runServer(runner: *Runner) ProtocolError { + runner.server.serveStringMessage(.zig_version, zig_version_string) catch |err| switch (err) { + error.WriteFailed => return error.ServerWriteFailed, + }; + while (true) { + _ = runner.arena.reset(.retain_capacity); + const arena = runner.arena.allocator(); + const hdr = runner.server.receiveMessage() catch |err| switch (err) { + error.ReadFailed => return error.ServerReadFailed, + error.EndOfStream => return error.ServerEndOfStream, + }; + switch (hdr.tag) { + else => |tag| return runner.fail("unsupported message: {t}", .{tag}), + .exit => std.process.exit(0), + .args => { + const args_body = try arena.alloc(u8, hdr.bytes_len); + runner.server.in.readSliceAll(args_body) catch unreachable; + const State = enum { + positional, + @"--zig", + @"--lib", + @"--src", + @"--target", + @"--libc-runtimes", + }; + var state: State = .positional; + const state_expected: std.enums.EnumArray(State, enum { + path, + dir, + file, + string, + }) = .init(.{ + .positional = .path, + .@"--zig" = .file, + .@"--lib" = .dir, + .@"--src" = .dir, + .@"--target" = .string, + .@"--libc-runtimes" = .dir, + }); + var input_dir: std.zig.Server.Message.InputDir = .cwd; + var args_body_offset: usize = 0; + while (args_body.len - args_body_offset > 0) { + const arg: std.zig.Client.Message.Arg = + @fromBackingInt(args_body[args_body_offset]); + args_body_offset += 1; + arg: switch (arg) { + .string => { + const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?; + const string = args_body[args_body_offset..end]; + args_body_offset = end + 1; + switch (state) { + .positional => if (std.mem.eql(u8, string, "--target")) { + state = .@"--target"; + continue; + } else if (std.mem.cutPrefix(u8, string, "--target=")) |target| { + try runner.args.addTarget(runner.gpa, target); + } else if (std.mem.eql(u8, string, "--libc-runtimes")) { + state = .@"--libc-runtimes"; + continue; + } else if (std.mem.eql(u8, string, "-fdarling")) { + runner.args.enable_darling = true; + } else if (std.mem.eql(u8, string, "-fqemu")) { + runner.args.enable_qemu = true; + } else if (std.mem.eql(u8, string, "-frosetta")) { + runner.args.enable_rosetta = true; + } else if (std.mem.eql(u8, string, "-fwasmtime")) { + runner.args.enable_wasmtime = true; + } else if (std.mem.eql(u8, string, "-fwine")) { + runner.args.enable_wine = true; + } else if (std.mem.eql(u8, string, "--quiet")) { + runner.args.quiet = true; + }, + .@"--target" => try runner.args.addTarget(runner.gpa, string), + else => return runner.fail( + "\"{t}\" expected {t}, got {t}", + .{ state, state_expected.get(state), arg }, + ), + } + }, + .prefix => { + const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?; + const string = args_body[args_body_offset..end]; + args_body_offset = end + 1; + assert(state == .positional); + state = if (std.mem.eql(u8, string, "--zig=")) + .@"--zig" + else if (std.mem.eql(u8, string, "--lib=")) + .@"--lib" + else if (std.mem.eql(u8, string, "--src=")) + .@"--src" + else if (std.mem.eql(u8, string, "--libc-runtimes=")) + .@"--libc-runtimes" + else + return runner.fail("unsupported arg prefix: {q}", .{string}); + continue; + }, + .suffix => { + const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?; + const string = args_body[args_body_offset..end]; + args_body_offset = end + 1; + return runner.fail("unsupported arg suffix: {q}", .{string}); + }, + .input_dir => { + input_dir = @fromBackingInt(@backingInt(input_dir) + 1); + continue :arg .output_dir; + }, + .output_dir => { + const dir_handle: *align(1) const std.Io.Dir.Handle = @ptrCast( + args_body[args_body_offset..][0..@sizeOf(std.Io.Dir.Handle)], + ); + args_body_offset += @sizeOf(std.Io.Dir.Handle); + const dir = runner.io.vtable.inheritParentDir( + runner.io.userdata, + dir_handle.*, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return runner.fail( + "unable to inherit parent dir: {t}", + .{e}, + ), + }; + switch (state) { + .positional => { + if (runner.args.@"test" != null or + runner.args.manifest_file != null) return runner.fail( + "{t} specified multiple times", + .{state}, + ); + runner.args.@"test" = .{ .input_dir = input_dir, .dir = dir }; + const manifest_path = "manifest"; + runner.args.manifest_file = dir.openFile( + runner.io, + manifest_path, + .{}, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return runner.fail( + "unable to open manifest file: {t}", + .{e}, + ), + }; + try runner.discoverInput(input_dir, manifest_path); + }, + .@"--lib" => { + if (runner.args.lib_dir != null) return runner.fail( + "\"{t}\" specified multiple times", + .{state}, + ); + runner.args.lib_dir = dir; + }, + .@"--src" => { + if (runner.args.src_dir != null) return runner.fail( + "\"{t}\" specified multiple times", + .{state}, + ); + runner.args.src_dir = dir; + }, + .@"--libc-runtimes" => { + if (runner.args.libc_runtimes_dir != null) return runner.fail( + "\"{t}\" specified multiple times", + .{state}, + ); + runner.args.libc_runtimes_dir = dir; + }, + else => return runner.fail( + "\"{t}\" expected {t}, got {t}", + .{ state, state_expected.get(state), arg }, + ), + } + }, + .input_file, .input_file_content, .output_file => { + const file_handle: *align(1) const std.Io.File.Handle = @ptrCast( + args_body[args_body_offset..][0..@sizeOf(std.Io.File.Handle)], + ); + args_body_offset += @sizeOf(std.Io.File.Handle); + const file = runner.io.vtable.inheritParentFile( + runner.io.userdata, + file_handle.*, + .{ .nonblocking = false }, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return runner.fail( + "unable to inherit parent file: {t}", + .{e}, + ), + }; + switch (state) { + .positional => { + if (runner.args.@"test" != null or + runner.args.manifest_file != null) return runner.fail( + "{t} specified multiple times", + .{state}, + ); + runner.args.manifest_file = file; + }, + .@"--zig" => { + if (runner.args.zig_exe != null) return runner.fail( + "\"{t}\" specified multiple times", + .{state}, + ); + runner.args.zig_exe = file; + }, + else => return runner.fail( + "\"{t}\" expected {t}, got {t}", + .{ state, state_expected.get(state), arg }, + ), + } + }, + } + state = .positional; + } + }, + .query_test_metadata => { + const expected_panic_msgs = try arena.alloc(u32, runner.args.targets.items.len); + @memset(expected_panic_msgs, 0); + runner.server.serveTestMetadata(.{ + .names = runner.args.targets.items, + .expected_panic_msgs = expected_panic_msgs, + .string_bytes = runner.args.target_bytes.items, + }) catch |err| switch (err) { + error.WriteFailed => return error.ServerWriteFailed, + }; + }, + .run_test => { + const test_index = runner.server.receiveBody_u32() catch unreachable; + runner.server.serveBodylessMessage(.test_started) catch |err| switch (err) { + error.WriteFailed => return error.ServerWriteFailed, + }; + runner.server.serveTestResults(.{ + .index = test_index, + .flags = .{ + .status = if (runner.testOne(test_index)) + .pass + else |err| status: switch (err) { + error.Canceled, + error.OutOfMemory, + error.ServerWriteFailed, + => |e| return e, + error.AlreadyReported => { + var eb = try runner.eb_wip.toOwnedBundle(""); + defer eb.deinit(runner.gpa); + assert(eb.errorMessageCount() > 0); // already reported what? + runner.server.serveErrorBundle( + .error_bundle, + eb, + ) catch |server_err| switch (server_err) { + error.WriteFailed => return error.ServerWriteFailed, + }; + const eb_wip: std.zig.ErrorBundle.Wip = try .init(runner.gpa); + runner.eb_wip.deinit(); + runner.eb_wip = eb_wip; + break :status .fail; + }, + error.SkipTest => .skip, + }, + .fuzz = false, + .log_err_count = 0, + .leak_count = 0, + }, + }) catch |err| switch (err) { + error.WriteFailed => return error.ServerWriteFailed, + }; + assert(runner.eb_wip.root_list.items.len == 0); // failed to report + }, + } + } +} + +fn discoverInput( + runner: *Runner, + dir: std.zig.Server.Message.InputDir, + sub_path: []const u8, +) error{ServerWriteFailed}!void { + return runner.discoverInputInner(dir, sub_path) catch |err| switch (err) { + error.WriteFailed => return error.ServerWriteFailed, + }; +} +fn discoverInputInner( + runner: *Runner, + dir: std.zig.Server.Message.InputDir, + sub_path: []const u8, +) std.Io.Writer.Error!void { + try runner.server.serveMessageHeader(.{ + .tag = .discovered_inputs, + .bytes_len = @intCast(@sizeOf(std.zig.Server.Message.InputDir) + + sub_path.len + 1), + }); + try runner.server.out.writeInt(u32, @backingInt(dir), .little); + try runner.server.out.writeAll(sub_path); + try runner.server.out.writeByte(0); + try runner.server.out.flush(); +} + +pub const SourceReader = struct { + const Io = std.Io; + + backing: *std.Io.Reader, + line_aw: std.Io.Writer.Allocating, + interface: std.Io.Reader, + line: u32, + eb_wip: *std.zig.ErrorBundle.Wip, + src_path: std.zig.ErrorBundle.String, + + pub fn init( + reader: *Io.Reader, + gpa: std.mem.Allocator, + eb_wip: *std.zig.ErrorBundle.Wip, + src_path: std.zig.ErrorBundle.String, + ) SourceReader { + return .{ + .backing = reader, + .line_aw = .init(gpa), + .interface = .{ + .vtable = &.{ + .stream = stream, + .discard = discard, + .readVec = readVec, + .rebase = rebase, + }, + .buffer = &.{}, + .seek = 0, + .end = 0, + }, + .line = 0, + .eb_wip = eb_wip, + .src_path = src_path, + }; + } + fn deinit(s: *SourceReader) void { + s.line_aw.deinit(); + } + fn stream(r: *Io.Reader, _: *Io.Writer, _: Io.Limit) Io.Reader.StreamError!usize { + const s: *SourceReader = @fieldParentPtr("interface", r); + try s.bufferLine(); + return 0; + } + fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { + const s: *SourceReader = @fieldParentPtr("interface", r); + const remaining = r.buffered(); + if (remaining.len > 0) { + const n = limit.minInt(remaining.len); + r.toss(n); + return n; + } + try s.bufferLine(); + return 0; + } + fn readVec(r: *Io.Reader, _: [][]u8) Io.Reader.Error!usize { + const s: *SourceReader = @fieldParentPtr("interface", r); + try s.bufferLine(); + return 0; + } + fn rebase(r: *Io.Reader, capacity: usize) Io.Reader.RebaseError!void { + const s: *SourceReader = @fieldParentPtr("interface", r); + defer s.updateBuffer(); + s.line_aw.ensureUnusedCapacity(capacity) catch |err| switch (err) { + error.OutOfMemory => return error.ReadFailed, + }; + } + fn updateBuffer(s: *SourceReader) void { + s.interface.buffer = s.line_aw.writer.buffer; + s.interface.end = s.line_aw.writer.end; + } + fn bufferLine(s: *SourceReader) Io.Reader.Error!void { + defer s.updateBuffer(); + { + const written = s.line_aw.written(); + const start = if (std.mem.findScalarLast(u8, written[0..s.interface.seek], '\n')) |newline| + newline + 1 + else + 0; + s.line += @intCast(std.mem.countScalar(u8, written[0..start], '\n')); + const preserve = written[start..]; + @memmove(written[0..preserve.len], preserve); + s.line_aw.shrinkRetainingCapacity(preserve.len); + s.interface.seek -= start; + } + const n = s.backing.streamDelimiterEnding(&s.line_aw.writer, '\n') catch |err| switch (err) { + error.ReadFailed => |e| return e, + error.WriteFailed => return error.ReadFailed, + }; + if (std.mem.endsWith(u8, s.line_aw.written(), "\r")) s.line_aw.writer.end -= 1; + const buffered = s.backing.buffered(); + if (n == 0 and buffered.len == 0) return error.EndOfStream; + if (std.mem.startsWith(u8, buffered, "\n")) { + s.line_aw.writer.writeByte('\n') catch |err| switch (err) { + error.WriteFailed => return error.ReadFailed, + }; + s.backing.toss(1); + } + } + fn fail( + s: *SourceReader, + src_loc: []const u8, + comptime fmt: []const u8, + args: anytype, + ) FailError { + return s.failString(src_loc, try s.eb_wip.printString(fmt, args)); + } + fn failString( + s: *SourceReader, + src_loc: []const u8, + msg: std.zig.ErrorBundle.String, + ) FailError { + const line: u32, const column: u32, const source_line = src_loc: { + const pos = src_loc.ptr - s.interface.buffer.ptr; + const end = std.mem.findScalarPos(u8, s.interface.buffer, pos, '\n') orelse + s.interface.buffer.len; + const start = if (std.mem.findScalarLast(u8, s.interface.buffer[0..end], '\n')) |newline| + newline + 1 + else + 0; + break :src_loc .{ + @intCast(s.line + std.mem.countScalar(u8, s.interface.buffer[0..start], '\n')), + @intCast(pos - start), + s.interface.buffer[start..end], + }; + }; + try s.eb_wip.addRootErrorMessage(.{ + .msg = msg, + .src_loc = try s.eb_wip.addSourceLocation(.{ + .src_path = s.src_path, + .line = line, + .column = column, + .span_start = column, + .span_main = column, + .span_end = @intCast(column + src_loc.len), + .source_line = try s.eb_wip.addString(source_line), + }), + }); + return error.AlreadyReported; + } +}; +pub const DelimitedReader = struct { + const Io = std.Io; + + unlimited: *Io.Reader, + remaining: Io.Limit, + delimiter: []const u8, + interface: Io.Reader, + + pub fn init(reader: *Io.Reader, delimiter: []const u8, buffer: []u8) DelimitedReader { + return .{ + .unlimited = reader, + .remaining = .nothing, + .delimiter = delimiter, + .interface = .{ + .vtable = &.{ + .stream = stream, + .discard = discard, + }, + .buffer = buffer, + .seek = 0, + .end = 0, + }, + }; + } + fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { + const d: *DelimitedReader = @fieldParentPtr("interface", r); + const block = try d.unlimited.peekGreedy(d.delimiter.len); + d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse + block.len - d.delimiter.len + 1); + if (d.remaining == .nothing) return error.EndOfStream; + const n = try d.unlimited.stream(w, limit.min(d.remaining)); + d.remaining = d.remaining.subtract(n).?; + return n; + } + fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { + const d: *DelimitedReader = @fieldParentPtr("interface", r); + const block = try d.unlimited.peekGreedy(d.delimiter.len); + d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse + block.len - d.delimiter.len + 1); + if (d.remaining == .nothing) return error.EndOfStream; + const n = try d.unlimited.discard(limit.min(d.remaining)); + d.remaining = d.remaining.subtract(n).?; + return n; + } +}; + +const Update = struct { + target_query: []const u8, + manifest_sr: SourceReader, + compiler: ?Compiler, + src_dir: std.Io.Dir, + prog_node: std.Progress.Node, + mtime: std.Io.Timestamp, + num: usize, + allow_skip: bool, +}; + +const TestError = error{ + SkipTest, +} || FailError; +fn testOne(runner: *Runner, test_index: u32) (error{ServerWriteFailed} || TestError)!void { + const target_query = + std.mem.sliceTo(runner.args.target_bytes.items[runner.args.targets.items[test_index]..], 0); + const manifest_file = runner.args.manifest_file orelse + return runner.fail("missing \"path/to/test\" arg", .{}); + var manifest_buffer: [512]u8 = undefined; + var manifest_fr = manifest_file.reader(runner.io, &manifest_buffer); + var update: Update = .{ + .target_query = target_query, + .manifest_sr = .init(&manifest_fr.interface, runner.gpa, &runner.eb_wip, src_path_string: { + var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; + const src_path = src_path: { + break :src_path path_buffer[0 .. manifest_file.realPath( + runner.io, + &path_buffer, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => break :src_path "manifest", + }]; + }; + break :src_path_string try runner.eb_wip.addString(src_path); + }), + .compiler = null, + .src_dir = (runner.args.src_dir orelse + return runner.fail("missing \"--src=path/to/src\" arg", .{})).createDirPathOpen( + runner.io, + target_query, + .{}, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return runner.fail("unable to create {q}: {t}", .{ target_query, e }), + }, + .prog_node = .none, + .mtime = .now(runner.io, .real), + .num = 0, + .allow_skip = true, + }; + defer { + update.manifest_sr.deinit(); + update.src_dir.close(runner.io); + update.prog_node.end(); + } + + // hack around `std.Io.Reader` interface bugs by ensuring the + // buffer is larger than the longest supported manifest line + try update.manifest_sr.line_aw.ensureTotalCapacityPrecise(512); + update.manifest_sr.updateBuffer(); + + var skip_delimiter = false; + while (update.manifest_sr.interface.takeSentinel('\n')) |line| { + var line_it = std.mem.tokenizeScalar(u8, line, ' '); + const cmd_src_loc = line_it.next() orelse continue; + if (skip_delimiter) { + assert(std.mem.eql(u8, cmd_src_loc, "#}")); + skip_delimiter = false; + const unexpected = line_it.rest(); + if (unexpected.len > 0) + return update.manifest_sr.fail(unexpected, "unexpected argument {q}", .{unexpected}); + continue; + } + const cmd = std.meta.stringToEnum(Command, std.mem.cutPrefix(u8, cmd_src_loc, "#") orelse + return update.manifest_sr.fail(cmd_src_loc, "expected command", .{})) orelse + return update.manifest_sr.fail(cmd_src_loc, "unknown command", .{}); + const arg = switch (cmd) { + .todo, .exe, .lib, .obj, .update => null, + .skip, .write, .delete, .check => line_it.next() orelse + return update.manifest_sr.fail(line_it.rest()[0..0], "missing argument", .{}), + }; + var contents_file: ?std.Io.File = null; + defer if (contents_file) |file| file.close(runner.io); + var contents_buffer: [512]u8 = undefined; + var contents_impl: union(enum) { + ending: std.Io.Reader, + dr: DelimitedReader, + fr: std.Io.File.Reader, + } = undefined; + const contents_r: *std.Io.Reader = contents_r: switch (cmd) { + .skip, .delete => { + const unexpected = line_it.rest(); + if (unexpected.len > 0) return update.manifest_sr.fail( + unexpected, + "unexpected argument {q}", + .{unexpected}, + ); + contents_impl = .{ .ending = .ending_instance }; + break :contents_r &contents_impl.ending; + }, + else => { + const contents_path = line_it.rest(); + if (contents_path.len == 0) { + contents_impl = .{ .ending = .ending_instance }; + break :contents_r &contents_impl.ending; + } + + if (std.mem.eql(u8, contents_path, "{")) { + contents_impl = .{ + .dr = .init(&update.manifest_sr.interface, "#}", &contents_buffer), + }; + skip_delimiter = true; + break :contents_r &contents_impl.dr.interface; + } + + const args_test = runner.args.@"test" orelse return update.manifest_sr.fail( + contents_path, + "contents file requires directory test", + .{}, + ); + try runner.discoverInput(args_test.input_dir, contents_path); + + contents_file = + args_test.dir.openFile(runner.io, contents_path, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.manifest_sr.fail( + contents_path, + "unable to open contents file: {t}", + .{e}, + ), + }; + contents_impl = .{ .fr = contents_file.?.reader(runner.io, &contents_buffer) }; + break :contents_r &contents_impl.fr.interface; + }, + }; + + runner.handleCommand(&update, cmd_src_loc, cmd, arg, contents_r) catch |err| switch (err) { + error.Canceled, + error.OutOfMemory, + error.AlreadyReported, + error.SkipTest, + => |e| return e, + error.ContentsReadFailed => switch (contents_impl) { + .ending => unreachable, + .dr => switch (manifest_fr.err orelse return error.OutOfMemory) { + error.Canceled => |e| return e, + else => |e| return runner.fail("unable to read manifest: {t}", .{e}), + }, + .fr => |fr| switch (fr.err.?) { + error.Canceled => |e| return e, + else => |e| return runner.fail("unable to read contents file: {t}", .{e}), + }, + }, + }; + } else |err| switch (err) { + error.ReadFailed => switch (manifest_fr.err orelse return error.OutOfMemory) { + error.Canceled => |e| return e, + else => |e| return runner.fail("unable to read manifest: {t}", .{e}), + }, + error.StreamTooLong => unreachable, + error.EndOfStream => if (skip_delimiter) + return runner.fail("manifest missing \"#}}\" delimiter", .{}), + } + if (update.compiler) |*comp| { + if (comp.state != .idle) return runner.fail("missing \"#check\"", .{}); + try comp.exit(); + } +} +const Command = enum { todo, skip, exe, lib, obj, write, delete, update, check }; +fn handleCommand( + runner: *Runner, + update: *Update, + cmd_src_loc: []const u8, + cmd: Command, + arg: ?[]const u8, + contents_r: *std.Io.Reader, +) (error{ContentsReadFailed} || TestError)!void { + var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined; + const cmd_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "#{t}{s}{s}", .{ + cmd, if (arg) |_| " " else "", arg orelse "", + }) catch &name_buffer, 0); + defer cmd_prog_node.end(); + + cmd: switch (cmd) { + .todo => _ = contents_r.discardRemaining() catch |err| switch (err) { + error.ReadFailed => {}, + }, + .skip => if (!update.allow_skip) return update.manifest_sr.fail( + cmd_src_loc, + "\"#skip\" must appear before other commands", + .{}, + ) else if (std.mem.eql(u8, update.target_query, arg.?)) return error.SkipTest, + .exe, .lib, .obj => runner.spawnCompiler(&update.compiler, update.src_dir, switch (cmd) { + else => unreachable, + .exe => .Exe, + .lib => .Lib, + .obj => .Obj, + }, contents_r, target: { + const backend_split = std.mem.findScalarLast(u8, update.target_query, '-') orelse + return runner.fail("target {q} missing query", .{update.target_query}); + const mode_split = + std.mem.findScalarLast(u8, update.target_query[0..backend_split], '-') orelse + return runner.fail("target {q} missing mode", .{update.target_query}); + const triple = update.target_query[0..mode_split]; + const backend_str = update.target_query[backend_split + 1 ..]; + const backend = std.meta.stringToEnum(Compiler.Target.Backend, backend_str) orelse + return runner.fail("target {q} unknown backend {q}", .{ + update.target_query, backend_str, + }); + const mode_str = update.target_query[mode_split + 1 .. backend_split]; + const mode = std.meta.stringToEnum(Compiler.Target.Mode, mode_str) orelse + return runner.fail("target {q} unknown mode {q}", .{ update.target_query, mode_str }); + break :target .{ + .triple = triple, + .resolved = std.zig.system.resolveTargetQuery( + runner.io, + std.Build.parseTargetQuery(.{ + .arch_os_abi = triple, + .object_format = switch (backend) { + .sema, .selfhosted, .llvm => null, + .cbe => "c", + }, + }) catch |err| switch (err) { + error.ParseFailed => return runner.fail("unable to parse triple {q}", .{ + triple, + }), + }, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return runner.fail("unable to resolve target {q}: {t}", .{ triple, e }), + }, + .mode = mode, + .backend = backend, + }; + }) catch |err| switch (err) { + error.Canceled, error.OutOfMemory, error.AlreadyReported => |e| return e, + error.ReadFailed => return error.ContentsReadFailed, + }, + .write => { + const file = update.src_dir.createFile(runner.io, arg.?, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.manifest_sr.fail(arg.?, "unable to create file: {t}", .{e}), + }; + defer file.close(runner.io); + var fw_buffer: [512]u8 = undefined; + var fw = file.writer(runner.io, &fw_buffer); + _ = contents_r.streamRemaining(&fw.interface) catch |err| switch (err) { + error.ReadFailed => return error.ContentsReadFailed, + error.WriteFailed => switch (fw.err.?) { + error.Canceled => |e| return e, + else => |e| return update.manifest_sr.fail( + arg.?, + "unable to write file: {t}", + .{e}, + ), + }, + }; + fw.flush() catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.manifest_sr.fail( + arg.?, + "unable to write file: {t}", + .{e}, + ), + }; + file.setTimestamps(runner.io, .{ .modify_timestamp = .{ + .new = update.mtime, + } }) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.manifest_sr.fail( + arg.?, + "unable to update file timestamps: {t}", + .{e}, + ), + }; + }, + .delete => update.src_dir.deleteFile(runner.io, arg.?) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.manifest_sr.fail(arg.?, "unable to delete file: {t}", .{e}), + }, + .update => { + const comp = &(update.compiler orelse return update.manifest_sr.fail( + cmd_src_loc, + "missing \"#exe\", \"#lib\", or \"#obj\"", + .{}, + )); + update.prog_node.end(); + update.prog_node = runner.prog_node.start( + switch (contents_r.readSliceShort(&name_buffer) catch |err| switch (err) { + error.ReadFailed => return error.ContentsReadFailed, + }) { + 0 => switch (update.num) { + 0 => "initial update", + else => |num| std.mem.print(&name_buffer, "update {d}", .{num}) catch + &name_buffer, + }, + else => |name_len| name_buffer[0..name_len], + }, + 0, + ); + _ = contents_r.discardRemaining() catch |err| switch (err) { + error.ReadFailed => return error.ContentsReadFailed, + }; + if (comp.state != .idle) + return update.manifest_sr.fail(cmd_src_loc, "missing \"#check\"", .{}); + comp.client.serveBodylessMessage(.update) catch |err| switch (err) { + error.WriteFailed => switch (comp.fw.err.?) { + error.Canceled => |e| return e, + else => |e| return runner.fail("unable to send message to compiler: {t}", .{e}), + }, + }; + comp.state = .update; + update.mtime = update.mtime.addDuration(.fromSeconds(2)); + update.num += 1; + }, + .check => { + const check = std.meta.stringToEnum(Compiler.Check, arg.?) orelse + return update.manifest_sr.fail(arg.?, "unknown check", .{}); + const expected = + contents_r.allocRemaining(runner.gpa, .unlimited) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.ReadFailed => return error.ContentsReadFailed, + error.StreamTooLong => unreachable, // .unlimited + }; + defer runner.gpa.free(expected); + + const comp = &(update.compiler orelse return update.manifest_sr.fail( + cmd_src_loc, + "missing \"#exe\", \"#lib\", or \"#obj\"", + .{}, + )); + if (comp.state != .update) + return update.manifest_sr.fail(cmd_src_loc, "missing \"#update\"", .{}); + const stderr = comp.mr.reader(1); + while (try comp.receiveMessage()) |message| switch (message.tag) { + else => {}, // Ignore other messages, + .config => { + var body_r: std.Io.Reader = .fixed(message.body); + comp.config = body_r.takeStruct(std.zig.Server.Message.Config, .little) catch + unreachable; + }, + .emit_digest => { + var body_r: std.Io.Reader = .fixed(message.body); + _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch + unreachable; + const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch + unreachable; + try comp.checkSuccess(check, expected, update.src_dir, digest); + }, + .error_bundle => { + const eb = std.zig.Server.allocErrorBundle( + runner.arena.allocator(), + message.body, + ) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.EndOfStream => return runner.fail("error bundle truncated", .{}), + }; + switch (check) { + .errors => { + var error_aw: std.Io.Writer.Allocating = .init(runner.gpa); + defer error_aw.deinit(); + eb.renderToWriter(.{ + .include_reference_trace = false, + .include_source_line = false, + .include_log_text = true, + }, &error_aw.writer) catch |err| switch (err) { + error.WriteFailed => return error.OutOfMemory, + }; + const actual = error_aw.written(); + std.mem.replaceScalar(u8, actual, '\\', '/'); + std.testing.expectEqualStrings(expected, actual) catch |err| switch (err) { + error.TestExpectedEqual => return runner.fail( + "errors did not match expected", + .{}, + ), + }; + }, + .stdout, .exit, .lldb => if (eb.errorMessageCount() > 0) { + eb.renderToStderr(runner.io, .{}, .auto) catch |err| switch (err) { + error.Canceled => {}, + else => {}, + }; + return runner.fail("unexpected compile errors", .{}); + }, + } + comp.state = .idle; + break :cmd; + }, + }; + + const buffered_stderr = stderr.buffered(); + if (buffered_stderr.len > 0) { + if (comp.allow_compiler_stderr) { + std.log.info("stderr:\n{s}", .{buffered_stderr}); + } else { + return runner.fail("unexpected stderr:\n{s}", .{buffered_stderr}); + } + } + + try comp.exit(); + return runner.fail("compiler failed to send terminating error_bundle", .{}); + }, + } + assert(contents_r.discardRemaining() catch |err| switch (err) { + error.ReadFailed => return error.ContentsReadFailed, + } == 0); + switch (cmd) { + .todo, .skip => {}, + else => update.allow_skip = false, + } +} + +fn spawnCompiler( + runner: *Runner, + compiler: *?Compiler, + src_dir: std.Io.Dir, + output_mode: std.lang.OutputMode, + args_r: *std.Io.Reader, + target: Compiler.Target, +) (std.Io.Reader.ShortError || FailError)!void { + const gpa = runner.gpa; + const arena = runner.arena.allocator(); + if (compiler.*) |*comp| { + try comp.exit(); + compiler.* = null; + } + + var argv: std.ArrayList([]const u8) = .empty; + defer argv.deinit(gpa); + try argv.appendSlice(gpa, &.{ + "zig", + switch (output_mode) { + .Obj => "build-obj", + .Exe => "build-exe", + .Lib => "build-lib", + }, + "-target", + target.triple, + "--cache-dir", + ".zig-cache", + }); + switch (target.mode) { + .whole => {}, + .incremental => try argv.append(gpa, "-fincremental"), + } + var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; + if (target.backend) |b| switch (b) { + .sema => try argv.append(gpa, "-fno-emit-bin"), + .selfhosted => try argv.append(gpa, "-fno-llvm"), + .llvm => try argv.append(gpa, "-fllvm"), + .cbe => try argv.append(gpa, "-ofmt=c"), + } else try argv.appendSlice(gpa, &.{ "-I", path_buffer[0 .. (runner.args.lib_dir orelse + return runner.fail("\"--lib=path/to/lib\" arg required with cbe", .{})) + .realPath(runner.io, &path_buffer) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return runner.fail("unable to get lib path: {t}", .{e}), + }] }); + var need: usize = 1; + var root_name: ?[]const u8 = null; + while (true) { + const done = if (args_r.fill(need)) false else |err| switch (err) { + error.ReadFailed => |e| return e, + error.EndOfStream => true, + }; + const buffered = args_r.buffered(); + const start = std.mem.findNone(u8, buffered, "\n ") orelse buffered.len; + const end = std.mem.findAnyPos(u8, buffered, start, "\n ") orelse buffered.len; + if (end - start > 0) { + const arg = try arena.dupe(u8, buffered[start..end]); + if (std.mem.cutPrefix(u8, arg, "-M")) |module| { + var module_name_it = std.mem.splitScalar(u8, module, '='); + const module_name = module_name_it.next().?; + root_name = root_name orelse module_name; + } + try argv.append(gpa, arg); + args_r.toss(end); + need = 1; + } else { + args_r.toss(start); + need = buffered.len - start + 1; + } + if (done) break; + } + try argv.append(gpa, "--listen=-"); + + const out_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{ + .root_name = root_name orelse "root", + .cpu_arch = target.resolved.cpu.arch, + .os_tag = target.resolved.os.tag, + .ofmt = target.resolved.ofmt, + .abi = target.resolved.abi, + .output_mode = output_mode, + }); + + var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined; + const comp_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "compile {s}", .{ + out_name, + }) catch &name_buffer, 0); + errdefer comp_prog_node.end(); + + const child = std.process.spawn(runner.io, .{ + .exe = .{ .file = runner.args.zig_exe orelse + return runner.fail("missing \"--zig=path/to/zig\" arg", .{}) }, + .argv = argv.items, + .cwd = .{ .dir = src_dir }, + .progress_node = comp_prog_node, + .stdin = .pipe, + .stdout = .pipe, + .stderr = .pipe, + }) catch |err| switch (err) { + error.Canceled, error.OutOfMemory => |e| return e, + else => |e| return runner.fail("unable to spawn compiler: {t}", .{e}), + }; + compiler.* = .{ + .runner = runner, + .prog_node = comp_prog_node, + .allow_compiler_stderr = true, + .output_mode = output_mode, + .state = .idle, + .child = child, + .fw = undefined, + .mr_buffer = undefined, + .mr = undefined, + .client = undefined, + .target = target, + .out_name = out_name, + .config = null, + }; + const comp = &compiler.*.?; + comp.fw = child.stdin.?.writerStreaming(runner.io, &.{}); + comp.mr.init(gpa, runner.io, comp.mr_buffer.toStreams(), &.{ + child.stdout.?, child.stderr.?, + }); + comp.client = .{ .in = comp.mr.reader(0), .out = &comp.fw.interface }; +} +const Compiler = struct { + runner: *Runner, + prog_node: std.Progress.Node, + allow_compiler_stderr: bool, + output_mode: std.lang.OutputMode, + state: enum { idle, update }, + child: std.process.Child, + fw: std.Io.File.Writer, + mr_buffer: std.Io.File.MultiReader.Buffer(2), + mr: std.Io.File.MultiReader, + client: std.zig.Client, + target: Target, + out_name: []const u8, + config: ?std.zig.Server.Message.Config, + + const Target = struct { + triple: []const u8, + resolved: std.Target, + mode: Mode, + backend: ?Backend, + + const Mode = enum { + whole, + incremental, + }; + + const Backend = enum { + /// Run semantic analysis only. Runtime output will not be tested, but we still verify + /// that compilation succeeds. Corresponds to `-fno-emit-bin`. + sema, + /// Use the self-hosted code generation backend for this target. + /// Corresponds to `-fno-llvm -fno-lld`. + selfhosted, + /// Use the LLVM backend. + /// Corresponds to `-fllvm -flld`. + llvm, + /// Use the C backend. The output is compiled with `zig cc`. + /// Corresponds to `-ofmt=c`. + cbe, + }; + }; + + fn receiveMessage(comp: *Compiler) FailError!?struct { + tag: std.zig.Server.Message.Tag, + body: []const u8, + } { + const header = comp.client.receiveMessageWithMultiReader( + &comp.mr, + .none, + ) catch |err| switch (err) { + error.Canceled, error.OutOfMemory => |e| return e, + error.Timeout => unreachable, + error.EndOfStream => return null, + else => |e| return comp.runner.fail("unable to receive message: {t}", .{e}), + }; + const body = comp.client.in.take(header.bytes_len) catch unreachable; + const stderr = comp.mr.reader(1); + if (stderr.bufferedLen() > 0) { + if (comp.allow_compiler_stderr) { + std.log.info("{t} stderr:\n{s}", .{ header.tag, stderr.buffered() }); + } else { + return comp.runner.fail("{t} unexpected stderr:\n{s}", .{ + header.tag, stderr.buffered(), + }); + } + stderr.tossBuffered(); + } + return .{ .tag = header.tag, .body = body }; + } + + const Check = enum { errors, stdout, exit, lldb }; + fn checkSuccess( + comp: *Compiler, + check: Check, + expected: []const u8, + src_dir: std.Io.Dir, + digest: *const std.Build.Cache.BinDigest, + ) FailError!void { + const runner = comp.runner; + const gpa = runner.gpa; + const arena = runner.arena.allocator(); + const io = runner.io; + const target = &comp.target.resolved; + const config = &comp.config.?; + const out_dir = ".zig-cache" ++ std.Io.Dir.path.sep_str ++ + "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*); + const out_path = try std.Io.Dir.path.join(arena, &.{ out_dir, comp.out_name }); + const bin_path = switch (comp.target.backend.?) { + .sema => return, + .selfhosted, .llvm => out_path, + .cbe => try comp.compileC(src_dir, out_path), + }; + switch (check) { + .errors => return, + .stdout, .exit, .lldb => {}, + } + + var argv: std.ArrayList([]const u8) = .initBuffer(try arena.alloc([]const u8, 4)); + var environ_map: std.process.Environ.Map = .init(gpa); + defer environ_map.deinit(); + const need_cross_libc = + target.os.tag == .linux and config.flags.link_libc and config.flags.link_mode == .dynamic; + const use_executor = use_executor: switch (std.zig.system.getExternalExecutor(io, target, .{ + .host_cpu_arch = runner.host.cpu.arch, + .host_os_tag = runner.host.os.tag, + .qemu_fixes_dl = need_cross_libc and runner.args.libc_runtimes_dir != null, + .link_mode = config.flags.link_mode, + .link_libc = config.flags.link_libc, + })) { + .bad_dl, .bad_os_or_cpu => { + // This binary cannot be executed on this host. + if (!runner.args.quiet) std.log.warn("skipping execution because host {q} cannot " ++ + "execute binaries for foreign target {q}", .{ + try runner.host.zigTriple(arena), comp.target.triple, + }); + return; + }, + .native => false, + .darling => |executor| if (runner.args.enable_darling) { + argv.appendAssumeCapacity(executor); + break :use_executor true; + } else continue :use_executor .bad_os_or_cpu, + .qemu => |executor| if (runner.args.enable_qemu) { + argv.appendAssumeCapacity(executor); + if (need_cross_libc) { + const libc_runtimes_dir = runner.args.libc_runtimes_dir orelse + continue :use_executor .bad_os_or_cpu; + var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; + const libc_runtimes_path = path_buffer[0 .. libc_runtimes_dir.realPath( + runner.io, + &path_buffer, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return runner.fail("unable to get libc runtimes path: {t}", .{e}), + }]; + argv.appendSliceAssumeCapacity(&.{ "-L", try std.Io.Dir.path.join(arena, &.{ + libc_runtimes_path, + try if (target.isGnuLibC()) std.zig.target.glibcRuntimeTriple( + arena, + target.cpu.arch, + target.os.tag, + target.abi, + ) else if (target.isMuslLibC()) std.zig.target.muslRuntimeTriple( + arena, + target.cpu.arch, + target.abi, + ) else unreachable, + }) }); + } + break :use_executor true; + } else continue :use_executor .bad_os_or_cpu, + .rosetta => if (runner.args.enable_rosetta) + true + else + continue :use_executor .bad_os_or_cpu, + .wine => |executor| if (runner.args.enable_wine) { + if (!environ_map.contains("WINEDEBUG")) try environ_map.put("WINEDEBUG", "-all"); + argv.appendAssumeCapacity(executor); + break :use_executor true; + } else continue :use_executor .bad_os_or_cpu, + .wasmtime => |executor| if (runner.args.enable_wasmtime) { + if (!environ_map.contains("WASMTIME_BACKTRACE_DETAILS")) + try environ_map.put("WASMTIME_BACKTRACE_DETAILS", "1"); + argv.appendSliceAssumeCapacity(&.{ executor, "--dir=.", "-Sinherit-env" }); + break :use_executor true; + } else continue :use_executor .bad_os_or_cpu, + }; + argv.appendAssumeCapacity(bin_path); + + var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined; + const run_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "run {s}", .{ + comp.out_name, + }) catch &name_buffer, 0); + defer run_prog_node.end(); + + const bin_file = src_dir.openFile(io, bin_path, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return runner.fail("unable to open {q}: {t}", .{ bin_path, e }), + }; + defer bin_file.close(io); + switch (check) { + .errors => unreachable, + .stdout, .exit => { + const result = std.process.run(gpa, io, .{ + .exe = if (use_executor) .search else .{ .file = bin_file }, + .argv = argv.items, + .cwd = .{ .dir = src_dir }, + .environ_map = &environ_map, + .progress_node = run_prog_node, + }) catch |err| if (use_executor) { + // Chances are the foreign executor isn't available. Skip this evaluation. + if (!runner.args.quiet) std.log.warn( + "skipping execution of {q} via executor for foreign target {q}: {t}", + .{ bin_path, comp.target.triple, err }, + ); + return; + } else return runner.fail("unable to run the generated executable {q}: {t}", .{ + bin_path, err, + }); + defer { + gpa.free(result.stdout); + gpa.free(result.stderr); + } + switch (result.term) { + .exited => |code| switch (check) { + .errors, .lldb => unreachable, + .stdout => { + if (code != 0) return runner.fail( + "generated executable {q} failed with code {d}", + .{ bin_path, code }, + ); + std.testing.expectEqualStrings( + expected, + result.stdout, + ) catch |err| switch (err) { + error.TestExpectedEqual => return runner.fail( + "stdout did not match expected", + .{}, + ), + }; + }, + .exit => { + var actual_code_buffer: [std.fmt.count("{d}", .{std.math.maxInt(u8)})]u8 = + undefined; + std.testing.expectEqualStrings( + expected, + std.mem.print(&actual_code_buffer, "{d}", .{code}) catch unreachable, + ) catch |err| switch (err) { + error.TestExpectedEqual => return runner.fail( + "exit code did not match expected", + .{}, + ), + }; + }, + }, + .signal => |sig| return runner.fail( + "generated executable {q} terminated with signal {t}", + .{ bin_path, sig }, + ), + .stopped => |sig| return runner.fail( + "generated executable {q} stopped with signal {t}", + .{ bin_path, sig }, + ), + .unknown => return runner.fail( + "generated executable {q} terminated unexpectedly", + .{bin_path}, + ), + } + if (!use_executor and result.stderr.len > 0) { + std.log.err("generated executable {q} had unexpected stderr:\n{s}", .{ + bin_path, result.stderr, + }); + } + }, + .lldb => {}, + } + } + + fn compileC(parent_comp: *Compiler, src_dir: std.Io.Dir, c_path: []const u8) FailError![]const u8 { + const runner = parent_comp.runner; + const arena = runner.arena.allocator(); + var compiler: ?Compiler = null; + var args_r: std.Io.Reader = .fixed(c_path); + parent_comp.runner.spawnCompiler(&compiler, src_dir, parent_comp.output_mode, &args_r, .{ + .triple = parent_comp.target.triple, + .resolved = resolved: { + var resolved = parent_comp.target.resolved; + resolved.ofmt = .default(resolved.os.tag, resolved.cpu.arch); + break :resolved resolved; + }, + .mode = .whole, + .backend = null, + }) catch |err| switch (err) { + error.Canceled, error.OutOfMemory, error.AlreadyReported => |e| return e, + error.ReadFailed => unreachable, // .fixed + }; + const comp = &compiler.?; + comp.client.serveBodylessMessage(.update) catch |err| switch (err) { + error.WriteFailed => switch (comp.fw.err.?) { + error.Canceled => |e| return e, + else => |e| return runner.fail("unable to send message: {t}", .{e}), + }, + }; + comp.state = .update; + var out_path: ?[]const u8 = null; + while (try comp.receiveMessage()) |message| switch (message.tag) { + else => {}, // Ignore other messages, + .config => { + var body_r: std.Io.Reader = .fixed(message.body); + comp.config = + body_r.takeStruct(std.zig.Server.Message.Config, .little) catch unreachable; + }, + .emit_digest => { + var body_r: std.Io.Reader = .fixed(message.body); + _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable; + const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch unreachable; + + const out_dir = ".zig-cache" ++ std.Io.Dir.path.sep_str ++ + "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*); + out_path = try std.Io.Dir.path.join(arena, &.{ out_dir, comp.out_name }); + }, + .error_bundle => { + const eb = std.zig.Server.allocErrorBundle( + runner.arena.allocator(), + message.body, + ) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.EndOfStream => return runner.fail("error bundle truncated", .{}), + }; + if (eb.errorMessageCount() > 0) { + try runner.eb_wip.addBundleAsRoots(eb); + return runner.fail("unexpected compile errors", .{}); + } + comp.state = .idle; + break; + }, + }; + try comp.exit(); + return out_path.?; + } + + fn exit(comp: *Compiler) FailError!void { + const runner = comp.runner; + comp.client.serveBodylessMessage(.exit) catch |err| switch (err) { + error.WriteFailed => switch (comp.fw.err.?) { + error.Canceled => |e| return e, + error.BrokenPipe => {}, + else => |e| return comp.runner.fail("unable to send exit: {t}", .{e}), + }, + }; + comp.child.stdin.?.close(runner.io); + comp.child.stdin = null; + while (try comp.receiveMessage()) |_| {} + if (comp.client.in.bufferedLen() > 0) return runner.fail("unable to receive messages", .{}); + const term = comp.child.wait(runner.io) catch |err| + return runner.fail("child process failed: {t}", .{err}); + comp.prog_node.end(); + switch (term) { + .exited => |code| if (code != 0) + return runner.fail("compiler failed with code {d}", .{code}), + .signal => |sig| return runner.fail("compiler terminated with signal {t}", .{sig}), + .stopped => |sig| return runner.fail("compiler stopped unexpectedly with signal {t}", .{ + sig, + }), + .unknown => return runner.fail("compiler terminated unexpectedly", .{}), + } + } +}; + +const ClientArgs = struct { + test_file: std.Io.File, + test_kind: std.Io.File.Kind, + zig_exe: std.Io.File, + lib_dir: std.Io.Dir, + src_dir: std.Io.Dir, + targets: std.ArrayList([]const u8), + keep_src: bool, + libc_runtimes_dir: ?std.Io.Dir, + enable_darling: bool, + enable_qemu: bool, + enable_rosetta: bool, + enable_wine: bool, + enable_wasmtime: bool, + quiet: bool, +}; +pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelable)!u8 { + const fatal = struct { + fn fatal(comptime fmt: []const u8, args: anytype) noreturn { + std.log.err(fmt, args); + std.process.exit(1); + } + }.fatal; + const arena = init.arena.allocator(); + + var test_path_arg: ?[]const u8 = null; + var zig_path_arg: ?[]const u8 = null; + var lib_path_arg: ?[]const u8 = null; + var libc_runtimes_path_arg: ?[]const u8 = null; + var args: ClientArgs = .{ + .test_file = undefined, + .test_kind = undefined, + .zig_exe = undefined, + .lib_dir = undefined, + .src_dir = undefined, + .targets = .empty, + .keep_src = false, + .libc_runtimes_dir = null, + .enable_darling = false, + .enable_qemu = false, + .enable_rosetta = false, + .enable_wine = false, + .enable_wasmtime = false, + .quiet = false, + }; + defer args.targets.deinit(init.gpa); + + var arg_it = try init.minimal.args.iterateAllocator(arena); + const self = arg_it.next().?; + while (arg_it.next()) |arg| { + if (std.mem.eql(u8, arg, "--listen=-")) { + var stdin_buffer: [512]u8 = undefined; + var stdin: std.Io.File.Reader = .initStreaming(.stdin(), init.io, &stdin_buffer); + var stdout_buffer: [512]u8 = undefined; + var stdout: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer); + var runner: Runner = .{ + .gpa = init.gpa, + .arena = init.arena, + .io = init.io, + .prog_node = std.Progress.start(init.io, .{}), + .args = .{ + .@"test" = null, + .manifest_file = null, + .zig_exe = null, + .lib_dir = null, + .src_dir = null, + .targets = .empty, + .target_bytes = .empty, + .libc_runtimes_dir = null, + .enable_darling = false, + .enable_qemu = false, + .enable_rosetta = false, + .enable_wasmtime = false, + .enable_wine = false, + .quiet = false, + }, + .host = std.zig.system.resolveTargetQuery(init.io, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to resolve host target: {t}", .{e}), + }, + .server = .{ + .in = &stdin.interface, + .out = &stdout.interface, + }, + .eb_wip = try .init(init.gpa), + }; + defer runner.deinit(); + switch (runner.runServer()) { + error.Canceled, error.OutOfMemory => |e| return e, + error.ServerReadFailed => switch (stdin.err.?) { + error.Canceled => |e| return e, + else => |e| fatal("unable to receive message: {t}", .{e}), + }, + error.ServerWriteFailed => switch (stdout.err.?) { + error.Canceled => |e| return e, + else => |e| fatal("unable to send message: {t}", .{e}), + }, + error.ServerEndOfStream => fatal("no more messages", .{}), + error.AlreadyReported => { + var eb = try runner.eb_wip.toOwnedBundle(""); + defer eb.deinit(init.gpa); + eb.renderToStderr(runner.io, .{}, .auto) catch |err| switch (err) { + error.Canceled => |e| return e, + else => {}, + }; + std.process.exit(1); + }, + } + } else if (std.mem.eql(u8, arg, "--zig")) { + zig_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg}); + } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| { + zig_path_arg = zig_path; + } else if (std.mem.eql(u8, arg, "--lib")) { + lib_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg}); + } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| { + lib_path_arg = lib_path; + } else if (std.mem.eql(u8, arg, "--target")) { + try args.targets.append( + init.gpa, + try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after {q}", .{arg})), + ); + } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| { + try args.targets.append(init.gpa, try arena.dupe(u8, target)); + } else if (std.mem.eql(u8, arg, "--keep-src")) { + args.keep_src = true; + } else if (std.mem.eql(u8, arg, "--libc-runtimes")) { + libc_runtimes_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg}); + } else if (std.mem.cutPrefix(u8, arg, "--libc-runtimes=")) |libc_runtimes_path| { + libc_runtimes_path_arg = libc_runtimes_path; + } else if (std.mem.eql(u8, arg, "-fdarling")) { + args.enable_darling = true; + } else if (std.mem.eql(u8, arg, "-fqemu")) { + args.enable_qemu = true; + } else if (std.mem.eql(u8, arg, "-frosetta")) { + args.enable_rosetta = true; + } else if (std.mem.eql(u8, arg, "-fwine")) { + args.enable_wine = true; + } else if (std.mem.eql(u8, arg, "-fwasmtime")) { + args.enable_wasmtime = true; + } else if (std.mem.eql(u8, arg, "--quiet")) { + args.quiet = true; + } else { + if (test_path_arg) |_| fatal("unexpected arg {q}", .{arg}); + test_path_arg = arg; + } + } + + const test_path = test_path_arg orelse fatal("missing \"path/to/test\" arg", .{}); + const zig_path = zig_path_arg orelse fatal("missing \"--zig=path/to/zig\" arg", .{}); + const lib_path = lib_path_arg orelse fatal("missing \"--lib=path/to/lib\" arg", .{}); + + const cwd: std.Io.Dir = .cwd(); + args.test_file = cwd.openFile(init.io, test_path, .{ + .allow_directory = true, + }) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to open {q}: {t}", .{ test_path, e }), + }; + defer args.test_file.close(init.io); + args.test_kind = (args.test_file.stat(init.io) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to stat {q}: {t}", .{ test_path, e }), + }).kind; + args.zig_exe = cwd.openFile(init.io, zig_path, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to open {q}: {t}", .{ zig_path, e }), + }; + defer args.zig_exe.close(init.io); + args.lib_dir = cwd.openDir(init.io, lib_path, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to open {q}: {t}", .{ lib_path, e }), + }; + defer args.lib_dir.close(init.io); + if (libc_runtimes_path_arg) |libc_runtimes_path| args.libc_runtimes_dir = cwd.openDir( + init.io, + libc_runtimes_path, + .{}, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to open {q}: {t}", .{ lib_path, e }), + }; + defer if (args.libc_runtimes_dir) |libc_runtimes_dir| libc_runtimes_dir.close(init.io); + + const src_dir_path = "src_" ++ std.fmt.hex(rand_int: { + var rand_int: u64 = undefined; + init.io.random(@ptrCast(&rand_int)); + break :rand_int rand_int; + }); + args.src_dir = cwd.createDirPathOpen(init.io, src_dir_path, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to create {q}: {t}", .{ src_dir_path, e }), + }; + defer { + args.src_dir.close(init.io); + if (!args.keep_src) cwd.deleteTree(init.io, src_dir_path) catch |err| { + std.log.warn("unable to delete tree {q}: {t}", .{ src_dir_path, err }); + }; + } + + var child = std.process.spawn(init.io, .{ + .argv = &.{ self, "--listen=-" }, + .stdin = .pipe, + .stdout = .pipe, + .inherit_dirs = &.{ args.lib_dir, args.src_dir }, + .inherit_files = &.{ args.zig_exe, args.test_file }, + }) catch |err| switch (err) { + error.Canceled, error.OutOfMemory => |e| return e, + else => |e| fatal("unable to spawn runner: {t}", .{e}), + }; + + var child_reader_buffer: [512]u8 = undefined; + var child_reader = child.stdout.?.readerStreaming(init.io, &child_reader_buffer); + var child_writer_buffer: [512]u8 = undefined; + var child_writer = child.stdin.?.writerStreaming(init.io, &child_writer_buffer); + var client: std.zig.Client = .{ + .in = &child_reader.interface, + .out = &child_writer.interface, + }; + const success = runClient(init.gpa, arena, init.io, &client, &args) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.ReadFailed => switch (child_reader.err.?) { + error.Canceled => |e| return e, + else => |e| fatal("unable to receive message: {t}", .{e}), + }, + error.WriteFailed => switch (child_writer.err.?) { + error.Canceled => |e| return e, + else => |e| fatal("unable to send message: {t}", .{e}), + }, + error.EndOfStream => fatal("no more messages", .{}), + }; + _ = child.wait(init.io) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to wait for runner: {t}", .{e}), + }; + return if (success) 0 else 1; +} +fn runClient( + gpa: std.mem.Allocator, + arena: std.mem.Allocator, + io: std.Io, + client: *std.zig.Client, + args: *const ClientArgs, +) (std.mem.Allocator.Error || std.Io.Reader.Error || std.Io.Writer.Error)!bool { + var stdout_buffer: [512]u8 = undefined; + var stdout_fw: std.Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buffer); + const stdout = &stdout_fw.interface; + var stderr_buffer: [512]u8 = undefined; + var stderr_fw: std.Io.File.Writer = .initStreaming(.stderr(), io, &stderr_buffer); + const stderr = &stderr_fw.interface; + + var argv: std.ArrayList(u8) = .empty; + defer argv.deinit(gpa); + const Arg = std.zig.Client.Message.Arg; + + try argv.append(gpa, @backingInt(@as(Arg, switch (args.test_kind) { + else => unreachable, + .file => .input_file, + .directory => .input_dir, + }))); + try argv.appendSlice(gpa, @ptrCast(&args.test_file.handle)); + + try argv.append(gpa, @backingInt(Arg.prefix)); + try argv.appendSlice(gpa, "--zig="); + try argv.append(gpa, 0); + try argv.append(gpa, @backingInt(Arg.input_file)); + try argv.appendSlice(gpa, @ptrCast(&args.zig_exe.handle)); + + try argv.append(gpa, @backingInt(Arg.prefix)); + try argv.appendSlice(gpa, "--lib="); + try argv.append(gpa, 0); + try argv.append(gpa, @backingInt(Arg.input_dir)); + try argv.appendSlice(gpa, @ptrCast(&args.lib_dir.handle)); + + try argv.append(gpa, @backingInt(Arg.prefix)); + try argv.appendSlice(gpa, "--src="); + try argv.append(gpa, 0); + try argv.append(gpa, @backingInt(Arg.output_dir)); + try argv.appendSlice(gpa, @ptrCast(&args.src_dir.handle)); + + for (args.targets.items) |target| { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "--target"); + try argv.append(gpa, 0); + + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, target); + try argv.append(gpa, 0); + } + + if (args.libc_runtimes_dir) |libc_runtimes_dir| { + try argv.append(gpa, @backingInt(Arg.prefix)); + try argv.appendSlice(gpa, "--libc-runtimes="); + try argv.append(gpa, 0); + try argv.append(gpa, @backingInt(Arg.input_dir)); + try argv.appendSlice(gpa, @ptrCast(&libc_runtimes_dir.handle)); + } + if (args.enable_darling) { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "-fdarling"); + try argv.append(gpa, 0); + } + if (args.enable_qemu) { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "-fqemu"); + try argv.append(gpa, 0); + } + if (args.enable_rosetta) { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "-frosetta"); + try argv.append(gpa, 0); + } + if (args.enable_wine) { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "-fwine"); + try argv.append(gpa, 0); + } + if (args.enable_wasmtime) { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "-fwasmtime"); + try argv.append(gpa, 0); + } + + if (args.quiet) { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "--quiet"); + try argv.append(gpa, 0); + } + + try client.serveMessageHeader(.{ + .tag = .args, + .bytes_len = @intCast(argv.items.len), + }); + try client.out.writeAll(argv.items); + + var success = true; + var metadata: struct { + index: u32, + len: u32, + names: []u32, + expected_panic_msgs: []u32, + string_bytes: []u8, + + fn name(m: *@This()) []const u8 { + return std.mem.sliceTo(m.string_bytes[m.names[m.index - 1]..], 0); + } + + fn expectedPanicMsg(m: *@This()) ?[:0]const u8 { + return switch (m.expected_panic_msgs[m.index - 1]) { + 0 => null, + else => |offset| m.string_bytes[offset..std.mem.findScalarPos( + u8, + m.string_bytes, + offset, + 0, + ).? :0], + }; + } + + fn next(m: *@This()) ?u32 { + if (m.len - m.index == 0) return null; + defer m.index += 1; + return m.index; + } + } = .{ .index = 0, .len = 0, .names = &.{}, .expected_panic_msgs = &.{}, .string_bytes = &.{} }; + try client.serveBodylessMessage(.query_test_metadata); + while (true) { + const hdr = try client.receiveMessage(); + switch (hdr.tag) { + else => try client.in.discardAll(hdr.bytes_len), + .zig_version => { + const actual_version = try client.in.take(hdr.bytes_len); + if (!std.mem.eql(u8, zig_version_string, actual_version)) { + try stderr.print( + "error: zig version mismatch compiler test runner vs compiler: {q} vs {q}\n", + .{ zig_version_string, actual_version }, + ); + try stderr.flush(); + std.process.exit(1); + } + }, + .error_bundle => { + const body = try arena.alloc(u8, hdr.bytes_len); + try client.in.readSliceAll(body); + const eb = std.zig.Server.allocErrorBundle(arena, body) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.EndOfStream => { + try stderr.writeAll("error bundle truncated\n"); + try stderr.flush(); + std.process.exit(1); + }, + }; + success = false; + eb.renderToStderr(io, .{}, .auto) catch {}; + }, + .test_metadata => { + const tm_hdr = + try client.in.takeStruct(std.zig.Server.Message.TestMetadata, .little); + metadata = .{ + .index = 0, + .len = tm_hdr.tests_len, + .names = try arena.alloc(u32, tm_hdr.tests_len), + .expected_panic_msgs = try arena.alloc(u32, tm_hdr.tests_len), + .string_bytes = try arena.alloc(u8, tm_hdr.string_bytes_len), + }; + try client.in.readSliceEndian(u32, metadata.names, .little); + try client.in.readSliceEndian(u32, metadata.expected_panic_msgs, .little); + try client.in.readSliceAll(metadata.string_bytes); + try client.serveRunTest(metadata.next() orelse break); + }, + .test_results => { + const tr = try client.in.takeStruct(std.zig.Server.Message.TestResults, .little); + switch (tr.flags.status) { + .pass, .skip => {}, + .fail => success = false, + } + try stdout.print("[{t}] {s}\n", .{ tr.flags.status, metadata.name() }); + try stdout.flush(); + try client.serveRunTest(metadata.next() orelse break); + }, + } + } + try client.serveBodylessMessage(.exit); + return success; +} diff --git a/test/tests.zig b/test/tests.zig index ff475109fb084df3c5948f05c7d9b0f0a4ffd0b5..307a061ac885678ee2868821135896409e01e817 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2214,9 +2214,9 @@ const IncrementalTarget = struct { /// These are passed to `incr-check` as `-` strings. /// -/// If only one specific test is failing on a target, instead of entirely disabling the target here, -/// you can skip the target for that specific test only by adding a line like this to the manifest: -/// #skip_target=x86_64-linux-selfhosted +/// If only one specific test is failing on a target, instead of entirely disabling the target here, you +/// can skip the target for that specific test only by adding a line like this to top of the manifest: +/// #skip x86_64-linux-selfhosted const incremental_targets = &[_]IncrementalTarget{ // Avoid adding more CBE or LLVM targets without good reason: they're a lot slower than others // 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( .simple_skip_release_small = mem.findScalar(OptimizeMode, optimize_modes, .small) == null, }); const test_cases_dep_step = test_cases_dep.builder.default_step; - test_cases_dep_step.name = b.graph.dupeString(test_cases_dep_name); + test_cases_dep_step.name = test_cases_dep_name; step.dependOn(test_cases_dep.builder.default_step); return step; } @@ -3344,41 +3344,41 @@ const IncrementalTestOptions = struct { pub fn addIncrementalTests( b: *std.Build, - test_step: *Step, + runner: *std.Build.Step.Compile, options: IncrementalTestOptions, -) !void { - const io = b.graph.io; - - const incr_check = b.addExecutable(.{ - .name = "incr-check", - .root_module = b.createModule(.{ - .root_source_file = b.path("tools/incr-check.zig"), - .target = b.graph.host, - .optimize = .debug, - }), - }); - - b.dependOnDirectoryContents(b.path("test/incremental")); - - var dir = try b.root.openDir(io, "test/incremental", .{ .iterate = true }); - defer dir.close(io); - - var it = try dir.walk(b.graph.arena); - while (try it.next(io)) |entry| { - if (std.mem.endsWith(u8, entry.basename, ".swp")) continue; - - for (options.test_filters) |test_filter| { - if (std.mem.find(u8, entry.path, test_filter)) |_| break; +) !*Step { + const tests_step = b.step("test-incremental", "Run the new incremental compilation test cases"); + + const tests_path = b.path("test/incremental"); + b.dependOnDirectoryContents(tests_path); + + var tests_dir = try b.root.openDir(b.graph.io, "test/incremental", .{ .iterate = true }); + defer tests_dir.close(b.graph.io); + var test_it = tests_dir.iterate(); + while (try test_it.next(b.graph.io)) |@"test"| { + for (options.test_filters) |filter| { + if (std.mem.find(u8, @"test".name, filter) != null) break; } else if (options.test_filters.len > 0) continue; - switch (entry.kind) { - .file => {}, - .directory => { - b.dependOnDirectoryContents(b.path(b.pathJoin(&.{ "test", "incremental", entry.path }))); - }, + const test_path = tests_path.path(b, @"test".name); + switch (@"test".kind) { else => continue, + .file => if (isEditorFileName(@"test".name)) continue, + .directory => {}, } - b.dependOnFileContents(b.path(b.pathJoin(&.{ "test", "incremental", entry.path }))); + + const run = b.addRunArtifact(runner); + run.setName(@"test".name); + run.enableProtocolMode(); + + switch (@"test".kind) { + else => continue, + .file => run.addFileArg(test_path), + .directory => run.addDirectoryArg(test_path), + } + run.addPrefixedFileArg("--zig=", .zig_exe); + run.addPrefixedDirectoryArg("--lib=", .zig_lib); + _ = run.addPrefixedOutputDirectoryArg("--src=", "src"); for (incremental_targets) |test_target| { const resolved_target = b.resolveTargetQuery(test_target.target); @@ -3399,40 +3399,36 @@ pub fn addIncrementalTests( if (options.skip_llvm and test_target.backend == .llvm) continue; - const target_str = b.fmt("{s}-{t}", .{ + const target_str = b.fmt("{s}-incremental-{t}", .{ resolved_target.query.zigTriple(b.allocator) catch @panic("OOM"), test_target.backend, }); - if (options.test_target_filters.len > 0) { - for (options.test_target_filters) |filter| { - if (std.mem.find(u8, target_str, filter) != null) break; - } else continue; - } - - const run = b.addRunArtifact(incr_check); - run.setName(b.fmt("incr-check {s} '{s}'", .{ target_str, entry.basename })); - - run.addArg(b.graph.zig_exe); - run.addFileArg(b.path("test/incremental/").path(b, entry.path)); - - run.addArg("--zig-lib-dir"); - run.addDirectoryArg(.zig_lib); + for (options.test_target_filters) |filter| { + if (std.mem.find(u8, target_str, filter) != null) break; + } else if (options.test_target_filters.len > 0) continue; run.addArgs(&.{ "--target", target_str }); - - run.addArg("--quiet"); // don't fill stderr telling us about skipped tests etc - - run.addThirdPartyEnabledArgDarling(.{ .enabled = "-fdarling" }); - run.addThirdPartyEnabledArgQemu(.{ .enabled = "-fqemu" }); - run.addThirdPartyEnabledArgRosetta(.{ .enabled = "-frosetta" }); - run.addThirdPartyEnabledArgWasmtime(.{ .enabled = "-fwasmtime" }); - run.addThirdPartyEnabledArgWine(.{ .enabled = "-fwine" }); - - run.addCheck(.{ .expect_term = .{ .exited = 0 } }); - test_step.dependOn(&run.step); } + + run.addPrefixedDirectoryArg("--libc-runtimes=", .{ .relative = .{ .base = .libc_runtimes } }); + run.addThirdPartyEnabledArgDarling(.{ .enabled = "-fdarling" }); + run.addThirdPartyEnabledArgQemu(.{ .enabled = "-fqemu" }); + run.addThirdPartyEnabledArgRosetta(.{ .enabled = "-frosetta" }); + run.addThirdPartyEnabledArgWasmtime(.{ .enabled = "-fwasmtime" }); + run.addThirdPartyEnabledArgWine(.{ .enabled = "-fwine" }); + + run.addArg("--quiet"); // don't fill stderr telling us about skipped tests etc + + tests_step.dependOn(&run.step); } + + return tests_step; +} + +fn isEditorFileName(name: []const u8) bool { + return (std.mem.startsWith(u8, name, "#") and std.mem.endsWith(u8, name, "#")) or + std.mem.endsWith(u8, name, "~") or std.mem.endsWith(u8, name, ".swp"); } pub fn addLlvmIrTests(b: *std.Build, options: LlvmIrContext.Options) ?*Step { diff --git a/tools/doctest.zig b/tools/doctest.zig index 18b73798d496f29e46660c213e19da67e8afcfa7..f39c56d4d12c65cca732f4c9dd9aea620b5f6ba9 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -314,6 +314,7 @@ fn printOutput( switch (getExternalExecutor(io, &target, .{ .host_cpu_arch = host.cpu.arch, .host_os_tag = host.os.tag, + .link_mode = code.link_mode orelse .dynamic, .link_libc = code.link_libc, })) { .native => {}, diff --git a/tools/gen_spirv_spec.zig b/tools/gen_spirv_spec.zig index b9967d378570ede07fa26cfc2aa7870ba9d6fed7..769f09936e66547da537be43125564991a803e04 100644 --- a/tools/gen_spirv_spec.zig +++ b/tools/gen_spirv_spec.zig @@ -86,8 +86,7 @@ pub fn main(init: std.process.Init) !void { var zir = try std.zig.AstGen.generate(arena, tree); if (zir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(arena); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena); defer wip_errors.deinit(); try wip_errors.addZirErrorMessages(zir, tree, output, ""); var error_bundle = try wip_errors.toOwnedBundle(""); diff --git a/tools/incr-check.zig b/tools/incr-check.zig deleted file mode 100644 index c8034260dbc2af7a256151c7c43f4429531e390b..0000000000000000000000000000000000000000 --- a/tools/incr-check.zig +++ /dev/null @@ -1,1032 +0,0 @@ -const std = @import("std"); -const Io = std.Io; -const Dir = std.Io.Dir; -const Allocator = std.mem.Allocator; -const Cache = std.Build.Cache; - -const usage = - \\Usage: incr-check [options] - \\Options: - \\ --target triple-backend - \\ --quiet - \\ --zig-lib-dir /path/to/zig/lib - \\ --zig-cc-binary /path/to/zig - \\ -fqemu - \\ -fwine - \\ -fwasmtime - \\Debug Options: - \\ --preserve-tmp - \\ --debug-log foo - \\ --debug-link-snapshot -; - -pub const std_options: std.Options = .{ - .logFn = logImpl, -}; -var log_cur_update: ?*const Case.Update = null; -fn logImpl( - comptime level: std.log.Level, - comptime scope: @EnumLiteral(), - comptime format: []const u8, - args: anytype, -) void { - const update = log_cur_update orelse { - return std.log.defaultLog(level, scope, format, args); - }; - std.log.defaultLog( - level, - scope, - "['{s}'] " ++ format, - .{update.name} ++ args, - ); -} - -pub fn main(init: std.process.Init) !void { - const gpa = init.gpa; - const arena = init.arena.allocator(); - const io = init.io; - const environ_map = init.environ_map; - const cwd_path = try std.process.currentPathAlloc(io, arena); - - var opt_zig_exe: ?[]const u8 = null; - var opt_input_file_name: ?[]const u8 = null; - var opt_lib_dir: ?[]const u8 = null; - var opt_cc_zig: ?[]const u8 = null; - var opt_target: ?struct { std.Target.Query, Backend } = null; - var preserve_tmp = false; - var enable_qemu: bool = false; - var enable_wine: bool = false; - var enable_wasmtime: bool = false; - var enable_darling: bool = false; - var quiet: bool = false; - - var debug_log_args: std.ArrayList([]const u8) = .empty; - var debug_link_snapshot = false; - - var arg_it = try init.minimal.args.iterateAllocator(arena); - _ = arg_it.skip(); - while (arg_it.next()) |arg| { - if (arg.len > 0 and arg[0] == '-') { - if (std.mem.eql(u8, arg, "--zig-lib-dir")) { - opt_lib_dir = arg_it.next() orelse badUsage("expected arg after --zig-lib-dir", .{}); - } else if (std.mem.eql(u8, arg, "--target")) { - const str = arg_it.next() orelse badUsage("expected arg after --zig-cc-binary", .{}); - opt_target = parseTargetQueryAndBackend(str, ""); - } else if (std.mem.eql(u8, arg, "--quiet")) { - quiet = true; - } else if (std.mem.eql(u8, arg, "--debug-log")) { - try debug_log_args.append( - arena, - arg_it.next() orelse badUsage("expected arg after --debug-log", .{}), - ); - } else if (std.mem.eql(u8, arg, "--debug-link-snapshot")) { - debug_link_snapshot = true; - } else if (std.mem.eql(u8, arg, "--preserve-tmp")) { - preserve_tmp = true; - } else if (std.mem.eql(u8, arg, "-fqemu")) { - enable_qemu = true; - } else if (std.mem.eql(u8, arg, "-fwine")) { - enable_wine = true; - } else if (std.mem.eql(u8, arg, "-fwasmtime")) { - enable_wasmtime = true; - } else if (std.mem.eql(u8, arg, "-fdarling")) { - enable_darling = true; - } else if (std.mem.eql(u8, arg, "--zig-cc-binary")) { - opt_cc_zig = arg_it.next() orelse badUsage("expected arg after --zig-cc-binary", .{}); - } else { - badUsage("unknown option '{s}'", .{arg}); - } - continue; - } - if (opt_zig_exe == null) { - opt_zig_exe = arg; - } else if (opt_input_file_name == null) { - opt_input_file_name = arg; - } else { - badUsage("unknown argument '{s}'\n{s}", .{ arg, usage }); - } - } - const zig_exe = opt_zig_exe orelse badUsage("missing path to zig", .{}); - const input_file_name = opt_input_file_name orelse badUsage("missing input file", .{}); - const target_query, const backend = opt_target orelse badUsage("missing required option '--target'", .{}); - - if (backend == .cbe and opt_lib_dir == null) { - std.process.fatal("'--zig-lib-dir' required when using backend 'cbe'", .{}); - } - - const input_file_bytes = try Dir.cwd().readFileAlloc(io, input_file_name, arena, .limited(std.math.maxInt(u32))); - const case: Case = try .parse(arena, input_file_bytes); - - for (case.skip_targets) |skip| { - if (target_query.eql(skip.query) and backend == skip.backend) { - if (!quiet) std.log.warn("skipping test because of a 'skip_target' match", .{}); - return; - } - } - - const target = try std.zig.system.resolveTargetQuery(io, target_query); - - const prog_node = std.Progress.start(io, .{}); - defer prog_node.end(); - - const rand_int = rand64(io); - const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int); - var tmp_dir = try Dir.cwd().createDirPathOpen(io, tmp_dir_path, .{}); - defer { - tmp_dir.close(io); - if (!preserve_tmp) { - Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| { - std.log.warn("failed to delete tree '{s}': {t}", .{ tmp_dir_path, err }); - }; - } - } - - // Convert paths to be relative to the cwd of the subprocess. - const resolved_zig_exe = try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, zig_exe); - const opt_resolved_lib_dir = if (opt_lib_dir) |lib_dir| - try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, lib_dir) - else - null; - - const host = try std.zig.system.resolveTargetQuery(io, .{}); - - var child_args: std.ArrayList([]const u8) = .empty; - try child_args.appendSlice(arena, &.{ - resolved_zig_exe, "build-exe", - "-fincremental", "-fno-ubsan-rt", - "-target", try target_query.zigTriple(arena), - "--cache-dir", ".local-cache", - "--global-cache-dir", ".global-cache", - }); - - if (opt_resolved_lib_dir) |resolved_lib_dir| { - try child_args.appendSlice(arena, &.{ "--zig-lib-dir", resolved_lib_dir }); - } - switch (backend) { - .sema => try child_args.append(arena, "-fno-emit-bin"), - .selfhosted => try child_args.appendSlice(arena, &.{ "-fno-llvm", "-fno-lld" }), - .llvm => try child_args.appendSlice(arena, &.{ "-fllvm", "-flld" }), - .cbe => try child_args.appendSlice(arena, &.{ "-ofmt=c", "-lc" }), - } - for (debug_log_args.items) |arg| { - try child_args.appendSlice(arena, &.{ "--debug-log", arg }); - } - if (debug_link_snapshot) { - try child_args.append(arena, "--debug-link-snapshot"); - } - for (case.modules) |mod| { - try child_args.appendSlice(arena, &.{ "--dep", mod.name }); - } - try child_args.append(arena, try std.fmt.allocPrint(arena, "-Mroot={s}", .{case.root_source_file})); - for (case.modules) |mod| { - try child_args.append(arena, try std.fmt.allocPrint(arena, "-M{s}={s}", .{ mod.name, mod.file })); - } - try child_args.append(arena, "--listen=-"); - - const zig_prog_node = prog_node.start("zig", 0); - defer zig_prog_node.end(); - - var cc_child_args: std.ArrayList([]const u8) = .empty; - if (backend == .cbe) { - const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe| - try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, cc_zig_exe) - else - resolved_zig_exe; - - try cc_child_args.appendSlice(arena, &.{ - resolved_cc_zig_exe, "build-exe", - "-target", try target_query.zigTriple(arena), - "--cache-dir", ".local-cache", - "--global-cache-dir", ".global-cache", - "-I", opt_resolved_lib_dir.?, // verified earlier - "-lc", - }); - } - - const allow_compiler_stderr = debug_log_args.items.len != 0; - - if (allow_compiler_stderr) { - const cmd: std.zig.SubprocessCommand = .{ - .argv = child_args.items, - .cwd = tmp_dir_path, - }; - std.log.scoped(.spawn).info("{f}", .{cmd}); - } - - var child = try std.process.spawn(io, .{ - .argv = child_args.items, - .stdin = .pipe, - .stdout = .pipe, - .stderr = .pipe, - .progress_node = zig_prog_node, - .cwd = .{ .path = tmp_dir_path }, - }); - defer child.kill(io); - - const updates_prog_node = prog_node.start("updates", case.updates.len); - defer updates_prog_node.end(); - - var eval: Eval = .{ - .arena = arena, - .io = io, - .case = case, - .host = host, - .target = target, - .backend = backend, - .tmp_dir = tmp_dir, - .tmp_dir_path = tmp_dir_path, - .child = &child, - .allow_compiler_stderr = allow_compiler_stderr, - .quiet = quiet, - .preserve_tmp_on_fatal = preserve_tmp, - .cc_child_args = &cc_child_args, - .enable_qemu = enable_qemu, - .enable_wine = enable_wine, - .enable_wasmtime = enable_wasmtime, - .enable_darling = enable_darling, - }; - - var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; - var multi_reader: Io.File.MultiReader = undefined; - multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); - defer multi_reader.deinit(); - - var update_mtime = Io.Clock.real.now(io); - for (case.updates) |update| { - var update_prog_node = updates_prog_node.start(update.name, 0); - defer update_prog_node.end(); - - if (debug_log_args.items.len != 0) { - // Print a line separating the debug logs from the compiler in the stderr output. - std.log.scoped(.status).info("update: '{s}'", .{update.name}); - } - - log_cur_update = &update; - defer log_cur_update = null; - - eval.write(update, update_mtime); - update_mtime = update_mtime.addDuration(.fromSeconds(5)); - try eval.requestUpdate(); - try eval.check(&multi_reader, update, update_prog_node); - } - - try eval.end(&multi_reader); - - waitChild(&child, &eval); -} - -const Eval = struct { - arena: Allocator, - io: Io, - case: Case, - host: std.Target, - target: std.Target, - backend: Backend, - tmp_dir: Dir, - tmp_dir_path: []const u8, - child: *std.process.Child, - allow_compiler_stderr: bool, - quiet: bool, - preserve_tmp_on_fatal: bool, - /// When `backend == .cbe`, this contains the first few arguments to `zig cc` to build the generated binary. - /// The arguments `out.c in.c` must be appended before spawning the subprocess. - cc_child_args: *std.ArrayList([]const u8), - - enable_qemu: bool, - enable_wine: bool, - enable_wasmtime: bool, - enable_darling: bool, - - /// Currently this function assumes the previous updates have already been written. - fn write(eval: *Eval, update: Case.Update, mtime: Io.Timestamp) void { - const io = eval.io; - for (update.changes) |full_contents| { - var update_file = eval.tmp_dir.createFile(io, full_contents.name, .{}) catch |err| { - eval.fatal("failed to create update '{s}': {t}", .{ full_contents.name, err }); - }; - defer update_file.close(io); - update_file.writeStreamingAll(io, full_contents.bytes) catch |err| { - eval.fatal("failed to write update '{s}': {t}", .{ full_contents.name, err }); - }; - update_file.setTimestamps(io, .{ .modify_timestamp = .{ .new = mtime } }) catch |err| { - eval.fatal("failed to set mtime for '{s}': {t}", .{ full_contents.name, err }); - }; - } - for (update.deletes) |doomed_name| { - eval.tmp_dir.deleteFile(io, doomed_name) catch |err| { - eval.fatal("failed to delete '{s}': {t}", .{ doomed_name, err }); - }; - } - } - - fn check(eval: *Eval, mr: *Io.File.MultiReader, update: Case.Update, prog_node: std.Progress.Node) !void { - const arena = eval.arena; - const stdout = mr.reader(0); - const stderr = mr.reader(1); - - var client: std.zig.Client = .{ - .in = stdout, - .out = undefined, - }; - - while (true) { - const header = client.receiveMessageWithMultiReader(mr, .none) catch |err| switch (err) { - error.Timeout => unreachable, - error.EndOfStream => break, - else => |e| return e, - }; - const body = client.in.take(header.bytes_len) catch unreachable; - - switch (header.tag) { - .error_bundle => { - const result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body); - if (stderr.bufferedLen() > 0) { - if (eval.allow_compiler_stderr) { - std.log.info("error_bundle stderr:\n{s}", .{stderr.buffered()}); - } else { - eval.fatal("error_bundle unexpected stderr:\n{s}", .{stderr.buffered()}); - } - stderr.tossBuffered(); - } - if (result_error_bundle.errorMessageCount() != 0) { - try eval.checkErrorOutcome(update, result_error_bundle); - } - // This message indicates the end of the update. - return; - }, - .emit_digest => { - var r: std.Io.Reader = .fixed(body); - _ = r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable; - - if (stderr.bufferedLen() > 0) { - if (eval.allow_compiler_stderr) { - std.log.info("emit_digest stderr:\n{s}", .{stderr.buffered()}); - } else { - eval.fatal("emit_digest unexpected stderr:\n{s}", .{stderr.buffered()}); - } - stderr.tossBuffered(); - } - if (eval.backend == .sema) { - try eval.checkSuccessOutcome(update, null, prog_node); - continue; - } - - const digest = r.takeArray(Cache.bin_digest_len) catch unreachable; - const result_dir = ".local-cache" ++ Dir.path.sep_str ++ "o" ++ Dir.path.sep_str ++ Cache.binToHex(digest.*); - - const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{ - .root_name = "root", // corresponds to the module name "root" - .cpu_arch = eval.target.cpu.arch, - .os_tag = eval.target.os.tag, - .ofmt = eval.target.ofmt, - .abi = eval.target.abi, - .output_mode = .Exe, - }); - const bin_path = try Dir.path.join(arena, &.{ result_dir, bin_name }); - - try eval.checkSuccessOutcome(update, bin_path, prog_node); - }, - else => { - // Ignore other messages. - }, - } - } - - const buffered_stderr = stderr.buffered(); - if (buffered_stderr.len > 0) { - if (eval.allow_compiler_stderr) { - std.log.info("stderr:\n{s}", .{buffered_stderr}); - } else { - eval.fatal("unexpected stderr:\n{s}", .{buffered_stderr}); - } - } - - waitChild(eval.child, eval); - eval.fatal("compiler failed to send terminating error_bundle", .{}); - } - - fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void { - const io = eval.io; - const expected = switch (update.outcome) { - .unknown => return, - .compile_errors => |ce| ce, - .stdout, .exit_code => { - try error_bundle.renderToStderr(io, .{}, .auto); - eval.fatal("unexpected compile errors", .{}); - }, - }; - - var expected_idx: usize = 0; - - for (error_bundle.getMessages()) |err_idx| { - if (expected_idx == expected.errors.len) { - try error_bundle.renderToStderr(io, .{}, .auto); - eval.fatal("more errors than expected", .{}); - } - try eval.checkOneError(error_bundle, expected.errors[expected_idx], false, err_idx); - expected_idx += 1; - - for (error_bundle.getNotes(err_idx)) |note_idx| { - if (expected_idx == expected.errors.len) { - try error_bundle.renderToStderr(io, .{}, .auto); - eval.fatal("more error notes than expected", .{}); - } - try eval.checkOneError(error_bundle, expected.errors[expected_idx], true, note_idx); - expected_idx += 1; - } - } - - if (!std.mem.eql(u8, error_bundle.getCompileLogOutput(), expected.compile_log_output)) { - try error_bundle.renderToStderr(io, .{}, .auto); - eval.fatal("unexpected compile log output", .{}); - } - } - - fn checkOneError( - eval: *Eval, - eb: std.zig.ErrorBundle, - expected: Case.ExpectedError, - is_note: bool, - err_idx: std.zig.ErrorBundle.MessageIndex, - ) Allocator.Error!void { - const io = eval.io; - const err = eb.getErrorMessage(err_idx); - if (err.count != 1) @panic("TODO error message with count>1"); - const msg = eb.nullTerminatedString(err.msg); - const matches = matches: { - if (expected.is_note != is_note) break :matches false; - if (!std.mem.eql(u8, expected.msg, msg)) break :matches false; - if (err.src_loc == .none) { - break :matches expected.src == null; - } - const expected_src = expected.src orelse break :matches false; - const src = eb.getSourceLocation(err.src_loc); - const raw_filename = eb.nullTerminatedString(src.src_path); - // We need to replace backslashes for consistency between platforms. - const filename = name: { - if (std.mem.findScalar(u8, raw_filename, '\\') == null) break :name raw_filename; - const copied = try eval.arena.dupe(u8, raw_filename); - std.mem.replaceScalar(u8, copied, '\\', '/'); - break :name copied; - }; - if (!std.mem.eql(u8, expected_src.filename, filename)) break :matches false; - if (expected_src.line != src.line + 1) break :matches false; - if (expected_src.column != src.column + 1) break :matches false; - break :matches true; - }; - if (!matches) { - eb.renderToStderr(io, .{}, .auto) catch {}; - eval.fatal("compile error did not match expected error", .{}); - } - } - - fn checkSuccessOutcome(eval: *Eval, update: Case.Update, opt_emitted_path: ?[]const u8, prog_node: std.Progress.Node) !void { - switch (update.outcome) { - .unknown => return, - .compile_errors => eval.fatal("expected compile errors but compilation incorrectly succeeded", .{}), - .stdout, .exit_code => {}, - } - const emitted_path = opt_emitted_path orelse { - std.debug.assert(eval.backend == .sema); - return; - }; - const io = eval.io; - - const binary_path = switch (eval.backend) { - .sema => unreachable, - .selfhosted, .llvm => emitted_path, - .cbe => bin: { - const rand_int = rand64(io); - const out_bin_name = "./out_" ++ std.fmt.hex(rand_int); - try eval.buildCOutput(emitted_path, out_bin_name, prog_node); - break :bin out_bin_name; - }, - }; - - var argv_buf: [2][]const u8 = undefined; - const argv: []const []const u8, const is_foreign: bool = sw: switch (std.zig.system.getExternalExecutor( - io, - &eval.target, - .{ - .link_libc = eval.backend == .cbe, - .host_cpu_arch = eval.host.cpu.arch, - .host_os_tag = eval.host.os.tag, - }, - )) { - .bad_dl, .bad_os_or_cpu => { - // This binary cannot be executed on this host. - if (!eval.quiet) { - std.log.warn("skipping execution because host '{s}' cannot execute binaries for foreign target '{s}'", .{ - try eval.host.zigTriple(eval.arena), - try eval.target.zigTriple(eval.arena), - }); - } - return; - }, - .native, .rosetta => argv: { - argv_buf[0] = binary_path; - break :argv .{ argv_buf[0..1], false }; - }, - .qemu => |executor_cmd| argv: { - if (eval.enable_qemu) { - argv_buf[0] = executor_cmd; - argv_buf[1] = binary_path; - break :argv .{ argv_buf[0..2], true }; - } else { - continue :sw .bad_os_or_cpu; - } - }, - .wine => |executor_cmd| argv: { - if (eval.enable_wine) { - argv_buf[0] = executor_cmd; - argv_buf[1] = binary_path; - break :argv .{ argv_buf[0..2], true }; - } else { - continue :sw .bad_os_or_cpu; - } - }, - .wasmtime => |executor_cmd| argv: { - if (eval.enable_wasmtime) { - argv_buf[0] = executor_cmd; - argv_buf[1] = binary_path; - break :argv .{ argv_buf[0..2], true }; - } else { - continue :sw .bad_os_or_cpu; - } - }, - .darling => |executor_cmd| argv: { - if (eval.enable_darling) { - argv_buf[0] = executor_cmd; - argv_buf[1] = binary_path; - break :argv .{ argv_buf[0..2], true }; - } else { - continue :sw .bad_os_or_cpu; - } - }, - }; - - const run_prog_node = prog_node.start("run generated executable", 0); - defer run_prog_node.end(); - - const result = std.process.run(eval.arena, io, .{ - .argv = argv, - .cwd = .{ .path = eval.tmp_dir_path }, - }) catch |err| { - if (is_foreign) { - // Chances are the foreign executor isn't available. Skip this evaluation. - if (!eval.quiet) { - std.log.warn("skipping execution of '{s}' via executor for foreign target '{s}': {t}", .{ - binary_path, - try eval.target.zigTriple(eval.arena), - err, - }); - } - return; - } - eval.fatal("failed to run the generated executable '{s}': {t}", .{ binary_path, err }); - }; - - // Some executors (looking at you, Wine) like throwing some stderr in, just for fun. - // Therefore, we'll ignore stderr when using a foreign executor. - if (!is_foreign and result.stderr.len != 0) { - std.log.err("generated executable '{s}' had unexpected stderr:\n{s}", .{ - binary_path, result.stderr, - }); - } - - switch (result.term) { - .exited => |code| switch (update.outcome) { - .unknown, .compile_errors => unreachable, - .stdout => |expected_stdout| { - if (code != 0) { - eval.fatal("generated executable '{s}' failed with code {d}", .{ binary_path, code }); - } - try std.testing.expectEqualStrings(expected_stdout, result.stdout); - }, - .exit_code => |expected_code| try std.testing.expectEqual(expected_code, code), - }, - .signal => |sig| { - eval.fatal("generated executable '{s}' terminated with signal {t}", .{ binary_path, sig }); - }, - .stopped => |sig| { - eval.fatal("generated executable '{s}' stopped with signal {t}", .{ binary_path, sig }); - }, - .unknown => { - eval.fatal("generated executable '{s}' terminated unexpectedly", .{binary_path}); - }, - } - - if (!is_foreign and result.stderr.len != 0) std.process.exit(1); - } - - fn requestUpdate(eval: *Eval) !void { - const io = eval.io; - - var w = eval.child.stdin.?.writerStreaming(io, &.{}); - var client: std.zig.Client = .{ - .in = undefined, - .out = &w.interface, - }; - client.serveBodylessMessage(.update) catch |err| switch (err) { - error.WriteFailed => return w.err.?, - }; - } - - fn end(eval: *Eval, mr: *Io.File.MultiReader) !void { - requestExit(eval.child, eval); - - var client: std.zig.Client = .{ - .in = mr.reader(0), - .out = undefined, - }; - - while (true) { - const header = client.receiveMessageWithMultiReader(mr, .none) catch |err| switch (err) { - error.Timeout => unreachable, - error.EndOfStream => |e| { - if (client.in.bufferedLen() == 0) break; - return e; - }, - else => |e| return e, - }; - try client.in.discardAll(header.bytes_len); - } - - const stderr = mr.reader(1).buffered(); - if (stderr.len > 0) eval.fatal("unexpected stderr:\n{s}", .{stderr}); - } - - fn buildCOutput(eval: *Eval, c_path: []const u8, out_path: []const u8, prog_node: std.Progress.Node) !void { - std.debug.assert(eval.cc_child_args.items.len > 0); - - const child_prog_node = prog_node.start("build cbe output", 0); - defer child_prog_node.end(); - - try eval.cc_child_args.append(eval.arena, try std.fmt.allocPrint(eval.arena, "-femit-bin={s}", .{out_path})); - try eval.cc_child_args.append(eval.arena, c_path); - defer eval.cc_child_args.items.len -= 2; - - if (eval.allow_compiler_stderr) { - const cmd: std.zig.SubprocessCommand = .{ - .argv = eval.cc_child_args.items, - .cwd = eval.tmp_dir_path, - }; - std.log.scoped(.spawn).info("{f}", .{cmd}); - } - - const result = std.process.run(eval.arena, eval.io, .{ - .argv = eval.cc_child_args.items, - .cwd = .{ .path = eval.tmp_dir_path }, - .progress_node = child_prog_node, - }) catch |err| { - eval.fatal("failed to spawn zig cc for '{s}': {t}", .{ c_path, err }); - }; - - if (result.term == .exited and result.term.exited == 0) return; - - if (result.stderr.len != 0) { - std.log.err("zig cc stderr:\n{s}", .{result.stderr}); - } - switch (result.term) { - .exited => |code| eval.fatal("zig cc for '{s}' failed with code {d}", .{ c_path, code }), - .signal => |sig| eval.fatal("zig cc for '{s}' terminated unexpectedly with signal {t}", .{ c_path, sig }), - .stopped => |sig| eval.fatal("zig cc for '{s}' stopped unexpectedly with signal {t}", .{ c_path, sig }), - .unknown => eval.fatal("zig cc for '{s}' terminated unexpectedly", .{c_path}), - } - } - - fn fatal(eval: *Eval, comptime fmt: []const u8, args: anytype) noreturn { - const io = eval.io; - eval.tmp_dir.close(io); - if (!eval.preserve_tmp_on_fatal) { - // Kill the child since it holds an open handle to its CWD which is the tmp dir path - eval.child.kill(io); - Dir.cwd().deleteTree(io, eval.tmp_dir_path) catch |err| { - std.log.warn("failed to delete tree '{s}': {t}", .{ eval.tmp_dir_path, err }); - }; - } - std.process.fatal(fmt, args); - } -}; - -const Backend = enum { - /// Run semantic analysis only. Runtime output will not be tested, but we still verify - /// that compilation succeeds. Corresponds to `-fno-emit-bin`. - sema, - /// Use the self-hosted code generation backend for this target. - /// Corresponds to `-fno-llvm -fno-lld`. - selfhosted, - /// Use the LLVM backend. - /// Corresponds to `-fllvm -flld`. - llvm, - /// Use the C backend. The output is compiled with `zig cc`. - /// Corresponds to `-ofmt=c`. - cbe, -}; - -const Case = struct { - updates: []Update, - root_source_file: []const u8, - skip_targets: []const SkipTarget, - modules: []const Module, - - const SkipTarget = struct { - query: std.Target.Query, - backend: Backend, - }; - - const Module = struct { - name: []const u8, - file: []const u8, - }; - - const Update = struct { - name: []const u8, - outcome: Outcome, - changes: []const FullContents = &.{}, - deletes: []const []const u8 = &.{}, - }; - - const FullContents = struct { - name: []const u8, - bytes: []const u8, - }; - - const Outcome = union(enum) { - unknown, - compile_errors: struct { - errors: []const ExpectedError, - compile_log_output: []const u8, - }, - stdout: []const u8, - exit_code: u8, - }; - - const ExpectedError = struct { - is_note: bool, - msg: []const u8, - src: ?struct { - filename: []const u8, - line: u32, - column: u32, - }, - }; - - fn parse(arena: Allocator, bytes: []const u8) !Case { - const fatal = std.process.fatal; - - var skip_targets: std.ArrayList(SkipTarget) = .empty; - var modules: std.ArrayList(Module) = .empty; - var updates: std.ArrayList(Update) = .empty; - var changes: std.ArrayList(FullContents) = .empty; - var deletes: std.ArrayList([]const u8) = .empty; - var it = std.mem.splitScalar(u8, bytes, '\n'); - var line_n: usize = 1; - var root_source_file: ?[]const u8 = null; - while (it.next()) |line| : (line_n += 1) { - if (std.mem.startsWith(u8, line, "#")) { - var line_it = std.mem.splitScalar(u8, line, '='); - const key = line_it.first()[1..]; - const val = std.mem.trimEnd(u8, line_it.rest(), "\r"); // windows moment - if (val.len == 0) { - fatal("line {d}: missing value", .{line_n}); - } else if (std.mem.eql(u8, key, "skip_target")) { - const query, const backend = parseTargetQueryAndBackend( - val, - try std.fmt.allocPrint(arena, "line {d}: ", .{line_n}), - ); - try skip_targets.append(arena, .{ - .query = query, - .backend = backend, - }); - } else if (std.mem.eql(u8, key, "module")) { - const split_idx = std.mem.findScalar(u8, val, '=') orelse - fatal("line {d}: module does not include file", .{line_n}); - const name = val[0..split_idx]; - const file = val[split_idx + 1 ..]; - try modules.append(arena, .{ - .name = name, - .file = file, - }); - } else if (std.mem.eql(u8, key, "update")) { - if (updates.items.len > 0) { - const last_update = &updates.items[updates.items.len - 1]; - last_update.changes = try changes.toOwnedSlice(arena); - last_update.deletes = try deletes.toOwnedSlice(arena); - } - try updates.append(arena, .{ - .name = val, - .outcome = .unknown, - }); - } else if (std.mem.eql(u8, key, "file")) { - if (updates.items.len == 0) fatal("line {d}: file directive before update", .{line_n}); - - if (root_source_file == null) - root_source_file = val; - - // Because Windows is so excellent, we need to convert CRLF to LF, so - // can't just slice into the input here. How delightful! - var src: std.ArrayList(u8) = .empty; - - while (true) { - const next_line_raw = it.peek() orelse fatal("line {d}: unexpected EOF", .{line_n}); - const next_line = std.mem.trimEnd(u8, next_line_raw, "\r"); - if (std.mem.startsWith(u8, next_line, "#")) break; - - _ = it.next(); - line_n += 1; - - try src.ensureUnusedCapacity(arena, next_line.len + 1); - src.appendSliceAssumeCapacity(next_line); - src.appendAssumeCapacity('\n'); - } - - try changes.append(arena, .{ - .name = val, - .bytes = src.items, - }); - } else if (std.mem.eql(u8, key, "rm_file")) { - if (updates.items.len == 0) fatal("line {d}: rm_file directive before update", .{line_n}); - try deletes.append(arena, val); - } else if (std.mem.eql(u8, key, "expect_stdout")) { - if (updates.items.len == 0) fatal("line {d}: expect directive before update", .{line_n}); - const last_update = &updates.items[updates.items.len - 1]; - if (last_update.outcome != .unknown) fatal("line {d}: conflicting expect directive", .{line_n}); - last_update.outcome = .{ - .stdout = std.zig.string_literal.parseAlloc(arena, val) catch |err| { - fatal("line {d}: bad string literal: {t}", .{ line_n, err }); - }, - }; - } else if (std.mem.eql(u8, key, "expect_error")) { - if (updates.items.len == 0) fatal("line {d}: expect directive before update", .{line_n}); - const last_update = &updates.items[updates.items.len - 1]; - if (last_update.outcome != .unknown) fatal("line {d}: conflicting expect directive", .{line_n}); - - var errors: std.ArrayList(ExpectedError) = .empty; - try errors.append(arena, parseExpectedError(val, line_n)); - while (true) { - const next_line = it.peek() orelse break; - if (!std.mem.startsWith(u8, next_line, "#")) break; - var new_line_it = std.mem.splitScalar(u8, next_line, '='); - const new_key = new_line_it.first()[1..]; - const new_val = std.mem.trimEnd(u8, new_line_it.rest(), "\r"); - if (new_val.len == 0) break; - if (!std.mem.eql(u8, new_key, "expect_error")) break; - - _ = it.next(); - line_n += 1; - try errors.append(arena, parseExpectedError(new_val, line_n)); - } - - var compile_log_output: std.ArrayList(u8) = .empty; - while (true) { - const next_line = it.peek() orelse break; - if (!std.mem.startsWith(u8, next_line, "#")) break; - var new_line_it = std.mem.splitScalar(u8, next_line, '='); - const new_key = new_line_it.first()[1..]; - const new_val = std.mem.trimEnd(u8, new_line_it.rest(), "\r"); - if (new_val.len == 0) break; - if (!std.mem.eql(u8, new_key, "expect_compile_log")) break; - - _ = it.next(); - line_n += 1; - try compile_log_output.ensureUnusedCapacity(arena, new_val.len + 1); - compile_log_output.appendSliceAssumeCapacity(new_val); - compile_log_output.appendAssumeCapacity('\n'); - } - - last_update.outcome = .{ .compile_errors = .{ - .errors = errors.items, - .compile_log_output = compile_log_output.items, - } }; - } else if (std.mem.eql(u8, key, "expect_compile_log")) { - fatal("line {d}: 'expect_compile_log' must immediately follow 'expect_error'", .{line_n}); - } else { - fatal("line {d}: unrecognized key '{s}'", .{ line_n, key }); - } - } - } - - if (changes.items.len > 0) { - const last_update = &updates.items[updates.items.len - 1]; - last_update.changes = changes.items; // arena so no need for toOwnedSlice - last_update.deletes = deletes.items; - } - - return .{ - .updates = updates.items, - .root_source_file = root_source_file orelse fatal("missing root source file", .{}), - .skip_targets = skip_targets.items, // arena so no need for toOwnedSlice - .modules = modules.items, - }; - } -}; - -fn requestExit(child: *std.process.Child, eval: *Eval) void { - if (child.stdin == null) return; - const io = eval.io; - - var w = eval.child.stdin.?.writerStreaming(io, &.{}); - var client: std.zig.Client = .{ - .in = undefined, - .out = &w.interface, - }; - client.serveBodylessMessage(.exit) catch |err| switch (err) { - error.WriteFailed => switch (w.err.?) { - error.BrokenPipe => {}, - else => |e| eval.fatal("failed to send exit: {t}", .{e}), - }, - }; - - // Send EOF to stdin. - child.stdin.?.close(io); - child.stdin = null; -} - -fn waitChild(child: *std.process.Child, eval: *Eval) void { - const io = eval.io; - requestExit(child, eval); - const term = child.wait(io) catch |err| eval.fatal("child process failed: {t}", .{err}); - switch (term) { - .exited => |code| if (code != 0) eval.fatal("compiler failed with code {d}", .{code}), - .signal => |sig| eval.fatal("compiler terminated with signal {t}", .{sig}), - .stopped => |sig| eval.fatal("compiler stopped unexpectedly with signal {t}", .{sig}), - .unknown => eval.fatal("compiler terminated unexpectedly", .{}), - } -} - -fn parseExpectedError(str: []const u8, l: usize) Case.ExpectedError { - // #expect_error=foo.zig:1:2: error: the error message - // #expect_error=foo.zig:1:2: note: and a note - - const fatal = std.process.fatal; - - var it = std.mem.splitScalar(u8, str, ':'); - const filename = it.first(); - const line_str, const column_str = if (filename.len > 0) .{ - it.next() orelse fatal("line {d}: incomplete error specification", .{l}), - it.next() orelse fatal("line {d}: incomplete error specification", .{l}), - } else .{ undefined, undefined }; - const error_or_note_str = std.mem.trim( - u8, - it.next() orelse fatal("line {d}: incomplete error specification", .{l}), - " ", - ); - - const is_note = if (std.mem.eql(u8, error_or_note_str, "error")) - false - else if (std.mem.eql(u8, error_or_note_str, "note")) - true - else - fatal("line {d}: expeted 'error' or 'note', found '{s}'", .{ l, error_or_note_str }); - - const message = std.mem.trim(u8, it.rest(), " "); - if (message.len == 0) fatal("line {d}: empty error message", .{l}); - - return .{ - .is_note = is_note, - .msg = message, - .src = if (filename.len == 0) null else .{ - .filename = filename, - .line = std.fmt.parseInt(u32, line_str, 10) catch - fatal("line {d}: invalid line number '{s}'", .{ l, line_str }), - .column = std.fmt.parseInt(u32, column_str, 10) catch - fatal("line {d}: invalid column number '{s}'", .{ l, column_str }), - }, - }; -} - -fn rand64(io: Io) u64 { - var x: u64 = undefined; - io.random(@ptrCast(&x)); - return x; -} - -/// Calls `std.process.fatal` on error. The error messages are prefixed with `err_prefix`. -fn parseTargetQueryAndBackend(input_str: []const u8, err_prefix: []const u8) struct { std.Target.Query, Backend } { - const fatal = std.process.fatal; - - const split_idx = std.mem.findScalarLast(u8, input_str, '-') orelse - fatal("{s}target does not include backend", .{err_prefix}); - - const query = input_str[0..split_idx]; - - const backend_str = input_str[split_idx + 1 ..]; - const backend: Backend = std.meta.stringToEnum(Backend, backend_str) orelse - fatal("{s}invalid backend '{s}'", .{ err_prefix, backend_str }); - - const parsed_query = std.Build.parseTargetQuery(.{ - .arch_os_abi = query, - .object_format = switch (backend) { - .sema, .selfhosted, .llvm => null, - .cbe => "c", - }, - }) catch fatal("{s}invalid target query '{s}'", .{ err_prefix, query }); - - return .{ parsed_query, backend }; -} - -fn badUsage(comptime fmt: []const u8, args: anytype) noreturn { - std.log.err(fmt ++ "\n{s}", args ++ .{usage}); - std.process.exit(1); -}