Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion lib/compiler/build_runner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Expand All @@ -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")) {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 => {},
};
}
Expand Down Expand Up @@ -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.
Expand Down
37 changes: 30 additions & 7 deletions lib/std/Build/Watch.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand All @@ -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);
Expand Down Expand Up @@ -103,15 +106,15 @@ 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;
var events_buf: [256 + 4096]u8 = undefined;
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);
Expand All @@ -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) {
Expand All @@ -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)}),
}
Expand All @@ -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| {
Expand Down Expand Up @@ -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(.{
Expand All @@ -521,6 +544,7 @@ pub fn init() !Watch {
}, 0);
return .{
.dir_table = .{},
.build_file = build_file,
.os = switch (builtin.os.tag) {
.linux => .{
.handle_table = .{},
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 });
},
Expand Down