| ... | @@ -44,6 +44,10 @@ gpa: Allocator, | ... | @@ -44,6 +44,10 @@ gpa: Allocator, |
| 44 | graph: *Graph, | 44 | graph: *Graph, |
| 45 | install_paths: InstallPaths, | 45 | install_paths: InstallPaths, |
| 46 | scanned_config: *const ScannedConfig, | 46 | scanned_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. |
| 47 | steps: []Step, | 51 | steps: []Step, |
| 48 | generated_files: []Path, | 52 | generated_files: []Path, |
| 49 | run_args: ?[]const []const u8, | 53 | run_args: ?[]const []const u8, |
| ... | @@ -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; |
| 710 | | 714 | |
| 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 user | 721 | // If this fails, we can still start the server and wait for user |
| 713 | // to request a rebuild. If it returns error.FailedButCacheIntact | 722 | // to request a rebuild. If it returns error.FailedButCacheIntact |
| 714 | // we can even still do file system watching and automatically | 723 | // 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 | }, |
| 768 | | 778 | |
| 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, |
| 772 | | 784 | |
| ... | @@ -843,7 +855,7 @@ pub fn main(init: process.Init.Minimal) !void { | ... | @@ -843,7 +855,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 843 | try select.concurrent(.message, Server.receiveMessage, .{s}); | 855 | try select.concurrent(.message, Server.receiveMessage, .{s}); |
| 844 | | 856 | |
| 845 | maker.watch = body.flags.watch; | 857 | maker.watch = body.flags.watch; |
| 846 | maker.prepare(steps) catch |err| switch (err) { | 858 | maker.prepare(steps, &configure_source_files) catch |err| switch (err) { |
| 847 | error.DependencyLoopDetected, error.InsufficientMemory => { | 859 | error.DependencyLoopDetected, error.InsufficientMemory => { |
| 848 | // TODO handle DependencyLoopDetected as error.FailedButCacheIntact | 860 | // TODO handle DependencyLoopDetected as error.FailedButCacheIntact |
| 849 | // and handle InsufficientMemory as error.AlreadyReported | 861 | // and handle InsufficientMemory as error.AlreadyReported |
| ... | @@ -859,8 +871,11 @@ pub fn main(init: process.Init.Minimal) !void { | ... | @@ -859,8 +871,11 @@ pub fn main(init: process.Init.Minimal) !void { |
| 859 | if (!Watch.have_impl) unreachable; | 871 | if (!Watch.have_impl) unreachable; |
| 860 | if (w == null) w = try .init(&maker); | 872 | if (w == null) w = try .init(&maker); |
| 861 | | 873 | |
| 862 | try w.?.update(maker.step_stack.keys()); | 874 | try updateWatch(&maker, &w.?); |
| 863 | try select.concurrent(.fs_event, Watch.wait, .{ &w.?, if (in_debounce) .{ .ms = debounce_interval_ms } else .none }); | 875 | try select.concurrent(.fs_event, Watch.wait, .{ |
| | 876 | &w.?, |
| | 877 | if (in_debounce) .{ .ms = debounce_interval_ms } else .none, |
| | 878 | }); |
| 864 | } | 879 | } |
| 865 | | 880 | |
| 866 | continue :loop try select.await(); | 881 | continue :loop try select.await(); |
| ... | @@ -870,7 +885,13 @@ pub fn main(init: process.Init.Minimal) !void { | ... | @@ -870,7 +885,13 @@ pub fn main(init: process.Init.Minimal) !void { |
| 870 | }, | 885 | }, |
| 871 | .fs_event => |payload| { | 886 | .fs_event => |payload| { |
| 872 | if (!Watch.have_impl) unreachable; | 887 | if (!Watch.have_impl) unreachable; |
| 873 | switch (try payload) { | 888 | switch (payload catch |err| switch (err) { |
| | 889 | error.MustReconfigure => { |
| | 890 | try io.sleep(.fromMilliseconds(debounce_interval_ms), .awake); |
| | 891 | continue :configure; |
| | 892 | }, |
| | 893 | else => |e| fatal("file watching failed: {t}", .{e}), |
| | 894 | }) { |
| 874 | .timeout => { | 895 | .timeout => { |
| 875 | assert(in_debounce); | 896 | assert(in_debounce); |
| 876 | markFailedStepsDirty(&maker); | 897 | markFailedStepsDirty(&maker); |
| ... | @@ -880,7 +901,10 @@ pub fn main(init: process.Init.Minimal) !void { | ... | @@ -880,7 +901,10 @@ pub fn main(init: process.Init.Minimal) !void { |
| 880 | .dirty => in_debounce = true, | 901 | .dirty => in_debounce = true, |
| 881 | .clean => {}, | 902 | .clean => {}, |
| 882 | } | 903 | } |
| 883 | try select.concurrent(.fs_event, Watch.wait, .{ &w.?, if (in_debounce) .{ .ms = debounce_interval_ms } else .none }); | 904 | try select.concurrent(.fs_event, Watch.wait, .{ |
| | 905 | &w.?, |
| | 906 | if (in_debounce) .{ .ms = debounce_interval_ms } else .none, |
| | 907 | }); |
| 884 | continue :loop try select.await(); | 908 | continue :loop try select.await(); |
| 885 | }, | 909 | }, |
| 886 | } | 910 | } |
| ... | @@ -889,7 +913,7 @@ pub fn main(init: process.Init.Minimal) !void { | ... | @@ -889,7 +913,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 889 | const initial_steps = try maker.resolveTopLevelSteps(step_names.items); | 913 | const initial_steps = try maker.resolveTopLevelSteps(step_names.items); |
| 890 | defer gpa.free(initial_steps); | 914 | defer gpa.free(initial_steps); |
| 891 | | 915 | |
| 892 | maker.prepare(initial_steps) catch |err| switch (err) { | 916 | maker.prepare(initial_steps, &configure_source_files) catch |err| switch (err) { |
| 893 | error.DependencyLoopDetected, error.InsufficientMemory => { | 917 | error.DependencyLoopDetected, error.InsufficientMemory => { |
| 894 | // TODO handle DependencyLoopDetected as error.FailedButCacheIntact | 918 | // TODO handle DependencyLoopDetected as error.FailedButCacheIntact |
| 895 | // and handle InsufficientMemory as error.AlreadyReported | 919 | // and handle InsufficientMemory as error.AlreadyReported |
| ... | @@ -938,7 +962,7 @@ pub fn main(init: process.Init.Minimal) !void { | ... | @@ -938,7 +962,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 938 | // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`. | 962 | // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`. |
| 939 | if (!Watch.have_impl) unreachable; | 963 | if (!Watch.have_impl) unreachable; |
| 940 | | 964 | |
| 941 | try w.update(maker.step_stack.keys()); | 965 | try updateWatch(&maker, &w); |
| 942 | | 966 | |
| 943 | // Wait until a file system notification arrives. Read all such events | 967 | // Wait until a file system notification arrives. Read all such events |
| 944 | // until the buffer is empty. Then wait for a debounce interval, resetting | 968 | // until the buffer is empty. Then wait for a debounce interval, resetting |
| ... | @@ -950,21 +974,34 @@ pub fn main(init: process.Init.Minimal) !void { | ... | @@ -950,21 +974,34 @@ pub fn main(init: process.Init.Minimal) !void { |
| 950 | w.dir_count, countSubProcesses(&maker), | 974 | w.dir_count, countSubProcesses(&maker), |
| 951 | }) catch &caption_buf; | 975 | }) catch &caption_buf; |
| 952 | var debouncing_node = main_progress_node.start(caption, 0); | 976 | var debouncing_node = main_progress_node.start(caption, 0); |
| | 977 | defer debouncing_node.end(); |
| 953 | var in_debounce = false; | 978 | var in_debounce = false; |
| 954 | while (true) switch (try w.wait(if (in_debounce) .{ .ms = debounce_interval_ms } else .none)) { | 979 | while (true) { |
| 955 | .timeout => { | 980 | const timeout: Watch.Timeout = if (in_debounce) .{ .ms = debounce_interval_ms } else .none; |
| 956 | assert(in_debounce); | 981 | switch (w.wait(timeout) catch |err| switch (err) { |
| 957 | debouncing_node.end(); | 982 | error.MustReconfigure => { |
| 958 | markFailedStepsDirty(&maker); | 983 | debouncing_node.end(); |
| 959 | continue :rebuild; | 984 | debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0); |
| 960 | }, | 985 | try io.sleep(.fromMilliseconds(debounce_interval_ms), .awake); |
| 961 | .dirty => if (!in_debounce) { | 986 | continue :configure; |
| 962 | in_debounce = true; | 987 | }, |
| 963 | debouncing_node.end(); | 988 | else => |e| fatal("file watching failed: {t}", .{e}), |
| 964 | debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0); | 989 | }) { |
| 965 | }, | 990 | .timeout => { |
| 966 | .clean => {}, | 991 | assert(in_debounce); |
| 967 | }; | 992 | debouncing_node.end(); |
| | 993 | debouncing_node = .none; |
| | 994 | markFailedStepsDirty(&maker); |
| | 995 | continue :rebuild; |
| | 996 | }, |
| | 997 | .dirty => if (!in_debounce) { |
| | 998 | in_debounce = true; |
| | 999 | debouncing_node.end(); |
| | 1000 | debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0); |
| | 1001 | }, |
| | 1002 | .clean => {}, |
| | 1003 | } |
| | 1004 | } |
| 968 | } | 1005 | } |
| 969 | } else |err| { | 1006 | } else |err| { |
| 970 | const can_fs_watch = switch (err) { | 1007 | const can_fs_watch = switch (err) { |
| ... | @@ -991,6 +1028,15 @@ pub fn main(init: process.Init.Minimal) !void { | ... | @@ -991,6 +1028,15 @@ pub fn main(init: process.Init.Minimal) !void { |
| 991 | } | 1028 | } |
| 992 | } | 1029 | } |
| 993 | | 1030 | |
| | 1031 | /// Temporarily adds the reconfigure pseudostep to step_stack, calls |
| | 1032 | /// `Watch.update`, and then pops it again. |
| | 1033 | fn updateWatch(maker: *Maker, watch: *Watch) !void { |
| | 1034 | const step_stack = &maker.step_stack; |
| | 1035 | try step_stack.putNoClobber(maker.gpa, @fromBackingInt(@intCast(maker.steps.len - 1)), {}); |
| | 1036 | defer _ = step_stack.pop().?; |
| | 1037 | try watch.update(step_stack.keys()); |
| | 1038 | } |
| | 1039 | |
| 994 | const ConfigureOptions = struct { | 1040 | const ConfigureOptions = struct { |
| 995 | configure_argv: [][]const u8, | 1041 | configure_argv: [][]const u8, |
| 996 | conf_argv_index_build_root: usize, | 1042 | conf_argv_index_build_root: usize, |
| ... | @@ -1008,6 +1054,7 @@ const ConfigureOptions = struct { | ... | @@ -1008,6 +1054,7 @@ const ConfigureOptions = struct { |
| 1008 | fetch_only: bool, | 1054 | fetch_only: bool, |
| 1009 | print_configuration: PrintConfiguration, | 1055 | print_configuration: PrintConfiguration, |
| 1010 | forks: []Fork, | 1056 | forks: []Fork, |
| | 1057 | src_files: *Cache.Manifest.Files, |
| 1011 | }; | 1058 | }; |
| 1012 | | 1059 | |
| 1013 | fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { | 1060 | fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| ... | @@ -1362,14 +1409,14 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { | ... | @@ -1362,14 +1409,14 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| 1362 | | 1409 | |
| 1363 | if (config_man) |man| { | 1410 | if (config_man) |man| { |
| 1364 | if (try man.hit(compile_prog_node)) { | 1411 | if (try man.hit(compile_prog_node)) { |
| | 1412 | log.debug("configuration cache hit", .{}); |
| 1365 | const digest = man.final(); | 1413 | const digest = man.final(); |
| 1366 | break :cp .{ | 1414 | const path: Path = .{ |
| 1367 | .{ | 1415 | .root_dir = graph.local_cache_root, |
| 1368 | .root_dir = graph.local_cache_root, | 1416 | .sub_path = try arena.print("c/{s}", .{&digest}), |
| 1369 | .sub_path = try arena.print("c/{s}", .{&digest}), | | |
| 1370 | }, | | |
| 1371 | man.toOwnedLock(), | | |
| 1372 | }; | 1417 | }; |
| | 1418 | options.src_files.* = man.takeFiles(); |
| | 1419 | break :cp .{ path, man.toOwnedLock() }; |
| 1373 | } | 1420 | } |
| 1374 | } | 1421 | } |
| 1375 | const configure_exe_path: Path = if (std.zig.buildExeSubprocess(gpa, io, .{ | 1422 | const configure_exe_path: Path = if (std.zig.buildExeSubprocess(gpa, io, .{ |
| ... | @@ -1505,6 +1552,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { | ... | @@ -1505,6 +1552,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| 1505 | }); | 1552 | }); |
| 1506 | }; | 1553 | }; |
| 1507 | man.writeManifest() catch |err| log.warn("failed to write cache manifest: {t}", .{err}); | 1554 | man.writeManifest() catch |err| log.warn("failed to write cache manifest: {t}", .{err}); |
| | 1555 | options.src_files.* = man.takeFiles(); |
| 1508 | break :cp .{ final_path, man.toOwnedLock() }; | 1556 | break :cp .{ final_path, man.toOwnedLock() }; |
| 1509 | } | 1557 | } |
| 1510 | }; | 1558 | }; |
| ... | @@ -2119,7 +2167,13 @@ fn markFailedStepsDirty(maker: *Maker) void { | ... | @@ -2119,7 +2167,13 @@ fn markFailedStepsDirty(maker: *Maker) void { |
| 2119 | for (all_steps) |step_index| { | 2167 | for (all_steps) |step_index| { |
| 2120 | const step = maker.stepByIndex(step_index); | 2168 | const step = maker.stepByIndex(step_index); |
| 2121 | switch (step.state) { | 2169 | switch (step.state) { |
| 2122 | .dependency_failure, .dependency_skipped, .failure, .skipped => _ = maker.invalidateResult(step), | 2170 | .dependency_failure, |
| | 2171 | .dependency_skipped, |
| | 2172 | .failure, |
| | 2173 | .skipped, |
| | 2174 | => _ = maker.invalidateResult(step) catch |err| switch (err) { |
| | 2175 | error.MustReconfigure => unreachable, |
| | 2176 | }, |
| 2123 | else => continue, | 2177 | else => continue, |
| 2124 | } | 2178 | } |
| 2125 | } | 2179 | } |
| ... | @@ -2173,7 +2227,11 @@ fn resolveTopLevelSteps(maker: *Maker, step_names: []const []const u8) ![]const | ... | @@ -2173,7 +2227,11 @@ fn resolveTopLevelSteps(maker: *Maker, step_names: []const []const u8) ![]const |
| 2173 | return try gpa.dupe(Configuration.Step.Index, result.keys()); | 2227 | return try gpa.dupe(Configuration.Step.Index, result.keys()); |
| 2174 | } | 2228 | } |
| 2175 | | 2229 | |
| 2176 | fn prepare(maker: *Maker, step_indices: []const Configuration.Step.Index) !void { | 2230 | fn prepare( |
| | 2231 | maker: *Maker, |
| | 2232 | step_indices: []const Configuration.Step.Index, |
| | 2233 | configure_source_files: *const Cache.Manifest.Files, |
| | 2234 | ) !void { |
| 2177 | const gpa = maker.gpa; | 2235 | const gpa = maker.gpa; |
| 2178 | const graph = maker.graph; | 2236 | const graph = maker.graph; |
| 2179 | const arena = graph.arena; | 2237 | const arena = graph.arena; |
| ... | @@ -2182,10 +2240,17 @@ fn prepare(maker: *Maker, step_indices: []const Configuration.Step.Index) !void | ... | @@ -2182,10 +2240,17 @@ fn prepare(maker: *Maker, step_indices: []const Configuration.Step.Index) !void |
| 2182 | const step_stack = &maker.step_stack; | 2240 | const step_stack = &maker.step_stack; |
| 2183 | const c = &maker.scanned_config.configuration; | 2241 | const c = &maker.scanned_config.configuration; |
| 2184 | | 2242 | |
| 2185 | for (maker.steps, 0..) |*step, step_index_usize| { | 2243 | // The last element is a reserved special pseudostep which contains the |
| | 2244 | // watch inputs for the configurer executable. |
| | 2245 | 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)); | 2246 | const step_index: Configuration.Step.Index = @fromBackingInt(@intCast(step_index_usize)); |
| 2187 | step.* = .{ .extended = .init(step_index.ptr(c).flags(c).tag) }; | 2247 | step.* = .{ .extended = .init(step_index.ptr(c).flags(c).tag) }; |
| 2188 | } | 2248 | } |
| | 2249 | { |
| | 2250 | const last_step = &maker.steps[maker.steps.len - 1]; |
| | 2251 | last_step.* = .{ .extended = .init(.top_level) }; |
| | 2252 | try last_step.setWatchInputsFromManifestFiles(maker, configure_source_files, graph.cache.prefixes()); |
| | 2253 | } |
| 2189 | | 2254 | |
| 2190 | try initial_steps.ensureUnusedCapacity(gpa, step_indices.len); | 2255 | try initial_steps.ensureUnusedCapacity(gpa, step_indices.len); |
| 2191 | try step_stack.ensureUnusedCapacity(gpa, step_indices.len); | 2256 | try step_stack.ensureUnusedCapacity(gpa, step_indices.len); |
| ... | @@ -3045,14 +3110,15 @@ fn constructGraphAndCheckForDependencyLoop( | ... | @@ -3045,14 +3110,15 @@ fn constructGraphAndCheckForDependencyLoop( |
| 3045 | /// When file watching, prepares the step for being re-evaluated. Returns | 3110 | /// When file watching, prepares the step for being re-evaluated. Returns |
| 3046 | /// `true` if the step was newly invalidated, `false` if it was already | 3111 | /// `true` if the step was newly invalidated, `false` if it was already |
| 3047 | /// invalidated. | 3112 | /// invalidated. |
| 3048 | pub fn invalidateResult(maker: *Maker, step: *Step) bool { | 3113 | pub fn invalidateResult(maker: *Maker, step: *Step) error{MustReconfigure}!bool { |
| | 3114 | if (step == &maker.steps[maker.steps.len - 1]) return error.MustReconfigure; |
| 3049 | if (step.state == .precheck_done) return false; | 3115 | if (step.state == .precheck_done) return false; |
| 3050 | assert(step.pending_deps == 0); | 3116 | assert(step.pending_deps == 0); |
| 3051 | step.state = .precheck_done; | 3117 | step.state = .precheck_done; |
| 3052 | step.reset(maker); | 3118 | step.reset(maker); |
| 3053 | for (step.dependants.items) |dependant_index| { | 3119 | for (step.dependants.items) |dependant_index| { |
| 3054 | const dependant = maker.stepByIndex(dependant_index); | 3120 | const dependant = maker.stepByIndex(dependant_index); |
| 3055 | _ = invalidateResult(maker, dependant); | 3121 | _ = try invalidateResult(maker, dependant); |
| 3056 | dependant.pending_deps += 1; | 3122 | dependant.pending_deps += 1; |
| 3057 | } | 3123 | } |
| 3058 | return true; | 3124 | return true; |