authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-09-17 04:17:54-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-09-26 19:08:50-04:00
log488c15320b6e2848dd61245303e75f7577313ff2
tree6201cb5b097c9b04705335f70f1430353c02f466
parent8a6475c4f79374975be61ac7be13e164956d1a5c

runner: spawn compiler process


9 files changed, 762 insertions(+), 354 deletions(-)

lib/compiler/Maker/Step/Run.zig+21-19
......@@ -231,6 +231,7 @@ pub fn make(
231231 argv_list.appendAssumeCapacity("--listen=-");
232232 },
233233 .protocol => {
234 argv_list.appendAssumeCapacity("--listen=-");
234235 try input_dirs.append(gpa, if (conf_run.cwd.value) |lazy_cwd|
235236 try maker.resolveLazyPathIndex(arena, lazy_cwd, run_index)
236237 else
......@@ -742,7 +743,8 @@ fn waitZigTest(
742743 run_index: Configuration.Step.Index,
743744 maker: *Maker,
744745 child: *process.Child,
745 progress_node: std.Progress.Node,
746 tests_prog_node: std.Progress.Node,
747 test_prog_node: std.Progress.Node,
746748 protocol_args: []const u8,
747749 input_dirs: []const Cache.Path,
748750 man: ?*Cache.Manifest,
......@@ -765,9 +767,6 @@ fn waitZigTest(
765767 const io = graph.io;
766768 const step = maker.stepByIndex(run_index);
767769
768 var sub_prog_node: ?std.Progress.Node = null;
769 defer if (sub_prog_node) |n| n.end();
770
771770 const stdout = multi_reader.reader(0);
772771 const stderr = multi_reader.reader(1);
773772
......@@ -788,7 +787,7 @@ fn waitZigTest(
788787
789788 if (opt_metadata.*) |*md| {
790789 // Previous unit test process died or was killed; we're continuing where it left off
791 requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
790 requestNextTest(&client, md, test_prog_node) catch |err| return .{ .write_failed = err };
792791 } else {
793792 // Running unit tests normally
794793 run.fuzz_tests.clearRetainingCapacity();
......@@ -859,27 +858,27 @@ fn waitZigTest(
859858
860859 const string_bytes = body_r.take(tm_hdr.string_bytes_len) catch unreachable;
861860
862 progress_node.setEstimatedTotalItems(names.len);
861 tests_prog_node.setEstimatedTotalItems(names.len);
863862 opt_metadata.* = .{
864863 .string_bytes = try arena.dupe(u8, string_bytes),
865864 .ns_per_test = try arena.alloc(u64, results.test_count),
866865 .names = names,
867866 .expected_panic_msgs = expected_panic_msgs,
868867 .next_index = 0,
869 .prog_node = progress_node,
870868 };
871869 @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64));
872870
873871 active_test_index = null;
874872 last_update = .now(io, .awake);
875873
876 requestNextTest(&client, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err };
874 requestNextTest(&client, &opt_metadata.*.?, test_prog_node) catch |err| return .{ .write_failed = err };
877875 },
878876 .test_started => {
879877 active_test_index = opt_metadata.*.?.next_index - 1;
880878 last_update = .now(io, .awake);
881879 },
882880 .test_results => {
881 tests_prog_node.completeOne();
883882 const md = &opt_metadata.*.?;
884883
885884 const tr_hdr = body_r.takeStruct(std.zig.Server.Message.TestResults, .little) catch unreachable;
......@@ -924,7 +923,7 @@ fn waitZigTest(
924923 md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds);
925924 last_update = now;
926925
927 requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
926 requestNextTest(&client, md, test_prog_node) catch |err| return .{ .write_failed = err };
928927 },
929928 .discovered_inputs => while (body_r.takeEnum(
930929 std.zig.Server.Message.InputDir,
......@@ -1549,6 +1548,7 @@ fn evalZigTest(
15491548 maker,
15501549 &child,
15511550 progress_node,
1551 spawn_options.progress_node,
15521552 protocol_args,
15531553 input_dirs,
15541554 man,
......@@ -1681,7 +1681,6 @@ const TestMetadata = struct {
16811681 expected_panic_msgs: []const u32,
16821682 string_bytes: []const u8,
16831683 next_index: u32,
1684 prog_node: std.Progress.Node,
16851684
16861685 fn toCachedTestMetadata(tm: TestMetadata) CachedTestMetadata {
16871686 return .{
......@@ -1704,16 +1703,14 @@ pub const CachedTestMetadata = struct {
17041703 }
17051704};
17061705
1707fn requestNextTest(client: *std.zig.Client, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void {
1706fn requestNextTest(client: *std.zig.Client, metadata: *TestMetadata, test_prog_node: std.Progress.Node) !void {
17081707 while (metadata.next_index < metadata.names.len) {
17091708 const i = metadata.next_index;
17101709 metadata.next_index += 1;
17111710
17121711 if (metadata.expected_panic_msgs[i] != 0) continue;
17131712
1714 const name = metadata.testName(i);
1715 if (sub_prog_node.*) |n| n.end();
1716 sub_prog_node.* = metadata.prog_node.start(name, 0);
1713 test_prog_node.setName(metadata.testName(i));
17171714
17181715 try client.serveRunTest(i);
17191716 return;
......@@ -2664,9 +2661,9 @@ fn spawnChildAndCollect(
26642661 .cwd = child_cwd,
26652662 .environ_map = environ_map,
26662663 .request_resource_usage_statistics = true,
2667 .stdin = if (conf_run.stdin.u != .none) s: {
2664 .stdin = if (conf_run.stdin.u != .none) stdin: {
26682665 assert(conf_run.flags.stdio != .inherit);
2669 break :s .pipe;
2666 break :stdin .pipe;
26702667 } else switch (conf_run.flags.stdio) {
26712668 .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore,
26722669 .inherit => .inherit,
......@@ -2691,10 +2688,10 @@ fn spawnChildAndCollect(
26912688
26922689 if (maker.protocol_server != null) {
26932690 if (spawn_options.stdin == .inherit) {
2694 return step.fail(maker, "Cannot inherit stdin when running through over the build system protocol", .{});
2691 return step.fail(maker, "Cannot inherit stdin when running over the build system protocol", .{});
26952692 }
26962693 if (spawn_options.stdout == .inherit) {
2697 return step.fail(maker, "Cannot inherit stdout when running through over the build system protocol", .{});
2694 return step.fail(maker, "Cannot inherit stdout when running over the build system protocol", .{});
26982695 }
26992696 assert(spawn_options.stderr != .inherit);
27002697 }
......@@ -2720,7 +2717,12 @@ fn spawnChildAndCollect(
27202717 step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);
27212718 return try result;
27222719 },
2723 .zig_test, .protocol => {
2720 .zig_test, .protocol => |stdio| {
2721 spawn_options.progress_node = if (conf_run.flags.disable_zig_progress or fuzz_context != null)
2722 .none
2723 else
2724 progress_node.start(@tagName(stdio), 0);
2725 defer spawn_options.progress_node.end();
27242726 try setColorEnvironmentVariables(&conf_run, environ_map, graph.stderr_mode.?);
27252727 const started: Io.Clock.Timestamp = .now(io, .awake);
27262728 const result = evalZigTest(
lib/std/Io.zig-18
......@@ -213,9 +213,7 @@ pub const VTable = struct {
213213 processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void,
214214 processSetCurrentPath: *const fn (?*anyopaque, []const u8) std.process.SetCurrentPathError!void,
215215 processReplace: *const fn (?*anyopaque, std.process.ReplaceOptions) std.process.ReplaceError,
216 processReplacePath: *const fn (?*anyopaque, Dir, std.process.ReplaceOptions) std.process.ReplaceError,
217216 processSpawn: *const fn (?*anyopaque, std.process.SpawnOptions) std.process.SpawnError!std.process.Child,
218 processSpawnPath: *const fn (?*anyopaque, Dir, std.process.SpawnOptions) std.process.SpawnError!std.process.Child,
219217 childWait: *const fn (?*anyopaque, *std.process.Child) std.process.Child.WaitError!std.process.Child.Term,
220218 childKill: *const fn (?*anyopaque, *std.process.Child) void,
221219
......@@ -2797,9 +2795,7 @@ pub const failing: std.Io = .{
27972795 .processSetCurrentDir = failingProcessSetCurrentDir,
27982796 .processSetCurrentPath = failingProcessSetCurrentPath,
27992797 .processReplace = failingProcessReplace,
2800 .processReplacePath = failingProcessReplacePath,
28012798 .processSpawn = failingProcessSpawn,
2802 .processSpawnPath = failingProcessSpawnPath,
28032799 .childWait = unreachableChildWait,
28042800 .childKill = unreachableChildKill,
28052801
......@@ -3451,26 +3447,12 @@ pub fn failingProcessReplace(userdata: ?*anyopaque, options: std.process.Replace
34513447 return error.OperationUnsupported;
34523448}
34533449
3454pub fn failingProcessReplacePath(userdata: ?*anyopaque, dir: Dir, options: std.process.ReplaceOptions) std.process.ReplaceError {
3455 _ = userdata;
3456 _ = dir;
3457 _ = options;
3458 return error.OperationUnsupported;
3459}
3460
34613450pub fn failingProcessSpawn(userdata: ?*anyopaque, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child {
34623451 _ = userdata;
34633452 _ = options;
34643453 return error.OperationUnsupported;
34653454}
34663455
3467pub fn failingProcessSpawnPath(userdata: ?*anyopaque, dir: Dir, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child {
3468 _ = userdata;
3469 _ = dir;
3470 _ = options;
3471 return error.OperationUnsupported;
3472}
3473
34743456pub fn unreachableChildWait(userdata: ?*anyopaque, child: *std.process.Child) std.process.Child.WaitError!std.process.Child.Term {
34753457 _ = userdata;
34763458 _ = child;
lib/std/Io/Threaded.zig+98-68
......@@ -1901,9 +1901,7 @@ pub fn io(t: *Threaded) Io {
19011901 .processSetCurrentDir = processSetCurrentDir,
19021902 .processSetCurrentPath = processSetCurrentPath,
19031903 .processReplace = processReplace,
1904 .processReplacePath = processReplacePath,
19051904 .processSpawn = processSpawn,
1906 .processSpawnPath = processSpawnPath,
19071905 .childWait = childWait,
19081906 .childKill = childKill,
19091907
......@@ -15133,23 +15131,7 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
1513315131 });
1513415132 };
1513515133
15136 return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
15137}
15138
15139fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError {
15140 if (!process.can_replace) return error.OperationUnsupported;
15141 _ = userdata;
15142 _ = dir;
15143 _ = options;
15144 @panic("TODO processReplacePath");
15145}
15146
15147fn processSpawnPath(userdata: ?*anyopaque, dir: Dir, options: process.SpawnOptions) process.SpawnError!process.Child {
15148 if (!process.can_spawn) return error.OperationUnsupported;
15149 _ = userdata;
15150 _ = dir;
15151 _ = options;
15152 @panic("TODO processSpawnPath");
15134 return posixExec(options.exe, argv_buf.ptr, options.expand_arg0, env_block, PATH);
1515315135}
1515415136
1515515137const processSpawn = switch (native_os) {
......@@ -15263,15 +15245,15 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1526315245 if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked;
1526415246 const ep1 = err_pipe[1];
1526515247
15266 // Must happen befor clobbering fds below.
15248 // Must happen before clobbering fds below.
1526715249 switch (options.cwd) {
1526815250 .inherit => {},
1526915251 .dir => |cwd| fchdir(cwd.handle) catch |err| forkBail(ep1, err),
1527015252 .path => |cwd| chdir(cwd) catch |err| forkBail(ep1, err),
1527115253 }
1527215254
15273 for (options.inherit_dirs) |dir| switch (posix.errno(
15274 posix.system.fcntl(dir.handle, posix.F.SETFD, @as(u32, 0)),
15255 for (options.inherit_dirs) |inherit_dir| switch (posix.errno(
15256 posix.system.fcntl(inherit_dir.handle, posix.F.SETFD, @as(u32, 0)),
1527515257 )) {
1527615258 .SUCCESS => {},
1527715259 else => |err| forkBail(ep1, posix.unexpectedErrno(err)),
......@@ -15331,14 +15313,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1533115313 }
1533215314
1533315315 if (options.start_suspended) {
15334 switch (posix.errno(posix.system.kill(0, .STOP))) {
15316 switch (posix.errno(posix.system.kill(posix.system.getpid(), .STOP))) {
1533515317 .SUCCESS => {},
1533615318 .PERM => forkBail(ep1, error.PermissionDenied),
1533715319 else => forkBail(ep1, error.Unexpected),
1533815320 }
1533915321 }
1534015322
15341 const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
15323 const err = posixExec(options.exe, argv_buf.ptr, options.expand_arg0, env_block, PATH);
1534215324 forkBail(ep1, err);
1534315325 }
1534415326
......@@ -17042,61 +17024,109 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c
1704217024 try std.testing.expectEqualStrings(expected_cmd_line, cmd_line);
1704317025}
1704417026
17045fn posixExecv(
17046 arg0_expand: process.ArgExpansion,
17047 file: [*:0]const u8,
17048 child_argv: [*:null]?[*:0]const u8,
17027fn posixExec(
17028 exe: process.ReplaceOptions.Exe,
17029 argv: [*:null]?[*:0]const u8,
17030 expand_arg0: process.ArgExpansion,
1704917031 env_block: process.Environ.PosixBlock,
1705017032 PATH: []const u8,
1705117033) process.ReplaceError {
17052 const file_slice = std.mem.sliceTo(file, 0);
17053 if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, env_block);
17054
17055 // Use of PATH_MAX here is valid as the path_buf will be passed
17056 // directly to the operating system in posixExecvPath.
17057 var path_buf: [posix.PATH_MAX]u8 = undefined;
17058 var it = std.mem.tokenizeScalar(u8, PATH, ':');
17059 var seen_eacces = false;
17060 var err: process.ReplaceError = error.FileNotFound;
17061
17062 // In case of expanding arg0 we must put it back if we return with an error.
17063 const prev_arg0 = child_argv[0];
17064 defer switch (arg0_expand) {
17065 .expand => child_argv[0] = prev_arg0,
17066 .no_expand => {},
17067 };
17034 const arg0_slice = std.mem.span(argv[0].?);
17035 exe: switch (exe) {
17036 .detect => continue :exe if (std.mem.findScalar(u8, arg0_slice, '/')) |_| .{
17037 .path = .cwd(),
17038 } else .search,
17039 .search => {
17040 // Use of PATH_MAX here is valid as the path_buf will be passed
17041 // directly to the operating system in posixExecveat.
17042 var path_buf: [posix.PATH_MAX]u8 = undefined;
17043 var it = std.mem.tokenizeScalar(u8, PATH, ':');
17044 var err: error{ AccessDenied, FileNotFound, NotDir } = error.FileNotFound;
1706817045
17069 while (it.next()) |search_path| {
17070 const path_len = search_path.len + file_slice.len + 1;
17071 if (path_buf.len < path_len + 1) return error.NameTooLong;
17072 @memcpy(path_buf[0..search_path.len], search_path);
17073 path_buf[search_path.len] = '/';
17074 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);
17075 path_buf[path_len] = 0;
17076 const full_path = path_buf[0..path_len :0].ptr;
17077 switch (arg0_expand) {
17078 .expand => child_argv[0] = full_path,
17079 .no_expand => {},
17080 }
17081 err = posixExecvPath(full_path, child_argv, env_block);
17082 switch (err) {
17083 error.AccessDenied => seen_eacces = true,
17084 error.FileNotFound, error.NotDir => {},
17085 else => |e| return e,
17086 }
17046 while (it.next()) |search_path| {
17047 const path_len = search_path.len + arg0_slice.len + 1;
17048 if (path_buf.len < path_len + 1) return error.NameTooLong;
17049 @memcpy(path_buf[0..search_path.len], search_path);
17050 path_buf[search_path.len] = '/';
17051 @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice);
17052 path_buf[path_len] = 0;
17053 const full_path = path_buf[0..path_len :0].ptr;
17054 switch (expand_arg0) {
17055 .expand => argv[0] = full_path,
17056 .no_expand => {},
17057 }
17058 switch (posixExecveat(posix.AT.FDCWD, full_path, argv, env_block)) {
17059 error.AccessDenied, error.FileNotFound, error.NotDir => |e| switch (err) {
17060 error.AccessDenied => {},
17061 error.FileNotFound, error.NotDir => err = e,
17062 },
17063 else => |e| return e,
17064 }
17065 }
17066 return err;
17067 },
17068 .path => |path| return posixExecveat(path.handle, argv[0], argv, env_block),
17069 .file => |file| return posixExecveat(file.handle, null, argv, env_block),
17070 .explicit => |explicit| {
17071 var path_buffer: [posix.PATH_MAX]u8 = undefined;
17072 const path_posix = try pathToPosix(explicit.path, &path_buffer);
17073 return posixExecveat(explicit.dir.handle, path_posix, argv, env_block);
17074 },
1708717075 }
17088 if (seen_eacces) return error.AccessDenied;
17089 return err;
1709017076}
1709117077
1709217078/// This function ignores PATH environment variable.
17093pub fn posixExecvPath(
17094 path: [*:0]const u8,
17095 child_argv: [*:null]const ?[*:0]const u8,
17079pub fn posixExecveat(
17080 dir: posix.fd_t,
17081 path: ?[*:0]const u8,
17082 argv: [*:null]const ?[*:0]const u8,
1709617083 env_block: process.Environ.PosixBlock,
1709717084) process.ReplaceError {
1709817085 try Thread.checkCancel();
17099 switch (posix.errno(posix.system.execve(path, child_argv, env_block.slice.ptr))) {
17086 switch (err: {
17087 if (path) |p| if (dir == posix.AT.FDCWD or Dir.path.isAbsoluteZ(p)) {
17088 break :err posix.errno(posix.system.execve(p, argv, env_block.slice.ptr));
17089 };
17090 if (comptime native_os == .linux and (!builtin.link_libc or
17091 (builtin.target.abi.isGnu() and builtin.target.os.version_range.linux.glibc.order(
17092 .{ .major = 2, .minor = 34, .patch = 0 },
17093 ).compare(.gte))))
17094 {
17095 switch (posix.errno(posix.system.execveat(dir, path orelse "", argv, env_block.slice.ptr, .{
17096 .EMPTY_PATH = path == null,
17097 .SYMLINK_NOFOLLOW = false,
17098 }))) {
17099 else => |err| break :err err,
17100 .NOSYS => {},
17101 }
17102 }
17103 var path_buffer: [posix.PATH_MAX:0]u8 = undefined;
17104 var path_len = realPathPosix(dir, &path_buffer) catch |err| switch (err) {
17105 else => |e| return e,
17106 error.InputOutput,
17107 error.FileTooBig,
17108 error.NoSpaceLeft,
17109 error.PathAlreadyExists,
17110 error.SymLinkLoop,
17111 error.NetworkNotFound,
17112 error.PipeBusy,
17113 error.AntivirusInterference,
17114 => return error.FileNotFound,
17115 error.DeviceBusy,
17116 error.NoDevice,
17117 error.UnrecognizedVolume,
17118 => return error.FileSystem,
17119 };
17120 if (path) |p| {
17121 const path_slice = std.mem.span(p);
17122 path_buffer[path_len] = Dir.path.sep;
17123 path_len += 1;
17124 @memcpy(path_buffer[path_len..][0..path_slice.len], path_slice);
17125 path_len += path_slice.len;
17126 }
17127 path_buffer[path_len] = 0;
17128 break :err posix.errno(posix.system.execve(&path_buffer, argv, env_block.slice.ptr));
17129 }) {
1710017130 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
1710117131 .@"2BIG" => return error.SystemResources,
1710217132 .MFILE => return error.ProcessFdQuotaExceeded,
lib/std/c.zig+6
......@@ -8809,6 +8809,11 @@ pub const O = switch (native_os) {
88098809 else => void,
88108810};
88118811
8812pub const EXECVEAT = switch (native_os) {
8813 .linux => linux.EXECVEAT,
8814 else => void,
8815};
8816
88128817pub const MAP = switch (native_os) {
88138818 .linux => linux.MAP,
88148819 .emscripten => packed struct(u32) {
......@@ -11045,6 +11050,7 @@ pub extern "c" fn renameat(olddirfd: fd_t, old: [*:0]const u8, newdirfd: fd_t, n
1104511050pub extern "c" fn chdir(path: [*:0]const u8) c_int;
1104611051pub extern "c" fn fchdir(fd: fd_t) c_int;
1104711052pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int;
11053pub extern "c" fn execveat(dirfd: fd_t, path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8, flags: EXECVEAT) c_int;
1104811054pub extern "c" fn dup(fd: fd_t) c_int;
1104911055pub extern "c" fn dup2(old_fd: fd_t, new_fd: fd_t) c_int;
1105011056pub extern "c" fn dup3(old: c_int, new: c_int, flags: c_uint) c_int;
lib/std/process.zig+23-29
......@@ -282,37 +282,40 @@ pub const ReplaceError = error{
282282} || Allocator.Error || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
283283
284284pub const ReplaceOptions = struct {
285 exe: Exe = .detect,
285286 argv: []const []const u8,
286287 expand_arg0: ArgExpansion = .no_expand,
287288 /// Replaces the environment when provided. The PATH value from here is
288289 /// never used to resolve `argv[0]`.
289290 environ_map: ?*const Environ.Map = null,
291
292 pub const Exe = union(enum) {
293 /// `argv[0]` is the name of the program to execute. If it is not already a
294 /// file path (i.e. it contains '/'), it is resolved into a file path based on
295 /// PATH from the parent environment.
296 detect,
297 /// `argv[0]` is the name of the program to execute, resolved into a file path
298 /// based on PATH from the parent environment.
299 search,
300 /// `argv[0]` is the file path of the program to execute, relative to `Dir` payload.
301 /// It is *always* treated as a file path, even if it does not contain '/'.
302 path: Dir,
303 /// `File` payload is the program to execute.
304 file: File,
305 /// `path` is the file path of the program to execute, relative to `dir`.
306 /// It is *always* treated as a file path, even if it does not contain '/'.
307 explicit: struct { dir: Dir, path: []const u8 },
308 };
290309};
291310
292311/// Replaces the current process image with the executed process. If this
293312/// function succeeds, it does not return.
294313///
295/// `argv[0]` is the name of the process to replace the current one with. If it
296/// is not already a file path (i.e. it contains '/'), it is resolved into a
297/// file path based on PATH from the parent environment.
298///
299314/// It is illegal to call this function in a fork() child.
300315pub fn replace(io: Io, options: ReplaceOptions) ReplaceError {
301316 return io.vtable.processReplace(io.userdata, options);
302317}
303318
304/// Replaces the current process image with the executed process. If this
305/// function succeeds, it does not return.
306///
307/// `argv[0]` is the file path of the process to replace the current one with,
308/// relative to `dir`. It is *always* treated as a file path, even if it does
309/// not contain '/'.
310///
311/// It is illegal to call this function in a fork() child.
312pub fn replacePath(io: Io, dir: Io.Dir, options: ReplaceOptions) ReplaceError {
313 return io.vtable.processReplacePath(io.userdata, dir, options);
314}
315
316319pub const ArgExpansion = enum { expand, no_expand };
317320
318321/// File name extensions supported natively by `CreateProcess()` on Windows.
......@@ -368,6 +371,7 @@ pub const SpawnError = error{
368371} || Io.File.OpenError || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
369372
370373pub const SpawnOptions = struct {
374 exe: ReplaceOptions.Exe = .detect,
371375 argv: []const []const u8,
372376
373377 /// Set to change the current working directory when spawning the child process.
......@@ -385,7 +389,7 @@ pub const SpawnOptions = struct {
385389 ///
386390 /// The child's progress tree will be grafted into the parent's progress tree,
387391 /// by substituting this node with the child's root node.
388 progress_node: std.Progress.Node = std.Progress.Node.none,
392 progress_node: std.Progress.Node = .none,
389393
390394 stdin: StdIo = .inherit,
391395 stdout: StdIo = .inherit,
......@@ -445,27 +449,16 @@ pub const SpawnOptions = struct {
445449};
446450
447451/// Creates a child process.
448///
449/// `argv[0]` is the name of the program to execute. If it is not already a
450/// file path (i.e. it contains '/'), it is resolved into a file path based on
451/// PATH from the parent environment.
452452pub fn spawn(io: Io, options: SpawnOptions) SpawnError!Child {
453453 return io.vtable.processSpawn(io.userdata, options);
454454}
455455
456/// Creates a child process.
457///
458/// `argv[0]` is the file path of the program to execute, relative to `dir`. It
459/// is *always* treated as a file path, even if it does not contain '/'.
460pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child {
461 return io.vtable.processSpawnPath(io.userdata, dir, options);
462}
463
464456pub const RunError = error{
465457 StreamTooLong,
466458} || SpawnError || Io.File.MultiReader.UnendingError || Io.Timeout.Error;
467459
468460pub const RunOptions = struct {
461 exe: ReplaceOptions.Exe = .detect,
469462 argv: []const []const u8,
470463 stderr_limit: Io.Limit = .unlimited,
471464 stdout_limit: Io.Limit = .unlimited,
......@@ -503,6 +496,7 @@ pub const RunResult = struct {
503496/// If it succeeds, the caller owns result.stdout and result.stderr memory.
504497pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult {
505498 var child = try spawn(io, .{
499 .exe = options.exe,
506500 .argv = options.argv,
507501 .cwd = options.cwd,
508502 .environ_map = options.environ_map,
test/src/runner.zig+613-41
......@@ -1,11 +1,218 @@
11const std = @import("std");
2
3pub fn main(init: std.process.Init) !void {
4 const arena = init.arena.allocator();
5
6 var test_path_arg: ?[]const u8 = null;
7 var zig_path_arg: ?[]const u8 = null;
8 var lib_path_arg: ?[]const u8 = null;
9 var target_args: std.ArrayList([]const u8) = .empty;
10 defer target_args.deinit(init.gpa);
11 var keep_src = false;
12
13 var arg_it = try init.minimal.args.iterateAllocator(arena);
14 const self = arg_it.next().?;
15 while (arg_it.next()) |arg| {
16 if (std.mem.eql(u8, arg, "--listen=-")) {
17 return protocol(init.gpa, init.arena, init.io);
18 } else if (std.mem.eql(u8, arg, "--zig")) {
19 zig_path_arg = arg_it.next() orelse fatal("missing arg after '{s}'", .{arg});
20 } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| {
21 zig_path_arg = zig_path;
22 } else if (std.mem.eql(u8, arg, "--lib")) {
23 lib_path_arg = arg_it.next() orelse fatal("missing arg after '{s}'", .{arg});
24 } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| {
25 lib_path_arg = lib_path;
26 } else if (std.mem.eql(u8, arg, "--target")) {
27 try target_args.append(
28 init.gpa,
29 try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after '{s}'", .{arg})),
30 );
31 } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| {
32 try target_args.append(init.gpa, try arena.dupe(u8, target));
33 } else if (std.mem.eql(u8, arg, "--keep-src")) {
34 keep_src = true;
35 } else {
36 if (test_path_arg) |_| fatal("unknown arg '{s}'", .{arg});
37 test_path_arg = arg;
38 }
39 }
40
41 const test_path = test_path_arg orelse fatal("missing 'path/to/test'", .{});
42 const zig_path = zig_path_arg orelse fatal("missing '--zig=path/to/zig'", .{});
43 const lib_path = lib_path_arg orelse fatal("missing '--lib=path/to/lib'", .{});
44
45 const test_file = try std.Io.Dir.cwd().openFile(init.io, test_path, .{
46 .allow_directory = true,
47 });
48 const zig_exe = try std.Io.Dir.cwd().openFile(init.io, zig_path, .{});
49 defer zig_exe.close(init.io);
50 const lib_dir = try std.Io.Dir.cwd().openDir(init.io, lib_path, .{});
51 defer lib_dir.close(init.io);
52
53 const src_dir_path = "src_" ++ std.fmt.hex(rand_int: {
54 var rand_int: u64 = undefined;
55 init.io.random(@ptrCast(&rand_int));
56 break :rand_int rand_int;
57 });
58 var src_dir = try std.Io.Dir.cwd().createDirPathOpen(init.io, src_dir_path, .{});
59 defer {
60 src_dir.close(init.io);
61 if (!keep_src) std.Io.Dir.cwd().deleteTree(init.io, src_dir_path) catch |err| {
62 std.log.warn("fataled to delete tree '{s}': {t}", .{ src_dir_path, err });
63 };
64 }
65
66 var stdout_buffer: [512]u8 = undefined;
67 var stdout_fw: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer);
68 const stdout = &stdout_fw.interface;
69
70 var child = try std.process.spawn(init.io, .{
71 .argv = &.{ self, "--listen=-" },
72 .stdin = .pipe,
73 .stdout = .pipe,
74 .inherit_dirs = &.{ lib_dir, src_dir },
75 .inherit_files = &.{ zig_exe, test_file },
76 });
77
78 var child_reader_buffer: [512]u8 = undefined;
79 var child_reader = child.stdout.?.readerStreaming(init.io, &child_reader_buffer);
80 var child_writer_buffer: [512]u8 = undefined;
81 var child_writer = child.stdin.?.writerStreaming(init.io, &child_writer_buffer);
82 var client: std.zig.Client = .{
83 .in = &child_reader.interface,
84 .out = &child_writer.interface,
85 };
86 {
87 var args: std.ArrayList(u8) = .empty;
88 defer args.deinit(init.gpa);
89 const Arg = std.zig.Client.Message.Arg;
90
91 try args.append(init.gpa, @backingInt(@as(Arg, switch ((try test_file.stat(init.io)).kind) {
92 else => unreachable,
93 .file => .input_file,
94 .directory => .input_dir,
95 })));
96 try args.appendSlice(init.gpa, @ptrCast(&test_file.handle));
97
98 try args.append(init.gpa, @backingInt(Arg.prefix));
99 try args.appendSlice(init.gpa, "--zig=");
100 try args.append(init.gpa, 0);
101
102 try args.append(init.gpa, @backingInt(Arg.input_file));
103 try args.appendSlice(init.gpa, @ptrCast(&zig_exe.handle));
104
105 try args.append(init.gpa, @backingInt(Arg.prefix));
106 try args.appendSlice(init.gpa, "--lib=");
107 try args.append(init.gpa, 0);
108
109 try args.append(init.gpa, @backingInt(Arg.input_dir));
110 try args.appendSlice(init.gpa, @ptrCast(&lib_dir.handle));
111
112 try args.append(init.gpa, @backingInt(Arg.prefix));
113 try args.appendSlice(init.gpa, "--src=");
114 try args.append(init.gpa, 0);
115
116 try args.append(init.gpa, @backingInt(Arg.output_dir));
117 try args.appendSlice(init.gpa, @ptrCast(&src_dir.handle));
118
119 for (target_args.items) |target_arg| {
120 try args.append(init.gpa, @backingInt(Arg.string));
121 try args.appendSlice(init.gpa, "--target");
122 try args.append(init.gpa, 0);
123
124 try args.append(init.gpa, @backingInt(Arg.string));
125 try args.appendSlice(init.gpa, target_arg);
126 try args.append(init.gpa, 0);
127 }
128
129 try client.serveMessageHeader(.{
130 .tag = .args,
131 .bytes_len = @intCast(args.items.len),
132 });
133 try client.out.writeAll(args.items);
134
135 var metadata: struct {
136 index: u32,
137 len: u32,
138 names: []u32,
139 expected_panic_msgs: []u32,
140 string_bytes: []u8,
141
142 fn name(m: *@This()) []const u8 {
143 return std.mem.sliceTo(m.string_bytes[m.names[m.index - 1]..], 0);
144 }
145
146 fn expectedPanicMsg(m: *@This()) ?[:0]const u8 {
147 return switch (m.expected_panic_msgs[m.index - 1]) {
148 0 => null,
149 else => |offset| m.string_bytes[offset..std.mem.findScalarPos(
150 u8,
151 m.string_bytes,
152 offset,
153 0,
154 ).? :0],
155 };
156 }
157
158 fn next(m: *@This()) ?u32 {
159 if (m.len - m.index == 0) return null;
160 defer m.index += 1;
161 return m.index;
162 }
163 } = .{ .index = 0, .len = 0, .names = &.{}, .expected_panic_msgs = &.{}, .string_bytes = &.{} };
164 try client.serveBodylessMessage(.query_test_metadata);
165 while (true) {
166 const hdr = try client.receiveMessage();
167 switch (hdr.tag) {
168 else => try client.in.discardAll(hdr.bytes_len),
169 .zig_version => {
170 const body = try arena.alloc(u8, hdr.bytes_len);
171 try client.in.readSliceAll(body);
172 if (!std.mem.eql(u8, @import("builtin").zig_version_string, body)) return fatal(
173 "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
174 .{ @import("builtin").zig_version_string, body },
175 );
176 },
177 .test_metadata => {
178 const tm_hdr =
179 try client.in.takeStruct(std.zig.Server.Message.TestMetadata, .little);
180 metadata = .{
181 .index = 0,
182 .len = tm_hdr.tests_len,
183 .names = try arena.alloc(u32, tm_hdr.tests_len),
184 .expected_panic_msgs = try arena.alloc(u32, tm_hdr.tests_len),
185 .string_bytes = try arena.alloc(u8, tm_hdr.string_bytes_len),
186 };
187 try client.in.readSliceEndian(u32, metadata.names, .little);
188 try client.in.readSliceEndian(u32, metadata.expected_panic_msgs, .little);
189 try client.in.readSliceAll(metadata.string_bytes);
190 try client.serveRunTest(metadata.next() orelse break);
191 },
192 .test_results => {
193 const tr = try client.in.takeStruct(std.zig.Server.Message.TestResults, .little);
194 try stdout.print("[{t}] {s}\n", .{ tr.flags.status, metadata.name() });
195 try stdout.flush();
196 try client.serveRunTest(metadata.next() orelse break);
197 },
198 }
199 }
200 try client.serveBodylessMessage(.exit);
201 }
202 _ = try child.wait(init.io);
203}
204fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
205 std.log.err(fmt, args);
206 std.process.exit(1);
207}
208
2209const Args = struct {
3210 @"test": ?struct {
4211 input_dir: std.zig.Server.Message.InputDir,
5212 dir: std.Io.Dir,
6213 },
7214 manifest_file: ?std.Io.File,
8 zig_file: ?std.Io.File,
215 zig_exe: ?std.Io.File,
9216 lib_dir: ?std.Io.Dir,
10217 src_dir: ?std.Io.Dir,
11218 targets: std.ArrayList(u32),
......@@ -23,15 +230,14 @@ const Args = struct {
23230 args.target_bytes.appendAssumeCapacity(0);
24231 }
25232};
26pub fn main(init: std.process.Init) !void {
27 const arena = init.arena.allocator();
233pub fn protocol(gpa: std.mem.Allocator, arena_impl: *std.heap.ArenaAllocator, io: std.Io) !void {
234 const prog_node = std.Progress.start(io, .{});
235 defer prog_node.end();
28236
29237 var stdin_buffer: [512]u8 = undefined;
30238 var stdout_buffer: [512]u8 = undefined;
31 var stderr_buffer: [512]u8 = undefined;
32 var stdin: std.Io.File.Reader = .initStreaming(.stdin(), init.io, &stdin_buffer);
33 var stdout: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer);
34 var stderr: std.Io.File.Writer = .initStreaming(.stderr(), init.io, &stderr_buffer);
239 var stdin: std.Io.File.Reader = .initStreaming(.stdin(), io, &stdin_buffer);
240 var stdout: std.Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buffer);
35241 var server: std.zig.Server = .{
36242 .in = &stdin.interface,
37243 .out = &stdout.interface,
......@@ -40,16 +246,18 @@ pub fn main(init: std.process.Init) !void {
40246 var args: Args = .{
41247 .@"test" = null,
42248 .manifest_file = null,
43 .zig_file = null,
249 .zig_exe = null,
44250 .lib_dir = null,
45251 .src_dir = null,
46252 .targets = .empty,
47253 .target_bytes = .empty,
48254 };
49 defer args.deinit(init.gpa);
255 defer args.deinit(gpa);
50256
51257 try server.serveStringMessage(.zig_version, @import("builtin").zig_version_string);
52258 while (true) {
259 _ = arena_impl.reset(.retain_capacity);
260 const arena = arena_impl.allocator();
53261 const hdr = try server.receiveMessage();
54262 switch (hdr.tag) {
55263 else => {
......@@ -77,12 +285,12 @@ pub fn main(init: std.process.Init) !void {
77285 state = .target;
78286 continue;
79287 } else if (std.mem.cutPrefix(u8, string, "--target=")) |target| {
80 try args.addTarget(init.gpa, target);
288 try args.addTarget(gpa, target);
81289 },
82290 .zig => unreachable,
83291 .lib => unreachable,
84292 .src => unreachable,
85 .target => try args.addTarget(init.gpa, string),
293 .target => try args.addTarget(gpa, string),
86294 }
87295 },
88296 .prefix => {
......@@ -118,7 +326,7 @@ pub fn main(init: std.process.Init) !void {
118326 args.@"test" = .{ .input_dir = input_dir, .dir = dir };
119327 std.debug.assert(args.manifest_file == null);
120328 const manifest_path = "manifest";
121 args.manifest_file = try dir.openFile(init.io, manifest_path, .{});
329 args.manifest_file = try dir.openFile(io, manifest_path, .{});
122330
123331 try server.serveMessageHeader(.{
124332 .tag = .discovered_inputs,
......@@ -157,8 +365,8 @@ pub fn main(init: std.process.Init) !void {
157365 args.manifest_file = file;
158366 },
159367 .zig => {
160 std.debug.assert(args.zig_file == null);
161 args.zig_file = file;
368 std.debug.assert(args.zig_exe == null);
369 args.zig_exe = file;
162370 },
163371 .lib => unreachable,
164372 .src => unreachable,
......@@ -180,16 +388,17 @@ pub fn main(init: std.process.Init) !void {
180388 },
181389 .run_test => {
182390 const test_index = try server.receiveBody_u32();
183 const target =
391 const target_query =
184392 std.mem.sliceTo(args.target_bytes.items[args.targets.items[test_index]..], 0);
185393 try server.serveBodylessMessage(.test_started);
186 const test_result = runTest(init.io, &stderr.interface, &server, &args, target);
187 try stderr.flush();
394 const test_result =
395 runTest(gpa, arena, io, prog_node, &server, &args, target_query);
188396 try server.serveTestResults(.{
189397 .index = test_index,
190398 .flags = .{
191399 .status = if (test_result) .pass else |err| switch (err) {
192400 else => status: {
401 std.debug.print("{t}\n", .{err});
193402 if (@errorReturnTrace()) |trace| std.debug.dumpErrorReturnTrace(trace);
194403 break :status .fail;
195404 },
......@@ -205,18 +414,49 @@ pub fn main(init: std.process.Init) !void {
205414 }
206415}
207416fn runTest(
417 gpa: std.mem.Allocator,
418 arena: std.mem.Allocator,
208419 io: std.Io,
209 stderr: *std.Io.Writer,
420 prog_node: std.Progress.Node,
210421 server: *std.zig.Server,
211422 args: *const Args,
212 target: []const u8,
423 target_string: []const u8,
213424) !void {
214425 const manifest_file = args.manifest_file orelse return error.MissingManifestArg;
215426 const src_dir = args.src_dir orelse return error.MissingSrcDir;
427 const target: Compiler.Target = target: {
428 const backend_split = std.mem.findScalarLast(u8, target_string, '-') orelse
429 return error.TargetMissingBackend;
430 const mode_split =
431 std.mem.findScalarLast(u8, target_string[0..backend_split], '-') orelse
432 return error.TargetMissingMode;
433 const triple = target_string[0..mode_split];
434 const backend = std.meta.stringToEnum(
435 Compiler.Target.Backend,
436 target_string[backend_split + 1 ..],
437 ) orelse return error.TargetMissingBackend;
438 const mode = std.meta.stringToEnum(
439 Compiler.Target.Mode,
440 target_string[mode_split + 1 .. backend_split],
441 ) orelse return error.TargetMissingMode;
442 break :target .{
443 .triple = triple,
444 .resolved = try std.zig.system.resolveTargetQuery(io, try std.Build.parseTargetQuery(.{
445 .arch_os_abi = triple,
446 .object_format = switch (backend) {
447 .sema, .selfhosted, .llvm => null,
448 .cbe => "c",
449 },
450 })),
451 .mode = mode,
452 .backend = backend,
453 };
454 };
216455 var manifest_buffer: [512]u8 = undefined;
217456 var manifest_fr = manifest_file.reader(io, &manifest_buffer);
218457 var allow_skip = true;
219458 var skip_delimiter = false;
459 var compiler: ?Compiler = null;
220460 var update_mtime = std.Io.Clock.real.now(io);
221461 var update_num: usize = 0;
222462 while (manifest_fr.interface.takeSentinel('\n')) |untrimmed_line| {
......@@ -229,12 +469,13 @@ fn runTest(
229469 continue;
230470 }
231471 const cmd = std.meta.stringToEnum(
232 enum { skip, obj, exe, lib, write, delete, update, check },
472 enum { skip, exe, lib, obj, write, delete, update, check },
233473 cmd_str,
234474 ) orelse return error.UnknownCommand;
235475 var maybe_arg = line_it.next();
236476 var contents_file: ?std.Io.File = null;
237477 defer if (contents_file) |file| file.close(io);
478 var contents_buffer: [512]u8 = undefined;
238479 var contents_impl: union {
239480 dr: DelimitedReader,
240481 fr: std.Io.File.Reader,
......@@ -253,7 +494,7 @@ fn runTest(
253494 break :contents_path contents_path;
254495 };
255496 if (std.mem.eql(u8, contents_path, "{")) {
256 contents_impl = .{ .dr = .init(&manifest_fr.interface, "#}", &.{}) };
497 contents_impl = .{ .dr = .init(&manifest_fr.interface, "#}", &contents_buffer) };
257498 skip_delimiter = true;
258499 break :contents_r &contents_impl.dr.interface;
259500 }
......@@ -270,43 +511,263 @@ fn runTest(
270511 try server.out.flush();
271512
272513 contents_file = try @"test".dir.openFile(io, contents_path, .{});
273 contents_impl = .{ .fr = contents_file.?.reader(io, &.{}) };
514 contents_impl = .{ .fr = contents_file.?.reader(io, &contents_buffer) };
274515 break :contents_r &contents_impl.fr.interface;
275516 },
276517 };
277 switch (cmd) {
278 else => {
279 try stderr.print("{t} {s} {{\n", .{ cmd, maybe_arg orelse "" });
280 _ = try contents_r.streamRemaining(stderr);
281 try stderr.writeAll("\n}\n");
282 },
518 const cmd_prog_node = cmd_prog_node: {
519 var cmd_name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
520 break :cmd_prog_node prog_node.start(std.mem.print(&cmd_name_buffer, "#{t} {s}", .{
521 cmd, maybe_arg orelse "",
522 }) catch &cmd_name_buffer, 0);
523 };
524 defer cmd_prog_node.end();
525 cmd: switch (cmd) {
283526 .skip => if (!allow_skip)
284527 return error.NonInitialSkipCommand // #skip must appear first
285 else if (std.mem.eql(u8, target, maybe_arg orelse return error.MissingArg))
528 else if (std.mem.eql(u8, target_string, maybe_arg orelse return error.MissingArg))
286529 return error.SkipZigTest,
530 .exe, .lib, .obj => {
531 if (compiler) |*comp| {
532 try comp.exit(io);
533 compiler = null;
534 }
535 var argv: std.ArrayList([]const u8) = .empty;
536 defer argv.deinit(gpa);
537 try argv.appendSlice(gpa, &.{
538 "zig",
539 switch (cmd) {
540 else => unreachable,
541 .obj => "build-obj",
542 .exe => "build-exe",
543 .lib => "build-lib",
544 },
545 "-target",
546 target.triple,
547 });
548 switch (target.mode) {
549 .whole => {},
550 .incremental => try argv.append(gpa, "-fincremental"),
551 }
552 switch (target.backend) {
553 .sema => try argv.append(gpa, "-fno-emit-bin"),
554 .selfhosted => try argv.append(gpa, "-fno-llvm"),
555 .llvm => try argv.append(gpa, "-fllvm"),
556 .cbe => try argv.append(gpa, "-ofmt=c"),
557 }
558 try argv.append(gpa, "--listen=-");
559 var need: usize = 1;
560 var root_name: ?[]const u8 = null;
561 while (contents_r.peekGreedy(need)) |buffered| {
562 const start = std.mem.findNone(u8, buffered, "\n ") orelse buffered.len;
563 if (std.mem.findAnyPos(u8, buffered, start, "\n ")) |end| {
564 const arg = try arena.dupe(u8, buffered[start..end]);
565 if (std.mem.cutPrefix(u8, arg, "-M")) |module| {
566 var module_name_it = std.mem.splitScalar(u8, module, '=');
567 const module_name = module_name_it.next().?;
568 root_name = root_name orelse module_name;
569 }
570 try argv.append(gpa, arg);
571 contents_r.toss(end);
572 need = 1;
573 } else {
574 contents_r.toss(start);
575 need = buffered.len - start + 1;
576 }
577 } else |err| switch (err) {
578 else => |e| return e,
579 error.EndOfStream => {},
580 }
581 const compile_prog_node = prog_node.start("initial update", 0);
582 const child = try std.process.spawn(io, .{
583 .exe = .{ .file = args.zig_exe orelse return error.MissingZigExe },
584 .argv = argv.items,
585 .cwd = .{ .dir = src_dir },
586 .progress_node = compile_prog_node,
587 .stdin = .pipe,
588 .stdout = .pipe,
589 .stderr = .pipe,
590 });
591 compiler = .{
592 .prog_node = compile_prog_node,
593 .allow_compiler_stderr = true,
594 .output_mode = switch (cmd) {
595 else => unreachable,
596 .exe => .Exe,
597 .lib => .Lib,
598 .obj => .Obj,
599 },
600 .root_name = root_name orelse return error.MissingRootModule,
601 .state = .idle,
602 .child = child,
603 .fw = undefined,
604 .mr_buffer = undefined,
605 .mr = undefined,
606 .client = undefined,
607 };
608 const comp = &compiler.?;
609 comp.fw = child.stdin.?.writerStreaming(io, &.{});
610 comp.mr.init(gpa, io, comp.mr_buffer.toStreams(), &.{
611 child.stdout.?, child.stderr.?,
612 });
613 comp.client = .{ .in = comp.mr.reader(0), .out = &comp.fw.interface };
614 },
287615 .write => {
288 const file = try src_dir.createFile(io, maybe_arg orelse return error.MissingArg, .{});
616 const file =
617 try src_dir.createFile(io, maybe_arg orelse return error.MissingFileArg, .{});
289618 defer file.close(io);
290619 var fw = file.writer(io, &.{});
291620 _ = try contents_r.streamRemaining(&fw.interface);
292621 try file.setTimestamps(io, .{ .modify_timestamp = .{ .new = update_mtime } });
293622 },
294 .delete => try src_dir.deleteFile(io, maybe_arg orelse return error.MissingArg),
623 .delete => try src_dir.deleteFile(io, maybe_arg orelse return error.MissingFileArg),
295624 .update => {
296 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
297 const name = switch (try contents_r.readSliceShort(&name_buffer)) {
298 0 => switch (update_num) {
299 0 => "initial update",
300 else => |num| std.mem.print(&name_buffer, "update {d}", .{num}) catch
301 &name_buffer,
302 },
303 else => |name_len| name_buffer[0..name_len],
304 };
625 const comp = &(compiler orelse return error.MissingCompiler);
626 name: {
627 var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined;
628 comp.prog_node.setName(switch (try contents_r.readSliceShort(&name_buffer)) {
629 0 => switch (update_num) {
630 0 => break :name,
631 else => |num| std.mem.print(&name_buffer, "update {d}", .{num}) catch
632 &name_buffer,
633 },
634 else => |name_len| name_buffer[0..name_len],
635 });
636 }
305637 _ = try contents_r.discardRemaining();
306 try stderr.print("update({q})\n", .{name});
638 if (comp.state != .idle) return error.MissingCheck;
639 try comp.client.serveBodylessMessage(.update);
640 comp.state = .update;
307641 update_mtime = update_mtime.addDuration(.fromSeconds(2));
308642 update_num += 1;
309643 },
644 .check => {
645 const expected = try contents_r.allocRemaining(gpa, .unlimited);
646 defer gpa.free(expected);
647 const check = std.meta.stringToEnum(
648 enum { errors, stdout, exit, lldb },
649 maybe_arg orelse return error.MissingCheckArg,
650 ) orelse return error.UnknownCheck;
651
652 const comp = &(compiler orelse return error.MissingCompiler);
653 if (comp.state != .update) return error.MissingUpdate;
654 const stderr = comp.mr.reader(1);
655 while (true) {
656 const header = comp.client.receiveMessageWithMultiReader(
657 &comp.mr,
658 .none,
659 ) catch |err| switch (err) {
660 error.Timeout => unreachable,
661 error.EndOfStream => break,
662 else => |e| return e,
663 };
664 const body = comp.client.in.take(header.bytes_len) catch unreachable;
665 switch (header.tag) {
666 .error_bundle => {
667 const error_bundle = try std.zig.Server.allocErrorBundle(arena, body);
668 if (stderr.bufferedLen() > 0) {
669 if (comp.allow_compiler_stderr) {
670 std.log.info("error_bundle stderr:\n{s}", .{stderr.buffered()});
671 } else {
672 fatal("error_bundle unexpected stderr:\n{s}", .{stderr.buffered()});
673 }
674 stderr.tossBuffered();
675 }
676 switch (check) {
677 .errors => if (error_bundle.errorMessageCount() != 0) {
678 @panic("TODO");
679 } else fatal("expected compile errors", .{}),
680 .stdout, .exit, .lldb => if (error_bundle.errorMessageCount() != 0) {
681 try error_bundle.renderToStderr(io, .{}, .auto);
682 fatal("unexpected compile errors", .{});
683 },
684 }
685 comp.state = .idle;
686 break :cmd;
687 },
688 .emit_digest => {
689 var r: std.Io.Reader = .fixed(body);
690 _ = r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch
691 unreachable;
692
693 if (stderr.bufferedLen() > 0) {
694 if (comp.allow_compiler_stderr) {
695 std.log.info("emit_digest stderr:\n{s}", .{stderr.buffered()});
696 } else {
697 fatal("emit_digest unexpected stderr:\n{s}", .{stderr.buffered()});
698 }
699 stderr.tossBuffered();
700 }
701 switch (target.backend) {
702 .sema => continue,
703 .selfhosted, .llvm, .cbe => {},
704 }
705 switch (check) {
706 .errors => continue,
707 .stdout, .exit, .lldb => {},
708 }
709 const digest = r.takeArray(std.Build.Cache.bin_digest_len) catch
710 unreachable;
711 const result_dir = ".zig-cache" ++ std.Io.Dir.path.sep_str ++
712 "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*);
713 const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{
714 .root_name = comp.root_name,
715 .cpu_arch = target.resolved.cpu.arch,
716 .os_tag = target.resolved.os.tag,
717 .ofmt = target.resolved.ofmt,
718 .abi = target.resolved.abi,
719 .output_mode = comp.output_mode,
720 });
721 const bin_path =
722 try std.Io.Dir.path.join(arena, &.{ result_dir, bin_name });
723 const bin_file = try std.Io.Dir.cwd().openFile(io, bin_path, .{});
724 defer bin_file.close(io);
725 switch (check) {
726 .errors => unreachable,
727 .stdout, .exit => {
728 const result = std.process.run(arena, io, .{
729 .exe = .{ .file = bin_file },
730 .argv = &.{bin_path},
731 }) catch continue;
732 switch (result.term) {
733 .exited => |code| switch (check) {
734 .errors, .lldb => unreachable,
735 .stdout => {
736 if (code != 0) fatal("generated executable '{s}' failed with code {d}", .{ bin_path, code });
737 try std.testing.expectEqualStrings(expected, result.stdout);
738 },
739 .exit => {
740 var actual_code_buffer: [std.fmt.count("{d}", .{std.math.maxInt(u8)})]u8 = undefined;
741 try std.testing.expectEqualStrings(
742 expected,
743 std.mem.print(&actual_code_buffer, "{d}", .{code}) catch unreachable,
744 );
745 },
746 },
747 .signal => |sig| fatal("generated executable '{s}' terminated with signal {t}", .{ bin_path, sig }),
748 .stopped => |sig| fatal("generated executable '{s}' stopped with signal {t}", .{ bin_path, sig }),
749 .unknown => fatal("generated executable '{s}' terminated unexpectedly", .{bin_path}),
750 }
751 },
752 .lldb => {},
753 }
754 },
755 else => {}, // Ignore other messages,
756 }
757 }
758
759 const buffered_stderr = stderr.buffered();
760 if (buffered_stderr.len > 0) {
761 if (comp.allow_compiler_stderr) {
762 std.log.info("stderr:\n{s}", .{buffered_stderr});
763 } else {
764 fatal("unexpected stderr:\n{s}", .{buffered_stderr});
765 }
766 }
767
768 try comp.exit(io);
769 fatal("compiler failed to send terminating error_bundle", .{});
770 },
310771 }
311772 std.debug.assert(try contents_r.discardRemaining() == 0);
312773 if (cmd != .skip) allow_skip = false;
......@@ -314,6 +775,7 @@ fn runTest(
314775 else => |e| return e,
315776 error.EndOfStream => if (skip_delimiter) return error.MissingDelimiter,
316777 }
778 if (compiler) |*comp| try comp.exit(io);
317779}
318780const DelimitedReader = struct {
319781 const Io = std.Io;
......@@ -360,3 +822,113 @@ const DelimitedReader = struct {
360822 return n;
361823 }
362824};
825const Compiler = struct {
826 prog_node: std.Progress.Node,
827 allow_compiler_stderr: bool,
828 output_mode: std.lang.OutputMode,
829 root_name: []const u8,
830 state: enum { idle, update },
831 child: std.process.Child,
832 fw: std.Io.File.Writer,
833 mr_buffer: std.Io.File.MultiReader.Buffer(2),
834 mr: std.Io.File.MultiReader,
835 client: std.zig.Client,
836
837 const Target = struct {
838 triple: []const u8,
839 resolved: std.Target,
840 mode: Mode,
841 backend: Backend,
842
843 const Mode = enum {
844 whole,
845 incremental,
846 };
847
848 const Backend = enum {
849 /// Run semantic analysis only. Runtime output will not be tested, but we still verify
850 /// that compilation succeeds. Corresponds to `-fno-emit-bin`.
851 sema,
852 /// Use the self-hosted code generation backend for this target.
853 /// Corresponds to `-fno-llvm -fno-lld`.
854 selfhosted,
855 /// Use the LLVM backend.
856 /// Corresponds to `-fllvm -flld`.
857 llvm,
858 /// Use the C backend. The output is compiled with `zig cc`.
859 /// Corresponds to `-ofmt=c`.
860 cbe,
861 };
862 };
863
864 fn update(comp: *Compiler, io: std.Io) !void {
865 comp.client.serveBodylessMessage(.update) catch |err| switch (err) {
866 error.WriteFailed => return comp.fw.err.?,
867 };
868 while (true) {
869 const header = comp.client.receiveMessageWithMultiReader(
870 &comp.mr,
871 .none,
872 ) catch |err| switch (err) {
873 error.Timeout => unreachable,
874 error.EndOfStream => break,
875 else => |e| return e,
876 };
877 const body = comp.client.in.take(header.bytes_len) catch unreachable;
878 switch (header.tag) {
879 .error_bundle => {
880 return;
881 },
882 .emit_digest => {
883 _ = body;
884 },
885 else => {}, // Ignore other messages,
886 }
887 }
888
889 const buffered_stderr = comp.mr.reader(1).buffered();
890 if (buffered_stderr.len > 0) {
891 if (comp.allow_compiler_stderr) {
892 std.log.info("stderr:\n{s}", .{buffered_stderr});
893 } else {
894 fatal("unexpected stderr:\n{s}", .{buffered_stderr});
895 }
896 }
897
898 try comp.exit(io);
899 fatal("compiler failed to send terminating error_bundle", .{});
900 }
901
902 fn exit(comp: *Compiler, io: std.Io) !void {
903 comp.client.serveBodylessMessage(.exit) catch |err| switch (err) {
904 error.WriteFailed => switch (comp.fw.err.?) {
905 error.BrokenPipe => {},
906 else => |e| fatal("failed to send exit: {t}", .{e}),
907 },
908 };
909 comp.child.stdin.?.close(io);
910 comp.child.stdin = null;
911 while (true) {
912 const header = comp.client.receiveMessageWithMultiReader(
913 &comp.mr,
914 .none,
915 ) catch |err| switch (err) {
916 error.Timeout => unreachable,
917 error.EndOfStream => |e| {
918 if (comp.client.in.bufferedLen() == 0) break;
919 return e;
920 },
921 else => |e| return e,
922 };
923 try comp.client.in.discardAll(header.bytes_len);
924 }
925 const term = comp.child.wait(io) catch |err| fatal("child process failed: {t}", .{err});
926 comp.prog_node.end();
927 switch (term) {
928 .exited => |code| if (code != 0) fatal("compiler failed with code {d}", .{code}),
929 .signal => |sig| fatal("compiler terminated with signal {t}", .{sig}),
930 .stopped => |sig| fatal("compiler stopped unexpectedly with signal {t}", .{sig}),
931 .unknown => fatal("compiler terminated unexpectedly", .{}),
932 }
933 }
934};
test/standalone/build.zig-1
......@@ -44,7 +44,6 @@ pub fn build(b: *std.Build) void {
4444 "../../tools/generate_linux_syscalls.zig",
4545 "../../tools/process_headers.zig",
4646 "../../tools/migrate_langref.zig",
47 "../../tools/runner-check.zig",
4847 "../../tools/unskip_spirv_tests.zig",
4948 "../../tools/update-linux-headers.zig",
5049 "../../tools/update_clang_options.zig",
test/tests.zig+1-1
......@@ -3488,7 +3488,7 @@ pub fn addNewIncrementalTests(
34883488
34893489 if (options.skip_llvm and test_target.backend == .llvm) continue;
34903490
3491 const target_str = b.fmt("{s}-{t}", .{
3491 const target_str = b.fmt("{s}-incremental-{t}", .{
34923492 resolved_target.query.zigTriple(b.allocator) catch @panic("OOM"),
34933493 test_target.backend,
34943494 });
tools/runner-check.zig deleted-177
......@@ -1,177 +0,0 @@
1const std = @import("std");
2pub fn main(init: std.process.Init) !void {
3 const arena = init.arena.allocator();
4
5 var test_path_arg: ?[]const u8 = null;
6 var zig_path_arg: ?[]const u8 = null;
7 var lib_path_arg: ?[]const u8 = null;
8 var target_args: std.ArrayList([]const u8) = .empty;
9 defer target_args.deinit(init.gpa);
10 var keep_src = false;
11
12 var arg_it = try init.minimal.args.iterateAllocator(arena);
13 std.debug.assert(arg_it.skip());
14 while (arg_it.next()) |arg| {
15 if (std.mem.eql(u8, arg, "--zig")) {
16 zig_path_arg = arg_it.next() orelse fail("missing arg after '{s}'", .{arg});
17 } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| {
18 zig_path_arg = zig_path;
19 } else if (std.mem.eql(u8, arg, "--lib")) {
20 lib_path_arg = arg_it.next() orelse fail("missing arg after '{s}'", .{arg});
21 } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| {
22 lib_path_arg = lib_path;
23 } else if (std.mem.eql(u8, arg, "--target")) {
24 try target_args.append(
25 init.gpa,
26 try arena.dupe(u8, arg_it.next() orelse fail("missing arg after '{s}'", .{arg})),
27 );
28 } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| {
29 try target_args.append(init.gpa, try arena.dupe(u8, target));
30 } else if (std.mem.eql(u8, arg, "--keep-src")) {
31 keep_src = true;
32 } else {
33 if (test_path_arg) |_| fail("unknown arg '{s}'", .{arg});
34 test_path_arg = arg;
35 }
36 }
37
38 const test_path = test_path_arg orelse fail("missing 'path/to/test'", .{});
39 const zig_path = zig_path_arg orelse fail("missing '--zig=path/to/zig'", .{});
40 const lib_path = lib_path_arg orelse fail("missing '--lib=path/to/lib'", .{});
41
42 const test_file = try std.Io.Dir.cwd().openFile(init.io, test_path, .{
43 .allow_directory = true,
44 });
45 const zig_file = try std.Io.Dir.cwd().openFile(init.io, zig_path, .{});
46 defer zig_file.close(init.io);
47 const lib_dir = try std.Io.Dir.cwd().openDir(init.io, lib_path, .{});
48 defer lib_dir.close(init.io);
49
50 const src_dir_path = "src_" ++ std.fmt.hex(rand_int: {
51 var rand_int: u64 = undefined;
52 init.io.random(@ptrCast(&rand_int));
53 break :rand_int rand_int;
54 });
55 var src_dir = try std.Io.Dir.cwd().createDirPathOpen(init.io, src_dir_path, .{});
56 defer {
57 src_dir.close(init.io);
58 if (!keep_src) std.Io.Dir.cwd().deleteTree(init.io, src_dir_path) catch |err| {
59 std.log.warn("failed to delete tree '{s}': {t}", .{ src_dir_path, err });
60 };
61 }
62
63 var child = try std.process.spawn(init.io, .{
64 .argv = &.{
65 zig_path,
66 "run",
67 try std.fs.path.resolveAlloc(arena, &.{ lib_path, "..", "test", "src", "runner.zig" }),
68 },
69 .stdin = .pipe,
70 .stdout = .pipe,
71 .inherit_dirs = &.{ lib_dir, src_dir },
72 .inherit_files = &.{ zig_file, test_file },
73 });
74
75 var stdout_buffer: [512]u8 = undefined;
76 var stdout_reader = child.stdout.?.readerStreaming(init.io, &stdout_buffer);
77 var stdin_buffer: [512]u8 = undefined;
78 var stdin_writer = child.stdin.?.writerStreaming(init.io, &stdin_buffer);
79 var client: std.zig.Client = .{
80 .in = &stdout_reader.interface,
81 .out = &stdin_writer.interface,
82 };
83 {
84 var args: std.ArrayList(u8) = .empty;
85 defer args.deinit(init.gpa);
86 const Arg = std.zig.Client.Message.Arg;
87
88 try args.append(init.gpa, @backingInt(@as(Arg, switch ((try test_file.stat(init.io)).kind) {
89 else => unreachable,
90 .file => .input_file,
91 .directory => .input_dir,
92 })));
93 try args.appendSlice(init.gpa, @ptrCast(&test_file.handle));
94
95 try args.append(init.gpa, @backingInt(Arg.prefix));
96 try args.appendSlice(init.gpa, "--zig=");
97 try args.append(init.gpa, 0);
98
99 try args.append(init.gpa, @backingInt(Arg.input_file));
100 try args.appendSlice(init.gpa, @ptrCast(&zig_file.handle));
101
102 try args.append(init.gpa, @backingInt(Arg.prefix));
103 try args.appendSlice(init.gpa, "--lib=");
104 try args.append(init.gpa, 0);
105
106 try args.append(init.gpa, @backingInt(Arg.input_dir));
107 try args.appendSlice(init.gpa, @ptrCast(&lib_dir.handle));
108
109 try args.append(init.gpa, @backingInt(Arg.prefix));
110 try args.appendSlice(init.gpa, "--src=");
111 try args.append(init.gpa, 0);
112
113 try args.append(init.gpa, @backingInt(Arg.output_dir));
114 try args.appendSlice(init.gpa, @ptrCast(&src_dir.handle));
115
116 for (target_args.items) |target_arg| {
117 try args.append(init.gpa, @backingInt(Arg.string));
118 try args.appendSlice(init.gpa, "--target");
119 try args.append(init.gpa, 0);
120
121 try args.append(init.gpa, @backingInt(Arg.string));
122 try args.appendSlice(init.gpa, target_arg);
123 try args.append(init.gpa, 0);
124 }
125
126 try client.serveMessageHeader(.{
127 .tag = .args,
128 .bytes_len = @intCast(args.items.len),
129 });
130 try client.out.writeAll(args.items);
131
132 var metadata: struct {
133 index: u32,
134 len: u32,
135
136 fn next(m: *@This()) ?u32 {
137 if (m.len - m.index == 0) return null;
138 defer m.index += 1;
139 return m.index;
140 }
141 } = .{ .index = 0, .len = 0 };
142 try client.serveBodylessMessage(.query_test_metadata);
143 while (true) {
144 const hdr = try client.receiveMessage();
145 switch (hdr.tag) {
146 else => try client.in.discardAll(hdr.bytes_len),
147 .zig_version => {
148 const body = try arena.alloc(u8, hdr.bytes_len);
149 try client.in.readSliceAll(body);
150 if (!std.mem.eql(u8, @import("builtin").zig_version_string, body)) return fail(
151 "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
152 .{ @import("builtin").zig_version_string, body },
153 );
154 },
155 .test_metadata => {
156 const tm_hdr =
157 try client.in.takeStruct(std.zig.Server.Message.TestMetadata, .little);
158 try client.in.discardAll(
159 hdr.bytes_len - @sizeOf(std.zig.Server.Message.TestMetadata),
160 );
161 metadata = .{ .index = 0, .len = tm_hdr.tests_len };
162 try client.serveRunTest(metadata.next() orelse break);
163 },
164 .test_results => {
165 try client.in.discardAll(hdr.bytes_len);
166 try client.serveRunTest(metadata.next() orelse break);
167 },
168 }
169 }
170 try client.serveBodylessMessage(.exit);
171 }
172 _ = try child.wait(init.io);
173}
174fn fail(comptime fmt: []const u8, args: anytype) noreturn {
175 std.log.err(fmt, args);
176 std.process.exit(1);
177}