| ... | ... | @@ -8,6 +8,7 @@ const process = std.process; |
| 8 | 8 | const ArrayList = std.ArrayList; |
| 9 | 9 | const File = std.fs.File; |
| 10 | 10 | const Step = std.Build.Step; |
| 11 | const Watch = std.Build.Watch; |
| 11 | 12 | const Allocator = std.mem.Allocator; |
| 12 | 13 | |
| 13 | 14 | pub const root = @import("@build"); |
| ... | ... | @@ -400,34 +401,26 @@ pub fn main() !void { |
| 400 | 401 | }; |
| 401 | 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 | 404 | // Add missing marks and note persisted ones. |
| 411 | 405 | for (run.step_stack.keys()) |step| { |
| 412 | 406 | for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { |
| 413 | | { |
| 407 | const reaction_set = rs: { |
| 414 | 408 | const gop = try w.dir_table.getOrPut(gpa, path); |
| 415 | | gop.value_ptr.* = w.generation; |
| 416 | 409 | if (!gop.found_existing) { |
| 417 | 410 | try std.posix.fanotify_mark(w.fan_fd, .{ |
| 418 | 411 | .ADD = true, |
| 419 | 412 | .ONLYDIR = true, |
| 420 | 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 | 420 | for (files.items) |basename| { |
| 424 | | const file_handle = try Watch.getFileHandle(gpa, path, 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); |
| 421 | const gop = try reaction_set.getOrPut(gpa, basename); |
| 429 | 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 | 428 | { |
| 436 | 429 | // Remove marks for files that are no longer inputs. |
| 437 | 430 | var i: usize = 0; |
| 438 | | while (i < w.dir_table.entries.len) { |
| 439 | | const generations = w.dir_table.values(); |
| 440 | | if (generations[i] == w.generation) { |
| 441 | | i += 1; |
| 442 | | continue; |
| 431 | while (i < w.handle_table.entries.len) { |
| 432 | { |
| 433 | const reaction_set = &w.handle_table.values()[i]; |
| 434 | var step_set_i: usize = 0; |
| 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 | 458 | const path = w.dir_table.keys()[i]; |
| ... | ... | @@ -450,6 +463,7 @@ pub fn main() !void { |
| 450 | 463 | }, Watch.fan_mask, path.root_dir.handle.fd, path.subPathOpt()); |
| 451 | 464 | |
| 452 | 465 | w.dir_table.swapRemoveAt(i); |
| 466 | w.handle_table.swapRemoveAt(i); |
| 453 | 467 | } |
| 454 | 468 | w.generation +%= 1; |
| 455 | 469 | } |
| ... | ... | @@ -459,7 +473,7 @@ pub fn main() !void { |
| 459 | 473 | // if any more events come in. After the debounce interval has passed, |
| 460 | 474 | // trigger a rebuild on all steps with modified inputs, as well as their |
| 461 | 475 | // recursive dependants. |
| 462 | | const debounce_interval_ms = 10; |
| 476 | const debounce_interval_ms = 50; |
| 463 | 477 | var poll_fds: [1]std.posix.pollfd = .{ |
| 464 | 478 | .{ |
| 465 | 479 | .fd = w.fan_fd, |
| ... | ... | @@ -517,46 +531,48 @@ fn markDirtySteps(w: *Watch) !bool { |
| 517 | 531 | const file_name = mem.span(file_name_z); |
| 518 | 532 | std.debug.print("DFID_NAME file_handle = {any}, found: '{s}'\n", .{ file_handle.*, file_name }); |
| 519 | 533 | const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; |
| 520 | | if (w.handle_table.get(lfh)) |step_set| { |
| 521 | | for (step_set.keys()) |step| { |
| 522 | | std.debug.print("DFID_NAME marking step '{s}' dirty\n", .{step.name}); |
| 523 | | step.state = .precheck_done; |
| 524 | | any_dirty = true; |
| 534 | if (w.handle_table.getPtr(lfh)) |reaction_set| { |
| 535 | if (reaction_set.getPtr(file_name)) |step_set| { |
| 536 | for (step_set.keys()) |step| { |
| 537 | std.debug.print("DFID_NAME marking step '{s}' dirty\n", .{step.name}); |
| 538 | step.state = .precheck_done; |
| 539 | any_dirty = true; |
| 540 | } |
| 525 | 541 | } |
| 526 | 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 | 544 | std.fmt.fmtSliceHexLower(lfh.slice()), |
| 529 | 545 | }); |
| 530 | 546 | } |
| 531 | 547 | }, |
| 532 | | .FID => { |
| 533 | | const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); |
| 534 | | const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; |
| 535 | | if (w.handle_table.get(lfh)) |step_set| { |
| 536 | | for (step_set.keys()) |step| { |
| 537 | | std.debug.print("FID marking step '{s}' dirty\n", .{step.name}); |
| 538 | | step.state = .precheck_done; |
| 539 | | any_dirty = true; |
| 540 | | } |
| 541 | | } else { |
| 542 | | std.debug.print("FID changed file did not match any steps: '{}'\n", .{ |
| 543 | | std.fmt.fmtSliceHexLower(lfh.slice()), |
| 544 | | }); |
| 545 | | } |
| 546 | | }, |
| 547 | | .DFID => { |
| 548 | | const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); |
| 549 | | const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; |
| 550 | | if (w.handle_table.get(lfh)) |step_set| { |
| 551 | | for (step_set.keys()) |step| { |
| 552 | | std.debug.print("DFID marking step '{s}' dirty\n", .{step.name}); |
| 553 | | step.state = .precheck_done; |
| 554 | | any_dirty = true; |
| 555 | | } |
| 556 | | } else { |
| 557 | | std.debug.print("DFID changed file did not match any steps\n", .{}); |
| 558 | | } |
| 559 | | }, |
| 548 | //.FID => { |
| 549 | // const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); |
| 550 | // const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; |
| 551 | // if (w.handle_table.get(lfh)) |step_set| { |
| 552 | // for (step_set.keys()) |step| { |
| 553 | // std.debug.print("FID marking step '{s}' dirty\n", .{step.name}); |
| 554 | // step.state = .precheck_done; |
| 555 | // any_dirty = true; |
| 556 | // } |
| 557 | // } else { |
| 558 | // std.debug.print("FID changed file did not match any steps: '{}'\n", .{ |
| 559 | // std.fmt.fmtSliceHexLower(lfh.slice()), |
| 560 | // }); |
| 561 | // } |
| 562 | //}, |
| 563 | //.DFID => { |
| 564 | // const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); |
| 565 | // const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; |
| 566 | // if (w.handle_table.get(lfh)) |step_set| { |
| 567 | // for (step_set.keys()) |step| { |
| 568 | // std.debug.print("DFID marking step '{s}' dirty\n", .{step.name}); |
| 569 | // step.state = .precheck_done; |
| 570 | // any_dirty = true; |
| 571 | // } |
| 572 | // } else { |
| 573 | // std.debug.print("DFID changed file did not match any steps\n", .{}); |
| 574 | // } |
| 575 | //}, |
| 560 | 576 | else => |t| { |
| 561 | 577 | std.debug.panic("TODO: received event type '{s}'", .{@tagName(t)}); |
| 562 | 578 | }, |
| ... | ... | @@ -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 | 584 | const Run = struct { |
| 661 | 585 | max_rss: u64, |
| 662 | 586 | max_rss_is_default: bool, |