| ... | @@ -74,7 +74,7 @@ pub const Options = struct { | ... | @@ -74,7 +74,7 @@ pub const Options = struct { |
| 74 | pub const Node = struct { | 74 | pub const Node = struct { |
| 75 | index: OptionalIndex, | 75 | index: OptionalIndex, |
| 76 | | 76 | |
| 77 | pub const max_name_len = 38; | 77 | pub const max_name_len = 40; |
| 78 | | 78 | |
| 79 | const Storage = extern struct { | 79 | const Storage = extern struct { |
| 80 | /// Little endian. | 80 | /// Little endian. |
| ... | @@ -268,17 +268,7 @@ var node_freelist_buffer: [default_node_storage_buffer_len]Node.OptionalIndex = | ... | @@ -268,17 +268,7 @@ var node_freelist_buffer: [default_node_storage_buffer_len]Node.OptionalIndex = |
| 268 | pub fn start(options: Options) Node { | 268 | pub fn start(options: Options) Node { |
| 269 | // Ensure there is only 1 global Progress object. | 269 | // Ensure there is only 1 global Progress object. |
| 270 | assert(global_progress.node_end_index == 0); | 270 | assert(global_progress.node_end_index == 0); |
| 271 | const stderr = std.io.getStdErr(); | 271 | |
| 272 | if (stderr.supportsAnsiEscapeCodes()) { | | |
| 273 | global_progress.terminal = stderr; | | |
| 274 | global_progress.supports_ansi_escape_codes = true; | | |
| 275 | } else if (builtin.os.tag == .windows and stderr.isTty()) { | | |
| 276 | global_progress.is_windows_terminal = true; | | |
| 277 | global_progress.terminal = stderr; | | |
| 278 | } else if (builtin.os.tag != .windows) { | | |
| 279 | // we are in a "dumb" terminal like in acme or writing to a file | | |
| 280 | global_progress.terminal = stderr; | | |
| 281 | } | | |
| 282 | @memset(global_progress.node_parents, .unused); | 272 | @memset(global_progress.node_parents, .unused); |
| 283 | const root_node = Node.init(@enumFromInt(0), .none, options.root_name, options.estimated_total_items); | 273 | const root_node = Node.init(@enumFromInt(0), .none, options.root_name, options.estimated_total_items); |
| 284 | global_progress.done = false; | 274 | global_progress.done = false; |
| ... | @@ -289,22 +279,51 @@ pub fn start(options: Options) Node { | ... | @@ -289,22 +279,51 @@ pub fn start(options: Options) Node { |
| 289 | global_progress.refresh_rate_ns = options.refresh_rate_ns; | 279 | global_progress.refresh_rate_ns = options.refresh_rate_ns; |
| 290 | global_progress.initial_delay_ns = options.initial_delay_ns; | 280 | global_progress.initial_delay_ns = options.initial_delay_ns; |
| 291 | | 281 | |
| 292 | var act: posix.Sigaction = .{ | 282 | if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| { |
| 293 | .handler = .{ .sigaction = handleSigWinch }, | 283 | if (std.Thread.spawn(.{}, ipcThreadRun, .{ipc_fd})) |thread| { |
| 294 | .mask = posix.empty_sigset, | | |
| 295 | .flags = (posix.SA.SIGINFO | posix.SA.RESTART), | | |
| 296 | }; | | |
| 297 | posix.sigaction(posix.SIG.WINCH, &act, null) catch { | | |
| 298 | global_progress.terminal = null; | | |
| 299 | return root_node; | | |
| 300 | }; | | |
| 301 | | | |
| 302 | if (global_progress.terminal != null) { | | |
| 303 | if (std.Thread.spawn(.{}, updateThreadRun, .{})) |thread| { | | |
| 304 | global_progress.update_thread = thread; | 284 | global_progress.update_thread = thread; |
| 305 | } else |_| { | 285 | } else |err| { |
| 306 | global_progress.terminal = null; | 286 | std.log.warn("failed to spawn IPC thread for communicating progress to parent: {s}", .{@errorName(err)}); |
| | 287 | return .{ .index = .none }; |
| 307 | } | 288 | } |
| | 289 | } else |env_err| switch (env_err) { |
| | 290 | error.EnvironmentVariableNotFound => { |
| | 291 | const stderr = std.io.getStdErr(); |
| | 292 | if (stderr.supportsAnsiEscapeCodes()) { |
| | 293 | global_progress.terminal = stderr; |
| | 294 | global_progress.supports_ansi_escape_codes = true; |
| | 295 | } else if (builtin.os.tag == .windows and stderr.isTty()) { |
| | 296 | global_progress.is_windows_terminal = true; |
| | 297 | global_progress.terminal = stderr; |
| | 298 | } else if (builtin.os.tag != .windows) { |
| | 299 | // we are in a "dumb" terminal like in acme or writing to a file |
| | 300 | global_progress.terminal = stderr; |
| | 301 | } |
| | 302 | |
| | 303 | if (global_progress.terminal == null) { |
| | 304 | return .{ .index = .none }; |
| | 305 | } |
| | 306 | |
| | 307 | var act: posix.Sigaction = .{ |
| | 308 | .handler = .{ .sigaction = handleSigWinch }, |
| | 309 | .mask = posix.empty_sigset, |
| | 310 | .flags = (posix.SA.SIGINFO | posix.SA.RESTART), |
| | 311 | }; |
| | 312 | posix.sigaction(posix.SIG.WINCH, &act, null) catch |err| { |
| | 313 | std.log.warn("failed to install SIGWINCH signal handler for noticing terminal resizes: {s}", .{@errorName(err)}); |
| | 314 | }; |
| | 315 | |
| | 316 | if (std.Thread.spawn(.{}, updateThreadRun, .{})) |thread| { |
| | 317 | global_progress.update_thread = thread; |
| | 318 | } else |err| { |
| | 319 | std.log.warn("unable to spawn thread for printing progress to terminal: {s}", .{@errorName(err)}); |
| | 320 | return .{ .index = .none }; |
| | 321 | } |
| | 322 | }, |
| | 323 | else => |e| { |
| | 324 | std.log.warn("invalid ZIG_PROGRESS file descriptor integer: {s}", .{@errorName(e)}); |
| | 325 | return .{ .index = .none }; |
| | 326 | }, |
| 308 | } | 327 | } |
| 309 | | 328 | |
| 310 | return root_node; | 329 | return root_node; |
| ... | @@ -326,12 +345,10 @@ fn updateThreadRun() void { | ... | @@ -326,12 +345,10 @@ fn updateThreadRun() void { |
| 326 | const resize_flag = wait(global_progress.initial_delay_ns); | 345 | const resize_flag = wait(global_progress.initial_delay_ns); |
| 327 | maybeUpdateSize(resize_flag); | 346 | maybeUpdateSize(resize_flag); |
| 328 | | 347 | |
| 329 | const buffer = b: { | 348 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) |
| 330 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) | 349 | return clearTerminal(); |
| 331 | return clearTerminal(); | | |
| 332 | | 350 | |
| 333 | break :b computeRedraw(); | 351 | const buffer = computeRedraw(); |
| 334 | }; | | |
| 335 | write(buffer); | 352 | write(buffer); |
| 336 | } | 353 | } |
| 337 | | 354 | |
| ... | @@ -339,16 +356,36 @@ fn updateThreadRun() void { | ... | @@ -339,16 +356,36 @@ fn updateThreadRun() void { |
| 339 | const resize_flag = wait(global_progress.refresh_rate_ns); | 356 | const resize_flag = wait(global_progress.refresh_rate_ns); |
| 340 | maybeUpdateSize(resize_flag); | 357 | maybeUpdateSize(resize_flag); |
| 341 | | 358 | |
| 342 | const buffer = b: { | 359 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) |
| 343 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) | 360 | return clearTerminal(); |
| 344 | return clearTerminal(); | | |
| 345 | | 361 | |
| 346 | break :b computeRedraw(); | 362 | const buffer = computeRedraw(); |
| 347 | }; | | |
| 348 | write(buffer); | 363 | write(buffer); |
| 349 | } | 364 | } |
| 350 | } | 365 | } |
| 351 | | 366 | |
| | 367 | fn ipcThreadRun(fd: posix.fd_t) void { |
| | 368 | { |
| | 369 | _ = wait(global_progress.initial_delay_ns); |
| | 370 | |
| | 371 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) |
| | 372 | return; |
| | 373 | |
| | 374 | const serialized = serialize(); |
| | 375 | writeIpc(fd, serialized); |
| | 376 | } |
| | 377 | |
| | 378 | while (true) { |
| | 379 | _ = wait(global_progress.refresh_rate_ns); |
| | 380 | |
| | 381 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) |
| | 382 | return clearTerminal(); |
| | 383 | |
| | 384 | const serialized = serialize(); |
| | 385 | writeIpc(fd, serialized); |
| | 386 | } |
| | 387 | } |
| | 388 | |
| 352 | const start_sync = "\x1b[?2026h"; | 389 | const start_sync = "\x1b[?2026h"; |
| 353 | const up_one_line = "\x1bM"; | 390 | const up_one_line = "\x1bM"; |
| 354 | const clear = "\x1b[J"; | 391 | const clear = "\x1b[J"; |
| ... | @@ -400,11 +437,17 @@ const Children = struct { | ... | @@ -400,11 +437,17 @@ const Children = struct { |
| 400 | sibling: Node.OptionalIndex, | 437 | sibling: Node.OptionalIndex, |
| 401 | }; | 438 | }; |
| 402 | | 439 | |
| 403 | fn computeRedraw() []u8 { | 440 | // TODO make this configurable |
| 404 | // TODO make this configurable | 441 | var serialized_node_parents_buffer: [default_node_storage_buffer_len]Node.Parent = undefined; |
| 405 | var serialized_node_parents_buffer: [default_node_storage_buffer_len]Node.Parent = undefined; | 442 | var serialized_node_storage_buffer: [default_node_storage_buffer_len]Node.Storage = undefined; |
| 406 | var serialized_node_storage_buffer: [default_node_storage_buffer_len]Node.Storage = undefined; | 443 | var serialized_node_map_buffer: [default_node_storage_buffer_len]Node.Index = undefined; |
| 407 | var serialized_node_map_buffer: [default_node_storage_buffer_len]Node.Index = undefined; | 444 | |
| | 445 | const Serialized = struct { |
| | 446 | parents: []Node.Parent, |
| | 447 | storage: []Node.Storage, |
| | 448 | }; |
| | 449 | |
| | 450 | fn serialize() Serialized { |
| 408 | var serialized_len: usize = 0; | 451 | var serialized_len: usize = 0; |
| 409 | | 452 | |
| 410 | // Iterate all of the nodes and construct a serializable copy of the state that can be examined | 453 | // Iterate all of the nodes and construct a serializable copy of the state that can be examined |
| ... | @@ -447,12 +490,21 @@ fn computeRedraw() []u8 { | ... | @@ -447,12 +490,21 @@ fn computeRedraw() []u8 { |
| 447 | }; | 490 | }; |
| 448 | } | 491 | } |
| 449 | | 492 | |
| | 493 | return .{ |
| | 494 | .parents = serialized_node_parents, |
| | 495 | .storage = serialized_node_storage, |
| | 496 | }; |
| | 497 | } |
| | 498 | |
| | 499 | fn computeRedraw() []u8 { |
| | 500 | const serialized = serialize(); |
| | 501 | |
| 450 | var children_buffer: [default_node_storage_buffer_len]Children = undefined; | 502 | var children_buffer: [default_node_storage_buffer_len]Children = undefined; |
| 451 | const children = children_buffer[0..serialized_len]; | 503 | const children = children_buffer[0..serialized.parents.len]; |
| 452 | | 504 | |
| 453 | @memset(children, .{ .child = .none, .sibling = .none }); | 505 | @memset(children, .{ .child = .none, .sibling = .none }); |
| 454 | | 506 | |
| 455 | for (serialized_node_parents, 0..) |parent, child_index_usize| { | 507 | for (serialized.parents, 0..) |parent, child_index_usize| { |
| 456 | const child_index: Node.Index = @enumFromInt(child_index_usize); | 508 | const child_index: Node.Index = @enumFromInt(child_index_usize); |
| 457 | assert(parent != .unused); | 509 | assert(parent != .unused); |
| 458 | const parent_index = parent.unwrap() orelse continue; | 510 | const parent_index = parent.unwrap() orelse continue; |
| ... | @@ -478,7 +530,7 @@ fn computeRedraw() []u8 { | ... | @@ -478,7 +530,7 @@ fn computeRedraw() []u8 { |
| 478 | i = computeClear(buf, i); | 530 | i = computeClear(buf, i); |
| 479 | | 531 | |
| 480 | const root_node_index: Node.Index = @enumFromInt(0); | 532 | const root_node_index: Node.Index = @enumFromInt(0); |
| 481 | i = computeNode(buf, i, serialized_node_storage, serialized_node_parents, children, root_node_index); | 533 | i = computeNode(buf, i, serialized, children, root_node_index); |
| 482 | | 534 | |
| 483 | // Truncate trailing newline. | 535 | // Truncate trailing newline. |
| 484 | if (buf[i - 1] == '\n') i -= 1; | 536 | if (buf[i - 1] == '\n') i -= 1; |
| ... | @@ -492,15 +544,14 @@ fn computeRedraw() []u8 { | ... | @@ -492,15 +544,14 @@ fn computeRedraw() []u8 { |
| 492 | fn computePrefix( | 544 | fn computePrefix( |
| 493 | buf: []u8, | 545 | buf: []u8, |
| 494 | start_i: usize, | 546 | start_i: usize, |
| 495 | serialized_node_storage: []const Node.Storage, | 547 | serialized: Serialized, |
| 496 | serialized_node_parents: []const Node.Parent, | | |
| 497 | children: []const Children, | 548 | children: []const Children, |
| 498 | node_index: Node.Index, | 549 | node_index: Node.Index, |
| 499 | ) usize { | 550 | ) usize { |
| 500 | var i = start_i; | 551 | var i = start_i; |
| 501 | const parent_index = serialized_node_parents[@intFromEnum(node_index)].unwrap() orelse return i; | 552 | const parent_index = serialized.parents[@intFromEnum(node_index)].unwrap() orelse return i; |
| 502 | if (serialized_node_parents[@intFromEnum(parent_index)] == .none) return i; | 553 | if (serialized.parents[@intFromEnum(parent_index)] == .none) return i; |
| 503 | i = computePrefix(buf, i, serialized_node_storage, serialized_node_parents, children, parent_index); | 554 | i = computePrefix(buf, i, serialized, children, parent_index); |
| 504 | if (children[@intFromEnum(parent_index)].sibling == .none) { | 555 | if (children[@intFromEnum(parent_index)].sibling == .none) { |
| 505 | buf[i..][0..3].* = " ".*; | 556 | buf[i..][0..3].* = " ".*; |
| 506 | i += 3; | 557 | i += 3; |
| ... | @@ -514,19 +565,18 @@ fn computePrefix( | ... | @@ -514,19 +565,18 @@ fn computePrefix( |
| 514 | fn computeNode( | 565 | fn computeNode( |
| 515 | buf: []u8, | 566 | buf: []u8, |
| 516 | start_i: usize, | 567 | start_i: usize, |
| 517 | serialized_node_storage: []const Node.Storage, | 568 | serialized: Serialized, |
| 518 | serialized_node_parents: []const Node.Parent, | | |
| 519 | children: []const Children, | 569 | children: []const Children, |
| 520 | node_index: Node.Index, | 570 | node_index: Node.Index, |
| 521 | ) usize { | 571 | ) usize { |
| 522 | var i = start_i; | 572 | var i = start_i; |
| 523 | i = computePrefix(buf, i, serialized_node_storage, serialized_node_parents, children, node_index); | 573 | i = computePrefix(buf, i, serialized, children, node_index); |
| 524 | | 574 | |
| 525 | const storage = &serialized_node_storage[@intFromEnum(node_index)]; | 575 | const storage = &serialized.storage[@intFromEnum(node_index)]; |
| 526 | const estimated_total = storage.estimated_total_count; | 576 | const estimated_total = storage.estimated_total_count; |
| 527 | const completed_items = storage.completed_count; | 577 | const completed_items = storage.completed_count; |
| 528 | const name = if (std.mem.indexOfScalar(u8, &storage.name, 0)) |end| storage.name[0..end] else &storage.name; | 578 | const name = if (std.mem.indexOfScalar(u8, &storage.name, 0)) |end| storage.name[0..end] else &storage.name; |
| 529 | const parent = serialized_node_parents[@intFromEnum(node_index)]; | 579 | const parent = serialized.parents[@intFromEnum(node_index)]; |
| 530 | | 580 | |
| 531 | if (parent != .none) { | 581 | if (parent != .none) { |
| 532 | if (children[@intFromEnum(node_index)].sibling == .none) { | 582 | if (children[@intFromEnum(node_index)].sibling == .none) { |
| ... | @@ -555,11 +605,11 @@ fn computeNode( | ... | @@ -555,11 +605,11 @@ fn computeNode( |
| 555 | global_progress.newline_count += 1; | 605 | global_progress.newline_count += 1; |
| 556 | | 606 | |
| 557 | if (children[@intFromEnum(node_index)].child.unwrap()) |child| { | 607 | if (children[@intFromEnum(node_index)].child.unwrap()) |child| { |
| 558 | i = computeNode(buf, i, serialized_node_storage, serialized_node_parents, children, child); | 608 | i = computeNode(buf, i, serialized, children, child); |
| 559 | } | 609 | } |
| 560 | | 610 | |
| 561 | if (children[@intFromEnum(node_index)].sibling.unwrap()) |sibling| { | 611 | if (children[@intFromEnum(node_index)].sibling.unwrap()) |sibling| { |
| 562 | i = computeNode(buf, i, serialized_node_storage, serialized_node_parents, children, sibling); | 612 | i = computeNode(buf, i, serialized, children, sibling); |
| 563 | } | 613 | } |
| 564 | | 614 | |
| 565 | return i; | 615 | return i; |
| ... | @@ -572,6 +622,27 @@ fn write(buf: []const u8) void { | ... | @@ -572,6 +622,27 @@ fn write(buf: []const u8) void { |
| 572 | }; | 622 | }; |
| 573 | } | 623 | } |
| 574 | | 624 | |
| | 625 | fn writeIpc(fd: posix.fd_t, serialized: Serialized) void { |
| | 626 | assert(serialized.parents.len == serialized.storage.len); |
| | 627 | const header = std.mem.asBytes(&serialized.parents.len); |
| | 628 | const storage = std.mem.sliceAsBytes(serialized.storage); |
| | 629 | const parents = std.mem.sliceAsBytes(serialized.parents); |
| | 630 | |
| | 631 | var vecs: [3]std.posix.iovec_const = .{ |
| | 632 | .{ .base = header.ptr, .len = header.len }, |
| | 633 | .{ .base = storage.ptr, .len = storage.len }, |
| | 634 | .{ .base = parents.ptr, .len = parents.len }, |
| | 635 | }; |
| | 636 | |
| | 637 | // TODO: if big endian, byteswap |
| | 638 | // this is needed because the parent or child process might be running in qemu |
| | 639 | |
| | 640 | const file: std.fs.File = .{ .handle = fd }; |
| | 641 | file.writevAll(&vecs) catch |err| { |
| | 642 | std.log.warn("failed to send progress to parent process: {s}", .{@errorName(err)}); |
| | 643 | }; |
| | 644 | } |
| | 645 | |
| 575 | fn maybeUpdateSize(resize_flag: bool) void { | 646 | fn maybeUpdateSize(resize_flag: bool) void { |
| 576 | if (!resize_flag) return; | 647 | if (!resize_flag) return; |
| 577 | | 648 | |