| ... | @@ -8,6 +8,7 @@ const process = std.process; | ... | @@ -8,6 +8,7 @@ const process = std.process; |
| 8 | const ArrayList = std.ArrayList; | 8 | const ArrayList = std.ArrayList; |
| 9 | const File = std.fs.File; | 9 | const File = std.fs.File; |
| 10 | const Step = std.Build.Step; | 10 | const Step = std.Build.Step; |
| | 11 | const Watch = std.Build.Watch; |
| 11 | const Allocator = std.mem.Allocator; | 12 | const Allocator = std.mem.Allocator; |
| 12 | | 13 | |
| 13 | pub const root = @import("@build"); | 14 | pub const root = @import("@build"); |
| ... | @@ -400,34 +401,26 @@ pub fn main() !void { | ... | @@ -400,34 +401,26 @@ pub fn main() !void { |
| 400 | }; | 401 | }; |
| 401 | if (!watch) return cleanExit(); | 402 | if (!watch) return cleanExit(); |
| 402 | | 403 | |
| 403 | // Clear all file handles. | | |
| 404 | for (w.handle_table.keys(), w.handle_table.values()) |lfh, *step_set| { | | |
| 405 | lfh.destroy(gpa); | | |
| 406 | step_set.clearAndFree(gpa); | | |
| 407 | } | | |
| 408 | w.handle_table.clearRetainingCapacity(); | | |
| 409 | | | |
| 410 | // Add missing marks and note persisted ones. | 404 | // Add missing marks and note persisted ones. |
| 411 | for (run.step_stack.keys()) |step| { | 405 | for (run.step_stack.keys()) |step| { |
| 412 | for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { | 406 | for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { |
| 413 | { | 407 | const reaction_set = rs: { |
| 414 | const gop = try w.dir_table.getOrPut(gpa, path); | 408 | const gop = try w.dir_table.getOrPut(gpa, path); |
| 415 | gop.value_ptr.* = w.generation; | | |
| 416 | if (!gop.found_existing) { | 409 | if (!gop.found_existing) { |
| 417 | try std.posix.fanotify_mark(w.fan_fd, .{ | 410 | try std.posix.fanotify_mark(w.fan_fd, .{ |
| 418 | .ADD = true, | 411 | .ADD = true, |
| 419 | .ONLYDIR = true, | 412 | .ONLYDIR = true, |
| 420 | }, Watch.fan_mask, path.root_dir.handle.fd, path.subPathOpt()); | 413 | }, Watch.fan_mask, path.root_dir.handle.fd, path.subPathOpt()); |
| | 414 | |
| | 415 | const dir_handle = try Watch.getDirHandle(gpa, path); |
| | 416 | try w.handle_table.putNoClobber(gpa, dir_handle, .{}); |
| 421 | } | 417 | } |
| 422 | } | 418 | break :rs &w.handle_table.values()[gop.index]; |
| | 419 | }; |
| 423 | for (files.items) |basename| { | 420 | for (files.items) |basename| { |
| 424 | const file_handle = try Watch.getFileHandle(gpa, path, basename); | 421 | const gop = try reaction_set.getOrPut(gpa, basename); |
| 425 | std.debug.print("watching file_handle '{}{s}' = {}\n", .{ | | |
| 426 | path, basename, std.fmt.fmtSliceHexLower(file_handle.slice()), | | |
| 427 | }); | | |
| 428 | const gop = try w.handle_table.getOrPut(gpa, file_handle); | | |
| 429 | if (!gop.found_existing) gop.value_ptr.* = .{}; | 422 | if (!gop.found_existing) gop.value_ptr.* = .{}; |
| 430 | try gop.value_ptr.put(gpa, step, {}); | 423 | try gop.value_ptr.put(gpa, step, w.generation); |
| 431 | } | 424 | } |
| 432 | } | 425 | } |
| 433 | } | 426 | } |
| ... | @@ -435,11 +428,31 @@ pub fn main() !void { | ... | @@ -435,11 +428,31 @@ pub fn main() !void { |
| 435 | { | 428 | { |
| 436 | // Remove marks for files that are no longer inputs. | 429 | // Remove marks for files that are no longer inputs. |
| 437 | var i: usize = 0; | 430 | var i: usize = 0; |
| 438 | while (i < w.dir_table.entries.len) { | 431 | while (i < w.handle_table.entries.len) { |
| 439 | const generations = w.dir_table.values(); | 432 | { |
| 440 | if (generations[i] == w.generation) { | 433 | const reaction_set = &w.handle_table.values()[i]; |
| 441 | i += 1; | 434 | var step_set_i: usize = 0; |
| 442 | continue; | 435 | while (step_set_i < reaction_set.entries.len) { |
| | 436 | const step_set = &reaction_set.values()[step_set_i]; |
| | 437 | var dirent_i: usize = 0; |
| | 438 | while (dirent_i < step_set.entries.len) { |
| | 439 | const generations = step_set.values(); |
| | 440 | if (generations[dirent_i] == w.generation) { |
| | 441 | dirent_i += 1; |
| | 442 | continue; |
| | 443 | } |
| | 444 | step_set.swapRemoveAt(dirent_i); |
| | 445 | } |
| | 446 | if (step_set.entries.len > 0) { |
| | 447 | step_set_i += 1; |
| | 448 | continue; |
| | 449 | } |
| | 450 | reaction_set.swapRemoveAt(step_set_i); |
| | 451 | } |
| | 452 | if (reaction_set.entries.len > 0) { |
| | 453 | i += 1; |
| | 454 | continue; |
| | 455 | } |
| 443 | } | 456 | } |
| 444 | | 457 | |
| 445 | const path = w.dir_table.keys()[i]; | 458 | const path = w.dir_table.keys()[i]; |
| ... | @@ -450,6 +463,7 @@ pub fn main() !void { | ... | @@ -450,6 +463,7 @@ pub fn main() !void { |
| 450 | }, Watch.fan_mask, path.root_dir.handle.fd, path.subPathOpt()); | 463 | }, Watch.fan_mask, path.root_dir.handle.fd, path.subPathOpt()); |
| 451 | | 464 | |
| 452 | w.dir_table.swapRemoveAt(i); | 465 | w.dir_table.swapRemoveAt(i); |
| | 466 | w.handle_table.swapRemoveAt(i); |
| 453 | } | 467 | } |
| 454 | w.generation +%= 1; | 468 | w.generation +%= 1; |
| 455 | } | 469 | } |
| ... | @@ -459,7 +473,7 @@ pub fn main() !void { | ... | @@ -459,7 +473,7 @@ pub fn main() !void { |
| 459 | // if any more events come in. After the debounce interval has passed, | 473 | // if any more events come in. After the debounce interval has passed, |
| 460 | // trigger a rebuild on all steps with modified inputs, as well as their | 474 | // trigger a rebuild on all steps with modified inputs, as well as their |
| 461 | // recursive dependants. | 475 | // recursive dependants. |
| 462 | const debounce_interval_ms = 10; | 476 | const debounce_interval_ms = 50; |
| 463 | var poll_fds: [1]std.posix.pollfd = .{ | 477 | var poll_fds: [1]std.posix.pollfd = .{ |
| 464 | .{ | 478 | .{ |
| 465 | .fd = w.fan_fd, | 479 | .fd = w.fan_fd, |
| ... | @@ -517,46 +531,48 @@ fn markDirtySteps(w: *Watch) !bool { | ... | @@ -517,46 +531,48 @@ fn markDirtySteps(w: *Watch) !bool { |
| 517 | const file_name = mem.span(file_name_z); | 531 | const file_name = mem.span(file_name_z); |
| 518 | std.debug.print("DFID_NAME file_handle = {any}, found: '{s}'\n", .{ file_handle.*, file_name }); | 532 | std.debug.print("DFID_NAME file_handle = {any}, found: '{s}'\n", .{ file_handle.*, file_name }); |
| 519 | const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; | 533 | const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; |
| 520 | if (w.handle_table.get(lfh)) |step_set| { | 534 | if (w.handle_table.getPtr(lfh)) |reaction_set| { |
| 521 | for (step_set.keys()) |step| { | 535 | if (reaction_set.getPtr(file_name)) |step_set| { |
| 522 | std.debug.print("DFID_NAME marking step '{s}' dirty\n", .{step.name}); | 536 | for (step_set.keys()) |step| { |
| 523 | step.state = .precheck_done; | 537 | std.debug.print("DFID_NAME marking step '{s}' dirty\n", .{step.name}); |
| 524 | any_dirty = true; | 538 | step.state = .precheck_done; |
| | 539 | any_dirty = true; |
| | 540 | } |
| 525 | } | 541 | } |
| 526 | } else { | 542 | } else { |
| 527 | std.debug.print("DFID_NAME changed file did not match any steps: '{}'\n", .{ | 543 | std.debug.print("DFID_NAME changed file did not match any directories: '{}'\n", .{ |
| 528 | std.fmt.fmtSliceHexLower(lfh.slice()), | 544 | std.fmt.fmtSliceHexLower(lfh.slice()), |
| 529 | }); | 545 | }); |
| 530 | } | 546 | } |
| 531 | }, | 547 | }, |
| 532 | .FID => { | 548 | //.FID => { |
| 533 | const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); | 549 | // const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); |
| 534 | const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; | 550 | // const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; |
| 535 | if (w.handle_table.get(lfh)) |step_set| { | 551 | // if (w.handle_table.get(lfh)) |step_set| { |
| 536 | for (step_set.keys()) |step| { | 552 | // for (step_set.keys()) |step| { |
| 537 | std.debug.print("FID marking step '{s}' dirty\n", .{step.name}); | 553 | // std.debug.print("FID marking step '{s}' dirty\n", .{step.name}); |
| 538 | step.state = .precheck_done; | 554 | // step.state = .precheck_done; |
| 539 | any_dirty = true; | 555 | // any_dirty = true; |
| 540 | } | 556 | // } |
| 541 | } else { | 557 | // } else { |
| 542 | std.debug.print("FID changed file did not match any steps: '{}'\n", .{ | 558 | // std.debug.print("FID changed file did not match any steps: '{}'\n", .{ |
| 543 | std.fmt.fmtSliceHexLower(lfh.slice()), | 559 | // std.fmt.fmtSliceHexLower(lfh.slice()), |
| 544 | }); | 560 | // }); |
| 545 | } | 561 | // } |
| 546 | }, | 562 | //}, |
| 547 | .DFID => { | 563 | //.DFID => { |
| 548 | const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); | 564 | // const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); |
| 549 | const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; | 565 | // const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; |
| 550 | if (w.handle_table.get(lfh)) |step_set| { | 566 | // if (w.handle_table.get(lfh)) |step_set| { |
| 551 | for (step_set.keys()) |step| { | 567 | // for (step_set.keys()) |step| { |
| 552 | std.debug.print("DFID marking step '{s}' dirty\n", .{step.name}); | 568 | // std.debug.print("DFID marking step '{s}' dirty\n", .{step.name}); |
| 553 | step.state = .precheck_done; | 569 | // step.state = .precheck_done; |
| 554 | any_dirty = true; | 570 | // any_dirty = true; |
| 555 | } | 571 | // } |
| 556 | } else { | 572 | // } else { |
| 557 | std.debug.print("DFID changed file did not match any steps\n", .{}); | 573 | // std.debug.print("DFID changed file did not match any steps\n", .{}); |
| 558 | } | 574 | // } |
| 559 | }, | 575 | //}, |
| 560 | else => |t| { | 576 | else => |t| { |
| 561 | std.debug.panic("TODO: received event type '{s}'", .{@tagName(t)}); | 577 | std.debug.panic("TODO: received event type '{s}'", .{@tagName(t)}); |
| 562 | }, | 578 | }, |
| ... | @@ -565,98 +581,6 @@ fn markDirtySteps(w: *Watch) !bool { | ... | @@ -565,98 +581,6 @@ fn markDirtySteps(w: *Watch) !bool { |
| 565 | } | 581 | } |
| 566 | } | 582 | } |
| 567 | | 583 | |
| 568 | const Watch = struct { | | |
| 569 | dir_table: DirTable, | | |
| 570 | handle_table: HandleTable, | | |
| 571 | fan_fd: std.posix.fd_t, | | |
| 572 | generation: u8, | | |
| 573 | | | |
| 574 | const fan_mask: std.os.linux.fanotify.MarkMask = .{ | | |
| 575 | .CLOSE_WRITE = true, | | |
| 576 | .DELETE = true, | | |
| 577 | .MOVED_FROM = true, | | |
| 578 | .MOVED_TO = true, | | |
| 579 | .EVENT_ON_CHILD = true, | | |
| 580 | }; | | |
| 581 | | | |
| 582 | const init: Watch = .{ | | |
| 583 | .dir_table = .{}, | | |
| 584 | .handle_table = .{}, | | |
| 585 | .fan_fd = -1, | | |
| 586 | .generation = 0, | | |
| 587 | }; | | |
| 588 | | | |
| 589 | /// Key is the directory to watch which contains one or more files we are | | |
| 590 | /// interested in noticing changes to. | | |
| 591 | /// | | |
| 592 | /// Value is generation. | | |
| 593 | const DirTable = std.ArrayHashMapUnmanaged(Cache.Path, u8, Cache.Path.TableAdapter, false); | | |
| 594 | | | |
| 595 | const HandleTable = std.ArrayHashMapUnmanaged(LinuxFileHandle, StepSet, LinuxFileHandle.Adapter, false); | | |
| 596 | const StepSet = std.AutoArrayHashMapUnmanaged(*Step, void); | | |
| 597 | | | |
| 598 | const Hash = std.hash.Wyhash; | | |
| 599 | const Cache = std.Build.Cache; | | |
| 600 | | | |
| 601 | const LinuxFileHandle = struct { | | |
| 602 | handle: *align(1) std.os.linux.file_handle, | | |
| 603 | | | |
| 604 | fn clone(lfh: LinuxFileHandle, gpa: Allocator) Allocator.Error!LinuxFileHandle { | | |
| 605 | const bytes = lfh.slice(); | | |
| 606 | const new_ptr = try gpa.alignedAlloc( | | |
| 607 | u8, | | |
| 608 | @alignOf(std.os.linux.file_handle), | | |
| 609 | @sizeOf(std.os.linux.file_handle) + bytes.len, | | |
| 610 | ); | | |
| 611 | const new_header: *std.os.linux.file_handle = @ptrCast(new_ptr); | | |
| 612 | new_header.* = lfh.handle.*; | | |
| 613 | const new: LinuxFileHandle = .{ .handle = new_header }; | | |
| 614 | @memcpy(new.slice(), lfh.slice()); | | |
| 615 | return new; | | |
| 616 | } | | |
| 617 | | | |
| 618 | fn destroy(lfh: LinuxFileHandle, gpa: Allocator) void { | | |
| 619 | const ptr: [*]u8 = @ptrCast(lfh.handle); | | |
| 620 | const allocated_slice = ptr[0 .. @sizeOf(std.os.linux.file_handle) + lfh.handle.handle_bytes]; | | |
| 621 | return gpa.free(allocated_slice); | | |
| 622 | } | | |
| 623 | | | |
| 624 | fn slice(lfh: LinuxFileHandle) []u8 { | | |
| 625 | const ptr: [*]u8 = &lfh.handle.f_handle; | | |
| 626 | return ptr[0..lfh.handle.handle_bytes]; | | |
| 627 | } | | |
| 628 | | | |
| 629 | const Adapter = struct { | | |
| 630 | pub fn hash(self: Adapter, a: LinuxFileHandle) u32 { | | |
| 631 | _ = self; | | |
| 632 | const unsigned_type: u32 = @bitCast(a.handle.handle_type); | | |
| 633 | return @truncate(Hash.hash(unsigned_type, a.slice())); | | |
| 634 | } | | |
| 635 | pub fn eql(self: Adapter, a: LinuxFileHandle, b: LinuxFileHandle, b_index: usize) bool { | | |
| 636 | _ = self; | | |
| 637 | _ = b_index; | | |
| 638 | return a.handle.handle_type == b.handle.handle_type and mem.eql(u8, a.slice(), b.slice()); | | |
| 639 | } | | |
| 640 | }; | | |
| 641 | }; | | |
| 642 | | | |
| 643 | fn getFileHandle(gpa: Allocator, path: std.Build.Cache.Path, basename: []const u8) !LinuxFileHandle { | | |
| 644 | var file_handle_buffer: [@sizeOf(std.os.linux.file_handle) + 128]u8 align(@alignOf(std.os.linux.file_handle)) = undefined; | | |
| 645 | var mount_id: i32 = undefined; | | |
| 646 | var buf: [std.fs.max_path_bytes]u8 = undefined; | | |
| 647 | const joined_path = if (path.sub_path.len == 0) basename else path: { | | |
| 648 | break :path std.fmt.bufPrint(&buf, "{s}" ++ std.fs.path.sep_str ++ "{s}", .{ | | |
| 649 | path.sub_path, basename, | | |
| 650 | }) catch return error.NameTooLong; | | |
| 651 | }; | | |
| 652 | const stack_ptr: *std.os.linux.file_handle = @ptrCast(&file_handle_buffer); | | |
| 653 | stack_ptr.handle_bytes = file_handle_buffer.len - @sizeOf(std.os.linux.file_handle); | | |
| 654 | try std.posix.name_to_handle_at(path.root_dir.handle.fd, joined_path, stack_ptr, &mount_id, 0); | | |
| 655 | const stack_lfh: LinuxFileHandle = .{ .handle = stack_ptr }; | | |
| 656 | return stack_lfh.clone(gpa); | | |
| 657 | } | | |
| 658 | }; | | |
| 659 | | | |
| 660 | const Run = struct { | 584 | const Run = struct { |
| 661 | max_rss: u64, | 585 | max_rss: u64, |
| 662 | max_rss_is_default: bool, | 586 | max_rss_is_default: bool, |