Commit 6e025fc2e2
Changed files (4)
lib
compiler
std
Build
lib/compiler/build_runner.zig
@@ -74,6 +74,7 @@ pub fn main() !void {
.query = .{},
.result = try std.zig.system.resolveTargetQuery(.{}),
},
+ .watch = null,
};
graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
@@ -97,12 +98,12 @@ pub fn main() !void {
var dir_list = std.Build.DirList{};
var summary: ?Summary = null;
var max_rss: u64 = 0;
- var skip_oom_steps: bool = false;
+ var skip_oom_steps = false;
var color: Color = .auto;
var seed: u32 = 0;
- var prominent_compile_errors: bool = false;
- var help_menu: bool = false;
- var steps_menu: bool = false;
+ var prominent_compile_errors = false;
+ var help_menu = false;
+ var steps_menu = false;
var output_tmp_nonce: ?[16]u8 = null;
while (nextArg(args, &arg_idx)) |arg| {
@@ -227,6 +228,10 @@ pub fn main() !void {
builder.verbose_llvm_cpu_features = true;
} else if (mem.eql(u8, arg, "--prominent-compile-errors")) {
prominent_compile_errors = true;
+ } else if (mem.eql(u8, arg, "--watch")) {
+ const watch = try arena.create(std.Build.Watch);
+ watch.* = std.Build.Watch.init;
+ graph.watch = watch;
} else if (mem.eql(u8, arg, "-fwine")) {
builder.enable_wine = true;
} else if (mem.eql(u8, arg, "-fno-wine")) {
@@ -344,7 +349,7 @@ pub fn main() !void {
.prominent_compile_errors = prominent_compile_errors,
.claimed_rss = 0,
- .summary = summary,
+ .summary = summary orelse if (graph.watch != null) .new else .failures,
.ttyconf = ttyconf,
.stderr = stderr,
};
@@ -363,7 +368,10 @@ pub fn main() !void {
&run,
seed,
) catch |err| switch (err) {
- error.UncleanExit => process.exit(1),
+ error.UncleanExit => {
+ if (graph.watch == null)
+ process.exit(1);
+ },
else => return err,
};
}
@@ -377,7 +385,7 @@ const Run = struct {
prominent_compile_errors: bool,
claimed_rss: usize,
- summary: ?Summary,
+ summary: Summary,
ttyconf: std.io.tty.Config,
stderr: File,
};
@@ -417,7 +425,7 @@ fn runStepNames(
for (starting_steps) |s| {
constructGraphAndCheckForDependencyLoop(b, s, &step_stack, rand) catch |err| switch (err) {
- error.DependencyLoopDetected => return error.UncleanExit,
+ error.DependencyLoopDetected => return uncleanExit(),
else => |e| return e,
};
}
@@ -442,7 +450,7 @@ fn runStepNames(
if (run.max_rss_is_default) {
std.debug.print("note: use --maxrss to override the default", .{});
}
- return error.UncleanExit;
+ return uncleanExit();
}
}
@@ -524,13 +532,19 @@ fn runStepNames(
// A proper command line application defaults to silently succeeding.
// The user may request verbose mode if they have a different preference.
- const failures_only = run.summary != .all and run.summary != .new;
- if (failure_count == 0 and failures_only) return cleanExit();
+ const failures_only = switch (run.summary) {
+ .failures, .none => true,
+ else => false,
+ };
+ if (failure_count == 0 and failures_only) {
+ if (b.graph.watch != null) return;
+ return cleanExit();
+ }
const ttyconf = run.ttyconf;
const stderr = run.stderr;
- if (run.summary != Summary.none) {
+ if (run.summary != .none) {
const total_count = success_count + failure_count + pending_count + skipped_count;
ttyconf.setColor(stderr, .cyan) catch {};
stderr.writeAll("Build Summary:") catch {};
@@ -544,11 +558,6 @@ fn runStepNames(
if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};
if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};
- if (run.summary == null) {
- ttyconf.setColor(stderr, .dim) catch {};
- stderr.writeAll(" (disable with --summary none)") catch {};
- ttyconf.setColor(stderr, .reset) catch {};
- }
stderr.writeAll("\n") catch {};
// Print a fancy tree with build results.
@@ -562,7 +571,7 @@ fn runStepNames(
while (i > 0) {
i -= 1;
const step = b.top_level_steps.get(step_names[i]).?.step;
- const found = switch (run.summary orelse .failures) {
+ const found = switch (run.summary) {
.all, .none => unreachable,
.failures => step.state != .success,
.new => !step.result_cached,
@@ -579,7 +588,10 @@ fn runStepNames(
}
}
- if (failure_count == 0) return cleanExit();
+ if (failure_count == 0) {
+ if (b.graph.watch != null) return;
+ return cleanExit();
+ }
// Finally, render compile errors at the bottom of the terminal.
// We use a separate compile_error_steps array list because step_stack is destructively
@@ -591,13 +603,24 @@ fn runStepNames(
}
}
+ if (b.graph.watch != null) return uncleanExit();
+
// Signal to parent process that we have printed compile errors. The
// parent process may choose to omit the "following command failed"
// line in this case.
process.exit(2);
}
- process.exit(1);
+ return uncleanExit();
+}
+
+fn uncleanExit() error{UncleanExit}!void {
+ if (builtin.mode == .Debug) {
+ return error.UncleanExit;
+ } else {
+ std.debug.lockStdErr();
+ process.exit(1);
+ }
}
const PrintNode = struct {
@@ -768,7 +791,7 @@ fn printTreeStep(
step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
) !void {
const first = step_stack.swapRemove(s);
- const summary = run.summary orelse .failures;
+ const summary = run.summary;
const skip = switch (summary) {
.none => unreachable,
.all => false,
@@ -1124,6 +1147,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void {
\\ --maxrss <bytes> Limit memory usage (default is to use available memory)
\\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
\\ --fetch Exit after fetching dependency tree
+ \\ --watch Continuously rebuild when source files are modified
\\
\\Project-Specific Options:
\\
lib/std/Build/Step/InstallFile.zig
@@ -40,6 +40,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
_ = prog_node;
const b = step.owner;
const install_file: *InstallFile = @fieldParentPtr("step", step);
+ step.addWatchInput(install_file.source);
const full_src_path = install_file.source.getPath2(b, step);
const full_dest_path = b.getInstallPath(install_file.dir, install_file.dest_rel_path);
const cwd = std.fs.cwd();
lib/std/Build/Step.zig
@@ -562,6 +562,52 @@ pub fn writeManifest(s: *Step, man: *std.Build.Cache.Manifest) !void {
}
}
+fn oom(err: anytype) noreturn {
+ switch (err) {
+ error.OutOfMemory => @panic("out of memory"),
+ }
+}
+
+pub fn addWatchInput(step: *Step, lazy_path: std.Build.LazyPath) void {
+ errdefer |err| oom(err);
+ const w = step.owner.graph.watch orelse return;
+ switch (lazy_path) {
+ .src_path => |src_path| try addWatchInputFromBuilder(step, w, src_path.owner, src_path.sub_path),
+ .dependency => |d| try addWatchInputFromBuilder(step, w, d.dependency.builder, d.sub_path),
+ .cwd_relative => |path_string| {
+ try addWatchInputFromPath(w, .{
+ .root_dir = .{
+ .path = null,
+ .handle = std.fs.cwd(),
+ },
+ .sub_path = std.fs.path.dirname(path_string) orelse "",
+ }, .{
+ .step = step,
+ .basename = std.fs.path.basename(path_string),
+ });
+ },
+ // Nothing to watch because this dependency edge is modeled instead via `dependants`.
+ .generated => {},
+ }
+}
+
+fn addWatchInputFromBuilder(step: *Step, w: *std.Build.Watch, builder: *std.Build, sub_path: []const u8) !void {
+ return addWatchInputFromPath(w, .{
+ .root_dir = builder.build_root,
+ .sub_path = std.fs.path.dirname(sub_path) orelse "",
+ }, .{
+ .step = step,
+ .basename = std.fs.path.basename(sub_path),
+ });
+}
+
+fn addWatchInputFromPath(w: *std.Build.Watch, path: std.Build.Cache.Path, match: std.Build.Watch.Match) !void {
+ const gpa = match.step.owner.allocator;
+ const gop = try w.table.getOrPut(gpa, path);
+ if (!gop.found_existing) gop.value_ptr.* = .{};
+ try gop.value_ptr.put(gpa, match, {});
+}
+
test {
_ = CheckFile;
_ = CheckObject;
lib/std/Build.zig
@@ -120,6 +120,61 @@ pub const Graph = struct {
needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .{},
/// Information about the native target. Computed before build() is invoked.
host: ResolvedTarget,
+ /// When `--watch` is provided, collects the set of files that should be
+ /// watched and the state to required to poll the system for changes.
+ watch: ?*Watch,
+};
+
+pub const Watch = struct {
+ table: Table,
+
+ pub const init: Watch = .{
+ .table = .{},
+ };
+
+ /// Key is the directory to watch which contains one or more files we are
+ /// interested in noticing changes to.
+ pub const Table = std.ArrayHashMapUnmanaged(Cache.Path, ReactionSet, TableContext, false);
+
+ const Hash = std.hash.Wyhash;
+
+ pub const TableContext = struct {
+ pub fn hash(self: TableContext, a: Cache.Path) u32 {
+ _ = self;
+ const seed: u32 = @bitCast(a.root_dir.handle.fd);
+ return @truncate(Hash.hash(seed, a.sub_path));
+ }
+ pub fn eql(self: TableContext, a: Cache.Path, b: Cache.Path, b_index: usize) bool {
+ _ = self;
+ _ = b_index;
+ return a.eql(b);
+ }
+ };
+
+ pub const ReactionSet = std.ArrayHashMapUnmanaged(Match, void, Match.Context, false);
+
+ pub const Match = struct {
+ /// Relative to the watched directory, the file path that triggers this
+ /// match.
+ basename: []const u8,
+ /// The step to re-run when file corresponding to `basename` is changed.
+ step: *Step,
+
+ pub const Context = struct {
+ pub fn hash(self: Context, a: Match) u32 {
+ _ = self;
+ var hasher = Hash.init(0);
+ std.hash.autoHash(&hasher, a.step);
+ hasher.update(a.basename);
+ return @truncate(hasher.final());
+ }
+ pub fn eql(self: Context, a: Match, b: Match, b_index: usize) bool {
+ _ = self;
+ _ = b_index;
+ return a.step == b.step and mem.eql(u8, a.basename, b.basename);
+ }
+ };
+ };
};
const AvailableDeps = []const struct { []const u8, []const u8 };