authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-05 12:24:32-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-12 00:14:07-07:00
log6e025fc2e298c633ab36e9058a2cc610f57e4522
treedc58c61d22da365eaae0321de0af242f250b43c6
parentd2bec8f92f15ac16a0714ddc8282ab31dd5bb889

build system: add --watch flag and report source file in InstallFile

This direction is not quite right because it mutates shared state in a threaded context, so the next commit will need to fix this.

4 files changed, 147 insertions(+), 21 deletions(-)

lib/compiler/build_runner.zig+45-21
...@@ -74,6 +74,7 @@ pub fn main() !void {...@@ -74,6 +74,7 @@ pub fn main() !void {
74 .query = .{},74 .query = .{},
75 .result = try std.zig.system.resolveTargetQuery(.{}),75 .result = try std.zig.system.resolveTargetQuery(.{}),
76 },76 },
77 .watch = null,
77 };78 };
7879
79 graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });80 graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
...@@ -97,12 +98,12 @@ pub fn main() !void {...@@ -97,12 +98,12 @@ pub fn main() !void {
97 var dir_list = std.Build.DirList{};98 var dir_list = std.Build.DirList{};
98 var summary: ?Summary = null;99 var summary: ?Summary = null;
99 var max_rss: u64 = 0;100 var max_rss: u64 = 0;
100 var skip_oom_steps: bool = false;101 var skip_oom_steps = false;
101 var color: Color = .auto;102 var color: Color = .auto;
102 var seed: u32 = 0;103 var seed: u32 = 0;
103 var prominent_compile_errors: bool = false;104 var prominent_compile_errors = false;
104 var help_menu: bool = false;105 var help_menu = false;
105 var steps_menu: bool = false;106 var steps_menu = false;
106 var output_tmp_nonce: ?[16]u8 = null;107 var output_tmp_nonce: ?[16]u8 = null;
107108
108 while (nextArg(args, &arg_idx)) |arg| {109 while (nextArg(args, &arg_idx)) |arg| {
...@@ -227,6 +228,10 @@ pub fn main() !void {...@@ -227,6 +228,10 @@ pub fn main() !void {
227 builder.verbose_llvm_cpu_features = true;228 builder.verbose_llvm_cpu_features = true;
228 } else if (mem.eql(u8, arg, "--prominent-compile-errors")) {229 } else if (mem.eql(u8, arg, "--prominent-compile-errors")) {
229 prominent_compile_errors = true;230 prominent_compile_errors = true;
231 } else if (mem.eql(u8, arg, "--watch")) {
232 const watch = try arena.create(std.Build.Watch);
233 watch.* = std.Build.Watch.init;
234 graph.watch = watch;
230 } else if (mem.eql(u8, arg, "-fwine")) {235 } else if (mem.eql(u8, arg, "-fwine")) {
231 builder.enable_wine = true;236 builder.enable_wine = true;
232 } else if (mem.eql(u8, arg, "-fno-wine")) {237 } else if (mem.eql(u8, arg, "-fno-wine")) {
...@@ -344,7 +349,7 @@ pub fn main() !void {...@@ -344,7 +349,7 @@ pub fn main() !void {
344 .prominent_compile_errors = prominent_compile_errors,349 .prominent_compile_errors = prominent_compile_errors,
345350
346 .claimed_rss = 0,351 .claimed_rss = 0,
347 .summary = summary,352 .summary = summary orelse if (graph.watch != null) .new else .failures,
348 .ttyconf = ttyconf,353 .ttyconf = ttyconf,
349 .stderr = stderr,354 .stderr = stderr,
350 };355 };
...@@ -363,7 +368,10 @@ pub fn main() !void {...@@ -363,7 +368,10 @@ pub fn main() !void {
363 &run,368 &run,
364 seed,369 seed,
365 ) catch |err| switch (err) {370 ) catch |err| switch (err) {
366 error.UncleanExit => process.exit(1),371 error.UncleanExit => {
372 if (graph.watch == null)
373 process.exit(1);
374 },
367 else => return err,375 else => return err,
368 };376 };
369}377}
...@@ -377,7 +385,7 @@ const Run = struct {...@@ -377,7 +385,7 @@ const Run = struct {
377 prominent_compile_errors: bool,385 prominent_compile_errors: bool,
378386
379 claimed_rss: usize,387 claimed_rss: usize,
380 summary: ?Summary,388 summary: Summary,
381 ttyconf: std.io.tty.Config,389 ttyconf: std.io.tty.Config,
382 stderr: File,390 stderr: File,
383};391};
...@@ -417,7 +425,7 @@ fn runStepNames(...@@ -417,7 +425,7 @@ fn runStepNames(
417425
418 for (starting_steps) |s| {426 for (starting_steps) |s| {
419 constructGraphAndCheckForDependencyLoop(b, s, &step_stack, rand) catch |err| switch (err) {427 constructGraphAndCheckForDependencyLoop(b, s, &step_stack, rand) catch |err| switch (err) {
420 error.DependencyLoopDetected => return error.UncleanExit,428 error.DependencyLoopDetected => return uncleanExit(),
421 else => |e| return e,429 else => |e| return e,
422 };430 };
423 }431 }
...@@ -442,7 +450,7 @@ fn runStepNames(...@@ -442,7 +450,7 @@ fn runStepNames(
442 if (run.max_rss_is_default) {450 if (run.max_rss_is_default) {
443 std.debug.print("note: use --maxrss to override the default", .{});451 std.debug.print("note: use --maxrss to override the default", .{});
444 }452 }
445 return error.UncleanExit;453 return uncleanExit();
446 }454 }
447 }455 }
448456
...@@ -524,13 +532,19 @@ fn runStepNames(...@@ -524,13 +532,19 @@ fn runStepNames(
524532
525 // A proper command line application defaults to silently succeeding.533 // A proper command line application defaults to silently succeeding.
526 // The user may request verbose mode if they have a different preference.534 // The user may request verbose mode if they have a different preference.
527 const failures_only = run.summary != .all and run.summary != .new;535 const failures_only = switch (run.summary) {
528 if (failure_count == 0 and failures_only) return cleanExit();536 .failures, .none => true,
537 else => false,
538 };
539 if (failure_count == 0 and failures_only) {
540 if (b.graph.watch != null) return;
541 return cleanExit();
542 }
529543
530 const ttyconf = run.ttyconf;544 const ttyconf = run.ttyconf;
531 const stderr = run.stderr;545 const stderr = run.stderr;
532546
533 if (run.summary != Summary.none) {547 if (run.summary != .none) {
534 const total_count = success_count + failure_count + pending_count + skipped_count;548 const total_count = success_count + failure_count + pending_count + skipped_count;
535 ttyconf.setColor(stderr, .cyan) catch {};549 ttyconf.setColor(stderr, .cyan) catch {};
536 stderr.writeAll("Build Summary:") catch {};550 stderr.writeAll("Build Summary:") catch {};
...@@ -544,11 +558,6 @@ fn runStepNames(...@@ -544,11 +558,6 @@ fn runStepNames(
544 if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};558 if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};
545 if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};559 if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};
546560
547 if (run.summary == null) {
548 ttyconf.setColor(stderr, .dim) catch {};
549 stderr.writeAll(" (disable with --summary none)") catch {};
550 ttyconf.setColor(stderr, .reset) catch {};
551 }
552 stderr.writeAll("\n") catch {};561 stderr.writeAll("\n") catch {};
553562
554 // Print a fancy tree with build results.563 // Print a fancy tree with build results.
...@@ -562,7 +571,7 @@ fn runStepNames(...@@ -562,7 +571,7 @@ fn runStepNames(
562 while (i > 0) {571 while (i > 0) {
563 i -= 1;572 i -= 1;
564 const step = b.top_level_steps.get(step_names[i]).?.step;573 const step = b.top_level_steps.get(step_names[i]).?.step;
565 const found = switch (run.summary orelse .failures) {574 const found = switch (run.summary) {
566 .all, .none => unreachable,575 .all, .none => unreachable,
567 .failures => step.state != .success,576 .failures => step.state != .success,
568 .new => !step.result_cached,577 .new => !step.result_cached,
...@@ -579,7 +588,10 @@ fn runStepNames(...@@ -579,7 +588,10 @@ fn runStepNames(
579 }588 }
580 }589 }
581590
582 if (failure_count == 0) return cleanExit();591 if (failure_count == 0) {
592 if (b.graph.watch != null) return;
593 return cleanExit();
594 }
583595
584 // Finally, render compile errors at the bottom of the terminal.596 // Finally, render compile errors at the bottom of the terminal.
585 // We use a separate compile_error_steps array list because step_stack is destructively597 // We use a separate compile_error_steps array list because step_stack is destructively
...@@ -591,13 +603,24 @@ fn runStepNames(...@@ -591,13 +603,24 @@ fn runStepNames(
591 }603 }
592 }604 }
593605
606 if (b.graph.watch != null) return uncleanExit();
607
594 // Signal to parent process that we have printed compile errors. The608 // Signal to parent process that we have printed compile errors. The
595 // parent process may choose to omit the "following command failed"609 // parent process may choose to omit the "following command failed"
596 // line in this case.610 // line in this case.
597 process.exit(2);611 process.exit(2);
598 }612 }
599613
600 process.exit(1);614 return uncleanExit();
615}
616
617fn uncleanExit() error{UncleanExit}!void {
618 if (builtin.mode == .Debug) {
619 return error.UncleanExit;
620 } else {
621 std.debug.lockStdErr();
622 process.exit(1);
623 }
601}624}
602625
603const PrintNode = struct {626const PrintNode = struct {
...@@ -768,7 +791,7 @@ fn printTreeStep(...@@ -768,7 +791,7 @@ fn printTreeStep(
768 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),791 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
769) !void {792) !void {
770 const first = step_stack.swapRemove(s);793 const first = step_stack.swapRemove(s);
771 const summary = run.summary orelse .failures;794 const summary = run.summary;
772 const skip = switch (summary) {795 const skip = switch (summary) {
773 .none => unreachable,796 .none => unreachable,
774 .all => false,797 .all => false,
...@@ -1124,6 +1147,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void {...@@ -1124,6 +1147,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void {
1124 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)1147 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
1125 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss1148 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
1126 \\ --fetch Exit after fetching dependency tree1149 \\ --fetch Exit after fetching dependency tree
1150 \\ --watch Continuously rebuild when source files are modified
1127 \\1151 \\
1128 \\Project-Specific Options:1152 \\Project-Specific Options:
1129 \\1153 \\
lib/std/Build.zig+55
...@@ -120,6 +120,61 @@ pub const Graph = struct {...@@ -120,6 +120,61 @@ pub const Graph = struct {
120 needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .{},120 needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .{},
121 /// Information about the native target. Computed before build() is invoked.121 /// Information about the native target. Computed before build() is invoked.
122 host: ResolvedTarget,122 host: ResolvedTarget,
123 /// When `--watch` is provided, collects the set of files that should be
124 /// watched and the state to required to poll the system for changes.
125 watch: ?*Watch,
126};
127
128pub const Watch = struct {
129 table: Table,
130
131 pub const init: Watch = .{
132 .table = .{},
133 };
134
135 /// Key is the directory to watch which contains one or more files we are
136 /// interested in noticing changes to.
137 pub const Table = std.ArrayHashMapUnmanaged(Cache.Path, ReactionSet, TableContext, false);
138
139 const Hash = std.hash.Wyhash;
140
141 pub const TableContext = struct {
142 pub fn hash(self: TableContext, a: Cache.Path) u32 {
143 _ = self;
144 const seed: u32 = @bitCast(a.root_dir.handle.fd);
145 return @truncate(Hash.hash(seed, a.sub_path));
146 }
147 pub fn eql(self: TableContext, a: Cache.Path, b: Cache.Path, b_index: usize) bool {
148 _ = self;
149 _ = b_index;
150 return a.eql(b);
151 }
152 };
153
154 pub const ReactionSet = std.ArrayHashMapUnmanaged(Match, void, Match.Context, false);
155
156 pub const Match = struct {
157 /// Relative to the watched directory, the file path that triggers this
158 /// match.
159 basename: []const u8,
160 /// The step to re-run when file corresponding to `basename` is changed.
161 step: *Step,
162
163 pub const Context = struct {
164 pub fn hash(self: Context, a: Match) u32 {
165 _ = self;
166 var hasher = Hash.init(0);
167 std.hash.autoHash(&hasher, a.step);
168 hasher.update(a.basename);
169 return @truncate(hasher.final());
170 }
171 pub fn eql(self: Context, a: Match, b: Match, b_index: usize) bool {
172 _ = self;
173 _ = b_index;
174 return a.step == b.step and mem.eql(u8, a.basename, b.basename);
175 }
176 };
177 };
123};178};
124179
125const AvailableDeps = []const struct { []const u8, []const u8 };180const AvailableDeps = []const struct { []const u8, []const u8 };
lib/std/Build/Step.zig+46
...@@ -562,6 +562,52 @@ pub fn writeManifest(s: *Step, man: *std.Build.Cache.Manifest) !void {...@@ -562,6 +562,52 @@ pub fn writeManifest(s: *Step, man: *std.Build.Cache.Manifest) !void {
562 }562 }
563}563}
564564
565fn oom(err: anytype) noreturn {
566 switch (err) {
567 error.OutOfMemory => @panic("out of memory"),
568 }
569}
570
571pub fn addWatchInput(step: *Step, lazy_path: std.Build.LazyPath) void {
572 errdefer |err| oom(err);
573 const w = step.owner.graph.watch orelse return;
574 switch (lazy_path) {
575 .src_path => |src_path| try addWatchInputFromBuilder(step, w, src_path.owner, src_path.sub_path),
576 .dependency => |d| try addWatchInputFromBuilder(step, w, d.dependency.builder, d.sub_path),
577 .cwd_relative => |path_string| {
578 try addWatchInputFromPath(w, .{
579 .root_dir = .{
580 .path = null,
581 .handle = std.fs.cwd(),
582 },
583 .sub_path = std.fs.path.dirname(path_string) orelse "",
584 }, .{
585 .step = step,
586 .basename = std.fs.path.basename(path_string),
587 });
588 },
589 // Nothing to watch because this dependency edge is modeled instead via `dependants`.
590 .generated => {},
591 }
592}
593
594fn addWatchInputFromBuilder(step: *Step, w: *std.Build.Watch, builder: *std.Build, sub_path: []const u8) !void {
595 return addWatchInputFromPath(w, .{
596 .root_dir = builder.build_root,
597 .sub_path = std.fs.path.dirname(sub_path) orelse "",
598 }, .{
599 .step = step,
600 .basename = std.fs.path.basename(sub_path),
601 });
602}
603
604fn addWatchInputFromPath(w: *std.Build.Watch, path: std.Build.Cache.Path, match: std.Build.Watch.Match) !void {
605 const gpa = match.step.owner.allocator;
606 const gop = try w.table.getOrPut(gpa, path);
607 if (!gop.found_existing) gop.value_ptr.* = .{};
608 try gop.value_ptr.put(gpa, match, {});
609}
610
565test {611test {
566 _ = CheckFile;612 _ = CheckFile;
567 _ = CheckObject;613 _ = CheckObject;
lib/std/Build/Step/InstallFile.zig+1
...@@ -40,6 +40,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -40,6 +40,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
40 _ = prog_node;40 _ = prog_node;
41 const b = step.owner;41 const b = step.owner;
42 const install_file: *InstallFile = @fieldParentPtr("step", step);42 const install_file: *InstallFile = @fieldParentPtr("step", step);
43 step.addWatchInput(install_file.source);
43 const full_src_path = install_file.source.getPath2(b, step);44 const full_src_path = install_file.source.getPath2(b, step);
44 const full_dest_path = b.getInstallPath(install_file.dir, install_file.dest_rel_path);45 const full_dest_path = b.getInstallPath(install_file.dir, install_file.dest_rel_path);
45 const cwd = std.fs.cwd();46 const cwd = std.fs.cwd();