Skip to content

Commit ac13c1a

Browse files
committed
making watch reload after buildscript change
1 parent 91c1797 commit ac13c1a

File tree

3 files changed

+50
-8
lines changed

3 files changed

+50
-8
lines changed

lib/compiler/build_runner.zig

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ pub fn main() !void {
106106
var watch = false;
107107
var fuzz = false;
108108
var debounce_interval_ms: u16 = 50;
109+
var build_file: std.Build.Cache.Path = .{
110+
.root_dir = build_root_directory,
111+
.sub_path = "build.zig",
112+
};
109113

110114
while (nextArg(args, &arg_idx)) |arg| {
111115
if (mem.startsWith(u8, arg, "-Z")) {
@@ -127,6 +131,8 @@ pub fn main() !void {
127131
} else if (mem.startsWith(u8, arg, "-")) {
128132
if (mem.eql(u8, arg, "--verbose")) {
129133
builder.verbose = true;
134+
} else if (mem.eql(u8, arg, "--build-file")) {
135+
build_file.sub_path = nextArgOrFatal(args, &arg_idx);
130136
} else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
131137
help_menu = true;
132138
} else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
@@ -383,7 +389,7 @@ pub fn main() !void {
383389
else => return err,
384390
};
385391

386-
var w = if (watch) try Watch.init() else undefined;
392+
var w = if (watch) try Watch.init(build_file) else undefined;
387393

388394
try run.thread_pool.init(thread_pool_options);
389395
defer run.thread_pool.deinit();
@@ -437,6 +443,9 @@ pub fn main() !void {
437443
debouncing_node.end();
438444
debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0);
439445
},
446+
.build_file_changed => {
447+
return rerunExit();
448+
},
440449
.clean => {},
441450
};
442451
}
@@ -1352,6 +1361,11 @@ fn cleanExit() void {
13521361
process.exit(0);
13531362
}
13541363

1364+
fn rerunExit() void {
1365+
std.debug.lockStdErr();
1366+
process.exit(4);
1367+
}
1368+
13551369
/// Perhaps in the future there could be an Advanced Options flag such as
13561370
/// --debug-build-runner-leaks which would make this function return instead of
13571371
/// calling exit.

lib/std/Build/Watch.zig

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const assert = std.debug.assert;
77
const fatal = std.zig.fatal;
88

99
dir_table: DirTable,
10+
build_file: Cache.Path,
1011
os: Os,
1112
generation: Generation,
1213

@@ -31,6 +32,8 @@ const Os = switch (builtin.os.tag) {
3132

3233
/// Keyed differently but indexes correspond 1:1 with `dir_table`.
3334
handle_table: HandleTable,
35+
/// Initialized lazily.
36+
build_file_handle: ?FileHandle = null,
3437
poll_fds: [1]posix.pollfd,
3538

3639
const HandleTable = std.ArrayHashMapUnmanaged(FileHandle, ReactionSet, FileHandle.Adapter, false);
@@ -103,15 +106,15 @@ const Os = switch (builtin.os.tag) {
103106
return stack_lfh.clone(gpa);
104107
}
105108

106-
fn markDirtySteps(w: *Watch, gpa: Allocator) !bool {
109+
fn markDirtySteps(w: *Watch, gpa: Allocator) !WaitResult {
107110
const fan_fd = w.os.getFanFd();
108111
const fanotify = std.os.linux.fanotify;
109112
const M = fanotify.event_metadata;
110113
var events_buf: [256 + 4096]u8 = undefined;
111114
var any_dirty = false;
112115
while (true) {
113116
var len = posix.read(fan_fd, &events_buf) catch |err| switch (err) {
114-
error.WouldBlock => return any_dirty,
117+
error.WouldBlock => return if (any_dirty) .dirty else .timeout,
115118
else => |e| return e,
116119
};
117120
var meta: [*]align(1) M = @ptrCast(&events_buf);
@@ -124,7 +127,7 @@ const Os = switch (builtin.os.tag) {
124127
any_dirty = true;
125128
std.log.warn("file system watch queue overflowed; falling back to fstat", .{});
126129
markAllFilesDirty(w, gpa);
127-
return true;
130+
return .dirty;
128131
}
129132
const fid: *align(1) fanotify.event_info_fid = @ptrCast(meta + 1);
130133
switch (fid.hdr.info_type) {
@@ -139,6 +142,12 @@ const Os = switch (builtin.os.tag) {
139142
if (reaction_set.getPtr(file_name)) |step_set|
140143
any_dirty = markStepSetDirty(gpa, step_set, any_dirty);
141144
}
145+
146+
if (FileHandle.Adapter.eql(undefined, lfh, w.os.build_file_handle.?, undefined) and
147+
std.mem.eql(u8, file_name, w.build_file.sub_path))
148+
{
149+
return .build_file_changed;
150+
}
142151
},
143152
else => |t| std.log.warn("unexpected fanotify event '{s}'", .{@tagName(t)}),
144153
}
@@ -152,6 +161,20 @@ const Os = switch (builtin.os.tag) {
152161

153162
fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void {
154163
const fan_fd = w.os.getFanFd();
164+
165+
if (w.os.build_file_handle == null) {
166+
w.os.build_file_handle = try Os.getDirHandle(gpa, .{
167+
.root_dir = w.build_file.root_dir,
168+
.sub_path = "",
169+
});
170+
posix.fanotify_mark(fan_fd, .{
171+
.ADD = true,
172+
.ONLYDIR = true,
173+
}, fan_mask, w.build_file.root_dir.handle.fd, ".") catch |err| {
174+
fatal("unable to watch build file at {}: {s}", .{ w.build_file, @errorName(err) });
175+
};
176+
}
177+
155178
// Add missing marks and note persisted ones.
156179
for (steps) |step| {
157180
for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
@@ -240,7 +263,7 @@ const Os = switch (builtin.os.tag) {
240263
else => void,
241264
};
242265

243-
pub fn init() !Watch {
266+
pub fn init(build_file: Cache.Path) !Watch {
244267
switch (builtin.os.tag) {
245268
.linux => {
246269
const fan_fd = try std.posix.fanotify_init(.{
@@ -254,6 +277,7 @@ pub fn init() !Watch {
254277
}, 0);
255278
return .{
256279
.dir_table = .{},
280+
.build_file = build_file,
257281
.os = switch (builtin.os.tag) {
258282
.linux => .{
259283
.handle_table = .{},
@@ -342,6 +366,7 @@ pub const WaitResult = enum {
342366
/// File system watching triggered on files that were marked as inputs to at least one Step.
343367
/// Relevant steps have been marked dirty.
344368
dirty,
369+
build_file_changed,
345370
/// File system watching triggered but none of the events were relevant to
346371
/// what we are listening to. There is nothing to do.
347372
clean,
@@ -353,10 +378,8 @@ pub fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult {
353378
const events_len = try std.posix.poll(&w.os.poll_fds, timeout.to_i32_ms());
354379
return if (events_len == 0)
355380
.timeout
356-
else if (try Os.markDirtySteps(w, gpa))
357-
.dirty
358381
else
359-
.clean;
382+
try Os.markDirtySteps(w, gpa);
360383
},
361384
else => @compileError("unimplemented"),
362385
}

src/main.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4801,6 +4801,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
48014801
if (mem.startsWith(u8, arg, "-")) {
48024802
if (mem.eql(u8, arg, "--build-file")) {
48034803
if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
4804+
try child_argv.appendSlice(args[i .. i + 2]);
48044805
i += 1;
48054806
build_file = args[i];
48064807
continue;
@@ -5030,6 +5031,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
50305031

50315032
// This loop is re-evaluated when the build script exits with an indication that it
50325033
// could not continue due to missing lazy dependencies.
5034+
// PROGRESS: or the buildscript changed
50335035
while (true) {
50345036
// We want to release all the locks before executing the child process, so we make a nice
50355037
// big block here to ensure the cleanup gets run when we extract out our argv.
@@ -5357,6 +5359,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
53575359
continue;
53585360
}
53595361

5362+
// buildscript changed
5363+
if (code == 4) continue;
5364+
53605365
const cmd = try std.mem.join(arena, " ", child_argv.items);
53615366
fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd });
53625367
},

0 commit comments

Comments
 (0)