authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-05-28 19:27:14-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-05-28 19:27:14-04:00
log963ffe9d572e6da4ef22672af9b7c54150f66b27
tree950c39722d71cdd6f2af75c255ee92a318cda516
parent759c2211c2eba44cccf0608267bf1a05934ad8a1
parent3a3d2187f986066859cfb793fb7ee1cae4dfea08
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #20059 from ziglang/progress

rework std.Progress

62 files changed, 1758 insertions(+), 819 deletions(-)

lib/compiler/aro/aro/Diagnostics.zig+2-2
......@@ -528,7 +528,7 @@ const MsgWriter = struct {
528528 config: std.io.tty.Config,
529529
530530 fn init(config: std.io.tty.Config) MsgWriter {
531 std.debug.getStderrMutex().lock();
531 std.debug.lockStdErr();
532532 return .{
533533 .w = std.io.bufferedWriter(std.io.getStdErr().writer()),
534534 .config = config,
......@@ -537,7 +537,7 @@ const MsgWriter = struct {
537537
538538 pub fn deinit(m: *MsgWriter) void {
539539 m.w.flush() catch {};
540 std.debug.getStderrMutex().unlock();
540 std.debug.unlockStdErr();
541541 }
542542
543543 pub fn print(m: *MsgWriter, comptime fmt: []const u8, args: anytype) void {
lib/compiler/build_runner.zig+13-13
......@@ -289,13 +289,14 @@ pub fn main() !void {
289289 .windows_api => {},
290290 }
291291
292 var progress: std.Progress = .{ .dont_print_on_dumb = true };
293 const main_progress_node = progress.start("", 0);
292 const main_progress_node = std.Progress.start(.{
293 .disable_printing = (color == .off),
294 });
294295
295296 builder.debug_log_scopes = debug_log_scopes.items;
296297 builder.resolveInstallPrefix(install_prefix, dir_list);
297298 {
298 var prog_node = main_progress_node.start("user build.zig logic", 0);
299 var prog_node = main_progress_node.start("Configure", 0);
299300 defer prog_node.end();
300301 try builder.runBuild(root);
301302 }
......@@ -385,7 +386,7 @@ fn runStepNames(
385386 arena: std.mem.Allocator,
386387 b: *std.Build,
387388 step_names: []const []const u8,
388 parent_prog_node: *std.Progress.Node,
389 parent_prog_node: std.Progress.Node,
389390 thread_pool_options: std.Thread.Pool.Options,
390391 run: *Run,
391392 seed: u32,
......@@ -452,7 +453,7 @@ fn runStepNames(
452453 {
453454 defer parent_prog_node.end();
454455
455 var step_prog = parent_prog_node.start("steps", step_stack.count());
456 const step_prog = parent_prog_node.start("steps", step_stack.count());
456457 defer step_prog.end();
457458
458459 var wait_group: std.Thread.WaitGroup = .{};
......@@ -467,7 +468,7 @@ fn runStepNames(
467468 if (step.state == .skipped_oom) continue;
468469
469470 thread_pool.spawnWg(&wait_group, workerMakeOneStep, .{
470 &wait_group, &thread_pool, b, step, &step_prog, run,
471 &wait_group, &thread_pool, b, step, step_prog, run,
471472 });
472473 }
473474 }
......@@ -891,7 +892,7 @@ fn workerMakeOneStep(
891892 thread_pool: *std.Thread.Pool,
892893 b: *std.Build,
893894 s: *Step,
894 prog_node: *std.Progress.Node,
895 prog_node: std.Progress.Node,
895896 run: *Run,
896897) void {
897898 // First, check the conditions for running this step. If they are not met,
......@@ -941,11 +942,10 @@ fn workerMakeOneStep(
941942 }
942943 }
943944
944 var sub_prog_node = prog_node.start(s.name, 0);
945 sub_prog_node.activate();
945 const sub_prog_node = prog_node.start(s.name, 0);
946946 defer sub_prog_node.end();
947947
948 const make_result = s.make(&sub_prog_node);
948 const make_result = s.make(sub_prog_node);
949949
950950 // No matter the result, we want to display error/warning messages.
951951 const show_compile_errors = !run.prominent_compile_errors and
......@@ -954,8 +954,8 @@ fn workerMakeOneStep(
954954 const show_stderr = s.result_stderr.len > 0;
955955
956956 if (show_error_msgs or show_compile_errors or show_stderr) {
957 sub_prog_node.context.lock_stderr();
958 defer sub_prog_node.context.unlock_stderr();
957 std.debug.lockStdErr();
958 defer std.debug.unlockStdErr();
959959
960960 printErrorMessages(b, s, run) catch {};
961961 }
......@@ -1225,7 +1225,7 @@ fn cleanExit() void {
12251225 process.exit(0);
12261226}
12271227
1228const Color = enum { auto, off, on };
1228const Color = std.zig.Color;
12291229const Summary = enum { all, new, failures, none };
12301230
12311231fn get_tty_conf(color: Color, stderr: File) std.io.tty.Config {
lib/compiler/resinator/cli.zig+2-2
......@@ -108,8 +108,8 @@ pub const Diagnostics = struct {
108108 }
109109
110110 pub fn renderToStdErr(self: *Diagnostics, args: []const []const u8, config: std.io.tty.Config) void {
111 std.debug.getStderrMutex().lock();
112 defer std.debug.getStderrMutex().unlock();
111 std.debug.lockStdErr();
112 defer std.debug.unlockStdErr();
113113 const stderr = std.io.getStdErr().writer();
114114 self.renderToWriter(args, stderr, config) catch return;
115115 }
lib/compiler/resinator/errors.zig+2-2
......@@ -60,8 +60,8 @@ pub const Diagnostics = struct {
6060 }
6161
6262 pub fn renderToStdErr(self: *Diagnostics, cwd: std.fs.Dir, source: []const u8, tty_config: std.io.tty.Config, source_mappings: ?SourceMappings) void {
63 std.debug.getStderrMutex().lock();
64 defer std.debug.getStderrMutex().unlock();
63 std.debug.lockStdErr();
64 defer std.debug.unlockStdErr();
6565 const stderr = std.io.getStdErr().writer();
6666 for (self.errors.items) |err_details| {
6767 renderErrorMessage(self.allocator, stderr, tty_config, cwd, err_details, source, self.strings.items, source_mappings) catch return;
lib/compiler/resinator/main.zig-6
......@@ -50,12 +50,6 @@ pub fn main() !void {
5050 },
5151 };
5252
53 if (zig_integration) {
54 // Send progress with a special string to indicate that the building of the
55 // resinator binary is finished and we've moved on to actually compiling the .rc file
56 try error_handler.server.serveStringMessage(.progress, "<resinator>");
57 }
58
5953 var options = options: {
6054 var cli_diagnostics = cli.Diagnostics.init(allocator);
6155 defer cli_diagnostics.deinit();
lib/compiler/test_runner.zig+19-12
......@@ -129,12 +129,11 @@ fn mainTerminal() void {
129129 var ok_count: usize = 0;
130130 var skip_count: usize = 0;
131131 var fail_count: usize = 0;
132 var progress = std.Progress{
133 .dont_print_on_dumb = true,
134 };
135 const root_node = progress.start("Test", test_fn_list.len);
136 const have_tty = progress.terminal != null and
137 (progress.supports_ansi_escape_codes or progress.is_windows_terminal);
132 const root_node = std.Progress.start(.{
133 .root_name = "Test",
134 .estimated_total_items = test_fn_list.len,
135 });
136 const have_tty = std.io.getStdErr().isTty();
138137
139138 var async_frame_buffer: []align(builtin.target.stackAlignment()) u8 = undefined;
140139 // TODO this is on the next line (using `undefined` above) because otherwise zig incorrectly
......@@ -151,11 +150,9 @@ fn mainTerminal() void {
151150 }
152151 std.testing.log_level = .warn;
153152
154 var test_node = root_node.start(test_fn.name, 0);
155 test_node.activate();
156 progress.refresh();
153 const test_node = root_node.start(test_fn.name, 0);
157154 if (!have_tty) {
158 std.debug.print("{d}/{d} {s}... ", .{ i + 1, test_fn_list.len, test_fn.name });
155 std.debug.print("{d}/{d} {s}...", .{ i + 1, test_fn_list.len, test_fn.name });
159156 }
160157 if (test_fn.func()) |_| {
161158 ok_count += 1;
......@@ -164,12 +161,22 @@ fn mainTerminal() void {
164161 } else |err| switch (err) {
165162 error.SkipZigTest => {
166163 skip_count += 1;
167 progress.log("SKIP\n", .{});
164 if (have_tty) {
165 std.debug.print("{d}/{d} {s}...SKIP\n", .{ i + 1, test_fn_list.len, test_fn.name });
166 } else {
167 std.debug.print("SKIP\n", .{});
168 }
168169 test_node.end();
169170 },
170171 else => {
171172 fail_count += 1;
172 progress.log("FAIL ({s})\n", .{@errorName(err)});
173 if (have_tty) {
174 std.debug.print("{d}/{d} {s}...FAIL ({s})\n", .{
175 i + 1, test_fn_list.len, test_fn.name, @errorName(err),
176 });
177 } else {
178 std.debug.print("FAIL ({s})\n", .{@errorName(err)});
179 }
173180 if (@errorReturnTrace()) |trace| {
174181 std.debug.dumpStackTrace(trace.*);
175182 }
lib/std/Build.zig+5-5
......@@ -1059,7 +1059,7 @@ pub fn getUninstallStep(b: *Build) *Step {
10591059 return &b.uninstall_tls.step;
10601060}
10611061
1062fn makeUninstall(uninstall_step: *Step, prog_node: *std.Progress.Node) anyerror!void {
1062fn makeUninstall(uninstall_step: *Step, prog_node: std.Progress.Node) anyerror!void {
10631063 _ = prog_node;
10641064 const uninstall_tls: *TopLevelStep = @fieldParentPtr("step", uninstall_step);
10651065 const b: *Build = @fieldParentPtr("uninstall_tls", uninstall_tls);
......@@ -2281,10 +2281,10 @@ pub const LazyPath = union(enum) {
22812281 .cwd_relative => |p| return src_builder.pathFromCwd(p),
22822282 .generated => |gen| {
22832283 var file_path: []const u8 = gen.file.step.owner.pathFromRoot(gen.file.path orelse {
2284 std.debug.getStderrMutex().lock();
2284 std.debug.lockStdErr();
22852285 const stderr = std.io.getStdErr();
22862286 dumpBadGetPathHelp(gen.file.step, stderr, src_builder, asking_step) catch {};
2287 std.debug.getStderrMutex().unlock();
2287 std.debug.unlockStdErr();
22882288 @panic("misconfigured build script");
22892289 });
22902290
......@@ -2351,8 +2351,8 @@ fn dumpBadDirnameHelp(
23512351 comptime msg: []const u8,
23522352 args: anytype,
23532353) anyerror!void {
2354 debug.getStderrMutex().lock();
2355 defer debug.getStderrMutex().unlock();
2354 debug.lockStdErr();
2355 defer debug.unlockStdErr();
23562356
23572357 const stderr = io.getStdErr();
23582358 const w = stderr.writer();
lib/std/Build/Step.zig+5-14
......@@ -58,7 +58,7 @@ pub const TestResults = struct {
5858 }
5959};
6060
61pub const MakeFn = *const fn (step: *Step, prog_node: *std.Progress.Node) anyerror!void;
61pub const MakeFn = *const fn (step: *Step, prog_node: std.Progress.Node) anyerror!void;
6262
6363pub const State = enum {
6464 precheck_unstarted,
......@@ -176,7 +176,7 @@ pub fn init(options: StepOptions) Step {
176176/// If the Step's `make` function reports `error.MakeFailed`, it indicates they
177177/// have already reported the error. Otherwise, we add a simple error report
178178/// here.
179pub fn make(s: *Step, prog_node: *std.Progress.Node) error{ MakeFailed, MakeSkipped }!void {
179pub fn make(s: *Step, prog_node: std.Progress.Node) error{ MakeFailed, MakeSkipped }!void {
180180 const arena = s.owner.allocator;
181181
182182 s.makeFn(s, prog_node) catch |err| switch (err) {
......@@ -217,7 +217,7 @@ pub fn getStackTrace(s: *Step) ?std.builtin.StackTrace {
217217 };
218218}
219219
220fn makeNoOp(step: *Step, prog_node: *std.Progress.Node) anyerror!void {
220fn makeNoOp(step: *Step, prog_node: std.Progress.Node) anyerror!void {
221221 _ = prog_node;
222222
223223 var all_cached = true;
......@@ -303,7 +303,7 @@ pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutO
303303pub fn evalZigProcess(
304304 s: *Step,
305305 argv: []const []const u8,
306 prog_node: *std.Progress.Node,
306 prog_node: std.Progress.Node,
307307) !?[]const u8 {
308308 assert(argv.len != 0);
309309 const b = s.owner;
......@@ -319,6 +319,7 @@ pub fn evalZigProcess(
319319 child.stdout_behavior = .Pipe;
320320 child.stderr_behavior = .Pipe;
321321 child.request_resource_usage_statistics = true;
322 child.progress_node = prog_node;
322323
323324 child.spawn() catch |err| return s.fail("unable to spawn {s}: {s}", .{
324325 argv[0], @errorName(err),
......@@ -337,11 +338,6 @@ pub fn evalZigProcess(
337338 const Header = std.zig.Server.Message.Header;
338339 var result: ?[]const u8 = null;
339340
340 var node_name: std.ArrayListUnmanaged(u8) = .{};
341 defer node_name.deinit(gpa);
342 var sub_prog_node = prog_node.start("", 0);
343 defer sub_prog_node.end();
344
345341 const stdout = poller.fifo(.stdout);
346342
347343 poll: while (true) {
......@@ -379,11 +375,6 @@ pub fn evalZigProcess(
379375 .extra = extra_array,
380376 };
381377 },
382 .progress => {
383 node_name.clearRetainingCapacity();
384 try node_name.appendSlice(gpa, body);
385 sub_prog_node.setName(node_name.items);
386 },
387378 .emit_bin_path => {
388379 const EbpHdr = std.zig.Server.Message.EmitBinPath;
389380 const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body));
lib/std/Build/Step/CheckFile.zig+1-1
......@@ -46,7 +46,7 @@ pub fn setName(check_file: *CheckFile, name: []const u8) void {
4646 check_file.step.name = name;
4747}
4848
49fn make(step: *Step, prog_node: *std.Progress.Node) !void {
49fn make(step: *Step, prog_node: std.Progress.Node) !void {
5050 _ = prog_node;
5151 const b = step.owner;
5252 const check_file: *CheckFile = @fieldParentPtr("step", step);
lib/std/Build/Step/CheckObject.zig+1-1
......@@ -550,7 +550,7 @@ pub fn checkComputeCompare(
550550 check_object.checks.append(check) catch @panic("OOM");
551551}
552552
553fn make(step: *Step, prog_node: *std.Progress.Node) !void {
553fn make(step: *Step, prog_node: std.Progress.Node) !void {
554554 _ = prog_node;
555555 const b = step.owner;
556556 const gpa = b.allocator;
lib/std/Build/Step/Compile.zig+3-3
......@@ -967,7 +967,7 @@ fn getGeneratedFilePath(compile: *Compile, comptime tag_name: []const u8, asking
967967 const maybe_path: ?*GeneratedFile = @field(compile, tag_name);
968968
969969 const generated_file = maybe_path orelse {
970 std.debug.getStderrMutex().lock();
970 std.debug.lockStdErr();
971971 const stderr = std.io.getStdErr();
972972
973973 std.Build.dumpBadGetPathHelp(&compile.step, stderr, compile.step.owner, asking_step) catch {};
......@@ -976,7 +976,7 @@ fn getGeneratedFilePath(compile: *Compile, comptime tag_name: []const u8, asking
976976 };
977977
978978 const path = generated_file.path orelse {
979 std.debug.getStderrMutex().lock();
979 std.debug.lockStdErr();
980980 const stderr = std.io.getStdErr();
981981
982982 std.Build.dumpBadGetPathHelp(&compile.step, stderr, compile.step.owner, asking_step) catch {};
......@@ -987,7 +987,7 @@ fn getGeneratedFilePath(compile: *Compile, comptime tag_name: []const u8, asking
987987 return path;
988988}
989989
990fn make(step: *Step, prog_node: *std.Progress.Node) !void {
990fn make(step: *Step, prog_node: std.Progress.Node) !void {
991991 const b = step.owner;
992992 const arena = b.allocator;
993993 const compile: *Compile = @fieldParentPtr("step", step);
lib/std/Build/Step/ConfigHeader.zig+1-1
......@@ -164,7 +164,7 @@ fn putValue(config_header: *ConfigHeader, field_name: []const u8, comptime T: ty
164164 }
165165}
166166
167fn make(step: *Step, prog_node: *std.Progress.Node) !void {
167fn make(step: *Step, prog_node: std.Progress.Node) !void {
168168 _ = prog_node;
169169 const b = step.owner;
170170 const config_header: *ConfigHeader = @fieldParentPtr("step", step);
lib/std/Build/Step/Fmt.zig+1-1
......@@ -36,7 +36,7 @@ pub fn create(owner: *std.Build, options: Options) *Fmt {
3636 return fmt;
3737}
3838
39fn make(step: *Step, prog_node: *std.Progress.Node) !void {
39fn make(step: *Step, prog_node: std.Progress.Node) !void {
4040 // zig fmt is fast enough that no progress is needed.
4141 _ = prog_node;
4242
lib/std/Build/Step/InstallArtifact.zig+1-1
......@@ -115,7 +115,7 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
115115 return install_artifact;
116116}
117117
118fn make(step: *Step, prog_node: *std.Progress.Node) !void {
118fn make(step: *Step, prog_node: std.Progress.Node) !void {
119119 _ = prog_node;
120120 const install_artifact: *InstallArtifact = @fieldParentPtr("step", step);
121121 const b = step.owner;
lib/std/Build/Step/InstallDir.zig+1-1
......@@ -56,7 +56,7 @@ pub fn create(owner: *std.Build, options: Options) *InstallDir {
5656 return install_dir;
5757}
5858
59fn make(step: *Step, prog_node: *std.Progress.Node) !void {
59fn make(step: *Step, prog_node: std.Progress.Node) !void {
6060 _ = prog_node;
6161 const b = step.owner;
6262 const install_dir: *InstallDir = @fieldParentPtr("step", step);
lib/std/Build/Step/InstallFile.zig+1-1
......@@ -36,7 +36,7 @@ pub fn create(
3636 return install_file;
3737}
3838
39fn make(step: *Step, prog_node: *std.Progress.Node) !void {
39fn make(step: *Step, prog_node: std.Progress.Node) !void {
4040 _ = prog_node;
4141 const b = step.owner;
4242 const install_file: *InstallFile = @fieldParentPtr("step", step);
lib/std/Build/Step/ObjCopy.zig+1-1
......@@ -90,7 +90,7 @@ pub fn getOutputSeparatedDebug(objcopy: *const ObjCopy) ?std.Build.LazyPath {
9090 return if (objcopy.output_file_debug) |*file| .{ .generated = .{ .file = file } } else null;
9191}
9292
93fn make(step: *Step, prog_node: *std.Progress.Node) !void {
93fn make(step: *Step, prog_node: std.Progress.Node) !void {
9494 const b = step.owner;
9595 const objcopy: *ObjCopy = @fieldParentPtr("step", step);
9696
lib/std/Build/Step/Options.zig+1-1
......@@ -410,7 +410,7 @@ pub fn getOutput(options: *Options) LazyPath {
410410 return .{ .generated = .{ .file = &options.generated_file } };
411411}
412412
413fn make(step: *Step, prog_node: *std.Progress.Node) !void {
413fn make(step: *Step, prog_node: std.Progress.Node) !void {
414414 // This step completes so quickly that no progress is necessary.
415415 _ = prog_node;
416416
lib/std/Build/Step/RemoveDir.zig+1-1
......@@ -22,7 +22,7 @@ pub fn create(owner: *std.Build, dir_path: []const u8) *RemoveDir {
2222 return remove_dir;
2323}
2424
25fn make(step: *Step, prog_node: *std.Progress.Node) !void {
25fn make(step: *Step, prog_node: std.Progress.Node) !void {
2626 // TODO update progress node while walking file system.
2727 // Should the standard library support this use case??
2828 _ = prog_node;
lib/std/Build/Step/Run.zig+17-7
......@@ -23,6 +23,11 @@ cwd: ?Build.LazyPath,
2323/// Override this field to modify the environment, or use setEnvironmentVariable
2424env_map: ?*EnvMap,
2525
26/// When `true` prevents `ZIG_PROGRESS` environment variable from being passed
27/// to the child process, which otherwise would be used for the child to send
28/// progress updates to the parent.
29disable_zig_progress: bool,
30
2631/// Configures whether the Run step is considered to have side-effects, and also
2732/// whether the Run step will inherit stdio streams, forwarding them to the
2833/// parent process, in which case will require a global lock to prevent other
......@@ -152,6 +157,7 @@ pub fn create(owner: *std.Build, name: []const u8) *Run {
152157 .argv = .{},
153158 .cwd = null,
154159 .env_map = null,
160 .disable_zig_progress = false,
155161 .stdio = .infer_from_args,
156162 .stdin = .none,
157163 .extra_file_dependencies = &.{},
......@@ -574,7 +580,7 @@ const IndexedOutput = struct {
574580 tag: @typeInfo(Arg).Union.tag_type.?,
575581 output: *Output,
576582};
577fn make(step: *Step, prog_node: *std.Progress.Node) !void {
583fn make(step: *Step, prog_node: std.Progress.Node) !void {
578584 const b = step.owner;
579585 const arena = b.allocator;
580586 const run: *Run = @fieldParentPtr("step", step);
......@@ -878,7 +884,7 @@ fn runCommand(
878884 argv: []const []const u8,
879885 has_side_effects: bool,
880886 output_dir_path: []const u8,
881 prog_node: *std.Progress.Node,
887 prog_node: std.Progress.Node,
882888) !void {
883889 const step = &run.step;
884890 const b = step.owner;
......@@ -1195,7 +1201,7 @@ fn spawnChildAndCollect(
11951201 run: *Run,
11961202 argv: []const []const u8,
11971203 has_side_effects: bool,
1198 prog_node: *std.Progress.Node,
1204 prog_node: std.Progress.Node,
11991205) !ChildProcResult {
12001206 const b = run.step.owner;
12011207 const arena = b.allocator;
......@@ -1235,6 +1241,10 @@ fn spawnChildAndCollect(
12351241 child.stdin_behavior = .Pipe;
12361242 }
12371243
1244 if (run.stdio != .zig_test and !run.disable_zig_progress) {
1245 child.progress_node = prog_node;
1246 }
1247
12381248 try child.spawn();
12391249 var timer = try std.time.Timer.start();
12401250
......@@ -1264,7 +1274,7 @@ const StdIoResult = struct {
12641274fn evalZigTest(
12651275 run: *Run,
12661276 child: *std.process.Child,
1267 prog_node: *std.Progress.Node,
1277 prog_node: std.Progress.Node,
12681278) !StdIoResult {
12691279 const gpa = run.step.owner.allocator;
12701280 const arena = run.step.owner.allocator;
......@@ -1291,7 +1301,7 @@ fn evalZigTest(
12911301 var metadata: ?TestMetadata = null;
12921302
12931303 var sub_prog_node: ?std.Progress.Node = null;
1294 defer if (sub_prog_node) |*n| n.end();
1304 defer if (sub_prog_node) |n| n.end();
12951305
12961306 poll: while (true) {
12971307 while (stdout.readableLength() < @sizeOf(Header)) {
......@@ -1406,7 +1416,7 @@ const TestMetadata = struct {
14061416 expected_panic_msgs: []const u32,
14071417 string_bytes: []const u8,
14081418 next_index: u32,
1409 prog_node: *std.Progress.Node,
1419 prog_node: std.Progress.Node,
14101420
14111421 fn testName(tm: TestMetadata, index: u32) []const u8 {
14121422 return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0);
......@@ -1421,7 +1431,7 @@ fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Pr
14211431 if (metadata.expected_panic_msgs[i] != 0) continue;
14221432
14231433 const name = metadata.testName(i);
1424 if (sub_prog_node.*) |*n| n.end();
1434 if (sub_prog_node.*) |n| n.end();
14251435 sub_prog_node.* = metadata.prog_node.start(name, 0);
14261436
14271437 try sendRunTestMessage(in, i);
lib/std/Build/Step/TranslateC.zig+1-1
......@@ -116,7 +116,7 @@ pub fn defineCMacroRaw(translate_c: *TranslateC, name_and_value: []const u8) voi
116116 translate_c.c_macros.append(translate_c.step.owner.dupe(name_and_value)) catch @panic("OOM");
117117}
118118
119fn make(step: *Step, prog_node: *std.Progress.Node) !void {
119fn make(step: *Step, prog_node: std.Progress.Node) !void {
120120 const b = step.owner;
121121 const translate_c: *TranslateC = @fieldParentPtr("step", step);
122122
lib/std/Build/Step/WriteFile.zig+1-1
......@@ -198,7 +198,7 @@ fn maybeUpdateName(write_file: *WriteFile) void {
198198 }
199199}
200200
201fn make(step: *Step, prog_node: *std.Progress.Node) !void {
201fn make(step: *Step, prog_node: std.Progress.Node) !void {
202202 _ = prog_node;
203203 const b = step.owner;
204204 const write_file: *WriteFile = @fieldParentPtr("step", step);
lib/std/Progress.zig+1194-343
......@@ -1,10 +1,4 @@
1//! This API is non-allocating, non-fallible, and thread-safe.
2//! The tradeoff is that users of this API must provide the storage
3//! for each `Progress.Node`.
4//!
5//! Initialize the struct directly, overriding these fields as desired:
6//! * `refresh_rate_ms`
7//! * `initial_delay_ms`
1//! This API is non-allocating, non-fallible, thread-safe, and lock-free.
82
93const std = @import("std");
104const builtin = @import("builtin");
......@@ -12,436 +6,1293 @@ const windows = std.os.windows;
126const testing = std.testing;
137const assert = std.debug.assert;
148const Progress = @This();
9const posix = std.posix;
10const is_big_endian = builtin.cpu.arch.endian() == .big;
11const is_windows = builtin.os.tag == .windows;
1512
1613/// `null` if the current node (and its children) should
1714/// not print on update()
18terminal: ?std.fs.File = undefined,
15terminal: std.fs.File,
1916
20/// Is this a windows API terminal (note: this is not the same as being run on windows
21/// because other terminals exist like MSYS/git-bash)
22is_windows_terminal: bool = false,
17terminal_mode: TerminalMode,
2318
24/// Whether the terminal supports ANSI escape codes.
25supports_ansi_escape_codes: bool = false,
19update_thread: ?std.Thread,
2620
27/// If the terminal is "dumb", don't print output.
28/// This can be useful if you don't want to print all
29/// the stages of code generation if there are a lot.
30/// You should not use it if the user should see output
31/// for example showing the user what tests run.
32dont_print_on_dumb: bool = false,
21/// Atomically set by SIGWINCH as well as the root done() function.
22redraw_event: std.Thread.ResetEvent,
23/// Indicates a request to shut down and reset global state.
24/// Accessed atomically.
25done: bool,
3326
34root: Node = undefined,
27refresh_rate_ns: u64,
28initial_delay_ns: u64,
3529
36/// Keeps track of how much time has passed since the beginning.
37/// Used to compare with `initial_delay_ms` and `refresh_rate_ms`.
38timer: ?std.time.Timer = null,
30rows: u16,
31cols: u16,
32/// Tracks the number of newlines that have been actually written to the terminal.
33written_newline_count: u16,
34/// Tracks the number of newlines that will be written to the terminal if the
35/// draw buffer is sent.
36accumulated_newline_count: u16,
3937
40/// When the previous refresh was written to the terminal.
41/// Used to compare with `refresh_rate_ms`.
42prev_refresh_timestamp: u64 = undefined,
38/// Accessed only by the update thread.
39draw_buffer: []u8,
4340
44/// This buffer represents the maximum number of bytes written to the terminal
45/// with each refresh.
46output_buffer: [100]u8 = undefined,
41/// This is in a separate array from `node_storage` but with the same length so
42/// that it can be iterated over efficiently without trashing too much of the
43/// CPU cache.
44node_parents: []Node.Parent,
45node_storage: []Node.Storage,
46node_freelist: []Node.OptionalIndex,
47node_freelist_first: Node.OptionalIndex,
48node_end_index: u32,
4749
48/// How many nanoseconds between writing updates to the terminal.
49refresh_rate_ns: u64 = 50 * std.time.ns_per_ms,
50pub const TerminalMode = union(enum) {
51 off,
52 ansi_escape_codes,
53 /// This is not the same as being run on windows because other terminals
54 /// exist like MSYS/git-bash.
55 windows_api: if (is_windows) WindowsApi else void,
5056
51/// How many nanoseconds to keep the output hidden
52initial_delay_ns: u64 = 500 * std.time.ns_per_ms,
53
54done: bool = true,
55
56/// Protects the `refresh` function, as well as `node.recently_updated_child`.
57/// Without this, callsites would call `Node.end` and then free `Node` memory
58/// while it was still being accessed by the `refresh` function.
59update_mutex: std.Thread.Mutex = .{},
57 pub const WindowsApi = struct {
58 /// The output code page of the console.
59 code_page: windows.UINT,
60 };
61};
6062
61/// Keeps track of how many columns in the terminal have been output, so that
62/// we can move the cursor back later.
63columns_written: usize = undefined,
63pub const Options = struct {
64 /// User-provided buffer with static lifetime.
65 ///
66 /// Used to store the entire write buffer sent to the terminal. Progress output will be truncated if it
67 /// cannot fit into this buffer which will look bad but not cause any malfunctions.
68 ///
69 /// Must be at least 200 bytes.
70 draw_buffer: []u8 = &default_draw_buffer,
71 /// How many nanoseconds between writing updates to the terminal.
72 refresh_rate_ns: u64 = 80 * std.time.ns_per_ms,
73 /// How many nanoseconds to keep the output hidden
74 initial_delay_ns: u64 = 200 * std.time.ns_per_ms,
75 /// If provided, causes the progress item to have a denominator.
76 /// 0 means unknown.
77 estimated_total_items: usize = 0,
78 root_name: []const u8 = "",
79 disable_printing: bool = false,
80};
6481
6582/// Represents one unit of progress. Each node can have children nodes, or
6683/// one can use integers with `update`.
6784pub const Node = struct {
68 context: *Progress,
69 parent: ?*Node,
70 name: []const u8,
71 unit: []const u8 = "",
72 /// Must be handled atomically to be thread-safe.
73 recently_updated_child: ?*Node = null,
74 /// Must be handled atomically to be thread-safe. 0 means null.
75 unprotected_estimated_total_items: usize,
76 /// Must be handled atomically to be thread-safe.
77 unprotected_completed_items: usize,
85 index: OptionalIndex,
86
87 pub const max_name_len = 40;
88
89 const Storage = extern struct {
90 /// Little endian.
91 completed_count: u32,
92 /// 0 means unknown.
93 /// Little endian.
94 estimated_total_count: u32,
95 name: [max_name_len]u8,
96
97 /// Not thread-safe.
98 fn getIpcFd(s: Storage) ?posix.fd_t {
99 return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(posix.fd_t)) {
100 .Int => @bitCast(s.completed_count),
101 .Pointer => @ptrFromInt(s.completed_count),
102 else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)),
103 } else null;
104 }
105
106 /// Thread-safe.
107 fn setIpcFd(s: *Storage, fd: posix.fd_t) void {
108 const integer: u32 = switch (@typeInfo(posix.fd_t)) {
109 .Int => @bitCast(fd),
110 .Pointer => @intFromPtr(fd),
111 else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)),
112 };
113 // `estimated_total_count` max int indicates the special state that
114 // causes `completed_count` to be treated as a file descriptor, so
115 // the order here matters.
116 @atomicStore(u32, &s.completed_count, integer, .monotonic);
117 @atomicStore(u32, &s.estimated_total_count, std.math.maxInt(u32), .release);
118 }
119
120 /// Not thread-safe.
121 fn byteSwap(s: *Storage) void {
122 s.completed_count = @byteSwap(s.completed_count);
123 s.estimated_total_count = @byteSwap(s.estimated_total_count);
124 }
125
126 comptime {
127 assert((@sizeOf(Storage) % 4) == 0);
128 }
129 };
130
131 const Parent = enum(u8) {
132 /// Unallocated storage.
133 unused = std.math.maxInt(u8) - 1,
134 /// Indicates root node.
135 none = std.math.maxInt(u8),
136 /// Index into `node_storage`.
137 _,
138
139 fn unwrap(i: @This()) ?Index {
140 return switch (i) {
141 .unused, .none => return null,
142 else => @enumFromInt(@intFromEnum(i)),
143 };
144 }
145 };
146
147 pub const OptionalIndex = enum(u8) {
148 none = std.math.maxInt(u8),
149 /// Index into `node_storage`.
150 _,
151
152 pub fn unwrap(i: @This()) ?Index {
153 if (i == .none) return null;
154 return @enumFromInt(@intFromEnum(i));
155 }
156
157 fn toParent(i: @This()) Parent {
158 assert(@intFromEnum(i) != @intFromEnum(Parent.unused));
159 return @enumFromInt(@intFromEnum(i));
160 }
161 };
162
163 /// Index into `node_storage`.
164 pub const Index = enum(u8) {
165 _,
166
167 fn toParent(i: @This()) Parent {
168 assert(@intFromEnum(i) != @intFromEnum(Parent.unused));
169 assert(@intFromEnum(i) != @intFromEnum(Parent.none));
170 return @enumFromInt(@intFromEnum(i));
171 }
172
173 pub fn toOptional(i: @This()) OptionalIndex {
174 return @enumFromInt(@intFromEnum(i));
175 }
176 };
78177
79178 /// Create a new child progress node. Thread-safe.
80 /// Call `Node.end` when done.
81 /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this
82 /// API to set `self.parent.recently_updated_child` with the return value.
83 /// Until that is fixed you probably want to call `activate` on the return value.
179 ///
84180 /// Passing 0 for `estimated_total_items` means unknown.
85 pub fn start(self: *Node, name: []const u8, estimated_total_items: usize) Node {
86 return Node{
87 .context = self.context,
88 .parent = self,
89 .name = name,
90 .unprotected_estimated_total_items = estimated_total_items,
91 .unprotected_completed_items = 0,
92 };
181 pub fn start(node: Node, name: []const u8, estimated_total_items: usize) Node {
182 if (noop_impl) {
183 assert(node.index == .none);
184 return .{ .index = .none };
185 }
186 const node_index = node.index.unwrap() orelse return .{ .index = .none };
187 const parent = node_index.toParent();
188
189 const freelist_head = &global_progress.node_freelist_first;
190 var opt_free_index = @atomicLoad(Node.OptionalIndex, freelist_head, .seq_cst);
191 while (opt_free_index.unwrap()) |free_index| {
192 const freelist_ptr = freelistByIndex(free_index);
193 opt_free_index = @cmpxchgWeak(Node.OptionalIndex, freelist_head, opt_free_index, freelist_ptr.*, .seq_cst, .seq_cst) orelse {
194 // We won the allocation race.
195 return init(free_index, parent, name, estimated_total_items);
196 };
197 }
198
199 const free_index = @atomicRmw(u32, &global_progress.node_end_index, .Add, 1, .monotonic);
200 if (free_index >= global_progress.node_storage.len) {
201 // Ran out of node storage memory. Progress for this node will not be tracked.
202 _ = @atomicRmw(u32, &global_progress.node_end_index, .Sub, 1, .monotonic);
203 return .{ .index = .none };
204 }
205
206 return init(@enumFromInt(free_index), parent, name, estimated_total_items);
93207 }
94208
95209 /// This is the same as calling `start` and then `end` on the returned `Node`. Thread-safe.
96 pub fn completeOne(self: *Node) void {
97 if (self.parent) |parent| {
98 @atomicStore(?*Node, &parent.recently_updated_child, self, .release);
99 }
100 _ = @atomicRmw(usize, &self.unprotected_completed_items, .Add, 1, .monotonic);
101 self.context.maybeRefresh();
210 pub fn completeOne(n: Node) void {
211 const index = n.index.unwrap() orelse return;
212 const storage = storageByIndex(index);
213 _ = @atomicRmw(u32, &storage.completed_count, .Add, 1, .monotonic);
214 }
215
216 /// Thread-safe.
217 pub fn setCompletedItems(n: Node, completed_items: usize) void {
218 const index = n.index.unwrap() orelse return;
219 const storage = storageByIndex(index);
220 @atomicStore(u32, &storage.completed_count, std.math.lossyCast(u32, completed_items), .monotonic);
221 }
222
223 /// Thread-safe. 0 means unknown.
224 pub fn setEstimatedTotalItems(n: Node, count: usize) void {
225 const index = n.index.unwrap() orelse return;
226 const storage = storageByIndex(index);
227 // Avoid u32 max int which is used to indicate a special state.
228 const saturated = @min(std.math.maxInt(u32) - 1, count);
229 @atomicStore(u32, &storage.estimated_total_count, saturated, .monotonic);
230 }
231
232 /// Thread-safe.
233 pub fn increaseEstimatedTotalItems(n: Node, count: usize) void {
234 const index = n.index.unwrap() orelse return;
235 const storage = storageByIndex(index);
236 _ = @atomicRmw(u32, &storage.estimated_total_count, .Add, std.math.lossyCast(u32, count), .monotonic);
102237 }
103238
104239 /// Finish a started `Node`. Thread-safe.
105 pub fn end(self: *Node) void {
106 self.context.maybeRefresh();
107 if (self.parent) |parent| {
108 {
109 self.context.update_mutex.lock();
110 defer self.context.update_mutex.unlock();
111 _ = @cmpxchgStrong(?*Node, &parent.recently_updated_child, self, null, .monotonic, .monotonic);
240 pub fn end(n: Node) void {
241 if (noop_impl) {
242 assert(n.index == .none);
243 return;
244 }
245 const index = n.index.unwrap() orelse return;
246 const parent_ptr = parentByIndex(index);
247 if (parent_ptr.unwrap()) |parent_index| {
248 _ = @atomicRmw(u32, &storageByIndex(parent_index).completed_count, .Add, 1, .monotonic);
249 @atomicStore(Node.Parent, parent_ptr, .unused, .seq_cst);
250
251 const freelist_head = &global_progress.node_freelist_first;
252 var first = @atomicLoad(Node.OptionalIndex, freelist_head, .seq_cst);
253 while (true) {
254 freelistByIndex(index).* = first;
255 first = @cmpxchgWeak(Node.OptionalIndex, freelist_head, first, index.toOptional(), .seq_cst, .seq_cst) orelse break;
112256 }
113 parent.completeOne();
114257 } else {
115 self.context.update_mutex.lock();
116 defer self.context.update_mutex.unlock();
117 self.context.done = true;
118 self.context.refreshWithHeldLock();
258 @atomicStore(bool, &global_progress.done, true, .seq_cst);
259 global_progress.redraw_event.set();
260 if (global_progress.update_thread) |thread| thread.join();
119261 }
120262 }
121263
122 /// Tell the parent node that this node is actively being worked on. Thread-safe.
123 pub fn activate(self: *Node) void {
124 if (self.parent) |parent| {
125 @atomicStore(?*Node, &parent.recently_updated_child, self, .release);
126 self.context.maybeRefresh();
127 }
264 /// Posix-only. Used by `std.process.Child`. Thread-safe.
265 pub fn setIpcFd(node: Node, fd: posix.fd_t) void {
266 const index = node.index.unwrap() orelse return;
267 assert(fd >= 0);
268 assert(fd != posix.STDOUT_FILENO);
269 assert(fd != posix.STDIN_FILENO);
270 assert(fd != posix.STDERR_FILENO);
271 storageByIndex(index).setIpcFd(fd);
128272 }
129273
130 /// Thread-safe.
131 pub fn setName(self: *Node, name: []const u8) void {
132 const progress = self.context;
133 progress.update_mutex.lock();
134 defer progress.update_mutex.unlock();
135 self.name = name;
136 if (self.parent) |parent| {
137 @atomicStore(?*Node, &parent.recently_updated_child, self, .release);
138 if (parent.parent) |grand_parent| {
139 @atomicStore(?*Node, &grand_parent.recently_updated_child, parent, .release);
140 }
141 if (progress.timer) |*timer| progress.maybeRefreshWithHeldLock(timer);
142 }
274 fn storageByIndex(index: Node.Index) *Node.Storage {
275 return &global_progress.node_storage[@intFromEnum(index)];
143276 }
144277
145 /// Thread-safe.
146 pub fn setUnit(self: *Node, unit: []const u8) void {
147 const progress = self.context;
148 progress.update_mutex.lock();
149 defer progress.update_mutex.unlock();
150 self.unit = unit;
151 if (self.parent) |parent| {
152 @atomicStore(?*Node, &parent.recently_updated_child, self, .release);
153 if (parent.parent) |grand_parent| {
154 @atomicStore(?*Node, &grand_parent.recently_updated_child, parent, .release);
155 }
156 if (progress.timer) |*timer| progress.maybeRefreshWithHeldLock(timer);
157 }
278 fn parentByIndex(index: Node.Index) *Node.Parent {
279 return &global_progress.node_parents[@intFromEnum(index)];
158280 }
159281
160 /// Thread-safe. 0 means unknown.
161 pub fn setEstimatedTotalItems(self: *Node, count: usize) void {
162 @atomicStore(usize, &self.unprotected_estimated_total_items, count, .monotonic);
282 fn freelistByIndex(index: Node.Index) *Node.OptionalIndex {
283 return &global_progress.node_freelist[@intFromEnum(index)];
163284 }
164285
165 /// Thread-safe.
166 pub fn setCompletedItems(self: *Node, completed_items: usize) void {
167 @atomicStore(usize, &self.unprotected_completed_items, completed_items, .monotonic);
286 fn init(free_index: Index, parent: Parent, name: []const u8, estimated_total_items: usize) Node {
287 assert(parent != .unused);
288
289 const storage = storageByIndex(free_index);
290 storage.* = .{
291 .completed_count = 0,
292 .estimated_total_count = std.math.lossyCast(u32, estimated_total_items),
293 .name = [1]u8{0} ** max_name_len,
294 };
295 const name_len = @min(max_name_len, name.len);
296 @memcpy(storage.name[0..name_len], name[0..name_len]);
297
298 const parent_ptr = parentByIndex(free_index);
299 assert(parent_ptr.* == .unused);
300 @atomicStore(Node.Parent, parent_ptr, parent, .release);
301
302 return .{ .index = free_index.toOptional() };
168303 }
169304};
170305
171/// Create a new progress node.
306var global_progress: Progress = .{
307 .terminal = undefined,
308 .terminal_mode = .off,
309 .update_thread = null,
310 .redraw_event = .{},
311 .refresh_rate_ns = undefined,
312 .initial_delay_ns = undefined,
313 .rows = 0,
314 .cols = 0,
315 .written_newline_count = 0,
316 .accumulated_newline_count = 0,
317 .draw_buffer = undefined,
318 .done = false,
319
320 .node_parents = &node_parents_buffer,
321 .node_storage = &node_storage_buffer,
322 .node_freelist = &node_freelist_buffer,
323 .node_freelist_first = .none,
324 .node_end_index = 0,
325};
326
327const node_storage_buffer_len = 200;
328var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined;
329var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined;
330var node_freelist_buffer: [node_storage_buffer_len]Node.OptionalIndex = undefined;
331
332var default_draw_buffer: [4096]u8 = undefined;
333
334var debug_start_trace = std.debug.Trace.init;
335
336const noop_impl = builtin.single_threaded or switch (builtin.os.tag) {
337 .wasi, .freestanding => true,
338 else => false,
339};
340
341/// Initializes a global Progress instance.
342///
343/// Asserts there is only one global Progress instance.
344///
172345/// Call `Node.end` when done.
173/// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this
174/// API to return Progress rather than accept it as a parameter.
175/// `estimated_total_items` value of 0 means unknown.
176pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) *Node {
177 const stderr = std.io.getStdErr();
178 self.terminal = null;
179 if (stderr.supportsAnsiEscapeCodes()) {
180 self.terminal = stderr;
181 self.supports_ansi_escape_codes = true;
182 } else if (builtin.os.tag == .windows and stderr.isTty()) {
183 self.is_windows_terminal = true;
184 self.terminal = stderr;
185 } else if (builtin.os.tag != .windows) {
186 // we are in a "dumb" terminal like in acme or writing to a file
187 self.terminal = stderr;
188 }
189 self.root = Node{
190 .context = self,
191 .parent = null,
192 .name = name,
193 .unprotected_estimated_total_items = estimated_total_items,
194 .unprotected_completed_items = 0,
346pub fn start(options: Options) Node {
347 // Ensure there is only 1 global Progress object.
348 if (global_progress.node_end_index != 0) {
349 debug_start_trace.dump();
350 unreachable;
351 }
352 debug_start_trace.add("first initialized here");
353
354 @memset(global_progress.node_parents, .unused);
355 const root_node = Node.init(@enumFromInt(0), .none, options.root_name, options.estimated_total_items);
356 global_progress.done = false;
357 global_progress.node_end_index = 1;
358
359 assert(options.draw_buffer.len >= 200);
360 global_progress.draw_buffer = options.draw_buffer;
361 global_progress.refresh_rate_ns = options.refresh_rate_ns;
362 global_progress.initial_delay_ns = options.initial_delay_ns;
363
364 if (noop_impl)
365 return .{ .index = .none };
366
367 if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| {
368 global_progress.update_thread = std.Thread.spawn(.{}, ipcThreadRun, .{
369 @as(posix.fd_t, switch (@typeInfo(posix.fd_t)) {
370 .Int => ipc_fd,
371 .Pointer => @ptrFromInt(ipc_fd),
372 else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)),
373 }),
374 }) catch |err| {
375 std.log.warn("failed to spawn IPC thread for communicating progress to parent: {s}", .{@errorName(err)});
376 return .{ .index = .none };
377 };
378 } else |env_err| switch (env_err) {
379 error.EnvironmentVariableNotFound => {
380 if (options.disable_printing) {
381 return .{ .index = .none };
382 }
383 const stderr = std.io.getStdErr();
384 global_progress.terminal = stderr;
385 if (stderr.supportsAnsiEscapeCodes()) {
386 global_progress.terminal_mode = .ansi_escape_codes;
387 } else if (is_windows and stderr.isTty()) {
388 global_progress.terminal_mode = TerminalMode{ .windows_api = .{
389 .code_page = windows.kernel32.GetConsoleOutputCP(),
390 } };
391 }
392
393 if (global_progress.terminal_mode == .off) {
394 return .{ .index = .none };
395 }
396
397 if (have_sigwinch) {
398 var act: posix.Sigaction = .{
399 .handler = .{ .sigaction = handleSigWinch },
400 .mask = posix.empty_sigset,
401 .flags = (posix.SA.SIGINFO | posix.SA.RESTART),
402 };
403 posix.sigaction(posix.SIG.WINCH, &act, null) catch |err| {
404 std.log.warn("failed to install SIGWINCH signal handler for noticing terminal resizes: {s}", .{@errorName(err)});
405 };
406 }
407
408 if (switch (global_progress.terminal_mode) {
409 .off => unreachable, // handled a few lines above
410 .ansi_escape_codes => std.Thread.spawn(.{}, updateThreadRun, .{}),
411 .windows_api => if (is_windows) std.Thread.spawn(.{}, windowsApiUpdateThreadRun, .{}) else unreachable,
412 }) |thread| {
413 global_progress.update_thread = thread;
414 } else |err| {
415 std.log.warn("unable to spawn thread for printing progress to terminal: {s}", .{@errorName(err)});
416 return .{ .index = .none };
417 }
418 },
419 else => |e| {
420 std.log.warn("invalid ZIG_PROGRESS file descriptor integer: {s}", .{@errorName(e)});
421 return .{ .index = .none };
422 },
423 }
424
425 return root_node;
426}
427
428/// Returns whether a resize is needed to learn the terminal size.
429fn wait(timeout_ns: u64) bool {
430 const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_|
431 true
432 else |err| switch (err) {
433 error.Timeout => false,
195434 };
196 self.columns_written = 0;
197 self.prev_refresh_timestamp = 0;
198 self.timer = std.time.Timer.start() catch null;
199 self.done = false;
200 return &self.root;
435 global_progress.redraw_event.reset();
436 return resize_flag or (global_progress.cols == 0);
201437}
202438
203/// Updates the terminal if enough time has passed since last update. Thread-safe.
204pub fn maybeRefresh(self: *Progress) void {
205 if (self.timer) |*timer| {
206 if (!self.update_mutex.tryLock()) return;
207 defer self.update_mutex.unlock();
208 maybeRefreshWithHeldLock(self, timer);
439fn updateThreadRun() void {
440 // Store this data in the thread so that it does not need to be part of the
441 // linker data of the main executable.
442 var serialized_buffer: Serialized.Buffer = undefined;
443
444 {
445 const resize_flag = wait(global_progress.initial_delay_ns);
446 if (@atomicLoad(bool, &global_progress.done, .seq_cst)) return;
447 maybeUpdateSize(resize_flag);
448
449 const buffer = computeRedraw(&serialized_buffer);
450 if (stderr_mutex.tryLock()) {
451 defer stderr_mutex.unlock();
452 write(buffer) catch return;
453 }
454 }
455
456 while (true) {
457 const resize_flag = wait(global_progress.refresh_rate_ns);
458
459 if (@atomicLoad(bool, &global_progress.done, .seq_cst)) {
460 stderr_mutex.lock();
461 defer stderr_mutex.unlock();
462 return clearWrittenWithEscapeCodes() catch {};
463 }
464
465 maybeUpdateSize(resize_flag);
466
467 const buffer = computeRedraw(&serialized_buffer);
468 if (stderr_mutex.tryLock()) {
469 defer stderr_mutex.unlock();
470 write(buffer) catch return;
471 }
472 }
473}
474
475fn windowsApiUpdateThreadRun() void {
476 var serialized_buffer: Serialized.Buffer = undefined;
477
478 {
479 const resize_flag = wait(global_progress.initial_delay_ns);
480 if (@atomicLoad(bool, &global_progress.done, .seq_cst)) return;
481 maybeUpdateSize(resize_flag);
482
483 const buffer = computeRedraw(&serialized_buffer);
484 if (stderr_mutex.tryLock()) {
485 defer stderr_mutex.unlock();
486 write(buffer) catch return;
487 }
488 }
489
490 while (true) {
491 const resize_flag = wait(global_progress.refresh_rate_ns);
492
493 if (@atomicLoad(bool, &global_progress.done, .seq_cst)) {
494 stderr_mutex.lock();
495 defer stderr_mutex.unlock();
496 return clearWrittenWindowsApi() catch {};
497 }
498
499 maybeUpdateSize(resize_flag);
500
501 const buffer = computeRedraw(&serialized_buffer);
502 if (stderr_mutex.tryLock()) {
503 defer stderr_mutex.unlock();
504 clearWrittenWindowsApi() catch return;
505 write(buffer) catch return;
506 }
209507 }
210508}
211509
212fn maybeRefreshWithHeldLock(self: *Progress, timer: *std.time.Timer) void {
213 const now = timer.read();
214 if (now < self.initial_delay_ns) return;
215 // TODO I have observed this to happen sometimes. I think we need to follow Rust's
216 // lead and guarantee monotonically increasing times in the std lib itself.
217 if (now < self.prev_refresh_timestamp) return;
218 if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return;
219 return self.refreshWithHeldLock();
510/// Allows the caller to freely write to stderr until `unlockStdErr` is called.
511///
512/// During the lock, any `std.Progress` information is cleared from the terminal.
513pub fn lockStdErr() void {
514 stderr_mutex.lock();
515 clearWrittenWithEscapeCodes() catch {};
516}
517
518pub fn unlockStdErr() void {
519 stderr_mutex.unlock();
220520}
221521
222/// Updates the terminal and resets `self.next_refresh_timestamp`. Thread-safe.
223pub fn refresh(self: *Progress) void {
224 if (!self.update_mutex.tryLock()) return;
225 defer self.update_mutex.unlock();
522fn ipcThreadRun(fd: posix.fd_t) anyerror!void {
523 // Store this data in the thread so that it does not need to be part of the
524 // linker data of the main executable.
525 var serialized_buffer: Serialized.Buffer = undefined;
526
527 {
528 _ = wait(global_progress.initial_delay_ns);
226529
227 return self.refreshWithHeldLock();
530 if (@atomicLoad(bool, &global_progress.done, .seq_cst))
531 return;
532
533 const serialized = serialize(&serialized_buffer);
534 writeIpc(fd, serialized) catch |err| switch (err) {
535 error.BrokenPipe => return,
536 };
537 }
538
539 while (true) {
540 _ = wait(global_progress.refresh_rate_ns);
541
542 if (@atomicLoad(bool, &global_progress.done, .seq_cst))
543 return;
544
545 const serialized = serialize(&serialized_buffer);
546 writeIpc(fd, serialized) catch |err| switch (err) {
547 error.BrokenPipe => return,
548 };
549 }
228550}
229551
230fn clearWithHeldLock(p: *Progress, end_ptr: *usize) void {
231 const file = p.terminal orelse return;
232 var end = end_ptr.*;
233 if (p.columns_written > 0) {
234 // restore the cursor position by moving the cursor
235 // `columns_written` cells to the left, then clear the rest of the
236 // line
237 if (p.supports_ansi_escape_codes) {
238 end += (std.fmt.bufPrint(p.output_buffer[end..], "\x1b[{d}D", .{p.columns_written}) catch unreachable).len;
239 end += (std.fmt.bufPrint(p.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
240 } else if (builtin.os.tag == .windows) winapi: {
241 std.debug.assert(p.is_windows_terminal);
242
243 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
244 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) {
245 // stop trying to write to this file
246 p.terminal = null;
247 break :winapi;
248 }
552const start_sync = "\x1b[?2026h";
553const up_one_line = "\x1bM";
554const clear = "\x1b[J";
555const save = "\x1b7";
556const restore = "\x1b8";
557const finish_sync = "\x1b[?2026l";
249558
250 var cursor_pos = windows.COORD{
251 .X = info.dwCursorPosition.X - @as(windows.SHORT, @intCast(p.columns_written)),
252 .Y = info.dwCursorPosition.Y,
559const TreeSymbol = enum {
560 /// ├─
561 tee,
562 /// │
563 line,
564 /// └─
565 langle,
566
567 const Encoding = enum {
568 ansi_escapes,
569 code_page_437,
570 utf8,
571 ascii,
572 };
573
574 /// The escape sequence representation as a string literal
575 fn escapeSeq(symbol: TreeSymbol) *const [9:0]u8 {
576 return switch (symbol) {
577 .tee => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ",
578 .line => "\x1B\x28\x30\x78\x1B\x28\x42 ",
579 .langle => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ",
580 };
581 }
582
583 fn bytes(symbol: TreeSymbol, encoding: Encoding) []const u8 {
584 return switch (encoding) {
585 .ansi_escapes => escapeSeq(symbol),
586 .code_page_437 => switch (symbol) {
587 .tee => "\xC3\xC4 ",
588 .line => "\xB3 ",
589 .langle => "\xC0\xC4 ",
590 },
591 .utf8 => switch (symbol) {
592 .tee => "├─ ",
593 .line => "│ ",
594 .langle => "└─ ",
595 },
596 .ascii => switch (symbol) {
597 .tee => "|- ",
598 .line => "| ",
599 .langle => "+- ",
600 },
601 };
602 }
603
604 fn maxByteLen(symbol: TreeSymbol) usize {
605 var max: usize = 0;
606 inline for (@typeInfo(Encoding).Enum.fields) |field| {
607 const len = symbol.bytes(@field(Encoding, field.name)).len;
608 max = @max(max, len);
609 }
610 return max;
611 }
612};
613
614fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize {
615 switch (global_progress.terminal_mode) {
616 .off => unreachable,
617 .ansi_escape_codes => {
618 const bytes = symbol.escapeSeq();
619 buf[start_i..][0..bytes.len].* = bytes.*;
620 return start_i + bytes.len;
621 },
622 .windows_api => |windows_api| {
623 const bytes = if (!is_windows) unreachable else switch (windows_api.code_page) {
624 // Code page 437 is the default code page and contains the box drawing symbols
625 437 => symbol.bytes(.code_page_437),
626 // UTF-8
627 65001 => symbol.bytes(.utf8),
628 // Fall back to ASCII approximation
629 else => symbol.bytes(.ascii),
253630 };
631 @memcpy(buf[start_i..][0..bytes.len], bytes);
632 return start_i + bytes.len;
633 },
634 }
635}
254636
255 if (cursor_pos.X < 0)
256 cursor_pos.X = 0;
257
258 const fill_chars = @as(windows.DWORD, @intCast(info.dwSize.X - cursor_pos.X));
259
260 var written: windows.DWORD = undefined;
261 if (windows.kernel32.FillConsoleOutputAttribute(
262 file.handle,
263 info.wAttributes,
264 fill_chars,
265 cursor_pos,
266 &written,
267 ) != windows.TRUE) {
268 // stop trying to write to this file
269 p.terminal = null;
270 break :winapi;
271 }
272 if (windows.kernel32.FillConsoleOutputCharacterW(
273 file.handle,
274 ' ',
275 fill_chars,
276 cursor_pos,
277 &written,
278 ) != windows.TRUE) {
279 // stop trying to write to this file
280 p.terminal = null;
281 break :winapi;
282 }
283 if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE) {
284 // stop trying to write to this file
285 p.terminal = null;
286 break :winapi;
637fn clearWrittenWithEscapeCodes() anyerror!void {
638 if (global_progress.written_newline_count == 0) return;
639
640 var i: usize = 0;
641 const buf = global_progress.draw_buffer;
642
643 buf[i..][0..start_sync.len].* = start_sync.*;
644 i += start_sync.len;
645
646 i = computeClear(buf, i);
647
648 buf[i..][0..finish_sync.len].* = finish_sync.*;
649 i += finish_sync.len;
650
651 global_progress.accumulated_newline_count = 0;
652 try write(buf[0..i]);
653}
654
655fn computeClear(buf: []u8, start_i: usize) usize {
656 var i = start_i;
657
658 const prev_nl_n = global_progress.written_newline_count;
659 if (prev_nl_n > 0) {
660 buf[i] = '\r';
661 i += 1;
662 for (0..prev_nl_n) |_| {
663 buf[i..][0..up_one_line.len].* = up_one_line.*;
664 i += up_one_line.len;
665 }
666 }
667
668 buf[i..][0..clear.len].* = clear.*;
669 i += clear.len;
670
671 return i;
672}
673
674/// U+25BA or â–º
675const windows_api_start_marker = 0x25BA;
676
677fn clearWrittenWindowsApi() error{Unexpected}!void {
678 // This uses a 'marker' strategy. The idea is:
679 // - Always write a marker (in this case U+25BA or â–º) at the beginning of the progress
680 // - Get the current cursor position (at the end of the progress)
681 // - Subtract the number of lines written to get the expected start of the progress
682 // - Check to see if the first character at the start of the progress is the marker
683 // - If it's not the marker, keep checking the line before until we find it
684 // - Clear the screen from that position down, and set the cursor position to the start
685 //
686 // This strategy works even if there is line wrapping, and can handle the window
687 // being resized/scrolled arbitrarily.
688 //
689 // Notes:
690 // - Ideally, the marker would be a zero-width character, but the Windows console
691 // doesn't seem to support rendering zero-width characters (they show up as a space)
692 // - This same marker idea could technically be done with an attribute instead
693 // (https://learn.microsoft.com/en-us/windows/console/console-screen-buffers#character-attributes)
694 // but it must be a valid attribute and it actually needs to apply to the first
695 // character in order to be readable via ReadConsoleOutputAttribute. It doesn't seem
696 // like any of the available attributes are invisible/benign.
697 const prev_nl_n = global_progress.written_newline_count;
698 if (prev_nl_n > 0) {
699 const handle = global_progress.terminal.handle;
700 const screen_area = @as(windows.DWORD, global_progress.cols) * global_progress.rows;
701
702 var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
703 if (windows.kernel32.GetConsoleScreenBufferInfo(handle, &console_info) == 0) {
704 return error.Unexpected;
705 }
706 const cursor_pos = console_info.dwCursorPosition;
707 const expected_y = cursor_pos.Y - @as(i16, @intCast(prev_nl_n));
708 var start_pos = windows.COORD{ .X = 0, .Y = expected_y };
709 while (start_pos.Y >= 0) {
710 var wchar: [1]u16 = undefined;
711 var num_console_chars_read: windows.DWORD = undefined;
712 if (windows.kernel32.ReadConsoleOutputCharacterW(handle, &wchar, wchar.len, start_pos, &num_console_chars_read) == 0) {
713 return error.Unexpected;
287714 }
715
716 if (wchar[0] == windows_api_start_marker) break;
717 start_pos.Y -= 1;
288718 } else {
289 // we are in a "dumb" terminal like in acme or writing to a file
290 p.output_buffer[end] = '\n';
291 end += 1;
719 // If we couldn't find the marker, then just assume that no lines wrapped
720 start_pos = .{ .X = 0, .Y = expected_y };
721 }
722 var num_chars_written: windows.DWORD = undefined;
723 if (windows.kernel32.FillConsoleOutputCharacterW(handle, ' ', screen_area, start_pos, &num_chars_written) == 0) {
724 return error.Unexpected;
725 }
726 if (windows.kernel32.SetConsoleCursorPosition(handle, start_pos) == 0) {
727 return error.Unexpected;
292728 }
293
294 p.columns_written = 0;
295729 }
296 end_ptr.* = end;
297730}
298731
299fn refreshWithHeldLock(self: *Progress) void {
300 const is_dumb = !self.supports_ansi_escape_codes and !self.is_windows_terminal;
301 if (is_dumb and self.dont_print_on_dumb) return;
732const Children = struct {
733 child: Node.OptionalIndex,
734 sibling: Node.OptionalIndex,
735};
736
737const Serialized = struct {
738 parents: []Node.Parent,
739 storage: []Node.Storage,
302740
303 const file = self.terminal orelse return;
741 const Buffer = struct {
742 parents: [node_storage_buffer_len]Node.Parent,
743 storage: [node_storage_buffer_len]Node.Storage,
744 map: [node_storage_buffer_len]Node.Index,
304745
305 var end: usize = 0;
306 clearWithHeldLock(self, &end);
746 parents_copy: [node_storage_buffer_len]Node.Parent,
747 storage_copy: [node_storage_buffer_len]Node.Storage,
748 ipc_metadata_copy: [node_storage_buffer_len]SavedMetadata,
307749
308 if (!self.done) {
309 var need_ellipse = false;
310 var maybe_node: ?*Node = &self.root;
311 while (maybe_node) |node| {
312 if (need_ellipse) {
313 self.bufWrite(&end, "... ", .{});
750 ipc_metadata: [node_storage_buffer_len]SavedMetadata,
751 };
752};
753
754fn serialize(serialized_buffer: *Serialized.Buffer) Serialized {
755 var serialized_len: usize = 0;
756 var any_ipc = false;
757
758 // Iterate all of the nodes and construct a serializable copy of the state that can be examined
759 // without atomics.
760 const end_index = @atomicLoad(u32, &global_progress.node_end_index, .monotonic);
761 const node_parents = global_progress.node_parents[0..end_index];
762 const node_storage = global_progress.node_storage[0..end_index];
763 for (node_parents, node_storage, 0..) |*parent_ptr, *storage_ptr, i| {
764 var begin_parent = @atomicLoad(Node.Parent, parent_ptr, .acquire);
765 while (begin_parent != .unused) {
766 const dest_storage = &serialized_buffer.storage[serialized_len];
767 @memcpy(&dest_storage.name, &storage_ptr.name);
768 dest_storage.estimated_total_count = @atomicLoad(u32, &storage_ptr.estimated_total_count, .acquire);
769 dest_storage.completed_count = @atomicLoad(u32, &storage_ptr.completed_count, .monotonic);
770 const end_parent = @atomicLoad(Node.Parent, parent_ptr, .acquire);
771 if (begin_parent == end_parent) {
772 any_ipc = any_ipc or (dest_storage.getIpcFd() != null);
773 serialized_buffer.parents[serialized_len] = begin_parent;
774 serialized_buffer.map[i] = @enumFromInt(serialized_len);
775 serialized_len += 1;
776 break;
314777 }
315 need_ellipse = false;
316 const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .monotonic);
317 const completed_items = @atomicLoad(usize, &node.unprotected_completed_items, .monotonic);
318 const current_item = completed_items + 1;
319 if (node.name.len != 0 or eti > 0) {
320 if (node.name.len != 0) {
321 self.bufWrite(&end, "{s}", .{node.name});
322 need_ellipse = true;
323 }
324 if (eti > 0) {
325 if (need_ellipse) self.bufWrite(&end, " ", .{});
326 self.bufWrite(&end, "[{d}/{d}{s}] ", .{ current_item, eti, node.unit });
327 need_ellipse = false;
328 } else if (completed_items != 0) {
329 if (need_ellipse) self.bufWrite(&end, " ", .{});
330 self.bufWrite(&end, "[{d}{s}] ", .{ current_item, node.unit });
331 need_ellipse = false;
778
779 begin_parent = end_parent;
780 }
781 }
782
783 // Remap parents to point inside serialized arrays.
784 for (serialized_buffer.parents[0..serialized_len]) |*parent| {
785 parent.* = switch (parent.*) {
786 .unused => unreachable,
787 .none => .none,
788 _ => |p| serialized_buffer.map[@intFromEnum(p)].toParent(),
789 };
790 }
791
792 // Find nodes which correspond to child processes.
793 if (any_ipc)
794 serialized_len = serializeIpc(serialized_len, serialized_buffer);
795
796 return .{
797 .parents = serialized_buffer.parents[0..serialized_len],
798 .storage = serialized_buffer.storage[0..serialized_len],
799 };
800}
801
802const SavedMetadata = struct {
803 ipc_fd: u16,
804 main_index: u8,
805 start_index: u8,
806 nodes_len: u8,
807
808 fn getIpcFd(metadata: SavedMetadata) posix.fd_t {
809 return if (is_windows)
810 @ptrFromInt(@as(usize, metadata.ipc_fd) << 2)
811 else
812 metadata.ipc_fd;
813 }
814
815 fn setIpcFd(fd: posix.fd_t) u16 {
816 return @intCast(if (is_windows)
817 @shrExact(@intFromPtr(fd), 2)
818 else
819 fd);
820 }
821};
822
823var ipc_metadata_len: u8 = 0;
824var remaining_read_trash_bytes: usize = 0;
825
826fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buffer) usize {
827 const ipc_metadata_copy = &serialized_buffer.ipc_metadata_copy;
828 const ipc_metadata = &serialized_buffer.ipc_metadata;
829
830 var serialized_len = start_serialized_len;
831 var pipe_buf: [2 * 4096]u8 align(4) = undefined;
832
833 const old_ipc_metadata = ipc_metadata_copy[0..ipc_metadata_len];
834 ipc_metadata_len = 0;
835
836 main_loop: for (
837 serialized_buffer.parents[0..serialized_len],
838 serialized_buffer.storage[0..serialized_len],
839 0..,
840 ) |main_parent, *main_storage, main_index| {
841 if (main_parent == .unused) continue;
842 const fd = main_storage.getIpcFd() orelse continue;
843 var bytes_read: usize = 0;
844 while (true) {
845 const n = posix.read(fd, pipe_buf[bytes_read..]) catch |err| switch (err) {
846 error.WouldBlock => break,
847 else => |e| {
848 std.log.debug("failed to read child progress data: {s}", .{@errorName(e)});
849 main_storage.completed_count = 0;
850 main_storage.estimated_total_count = 0;
851 continue :main_loop;
852 },
853 };
854 if (n == 0) break;
855 if (remaining_read_trash_bytes > 0) {
856 assert(bytes_read == 0);
857 if (remaining_read_trash_bytes >= n) {
858 remaining_read_trash_bytes -= n;
859 continue;
332860 }
861 const src = pipe_buf[remaining_read_trash_bytes..n];
862 std.mem.copyForwards(u8, &pipe_buf, src);
863 remaining_read_trash_bytes = 0;
864 bytes_read = src.len;
865 continue;
333866 }
334 maybe_node = @atomicLoad(?*Node, &node.recently_updated_child, .acquire);
867 bytes_read += n;
335868 }
336 if (need_ellipse) {
337 self.bufWrite(&end, "... ", .{});
869 // Ignore all but the last message on the pipe.
870 var input: []u8 = pipe_buf[0..bytes_read];
871 if (input.len == 0) {
872 serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, old_ipc_metadata);
873 continue;
874 }
875
876 const storage, const parents = while (true) {
877 const subtree_len: usize = input[0];
878 const expected_bytes = 1 + subtree_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent));
879 if (input.len < expected_bytes) {
880 // Ignore short reads. We'll handle the next full message when it comes instead.
881 assert(remaining_read_trash_bytes == 0);
882 remaining_read_trash_bytes = expected_bytes - input.len;
883 serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, old_ipc_metadata);
884 continue :main_loop;
885 }
886 if (input.len > expected_bytes) {
887 input = input[expected_bytes..];
888 continue;
889 }
890 const storage_bytes = input[1..][0 .. subtree_len * @sizeOf(Node.Storage)];
891 const parents_bytes = input[1 + storage_bytes.len ..][0 .. subtree_len * @sizeOf(Node.Parent)];
892 break .{
893 std.mem.bytesAsSlice(Node.Storage, storage_bytes),
894 std.mem.bytesAsSlice(Node.Parent, parents_bytes),
895 };
896 };
897
898 const nodes_len: u8 = @intCast(@min(parents.len - 1, serialized_buffer.storage.len - serialized_len));
899
900 // Remember in case the pipe is empty on next update.
901 ipc_metadata[ipc_metadata_len] = .{
902 .ipc_fd = SavedMetadata.setIpcFd(fd),
903 .start_index = @intCast(serialized_len),
904 .nodes_len = nodes_len,
905 .main_index = @intCast(main_index),
906 };
907 ipc_metadata_len += 1;
908
909 // Mount the root here.
910 copyRoot(main_storage, &storage[0]);
911 if (is_big_endian) main_storage.byteSwap();
912
913 // Copy the rest of the tree to the end.
914 const storage_dest = serialized_buffer.storage[serialized_len..][0..nodes_len];
915 @memcpy(storage_dest, storage[1..][0..nodes_len]);
916
917 // Always little-endian over the pipe.
918 if (is_big_endian) for (storage_dest) |*s| s.byteSwap();
919
920 // Patch up parent pointers taking into account how the subtree is mounted.
921 for (serialized_buffer.parents[serialized_len..][0..nodes_len], parents[1..][0..nodes_len]) |*dest, p| {
922 dest.* = switch (p) {
923 // Fix bad data so the rest of the code does not see `unused`.
924 .none, .unused => .none,
925 // Root node is being mounted here.
926 @as(Node.Parent, @enumFromInt(0)) => @enumFromInt(main_index),
927 // Other nodes mounted at the end.
928 // Don't trust child data; if the data is outside the expected range, ignore the data.
929 // This also handles the case when data was truncated.
930 _ => |off| if (@intFromEnum(off) > nodes_len)
931 .none
932 else
933 @enumFromInt(serialized_len + @intFromEnum(off) - 1),
934 };
338935 }
936
937 serialized_len += nodes_len;
339938 }
340939
341 _ = file.write(self.output_buffer[0..end]) catch {
342 // stop trying to write to this file
343 self.terminal = null;
940 // Save a copy in case any pipes are empty on the next update.
941 @memcpy(serialized_buffer.parents_copy[0..serialized_len], serialized_buffer.parents[0..serialized_len]);
942 @memcpy(serialized_buffer.storage_copy[0..serialized_len], serialized_buffer.storage[0..serialized_len]);
943 @memcpy(ipc_metadata_copy[0..ipc_metadata_len], ipc_metadata[0..ipc_metadata_len]);
944
945 return serialized_len;
946}
947
948fn copyRoot(dest: *Node.Storage, src: *align(1) Node.Storage) void {
949 dest.* = .{
950 .completed_count = src.completed_count,
951 .estimated_total_count = src.estimated_total_count,
952 .name = if (src.name[0] == 0) dest.name else src.name,
344953 };
345 if (self.timer) |*timer| {
346 self.prev_refresh_timestamp = timer.read();
954}
955
956fn findOld(ipc_fd: posix.fd_t, old_metadata: []const SavedMetadata) ?*const SavedMetadata {
957 for (old_metadata) |*m| {
958 if (m.getIpcFd() == ipc_fd)
959 return m;
347960 }
961 return null;
348962}
349963
350pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {
351 const file = self.terminal orelse {
352 std.debug.print(format, args);
353 return;
964fn useSavedIpcData(
965 start_serialized_len: usize,
966 serialized_buffer: *Serialized.Buffer,
967 main_storage: *Node.Storage,
968 main_index: usize,
969 old_metadata: []const SavedMetadata,
970) usize {
971 const parents_copy = &serialized_buffer.parents_copy;
972 const storage_copy = &serialized_buffer.storage_copy;
973 const ipc_metadata = &serialized_buffer.ipc_metadata;
974
975 const ipc_fd = main_storage.getIpcFd().?;
976 const saved_metadata = findOld(ipc_fd, old_metadata) orelse {
977 main_storage.completed_count = 0;
978 main_storage.estimated_total_count = 0;
979 return start_serialized_len;
354980 };
355 self.refresh();
356 file.writer().print(format, args) catch {
357 self.terminal = null;
358 return;
981
982 const start_index = saved_metadata.start_index;
983 const nodes_len = @min(saved_metadata.nodes_len, serialized_buffer.storage.len - start_serialized_len);
984 const old_main_index = saved_metadata.main_index;
985
986 ipc_metadata[ipc_metadata_len] = .{
987 .ipc_fd = SavedMetadata.setIpcFd(ipc_fd),
988 .start_index = @intCast(start_serialized_len),
989 .nodes_len = nodes_len,
990 .main_index = @intCast(main_index),
359991 };
360 self.columns_written = 0;
361}
992 ipc_metadata_len += 1;
993
994 const parents = parents_copy[start_index..][0..nodes_len];
995 const storage = storage_copy[start_index..][0..nodes_len];
362996
363/// Allows the caller to freely write to stderr until unlock_stderr() is called.
364/// During the lock, the progress information is cleared from the terminal.
365pub fn lock_stderr(p: *Progress) void {
366 p.update_mutex.lock();
367 if (p.terminal) |file| {
368 var end: usize = 0;
369 clearWithHeldLock(p, &end);
370 _ = file.write(p.output_buffer[0..end]) catch {
371 // stop trying to write to this file
372 p.terminal = null;
997 copyRoot(main_storage, &storage_copy[old_main_index]);
998
999 @memcpy(serialized_buffer.storage[start_serialized_len..][0..storage.len], storage);
1000
1001 for (serialized_buffer.parents[start_serialized_len..][0..parents.len], parents) |*dest, p| {
1002 dest.* = switch (p) {
1003 .none, .unused => .none,
1004 _ => |prev| d: {
1005 if (@intFromEnum(prev) == old_main_index) {
1006 break :d @enumFromInt(main_index);
1007 } else if (@intFromEnum(prev) > nodes_len) {
1008 break :d .none;
1009 } else {
1010 break :d @enumFromInt(@intFromEnum(prev) - start_index + start_serialized_len);
1011 }
1012 },
3731013 };
3741014 }
375 std.debug.getStderrMutex().lock();
1015
1016 return start_serialized_len + storage.len;
1017}
1018
1019fn computeRedraw(serialized_buffer: *Serialized.Buffer) []u8 {
1020 const serialized = serialize(serialized_buffer);
1021
1022 // Now we can analyze our copy of the graph without atomics, reconstructing
1023 // children lists which do not exist in the canonical data. These are
1024 // needed for tree traversal below.
1025
1026 var children_buffer: [node_storage_buffer_len]Children = undefined;
1027 const children = children_buffer[0..serialized.parents.len];
1028
1029 @memset(children, .{ .child = .none, .sibling = .none });
1030
1031 for (serialized.parents, 0..) |parent, child_index_usize| {
1032 const child_index: Node.Index = @enumFromInt(child_index_usize);
1033 assert(parent != .unused);
1034 const parent_index = parent.unwrap() orelse continue;
1035 const children_node = &children[@intFromEnum(parent_index)];
1036 if (children_node.child.unwrap()) |existing_child_index| {
1037 const existing_child = &children[@intFromEnum(existing_child_index)];
1038 children[@intFromEnum(child_index)].sibling = existing_child.sibling;
1039 existing_child.sibling = child_index.toOptional();
1040 } else {
1041 children_node.child = child_index.toOptional();
1042 }
1043 }
1044
1045 // The strategy is: keep the cursor at the end, and then with every redraw:
1046 // move cursor to beginning of line, move cursor up N lines, erase to end of screen, write
1047
1048 var i: usize = 0;
1049 const buf = global_progress.draw_buffer;
1050
1051 buf[i..][0..start_sync.len].* = start_sync.*;
1052 i += start_sync.len;
1053
1054 switch (global_progress.terminal_mode) {
1055 .off => unreachable,
1056 .ansi_escape_codes => i = computeClear(buf, i),
1057 .windows_api => if (!is_windows) unreachable,
1058 }
1059
1060 global_progress.accumulated_newline_count = 0;
1061 const root_node_index: Node.Index = @enumFromInt(0);
1062 i = computeNode(buf, i, serialized, children, root_node_index);
1063
1064 buf[i..][0..finish_sync.len].* = finish_sync.*;
1065 i += finish_sync.len;
1066
1067 return buf[0..i];
1068}
1069
1070fn computePrefix(
1071 buf: []u8,
1072 start_i: usize,
1073 serialized: Serialized,
1074 children: []const Children,
1075 node_index: Node.Index,
1076) usize {
1077 var i = start_i;
1078 const parent_index = serialized.parents[@intFromEnum(node_index)].unwrap() orelse return i;
1079 if (serialized.parents[@intFromEnum(parent_index)] == .none) return i;
1080 if (@intFromEnum(serialized.parents[@intFromEnum(parent_index)]) == 0 and
1081 serialized.storage[0].name[0] == 0)
1082 {
1083 return i;
1084 }
1085 i = computePrefix(buf, i, serialized, children, parent_index);
1086 if (children[@intFromEnum(parent_index)].sibling == .none) {
1087 const prefix = " ";
1088 const upper_bound_len = prefix.len + line_upper_bound_len;
1089 if (i + upper_bound_len > buf.len) return buf.len;
1090 buf[i..][0..prefix.len].* = prefix.*;
1091 i += prefix.len;
1092 } else {
1093 const upper_bound_len = comptime (TreeSymbol.line.maxByteLen() + line_upper_bound_len);
1094 if (i + upper_bound_len > buf.len) return buf.len;
1095 i = appendTreeSymbol(.line, buf, i);
1096 }
1097 return i;
1098}
1099
1100const line_upper_bound_len = @max(TreeSymbol.tee.maxByteLen(), TreeSymbol.langle.maxByteLen()) +
1101 "[4294967296/4294967296] ".len + Node.max_name_len + finish_sync.len;
1102
1103fn computeNode(
1104 buf: []u8,
1105 start_i: usize,
1106 serialized: Serialized,
1107 children: []const Children,
1108 node_index: Node.Index,
1109) usize {
1110 var i = start_i;
1111 i = computePrefix(buf, i, serialized, children, node_index);
1112
1113 if (i + line_upper_bound_len > buf.len)
1114 return start_i;
1115
1116 const storage = &serialized.storage[@intFromEnum(node_index)];
1117 const estimated_total = storage.estimated_total_count;
1118 const completed_items = storage.completed_count;
1119 const name = if (std.mem.indexOfScalar(u8, &storage.name, 0)) |end| storage.name[0..end] else &storage.name;
1120 const parent = serialized.parents[@intFromEnum(node_index)];
1121
1122 if (parent != .none) p: {
1123 if (@intFromEnum(parent) == 0 and serialized.storage[0].name[0] == 0) {
1124 break :p;
1125 }
1126 if (children[@intFromEnum(node_index)].sibling == .none) {
1127 i = appendTreeSymbol(.langle, buf, i);
1128 } else {
1129 i = appendTreeSymbol(.tee, buf, i);
1130 }
1131 }
1132
1133 const is_empty_root = @intFromEnum(node_index) == 0 and serialized.storage[0].name[0] == 0;
1134 if (!is_empty_root) {
1135 if (name.len != 0 or estimated_total > 0) {
1136 if (estimated_total > 0) {
1137 i += (std.fmt.bufPrint(buf[i..], "[{d}/{d}] ", .{ completed_items, estimated_total }) catch &.{}).len;
1138 } else if (completed_items != 0) {
1139 i += (std.fmt.bufPrint(buf[i..], "[{d}] ", .{completed_items}) catch &.{}).len;
1140 }
1141 if (name.len != 0) {
1142 i += (std.fmt.bufPrint(buf[i..], "{s}", .{name}) catch &.{}).len;
1143 }
1144 }
1145
1146 i = @min(global_progress.cols + start_i, i);
1147 buf[i] = '\n';
1148 i += 1;
1149 global_progress.accumulated_newline_count += 1;
1150 }
1151
1152 if (global_progress.withinRowLimit()) {
1153 if (children[@intFromEnum(node_index)].child.unwrap()) |child| {
1154 i = computeNode(buf, i, serialized, children, child);
1155 }
1156 }
1157
1158 if (global_progress.withinRowLimit()) {
1159 if (children[@intFromEnum(node_index)].sibling.unwrap()) |sibling| {
1160 i = computeNode(buf, i, serialized, children, sibling);
1161 }
1162 }
1163
1164 return i;
3761165}
3771166
378pub fn unlock_stderr(p: *Progress) void {
379 std.debug.getStderrMutex().unlock();
380 p.update_mutex.unlock();
1167fn withinRowLimit(p: *Progress) bool {
1168 // The +2 here is so that the PS1 is not scrolled off the top of the terminal.
1169 // one because we keep the cursor on the next line
1170 // one more to account for the PS1
1171 return p.accumulated_newline_count + 2 < p.rows;
3811172}
3821173
383fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void {
384 if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {
385 const amt = written.len;
386 end.* += amt;
387 self.columns_written += amt;
1174fn write(buf: []const u8) anyerror!void {
1175 try global_progress.terminal.writeAll(buf);
1176 global_progress.written_newline_count = global_progress.accumulated_newline_count;
1177}
1178
1179var remaining_write_trash_bytes: usize = 0;
1180
1181fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void {
1182 // Byteswap if necessary to ensure little endian over the pipe. This is
1183 // needed because the parent or child process might be running in qemu.
1184 if (is_big_endian) for (serialized.storage) |*s| s.byteSwap();
1185
1186 assert(serialized.parents.len == serialized.storage.len);
1187 const serialized_len: u8 = @intCast(serialized.parents.len);
1188 const header = std.mem.asBytes(&serialized_len);
1189 const storage = std.mem.sliceAsBytes(serialized.storage);
1190 const parents = std.mem.sliceAsBytes(serialized.parents);
1191
1192 var vecs: [3]posix.iovec_const = .{
1193 .{ .base = header.ptr, .len = header.len },
1194 .{ .base = storage.ptr, .len = storage.len },
1195 .{ .base = parents.ptr, .len = parents.len },
1196 };
1197
1198 while (remaining_write_trash_bytes > 0) {
1199 // We do this in a separate write call to give a better chance for the
1200 // writev below to be in a single packet.
1201 const n = @min(parents.len, remaining_write_trash_bytes);
1202 if (posix.write(fd, parents[0..n])) |written| {
1203 remaining_write_trash_bytes -= written;
1204 continue;
1205 } else |err| switch (err) {
1206 error.WouldBlock => return,
1207 error.BrokenPipe => return error.BrokenPipe,
1208 else => |e| {
1209 std.log.debug("failed to send progress to parent process: {s}", .{@errorName(e)});
1210 return error.BrokenPipe;
1211 },
1212 }
1213 }
1214
1215 // If this write would block we do not want to keep trying, but we need to
1216 // know if a partial message was written.
1217 if (posix.writev(fd, &vecs)) |written| {
1218 const total = header.len + storage.len + parents.len;
1219 if (written < total) {
1220 remaining_write_trash_bytes = total - written;
1221 }
3881222 } else |err| switch (err) {
389 error.NoSpaceLeft => {
390 self.columns_written += self.output_buffer.len - end.*;
391 end.* = self.output_buffer.len;
392 const suffix = "... ";
393 @memcpy(self.output_buffer[self.output_buffer.len - suffix.len ..], suffix);
1223 error.WouldBlock => {},
1224 error.BrokenPipe => return error.BrokenPipe,
1225 else => |e| {
1226 std.log.debug("failed to send progress to parent process: {s}", .{@errorName(e)});
1227 return error.BrokenPipe;
3941228 },
3951229 }
3961230}
3971231
398test "basic functionality" {
399 var disable = true;
400 _ = &disable;
401 if (disable) {
402 // This test is disabled because it uses time.sleep() and is therefore slow. It also
403 // prints bogus progress data to stderr.
404 return error.SkipZigTest;
405 }
406 var progress = Progress{};
407 const root_node = progress.start("", 100);
408 defer root_node.end();
409
410 const speed_factor = std.time.ns_per_ms;
411
412 const sub_task_names = [_][]const u8{
413 "reticulating splines",
414 "adjusting shoes",
415 "climbing towers",
416 "pouring juice",
417 };
418 var next_sub_task: usize = 0;
1232fn maybeUpdateSize(resize_flag: bool) void {
1233 if (!resize_flag) return;
4191234
420 var i: usize = 0;
421 while (i < 100) : (i += 1) {
422 var node = root_node.start(sub_task_names[next_sub_task], 5);
423 node.activate();
424 next_sub_task = (next_sub_task + 1) % sub_task_names.len;
1235 const fd = global_progress.terminal.handle;
4251236
426 node.completeOne();
427 std.time.sleep(5 * speed_factor);
428 node.completeOne();
429 node.completeOne();
430 std.time.sleep(5 * speed_factor);
431 node.completeOne();
432 node.completeOne();
433 std.time.sleep(5 * speed_factor);
1237 if (is_windows) {
1238 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
4341239
435 node.end();
1240 if (windows.kernel32.GetConsoleScreenBufferInfo(fd, &info) != windows.FALSE) {
1241 // In the old Windows console, dwSize.Y is the line count of the
1242 // entire scrollback buffer, so we use this instead so that we
1243 // always get the size of the screen.
1244 const screen_height = info.srWindow.Bottom - info.srWindow.Top;
1245 global_progress.rows = @intCast(screen_height);
1246 global_progress.cols = @intCast(info.dwSize.X);
1247 } else {
1248 std.log.debug("failed to determine terminal size; using conservative guess 80x25", .{});
1249 global_progress.rows = 25;
1250 global_progress.cols = 80;
1251 }
1252 } else {
1253 var winsize: posix.winsize = .{
1254 .ws_row = 0,
1255 .ws_col = 0,
1256 .ws_xpixel = 0,
1257 .ws_ypixel = 0,
1258 };
4361259
437 std.time.sleep(5 * speed_factor);
438 }
439 {
440 var node = root_node.start("this is a really long name designed to activate the truncation code. let's find out if it works", 0);
441 node.activate();
442 std.time.sleep(10 * speed_factor);
443 progress.refresh();
444 std.time.sleep(10 * speed_factor);
445 node.end();
1260 const err = posix.system.ioctl(fd, posix.T.IOCGWINSZ, @intFromPtr(&winsize));
1261 if (posix.errno(err) == .SUCCESS) {
1262 global_progress.rows = winsize.ws_row;
1263 global_progress.cols = winsize.ws_col;
1264 } else {
1265 std.log.debug("failed to determine terminal size; using conservative guess 80x25", .{});
1266 global_progress.rows = 25;
1267 global_progress.cols = 80;
1268 }
4461269 }
4471270}
1271
1272fn handleSigWinch(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.C) void {
1273 _ = info;
1274 _ = ctx_ptr;
1275 assert(sig == posix.SIG.WINCH);
1276 global_progress.redraw_event.set();
1277}
1278
1279const have_sigwinch = switch (builtin.os.tag) {
1280 .linux,
1281 .plan9,
1282 .solaris,
1283 .netbsd,
1284 .openbsd,
1285 .haiku,
1286 .macos,
1287 .ios,
1288 .watchos,
1289 .tvos,
1290 .visionos,
1291 .dragonfly,
1292 .freebsd,
1293 => true,
1294
1295 else => false,
1296};
1297
1298var stderr_mutex: std.Thread.Mutex = .{};
lib/std/debug.zig+24-9
......@@ -77,19 +77,28 @@ const PdbOrDwarf = union(enum) {
7777 }
7878};
7979
80var stderr_mutex = std.Thread.Mutex{};
80/// Allows the caller to freely write to stderr until `unlockStdErr` is called.
81///
82/// During the lock, any `std.Progress` information is cleared from the terminal.
83pub fn lockStdErr() void {
84 std.Progress.lockStdErr();
85}
86
87pub fn unlockStdErr() void {
88 std.Progress.unlockStdErr();
89}
8190
8291/// Print to stderr, unbuffered, and silently returning on failure. Intended
8392/// for use in "printf debugging." Use `std.log` functions for proper logging.
8493pub fn print(comptime fmt: []const u8, args: anytype) void {
85 stderr_mutex.lock();
86 defer stderr_mutex.unlock();
94 lockStdErr();
95 defer unlockStdErr();
8796 const stderr = io.getStdErr().writer();
8897 nosuspend stderr.print(fmt, args) catch return;
8998}
9099
91100pub fn getStderrMutex() *std.Thread.Mutex {
92 return &stderr_mutex;
101 @compileError("deprecated. call std.debug.lockStdErr() and std.debug.unlockStdErr() instead which will integrate properly with std.Progress");
93102}
94103
95104/// TODO multithreaded awareness
......@@ -107,8 +116,8 @@ pub fn getSelfDebugInfo() !*DebugInfo {
107116/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.
108117/// Obtains the stderr mutex while dumping.
109118pub fn dump_hex(bytes: []const u8) void {
110 stderr_mutex.lock();
111 defer stderr_mutex.unlock();
119 lockStdErr();
120 defer unlockStdErr();
112121 dump_hex_fallible(bytes) catch {};
113122}
114123
......@@ -2750,13 +2759,19 @@ pub const Trace = ConfigurableTrace(2, 4, builtin.mode == .Debug);
27502759
27512760pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize, comptime is_enabled: bool) type {
27522761 return struct {
2753 addrs: [actual_size][stack_frame_count]usize = undefined,
2754 notes: [actual_size][]const u8 = undefined,
2755 index: Index = 0,
2762 addrs: [actual_size][stack_frame_count]usize,
2763 notes: [actual_size][]const u8,
2764 index: Index,
27562765
27572766 const actual_size = if (enabled) size else 0;
27582767 const Index = if (enabled) usize else u0;
27592768
2769 pub const init: @This() = .{
2770 .addrs = undefined,
2771 .notes = undefined,
2772 .index = 0,
2773 };
2774
27602775 pub const enabled = is_enabled;
27612776
27622777 pub const add = if (enabled) addNoInline else addNoOp;
lib/std/fmt.zig+35-24
......@@ -9,7 +9,7 @@ const assert = std.debug.assert;
99const mem = std.mem;
1010const unicode = std.unicode;
1111const meta = std.meta;
12const lossyCast = std.math.lossyCast;
12const lossyCast = math.lossyCast;
1313const expectFmt = std.testing.expectFmt;
1414
1515pub const default_max_depth = 3;
......@@ -1494,10 +1494,20 @@ pub fn Formatter(comptime format_fn: anytype) type {
14941494/// Ignores '_' character in `buf`.
14951495/// See also `parseUnsigned`.
14961496pub fn parseInt(comptime T: type, buf: []const u8, base: u8) ParseIntError!T {
1497 return parseIntWithGenericCharacter(T, u8, buf, base);
1498}
1499
1500/// Like `parseInt`, but with a generic `Character` type.
1501pub fn parseIntWithGenericCharacter(
1502 comptime Result: type,
1503 comptime Character: type,
1504 buf: []const Character,
1505 base: u8,
1506) ParseIntError!Result {
14971507 if (buf.len == 0) return error.InvalidCharacter;
1498 if (buf[0] == '+') return parseWithSign(T, buf[1..], base, .pos);
1499 if (buf[0] == '-') return parseWithSign(T, buf[1..], base, .neg);
1500 return parseWithSign(T, buf, base, .pos);
1508 if (buf[0] == '+') return parseIntWithSign(Result, Character, buf[1..], base, .pos);
1509 if (buf[0] == '-') return parseIntWithSign(Result, Character, buf[1..], base, .neg);
1510 return parseIntWithSign(Result, Character, buf, base, .pos);
15011511}
15021512
15031513test parseInt {
......@@ -1560,12 +1570,13 @@ test parseInt {
15601570 try std.testing.expectEqual(@as(i5, -16), try std.fmt.parseInt(i5, "-10", 16));
15611571}
15621572
1563fn parseWithSign(
1564 comptime T: type,
1565 buf: []const u8,
1573fn parseIntWithSign(
1574 comptime Result: type,
1575 comptime Character: type,
1576 buf: []const Character,
15661577 base: u8,
15671578 comptime sign: enum { pos, neg },
1568) ParseIntError!T {
1579) ParseIntError!Result {
15691580 if (buf.len == 0) return error.InvalidCharacter;
15701581
15711582 var buf_base = base;
......@@ -1575,7 +1586,7 @@ fn parseWithSign(
15751586 buf_base = 10;
15761587 // Detect the base by looking at buf prefix.
15771588 if (buf.len > 2 and buf[0] == '0') {
1578 switch (std.ascii.toLower(buf[1])) {
1589 if (math.cast(u8, buf[1])) |c| switch (std.ascii.toLower(c)) {
15791590 'b' => {
15801591 buf_base = 2;
15811592 buf_start = buf[2..];
......@@ -1589,7 +1600,7 @@ fn parseWithSign(
15891600 buf_start = buf[2..];
15901601 },
15911602 else => {},
1592 }
1603 };
15931604 }
15941605 }
15951606
......@@ -1598,33 +1609,33 @@ fn parseWithSign(
15981609 .neg => math.sub,
15991610 };
16001611
1601 // accumulate into U which is always 8 bits or larger. this prevents
1602 // `buf_base` from overflowing T.
1603 const info = @typeInfo(T);
1604 const U = std.meta.Int(info.Int.signedness, @max(8, info.Int.bits));
1605 var x: U = 0;
1612 // accumulate into Accumulate which is always 8 bits or larger. this prevents
1613 // `buf_base` from overflowing Result.
1614 const info = @typeInfo(Result);
1615 const Accumulate = std.meta.Int(info.Int.signedness, @max(8, info.Int.bits));
1616 var accumulate: Accumulate = 0;
16061617
16071618 if (buf_start[0] == '_' or buf_start[buf_start.len - 1] == '_') return error.InvalidCharacter;
16081619
16091620 for (buf_start) |c| {
16101621 if (c == '_') continue;
1611 const digit = try charToDigit(c, buf_base);
1612 if (x != 0) {
1613 x = try math.mul(U, x, math.cast(U, buf_base) orelse return error.Overflow);
1622 const digit = try charToDigit(math.cast(u8, c) orelse return error.InvalidCharacter, buf_base);
1623 if (accumulate != 0) {
1624 accumulate = try math.mul(Accumulate, accumulate, math.cast(Accumulate, buf_base) orelse return error.Overflow);
16141625 } else if (sign == .neg) {
16151626 // The first digit of a negative number.
16161627 // Consider parsing "-4" as an i3.
16171628 // This should work, but positive 4 overflows i3, so we can't cast the digit to T and subtract.
1618 x = math.cast(U, -@as(i8, @intCast(digit))) orelse return error.Overflow;
1629 accumulate = math.cast(Accumulate, -@as(i8, @intCast(digit))) orelse return error.Overflow;
16191630 continue;
16201631 }
1621 x = try add(U, x, math.cast(U, digit) orelse return error.Overflow);
1632 accumulate = try add(Accumulate, accumulate, math.cast(Accumulate, digit) orelse return error.Overflow);
16221633 }
16231634
1624 return if (T == U)
1625 x
1635 return if (Result == Accumulate)
1636 accumulate
16261637 else
1627 math.cast(T, x) orelse return error.Overflow;
1638 math.cast(Result, accumulate) orelse return error.Overflow;
16281639}
16291640
16301641/// Parses the string `buf` as unsigned representation in the specified base
......@@ -1639,7 +1650,7 @@ fn parseWithSign(
16391650/// Ignores '_' character in `buf`.
16401651/// See also `parseInt`.
16411652pub fn parseUnsigned(comptime T: type, buf: []const u8, base: u8) ParseIntError!T {
1642 return parseWithSign(T, buf, base, .pos);
1653 return parseIntWithSign(T, u8, buf, base, .pos);
16431654}
16441655
16451656test parseUnsigned {
lib/std/io/tty.zig+1-1
......@@ -24,7 +24,7 @@ pub fn detectConfig(file: File) Config {
2424
2525 if (native_os == .windows and file.isTty()) {
2626 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
27 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) {
27 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) == windows.FALSE) {
2828 return if (force_color == true) .escape_codes else .no_color;
2929 }
3030 return .{ .windows_api = .{
lib/std/json/dynamic.zig+2-2
......@@ -52,8 +52,8 @@ pub const Value = union(enum) {
5252 }
5353
5454 pub fn dump(self: Value) void {
55 std.debug.getStderrMutex().lock();
56 defer std.debug.getStderrMutex().unlock();
55 std.debug.lockStdErr();
56 defer std.debug.unlockStdErr();
5757
5858 const stderr = std.io.getStdErr().writer();
5959 stringify(self, .{}, stderr) catch return;
lib/std/log.zig+4-4
......@@ -45,8 +45,8 @@
4545//! const prefix = "[" ++ comptime level.asText() ++ "] " ++ scope_prefix;
4646//!
4747//! // Print the message to stderr, silently ignoring any errors
48//! std.debug.getStderrMutex().lock();
49//! defer std.debug.getStderrMutex().unlock();
48//! std.debug.lockStdErr();
49//! defer std.debug.unlockStdErr();
5050//! const stderr = std.io.getStdErr().writer();
5151//! nosuspend stderr.print(prefix ++ format ++ "\n", args) catch return;
5252//! }
......@@ -152,8 +152,8 @@ pub fn defaultLog(
152152 var bw = std.io.bufferedWriter(stderr);
153153 const writer = bw.writer();
154154
155 std.debug.getStderrMutex().lock();
156 defer std.debug.getStderrMutex().unlock();
155 std.debug.lockStdErr();
156 defer std.debug.unlockStdErr();
157157 nosuspend {
158158 writer.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return;
159159 bw.flush() catch return;
lib/std/os/windows/kernel32.zig+9
......@@ -175,6 +175,15 @@ pub extern "kernel32" fn FillConsoleOutputCharacterW(hConsoleOutput: HANDLE, cCh
175175pub extern "kernel32" fn FillConsoleOutputAttribute(hConsoleOutput: HANDLE, wAttribute: WORD, nLength: DWORD, dwWriteCoord: COORD, lpNumberOfAttrsWritten: *DWORD) callconv(WINAPI) BOOL;
176176pub extern "kernel32" fn SetConsoleCursorPosition(hConsoleOutput: HANDLE, dwCursorPosition: COORD) callconv(WINAPI) BOOL;
177177
178pub extern "kernel32" fn WriteConsoleW(hConsoleOutput: HANDLE, lpBuffer: [*]const u16, nNumberOfCharsToWrite: DWORD, lpNumberOfCharsWritten: ?*DWORD, lpReserved: ?LPVOID) callconv(WINAPI) BOOL;
179pub extern "kernel32" fn ReadConsoleOutputCharacterW(
180 hConsoleOutput: windows.HANDLE,
181 lpCharacter: [*]u16,
182 nLength: windows.DWORD,
183 dwReadCoord: windows.COORD,
184 lpNumberOfCharsRead: *windows.DWORD,
185) callconv(windows.WINAPI) windows.BOOL;
186
178187pub extern "kernel32" fn GetCurrentDirectoryW(nBufferLength: DWORD, lpBuffer: ?[*]WCHAR) callconv(WINAPI) DWORD;
179188
180189pub extern "kernel32" fn GetCurrentThread() callconv(WINAPI) HANDLE;
lib/std/process.zig+151-11
......@@ -431,6 +431,26 @@ pub fn hasEnvVarConstant(comptime key: []const u8) bool {
431431 }
432432}
433433
434pub const ParseEnvVarIntError = std.fmt.ParseIntError || error{EnvironmentVariableNotFound};
435
436/// Parses an environment variable as an integer.
437///
438/// Since the key is comptime-known, no allocation is needed.
439///
440/// On Windows, `key` must be valid UTF-8.
441pub fn parseEnvVarInt(comptime key: []const u8, comptime I: type, base: u8) ParseEnvVarIntError!I {
442 if (native_os == .windows) {
443 const key_w = comptime std.unicode.utf8ToUtf16LeStringLiteral(key);
444 const text = getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
445 return std.fmt.parseIntWithGenericCharacter(I, u16, text, base);
446 } else if (native_os == .wasi and !builtin.link_libc) {
447 @compileError("parseEnvVarInt is not supported for WASI without libc");
448 } else {
449 const text = posix.getenv(key) orelse return error.EnvironmentVariableNotFound;
450 return std.fmt.parseInt(I, text, base);
451 }
452}
453
434454pub const HasEnvVarError = error{
435455 OutOfMemory,
436456
......@@ -1740,6 +1760,7 @@ pub fn cleanExit() void {
17401760 if (builtin.mode == .Debug) {
17411761 return;
17421762 } else {
1763 std.debug.lockStdErr();
17431764 exit(0);
17441765 }
17451766}
......@@ -1790,24 +1811,143 @@ test raiseFileDescriptorLimit {
17901811 raiseFileDescriptorLimit();
17911812}
17921813
1793pub fn createNullDelimitedEnvMap(arena: mem.Allocator, env_map: *const EnvMap) ![:null]?[*:0]u8 {
1794 const envp_count = env_map.count();
1814pub const CreateEnvironOptions = struct {
1815 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
1816 /// If non-null, negative means to remove the environment variable, and >= 0
1817 /// means to provide it with the given integer.
1818 zig_progress_fd: ?i32 = null,
1819};
1820
1821/// Creates a null-deliminated environment variable block in the format
1822/// expected by POSIX, from a hash map plus options.
1823pub fn createEnvironFromMap(
1824 arena: Allocator,
1825 map: *const EnvMap,
1826 options: CreateEnvironOptions,
1827) Allocator.Error![:null]?[*:0]u8 {
1828 const ZigProgressAction = enum { nothing, edit, delete, add };
1829 const zig_progress_action: ZigProgressAction = a: {
1830 const fd = options.zig_progress_fd orelse break :a .nothing;
1831 const contains = map.get("ZIG_PROGRESS") != null;
1832 if (fd >= 0) {
1833 break :a if (contains) .edit else .add;
1834 } else {
1835 if (contains) break :a .delete;
1836 }
1837 break :a .nothing;
1838 };
1839
1840 const envp_count: usize = c: {
1841 var count: usize = map.count();
1842 switch (zig_progress_action) {
1843 .add => count += 1,
1844 .delete => count -= 1,
1845 .nothing, .edit => {},
1846 }
1847 break :c count;
1848 };
1849
17951850 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
1851 var i: usize = 0;
1852
1853 if (zig_progress_action == .add) {
1854 envp_buf[i] = try std.fmt.allocPrintZ(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?});
1855 i += 1;
1856 }
1857
17961858 {
1797 var it = env_map.iterator();
1798 var i: usize = 0;
1799 while (it.next()) |pair| : (i += 1) {
1800 const env_buf = try arena.allocSentinel(u8, pair.key_ptr.len + pair.value_ptr.len + 1, 0);
1801 @memcpy(env_buf[0..pair.key_ptr.len], pair.key_ptr.*);
1802 env_buf[pair.key_ptr.len] = '=';
1803 @memcpy(env_buf[pair.key_ptr.len + 1 ..][0..pair.value_ptr.len], pair.value_ptr.*);
1804 envp_buf[i] = env_buf.ptr;
1859 var it = map.iterator();
1860 while (it.next()) |pair| {
1861 if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) {
1862 .add => unreachable,
1863 .delete => continue,
1864 .edit => {
1865 envp_buf[i] = try std.fmt.allocPrintZ(arena, "{s}={d}", .{
1866 pair.key_ptr.*, options.zig_progress_fd.?,
1867 });
1868 i += 1;
1869 continue;
1870 },
1871 .nothing => {},
1872 };
1873
1874 envp_buf[i] = try std.fmt.allocPrintZ(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* });
1875 i += 1;
1876 }
1877 }
1878
1879 assert(i == envp_count);
1880 return envp_buf;
1881}
1882
1883/// Creates a null-deliminated environment variable block in the format
1884/// expected by POSIX, from a hash map plus options.
1885pub fn createEnvironFromExisting(
1886 arena: Allocator,
1887 existing: [*:null]const ?[*:0]const u8,
1888 options: CreateEnvironOptions,
1889) Allocator.Error![:null]?[*:0]u8 {
1890 const existing_count, const contains_zig_progress = c: {
1891 var count: usize = 0;
1892 var contains = false;
1893 while (existing[count]) |line| : (count += 1) {
1894 contains = contains or mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS");
1895 }
1896 break :c .{ count, contains };
1897 };
1898 const ZigProgressAction = enum { nothing, edit, delete, add };
1899 const zig_progress_action: ZigProgressAction = a: {
1900 const fd = options.zig_progress_fd orelse break :a .nothing;
1901 if (fd >= 0) {
1902 break :a if (contains_zig_progress) .edit else .add;
1903 } else {
1904 if (contains_zig_progress) break :a .delete;
1905 }
1906 break :a .nothing;
1907 };
1908
1909 const envp_count: usize = c: {
1910 var count: usize = existing_count;
1911 switch (zig_progress_action) {
1912 .add => count += 1,
1913 .delete => count -= 1,
1914 .nothing, .edit => {},
18051915 }
1806 assert(i == envp_count);
1916 break :c count;
1917 };
1918
1919 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
1920 var i: usize = 0;
1921 var existing_index: usize = 0;
1922
1923 if (zig_progress_action == .add) {
1924 envp_buf[i] = try std.fmt.allocPrintZ(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?});
1925 i += 1;
1926 }
1927
1928 while (existing[existing_index]) |line| : (existing_index += 1) {
1929 if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
1930 .add => unreachable,
1931 .delete => continue,
1932 .edit => {
1933 envp_buf[i] = try std.fmt.allocPrintZ(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?});
1934 i += 1;
1935 continue;
1936 },
1937 .nothing => {},
1938 };
1939 envp_buf[i] = try arena.dupeZ(u8, mem.span(line));
1940 i += 1;
18071941 }
1942
1943 assert(i == envp_count);
18081944 return envp_buf;
18091945}
18101946
1947pub fn createNullDelimitedEnvMap(arena: mem.Allocator, env_map: *const EnvMap) Allocator.Error![:null]?[*:0]u8 {
1948 return createEnvironFromMap(arena, env_map, .{});
1949}
1950
18111951test createNullDelimitedEnvMap {
18121952 const allocator = testing.allocator;
18131953 var envmap = EnvMap.init(allocator);
lib/std/process/Child.zig+64-25
......@@ -12,6 +12,7 @@ const EnvMap = std.process.EnvMap;
1212const maxInt = std.math.maxInt;
1313const assert = std.debug.assert;
1414const native_os = builtin.os.tag;
15const Allocator = std.mem.Allocator;
1516const ChildProcess = @This();
1617
1718pub const Id = switch (native_os) {
......@@ -92,6 +93,16 @@ request_resource_usage_statistics: bool = false,
9293/// `spawn`.
9394resource_usage_statistics: ResourceUsageStatistics = .{},
9495
96/// When populated, a pipe will be created for the child process to
97/// communicate progress back to the parent. The file descriptor of the
98/// write end of the pipe will be specified in the `ZIG_PROGRESS`
99/// environment variable inside the child process. The progress reported by
100/// the child will be attached to this progress node in the parent process.
101///
102/// The child's progress tree will be grafted into the parent's progress tree,
103/// by substituting this node with the child's root node.
104progress_node: std.Progress.Node = .{ .index = .none },
105
95106pub const ResourceUsageStatistics = struct {
96107 rusage: @TypeOf(rusage_init) = rusage_init,
97108
......@@ -205,9 +216,9 @@ pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess {
205216 .stdin = null,
206217 .stdout = null,
207218 .stderr = null,
208 .stdin_behavior = StdIo.Inherit,
209 .stdout_behavior = StdIo.Inherit,
210 .stderr_behavior = StdIo.Inherit,
219 .stdin_behavior = .Inherit,
220 .stdout_behavior = .Inherit,
221 .stderr_behavior = .Inherit,
211222 .expand_arg0 = .no_expand,
212223 };
213224}
......@@ -538,22 +549,22 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
538549 // turns out, we `dup2` everything anyway, so there's no need!
539550 const pipe_flags: posix.O = .{ .CLOEXEC = true };
540551
541 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined;
542 errdefer if (self.stdin_behavior == StdIo.Pipe) {
552 const stdin_pipe = if (self.stdin_behavior == .Pipe) try posix.pipe2(pipe_flags) else undefined;
553 errdefer if (self.stdin_behavior == .Pipe) {
543554 destroyPipe(stdin_pipe);
544555 };
545556
546 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined;
547 errdefer if (self.stdout_behavior == StdIo.Pipe) {
557 const stdout_pipe = if (self.stdout_behavior == .Pipe) try posix.pipe2(pipe_flags) else undefined;
558 errdefer if (self.stdout_behavior == .Pipe) {
548559 destroyPipe(stdout_pipe);
549560 };
550561
551 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined;
552 errdefer if (self.stderr_behavior == StdIo.Pipe) {
562 const stderr_pipe = if (self.stderr_behavior == .Pipe) try posix.pipe2(pipe_flags) else undefined;
563 errdefer if (self.stderr_behavior == .Pipe) {
553564 destroyPipe(stderr_pipe);
554565 };
555566
556 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
567 const any_ignore = (self.stdin_behavior == .Ignore or self.stdout_behavior == .Ignore or self.stderr_behavior == .Ignore);
557568 const dev_null_fd = if (any_ignore)
558569 posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) {
559570 error.PathAlreadyExists => unreachable,
......@@ -572,6 +583,16 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
572583 if (any_ignore) posix.close(dev_null_fd);
573584 }
574585
586 const prog_pipe: [2]posix.fd_t = p: {
587 if (self.progress_node.index == .none) {
588 break :p .{ -1, -1 };
589 } else {
590 // We use CLOEXEC for the same reason as in `pipe_flags`.
591 break :p try posix.pipe2(.{ .NONBLOCK = true, .CLOEXEC = true });
592 }
593 };
594 errdefer destroyPipe(prog_pipe);
595
575596 var arena_allocator = std.heap.ArenaAllocator.init(self.allocator);
576597 defer arena_allocator.deinit();
577598 const arena = arena_allocator.allocator();
......@@ -588,16 +609,25 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
588609 const argv_buf = try arena.allocSentinel(?[*:0]const u8, self.argv.len, null);
589610 for (self.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
590611
591 const envp = m: {
612 const prog_fileno = 3;
613 comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno);
614
615 const envp: [*:null]const ?[*:0]const u8 = m: {
616 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
592617 if (self.env_map) |env_map| {
593 const envp_buf = try process.createNullDelimitedEnvMap(arena, env_map);
594 break :m envp_buf.ptr;
618 break :m (try process.createEnvironFromMap(arena, env_map, .{
619 .zig_progress_fd = prog_fd,
620 })).ptr;
595621 } else if (builtin.link_libc) {
596 break :m std.c.environ;
622 break :m (try process.createEnvironFromExisting(arena, std.c.environ, .{
623 .zig_progress_fd = prog_fd,
624 })).ptr;
597625 } else if (builtin.output_mode == .Exe) {
598626 // Then we have Zig start code and this works.
599627 // TODO type-safety for null-termination of `os.environ`.
600 break :m @as([*:null]const ?[*:0]const u8, @ptrCast(std.os.environ.ptr));
628 break :m (try process.createEnvironFromExisting(arena, @ptrCast(std.os.environ.ptr), .{
629 .zig_progress_fd = prog_fd,
630 })).ptr;
601631 } else {
602632 // TODO come up with a solution for this.
603633 @compileError("missing std lib enhancement: ChildProcess implementation has no way to collect the environment variables to forward to the child process");
......@@ -631,6 +661,10 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
631661 posix.chdir(cwd) catch |err| forkChildErrReport(err_pipe[1], err);
632662 }
633663
664 // Must happen after fchdir above, the cwd file descriptor might be
665 // equal to prog_fileno and be clobbered by this dup2 call.
666 if (prog_pipe[1] != -1) posix.dup2(prog_pipe[1], prog_fileno) catch |err| forkChildErrReport(err_pipe[1], err);
667
634668 if (self.gid) |gid| {
635669 posix.setregid(gid, gid) catch |err| forkChildErrReport(err_pipe[1], err);
636670 }
......@@ -648,18 +682,18 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
648682
649683 // we are the parent
650684 const pid: i32 = @intCast(pid_result);
651 if (self.stdin_behavior == StdIo.Pipe) {
652 self.stdin = File{ .handle = stdin_pipe[1] };
685 if (self.stdin_behavior == .Pipe) {
686 self.stdin = .{ .handle = stdin_pipe[1] };
653687 } else {
654688 self.stdin = null;
655689 }
656 if (self.stdout_behavior == StdIo.Pipe) {
657 self.stdout = File{ .handle = stdout_pipe[0] };
690 if (self.stdout_behavior == .Pipe) {
691 self.stdout = .{ .handle = stdout_pipe[0] };
658692 } else {
659693 self.stdout = null;
660694 }
661 if (self.stderr_behavior == StdIo.Pipe) {
662 self.stderr = File{ .handle = stderr_pipe[0] };
695 if (self.stderr_behavior == .Pipe) {
696 self.stderr = .{ .handle = stderr_pipe[0] };
663697 } else {
664698 self.stderr = null;
665699 }
......@@ -668,15 +702,20 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
668702 self.err_pipe = err_pipe;
669703 self.term = null;
670704
671 if (self.stdin_behavior == StdIo.Pipe) {
705 if (self.stdin_behavior == .Pipe) {
672706 posix.close(stdin_pipe[0]);
673707 }
674 if (self.stdout_behavior == StdIo.Pipe) {
708 if (self.stdout_behavior == .Pipe) {
675709 posix.close(stdout_pipe[1]);
676710 }
677 if (self.stderr_behavior == StdIo.Pipe) {
711 if (self.stderr_behavior == .Pipe) {
678712 posix.close(stderr_pipe[1]);
679713 }
714
715 if (prog_pipe[1] != -1) {
716 posix.close(prog_pipe[1]);
717 }
718 self.progress_node.setIpcFd(prog_pipe[0]);
680719}
681720
682721fn spawnWindows(self: *ChildProcess) SpawnError!void {
......@@ -962,7 +1001,7 @@ fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !
9621001}
9631002
9641003fn destroyPipe(pipe: [2]posix.fd_t) void {
965 posix.close(pipe[0]);
1004 if (pipe[0] != -1) posix.close(pipe[0]);
9661005 if (pipe[0] != pipe[1]) posix.close(pipe[1]);
9671006}
9681007
lib/std/zig.zig+1-1
......@@ -718,7 +718,7 @@ pub const LazySrcLoc = union(enum) {
718718 /// where in semantic analysis the value got set.
719719 pub const TracedOffset = struct {
720720 x: i32,
721 trace: std.debug.Trace = .{},
721 trace: std.debug.Trace = std.debug.Trace.init,
722722
723723 const want_tracing = false;
724724 };
lib/std/zig/ErrorBundle.zig+2-2
......@@ -155,8 +155,8 @@ pub const RenderOptions = struct {
155155};
156156
157157pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions) void {
158 std.debug.getStderrMutex().lock();
159 defer std.debug.getStderrMutex().unlock();
158 std.debug.lockStdErr();
159 defer std.debug.unlockStdErr();
160160 const stderr = std.io.getStdErr();
161161 return renderToWriter(eb, options, stderr.writer()) catch return;
162162}
lib/std/zig/Server.zig-2
......@@ -14,8 +14,6 @@ pub const Message = struct {
1414 zig_version,
1515 /// Body is an ErrorBundle.
1616 error_bundle,
17 /// Body is a UTF-8 string.
18 progress,
1917 /// Body is a EmitBinPath.
2018 emit_bin_path,
2119 /// Body is a TestMetadata
src/Compilation.zig+44-72
......@@ -1273,8 +1273,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
12731273 if (options.verbose_llvm_cpu_features) {
12741274 if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: {
12751275 const target = options.root_mod.resolved_target.result;
1276 std.debug.getStderrMutex().lock();
1277 defer std.debug.getStderrMutex().unlock();
1276 std.debug.lockStdErr();
1277 defer std.debug.unlockStdErr();
12781278 const stderr = std.io.getStdErr().writer();
12791279 nosuspend {
12801280 stderr.print("compilation: {s}\n", .{options.root_name}) catch break :print;
......@@ -1934,7 +1934,7 @@ pub fn getTarget(self: Compilation) Target {
19341934/// Only legal to call when cache mode is incremental and a link file is present.
19351935pub fn hotCodeSwap(
19361936 comp: *Compilation,
1937 prog_node: *std.Progress.Node,
1937 prog_node: std.Progress.Node,
19381938 pid: std.process.Child.Id,
19391939) !void {
19401940 const lf = comp.bin_file.?;
......@@ -1966,7 +1966,7 @@ fn cleanupAfterUpdate(comp: *Compilation) void {
19661966}
19671967
19681968/// Detect changes to source files, perform semantic analysis, and update the output files.
1969pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void {
1969pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
19701970 const tracy_trace = trace(@src());
19711971 defer tracy_trace.end();
19721972
......@@ -2256,7 +2256,7 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void
22562256 }
22572257}
22582258
2259fn flush(comp: *Compilation, arena: Allocator, prog_node: *std.Progress.Node) !void {
2259fn flush(comp: *Compilation, arena: Allocator, prog_node: std.Progress.Node) !void {
22602260 if (comp.bin_file) |lf| {
22612261 // This is needed before reading the error flags.
22622262 lf.flush(arena, prog_node) catch |err| switch (err) {
......@@ -2566,13 +2566,11 @@ pub fn emitLlvmObject(
25662566 default_emit: Emit,
25672567 bin_emit_loc: ?EmitLoc,
25682568 llvm_object: *LlvmObject,
2569 prog_node: *std.Progress.Node,
2569 prog_node: std.Progress.Node,
25702570) !void {
25712571 if (build_options.only_c) @compileError("unreachable");
25722572
2573 var sub_prog_node = prog_node.start("LLVM Emit Object", 0);
2574 sub_prog_node.activate();
2575 sub_prog_node.context.refresh();
2573 const sub_prog_node = prog_node.start("LLVM Emit Object", 0);
25762574 defer sub_prog_node.end();
25772575
25782576 try llvm_object.emit(.{
......@@ -3249,32 +3247,20 @@ pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Module.File) !void {
32493247
32503248pub fn performAllTheWork(
32513249 comp: *Compilation,
3252 main_progress_node: *std.Progress.Node,
3250 main_progress_node: std.Progress.Node,
32533251) error{ TimerUnsupported, OutOfMemory }!void {
32543252 // Here we queue up all the AstGen tasks first, followed by C object compilation.
32553253 // We wait until the AstGen tasks are all completed before proceeding to the
32563254 // (at least for now) single-threaded main work queue. However, C object compilation
32573255 // only needs to be finished by the end of this function.
32583256
3259 var zir_prog_node = main_progress_node.start("AST Lowering", 0);
3260 defer zir_prog_node.end();
3261
3262 var wasm_prog_node = main_progress_node.start("Compile Autodocs", 0);
3263 defer wasm_prog_node.end();
3264
3265 var c_obj_prog_node = main_progress_node.start("Compile C Objects", comp.c_source_files.len);
3266 defer c_obj_prog_node.end();
3267
3268 var win32_resource_prog_node = main_progress_node.start("Compile Win32 Resources", comp.rc_source_files.len);
3269 defer win32_resource_prog_node.end();
3270
32713257 comp.work_queue_wait_group.reset();
32723258 defer comp.work_queue_wait_group.wait();
32733259
32743260 if (!build_options.only_c and !build_options.only_core_functionality) {
32753261 if (comp.docs_emit != null) {
32763262 comp.thread_pool.spawnWg(&comp.work_queue_wait_group, workerDocsCopy, .{comp});
3277 comp.work_queue_wait_group.spawnManager(workerDocsWasm, .{ comp, &wasm_prog_node });
3263 comp.work_queue_wait_group.spawnManager(workerDocsWasm, .{ comp, main_progress_node });
32783264 }
32793265 }
32803266
......@@ -3282,6 +3268,9 @@ pub fn performAllTheWork(
32823268 const astgen_frame = tracy.namedFrame("astgen");
32833269 defer astgen_frame.end();
32843270
3271 const zir_prog_node = main_progress_node.start("AST Lowering", 0);
3272 defer zir_prog_node.end();
3273
32853274 comp.astgen_wait_group.reset();
32863275 defer comp.astgen_wait_group.wait();
32873276
......@@ -3313,7 +3302,7 @@ pub fn performAllTheWork(
33133302
33143303 while (comp.astgen_work_queue.readItem()) |file| {
33153304 comp.thread_pool.spawnWg(&comp.astgen_wait_group, workerAstGenFile, .{
3316 comp, file, &zir_prog_node, &comp.astgen_wait_group, .root,
3305 comp, file, zir_prog_node, &comp.astgen_wait_group, .root,
33173306 });
33183307 }
33193308
......@@ -3325,14 +3314,14 @@ pub fn performAllTheWork(
33253314
33263315 while (comp.c_object_work_queue.readItem()) |c_object| {
33273316 comp.thread_pool.spawnWg(&comp.work_queue_wait_group, workerUpdateCObject, .{
3328 comp, c_object, &c_obj_prog_node,
3317 comp, c_object, main_progress_node,
33293318 });
33303319 }
33313320
33323321 if (!build_options.only_core_functionality) {
33333322 while (comp.win32_resource_work_queue.readItem()) |win32_resource| {
33343323 comp.thread_pool.spawnWg(&comp.work_queue_wait_group, workerUpdateWin32Resource, .{
3335 comp, win32_resource, &win32_resource_prog_node,
3324 comp, win32_resource, main_progress_node,
33363325 });
33373326 }
33383327 }
......@@ -3342,11 +3331,13 @@ pub fn performAllTheWork(
33423331 try reportMultiModuleErrors(mod);
33433332 try mod.flushRetryableFailures();
33443333 mod.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
3345 mod.sema_prog_node.activate();
3334 mod.codegen_prog_node = main_progress_node.start("Code Generation", 0);
33463335 }
33473336 defer if (comp.module) |mod| {
33483337 mod.sema_prog_node.end();
33493338 mod.sema_prog_node = undefined;
3339 mod.codegen_prog_node.end();
3340 mod.codegen_prog_node = undefined;
33503341 };
33513342
33523343 while (true) {
......@@ -3379,7 +3370,7 @@ pub fn performAllTheWork(
33793370 }
33803371}
33813372
3382fn processOneJob(comp: *Compilation, job: Job, prog_node: *std.Progress.Node) !void {
3373fn processOneJob(comp: *Compilation, job: Job, prog_node: std.Progress.Node) !void {
33833374 switch (job) {
33843375 .codegen_decl => |decl_index| {
33853376 const module = comp.module.?;
......@@ -3803,7 +3794,10 @@ fn docsCopyModule(comp: *Compilation, module: *Package.Module, name: []const u8,
38033794 }
38043795}
38053796
3806fn workerDocsWasm(comp: *Compilation, prog_node: *std.Progress.Node) void {
3797fn workerDocsWasm(comp: *Compilation, parent_prog_node: std.Progress.Node) void {
3798 const prog_node = parent_prog_node.start("Compile Autodocs", 0);
3799 defer prog_node.end();
3800
38073801 workerDocsWasmFallible(comp, prog_node) catch |err| {
38083802 comp.lockAndSetMiscFailure(.docs_wasm, "unable to build autodocs: {s}", .{
38093803 @errorName(err),
......@@ -3811,7 +3805,7 @@ fn workerDocsWasm(comp: *Compilation, prog_node: *std.Progress.Node) void {
38113805 };
38123806}
38133807
3814fn workerDocsWasmFallible(comp: *Compilation, prog_node: *std.Progress.Node) anyerror!void {
3808fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anyerror!void {
38153809 const gpa = comp.gpa;
38163810
38173811 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
......@@ -3952,12 +3946,11 @@ const AstGenSrc = union(enum) {
39523946fn workerAstGenFile(
39533947 comp: *Compilation,
39543948 file: *Module.File,
3955 prog_node: *std.Progress.Node,
3949 prog_node: std.Progress.Node,
39563950 wg: *WaitGroup,
39573951 src: AstGenSrc,
39583952) void {
3959 var child_prog_node = prog_node.start(file.sub_file_path, 0);
3960 child_prog_node.activate();
3953 const child_prog_node = prog_node.start(file.sub_file_path, 0);
39613954 defer child_prog_node.end();
39623955
39633956 const mod = comp.module.?;
......@@ -4265,7 +4258,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module
42654258fn workerUpdateCObject(
42664259 comp: *Compilation,
42674260 c_object: *CObject,
4268 progress_node: *std.Progress.Node,
4261 progress_node: std.Progress.Node,
42694262) void {
42704263 comp.updateCObject(c_object, progress_node) catch |err| switch (err) {
42714264 error.AnalysisFail => return,
......@@ -4282,7 +4275,7 @@ fn workerUpdateCObject(
42824275fn workerUpdateWin32Resource(
42834276 comp: *Compilation,
42844277 win32_resource: *Win32Resource,
4285 progress_node: *std.Progress.Node,
4278 progress_node: std.Progress.Node,
42864279) void {
42874280 comp.updateWin32Resource(win32_resource, progress_node) catch |err| switch (err) {
42884281 error.AnalysisFail => return,
......@@ -4300,7 +4293,7 @@ fn buildCompilerRtOneShot(
43004293 comp: *Compilation,
43014294 output_mode: std.builtin.OutputMode,
43024295 out: *?CRTFile,
4303 prog_node: *std.Progress.Node,
4296 prog_node: std.Progress.Node,
43044297) void {
43054298 comp.buildOutputFromZig(
43064299 "compiler_rt.zig",
......@@ -4427,7 +4420,7 @@ fn reportRetryableEmbedFileError(
44274420 }
44284421}
44294422
4430fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.Progress.Node) !void {
4423fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Progress.Node) !void {
44314424 if (comp.config.c_frontend == .aro) {
44324425 return comp.failCObj(c_object, "aro does not support compiling C objects yet", .{});
44334426 }
......@@ -4467,9 +4460,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
44674460
44684461 const c_source_basename = std.fs.path.basename(c_object.src.src_path);
44694462
4470 c_obj_prog_node.activate();
4471 var child_progress_node = c_obj_prog_node.start(c_source_basename, 0);
4472 child_progress_node.activate();
4463 const child_progress_node = c_obj_prog_node.start(c_source_basename, 0);
44734464 defer child_progress_node.end();
44744465
44754466 // Special case when doing build-obj for just one C file. When there are more than one object
......@@ -4731,7 +4722,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
47314722 };
47324723}
47334724
4734fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32_resource_prog_node: *std.Progress.Node) !void {
4725fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32_resource_prog_node: std.Progress.Node) !void {
47354726 if (!std.process.can_spawn) {
47364727 return comp.failWin32Resource(win32_resource, "{s} does not support spawning a child process", .{@tagName(builtin.os.tag)});
47374728 }
......@@ -4763,9 +4754,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
47634754 _ = comp.failed_win32_resources.swapRemove(win32_resource);
47644755 }
47654756
4766 win32_resource_prog_node.activate();
4767 var child_progress_node = win32_resource_prog_node.start(src_basename, 0);
4768 child_progress_node.activate();
4757 const child_progress_node = win32_resource_prog_node.start(src_basename, 0);
47694758 defer child_progress_node.end();
47704759
47714760 var man = comp.obtainWin32ResourceCacheManifest();
......@@ -4833,7 +4822,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
48334822 });
48344823 try argv.appendSlice(&.{ "--", in_rc_path, out_res_path });
48354824
4836 try spawnZigRc(comp, win32_resource, src_basename, arena, argv.items, &child_progress_node);
4825 try spawnZigRc(comp, win32_resource, arena, argv.items, child_progress_node);
48374826
48384827 break :blk digest;
48394828 };
......@@ -4901,7 +4890,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
49014890 try argv.appendSlice(rc_src.extra_flags);
49024891 try argv.appendSlice(&.{ "--", rc_src.src_path, out_res_path });
49034892
4904 try spawnZigRc(comp, win32_resource, src_basename, arena, argv.items, &child_progress_node);
4893 try spawnZigRc(comp, win32_resource, arena, argv.items, child_progress_node);
49054894
49064895 // Read depfile and update cache manifest
49074896 {
......@@ -4966,10 +4955,9 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
49664955fn spawnZigRc(
49674956 comp: *Compilation,
49684957 win32_resource: *Win32Resource,
4969 src_basename: []const u8,
49704958 arena: Allocator,
49714959 argv: []const []const u8,
4972 child_progress_node: *std.Progress.Node,
4960 child_progress_node: std.Progress.Node,
49734961) !void {
49744962 var node_name: std.ArrayListUnmanaged(u8) = .{};
49754963 defer node_name.deinit(arena);
......@@ -4978,6 +4966,7 @@ fn spawnZigRc(
49784966 child.stdin_behavior = .Ignore;
49794967 child.stdout_behavior = .Pipe;
49804968 child.stderr_behavior = .Pipe;
4969 child.progress_node = child_progress_node;
49814970
49824971 child.spawn() catch |err| {
49834972 return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {s}", .{ argv[0], @errorName(err) });
......@@ -5019,22 +5008,6 @@ fn spawnZigRc(
50195008 };
50205009 return comp.failWin32ResourceWithOwnedBundle(win32_resource, error_bundle);
50215010 },
5022 .progress => {
5023 node_name.clearRetainingCapacity();
5024 // <resinator> is a special string that indicates that the child
5025 // process has reached resinator's main function
5026 if (std.mem.eql(u8, body, "<resinator>")) {
5027 child_progress_node.setName(src_basename);
5028 }
5029 // Ignore 0-length strings since if multiple zig rc commands
5030 // are executed at the same time, only one will send progress strings
5031 // while the other(s) will send empty strings.
5032 else if (body.len > 0) {
5033 try node_name.appendSlice(arena, "build 'zig rc'... ");
5034 try node_name.appendSlice(arena, body);
5035 child_progress_node.setName(node_name.items);
5036 }
5037 },
50385011 else => {}, // ignore other messages
50395012 }
50405013
......@@ -5937,8 +5910,8 @@ pub fn lockAndParseLldStderr(comp: *Compilation, prefix: []const u8, stderr: []c
59375910}
59385911
59395912pub fn dump_argv(argv: []const []const u8) void {
5940 std.debug.getStderrMutex().lock();
5941 defer std.debug.getStderrMutex().unlock();
5913 std.debug.lockStdErr();
5914 defer std.debug.unlockStdErr();
59425915 const stderr = std.io.getStdErr().writer();
59435916 for (argv[0 .. argv.len - 1]) |arg| {
59445917 nosuspend stderr.print("{s} ", .{arg}) catch return;
......@@ -5989,14 +5962,13 @@ pub fn updateSubCompilation(
59895962 parent_comp: *Compilation,
59905963 sub_comp: *Compilation,
59915964 misc_task: MiscTask,
5992 prog_node: *std.Progress.Node,
5965 prog_node: std.Progress.Node,
59935966) !void {
59945967 {
5995 var sub_node = prog_node.start(@tagName(misc_task), 0);
5996 sub_node.activate();
5968 const sub_node = prog_node.start(@tagName(misc_task), 0);
59975969 defer sub_node.end();
59985970
5999 try sub_comp.update(prog_node);
5971 try sub_comp.update(sub_node);
60005972 }
60015973
60025974 // Look for compilation errors in this sub compilation
......@@ -6024,7 +5996,7 @@ fn buildOutputFromZig(
60245996 output_mode: std.builtin.OutputMode,
60255997 out: *?CRTFile,
60265998 misc_task_tag: MiscTask,
6027 prog_node: *std.Progress.Node,
5999 prog_node: std.Progress.Node,
60286000) !void {
60296001 const tracy_trace = trace(@src());
60306002 defer tracy_trace.end();
......@@ -6131,7 +6103,7 @@ pub fn build_crt_file(
61316103 root_name: []const u8,
61326104 output_mode: std.builtin.OutputMode,
61336105 misc_task_tag: MiscTask,
6134 prog_node: *std.Progress.Node,
6106 prog_node: std.Progress.Node,
61356107 /// These elements have to get mutated to add the owner module after it is
61366108 /// created within this function.
61376109 c_source_files: []CSourceFile,
src/Module.zig+28-11
......@@ -66,6 +66,7 @@ root_mod: *Package.Module,
6666main_mod: *Package.Module,
6767std_mod: *Package.Module,
6868sema_prog_node: std.Progress.Node = undefined,
69codegen_prog_node: std.Progress.Node = undefined,
6970
7071/// Used by AstGen worker to load and store ZIR cache.
7172global_zir_cache: Compilation.Directory,
......@@ -2942,11 +2943,12 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
29422943 const tracy = trace(@src());
29432944 defer tracy.end();
29442945
2946 const ip = &mod.intern_pool;
29452947 const decl = mod.declPtr(decl_index);
29462948
29472949 log.debug("ensureDeclAnalyzed '{d}' (name '{}')", .{
29482950 @intFromEnum(decl_index),
2949 decl.name.fmt(&mod.intern_pool),
2951 decl.name.fmt(ip),
29502952 });
29512953
29522954 // Determine whether or not this Decl is outdated, i.e. requires re-analysis
......@@ -2991,10 +2993,6 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
29912993 try mod.deleteDeclExports(decl_index);
29922994 }
29932995
2994 var decl_prog_node = mod.sema_prog_node.start("", 0);
2995 decl_prog_node.activate();
2996 defer decl_prog_node.end();
2997
29982996 const sema_result: SemaDeclResult = blk: {
29992997 if (decl.zir_decl_index == .none and !mod.declIsRoot(decl_index)) {
30002998 // Anonymous decl. We don't semantically analyze these.
......@@ -3012,6 +3010,9 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
30123010 };
30133011 }
30143012
3013 const decl_prog_node = mod.sema_prog_node.start((try decl.fullyQualifiedName(mod)).toSlice(ip), 0);
3014 defer decl_prog_node.end();
3015
30153016 break :blk mod.semaDecl(decl_index) catch |err| switch (err) {
30163017 error.AnalysisFail => {
30173018 if (decl.analysis == .in_progress) {
......@@ -3215,6 +3216,9 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, maybe_coerced_func_index: InternPool.In
32153216 };
32163217 }
32173218
3219 const codegen_prog_node = zcu.codegen_prog_node.start((try decl.fullyQualifiedName(zcu)).toSlice(ip), 0);
3220 defer codegen_prog_node.end();
3221
32183222 if (comp.bin_file) |lf| {
32193223 lf.updateFunc(zcu, func_index, air, liveness) catch |err| switch (err) {
32203224 error.OutOfMemory => return error.OutOfMemory,
......@@ -4500,6 +4504,9 @@ pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocato
45004504 log.debug("finish func name '{}'", .{(decl.fullyQualifiedName(mod) catch break :blk).fmt(ip)});
45014505 }
45024506
4507 const decl_prog_node = mod.sema_prog_node.start((try decl.fullyQualifiedName(mod)).toSlice(ip), 0);
4508 defer decl_prog_node.end();
4509
45034510 mod.intern_pool.removeDependenciesForDepender(gpa, InternPool.Depender.wrap(.{ .func = func_index }));
45044511
45054512 var comptime_err_ret_trace = std.ArrayList(SrcLoc).init(gpa);
......@@ -5316,7 +5323,7 @@ fn handleUpdateExports(
53165323
53175324pub fn populateTestFunctions(
53185325 mod: *Module,
5319 main_progress_node: *std.Progress.Node,
5326 main_progress_node: std.Progress.Node,
53205327) !void {
53215328 const gpa = mod.gpa;
53225329 const ip = &mod.intern_pool;
......@@ -5333,13 +5340,13 @@ pub fn populateTestFunctions(
53335340 // We have to call `ensureDeclAnalyzed` here in case `builtin.test_functions`
53345341 // was not referenced by start code.
53355342 mod.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
5336 mod.sema_prog_node.activate();
53375343 defer {
53385344 mod.sema_prog_node.end();
53395345 mod.sema_prog_node = undefined;
53405346 }
53415347 try mod.ensureDeclAnalyzed(decl_index);
53425348 }
5349
53435350 const decl = mod.declPtr(decl_index);
53445351 const test_fn_ty = decl.typeOf(mod).slicePtrFieldType(mod).childType(mod);
53455352
......@@ -5440,21 +5447,32 @@ pub fn populateTestFunctions(
54405447 decl.val = new_val;
54415448 decl.has_tv = true;
54425449 }
5443 try mod.linkerUpdateDecl(decl_index);
5450 {
5451 mod.codegen_prog_node = main_progress_node.start("Code Generation", 0);
5452 defer {
5453 mod.codegen_prog_node.end();
5454 mod.codegen_prog_node = undefined;
5455 }
5456
5457 try mod.linkerUpdateDecl(decl_index);
5458 }
54445459}
54455460
54465461pub fn linkerUpdateDecl(zcu: *Zcu, decl_index: Decl.Index) !void {
54475462 const comp = zcu.comp;
54485463
5464 const decl = zcu.declPtr(decl_index);
5465
5466 const codegen_prog_node = zcu.codegen_prog_node.start((try decl.fullyQualifiedName(zcu)).toSlice(&zcu.intern_pool), 0);
5467 defer codegen_prog_node.end();
5468
54495469 if (comp.bin_file) |lf| {
54505470 lf.updateDecl(zcu, decl_index) catch |err| switch (err) {
54515471 error.OutOfMemory => return error.OutOfMemory,
54525472 error.AnalysisFail => {
5453 const decl = zcu.declPtr(decl_index);
54545473 decl.analysis = .codegen_failure;
54555474 },
54565475 else => {
5457 const decl = zcu.declPtr(decl_index);
54585476 const gpa = zcu.gpa;
54595477 try zcu.failed_decls.ensureUnusedCapacity(gpa, 1);
54605478 zcu.failed_decls.putAssumeCapacityNoClobber(decl_index, try ErrorMsg.create(
......@@ -5472,7 +5490,6 @@ pub fn linkerUpdateDecl(zcu: *Zcu, decl_index: Decl.Index) !void {
54725490 llvm_object.updateDecl(zcu, decl_index) catch |err| switch (err) {
54735491 error.OutOfMemory => return error.OutOfMemory,
54745492 error.AnalysisFail => {
5475 const decl = zcu.declPtr(decl_index);
54765493 decl.analysis = .codegen_failure;
54775494 },
54785495 };
src/Package/Fetch.zig+5-9
......@@ -35,7 +35,7 @@ name_tok: std.zig.Ast.TokenIndex,
3535lazy_status: LazyStatus,
3636parent_package_root: Cache.Path,
3737parent_manifest_ast: ?*const std.zig.Ast,
38prog_node: *std.Progress.Node,
38prog_node: std.Progress.Node,
3939job_queue: *JobQueue,
4040/// If true, don't add an error for a missing hash. This flag is not passed
4141/// down to recursive dependencies. It's intended to be used only be the CLI.
......@@ -720,8 +720,7 @@ fn queueJobsForDeps(f: *Fetch) RunError!void {
720720 };
721721 }
722722
723 // job_queue mutex is locked so this is OK.
724 f.prog_node.unprotected_estimated_total_items += new_fetch_index;
723 f.prog_node.increaseEstimatedTotalItems(new_fetch_index);
725724
726725 break :nf .{ new_fetches[0..new_fetch_index], prog_names[0..new_fetch_index] };
727726 };
......@@ -751,9 +750,8 @@ pub fn relativePathDigest(
751750}
752751
753752pub fn workerRun(f: *Fetch, prog_name: []const u8) void {
754 var prog_node = f.prog_node.start(prog_name, 0);
753 const prog_node = f.prog_node.start(prog_name, 0);
755754 defer prog_node.end();
756 prog_node.activate();
757755
758756 run(f) catch |err| switch (err) {
759757 error.OutOfMemory => f.oom_flag = true,
......@@ -1311,9 +1309,8 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!Unpac
13111309 var index_file = try pack_dir.createFile("pkg.idx", .{ .read = true });
13121310 defer index_file.close();
13131311 {
1314 var index_prog_node = f.prog_node.start("Index pack", 0);
1312 const index_prog_node = f.prog_node.start("Index pack", 0);
13151313 defer index_prog_node.end();
1316 index_prog_node.activate();
13171314 var index_buffered_writer = std.io.bufferedWriter(index_file.writer());
13181315 try git.indexPack(gpa, pack_file, index_buffered_writer.writer());
13191316 try index_buffered_writer.flush();
......@@ -1321,9 +1318,8 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!Unpac
13211318 }
13221319
13231320 {
1324 var checkout_prog_node = f.prog_node.start("Checkout", 0);
1321 const checkout_prog_node = f.prog_node.start("Checkout", 0);
13251322 defer checkout_prog_node.end();
1326 checkout_prog_node.activate();
13271323 var repository = try git.Repository.init(gpa, pack_file, index_file);
13281324 defer repository.deinit();
13291325 var diagnostics: git.Diagnostics = .{ .allocator = arena };
src/glibc.zig+3-3
......@@ -160,7 +160,7 @@ pub const CRTFile = enum {
160160 libc_nonshared_a,
161161};
162162
163pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void {
163pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: std.Progress.Node) !void {
164164 if (!build_options.have_llvm) {
165165 return error.ZigCompilerNotBuiltWithLLVMExtensions;
166166 }
......@@ -658,7 +658,7 @@ pub const BuiltSharedObjects = struct {
658658
659659const all_map_basename = "all.map";
660660
661pub fn buildSharedObjects(comp: *Compilation, prog_node: *std.Progress.Node) !void {
661pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) !void {
662662 const tracy = trace(@src());
663663 defer tracy.end();
664664
......@@ -1065,7 +1065,7 @@ fn buildSharedLib(
10651065 bin_directory: Compilation.Directory,
10661066 asm_file_basename: []const u8,
10671067 lib: Lib,
1068 prog_node: *std.Progress.Node,
1068 prog_node: std.Progress.Node,
10691069) !void {
10701070 const tracy = trace(@src());
10711071 defer tracy.end();
src/libcxx.zig+2-2
......@@ -113,7 +113,7 @@ pub const BuildError = error{
113113 ZigCompilerNotBuiltWithLLVMExtensions,
114114};
115115
116pub fn buildLibCXX(comp: *Compilation, prog_node: *std.Progress.Node) BuildError!void {
116pub fn buildLibCXX(comp: *Compilation, prog_node: std.Progress.Node) BuildError!void {
117117 if (!build_options.have_llvm) {
118118 return error.ZigCompilerNotBuiltWithLLVMExtensions;
119119 }
......@@ -357,7 +357,7 @@ pub fn buildLibCXX(comp: *Compilation, prog_node: *std.Progress.Node) BuildError
357357 comp.libcxx_static_lib = try sub_compilation.toCrtFile();
358358}
359359
360pub fn buildLibCXXABI(comp: *Compilation, prog_node: *std.Progress.Node) BuildError!void {
360pub fn buildLibCXXABI(comp: *Compilation, prog_node: std.Progress.Node) BuildError!void {
361361 if (!build_options.have_llvm) {
362362 return error.ZigCompilerNotBuiltWithLLVMExtensions;
363363 }
src/libtsan.zig+1-1
......@@ -13,7 +13,7 @@ pub const BuildError = error{
1313 TSANUnsupportedCPUArchitecture,
1414};
1515
16pub fn buildTsan(comp: *Compilation, prog_node: *std.Progress.Node) BuildError!void {
16pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!void {
1717 if (!build_options.have_llvm) {
1818 return error.ZigCompilerNotBuiltWithLLVMExtensions;
1919 }
src/libunwind.zig+1-1
......@@ -14,7 +14,7 @@ pub const BuildError = error{
1414 ZigCompilerNotBuiltWithLLVMExtensions,
1515};
1616
17pub fn buildStaticLib(comp: *Compilation, prog_node: *std.Progress.Node) BuildError!void {
17pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildError!void {
1818 if (!build_options.have_llvm) {
1919 return error.ZigCompilerNotBuiltWithLLVMExtensions;
2020 }
src/link.zig+4-4
......@@ -535,7 +535,7 @@ pub const File = struct {
535535 /// Commit pending changes and write headers. Takes into account final output mode
536536 /// and `use_lld`, not only `effectiveOutputMode`.
537537 /// `arena` has the lifetime of the call to `Compilation.update`.
538 pub fn flush(base: *File, arena: Allocator, prog_node: *std.Progress.Node) FlushError!void {
538 pub fn flush(base: *File, arena: Allocator, prog_node: std.Progress.Node) FlushError!void {
539539 if (build_options.only_c) {
540540 assert(base.tag == .c);
541541 return @as(*C, @fieldParentPtr("base", base)).flush(arena, prog_node);
......@@ -572,7 +572,7 @@ pub const File = struct {
572572
573573 /// Commit pending changes and write headers. Works based on `effectiveOutputMode`
574574 /// rather than final output mode.
575 pub fn flushModule(base: *File, arena: Allocator, prog_node: *std.Progress.Node) FlushError!void {
575 pub fn flushModule(base: *File, arena: Allocator, prog_node: std.Progress.Node) FlushError!void {
576576 switch (base.tag) {
577577 inline else => |tag| {
578578 if (tag != .c and build_options.only_c) unreachable;
......@@ -688,7 +688,7 @@ pub const File = struct {
688688 }
689689 }
690690
691 pub fn linkAsArchive(base: *File, arena: Allocator, prog_node: *std.Progress.Node) FlushError!void {
691 pub fn linkAsArchive(base: *File, arena: Allocator, prog_node: std.Progress.Node) FlushError!void {
692692 const tracy = trace(@src());
693693 defer tracy.end();
694694
......@@ -966,7 +966,7 @@ pub const File = struct {
966966 base: File,
967967 arena: Allocator,
968968 llvm_object: *LlvmObject,
969 prog_node: *std.Progress.Node,
969 prog_node: std.Progress.Node,
970970 ) !void {
971971 return base.comp.emitLlvmObject(arena, base.emit, .{
972972 .directory = null,
src/link/C.zig+3-4
......@@ -370,7 +370,7 @@ pub fn updateDeclLineNumber(self: *C, zcu: *Zcu, decl_index: InternPool.DeclInde
370370 _ = decl_index;
371371}
372372
373pub fn flush(self: *C, arena: Allocator, prog_node: *std.Progress.Node) !void {
373pub fn flush(self: *C, arena: Allocator, prog_node: std.Progress.Node) !void {
374374 return self.flushModule(arena, prog_node);
375375}
376376
......@@ -389,14 +389,13 @@ fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) {
389389 return defines;
390390}
391391
392pub fn flushModule(self: *C, arena: Allocator, prog_node: *std.Progress.Node) !void {
392pub fn flushModule(self: *C, arena: Allocator, prog_node: std.Progress.Node) !void {
393393 _ = arena; // Has the same lifetime as the call to Compilation.update.
394394
395395 const tracy = trace(@src());
396396 defer tracy.end();
397397
398 var sub_prog_node = prog_node.start("Flush Module", 0);
399 sub_prog_node.activate();
398 const sub_prog_node = prog_node.start("Flush Module", 0);
400399 defer sub_prog_node.end();
401400
402401 const comp = self.base.comp;
src/link/Coff.zig+3-4
......@@ -1702,7 +1702,7 @@ fn resolveGlobalSymbol(self: *Coff, current: SymbolWithLoc) !void {
17021702 gop.value_ptr.* = current;
17031703}
17041704
1705pub fn flush(self: *Coff, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
1705pub fn flush(self: *Coff, arena: Allocator, prog_node: std.Progress.Node) link.File.FlushError!void {
17061706 const comp = self.base.comp;
17071707 const use_lld = build_options.have_llvm and comp.config.use_lld;
17081708 if (use_lld) {
......@@ -1714,7 +1714,7 @@ pub fn flush(self: *Coff, arena: Allocator, prog_node: *std.Progress.Node) link.
17141714 }
17151715}
17161716
1717pub fn flushModule(self: *Coff, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
1717pub fn flushModule(self: *Coff, arena: Allocator, prog_node: std.Progress.Node) link.File.FlushError!void {
17181718 const tracy = trace(@src());
17191719 defer tracy.end();
17201720
......@@ -1726,8 +1726,7 @@ pub fn flushModule(self: *Coff, arena: Allocator, prog_node: *std.Progress.Node)
17261726 return;
17271727 }
17281728
1729 var sub_prog_node = prog_node.start("COFF Flush", 0);
1730 sub_prog_node.activate();
1729 const sub_prog_node = prog_node.start("COFF Flush", 0);
17311730 defer sub_prog_node.end();
17321731
17331732 const module = comp.module orelse return error.LinkingWithoutZigSourceUnimplemented;
src/link/Coff/lld.zig+2-4
......@@ -16,7 +16,7 @@ const Allocator = mem.Allocator;
1616const Coff = @import("../Coff.zig");
1717const Compilation = @import("../../Compilation.zig");
1818
19pub fn linkWithLLD(self: *Coff, arena: Allocator, prog_node: *std.Progress.Node) !void {
19pub fn linkWithLLD(self: *Coff, arena: Allocator, prog_node: std.Progress.Node) !void {
2020 const tracy = trace(@src());
2121 defer tracy.end();
2222
......@@ -38,9 +38,7 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, prog_node: *std.Progress.Node)
3838 }
3939 } else null;
4040
41 var sub_prog_node = prog_node.start("LLD Link", 0);
42 sub_prog_node.activate();
43 sub_prog_node.context.refresh();
41 const sub_prog_node = prog_node.start("LLD Link", 0);
4442 defer sub_prog_node.end();
4543
4644 const is_lib = comp.config.output_mode == .Lib;
src/link/Elf.zig+5-8
......@@ -1064,7 +1064,7 @@ pub fn markDirty(self: *Elf, shdr_index: u32) void {
10641064 }
10651065}
10661066
1067pub fn flush(self: *Elf, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
1067pub fn flush(self: *Elf, arena: Allocator, prog_node: std.Progress.Node) link.File.FlushError!void {
10681068 const use_lld = build_options.have_llvm and self.base.comp.config.use_lld;
10691069 if (use_lld) {
10701070 return self.linkWithLLD(arena, prog_node);
......@@ -1072,7 +1072,7 @@ pub fn flush(self: *Elf, arena: Allocator, prog_node: *std.Progress.Node) link.F
10721072 try self.flushModule(arena, prog_node);
10731073}
10741074
1075pub fn flushModule(self: *Elf, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
1075pub fn flushModule(self: *Elf, arena: Allocator, prog_node: std.Progress.Node) link.File.FlushError!void {
10761076 const tracy = trace(@src());
10771077 defer tracy.end();
10781078
......@@ -1085,8 +1085,7 @@ pub fn flushModule(self: *Elf, arena: Allocator, prog_node: *std.Progress.Node)
10851085 if (use_lld) return;
10861086 }
10871087
1088 var sub_prog_node = prog_node.start("ELF Flush", 0);
1089 sub_prog_node.activate();
1088 const sub_prog_node = prog_node.start("ELF Flush", 0);
10901089 defer sub_prog_node.end();
10911090
10921091 const target = comp.root_mod.resolved_target.result;
......@@ -2147,7 +2146,7 @@ fn scanRelocs(self: *Elf) !void {
21472146 }
21482147}
21492148
2150fn linkWithLLD(self: *Elf, arena: Allocator, prog_node: *std.Progress.Node) !void {
2149fn linkWithLLD(self: *Elf, arena: Allocator, prog_node: std.Progress.Node) !void {
21512150 const tracy = trace(@src());
21522151 defer tracy.end();
21532152
......@@ -2169,9 +2168,7 @@ fn linkWithLLD(self: *Elf, arena: Allocator, prog_node: *std.Progress.Node) !voi
21692168 }
21702169 } else null;
21712170
2172 var sub_prog_node = prog_node.start("LLD Link", 0);
2173 sub_prog_node.activate();
2174 sub_prog_node.context.refresh();
2171 const sub_prog_node = prog_node.start("LLD Link", 0);
21752172 defer sub_prog_node.end();
21762173
21772174 const output_mode = comp.config.output_mode;
src/link/MachO.zig+3-4
......@@ -360,11 +360,11 @@ pub fn deinit(self: *MachO) void {
360360 self.unwind_records.deinit(gpa);
361361}
362362
363pub fn flush(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
363pub fn flush(self: *MachO, arena: Allocator, prog_node: std.Progress.Node) link.File.FlushError!void {
364364 try self.flushModule(arena, prog_node);
365365}
366366
367pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
367pub fn flushModule(self: *MachO, arena: Allocator, prog_node: std.Progress.Node) link.File.FlushError!void {
368368 const tracy = trace(@src());
369369 defer tracy.end();
370370
......@@ -375,8 +375,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node
375375 try self.base.emitLlvmObject(arena, llvm_object, prog_node);
376376 }
377377
378 var sub_prog_node = prog_node.start("MachO Flush", 0);
379 sub_prog_node.activate();
378 const sub_prog_node = prog_node.start("MachO Flush", 0);
380379 defer sub_prog_node.end();
381380
382381 const directory = self.base.emit.directory;
src/link/NvPtx.zig+2-2
......@@ -106,11 +106,11 @@ pub fn freeDecl(self: *NvPtx, decl_index: InternPool.DeclIndex) void {
106106 return self.llvm_object.freeDecl(decl_index);
107107}
108108
109pub fn flush(self: *NvPtx, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
109pub fn flush(self: *NvPtx, arena: Allocator, prog_node: std.Progress.Node) link.File.FlushError!void {
110110 return self.flushModule(arena, prog_node);
111111}
112112
113pub fn flushModule(self: *NvPtx, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
113pub fn flushModule(self: *NvPtx, arena: Allocator, prog_node: std.Progress.Node) link.File.FlushError!void {
114114 if (build_options.skip_non_native)
115115 @panic("Attempted to compile for architecture that was disabled by build configuration");
116116
src/link/Plan9.zig+3-4
......@@ -604,7 +604,7 @@ fn allocateGotIndex(self: *Plan9) usize {
604604 }
605605}
606606
607pub fn flush(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
607pub fn flush(self: *Plan9, arena: Allocator, prog_node: std.Progress.Node) link.File.FlushError!void {
608608 const comp = self.base.comp;
609609 const use_lld = build_options.have_llvm and comp.config.use_lld;
610610 assert(!use_lld);
......@@ -663,7 +663,7 @@ fn atomCount(self: *Plan9) usize {
663663 return data_decl_count + fn_decl_count + unnamed_const_count + lazy_atom_count + extern_atom_count + anon_atom_count;
664664}
665665
666pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
666pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: std.Progress.Node) link.File.FlushError!void {
667667 if (build_options.skip_non_native and builtin.object_format != .plan9) {
668668 @panic("Attempted to compile for object format that was disabled by build configuration");
669669 }
......@@ -677,8 +677,7 @@ pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node
677677 const tracy = trace(@src());
678678 defer tracy.end();
679679
680 var sub_prog_node = prog_node.start("Flush Module", 0);
681 sub_prog_node.activate();
680 const sub_prog_node = prog_node.start("Flush Module", 0);
682681 defer sub_prog_node.end();
683682
684683 log.debug("flushModule", .{});
src/link/SpirV.zig+5-6
......@@ -193,11 +193,11 @@ pub fn freeDecl(self: *SpirV, decl_index: InternPool.DeclIndex) void {
193193 _ = decl_index;
194194}
195195
196pub fn flush(self: *SpirV, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
196pub fn flush(self: *SpirV, arena: Allocator, prog_node: std.Progress.Node) link.File.FlushError!void {
197197 return self.flushModule(arena, prog_node);
198198}
199199
200pub fn flushModule(self: *SpirV, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
200pub fn flushModule(self: *SpirV, arena: Allocator, prog_node: std.Progress.Node) link.File.FlushError!void {
201201 if (build_options.skip_non_native) {
202202 @panic("Attempted to compile for architecture that was disabled by build configuration");
203203 }
......@@ -205,8 +205,7 @@ pub fn flushModule(self: *SpirV, arena: Allocator, prog_node: *std.Progress.Node
205205 const tracy = trace(@src());
206206 defer tracy.end();
207207
208 var sub_prog_node = prog_node.start("Flush Module", 0);
209 sub_prog_node.activate();
208 const sub_prog_node = prog_node.start("Flush Module", 0);
210209 defer sub_prog_node.end();
211210
212211 const spv = &self.object.spv;
......@@ -253,7 +252,7 @@ pub fn flushModule(self: *SpirV, arena: Allocator, prog_node: *std.Progress.Node
253252 const module = try spv.finalize(arena, target);
254253 errdefer arena.free(module);
255254
256 const linked_module = self.linkModule(arena, module, &sub_prog_node) catch |err| switch (err) {
255 const linked_module = self.linkModule(arena, module, sub_prog_node) catch |err| switch (err) {
257256 error.OutOfMemory => return error.OutOfMemory,
258257 else => |other| {
259258 log.err("error while linking: {s}\n", .{@errorName(other)});
......@@ -264,7 +263,7 @@ pub fn flushModule(self: *SpirV, arena: Allocator, prog_node: *std.Progress.Node
264263 try self.base.file.?.writeAll(std.mem.sliceAsBytes(linked_module));
265264}
266265
267fn linkModule(self: *SpirV, a: Allocator, module: []Word, progress: *std.Progress.Node) ![]Word {
266fn linkModule(self: *SpirV, a: Allocator, module: []Word, progress: std.Progress.Node) ![]Word {
268267 _ = self;
269268
270269 const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");
src/link/SpirV/deduplicate.zig+2-3
......@@ -418,9 +418,8 @@ const EntityHashContext = struct {
418418 }
419419};
420420
421pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: *std.Progress.Node) !void {
422 var sub_node = progress.start("deduplicate", 0);
423 sub_node.activate();
421pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Progress.Node) !void {
422 const sub_node = progress.start("deduplicate", 0);
424423 defer sub_node.end();
425424
426425 var arena = std.heap.ArenaAllocator.init(parser.a);
src/link/SpirV/lower_invocation_globals.zig+2-3
......@@ -682,9 +682,8 @@ const ModuleBuilder = struct {
682682 }
683683};
684684
685pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: *std.Progress.Node) !void {
686 var sub_node = progress.start("Lower invocation globals", 6);
687 sub_node.activate();
685pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Progress.Node) !void {
686 const sub_node = progress.start("Lower invocation globals", 6);
688687 defer sub_node.end();
689688
690689 var arena = std.heap.ArenaAllocator.init(parser.a);
src/link/SpirV/prune_unused.zig+2-3
......@@ -255,9 +255,8 @@ fn removeIdsFromMap(a: Allocator, map: anytype, info: ModuleInfo, alive_marker:
255255 }
256256}
257257
258pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: *std.Progress.Node) !void {
259 var sub_node = progress.start("Prune unused IDs", 0);
260 sub_node.activate();
258pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Progress.Node) !void {
259 const sub_node = progress.start("Prune unused IDs", 0);
261260 defer sub_node.end();
262261
263262 var arena = std.heap.ArenaAllocator.init(parser.a);
src/link/Wasm.zig+5-8
......@@ -2464,7 +2464,7 @@ fn appendDummySegment(wasm: *Wasm) !void {
24642464 });
24652465}
24662466
2467pub fn flush(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
2467pub fn flush(wasm: *Wasm, arena: Allocator, prog_node: std.Progress.Node) link.File.FlushError!void {
24682468 const comp = wasm.base.comp;
24692469 const use_lld = build_options.have_llvm and comp.config.use_lld;
24702470
......@@ -2475,7 +2475,7 @@ pub fn flush(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) link.
24752475}
24762476
24772477/// Uses the in-house linker to link one or multiple object -and archive files into a WebAssembly binary.
2478pub fn flushModule(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
2478pub fn flushModule(wasm: *Wasm, arena: Allocator, prog_node: std.Progress.Node) link.File.FlushError!void {
24792479 const tracy = trace(@src());
24802480 defer tracy.end();
24812481
......@@ -2486,8 +2486,7 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node)
24862486 if (use_lld) return;
24872487 }
24882488
2489 var sub_prog_node = prog_node.start("Wasm Flush", 0);
2490 sub_prog_node.activate();
2489 const sub_prog_node = prog_node.start("Wasm Flush", 0);
24912490 defer sub_prog_node.end();
24922491
24932492 const directory = wasm.base.emit.directory; // Just an alias to make it shorter to type.
......@@ -3323,7 +3322,7 @@ fn emitImport(wasm: *Wasm, writer: anytype, import: types.Import) !void {
33233322 }
33243323}
33253324
3326fn linkWithLLD(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) !void {
3325fn linkWithLLD(wasm: *Wasm, arena: Allocator, prog_node: std.Progress.Node) !void {
33273326 const tracy = trace(@src());
33283327 defer tracy.end();
33293328
......@@ -3350,9 +3349,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) !vo
33503349 }
33513350 } else null;
33523351
3353 var sub_prog_node = prog_node.start("LLD Link", 0);
3354 sub_prog_node.activate();
3355 sub_prog_node.context.refresh();
3352 const sub_prog_node = prog_node.start("LLD Link", 0);
33563353 defer sub_prog_node.end();
33573354
33583355 const is_obj = comp.config.output_mode == .Obj;
src/main.zig+57-154
......@@ -3404,11 +3404,16 @@ fn buildOutputType(
34043404 },
34053405 }
34063406
3407 const root_prog_node = std.Progress.start(.{
3408 .disable_printing = (color == .off),
3409 });
3410 defer root_prog_node.end();
3411
34073412 if (arg_mode == .translate_c) {
3408 return cmdTranslateC(comp, arena, null);
3413 return cmdTranslateC(comp, arena, null, root_prog_node);
34093414 }
34103415
3411 updateModule(comp, color) catch |err| switch (err) {
3416 updateModule(comp, color, root_prog_node) catch |err| switch (err) {
34123417 error.SemanticAnalyzeFail => {
34133418 assert(listen == .none);
34143419 saveState(comp, debug_incremental);
......@@ -4028,22 +4033,7 @@ fn serve(
40284033
40294034 var child_pid: ?std.process.Child.Id = null;
40304035
4031 var progress: std.Progress = .{
4032 .terminal = null,
4033 .root = .{
4034 .context = undefined,
4035 .parent = null,
4036 .name = "",
4037 .unprotected_estimated_total_items = 0,
4038 .unprotected_completed_items = 0,
4039 },
4040 .columns_written = 0,
4041 .prev_refresh_timestamp = 0,
4042 .timer = null,
4043 .done = false,
4044 };
4045 const main_progress_node = &progress.root;
4046 main_progress_node.context = &progress;
4036 const main_progress_node = std.Progress.start(.{});
40474037
40484038 while (true) {
40494039 const hdr = try server.receiveMessage();
......@@ -4051,7 +4041,6 @@ fn serve(
40514041 switch (hdr.tag) {
40524042 .exit => return cleanExit(),
40534043 .update => {
4054 assert(main_progress_node.recently_updated_child == null);
40554044 tracy.frameMark();
40564045
40574046 if (arg_mode == .translate_c) {
......@@ -4059,7 +4048,7 @@ fn serve(
40594048 defer arena_instance.deinit();
40604049 const arena = arena_instance.allocator();
40614050 var output: Compilation.CImportResult = undefined;
4062 try cmdTranslateC(comp, arena, &output);
4051 try cmdTranslateC(comp, arena, &output, main_progress_node);
40634052 defer output.deinit(gpa);
40644053 if (output.errors.errorMessageCount() != 0) {
40654054 try server.serveErrorBundle(output.errors);
......@@ -4075,21 +4064,7 @@ fn serve(
40754064 try comp.makeBinFileWritable();
40764065 }
40774066
4078 if (builtin.single_threaded) {
4079 try comp.update(main_progress_node);
4080 } else {
4081 var reset: std.Thread.ResetEvent = .{};
4082
4083 var progress_thread = try std.Thread.spawn(.{}, progressThread, .{
4084 &progress, &server, &reset,
4085 });
4086 defer {
4087 reset.set();
4088 progress_thread.join();
4089 }
4090
4091 try comp.update(main_progress_node);
4092 }
4067 try comp.update(main_progress_node);
40934068
40944069 try comp.makeBinFileExecutable();
40954070 try serveUpdateResults(&server, comp);
......@@ -4116,7 +4091,6 @@ fn serve(
41164091 },
41174092 .hot_update => {
41184093 tracy.frameMark();
4119 assert(main_progress_node.recently_updated_child == null);
41204094 if (child_pid) |pid| {
41214095 try comp.hotCodeSwap(main_progress_node, pid);
41224096 try serveUpdateResults(&server, comp);
......@@ -4146,63 +4120,6 @@ fn serve(
41464120 }
41474121}
41484122
4149fn progressThread(progress: *std.Progress, server: *const Server, reset: *std.Thread.ResetEvent) void {
4150 while (true) {
4151 if (reset.timedWait(500 * std.time.ns_per_ms)) |_| {
4152 // The Compilation update has completed.
4153 return;
4154 } else |err| switch (err) {
4155 error.Timeout => {},
4156 }
4157
4158 var buf: std.BoundedArray(u8, 160) = .{};
4159
4160 {
4161 progress.update_mutex.lock();
4162 defer progress.update_mutex.unlock();
4163
4164 var need_ellipse = false;
4165 var maybe_node: ?*std.Progress.Node = &progress.root;
4166 while (maybe_node) |node| {
4167 if (need_ellipse) {
4168 buf.appendSlice("... ") catch {};
4169 }
4170 need_ellipse = false;
4171 const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .monotonic);
4172 const completed_items = @atomicLoad(usize, &node.unprotected_completed_items, .monotonic);
4173 const current_item = completed_items + 1;
4174 if (node.name.len != 0 or eti > 0) {
4175 if (node.name.len != 0) {
4176 buf.appendSlice(node.name) catch {};
4177 need_ellipse = true;
4178 }
4179 if (eti > 0) {
4180 if (need_ellipse) buf.appendSlice(" ") catch {};
4181 buf.writer().print("[{d}/{d}] ", .{ current_item, eti }) catch {};
4182 need_ellipse = false;
4183 } else if (completed_items != 0) {
4184 if (need_ellipse) buf.appendSlice(" ") catch {};
4185 buf.writer().print("[{d}] ", .{current_item}) catch {};
4186 need_ellipse = false;
4187 }
4188 }
4189 maybe_node = @atomicLoad(?*std.Progress.Node, &node.recently_updated_child, .acquire);
4190 }
4191 }
4192
4193 const progress_string = buf.slice();
4194
4195 server.serveMessage(.{
4196 .tag = .progress,
4197 .bytes_len = @as(u32, @intCast(progress_string.len)),
4198 }, &.{
4199 progress_string,
4200 }) catch |err| {
4201 fatal("unable to write to client: {s}", .{@errorName(err)});
4202 };
4203 }
4204}
4205
42064123fn serveUpdateResults(s: *Server, comp: *Compilation) !void {
42074124 const gpa = comp.gpa;
42084125 var error_bundle = try comp.getAllErrorsAlloc();
......@@ -4469,25 +4386,8 @@ fn runOrTestHotSwap(
44694386 }
44704387}
44714388
4472fn updateModule(comp: *Compilation, color: Color) !void {
4473 {
4474 // If the terminal is dumb, we dont want to show the user all the output.
4475 var progress: std.Progress = .{ .dont_print_on_dumb = true };
4476 const main_progress_node = progress.start("", 0);
4477 defer main_progress_node.end();
4478 switch (color) {
4479 .off => {
4480 progress.terminal = null;
4481 },
4482 .on => {
4483 progress.terminal = std.io.getStdErr();
4484 progress.supports_ansi_escape_codes = true;
4485 },
4486 .auto => {},
4487 }
4488
4489 try comp.update(main_progress_node);
4490 }
4389fn updateModule(comp: *Compilation, color: Color, prog_node: std.Progress.Node) !void {
4390 try comp.update(prog_node);
44914391
44924392 var errors = try comp.getAllErrorsAlloc();
44934393 defer errors.deinit(comp.gpa);
......@@ -4498,7 +4398,12 @@ fn updateModule(comp: *Compilation, color: Color) !void {
44984398 }
44994399}
45004400
4501fn cmdTranslateC(comp: *Compilation, arena: Allocator, fancy_output: ?*Compilation.CImportResult) !void {
4401fn cmdTranslateC(
4402 comp: *Compilation,
4403 arena: Allocator,
4404 fancy_output: ?*Compilation.CImportResult,
4405 prog_node: std.Progress.Node,
4406) !void {
45024407 if (build_options.only_core_functionality) @panic("@translate-c is not available in a zig2.c build");
45034408 const color: Color = .auto;
45044409 assert(comp.c_source_files.len == 1);
......@@ -4559,6 +4464,7 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, fancy_output: ?*Compilati
45594464 .root_src_path = "aro_translate_c.zig",
45604465 .depend_on_aro = true,
45614466 .capture = &stdout,
4467 .progress_node = prog_node,
45624468 });
45634469 break :f stdout;
45644470 },
......@@ -4736,8 +4642,6 @@ const usage_build =
47364642;
47374643
47384644fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4739 var progress: std.Progress = .{ .dont_print_on_dumb = true };
4740
47414645 var build_file: ?[]const u8 = null;
47424646 var override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena);
47434647 var override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena);
......@@ -4798,6 +4702,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
47984702 const results_tmp_file_nonce = Package.Manifest.hex64(std.crypto.random.int(u64));
47994703 try child_argv.append("-Z" ++ results_tmp_file_nonce);
48004704
4705 var color: Color = .auto;
4706
48014707 {
48024708 var i: usize = 0;
48034709 while (i < args.len) : (i += 1) {
......@@ -4882,6 +4788,14 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
48824788 verbose_cimport = true;
48834789 } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
48844790 verbose_llvm_cpu_features = true;
4791 } else if (mem.eql(u8, arg, "--color")) {
4792 if (i + 1 >= args.len) fatal("expected [auto|on|off] after {s}", .{arg});
4793 i += 1;
4794 color = std.meta.stringToEnum(Color, args[i]) orelse {
4795 fatal("expected [auto|on|off] after {s}, found '{s}'", .{ arg, args[i] });
4796 };
4797 try child_argv.appendSlice(&.{ arg, args[i] });
4798 continue;
48854799 } else if (mem.eql(u8, arg, "--seed")) {
48864800 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
48874801 i += 1;
......@@ -4895,7 +4809,11 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
48954809
48964810 const work_around_btrfs_bug = native_os == .linux and
48974811 EnvVar.ZIG_BTRFS_WORKAROUND.isSet();
4898 const color: Color = .auto;
4812 const root_prog_node = std.Progress.start(.{
4813 .disable_printing = (color == .off),
4814 .root_name = "Compile Build Script",
4815 });
4816 defer root_prog_node.end();
48994817
49004818 const target_query: std.Target.Query = .{};
49014819 const resolved_target: Package.Module.ResolvedTarget = .{
......@@ -5051,8 +4969,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
50514969 config,
50524970 );
50534971 } else {
5054 const root_prog_node = progress.start("Fetch Packages", 0);
5055 defer root_prog_node.end();
4972 const fetch_prog_node = root_prog_node.start("Fetch Packages", 0);
4973 defer fetch_prog_node.end();
50564974
50574975 var job_queue: Package.Fetch.JobQueue = .{
50584976 .http_client = &http_client,
......@@ -5093,7 +5011,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
50935011 .lazy_status = .eager,
50945012 .parent_package_root = build_mod.root,
50955013 .parent_manifest_ast = null,
5096 .prog_node = root_prog_node,
5014 .prog_node = fetch_prog_node,
50975015 .job_queue = &job_queue,
50985016 .omit_missing_hash_error = true,
50995017 .allow_missing_paths_field = false,
......@@ -5232,7 +5150,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
52325150 };
52335151 defer comp.destroy();
52345152
5235 updateModule(comp, color) catch |err| switch (err) {
5153 updateModule(comp, color, root_prog_node) catch |err| switch (err) {
52365154 error.SemanticAnalyzeFail => process.exit(2),
52375155 else => |e| return e,
52385156 };
......@@ -5250,7 +5168,12 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
52505168 child.stdout_behavior = .Inherit;
52515169 child.stderr_behavior = .Inherit;
52525170
5253 const term = try child.spawnAndWait();
5171 const term = t: {
5172 std.debug.lockStdErr();
5173 defer std.debug.unlockStdErr();
5174 break :t try child.spawnAndWait();
5175 };
5176
52545177 switch (term) {
52555178 .Exited => |code| {
52565179 if (code == 0) return cleanExit();
......@@ -5326,8 +5249,9 @@ const JitCmdOptions = struct {
53265249 prepend_zig_exe_path: bool = false,
53275250 depend_on_aro: bool = false,
53285251 capture: ?*[]u8 = null,
5329 /// Send progress and error bundles via std.zig.Server over stdout
5252 /// Send error bundles via std.zig.Server over stdout
53305253 server: bool = false,
5254 progress_node: ?std.Progress.Node = null,
53315255};
53325256
53335257fn jitCmd(
......@@ -5337,6 +5261,9 @@ fn jitCmd(
53375261 options: JitCmdOptions,
53385262) !void {
53395263 const color: Color = .auto;
5264 const root_prog_node = if (options.progress_node) |node| node else std.Progress.start(.{
5265 .disable_printing = (color == .off),
5266 });
53405267
53415268 const target_query: std.Target.Query = .{};
53425269 const resolved_target: Package.Module.ResolvedTarget = .{
......@@ -5473,39 +5400,14 @@ fn jitCmd(
54735400 };
54745401 defer comp.destroy();
54755402
5476 if (options.server and !builtin.single_threaded) {
5477 var reset: std.Thread.ResetEvent = .{};
5478 var progress: std.Progress = .{
5479 .terminal = null,
5480 .root = .{
5481 .context = undefined,
5482 .parent = null,
5483 .name = "",
5484 .unprotected_estimated_total_items = 0,
5485 .unprotected_completed_items = 0,
5486 },
5487 .columns_written = 0,
5488 .prev_refresh_timestamp = 0,
5489 .timer = null,
5490 .done = false,
5491 };
5492 const main_progress_node = &progress.root;
5493 main_progress_node.context = &progress;
5403 if (options.server) {
54945404 var server = std.zig.Server{
54955405 .out = std.io.getStdOut(),
54965406 .in = undefined, // won't be receiving messages
54975407 .receive_fifo = undefined, // won't be receiving messages
54985408 };
54995409
5500 var progress_thread = try std.Thread.spawn(.{}, progressThread, .{
5501 &progress, &server, &reset,
5502 });
5503 defer {
5504 reset.set();
5505 progress_thread.join();
5506 }
5507
5508 try comp.update(main_progress_node);
5410 try comp.update(root_prog_node);
55095411
55105412 var error_bundle = try comp.getAllErrorsAlloc();
55115413 defer error_bundle.deinit(comp.gpa);
......@@ -5514,7 +5416,7 @@ fn jitCmd(
55145416 process.exit(2);
55155417 }
55165418 } else {
5517 updateModule(comp, color) catch |err| switch (err) {
5419 updateModule(comp, color, root_prog_node) catch |err| switch (err) {
55185420 error.SemanticAnalyzeFail => process.exit(2),
55195421 else => |e| return e,
55205422 };
......@@ -6963,8 +6865,9 @@ fn cmdFetch(
69636865
69646866 try http_client.initDefaultProxies(arena);
69656867
6966 var progress: std.Progress = .{ .dont_print_on_dumb = true };
6967 const root_prog_node = progress.start("Fetch", 0);
6868 var root_prog_node = std.Progress.start(.{
6869 .root_name = "Fetch",
6870 });
69686871 defer root_prog_node.end();
69696872
69706873 var global_cache_directory: Compilation.Directory = l: {
......@@ -7028,8 +6931,8 @@ fn cmdFetch(
70286931
70296932 const hex_digest = Package.Manifest.hexDigest(fetch.actual_hash);
70306933
7031 progress.done = true;
7032 progress.refresh();
6934 root_prog_node.end();
6935 root_prog_node = .{ .index = .none };
70336936
70346937 const name = switch (save) {
70356938 .no => {
src/mingw.zig+3-3
......@@ -16,7 +16,7 @@ pub const CRTFile = enum {
1616 mingw32_lib,
1717};
1818
19pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void {
19pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: std.Progress.Node) !void {
2020 if (!build_options.have_llvm) {
2121 return error.ZigCompilerNotBuiltWithLLVMExtensions;
2222 }
......@@ -234,8 +234,8 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
234234 const include_dir = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", "mingw", "def-include" });
235235
236236 if (comp.verbose_cc) print: {
237 std.debug.getStderrMutex().lock();
238 defer std.debug.getStderrMutex().unlock();
237 std.debug.lockStdErr();
238 defer std.debug.unlockStdErr();
239239 const stderr = std.io.getStdErr().writer();
240240 nosuspend stderr.print("def file: {s}\n", .{def_file_path}) catch break :print;
241241 nosuspend stderr.print("include dir: {s}\n", .{include_dir}) catch break :print;
src/musl.zig+1-1
......@@ -19,7 +19,7 @@ pub const CRTFile = enum {
1919 libc_so,
2020};
2121
22pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void {
22pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: std.Progress.Node) !void {
2323 if (!build_options.have_llvm) {
2424 return error.ZigCompilerNotBuiltWithLLVMExtensions;
2525 }
src/wasi_libc.zig+1-1
......@@ -57,7 +57,7 @@ pub fn execModelCrtFileFullName(wasi_exec_model: std.builtin.WasiExecModel) []co
5757 };
5858}
5959
60pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void {
60pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: std.Progress.Node) !void {
6161 if (!build_options.have_llvm) {
6262 return error.ZigCompilerNotBuiltWithLLVMExtensions;
6363 }
test/src/Cases.zig+1-1
......@@ -561,7 +561,7 @@ pub fn lowerToTranslateCSteps(
561561 for (self.translate.items) |case| switch (case.kind) {
562562 .run => |output| {
563563 if (translate_c_options.skip_run_translated_c) continue;
564 const annotated_case_name = b.fmt("run-translated-c {s}", .{case.name});
564 const annotated_case_name = b.fmt("run-translated-c {s}", .{case.name});
565565 for (test_filters) |test_filter| {
566566 if (std.mem.indexOf(u8, annotated_case_name, test_filter)) |_| break;
567567 } else if (test_filters.len > 0) continue;
test/src/RunTranslatedC.zig+1
......@@ -91,6 +91,7 @@ pub fn addCase(self: *RunTranslatedCContext, case: *const TestCase) void {
9191 run.expectStdErrEqual("");
9292 }
9393 run.expectStdOutEqual(case.expected_stdout);
94 run.skip_foreign_checks = true;
9495
9596 self.step.dependOn(&run.step);
9697}
test/standalone/cmakedefine/build.zig+1-1
......@@ -80,7 +80,7 @@ pub fn build(b: *std.Build) void {
8080 test_step.dependOn(&wrapper_header.step);
8181}
8282
83fn compare_headers(step: *std.Build.Step, prog_node: *std.Progress.Node) !void {
83fn compare_headers(step: *std.Build.Step, prog_node: std.Progress.Node) !void {
8484 _ = prog_node;
8585 const allocator = step.owner.allocator;
8686 const expected_fmt = "expected_{s}";
test/standalone/empty_env/build.zig+1
......@@ -21,6 +21,7 @@ pub fn build(b: *std.Build) void {
2121
2222 const run = b.addRunArtifact(main);
2323 run.clearEnvironment();
24 run.disable_zig_progress = true;
2425
2526 test_step.dependOn(&run.step);
2627}