| ... | ... | @@ -44,6 +44,10 @@ gpa: Allocator, |
| 44 | 44 | graph: *Graph, |
| 45 | 45 | install_paths: InstallPaths, |
| 46 | 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 | 51 | steps: []Step, |
| 48 | 52 | generated_files: []Path, |
| 49 | 53 | run_args: ?[]const []const u8, |
| ... | ... | @@ -708,7 +712,12 @@ pub fn main(init: process.Init.Minimal) !void { |
| 708 | 712 | break :s &protocol_server_allocation; |
| 709 | 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 | 721 | // If this fails, we can still start the server and wait for user |
| 713 | 722 | // to request a rebuild. If it returns error.FailedButCacheIntact |
| 714 | 723 | // we can even still do file system watching and automatically |
| ... | ... | @@ -730,6 +739,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 730 | 739 | .fetch_only = fetch_only, |
| 731 | 740 | .print_configuration = print_configuration, |
| 732 | 741 | .forks = forks.items, |
| 742 | .src_files = &configure_source_files, |
| 733 | 743 | })) |scanned_config| { |
| 734 | 744 | if (help_menu) { |
| 735 | 745 | scanned_config.printUsage(&graph, initStdoutWriter(io)) catch |err| switch (err) { |
| ... | ... | @@ -766,7 +776,9 @@ pub fn main(init: process.Init.Minimal) !void { |
| 766 | 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 | 782 | .generated_files = try arena.alloc(Path, scanned_config.configuration.generated_files_len), |
| 771 | 783 | .run_args = run_args, |
| 772 | 784 | |
| ... | ... | @@ -843,7 +855,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 843 | 855 | try select.concurrent(.message, Server.receiveMessage, .{s}); |
| 844 | 856 | |
| 845 | 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 | 859 | error.DependencyLoopDetected, error.InsufficientMemory => { |
| 848 | 860 | // TODO handle DependencyLoopDetected as error.FailedButCacheIntact |
| 849 | 861 | // and handle InsufficientMemory as error.AlreadyReported |
| ... | ... | @@ -859,8 +871,11 @@ pub fn main(init: process.Init.Minimal) !void { |
| 859 | 871 | if (!Watch.have_impl) unreachable; |
| 860 | 872 | if (w == null) w = try .init(&maker); |
| 861 | 873 | |
| 862 | | try w.?.update(maker.step_stack.keys()); |
| 863 | | try select.concurrent(.fs_event, Watch.wait, .{ &w.?, if (in_debounce) .{ .ms = debounce_interval_ms } else .none }); |
| 874 | try updateWatch(&maker, &w.?); |
| 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 | 881 | continue :loop try select.await(); |
| ... | ... | @@ -870,7 +885,13 @@ pub fn main(init: process.Init.Minimal) !void { |
| 870 | 885 | }, |
| 871 | 886 | .fs_event => |payload| { |
| 872 | 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 | 895 | .timeout => { |
| 875 | 896 | assert(in_debounce); |
| 876 | 897 | markFailedStepsDirty(&maker); |
| ... | ... | @@ -880,7 +901,10 @@ pub fn main(init: process.Init.Minimal) !void { |
| 880 | 901 | .dirty => in_debounce = true, |
| 881 | 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 | 908 | continue :loop try select.await(); |
| 885 | 909 | }, |
| 886 | 910 | } |
| ... | ... | @@ -889,7 +913,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 889 | 913 | const initial_steps = try maker.resolveTopLevelSteps(step_names.items); |
| 890 | 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 | 917 | error.DependencyLoopDetected, error.InsufficientMemory => { |
| 894 | 918 | // TODO handle DependencyLoopDetected as error.FailedButCacheIntact |
| 895 | 919 | // and handle InsufficientMemory as error.AlreadyReported |
| ... | ... | @@ -938,7 +962,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 938 | 962 | // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`. |
| 939 | 963 | if (!Watch.have_impl) unreachable; |
| 940 | 964 | |
| 941 | | try w.update(maker.step_stack.keys()); |
| 965 | try updateWatch(&maker, &w); |
| 942 | 966 | |
| 943 | 967 | // Wait until a file system notification arrives. Read all such events |
| 944 | 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 | 974 | w.dir_count, countSubProcesses(&maker), |
| 951 | 975 | }) catch &caption_buf; |
| 952 | 976 | var debouncing_node = main_progress_node.start(caption, 0); |
| 977 | defer debouncing_node.end(); |
| 953 | 978 | var in_debounce = false; |
| 954 | | while (true) switch (try w.wait(if (in_debounce) .{ .ms = debounce_interval_ms } else .none)) { |
| 955 | | .timeout => { |
| 956 | | assert(in_debounce); |
| 957 | | debouncing_node.end(); |
| 958 | | markFailedStepsDirty(&maker); |
| 959 | | continue :rebuild; |
| 960 | | }, |
| 961 | | .dirty => if (!in_debounce) { |
| 962 | | in_debounce = true; |
| 963 | | debouncing_node.end(); |
| 964 | | debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0); |
| 965 | | }, |
| 966 | | .clean => {}, |
| 967 | | }; |
| 979 | while (true) { |
| 980 | const timeout: Watch.Timeout = if (in_debounce) .{ .ms = debounce_interval_ms } else .none; |
| 981 | switch (w.wait(timeout) catch |err| switch (err) { |
| 982 | error.MustReconfigure => { |
| 983 | debouncing_node.end(); |
| 984 | debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0); |
| 985 | try io.sleep(.fromMilliseconds(debounce_interval_ms), .awake); |
| 986 | continue :configure; |
| 987 | }, |
| 988 | else => |e| fatal("file watching failed: {t}", .{e}), |
| 989 | }) { |
| 990 | .timeout => { |
| 991 | assert(in_debounce); |
| 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 | 1006 | } else |err| { |
| 970 | 1007 | const can_fs_watch = switch (err) { |
| ... | ... | @@ -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 | 1040 | const ConfigureOptions = struct { |
| 995 | 1041 | configure_argv: [][]const u8, |
| 996 | 1042 | conf_argv_index_build_root: usize, |
| ... | ... | @@ -1008,6 +1054,7 @@ const ConfigureOptions = struct { |
| 1008 | 1054 | fetch_only: bool, |
| 1009 | 1055 | print_configuration: PrintConfiguration, |
| 1010 | 1056 | forks: []Fork, |
| 1057 | src_files: *Cache.Manifest.Files, |
| 1011 | 1058 | }; |
| 1012 | 1059 | |
| 1013 | 1060 | fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| ... | ... | @@ -1362,14 +1409,14 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| 1362 | 1409 | |
| 1363 | 1410 | if (config_man) |man| { |
| 1364 | 1411 | if (try man.hit(compile_prog_node)) { |
| 1412 | log.debug("configuration cache hit", .{}); |
| 1365 | 1413 | const digest = man.final(); |
| 1366 | | break :cp .{ |
| 1367 | | .{ |
| 1368 | | .root_dir = graph.local_cache_root, |
| 1369 | | .sub_path = try arena.print("c/{s}", .{&digest}), |
| 1370 | | }, |
| 1371 | | man.toOwnedLock(), |
| 1414 | const path: Path = .{ |
| 1415 | .root_dir = graph.local_cache_root, |
| 1416 | .sub_path = try arena.print("c/{s}", .{&digest}), |
| 1372 | 1417 | }; |
| 1418 | options.src_files.* = man.takeFiles(); |
| 1419 | break :cp .{ path, man.toOwnedLock() }; |
| 1373 | 1420 | } |
| 1374 | 1421 | } |
| 1375 | 1422 | const configure_exe_path: Path = if (std.zig.buildExeSubprocess(gpa, io, .{ |
| ... | ... | @@ -1505,6 +1552,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| 1505 | 1552 | }); |
| 1506 | 1553 | }; |
| 1507 | 1554 | man.writeManifest() catch |err| log.warn("failed to write cache manifest: {t}", .{err}); |
| 1555 | options.src_files.* = man.takeFiles(); |
| 1508 | 1556 | break :cp .{ final_path, man.toOwnedLock() }; |
| 1509 | 1557 | } |
| 1510 | 1558 | }; |
| ... | ... | @@ -2119,7 +2167,13 @@ fn markFailedStepsDirty(maker: *Maker) void { |
| 2119 | 2167 | for (all_steps) |step_index| { |
| 2120 | 2168 | const step = maker.stepByIndex(step_index); |
| 2121 | 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 | 2177 | else => continue, |
| 2124 | 2178 | } |
| 2125 | 2179 | } |
| ... | ... | @@ -2173,7 +2227,11 @@ fn resolveTopLevelSteps(maker: *Maker, step_names: []const []const u8) ![]const |
| 2173 | 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 | 2235 | const gpa = maker.gpa; |
| 2178 | 2236 | const graph = maker.graph; |
| 2179 | 2237 | const arena = graph.arena; |
| ... | ... | @@ -2182,10 +2240,17 @@ fn prepare(maker: *Maker, step_indices: []const Configuration.Step.Index) !void |
| 2182 | 2240 | const step_stack = &maker.step_stack; |
| 2183 | 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 | 2246 | const step_index: Configuration.Step.Index = @fromBackingInt(@intCast(step_index_usize)); |
| 2187 | 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 | 2255 | try initial_steps.ensureUnusedCapacity(gpa, step_indices.len); |
| 2191 | 2256 | try step_stack.ensureUnusedCapacity(gpa, step_indices.len); |
| ... | ... | @@ -3045,14 +3110,15 @@ fn constructGraphAndCheckForDependencyLoop( |
| 3045 | 3110 | /// When file watching, prepares the step for being re-evaluated. Returns |
| 3046 | 3111 | /// `true` if the step was newly invalidated, `false` if it was already |
| 3047 | 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 | 3115 | if (step.state == .precheck_done) return false; |
| 3050 | 3116 | assert(step.pending_deps == 0); |
| 3051 | 3117 | step.state = .precheck_done; |
| 3052 | 3118 | step.reset(maker); |
| 3053 | 3119 | for (step.dependants.items) |dependant_index| { |
| 3054 | 3120 | const dependant = maker.stepByIndex(dependant_index); |
| 3055 | | _ = invalidateResult(maker, dependant); |
| 3121 | _ = try invalidateResult(maker, dependant); |
| 3056 | 3122 | dependant.pending_deps += 1; |
| 3057 | 3123 | } |
| 3058 | 3124 | return true; |