authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-14 08:39:05+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-14 08:39:05+02:00
log4e5b5356094a63a13955c757cb2713f120fa920e
tree80e4b93f3f7159780bba5bbe9fd13b0544ebc46c
parent613c03321a0970cce3a5d04ede04ab4a24ac1dbb
parentbc1f280a77ce118300d3f486932c5768911b0e6f

Merge pull request 'Maker: detect modifications to configurer and recompile it' (#36485) from reconfigure into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/36485

6 files changed, 259 insertions(+), 113 deletions(-)

lib/compiler/Maker.zig+113-45
...@@ -44,6 +44,10 @@ gpa: Allocator,...@@ -44,6 +44,10 @@ gpa: Allocator,
44graph: *Graph,44graph: *Graph,
45install_paths: InstallPaths,45install_paths: InstallPaths,
46scanned_config: *const ScannedConfig,46scanned_config: *const ScannedConfig,
47/// Includes an extra auto-generated placeholder Step at the end that indicates
48/// configure must be rerun. It is done this way so that the hot path of file
49/// system watching does not need to make any special cases, and to avoid more
50/// OS-specific logic in file system watching implementation.
47steps: []Step,51steps: []Step,
48generated_files: []Path,52generated_files: []Path,
49run_args: ?[]const []const u8,53run_args: ?[]const []const u8,
...@@ -221,7 +225,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -221,7 +225,7 @@ pub fn main(init: process.Init.Minimal) !void {
221 var skip_oom_steps = false;225 var skip_oom_steps = false;
222 var test_timeout_ns: ?u64 = null;226 var test_timeout_ns: ?u64 = null;
223 var color: Color = .settingFromEnvironment(&graph.environ_map);227 var color: Color = .settingFromEnvironment(&graph.environ_map);
224 var watch = false;228 var watch_flag = false;
225 var fuzz: ?Fuzz.Mode = null;229 var fuzz: ?Fuzz.Mode = null;
226 var debounce_interval_ms: u16 = 50;230 var debounce_interval_ms: u16 = 50;
227 var listen: bool = false;231 var listen: bool = false;
...@@ -470,7 +474,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -470,7 +474,7 @@ pub fn main(init: process.Init.Minimal) !void {
470 } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {474 } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
471 graph.verbose_llvm_ir = true;475 graph.verbose_llvm_ir = true;
472 } else if (mem.eql(u8, arg, "--watch")) {476 } else if (mem.eql(u8, arg, "--watch")) {
473 watch = true;477 watch_flag = true;
474 } else if (mem.eql(u8, arg, "--time-report")) {478 } else if (mem.eql(u8, arg, "--time-report")) {
475 graph.time_report = true;479 graph.time_report = true;
476 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };480 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
...@@ -570,7 +574,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -570,7 +574,7 @@ pub fn main(init: process.Init.Minimal) !void {
570 }574 }
571575
572 const early_exit_mode = fetch_only or help_menu or steps_menu or print_configuration != .none;576 const early_exit_mode = fetch_only or help_menu or steps_menu or print_configuration != .none;
573 const server_mode = !early_exit_mode and (watch or webui_listen != null or fuzz != null or listen);577 const server_mode = !early_exit_mode and (watch_flag or webui_listen != null or fuzz != null or listen);
574578
575 process.raiseFileDescriptorLimit();579 process.raiseFileDescriptorLimit();
576580
...@@ -697,7 +701,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -697,7 +701,7 @@ pub fn main(init: process.Init.Minimal) !void {
697 var protocol_server_allocation: AvoidableServer = undefined;701 var protocol_server_allocation: AvoidableServer = undefined;
698 const protocol_server: ?*AvoidableServer = if (listen) s: {702 const protocol_server: ?*AvoidableServer = if (listen) s: {
699 if (builtin.single_threaded) fatal("--listen is not yet supported on single-threaded hosts", .{});703 if (builtin.single_threaded) fatal("--listen is not yet supported on single-threaded hosts", .{});
700 if (watch) fatal("using '--watch' and '--listen' together is not supported", .{});704 if (watch_flag) fatal("using '--watch' and '--listen' together is not supported", .{});
701 if (fuzz != null) fatal("using '--fuzz' and '--listen' together is not supported", .{});705 if (fuzz != null) fatal("using '--fuzz' and '--listen' together is not supported", .{});
702 if (step_names.items.len > 0) fatal("build steps must be provided over the protocol instead of using CLI arguments", .{});706 if (step_names.items.len > 0) fatal("build steps must be provided over the protocol instead of using CLI arguments", .{});
703 protocol_server_allocation = .{707 protocol_server_allocation = .{
...@@ -708,7 +712,12 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -708,7 +712,12 @@ pub fn main(init: process.Init.Minimal) !void {
708 break :s &protocol_server_allocation;712 break :s &protocol_server_allocation;
709 } else null;713 } else null;
710714
711 while (true) {715 configure: while (true) {
716 // Set of files that, if modified, imply that recompiling and rerunning
717 // configurer is needed.
718 var configure_source_files: Cache.Manifest.Files = .empty;
719 defer Cache.Manifest.freeFiles(gpa, &configure_source_files);
720
712 // If this fails, we can still start the server and wait for user721 // If this fails, we can still start the server and wait for user
713 // to request a rebuild. If it returns error.FailedButCacheIntact722 // to request a rebuild. If it returns error.FailedButCacheIntact
714 // we can even still do file system watching and automatically723 // we can even still do file system watching and automatically
...@@ -730,6 +739,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -730,6 +739,7 @@ pub fn main(init: process.Init.Minimal) !void {
730 .fetch_only = fetch_only,739 .fetch_only = fetch_only,
731 .print_configuration = print_configuration,740 .print_configuration = print_configuration,
732 .forks = forks.items,741 .forks = forks.items,
742 .src_files = &configure_source_files,
733 })) |scanned_config| {743 })) |scanned_config| {
734 if (help_menu) {744 if (help_menu) {
735 scanned_config.printUsage(&graph, initStdoutWriter(io)) catch |err| switch (err) {745 scanned_config.printUsage(&graph, initStdoutWriter(io)) catch |err| switch (err) {
...@@ -766,7 +776,9 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -766,7 +776,9 @@ pub fn main(init: process.Init.Minimal) !void {
766 .include = install_include_path,776 .include = install_include_path,
767 },777 },
768778
769 .steps = try arena.alloc(Step, scanned_config.configuration.steps.len),779 // Extra step at the end which is the autogenerated placeholder
780 // step which indicates that we need to reconfigure.
781 .steps = try arena.alloc(Step, scanned_config.configuration.steps.len + 1),
770 .generated_files = try arena.alloc(Path, scanned_config.configuration.generated_files_len),782 .generated_files = try arena.alloc(Path, scanned_config.configuration.generated_files_len),
771 .run_args = run_args,783 .run_args = run_args,
772784
...@@ -776,7 +788,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -776,7 +788,7 @@ pub fn main(init: process.Init.Minimal) !void {
776 .skip_oom_steps = skip_oom_steps,788 .skip_oom_steps = skip_oom_steps,
777 .unit_test_timeout_ns = test_timeout_ns,789 .unit_test_timeout_ns = test_timeout_ns,
778790
779 .watch = watch,791 .watch = watch_flag,
780 .web_server = web_server,792 .web_server = web_server,
781 .protocol_server = protocol_server,793 .protocol_server = protocol_server,
782 .protocol_server_mutex = .init,794 .protocol_server_mutex = .init,
...@@ -789,7 +801,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -789,7 +801,7 @@ pub fn main(init: process.Init.Minimal) !void {
789 .multiline_errors = multiline_errors,801 .multiline_errors = multiline_errors,
790 .summary = summary orelse if (listen)802 .summary = summary orelse if (listen)
791 .none803 .none
792 else if (watch or webui_listen != null)804 else if (watch_flag or webui_listen != null)
793 .new805 .new
794 else806 else
795 .failures,807 .failures,
...@@ -808,7 +820,8 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -808,7 +820,8 @@ pub fn main(init: process.Init.Minimal) !void {
808 if (protocol_server) |s| {820 if (protocol_server) |s| {
809 try s.serveStringMessage(.bsp_configuration, try arena.print("{f}", .{scanned_config.path}));821 try s.serveStringMessage(.bsp_configuration, try arena.print("{f}", .{scanned_config.path}));
810822
811 var w: ?Watch = null;823 var watch: ?Watch = null;
824 defer if (watch) |*w| w.deinit();
812825
813 const Event = union(enum) {826 const Event = union(enum) {
814 message: Reader.Error!Client.Message.Header,827 message: Reader.Error!Client.Message.Header,
...@@ -843,7 +856,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -843,7 +856,7 @@ pub fn main(init: process.Init.Minimal) !void {
843 try select.concurrent(.message, Server.receiveMessage, .{s});856 try select.concurrent(.message, Server.receiveMessage, .{s});
844857
845 maker.watch = body.flags.watch;858 maker.watch = body.flags.watch;
846 maker.prepare(steps) catch |err| switch (err) {859 maker.prepare(steps, &configure_source_files) catch |err| switch (err) {
847 error.DependencyLoopDetected, error.InsufficientMemory => {860 error.DependencyLoopDetected, error.InsufficientMemory => {
848 // TODO handle DependencyLoopDetected as error.FailedButCacheIntact861 // TODO handle DependencyLoopDetected as error.FailedButCacheIntact
849 // and handle InsufficientMemory as error.AlreadyReported862 // and handle InsufficientMemory as error.AlreadyReported
...@@ -857,10 +870,13 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -857,10 +870,13 @@ pub fn main(init: process.Init.Minimal) !void {
857870
858 if (body.flags.watch) {871 if (body.flags.watch) {
859 if (!Watch.have_impl) unreachable;872 if (!Watch.have_impl) unreachable;
860 if (w == null) w = try .init(&maker);873 if (watch == null) watch = try .init(&maker);
861874
862 try w.?.update(maker.step_stack.keys());875 try updateWatch(&maker, &watch.?);
863 try select.concurrent(.fs_event, Watch.wait, .{ &w.?, if (in_debounce) .{ .ms = debounce_interval_ms } else .none });876 try select.concurrent(.fs_event, Watch.wait, .{
877 &watch.?,
878 if (in_debounce) .{ .ms = debounce_interval_ms } else .none,
879 });
864 }880 }
865881
866 continue :loop try select.await();882 continue :loop try select.await();
...@@ -870,7 +886,13 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -870,7 +886,13 @@ pub fn main(init: process.Init.Minimal) !void {
870 },886 },
871 .fs_event => |payload| {887 .fs_event => |payload| {
872 if (!Watch.have_impl) unreachable;888 if (!Watch.have_impl) unreachable;
873 switch (try payload) {889 switch (payload catch |err| switch (err) {
890 error.MustReconfigure => {
891 try io.sleep(.fromMilliseconds(debounce_interval_ms), .awake);
892 continue :configure;
893 },
894 else => |e| fatal("file watching failed: {t}", .{e}),
895 }) {
874 .timeout => {896 .timeout => {
875 assert(in_debounce);897 assert(in_debounce);
876 markFailedStepsDirty(&maker);898 markFailedStepsDirty(&maker);
...@@ -880,7 +902,10 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -880,7 +902,10 @@ pub fn main(init: process.Init.Minimal) !void {
880 .dirty => in_debounce = true,902 .dirty => in_debounce = true,
881 .clean => {},903 .clean => {},
882 }904 }
883 try select.concurrent(.fs_event, Watch.wait, .{ &w.?, if (in_debounce) .{ .ms = debounce_interval_ms } else .none });905 try select.concurrent(.fs_event, Watch.wait, .{
906 &watch.?,
907 if (in_debounce) .{ .ms = debounce_interval_ms } else .none,
908 });
884 continue :loop try select.await();909 continue :loop try select.await();
885 },910 },
886 }911 }
...@@ -889,7 +914,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -889,7 +914,7 @@ pub fn main(init: process.Init.Minimal) !void {
889 const initial_steps = try maker.resolveTopLevelSteps(step_names.items);914 const initial_steps = try maker.resolveTopLevelSteps(step_names.items);
890 defer gpa.free(initial_steps);915 defer gpa.free(initial_steps);
891916
892 maker.prepare(initial_steps) catch |err| switch (err) {917 maker.prepare(initial_steps, &configure_source_files) catch |err| switch (err) {
893 error.DependencyLoopDetected, error.InsufficientMemory => {918 error.DependencyLoopDetected, error.InsufficientMemory => {
894 // TODO handle DependencyLoopDetected as error.FailedButCacheIntact919 // TODO handle DependencyLoopDetected as error.FailedButCacheIntact
895 // and handle InsufficientMemory as error.AlreadyReported920 // and handle InsufficientMemory as error.AlreadyReported
...@@ -900,10 +925,11 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -900,10 +925,11 @@ pub fn main(init: process.Init.Minimal) !void {
900 };925 };
901926
902 var w: Watch = w: {927 var w: Watch = w: {
903 if (!watch) break :w undefined;928 if (!watch_flag) break :w undefined;
904 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{native_os});929 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{native_os});
905 break :w try .init(&maker);930 break :w try .init(&maker);
906 };931 };
932 defer w.deinit();
907933
908 if (web_server) |ws| try ws.updateConfiguration(&maker);934 if (web_server) |ws| try ws.updateConfiguration(&maker);
909935
...@@ -918,7 +944,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -918,7 +944,7 @@ pub fn main(init: process.Init.Minimal) !void {
918944
919 if (web_server) |ws| {945 if (web_server) |ws| {
920 const c = &scanned_config.configuration;946 const c = &scanned_config.configuration;
921 assert(!watch); // fatal error after CLI parsing947 assert(!watch_flag); // fatal error after CLI parsing
922 while (true) switch (try ws.wait()) {948 while (true) switch (try ws.wait()) {
923 .rebuild => {949 .rebuild => {
924 for (maker.step_stack.keys()) |step_index| {950 for (maker.step_stack.keys()) |step_index| {
...@@ -938,7 +964,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -938,7 +964,7 @@ pub fn main(init: process.Init.Minimal) !void {
938 // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.964 // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.
939 if (!Watch.have_impl) unreachable;965 if (!Watch.have_impl) unreachable;
940966
941 try w.update(maker.step_stack.keys());967 try updateWatch(&maker, &w);
942968
943 // Wait until a file system notification arrives. Read all such events969 // Wait until a file system notification arrives. Read all such events
944 // until the buffer is empty. Then wait for a debounce interval, resetting970 // until the buffer is empty. Then wait for a debounce interval, resetting
...@@ -950,21 +976,34 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -950,21 +976,34 @@ pub fn main(init: process.Init.Minimal) !void {
950 w.dir_count, countSubProcesses(&maker),976 w.dir_count, countSubProcesses(&maker),
951 }) catch &caption_buf;977 }) catch &caption_buf;
952 var debouncing_node = main_progress_node.start(caption, 0);978 var debouncing_node = main_progress_node.start(caption, 0);
979 defer debouncing_node.end();
953 var in_debounce = false;980 var in_debounce = false;
954 while (true) switch (try w.wait(if (in_debounce) .{ .ms = debounce_interval_ms } else .none)) {981 while (true) {
955 .timeout => {982 const timeout: Watch.Timeout = if (in_debounce) .{ .ms = debounce_interval_ms } else .none;
956 assert(in_debounce);983 switch (w.wait(timeout) catch |err| switch (err) {
957 debouncing_node.end();984 error.MustReconfigure => {
958 markFailedStepsDirty(&maker);985 debouncing_node.end();
959 continue :rebuild;986 debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0);
960 },987 try io.sleep(.fromMilliseconds(debounce_interval_ms), .awake);
961 .dirty => if (!in_debounce) {988 continue :configure;
962 in_debounce = true;989 },
963 debouncing_node.end();990 else => |e| fatal("file watching failed: {t}", .{e}),
964 debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0);991 }) {
965 },992 .timeout => {
966 .clean => {},993 assert(in_debounce);
967 };994 debouncing_node.end();
995 debouncing_node = .none;
996 markFailedStepsDirty(&maker);
997 continue :rebuild;
998 },
999 .dirty => if (!in_debounce) {
1000 in_debounce = true;
1001 debouncing_node.end();
1002 debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0);
1003 },
1004 .clean => {},
1005 }
1006 }
968 }1007 }
969 } else |err| {1008 } else |err| {
970 const can_fs_watch = switch (err) {1009 const can_fs_watch = switch (err) {
...@@ -982,7 +1021,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -982,7 +1021,7 @@ pub fn main(init: process.Init.Minimal) !void {
982 if (protocol_server != null) {1021 if (protocol_server != null) {
983 fatal("(zig build system) TODO send error messages to client when build.zig compilation fails", .{});1022 fatal("(zig build system) TODO send error messages to client when build.zig compilation fails", .{});
984 }1023 }
985 if (watch and can_fs_watch) {1024 if (watch_flag and can_fs_watch) {
986 fatal("(zig build system) TODO set up fs watching even when build.zig compilation fails", .{});1025 fatal("(zig build system) TODO set up fs watching even when build.zig compilation fails", .{});
987 } else {1026 } else {
988 fatal("(zig build system) TODO stay running and wait for user to request rebuild even when build.zig compilation fails", .{});1027 fatal("(zig build system) TODO stay running and wait for user to request rebuild even when build.zig compilation fails", .{});
...@@ -991,6 +1030,15 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -991,6 +1030,15 @@ pub fn main(init: process.Init.Minimal) !void {
991 }1030 }
992}1031}
9931032
1033/// Temporarily adds the reconfigure pseudostep to step_stack, calls
1034/// `Watch.update`, and then pops it again.
1035fn updateWatch(maker: *Maker, watch: *Watch) !void {
1036 const step_stack = &maker.step_stack;
1037 try step_stack.putNoClobber(maker.gpa, @fromBackingInt(@intCast(maker.steps.len - 1)), {});
1038 defer _ = step_stack.pop().?;
1039 try watch.update(step_stack.keys());
1040}
1041
994const ConfigureOptions = struct {1042const ConfigureOptions = struct {
995 configure_argv: [][]const u8,1043 configure_argv: [][]const u8,
996 conf_argv_index_build_root: usize,1044 conf_argv_index_build_root: usize,
...@@ -1008,6 +1056,7 @@ const ConfigureOptions = struct {...@@ -1008,6 +1056,7 @@ const ConfigureOptions = struct {
1008 fetch_only: bool,1056 fetch_only: bool,
1009 print_configuration: PrintConfiguration,1057 print_configuration: PrintConfiguration,
1010 forks: []Fork,1058 forks: []Fork,
1059 src_files: *Cache.Manifest.Files,
1011};1060};
10121061
1013fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {1062fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
...@@ -1362,14 +1411,14 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {...@@ -1362,14 +1411,14 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
13621411
1363 if (config_man) |man| {1412 if (config_man) |man| {
1364 if (try man.hit(compile_prog_node)) {1413 if (try man.hit(compile_prog_node)) {
1414 log.debug("configuration cache hit", .{});
1365 const digest = man.final();1415 const digest = man.final();
1366 break :cp .{1416 const path: Path = .{
1367 .{1417 .root_dir = graph.local_cache_root,
1368 .root_dir = graph.local_cache_root,1418 .sub_path = try arena.print("c/{s}", .{&digest}),
1369 .sub_path = try arena.print("c/{s}", .{&digest}),
1370 },
1371 man.toOwnedLock(),
1372 };1419 };
1420 options.src_files.* = man.takeFiles();
1421 break :cp .{ path, man.toOwnedLock() };
1373 }1422 }
1374 }1423 }
1375 const configure_exe_path: Path = if (std.zig.buildExeSubprocess(gpa, io, .{1424 const configure_exe_path: Path = if (std.zig.buildExeSubprocess(gpa, io, .{
...@@ -1505,6 +1554,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {...@@ -1505,6 +1554,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
1505 });1554 });
1506 };1555 };
1507 man.writeManifest() catch |err| log.warn("failed to write cache manifest: {t}", .{err});1556 man.writeManifest() catch |err| log.warn("failed to write cache manifest: {t}", .{err});
1557 options.src_files.* = man.takeFiles();
1508 break :cp .{ final_path, man.toOwnedLock() };1558 break :cp .{ final_path, man.toOwnedLock() };
1509 }1559 }
1510 };1560 };
...@@ -2119,7 +2169,13 @@ fn markFailedStepsDirty(maker: *Maker) void {...@@ -2119,7 +2169,13 @@ fn markFailedStepsDirty(maker: *Maker) void {
2119 for (all_steps) |step_index| {2169 for (all_steps) |step_index| {
2120 const step = maker.stepByIndex(step_index);2170 const step = maker.stepByIndex(step_index);
2121 switch (step.state) {2171 switch (step.state) {
2122 .dependency_failure, .dependency_skipped, .failure, .skipped => _ = maker.invalidateResult(step),2172 .dependency_failure,
2173 .dependency_skipped,
2174 .failure,
2175 .skipped,
2176 => _ = maker.invalidateResult(step) catch |err| switch (err) {
2177 error.MustReconfigure => unreachable,
2178 },
2123 else => continue,2179 else => continue,
2124 }2180 }
2125 }2181 }
...@@ -2173,7 +2229,11 @@ fn resolveTopLevelSteps(maker: *Maker, step_names: []const []const u8) ![]const...@@ -2173,7 +2229,11 @@ fn resolveTopLevelSteps(maker: *Maker, step_names: []const []const u8) ![]const
2173 return try gpa.dupe(Configuration.Step.Index, result.keys());2229 return try gpa.dupe(Configuration.Step.Index, result.keys());
2174}2230}
21752231
2176fn prepare(maker: *Maker, step_indices: []const Configuration.Step.Index) !void {2232fn prepare(
2233 maker: *Maker,
2234 step_indices: []const Configuration.Step.Index,
2235 configure_source_files: *const Cache.Manifest.Files,
2236) !void {
2177 const gpa = maker.gpa;2237 const gpa = maker.gpa;
2178 const graph = maker.graph;2238 const graph = maker.graph;
2179 const arena = graph.arena;2239 const arena = graph.arena;
...@@ -2182,10 +2242,17 @@ fn prepare(maker: *Maker, step_indices: []const Configuration.Step.Index) !void...@@ -2182,10 +2242,17 @@ fn prepare(maker: *Maker, step_indices: []const Configuration.Step.Index) !void
2182 const step_stack = &maker.step_stack;2242 const step_stack = &maker.step_stack;
2183 const c = &maker.scanned_config.configuration;2243 const c = &maker.scanned_config.configuration;
21842244
2185 for (maker.steps, 0..) |*step, step_index_usize| {2245 // The last element is a reserved special pseudostep which contains the
2246 // watch inputs for the configurer executable.
2247 for (maker.steps[0 .. maker.steps.len - 1], 0..) |*step, step_index_usize| {
2186 const step_index: Configuration.Step.Index = @fromBackingInt(@intCast(step_index_usize));2248 const step_index: Configuration.Step.Index = @fromBackingInt(@intCast(step_index_usize));
2187 step.* = .{ .extended = .init(step_index.ptr(c).flags(c).tag) };2249 step.* = .{ .extended = .init(step_index.ptr(c).flags(c).tag) };
2188 }2250 }
2251 {
2252 const last_step = &maker.steps[maker.steps.len - 1];
2253 last_step.* = .{ .extended = .init(.top_level) };
2254 try last_step.setWatchInputsFromManifestFiles(maker, configure_source_files, graph.cache.prefixes());
2255 }
21892256
2190 try initial_steps.ensureUnusedCapacity(gpa, step_indices.len);2257 try initial_steps.ensureUnusedCapacity(gpa, step_indices.len);
2191 try step_stack.ensureUnusedCapacity(gpa, step_indices.len);2258 try step_stack.ensureUnusedCapacity(gpa, step_indices.len);
...@@ -3045,14 +3112,15 @@ fn constructGraphAndCheckForDependencyLoop(...@@ -3045,14 +3112,15 @@ fn constructGraphAndCheckForDependencyLoop(
3045/// When file watching, prepares the step for being re-evaluated. Returns3112/// When file watching, prepares the step for being re-evaluated. Returns
3046/// `true` if the step was newly invalidated, `false` if it was already3113/// `true` if the step was newly invalidated, `false` if it was already
3047/// invalidated.3114/// invalidated.
3048pub fn invalidateResult(maker: *Maker, step: *Step) bool {3115pub fn invalidateResult(maker: *Maker, step: *Step) error{MustReconfigure}!bool {
3116 if (step == &maker.steps[maker.steps.len - 1]) return error.MustReconfigure;
3049 if (step.state == .precheck_done) return false;3117 if (step.state == .precheck_done) return false;
3050 assert(step.pending_deps == 0);3118 assert(step.pending_deps == 0);
3051 step.state = .precheck_done;3119 step.state = .precheck_done;
3052 step.reset(maker);3120 step.reset(maker);
3053 for (step.dependants.items) |dependant_index| {3121 for (step.dependants.items) |dependant_index| {
3054 const dependant = maker.stepByIndex(dependant_index);3122 const dependant = maker.stepByIndex(dependant_index);
3055 _ = invalidateResult(maker, dependant);3123 _ = try invalidateResult(maker, dependant);
3056 dependant.pending_deps += 1;3124 dependant.pending_deps += 1;
3057 }3125 }
3058 return true;3126 return true;
lib/compiler/Maker/Step.zig+11-3
...@@ -781,12 +781,20 @@ pub fn writeManifestAndWatch(s: *Step, maker: *Maker, man: *Cache.Manifest) !voi...@@ -781,12 +781,20 @@ pub fn writeManifestAndWatch(s: *Step, maker: *Maker, man: *Cache.Manifest) !voi
781 try setWatchInputsFromManifest(s, maker, man);781 try setWatchInputsFromManifest(s, maker, man);
782}782}
783783
784fn setWatchInputsFromManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {784pub fn setWatchInputsFromManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {
785 return setWatchInputsFromManifestFiles(s, maker, &man.files, man.cache.prefixes());
786}
787
788pub fn setWatchInputsFromManifestFiles(
789 s: *Step,
790 maker: *Maker,
791 files: *const Cache.Manifest.Files,
792 prefixes: []const Cache.Directory,
793) !void {
785 const graph = maker.graph;794 const graph = maker.graph;
786 const arena = graph.arena; // TODO don't leak into process arena795 const arena = graph.arena; // TODO don't leak into process arena
787 const prefixes = man.cache.prefixes();
788 clearWatchInputs(s, maker);796 clearWatchInputs(s, maker);
789 for (man.files.keys()) |file| {797 for (files.keys()) |file| {
790 // The file path data is freed when the cache manifest is cleaned up at the end of `make`.798 // The file path data is freed when the cache manifest is cleaned up at the end of `make`.
791 const sub_path = try arena.dupe(u8, file.prefixed_path.sub_path);799 const sub_path = try arena.dupe(u8, file.prefixed_path.sub_path);
792 try addWatchInputFromPath(s, maker, .{800 try addWatchInputFromPath(s, maker, .{
lib/compiler/Maker/Watch.zig+84-46
...@@ -49,7 +49,10 @@ const Os = switch (builtin.os.tag) {...@@ -49,7 +49,10 @@ const Os = switch (builtin.os.tag) {
49 poll_fds: std.array_hash_map.Auto(MountId, posix.pollfd),49 poll_fds: std.array_hash_map.Auto(MountId, posix.pollfd),
5050
51 const MountId = i32;51 const MountId = i32;
52 const HandleTable = std.array_hash_map.Custom(FileHandle, struct { mount_id: MountId, reaction_set: ReactionSet }, FileHandle.Adapter, false);52 const HandleTable = std.array_hash_map.Custom(FileHandle, struct {
53 mount_id: MountId,
54 reaction_set: ReactionSet,
55 }, FileHandle.Adapter, false);
5356
54 const fan_mask: std.os.linux.fanotify.MarkMask = .{57 const fan_mask: std.os.linux.fanotify.MarkMask = .{
55 .CLOSE_WRITE = true,58 .CLOSE_WRITE = true,
...@@ -81,7 +84,7 @@ const Os = switch (builtin.os.tag) {...@@ -81,7 +84,7 @@ const Os = switch (builtin.os.tag) {
81 }84 }
8285
83 fn destroy(lfh: FileHandle, gpa: Allocator) void {86 fn destroy(lfh: FileHandle, gpa: Allocator) void {
84 const ptr: [*]u8 = @ptrCast(lfh.handle);87 const ptr: [*]align(@alignOf(std.os.linux.file_handle)) u8 = @ptrCast(@alignCast(lfh.handle));
85 const allocated_slice = ptr[0 .. @sizeOf(std.os.linux.file_handle) + lfh.handle.handle_bytes];88 const allocated_slice = ptr[0 .. @sizeOf(std.os.linux.file_handle) + lfh.handle.handle_bytes];
86 return gpa.free(allocated_slice);89 return gpa.free(allocated_slice);
87 }90 }
...@@ -121,6 +124,24 @@ const Os = switch (builtin.os.tag) {...@@ -121,6 +124,24 @@ const Os = switch (builtin.os.tag) {
121 };124 };
122 }125 }
123126
127 fn deinit(w: *Watch) void {
128 const gpa = w.maker.gpa;
129
130 for (w.os.handle_table.keys(), w.os.handle_table.values()) |fh, *reaction| {
131 fh.destroy(gpa);
132 reaction.reaction_set.deinit(gpa);
133 }
134 w.os.handle_table.deinit(gpa);
135
136 for (w.os.poll_fds.values()) |pollfd| {
137 Io.Threaded.closeFd(pollfd.fd);
138 }
139 w.os.poll_fds.deinit(gpa);
140
141 w.dir_table.deinit(gpa);
142 w.* = undefined;
143 }
144
124 fn getDirHandle(gpa: Allocator, path: std.Build.Cache.Path, mount_id: *MountId) !FileHandle {145 fn getDirHandle(gpa: Allocator, path: std.Build.Cache.Path, mount_id: *MountId) !FileHandle {
125 var file_handle_buffer: [@sizeOf(std.os.linux.file_handle) + 128]u8 align(@alignOf(std.os.linux.file_handle)) = undefined;146 var file_handle_buffer: [@sizeOf(std.os.linux.file_handle) + 128]u8 align(@alignOf(std.os.linux.file_handle)) = undefined;
126 var buf: [std.fs.max_path_bytes]u8 = undefined;147 var buf: [std.fs.max_path_bytes]u8 = undefined;
...@@ -152,10 +173,8 @@ const Os = switch (builtin.os.tag) {...@@ -152,10 +173,8 @@ const Os = switch (builtin.os.tag) {
152 }) {173 }) {
153 assert(meta[0].vers == M.VERSION);174 assert(meta[0].vers == M.VERSION);
154 if (meta[0].mask.Q_OVERFLOW) {175 if (meta[0].mask.Q_OVERFLOW) {
155 any_dirty = true;176 std.log.warn("file system watch queue overflowed; reconfiguring", .{});
156 std.log.warn("file system watch queue overflowed; falling back to fstat", .{});177 return error.MustReconfigure;
157 markAllFilesDirty(w);
158 return true;
159 }178 }
160 const fid: *align(1) fanotify.event_info_fid = @ptrCast(meta + 1);179 const fid: *align(1) fanotify.event_info_fid = @ptrCast(meta + 1);
161 switch (fid.hdr.info_type) {180 switch (fid.hdr.info_type) {
...@@ -166,9 +185,9 @@ const Os = switch (builtin.os.tag) {...@@ -166,9 +185,9 @@ const Os = switch (builtin.os.tag) {
166 const lfh: FileHandle = .{ .handle = file_handle };185 const lfh: FileHandle = .{ .handle = file_handle };
167 if (w.os.handle_table.getPtr(lfh)) |value| {186 if (w.os.handle_table.getPtr(lfh)) |value| {
168 if (value.reaction_set.getPtr(".")) |glob_set|187 if (value.reaction_set.getPtr(".")) |glob_set|
169 any_dirty = markStepSetDirty(maker, glob_set, any_dirty);188 any_dirty = try markStepSetDirty(maker, glob_set, any_dirty);
170 if (value.reaction_set.getPtr(file_name)) |step_set|189 if (value.reaction_set.getPtr(file_name)) |step_set|
171 any_dirty = markStepSetDirty(maker, step_set, any_dirty);190 any_dirty = try markStepSetDirty(maker, step_set, any_dirty);
172 }191 }
173 },192 },
174 else => |t| std.log.warn("unexpected fanotify event '{t}'", .{t}),193 else => |t| std.log.warn("unexpected fanotify event '{t}'", .{t}),
...@@ -304,8 +323,11 @@ const Os = switch (builtin.os.tag) {...@@ -304,8 +323,11 @@ const Os = switch (builtin.os.tag) {
304 if (events_len == 0)323 if (events_len == 0)
305 return .timeout;324 return .timeout;
306 for (w.os.poll_fds.values()) |poll_fd| {325 for (w.os.poll_fds.values()) |poll_fd| {
307 if (poll_fd.revents & std.posix.POLL.IN == std.posix.POLL.IN and try markDirtySteps(w, poll_fd.fd))326 if (poll_fd.revents & std.posix.POLL.IN == std.posix.POLL.IN and
327 try markDirtySteps(w, poll_fd.fd))
328 {
308 return .dirty;329 return .dirty;
330 }
309 }331 }
310 return .clean;332 return .clean;
311 }333 }
...@@ -361,7 +383,7 @@ const Os = switch (builtin.os.tag) {...@@ -361,7 +383,7 @@ const Os = switch (builtin.os.tag) {
361 }383 }
362 }384 }
363385
364 fn notifyApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows.ULONG) align(std.Io.Threaded.apc_align) callconv(.winapi) void {386 fn notifyApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows.ULONG) align(Io.Threaded.apc_align) callconv(.winapi) void {
365 const w: *Watch = @ptrCast(@alignCast(apc_context));387 const w: *Watch = @ptrCast(@alignCast(apc_context));
366 const dir: *Directory = @fieldParentPtr("iosb", iosb);388 const dir: *Directory = @fieldParentPtr("iosb", iosb);
367 assert(iosb.u.Status != .PENDING);389 assert(iosb.u.Status != .PENDING);
...@@ -481,6 +503,18 @@ const Os = switch (builtin.os.tag) {...@@ -481,6 +503,18 @@ const Os = switch (builtin.os.tag) {
481 };503 };
482 }504 }
483505
506 fn deinit(w: *Watch) void {
507 const gpa = w.maker.gpa;
508
509 for (w.os.handle_table.keys()) |dir| {
510 dir.deinit(gpa, w);
511 }
512 w.os.handle_table.deinit(gpa);
513
514 w.dir_table.deinit(gpa);
515 w.* = undefined;
516 }
517
484 fn getFileId(handle: windows.HANDLE) !FileId {518 fn getFileId(handle: windows.HANDLE) !FileId {
485 var file_id: FileId = undefined;519 var file_id: FileId = undefined;
486 var io_status: windows.IO_STATUS_BLOCK = undefined;520 var io_status: windows.IO_STATUS_BLOCK = undefined;
...@@ -521,10 +555,8 @@ const Os = switch (builtin.os.tag) {...@@ -521,10 +555,8 @@ const Os = switch (builtin.os.tag) {
521 var any_dirty = false;555 var any_dirty = false;
522 const bytes_returned = dir.iosb.Information;556 const bytes_returned = dir.iosb.Information;
523 if (bytes_returned == 0) {557 if (bytes_returned == 0) {
524 std.log.warn("file system watch queue overflowed; falling back to fstat", .{});558 std.log.warn("file system watch queue overflowed; reconfiguring", .{});
525 markAllFilesDirty(w);559 return error.MustReconfigure;
526 try dir.startListening(w);
527 return true;
528 }560 }
529 var file_name_buf: [std.fs.max_path_bytes]u8 = undefined;561 var file_name_buf: [std.fs.max_path_bytes]u8 = undefined;
530 var offset: usize = 0;562 var offset: usize = 0;
...@@ -532,9 +564,9 @@ const Os = switch (builtin.os.tag) {...@@ -532,9 +564,9 @@ const Os = switch (builtin.os.tag) {
532 const notify: *windows.FILE.NOTIFY.INFORMATION = @ptrCast(@alignCast(&dir.buffer[offset]));564 const notify: *windows.FILE.NOTIFY.INFORMATION = @ptrCast(@alignCast(&dir.buffer[offset]));
533 const file_name = file_name_buf[0..std.unicode.wtf16LeToWtf8(&file_name_buf, notify.fileName())];565 const file_name = file_name_buf[0..std.unicode.wtf16LeToWtf8(&file_name_buf, notify.fileName())];
534 if (dir.reaction_set.getPtr(".")) |glob_set|566 if (dir.reaction_set.getPtr(".")) |glob_set|
535 any_dirty = markStepSetDirty(maker, glob_set, any_dirty);567 any_dirty = try markStepSetDirty(maker, glob_set, any_dirty);
536 if (dir.reaction_set.getPtr(file_name)) |step_set|568 if (dir.reaction_set.getPtr(file_name)) |step_set|
537 any_dirty = markStepSetDirty(maker, step_set, any_dirty);569 any_dirty = try markStepSetDirty(maker, step_set, any_dirty);
538 if (notify.NextEntryOffset == 0)570 if (notify.NextEntryOffset == 0)
539 break;571 break;
540572
...@@ -693,6 +725,21 @@ const Os = switch (builtin.os.tag) {...@@ -693,6 +725,21 @@ const Os = switch (builtin.os.tag) {
693 };725 };
694 }726 }
695727
728 fn deinit(w: *Watch) void {
729 const gpa = w.maker.gpa;
730
731 for (w.os.handles.items(.rs), w.os.handles.items(.dir_fd)) |*rs, dir_fd| {
732 rs.deinit(gpa);
733 Io.Threaded.closeFd(dir_fd);
734 }
735 w.os.handles.deinit(gpa);
736
737 Io.Threaded.closeFd(w.os.kq_fd);
738
739 w.dir_table.deinit(gpa);
740 w.* = undefined;
741 }
742
696 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {743 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
697 const maker = w.maker;744 const maker = w.maker;
698 const gpa = maker.gpa;745 const gpa = maker.gpa;
...@@ -711,7 +758,7 @@ const Os = switch (builtin.os.tag) {...@@ -711,7 +758,7 @@ const Os = switch (builtin.os.tag) {
711 fatal("failed to open directory {f}: {t}", .{ path, err });758 fatal("failed to open directory {f}: {t}", .{ path, err });
712 };759 };
713 // Empirically the dir has to stay open or else no events are triggered.760 // Empirically the dir has to stay open or else no events are triggered.
714 errdefer if (!skip_open_dir) std.Io.Threaded.closeFd(dir_fd);761 errdefer if (!skip_open_dir) Io.Threaded.closeFd(dir_fd);
715 const changes = [1]posix.Kevent{.{762 const changes = [1]posix.Kevent{.{
716 .ident = @bitCast(@as(isize, dir_fd)),763 .ident = @bitCast(@as(isize, dir_fd)),
717 .filter = std.c.EVFILT.VNODE,764 .filter = std.c.EVFILT.VNODE,
...@@ -811,7 +858,7 @@ const Os = switch (builtin.os.tag) {...@@ -811,7 +858,7 @@ const Os = switch (builtin.os.tag) {
811 };858 };
812 const filtered_changes = if (i == handles.len - 1) changes[0..1] else &changes;859 const filtered_changes = if (i == handles.len - 1) changes[0..1] else &changes;
813 _ = try Io.Kqueue.kevent(w.os.kq_fd, filtered_changes, &.{}, null);860 _ = try Io.Kqueue.kevent(w.os.kq_fd, filtered_changes, &.{}, null);
814 if (path.sub_path.len != 0) std.Io.Threaded.closeFd(dir_fd);861 if (path.sub_path.len != 0) Io.Threaded.closeFd(dir_fd);
815862
816 w.dir_table.swapRemoveAt(i);863 w.dir_table.swapRemoveAt(i);
817 handles.swapRemove(i);864 handles.swapRemove(i);
...@@ -828,12 +875,12 @@ const Os = switch (builtin.os.tag) {...@@ -828,12 +875,12 @@ const Os = switch (builtin.os.tag) {
828 var n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(&timespec_buffer));875 var n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(&timespec_buffer));
829 if (n == 0) return .timeout;876 if (n == 0) return .timeout;
830 const reaction_sets = w.os.handles.items(.rs);877 const reaction_sets = w.os.handles.items(.rs);
831 var any_dirty = markDirtySteps(maker, reaction_sets, event_buffer[0..n], false);878 var any_dirty = try markDirtySteps(maker, reaction_sets, event_buffer[0..n], false);
832 timespec_buffer = .{ .sec = 0, .nsec = 0 };879 timespec_buffer = .{ .sec = 0, .nsec = 0 };
833 while (n == event_buffer.len) {880 while (n == event_buffer.len) {
834 n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, &timespec_buffer);881 n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, &timespec_buffer);
835 if (n == 0) break;882 if (n == 0) break;
836 any_dirty = markDirtySteps(maker, reaction_sets, event_buffer[0..n], any_dirty);883 any_dirty = try markDirtySteps(maker, reaction_sets, event_buffer[0..n], any_dirty);
837 }884 }
838 return if (any_dirty) .dirty else .clean;885 return if (any_dirty) .dirty else .clean;
839 }886 }
...@@ -843,7 +890,7 @@ const Os = switch (builtin.os.tag) {...@@ -843,7 +890,7 @@ const Os = switch (builtin.os.tag) {
843 reaction_sets: []ReactionSet,890 reaction_sets: []ReactionSet,
844 events: []const std.c.Kevent,891 events: []const std.c.Kevent,
845 start_any_dirty: bool,892 start_any_dirty: bool,
846 ) bool {893 ) !bool {
847 var any_dirty = start_any_dirty;894 var any_dirty = start_any_dirty;
848 for (events) |event| {895 for (events) |event| {
849 const index: usize = @intCast(event.udata);896 const index: usize = @intCast(event.udata);
...@@ -851,13 +898,13 @@ const Os = switch (builtin.os.tag) {...@@ -851,13 +898,13 @@ const Os = switch (builtin.os.tag) {
851 // If we knew the basename of the changed file, here we would898 // If we knew the basename of the changed file, here we would
852 // mark only the step set dirty, and possibly the glob set:899 // mark only the step set dirty, and possibly the glob set:
853 //if (reaction_set.getPtr(".")) |glob_set|900 //if (reaction_set.getPtr(".")) |glob_set|
854 // any_dirty = markStepSetDirty(maker, glob_set, any_dirty);901 // any_dirty = try markStepSetDirty(maker, glob_set, any_dirty);
855 //if (reaction_set.getPtr(file_name)) |step_set|902 //if (reaction_set.getPtr(file_name)) |step_set|
856 // any_dirty = markStepSetDirty(maker, step_set, any_dirty);903 // any_dirty = try markStepSetDirty(maker, step_set, any_dirty);
857 // However we don't know the file name so just mark all the904 // However we don't know the file name so just mark all the
858 // sets dirty for this directory.905 // sets dirty for this directory.
859 for (reaction_set.values()) |*step_set| {906 for (reaction_set.values()) |*step_set| {
860 any_dirty = markStepSetDirty(maker, step_set, any_dirty);907 any_dirty = try markStepSetDirty(maker, step_set, any_dirty);
861 }908 }
862 }909 }
863 return any_dirty;910 return any_dirty;
...@@ -875,6 +922,12 @@ const Os = switch (builtin.os.tag) {...@@ -875,6 +922,12 @@ const Os = switch (builtin.os.tag) {
875 .maker = maker,922 .maker = maker,
876 };923 };
877 }924 }
925 fn deinit(w: *Watch) void {
926 const gpa = w.maker.gpa;
927 const io = w.maker.graph.io;
928 w.os.fse.deinit(gpa, io);
929 w.* = undefined;
930 }
878 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {931 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
879 try w.os.fse.setPaths(w.maker, steps);932 try w.os.fse.setPaths(w.maker, steps);
880 w.dir_count = w.os.fse.watch_roots.len;933 w.dir_count = w.os.fse.watch_roots.len;
...@@ -915,31 +968,11 @@ pub const Match = struct {...@@ -915,31 +968,11 @@ pub const Match = struct {
915 };968 };
916};969};
917970
918fn markAllFilesDirty(w: *Watch) void {971fn markStepSetDirty(maker: *Maker, step_set: *StepSet, any_dirty: bool) error{MustReconfigure}!bool {
919 const maker = w.maker;
920
921 for (switch (builtin.os.tag) {
922 .windows => w.os.handle_table.keys(),
923 else => w.os.handle_table.values(),
924 }) |item| {
925 const reaction_set = switch (builtin.os.tag) {
926 .linux, .windows => item.reaction_set,
927 else => item,
928 };
929 for (reaction_set.values()) |step_set| {
930 for (step_set.keys()) |step_index| {
931 const step = maker.stepByIndex(step_index);
932 _ = maker.invalidateResult(step);
933 }
934 }
935 }
936}
937
938fn markStepSetDirty(maker: *Maker, step_set: *StepSet, any_dirty: bool) bool {
939 var this_any_dirty = false;972 var this_any_dirty = false;
940 for (step_set.keys()) |step_index| {973 for (step_set.keys()) |step_index| {
941 const step = maker.stepByIndex(step_index);974 const step = maker.stepByIndex(step_index);
942 if (maker.invalidateResult(step)) this_any_dirty = true;975 if (try maker.invalidateResult(step)) this_any_dirty = true;
943 }976 }
944 return any_dirty or this_any_dirty;977 return any_dirty or this_any_dirty;
945}978}
...@@ -984,6 +1017,11 @@ pub const WaitResult = enum {...@@ -984,6 +1017,11 @@ pub const WaitResult = enum {
984 clean,1017 clean,
985};1018};
9861019
1020/// May return `error.MustReconfigure`.
987pub fn wait(w: *Watch, timeout: Timeout) !WaitResult {1021pub fn wait(w: *Watch, timeout: Timeout) !WaitResult {
988 return Os.wait(w, timeout);1022 return Os.wait(w, timeout);
989}1023}
1024
1025pub fn deinit(w: *Watch) void {
1026 Os.deinit(w);
1027}
lib/compiler/Maker/Watch/FsEvents.zig+28-8
...@@ -46,6 +46,8 @@ since_event: FSEventStreamEventId,...@@ -46,6 +46,8 @@ since_event: FSEventStreamEventId,
4646
47cwd_path: []const u8,47cwd_path: []const u8,
4848
49must_reconfigure: bool,
50
49/// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols51/// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols
50/// is not present, `init` will close the framework and return an error.52/// is not present, `init` will close the framework and return an error.
51const ResolvedSymbols = struct {53const ResolvedSymbols = struct {
...@@ -104,13 +106,15 @@ pub fn init(cwd_path: []const u8) error{ OpenFrameworkFailed, MissingCoreService...@@ -104,13 +106,15 @@ pub fn init(cwd_path: []const u8) error{ OpenFrameworkFailed, MissingCoreService
104 // to notice any changes which happened during said work.106 // to notice any changes which happened during said work.
105 .since_event = resolved_symbols.FSEventsGetCurrentEventId(),107 .since_event = resolved_symbols.FSEventsGetCurrentEventId(),
106 .cwd_path = cwd_path,108 .cwd_path = cwd_path,
109 .must_reconfigure = false,
107 };110 };
108}111}
109112
110pub fn deinit(fse: *FsEvents, gpa: Allocator, io: Io) void {113pub fn deinit(fse: *FsEvents, gpa: Allocator, io: Io) void {
114 _ = io;
111 fse.waiting_semaphore.as_object().release();115 fse.waiting_semaphore.as_object().release();
112 fse.dispatch_queue.as_object().release();116 fse.dispatch_queue.as_object().release();
113 fse.core_services.close(io);117 fse.core_services.close();
114118
115 gpa.free(fse.watch_roots);119 gpa.free(fse.watch_roots);
116 fse.watch_paths.deinit(gpa);120 fse.watch_paths.deinit(gpa);
...@@ -211,7 +215,7 @@ pub fn setPaths(fse: *FsEvents, maker: *Maker, steps: []const std.Build.Configur...@@ -211,7 +215,7 @@ pub fn setPaths(fse: *FsEvents, maker: *Maker, steps: []const std.Build.Configur
211 }215 }
212}216}
213217
214pub fn wait(fse: *FsEvents, maker: *Maker, timeout_ns: ?u64) error{ OutOfMemory, StartFailed }!Watch.WaitResult {218pub fn wait(fse: *FsEvents, maker: *Maker, timeout_ns: ?u64) error{ OutOfMemory, StartFailed, MustReconfigure }!Watch.WaitResult {
215 if (fse.watch_roots.len == 0) @panic("nothing to watch");219 if (fse.watch_roots.len == 0) @panic("nothing to watch");
216 const gpa = maker.gpa;220 const gpa = maker.gpa;
217221
...@@ -285,6 +289,7 @@ pub fn wait(fse: *FsEvents, maker: *Maker, timeout_ns: ?u64) error{ OutOfMemory,...@@ -285,6 +289,7 @@ pub fn wait(fse: *FsEvents, maker: *Maker, timeout_ns: ?u64) error{ OutOfMemory,
285 const ns = timeout_ns orelse break :timeout .FOREVER;289 const ns = timeout_ns orelse break :timeout .FOREVER;
286 break :timeout .time(.NOW, @intCast(ns));290 break :timeout .time(.NOW, @intCast(ns));
287 });291 });
292 if (fse.must_reconfigure) return error.MustReconfigure;
288 return switch (result) {293 return switch (result) {
289 0 => .dirty,294 0 => .dirty,
290 else => .timeout,295 else => .timeout,
...@@ -355,13 +360,23 @@ fn eventCallback(...@@ -355,13 +360,23 @@ fn eventCallback(
355 false => {360 false => {
356 if (fse.watch_paths.get(event_path)) |steps| {361 if (fse.watch_paths.get(event_path)) |steps| {
357 assert(steps.len > 0);362 assert(steps.len > 0);
358 if (invalidateSteps(maker, steps)) any_dirty = true;363 if (invalidateSteps(maker, steps) catch |err| switch (err) {
364 error.MustReconfigure => {
365 fse.must_reconfigure = true;
366 break;
367 },
368 }) any_dirty = true;
359 }369 }
360 if (std.fs.path.dirname(event_path)) |event_dirname| {370 if (std.fs.path.dirname(event_path)) |event_dirname| {
361 // Modifying '/foo/bar' triggers the watch on '/foo'.371 // Modifying '/foo/bar' triggers the watch on '/foo'.
362 if (fse.watch_paths.get(event_dirname)) |steps| {372 if (fse.watch_paths.get(event_dirname)) |steps| {
363 assert(steps.len > 0);373 assert(steps.len > 0);
364 if (invalidateSteps(maker, steps)) any_dirty = true;374 if (invalidateSteps(maker, steps) catch |err| switch (err) {
375 error.MustReconfigure => {
376 fse.must_reconfigure = true;
377 break;
378 },
379 }) any_dirty = true;
365 }380 }
366 }381 }
367 },382 },
...@@ -374,13 +389,18 @@ fn eventCallback(...@@ -374,13 +389,18 @@ fn eventCallback(
374 const changed_path = std.fs.path.dirname(event_path) orelse event_path;389 const changed_path = std.fs.path.dirname(event_path) orelse event_path;
375 for (fse.watch_paths.keys(), fse.watch_paths.values()) |watching_path, steps| {390 for (fse.watch_paths.keys(), fse.watch_paths.values()) |watching_path, steps| {
376 if (dirStartsWith(watching_path, changed_path)) {391 if (dirStartsWith(watching_path, changed_path)) {
377 if (invalidateSteps(maker, steps)) any_dirty = true;392 if (invalidateSteps(maker, steps) catch |err| switch (err) {
393 error.MustReconfigure => {
394 fse.must_reconfigure = true;
395 break;
396 },
397 }) any_dirty = true;
378 }398 }
379 }399 }
380 },400 },
381 }401 }
382 }402 }
383 if (any_dirty) {403 if (any_dirty or fse.must_reconfigure) {
384 fse.since_event = rs.FSEventStreamGetLatestEventId(stream);404 fse.since_event = rs.FSEventStreamGetLatestEventId(stream);
385 _ = fse.waiting_semaphore.signal();405 _ = fse.waiting_semaphore.signal();
386 }406 }
...@@ -392,11 +412,11 @@ fn dirStartsWith(path: []const u8, prefix: []const u8) bool {...@@ -392,11 +412,11 @@ fn dirStartsWith(path: []const u8, prefix: []const u8) bool {
392 return true; // `path` is `/foo/bar/...`, `prefix` is `/foo/bar`412 return true; // `path` is `/foo/bar/...`, `prefix` is `/foo/bar`
393}413}
394414
395fn invalidateSteps(maker: *Maker, steps: []const std.Build.Configuration.Step.Index) bool {415fn invalidateSteps(maker: *Maker, steps: []const std.Build.Configuration.Step.Index) !bool {
396 var any_dirty = false;416 var any_dirty = false;
397 for (steps) |step_index| {417 for (steps) |step_index| {
398 const step = maker.stepByIndex(step_index);418 const step = maker.stepByIndex(step_index);
399 if (maker.invalidateResult(step)) any_dirty = true;419 if (try maker.invalidateResult(step)) any_dirty = true;
400 }420 }
401 return any_dirty;421 return any_dirty;
402}422}
lib/std/Build/Cache.zig+22-11
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1//! Manages `zig-cache` directories.1//! Tracks metadata of file inputs associated with Zig compiler and build
2//! This is not a general-purpose cache. It is designed to be fast and simple,2//! system artifacts in order to determine whether those artifacts must be
3//! not to withstand attacks using specially-crafted input.3//! produced again, or may be retrieved from the cache directory on the
44//! filesystem.
5const Cache = @This();5const Cache = @This();
6const builtin = @import("builtin");6const builtin = @import("builtin");
77
...@@ -1236,19 +1236,32 @@ pub const Manifest = struct {...@@ -1236,19 +1236,32 @@ pub const Manifest = struct {
12361236
1237 /// Obtain only the data needed to maintain a lock on the manifest file.1237 /// Obtain only the data needed to maintain a lock on the manifest file.
1238 /// The `Manifest` remains safe to deinit.1238 /// The `Manifest` remains safe to deinit.
1239 ///
1239 /// Don't forget to call `writeManifest` before this!1240 /// Don't forget to call `writeManifest` before this!
1240 pub fn toOwnedLock(self: *Manifest) Lock {1241 pub fn toOwnedLock(self: *Manifest) Lock {
1241 defer self.manifest_file = null;1242 defer self.manifest_file = null;
1242 return .{ .manifest_file = self.manifest_file.? };1243 return .{ .manifest_file = self.manifest_file.? };
1243 }1244 }
12441245
1246 pub fn takeFiles(man: *Manifest) Files {
1247 defer man.files = .empty;
1248 return man.files;
1249 }
1250
1251 pub fn freeFiles(gpa: Allocator, files: *Files) void {
1252 for (files.keys()) |*file| file.deinit(gpa);
1253 files.deinit(gpa);
1254 }
1255
1245 /// Releases the manifest file and frees any memory the Manifest was using.1256 /// Releases the manifest file and frees any memory the Manifest was using.
1246 /// `Manifest.hit` must be called first.1257 /// `Manifest.hit` must be called first.
1258 ///
1247 /// Don't forget to call `writeManifest` before this!1259 /// Don't forget to call `writeManifest` before this!
1248 pub fn deinit(self: *Manifest) void {1260 pub fn deinit(man: *Manifest) void {
1249 const io = self.cache.io;1261 const io = man.cache.io;
1262 const gpa = man.cache.gpa;
12501263
1251 if (self.manifest_file) |file| {1264 if (man.manifest_file) |file| {
1252 if (builtin.os.tag == .windows) {1265 if (builtin.os.tag == .windows) {
1253 // See Lock.release for why this is required on Windows1266 // See Lock.release for why this is required on Windows
1254 file.unlock(io);1267 file.unlock(io);
...@@ -1256,10 +1269,8 @@ pub const Manifest = struct {...@@ -1256,10 +1269,8 @@ pub const Manifest = struct {
12561269
1257 file.close(io);1270 file.close(io);
1258 }1271 }
1259 for (self.files.keys()) |*file| {1272 freeFiles(gpa, &man.files);
1260 file.deinit(self.cache.gpa);1273 man.* = undefined;
1261 }
1262 self.files.deinit(self.cache.gpa);
1263 }1274 }
12641275
1265 pub fn populateFileSystemInputs(man: *Manifest, buf: *std.ArrayList(u8)) Allocator.Error!void {1276 pub fn populateFileSystemInputs(man: *Manifest, buf: *std.ArrayList(u8)) Allocator.Error!void {
src/main.zig+1
...@@ -5134,6 +5134,7 @@ fn jitCmdInner(...@@ -5134,6 +5134,7 @@ fn jitCmdInner(
5134 }5134 }
51355135
5136 if (process.can_replace and options.capture == null) {5136 if (process.can_replace and options.capture == null) {
5137 _ = try io.lockStderr(&.{}, .no_color);
5137 const err = process.replace(io, .{ .argv = child_argv.items, .environ_map = environ_map });5138 const err = process.replace(io, .{ .argv = child_argv.items, .environ_map = environ_map });
5138 const cmd = try std.mem.join(arena, " ", child_argv.items);5139 const cmd = try std.mem.join(arena, " ", child_argv.items);
5139 fatal("the following command failed to execve with {t}:\n{s}", .{ err, cmd });5140 fatal("the following command failed to execve with {t}:\n{s}", .{ err, cmd });