| ... | ... | @@ -74,7 +74,7 @@ pub const Options = struct { |
| 74 | 74 | pub const Node = struct { |
| 75 | 75 | index: OptionalIndex, |
| 76 | 76 | |
| 77 | | pub const max_name_len = 38; |
| 77 | pub const max_name_len = 40; |
| 78 | 78 | |
| 79 | 79 | const Storage = extern struct { |
| 80 | 80 | /// Little endian. |
| ... | ... | @@ -268,17 +268,7 @@ var node_freelist_buffer: [default_node_storage_buffer_len]Node.OptionalIndex = |
| 268 | 268 | pub fn start(options: Options) Node { |
| 269 | 269 | // Ensure there is only 1 global Progress object. |
| 270 | 270 | assert(global_progress.node_end_index == 0); |
| 271 | | const stderr = std.io.getStdErr(); |
| 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 | | } |
| 271 | |
| 282 | 272 | @memset(global_progress.node_parents, .unused); |
| 283 | 273 | const root_node = Node.init(@enumFromInt(0), .none, options.root_name, options.estimated_total_items); |
| 284 | 274 | global_progress.done = false; |
| ... | ... | @@ -289,22 +279,51 @@ pub fn start(options: Options) Node { |
| 289 | 279 | global_progress.refresh_rate_ns = options.refresh_rate_ns; |
| 290 | 280 | global_progress.initial_delay_ns = options.initial_delay_ns; |
| 291 | 281 | |
| 292 | | var act: posix.Sigaction = .{ |
| 293 | | .handler = .{ .sigaction = handleSigWinch }, |
| 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| { |
| 282 | if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| { |
| 283 | if (std.Thread.spawn(.{}, ipcThreadRun, .{ipc_fd})) |thread| { |
| 304 | 284 | global_progress.update_thread = thread; |
| 305 | | } else |_| { |
| 306 | | global_progress.terminal = null; |
| 285 | } else |err| { |
| 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 | 329 | return root_node; |
| ... | ... | @@ -326,12 +345,10 @@ fn updateThreadRun() void { |
| 326 | 345 | const resize_flag = wait(global_progress.initial_delay_ns); |
| 327 | 346 | maybeUpdateSize(resize_flag); |
| 328 | 347 | |
| 329 | | const buffer = b: { |
| 330 | | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) |
| 331 | | return clearTerminal(); |
| 348 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) |
| 349 | return clearTerminal(); |
| 332 | 350 | |
| 333 | | break :b computeRedraw(); |
| 334 | | }; |
| 351 | const buffer = computeRedraw(); |
| 335 | 352 | write(buffer); |
| 336 | 353 | } |
| 337 | 354 | |
| ... | ... | @@ -339,16 +356,36 @@ fn updateThreadRun() void { |
| 339 | 356 | const resize_flag = wait(global_progress.refresh_rate_ns); |
| 340 | 357 | maybeUpdateSize(resize_flag); |
| 341 | 358 | |
| 342 | | const buffer = b: { |
| 343 | | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) |
| 344 | | return clearTerminal(); |
| 359 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) |
| 360 | return clearTerminal(); |
| 345 | 361 | |
| 346 | | break :b computeRedraw(); |
| 347 | | }; |
| 362 | const buffer = computeRedraw(); |
| 348 | 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 | 389 | const start_sync = "\x1b[?2026h"; |
| 353 | 390 | const up_one_line = "\x1bM"; |
| 354 | 391 | const clear = "\x1b[J"; |
| ... | ... | @@ -400,11 +437,17 @@ const Children = struct { |
| 400 | 437 | sibling: Node.OptionalIndex, |
| 401 | 438 | }; |
| 402 | 439 | |
| 403 | | fn computeRedraw() []u8 { |
| 404 | | // TODO make this configurable |
| 405 | | var serialized_node_parents_buffer: [default_node_storage_buffer_len]Node.Parent = undefined; |
| 406 | | var serialized_node_storage_buffer: [default_node_storage_buffer_len]Node.Storage = undefined; |
| 407 | | var serialized_node_map_buffer: [default_node_storage_buffer_len]Node.Index = undefined; |
| 440 | // TODO make this configurable |
| 441 | 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; |
| 443 | 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 | 451 | var serialized_len: usize = 0; |
| 409 | 452 | |
| 410 | 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 | 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 | 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 | 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 | 508 | const child_index: Node.Index = @enumFromInt(child_index_usize); |
| 457 | 509 | assert(parent != .unused); |
| 458 | 510 | const parent_index = parent.unwrap() orelse continue; |
| ... | ... | @@ -478,7 +530,7 @@ fn computeRedraw() []u8 { |
| 478 | 530 | i = computeClear(buf, i); |
| 479 | 531 | |
| 480 | 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 | 535 | // Truncate trailing newline. |
| 484 | 536 | if (buf[i - 1] == '\n') i -= 1; |
| ... | ... | @@ -492,15 +544,14 @@ fn computeRedraw() []u8 { |
| 492 | 544 | fn computePrefix( |
| 493 | 545 | buf: []u8, |
| 494 | 546 | start_i: usize, |
| 495 | | serialized_node_storage: []const Node.Storage, |
| 496 | | serialized_node_parents: []const Node.Parent, |
| 547 | serialized: Serialized, |
| 497 | 548 | children: []const Children, |
| 498 | 549 | node_index: Node.Index, |
| 499 | 550 | ) usize { |
| 500 | 551 | var i = start_i; |
| 501 | | const parent_index = serialized_node_parents[@intFromEnum(node_index)].unwrap() orelse return i; |
| 502 | | if (serialized_node_parents[@intFromEnum(parent_index)] == .none) return i; |
| 503 | | i = computePrefix(buf, i, serialized_node_storage, serialized_node_parents, children, parent_index); |
| 552 | const parent_index = serialized.parents[@intFromEnum(node_index)].unwrap() orelse return i; |
| 553 | if (serialized.parents[@intFromEnum(parent_index)] == .none) return i; |
| 554 | i = computePrefix(buf, i, serialized, children, parent_index); |
| 504 | 555 | if (children[@intFromEnum(parent_index)].sibling == .none) { |
| 505 | 556 | buf[i..][0..3].* = " ".*; |
| 506 | 557 | i += 3; |
| ... | ... | @@ -514,19 +565,18 @@ fn computePrefix( |
| 514 | 565 | fn computeNode( |
| 515 | 566 | buf: []u8, |
| 516 | 567 | start_i: usize, |
| 517 | | serialized_node_storage: []const Node.Storage, |
| 518 | | serialized_node_parents: []const Node.Parent, |
| 568 | serialized: Serialized, |
| 519 | 569 | children: []const Children, |
| 520 | 570 | node_index: Node.Index, |
| 521 | 571 | ) usize { |
| 522 | 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 | 576 | const estimated_total = storage.estimated_total_count; |
| 527 | 577 | const completed_items = storage.completed_count; |
| 528 | 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 | 581 | if (parent != .none) { |
| 532 | 582 | if (children[@intFromEnum(node_index)].sibling == .none) { |
| ... | ... | @@ -555,11 +605,11 @@ fn computeNode( |
| 555 | 605 | global_progress.newline_count += 1; |
| 556 | 606 | |
| 557 | 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 | 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 | 615 | return i; |
| ... | ... | @@ -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 | 646 | fn maybeUpdateSize(resize_flag: bool) void { |
| 576 | 647 | if (!resize_flag) return; |
| 577 | 648 | |