diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 690c93754553..c18b1e19b693 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -112,6 +112,10 @@ pub fn main() !void { var watch = false; var fuzz = false; var debounce_interval_ms: u16 = 50; + var build_file: std.Build.Cache.Path = .{ + .root_dir = build_root_directory, + .sub_path = "build.zig", + }; var listen_port: u16 = 0; while (nextArg(args, &arg_idx)) |arg| { @@ -134,6 +138,8 @@ pub fn main() !void { } else if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "--verbose")) { builder.verbose = true; + } else if (mem.eql(u8, arg, "--build-file")) { + build_file.sub_path = nextArgOrFatal(args, &arg_idx); } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { help_menu = true; } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) { @@ -398,7 +404,7 @@ pub fn main() !void { else => return err, }; - var w = if (watch) try Watch.init() else undefined; + var w = if (watch) try Watch.init(build_file) else undefined; try run.thread_pool.init(thread_pool_options); defer run.thread_pool.deinit(); @@ -472,6 +478,9 @@ pub fn main() !void { debouncing_node.end(); debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0); }, + .build_file_changed => { + return rerunExit(); + }, .clean => {}, }; } @@ -1387,6 +1396,11 @@ fn cleanExit() void { process.exit(0); } +fn rerunExit() void { + std.debug.lockStdErr(); + process.exit(4); +} + /// Perhaps in the future there could be an Advanced Options flag such as /// --debug-build-runner-leaks which would make this function return instead of /// calling exit. diff --git a/lib/std/Build/Watch.zig b/lib/std/Build/Watch.zig index efcf055fdb37..3d865bc551e2 100644 --- a/lib/std/Build/Watch.zig +++ b/lib/std/Build/Watch.zig @@ -7,6 +7,7 @@ const assert = std.debug.assert; const fatal = std.zig.fatal; dir_table: DirTable, +build_file: Cache.Path, os: Os, generation: Generation, @@ -31,6 +32,8 @@ const Os = switch (builtin.os.tag) { /// Keyed differently but indexes correspond 1:1 with `dir_table`. handle_table: HandleTable, + /// Initialized lazily. + build_file_handle: ?FileHandle = null, poll_fds: [1]posix.pollfd, const HandleTable = std.ArrayHashMapUnmanaged(FileHandle, ReactionSet, FileHandle.Adapter, false); @@ -103,7 +106,7 @@ const Os = switch (builtin.os.tag) { return stack_lfh.clone(gpa); } - fn markDirtySteps(w: *Watch, gpa: Allocator) !bool { + fn markDirtySteps(w: *Watch, gpa: Allocator) !WaitResult { const fan_fd = w.os.getFanFd(); const fanotify = std.os.linux.fanotify; const M = fanotify.event_metadata; @@ -111,7 +114,7 @@ const Os = switch (builtin.os.tag) { var any_dirty = false; while (true) { var len = posix.read(fan_fd, &events_buf) catch |err| switch (err) { - error.WouldBlock => return any_dirty, + error.WouldBlock => return if (any_dirty) .dirty else .timeout, else => |e| return e, }; var meta: [*]align(1) M = @ptrCast(&events_buf); @@ -124,7 +127,7 @@ const Os = switch (builtin.os.tag) { any_dirty = true; std.log.warn("file system watch queue overflowed; falling back to fstat", .{}); markAllFilesDirty(w, gpa); - return true; + return .dirty; } const fid: *align(1) fanotify.event_info_fid = @ptrCast(meta + 1); switch (fid.hdr.info_type) { @@ -139,6 +142,12 @@ const Os = switch (builtin.os.tag) { if (reaction_set.getPtr(file_name)) |step_set| any_dirty = markStepSetDirty(gpa, step_set, any_dirty); } + + if (FileHandle.Adapter.eql(undefined, lfh, w.os.build_file_handle.?, undefined) and + std.mem.eql(u8, file_name, w.build_file.sub_path)) + { + return .build_file_changed; + } }, else => |t| std.log.warn("unexpected fanotify event '{s}'", .{@tagName(t)}), } @@ -152,6 +161,20 @@ const Os = switch (builtin.os.tag) { fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void { const fan_fd = w.os.getFanFd(); + + if (w.os.build_file_handle == null) { + w.os.build_file_handle = try Os.getDirHandle(gpa, .{ + .root_dir = w.build_file.root_dir, + .sub_path = "", + }); + posix.fanotify_mark(fan_fd, .{ + .ADD = true, + .ONLYDIR = true, + }, fan_mask, w.build_file.root_dir.handle.fd, ".") catch |err| { + fatal("unable to watch build file at {}: {s}", .{ w.build_file, @errorName(err) }); + }; + } + // Add missing marks and note persisted ones. for (steps) |step| { for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { @@ -507,7 +530,7 @@ const Os = switch (builtin.os.tag) { else => void, }; -pub fn init() !Watch { +pub fn init(build_file: Cache.Path) !Watch { switch (builtin.os.tag) { .linux => { const fan_fd = try std.posix.fanotify_init(.{ @@ -521,6 +544,7 @@ pub fn init() !Watch { }, 0); return .{ .dir_table = .{}, + .build_file = build_file, .os = switch (builtin.os.tag) { .linux => .{ .handle_table = .{}, @@ -623,6 +647,7 @@ pub const WaitResult = enum { /// File system watching triggered on files that were marked as inputs to at least one Step. /// Relevant steps have been marked dirty. dirty, + build_file_changed, /// File system watching triggered but none of the events were relevant to /// what we are listening to. There is nothing to do. clean, @@ -634,10 +659,8 @@ pub fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult { const events_len = try std.posix.poll(&w.os.poll_fds, timeout.to_i32_ms()); return if (events_len == 0) .timeout - else if (try Os.markDirtySteps(w, gpa)) - .dirty else - .clean; + try Os.markDirtySteps(w, gpa); }, .windows => { var bytes_transferred: std.os.windows.DWORD = undefined; diff --git a/src/main.zig b/src/main.zig index 4213d316be65..95a809b58bed 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4817,6 +4817,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "--build-file")) { if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); + try child_argv.appendSlice(args[i .. i + 2]); i += 1; build_file = args[i]; continue; @@ -5046,6 +5047,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { // This loop is re-evaluated when the build script exits with an indication that it // could not continue due to missing lazy dependencies. + // PROGRESS: or the buildscript changed while (true) { // We want to release all the locks before executing the child process, so we make a nice // big block here to ensure the cleanup gets run when we extract out our argv. @@ -5373,6 +5375,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { continue; } + // buildscript changed + if (code == 4) continue; + const cmd = try std.mem.join(arena, " ", child_argv.items); fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd }); },