authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-06-24 20:01:56-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-06-29 23:50:20-07:00
loga93d855a0f0d61e57142cee0e1c4b5de8c083cc2
tree3c714d230322d7cae5458bbec259d203d5c2ab41
parentb68192254687e279bef4196cdf0575ed748964f7

Maker: restructure such that configuration can be repeated


5 files changed, 915 insertions(+), 820 deletions(-)

lib/compiler/Maker.zig+742-679
...@@ -52,7 +52,7 @@ max_rss_mutex: Io.Mutex,...@@ -52,7 +52,7 @@ max_rss_mutex: Io.Mutex,
52skip_oom_steps: bool,52skip_oom_steps: bool,
53unit_test_timeout_ns: ?u64,53unit_test_timeout_ns: ?u64,
54watch: bool,54watch: bool,
55web_server: if (!builtin.single_threaded) ?WebServer else ?noreturn,55web_server: ?*AvoidableWebServer,
56/// Allocated into `gpa`.56/// Allocated into `gpa`.
57memory_blocked_steps: std.ArrayList(Configuration.Step.Index),57memory_blocked_steps: std.ArrayList(Configuration.Step.Index),
58/// Allocated into `gpa`.58/// Allocated into `gpa`.
...@@ -68,6 +68,8 @@ var stdio_buffer_allocation: [256]u8 = undefined;...@@ -68,6 +68,8 @@ var stdio_buffer_allocation: [256]u8 = undefined;
68var stdout_writer_allocation: Io.File.Writer = undefined;68var stdout_writer_allocation: Io.File.Writer = undefined;
69var debug_maker_leaks: bool = false;69var debug_maker_leaks: bool = false;
7070
71const AvoidableWebServer = if (builtin.single_threaded) void else WebServer;
72
71const is_debug_mode = builtin.mode == .Debug;73const is_debug_mode = builtin.mode == .Debug;
72const use_safe_allocator = switch (builtin.mode) {74const use_safe_allocator = switch (builtin.mode) {
73 .Debug, .ReleaseSafe => true,75 .Debug, .ReleaseSafe => true,
...@@ -106,6 +108,7 @@ const ErrorStyle = enum {...@@ -106,6 +108,7 @@ const ErrorStyle = enum {
106};108};
107const MultilineErrors = enum { indent, newline, none };109const MultilineErrors = enum { indent, newline, none };
108const Summary = enum { all, new, failures, line, none };110const Summary = enum { all, new, failures, line, none };
111const PrintConfiguration = enum { none, zon, path };
109112
110/// Used to build the -M flags to pass to build-exe.113/// Used to build the -M flags to pass to build-exe.
111pub const CliModule = struct {114pub const CliModule = struct {
...@@ -195,7 +198,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -195,7 +198,7 @@ pub fn main(init: process.Init.Minimal) !void {
195 var step_names: std.ArrayList([]const u8) = .empty;198 var step_names: std.ArrayList([]const u8) = .empty;
196 var help_menu = false;199 var help_menu = false;
197 var steps_menu = false;200 var steps_menu = false;
198 var print_configuration: enum { none, zon, path } = .none;201 var print_configuration: PrintConfiguration = .none;
199 var override_install_prefix: ?[]const u8 = null;202 var override_install_prefix: ?[]const u8 = null;
200 var override_lib_dir: ?[]const u8 = null;203 var override_lib_dir: ?[]const u8 = null;
201 var override_bin_dir: ?[]const u8 = null;204 var override_bin_dir: ?[]const u8 = null;
...@@ -546,6 +549,9 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -546,6 +549,9 @@ pub fn main(init: process.Init.Minimal) !void {
546 }549 }
547 }550 }
548551
552 const early_exit_mode = fetch_only or help_menu or steps_menu or print_configuration != .none;
553 const server_mode = !early_exit_mode and (watch or webui_listen != null or fuzz != null);
554
549 const cwd_path = std.zig.getResolvedCwd(io, arena) catch |err|555 const cwd_path = std.zig.getResolvedCwd(io, arena) catch |err|
550 fatal("resolving current directory path failed: {t}", .{err});556 fatal("resolving current directory path failed: {t}", .{err});
551557
...@@ -593,752 +599,809 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -593,752 +599,809 @@ pub fn main(init: process.Init.Minimal) !void {
593 .off => .no_color,599 .off => .no_color,
594 };600 };
595601
602 const pkg_root: Path = if (override_pkg_dir) |p|
603 .initCwd(p)
604 else if (system_pkg_dir_path) |p|
605 .initCwd(p)
606 else
607 .{
608 .root_dir = build_root.directory,
609 .sub_path = "zig-pkg",
610 };
611
596 const main_progress_node = std.Progress.start(io, .{612 const main_progress_node = std.Progress.start(io, .{
597 .disable_printing = (graph.stderr_mode.? == .no_color),613 .disable_printing = (graph.stderr_mode.? == .no_color),
598 });614 });
599 defer main_progress_node.end();615 defer main_progress_node.end();
600616
601 const scanned_config: ScannedConfig = sc: {617 const install_prefix_path: Path = if (graph.environ_map.get("DESTDIR")) |dest_dir| .{
602 // Cache lookup for configure options. If we get a match, we can skip618 .root_dir = .cwd(),
603 // execution of the configure script. If not, we get the file path to pass619 .sub_path = try Dir.path.join(arena, &.{ dest_dir, override_install_prefix orelse "/usr" }),
604 // to the configure process.620 } else if (override_install_prefix) |cwd_relative| .{
605 //621 .root_dir = .cwd(),
606 // In the hot path, we only check this cache, which means that also622 .sub_path = cwd_relative,
607 // configure source files need to go in here.623 } else .{
608 var config_man = graph.cache.obtain();624 .root_dir = graph.build_root_directory,
609 defer config_man.deinit();625 .sub_path = "zig-out",
610626 };
611 for (cached_passthru_configure.items) |i|627
612 config_man.hash.addBytes(configure_argv.items[i]);628 const install_lib_path: Path = if (override_lib_dir) |cwd_relative| .{
613629 .root_dir = .cwd(),
614 // Prevents a `zig build` from getting a false positive cache hit following630 .sub_path = cwd_relative,
615 // a `zig build --cache-poison=ignored`.631 } else try install_prefix_path.join(arena, "lib");
616 config_man.hash.add(cache_poison == .ignored);632
617633 const install_bin_path: Path = if (override_bin_dir) |cwd_relative| .{
618 const pkg_root: Path = if (override_pkg_dir) |p|634 .root_dir = .cwd(),
619 .initCwd(p)635 .sub_path = cwd_relative,
620 else if (system_pkg_dir_path) |p|636 } else try install_prefix_path.join(arena, "bin");
621 .initCwd(p)637
622 else638 const install_include_path: Path = if (override_include_dir) |cwd_relative| .{
623 .{639 .root_dir = .cwd(),
624 .root_dir = build_root.directory,640 .sub_path = cwd_relative,
625 .sub_path = "zig-pkg",641 } else try install_prefix_path.join(arena, "include");
642
643 const now = Io.Clock.Timestamp.now(io, .awake);
644
645 var web_server_allocation: AvoidableWebServer = undefined;
646 const web_server: ?*AvoidableWebServer = if (webui_listen) |listen_address| ws: {
647 if (builtin.single_threaded) fatal("--webui is not yet supported on single-threaded hosts", .{});
648 web_server_allocation = .init(.{
649 .graph = &graph,
650 .root_prog_node = main_progress_node,
651 .listen_address = listen_address,
652 .base_timestamp = now,
653 });
654 web_server_allocation.start() catch |err| fatal("failed to start web server: {t}", .{err});
655 break :ws &web_server_allocation;
656 } else null;
657
658 while (true) {
659 // If this fails, we can still start the server and wait for user
660 // to request a rebuild. If it returns error.FailedButCacheIntact
661 // we can even still do file system watching and automatically
662 // rebuild on source changes.
663 if (configure(&graph, .{
664 .configure_argv = configure_argv.items,
665 .conf_argv_index_build_root = conf_argv_index_build_root,
666 .cached_passthru_configure = cached_passthru_configure.items,
667
668 .cache_poison = cache_poison,
669 .pkg_root = pkg_root,
670 .build_root = build_root,
671 .cwd_path = cwd_path,
672 .color = color,
673 .debug_target = debug_target,
674 .parent_progress_node = main_progress_node,
675 .fetch_mode = fetch_mode,
676 .system_pkg_dir_path = system_pkg_dir_path,
677 .fetch_only = fetch_only,
678 .print_configuration = print_configuration,
679 .forks = forks.items,
680 })) |scanned_config| {
681 if (help_menu) {
682 scanned_config.printUsage(&graph, initStdoutWriter(io)) catch |err| switch (err) {
683 error.WriteFailed => return stdout_writer_allocation.err.?,
684 else => |e| return e,
685 };
686 try stdout_writer_allocation.flush();
687 return cleanExit(io, &scanned_config);
688 } else if (steps_menu) {
689 scanned_config.printSteps(&graph, initStdoutWriter(io)) catch |err| switch (err) {
690 error.WriteFailed => return stdout_writer_allocation.err.?,
691 else => |e| return e,
692 };
693 try stdout_writer_allocation.flush();
694 return cleanExit(io, &scanned_config);
695 } else switch (print_configuration) {
696 .none => {},
697 .zon => {
698 scanned_config.print(initStdoutWriter(io)) catch return stdout_writer_allocation.err.?;
699 try stdout_writer_allocation.flush();
700 return cleanExit(io, &scanned_config);
701 },
702 .path => unreachable,
703 }
704
705 var maker: Maker = .{
706 .gpa = gpa,
707 .graph = &graph,
708 .scanned_config = &scanned_config,
709 .install_paths = .{
710 .prefix = install_prefix_path,
711 .lib = install_lib_path,
712 .bin = install_bin_path,
713 .include = install_include_path,
714 },
715
716 .steps = try arena.alloc(Step, scanned_config.configuration.steps.len),
717 .generated_files = try arena.alloc(Path, scanned_config.configuration.generated_files_len),
718 .run_args = run_args,
719
720 .available_rss = max_rss,
721 .max_rss_is_default = false,
722 .max_rss_mutex = .init,
723 .skip_oom_steps = skip_oom_steps,
724 .unit_test_timeout_ns = test_timeout_ns,
725
726 .watch = watch,
727 .web_server = undefined, // set after `prepare`
728 .memory_blocked_steps = .empty,
729 .step_stack = .empty,
730 .pkg_config = .{ .debug = debug_pkg_config },
731
732 .error_style = error_style,
733 .multiline_errors = multiline_errors,
734 .summary = summary orelse if (watch or webui_listen != null) .new else .failures,
735 };
736 defer {
737 maker.memory_blocked_steps.deinit(gpa);
738 maker.step_stack.deinit(gpa);
739 }
740
741 if (maker.available_rss == 0) {
742 maker.available_rss = process.totalSystemMemory() catch std.math.maxInt(u64);
743 maker.max_rss_is_default = true;
744 }
745
746 maker.prepare(step_names.items) catch |err| switch (err) {
747 error.DependencyLoopDetected, error.InsufficientMemory => {
748 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
749 process.exit(1);
750 },
751 else => |e| return e,
752 };
753
754 var w: Watch = w: {
755 if (!watch) break :w undefined;
756 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{native_os});
757 break :w try .init(&maker);
626 };758 };
627759
628 configure_argv.items[conf_argv_index_build_root] = build_root.directory.path orelse cwd_path;760 if (web_server) |ws| try ws.updateConfiguration(&maker);
629761
630 var http_client: std.http.Client = .{ .allocator = gpa, .io = io };762 rebuild: while (true) : (if (maker.error_style.clearOnUpdate()) {
631 defer http_client.deinit();763 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
764 defer io.unlockStderr();
765 stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H") catch |err| switch (err) {
766 error.WriteFailed => return stderr.file_writer.err.?,
767 };
768 }) {
769 if (web_server) |ws| ws.startBuild();
632770
633 var unlazy_set: Package.Fetch.JobQueue.UnlazySet = .{};771 try maker.makeStepNames(step_names.items, main_progress_node, fuzz);
634 var fork_set: Package.Fetch.JobQueue.ForkSet = .{};
635772
636 {773 if (web_server) |ws| {
637 // Populate fork_set.774 if (fuzz) |mode| if (mode != .forever) fatal(
638 var group: Io.Group = .init;775 "error: limited fuzzing is not implemented yet for --webui",
639 defer group.cancel(io);776 .{},
640777 );
641 for (forks.items) |*fork|778
642 group.async(io, Fork.load, .{ io, gpa, fork, color });779 ws.finishBuild(.{ .fuzz = fuzz != null });
643780 }
644 try group.await(io);781
645782 if (web_server) |ws| {
646 for (forks.items) |*fork| {783 const c = &scanned_config.configuration;
647 if (fork.failed) process.exit(1);784 assert(!watch); // fatal error after CLI parsing
648 try fork_set.put(arena, .{785 while (true) switch (try ws.wait()) {
649 .path = fork.path,786 .rebuild => {
650 .manifest_ast = fork.manifest_ast,787 for (maker.step_stack.keys()) |step_index| {
651 .manifest = fork.manifest,788 const step = maker.stepByIndex(step_index);
652 .uses = 0,789 step.state = .precheck_done;
653 }, {});790 const deps = step_index.ptr(c).deps.slice(c);
791 step.pending_deps = @intCast(deps.len);
792 step.reset(&maker);
793 }
794 continue :rebuild;
795 },
796 };
797 }
798
799 if (!maker.watch) return;
800
801 // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.
802 if (!Watch.have_impl) unreachable;
803
804 try w.update(maker.step_stack.keys());
805
806 // Wait until a file system notification arrives. Read all such events
807 // until the buffer is empty. Then wait for a debounce interval, resetting
808 // if any more events come in. After the debounce interval has passed,
809 // trigger a rebuild on all steps with modified inputs, as well as their
810 // recursive dependants.
811 var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined;
812 const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{
813 w.dir_count, countSubProcesses(&maker),
814 }) catch &caption_buf;
815 var debouncing_node = main_progress_node.start(caption, 0);
816 var in_debounce = false;
817 while (true) switch (try w.wait(if (in_debounce) .{ .ms = debounce_interval_ms } else .none)) {
818 .timeout => {
819 assert(in_debounce);
820 debouncing_node.end();
821 markFailedStepsDirty(&maker);
822 continue :rebuild;
823 },
824 .dirty => if (!in_debounce) {
825 in_debounce = true;
826 debouncing_node.end();
827 debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0);
828 },
829 .clean => {},
830 };
831 }
832 } else |err| {
833 const can_fs_watch = switch (err) {
834 error.AlreadyReported => false,
835 error.FailedButCacheIntact => true,
836 else => |e| w: {
837 log.err("configuration failed: {t}", .{e});
838 break :w false;
839 },
840 };
841 if (!server_mode) process.exit(1);
842 if (can_fs_watch) {
843 @panic("TODO set up fs watching");
844 } else {
845 @panic("TODO wait for user to request rebuild");
654 }846 }
655 }847 }
656 defer Fork.deinitList(forks.items);848 }
849}
657850
658 var build_configurer_argv: std.ArrayList([]const u8) = .empty;851const ConfigureOptions = struct {
659 defer build_configurer_argv.deinit(gpa);852 configure_argv: [][]const u8,
853 conf_argv_index_build_root: usize,
854 cached_passthru_configure: []const u32,
660855
661 var dependencies_source: std.ArrayList(u8) = .empty;856 cache_poison: std.Build.Graph.CachePoison,
662 defer dependencies_source.deinit(gpa);857 pkg_root: Path,
858 build_root: BuildRoot,
859 cwd_path: []const u8,
860 color: Color,
861 debug_target: ?[]const u8,
862 parent_progress_node: std.Progress.Node,
863 fetch_mode: Fetch.JobQueue.Mode,
864 system_pkg_dir_path: ?[]const u8,
865 fetch_only: bool,
866 print_configuration: PrintConfiguration,
867 forks: []Fork,
868};
663869
664 const configurer_root_src_path: Cache.Path = .{870fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
665 .root_dir = graph.zig_lib_directory,871 const configure_argv = options.configure_argv;
666 .sub_path = "compiler/configurer.zig",872 const gpa = graph.cache.gpa;
667 };873 const io = graph.io;
874 const arena = graph.arena;
668875
669 const root_build_src_path: Cache.Path = .{876 // Cache lookup for configure options. If we get a match, we can skip
670 .root_dir = build_root.directory,877 // execution of the configure script. If not, we get the file path to pass
671 .sub_path = build_root.build_zig_basename,878 // to the configure process.
672 };879 //
880 // In the hot path, we only check this cache, which means that also
881 // configure source files need to go in here.
882 var config_man = graph.cache.obtain();
883 defer config_man.deinit();
673884
674 const configurer_exe_name = "configurer";885 for (options.cached_passthru_configure) |i|
886 config_man.hash.addBytes(configure_argv[i]);
675887
676 try build_configurer_argv.appendSlice(gpa, &.{888 // Prevents a `zig build` from getting a false positive cache hit following
677 graph.zig_exe, "build-exe", //889 // a `zig build --cache-poison=ignored`.
678 "--cache-dir", graph.local_cache_root.path orelse ".", //890 config_man.hash.add(options.cache_poison == .ignored);
679 "--global-cache-dir", graph.global_cache_root.path orelse ".", //
680 "--zig-lib-dir", graph.zig_lib_directory.path orelse ".", //
681 "--name", configurer_exe_name, //
682 "-fsingle-threaded", //
683 });
684891
685 // Normally the build runner is compiled for the host target but here is892 configure_argv[options.conf_argv_index_build_root] = options.build_root.directory.path orelse options.cwd_path;
686 // some code to help when debugging edits to the build runner so that you893
687 // can make sure it compiles successfully on other targets.894 var http_client: std.http.Client = .{ .allocator = gpa, .io = io };
688 const target_arch_os_abi: ?[]const u8 = if (debug_target) |triple| t: {895 defer http_client.deinit();
689 config_man.hash.addBytes(triple);896
690 try build_configurer_argv.appendSlice(gpa, &.{ "-target", triple });897 var unlazy_set: Package.Fetch.JobQueue.UnlazySet = .{};
691 break :t triple;898 var fork_set: Package.Fetch.JobQueue.ForkSet = .{};
692 } else null;899
693900 {
694 if (graph.libc_file) |libc_file| {901 // Populate fork_set.
695 try build_configurer_argv.appendSlice(gpa, &.{ "--libc", libc_file });902 var group: Io.Group = .init;
696 }903 defer group.cancel(io);
697 if (graph.reference_trace) |n| {904
698 try build_configurer_argv.append(gpa, try allocPrint(arena, "-freference-trace={d}", .{n}));905 for (options.forks) |*fork|
699 }906 group.async(io, Fork.load, .{ io, gpa, fork, options.color });
700 if (graph.debug_compile_errors) {907
701 try build_configurer_argv.append(gpa, "--debug-compile-errors");908 try group.await(io);
909
910 for (options.forks) |*fork| {
911 if (fork.failed) process.exit(1);
912 try fork_set.put(arena, .{
913 .path = fork.path,
914 .manifest_ast = fork.manifest_ast,
915 .manifest = fork.manifest,
916 .uses = 0,
917 }, {});
702 }918 }
703 try build_configurer_argv.appendSlice(gpa, &.{919 }
704 "--dep", "@build", //920 defer Fork.deinitList(options.forks);
705 "--dep", "@dependencies", //
706 try allocPrint(arena, "-Mroot={f}", .{configurer_root_src_path}), //
707 });
708921
709 // In the loop below, after doing the fetch operation, the argv will be922 var build_configurer_argv: std.ArrayList([]const u8) = .empty;
710 // truncated at this point, dependencies added, and then the923 defer build_configurer_argv.deinit(gpa);
711 // "--listen=-" arg appended at the end.
712 const argv_deps_index = build_configurer_argv.items.len;
713924
714 const build_mod = try arena.create(CliModule);925 var dependencies_source: std.ArrayList(u8) = .empty;
715 build_mod.* = .{926 defer dependencies_source.deinit(gpa);
716 .name = "@build",
717 .root_path = try root_build_src_path.toString(arena),
718 };
719927
720 const deps_mod = try arena.create(CliModule);928 const configurer_root_src_path: Cache.Path = .{
721 deps_mod.* = .{929 .root_dir = graph.zig_lib_directory,
722 .name = "@dependencies",930 .sub_path = "compiler/configurer.zig",
723 .root_path = undefined,931 };
724 };
725932
726 // This loop is re-evaluated when the build script exits with an indication that it933 const root_build_src_path: Cache.Path = .{
727 // could not continue due to missing lazy dependencies.934 .root_dir = options.build_root.directory,
728 const configuration_path: Path, const poisoned: bool = cp: while (true) {935 .sub_path = options.build_root.build_zig_basename,
729 build_mod.deps.clearRetainingCapacity();936 };
730 deps_mod.deps.clearRetainingCapacity();
731937
732 // We want to release all the locks before executing the child process, so we make a nice938 const configurer_exe_name = "configurer";
733 // big block here to ensure the cleanup gets run when we extract out our argv.
734 {
735 {
736 const fetch_prog_node = main_progress_node.start("Fetch Packages", 0);
737 defer fetch_prog_node.end();
738
739 // Reset fork match counts.
740 for (fork_set.keys()) |*fork| fork.uses = 0;
741
742 var job_queue: Package.Fetch.JobQueue = .{
743 .io = io,
744 .http_client = &http_client,
745 .global_cache = graph.global_cache_root,
746 .local_storage = &.{
747 .cache_root = .{ .root_dir = graph.local_cache_root },
748 .pkg_root = pkg_root,
749 },
750 .recursive = true,
751 .debug_hash = false,
752 .unlazy_set = unlazy_set,
753 .fork_set = fork_set,
754 .mode = fetch_mode,
755 .prog_node = fetch_prog_node,
756 .read_only = system_pkg_dir_path != null,
757 };
758 defer job_queue.deinit();
759939
760 if (system_pkg_dir_path == null) {940 try build_configurer_argv.appendSlice(gpa, &.{
761 try http_client.initDefaultProxies(arena, &graph.environ_map);941 graph.zig_exe, "build-exe", //
762 }942 "--cache-dir", graph.local_cache_root.path orelse ".", //
943 "--global-cache-dir", graph.global_cache_root.path orelse ".", //
944 "--zig-lib-dir", graph.zig_lib_directory.path orelse ".", //
945 "--name", configurer_exe_name, //
946 "-fsingle-threaded", //
947 });
763948
764 try job_queue.all_fetches.ensureUnusedCapacity(gpa, 1);949 // Normally the build runner is compiled for the host target but here is
765 try job_queue.table.ensureUnusedCapacity(gpa, 1);950 // some code to help when debugging edits to the build runner so that you
766951 // can make sure it compiles successfully on other targets.
767 const phantom_package_root: Cache.Path = .{ .root_dir = build_root.directory };952 const target_arch_os_abi: ?[]const u8 = if (options.debug_target) |triple| t: {
768953 config_man.hash.addBytes(triple);
769 var fetch: Package.Fetch = .{954 try build_configurer_argv.appendSlice(gpa, &.{ "-target", triple });
770 .arena = std.heap.ArenaAllocator.init(gpa),955 break :t triple;
771 .location = .{ .relative_path = phantom_package_root },956 } else null;
772 .location_tok = 0,
773 .hash_tok = .none,
774 .name_tok = 0,
775 .lazy_status = .eager,
776 .remote_package_root = phantom_package_root,
777 .parent_package_root = phantom_package_root,
778 .parent_manifest_ast = null,
779 .prog_node = fetch_prog_node,
780 .job_queue = &job_queue,
781 .omit_missing_hash_error = true,
782 .allow_missing_paths_field = false,
783 .use_latest_commit = false,
784
785 .package_root = undefined,
786 .error_bundle = undefined,
787 .manifest = undefined,
788 .manifest_ast = undefined,
789 .have_manifest = false,
790 .computed_hash = undefined,
791 .has_build_zig = true,
792 .oom_flag = false,
793 .latest_commit = null,
794
795 .cli_module = build_mod,
796 };
797957
798 job_queue.all_fetches.appendAssumeCapacity(&fetch);958 if (graph.libc_file) |libc_file| {
959 try build_configurer_argv.appendSlice(gpa, &.{ "--libc", libc_file });
960 }
961 if (graph.reference_trace) |n| {
962 try build_configurer_argv.append(gpa, try allocPrint(arena, "-freference-trace={d}", .{n}));
963 }
964 if (graph.debug_compile_errors) {
965 try build_configurer_argv.append(gpa, "--debug-compile-errors");
966 }
967 try build_configurer_argv.appendSlice(gpa, &.{
968 "--dep", "@build", //
969 "--dep", "@dependencies", //
970 try allocPrint(arena, "-Mroot={f}", .{configurer_root_src_path}), //
971 });
799972
800 job_queue.table.putAssumeCapacityNoClobber(973 // In the loop below, after doing the fetch operation, the argv will be
801 Package.Fetch.relativePathDigest(phantom_package_root, graph.global_cache_root),974 // truncated at this point, dependencies added, and then the
802 &fetch,975 // "--listen=-" arg appended at the end.
803 );976 const argv_deps_index = build_configurer_argv.items.len;
804977
805 job_queue.group.async(io, Package.Fetch.workerRun, .{ &fetch, "root" });978 const build_mod = try arena.create(CliModule);
806 try job_queue.group.await(io);979 build_mod.* = .{
980 .name = "@build",
981 .root_path = try root_build_src_path.toString(arena),
982 };
807983
808 {984 const deps_mod = try arena.create(CliModule);
809 // Ensure that forks were actually used. This is done985 deps_mod.* = .{
810 // before printing manifest errors because using a fork can986 .name = "@dependencies",
811 // prevent them.987 .root_path = undefined,
812 var any_unused = false;988 };
813 for (fork_set.keys()) |*fork| {
814 if (fork.uses == 0) {
815 log.err("fork {f} matched no {s} packages", .{
816 fork.path, fork.manifest.name,
817 });
818 any_unused = true;
819 } else {
820 log.info("fork {f} matched {d} {s} packages", .{
821 fork.path, fork.uses, fork.manifest.name,
822 });
823 }
824 }
825 if (any_unused) process.exit(1);
826 }
827989
828 try job_queue.consolidateErrors();990 // This loop is re-evaluated when the build script exits with an indication that it
991 // could not continue due to missing lazy dependencies.
992 const configuration_path: Path, const poisoned: bool = cp: while (true) {
993 build_mod.deps.clearRetainingCapacity();
994 deps_mod.deps.clearRetainingCapacity();
829995
830 if (fetch.error_bundle.root_list.items.len > 0) {996 // We want to release all the locks before executing the child process, so we make a nice
831 var errors = try fetch.error_bundle.toOwnedBundle("");997 // big block here to ensure the cleanup gets run when we extract out our argv.
832 // TODO when watching, watch and rebuild configure script rather than exit here998 {
833 errors.renderToStderr(io, .{}, color) catch {};999 {
834 process.exit(1);1000 const fetch_prog_node = options.parent_progress_node.start("Fetch Packages", 0);
835 }1001 defer fetch_prog_node.end();
1002
1003 // Reset fork match counts.
1004 for (fork_set.keys()) |*fork| fork.uses = 0;
1005
1006 var job_queue: Package.Fetch.JobQueue = .{
1007 .io = io,
1008 .http_client = &http_client,
1009 .global_cache = graph.global_cache_root,
1010 .local_storage = &.{
1011 .cache_root = .{ .root_dir = graph.local_cache_root },
1012 .pkg_root = options.pkg_root,
1013 },
1014 .recursive = true,
1015 .debug_hash = false,
1016 .unlazy_set = unlazy_set,
1017 .fork_set = fork_set,
1018 .mode = options.fetch_mode,
1019 .prog_node = fetch_prog_node,
1020 .read_only = options.system_pkg_dir_path != null,
1021 };
1022 defer job_queue.deinit();
8361023
837 if (fetch_only) return process.cleanExit(io);1024 if (options.system_pkg_dir_path == null) {
1025 try http_client.initDefaultProxies(arena, &graph.environ_map);
1026 }
8381027
839 // Create the dependencies.zig file for configurer to1028 try job_queue.all_fetches.ensureUnusedCapacity(gpa, 1);
840 // obtain via `@import("@dependencies")`.1029 try job_queue.table.ensureUnusedCapacity(gpa, 1);
841 {1030
842 {1031 const phantom_package_root: Cache.Path = .{ .root_dir = options.build_root.directory };
843 dependencies_source.clearRetainingCapacity();1032
844 var source_writer: Io.Writer.Allocating = .fromArrayList(gpa, &dependencies_source);1033 var fetch: Package.Fetch = .{
845 defer dependencies_source = source_writer.toArrayList();1034 .arena = std.heap.ArenaAllocator.init(gpa),
846 job_queue.createDependenciesSource(&source_writer.writer) catch |err| switch (err) {1035 .location = .{ .relative_path = phantom_package_root },
847 error.WriteFailed => return error.OutOfMemory,1036 .location_tok = 0,
848 };1037 .hash_tok = .none,
849 }1038 .name_tok = 0,
850 // Atomically create the file in a directory named after the hash of its contents.1039 .lazy_status = .eager,
851 var hh: Cache.HashHelper = .{};1040 .remote_package_root = phantom_package_root,
852 hh.addBytes(builtin.zig_version_string);1041 .parent_package_root = phantom_package_root,
853 hh.addBytes(dependencies_source.items);1042 .parent_manifest_ast = null,
854 const hex_digest = hh.final();1043 .prog_node = fetch_prog_node,
855 const dependencies_zig_path: Path = .{1044 .job_queue = &job_queue,
856 .root_dir = graph.local_cache_root,1045 .omit_missing_hash_error = true,
857 .sub_path = try allocPrint(arena, "o/{s}/dependencies.zig", .{&hex_digest}),1046 .allow_missing_paths_field = false,
858 };1047 .use_latest_commit = false,
859 var atomic_file = try dependencies_zig_path.root_dir.handle.createFileAtomic(1048
860 io,1049 .package_root = undefined,
861 dependencies_zig_path.sub_path,1050 .error_bundle = undefined,
862 .{ .make_path = true, .replace = true },1051 .manifest = undefined,
863 );1052 .manifest_ast = undefined,
864 defer atomic_file.deinit(io);1053 .have_manifest = false,
865 atomic_file.file.writeStreamingAll(io, dependencies_source.items) catch |err|1054 .computed_hash = undefined,
866 fatal("writing dependencies.zig contents: {t}", .{err});1055 .has_build_zig = true,
867 atomic_file.replace(io) catch |err|1056 .oom_flag = false,
868 fatal("replacing {f}: {t}", .{ dependencies_zig_path, err });1057 .latest_commit = null,
8691058
870 deps_mod.root_path = try dependencies_zig_path.toString(arena);1059 .cli_module = build_mod,
871 }1060 };
8721061
873 {1062 job_queue.all_fetches.appendAssumeCapacity(&fetch);
874 // Add a CliModule for each package's build.zig.
875 const hashes = job_queue.table.keys();
876 const fetches = job_queue.table.values();
877 try deps_mod.deps.ensureUnusedCapacity(arena, @intCast(hashes.len));
878 for (hashes, fetches) |*hash, f| {
879 if (f == &fetch) {
880 // The first one is a dummy package for the current project.
881 continue;
882 }
883 if (!f.has_build_zig)
884 continue;
885 const hash_slice = try arena.dupe(u8, hash.toSlice());
886
887 const m = try arena.create(CliModule);
888 m.* = .{
889 .root_path = try f.package_root.toString(arena),
890 .name = hash_slice,
891 };
892 deps_mod.deps.putAssumeCapacityNoClobber(hash_slice, m);
893 f.cli_module = m;
894 }
8951063
896 // Each build.zig module needs access to each of its1064 job_queue.table.putAssumeCapacityNoClobber(
897 // dependencies' build.zig modules by name.1065 Package.Fetch.relativePathDigest(phantom_package_root, graph.global_cache_root),
898 for (fetches) |f| {1066 &fetch,
899 const mod = f.cli_module orelse continue;1067 );
900 if (!f.have_manifest) continue;
901 const man = &f.manifest;
902 const dep_names = man.dependencies.keys();
903 try mod.deps.ensureUnusedCapacity(arena, @intCast(dep_names.len));
904 for (dep_names, man.dependencies.values()) |name, dep| {
905 const dep_digest = Package.Fetch.depDigest(
906 f.package_root,
907 global_cache_directory,
908 dep,
909 ) orelse continue;
910 const dep_mod = job_queue.table.get(dep_digest).?.cli_module orelse continue;
911 const name_cloned = try arena.dupe(u8, name);
912 mod.deps.putAssumeCapacityNoClobber(name_cloned, dep_mod);
913 }
914 }
915 }
9161068
917 // Lower module dependencies to CLI argv.1069 job_queue.group.async(io, Package.Fetch.workerRun, .{ &fetch, "root" });
918 build_configurer_argv.shrinkRetainingCapacity(argv_deps_index);1070 try job_queue.group.await(io);
919 for (deps_mod.deps.values()) |dep| {1071
920 try build_configurer_argv.ensureUnusedCapacity(gpa, 2 * dep.deps.count() + 1);1072 {
921 for (dep.deps.keys(), dep.deps.values()) |name, sub| {1073 // Ensure that forks were actually used. This is done
922 build_configurer_argv.appendAssumeCapacity("--dep");1074 // before printing manifest errors because using a fork can
923 if (mem.eql(u8, name, sub.name)) {1075 // prevent them.
924 build_configurer_argv.appendAssumeCapacity(sub.name);1076 var any_unused = false;
925 } else {1077 for (fork_set.keys()) |*fork| {
926 build_configurer_argv.appendAssumeCapacity(try allocPrint(arena, "{s}={s}", .{1078 if (fork.uses == 0) {
927 name, sub.name,1079 log.err("fork {f} matched no {s} packages", .{
928 }));1080 fork.path, fork.manifest.name,
929 }1081 });
1082 any_unused = true;
1083 } else {
1084 log.info("fork {f} matched {d} {s} packages", .{
1085 fork.path, fork.uses, fork.manifest.name,
1086 });
930 }1087 }
931 build_configurer_argv.appendAssumeCapacity(try allocPrint(arena, "-M{s}={s}/{s}", .{
932 dep.name, dep.root_path, std.zig.build_zig_basename,
933 }));
934 }1088 }
935 try deps_mod.lower(arena, gpa, &build_configurer_argv);1089 if (any_unused) process.exit(1);
936 try build_mod.lower(arena, gpa, &build_configurer_argv);1090 }
1091
1092 try job_queue.consolidateErrors();
9371093
938 try build_configurer_argv.append(gpa, "--listen=-");1094 if (fetch.error_bundle.root_list.items.len > 0) {
1095 var errors = try fetch.error_bundle.toOwnedBundle("");
1096 errors.renderToStderr(io, .{}, options.color) catch process.exit(1);
1097 return error.FailedButCacheIntact;
939 }1098 }
9401099
941 const compile_prog_node = main_progress_node.start("Compile Configure Script", 0);1100 if (options.fetch_only) {
942 defer compile_prog_node.end();1101 _ = io.lockStderr(&.{}, .no_color) catch {};
9431102 process.exit(0);
944 switch (cache_poison) {
945 .pure, .disallowed, .ignored => if (try config_man.hit()) {
946 const digest = config_man.final();
947 break :cp .{
948 .{
949 .root_dir = graph.local_cache_root,
950 .sub_path = try allocPrint(arena, "c/{s}", .{&digest}),
951 },
952 false,
953 };
954 },
955 .poisoned => {}, // Don't bother checking for cache hit.
956 }1103 }
9571104
958 const configure_exe_path: Path = if (std.zig.buildExeSubprocess(gpa, io, .{1105 // Create the dependencies.zig file for configurer to
959 .argv = build_configurer_argv.items,1106 // obtain via `@import("@dependencies")`.
960 .cache_root = graph.local_cache_root,1107 {
961 .root_name = configurer_exe_name,1108 {
962 .environ_map = &graph.environ_map,1109 dependencies_source.clearRetainingCapacity();
963 .cache_manifest = &config_man,1110 var source_writer: Io.Writer.Allocating = .fromArrayList(gpa, &dependencies_source);
964 .arch_os_abi = target_arch_os_abi,1111 defer dependencies_source = source_writer.toArrayList();
965 .progress_node = compile_prog_node,1112 job_queue.createDependenciesSource(&source_writer.writer) catch |err| switch (err) {
966 })) |r| r.path else |err| switch (err) {1113 error.WriteFailed => return error.OutOfMemory,
967 error.AlreadyReported => process.exit(1),1114 };
968 // If the file system inputs are populated, we can1115 }
969 // still watch for changes and try again.1116 // Atomically create the file in a directory named after the hash of its contents.
970 error.FailedButCacheIntact => @panic("TODO"),1117 var hh: Cache.HashHelper = .{};
971 error.Canceled, error.OutOfMemory => |e| return e,1118 hh.addBytes(builtin.zig_version_string);
972 };1119 hh.addBytes(dependencies_source.items);
973 defer gpa.free(configure_exe_path.sub_path);1120 const hex_digest = hh.final();
1121 const dependencies_zig_path: Path = .{
1122 .root_dir = graph.local_cache_root,
1123 .sub_path = try allocPrint(arena, "o/{s}/dependencies.zig", .{&hex_digest}),
1124 };
1125 var atomic_file = try dependencies_zig_path.root_dir.handle.createFileAtomic(
1126 io,
1127 dependencies_zig_path.sub_path,
1128 .{ .make_path = true, .replace = true },
1129 );
1130 defer atomic_file.deinit(io);
1131 atomic_file.file.writeStreamingAll(io, dependencies_source.items) catch |err|
1132 fatal("writing dependencies.zig contents: {t}", .{err});
1133 atomic_file.replace(io) catch |err|
1134 fatal("replacing {f}: {t}", .{ dependencies_zig_path, err });
9741135
975 configure_argv.items[0] = try configure_exe_path.toString(arena);1136 deps_mod.root_path = try dependencies_zig_path.toString(arena);
976 }1137 }
9771138
978 if (!process.can_spawn) {1139 {
979 fatal("cannot spawn command on {t}: {f}", .{ native_os, @as(std.zig.SubprocessCommand, .{1140 // Add a CliModule for each package's build.zig.
980 .argv = configure_argv.items,1141 const hashes = job_queue.table.keys();
981 }) });1142 const fetches = job_queue.table.values();
982 }1143 try deps_mod.deps.ensureUnusedCapacity(arena, @intCast(hashes.len));
1144 for (hashes, fetches) |*hash, f| {
1145 if (f == &fetch) {
1146 // The first one is a dummy package for the current project.
1147 continue;
1148 }
1149 if (!f.has_build_zig)
1150 continue;
1151 const hash_slice = try arena.dupe(u8, hash.toSlice());
1152
1153 const m = try arena.create(CliModule);
1154 m.* = .{
1155 .root_path = try f.package_root.toString(arena),
1156 .name = hash_slice,
1157 };
1158 deps_mod.deps.putAssumeCapacityNoClobber(hash_slice, m);
1159 f.cli_module = m;
1160 }
9831161
984 const rand_int = randInt(io, u64);1162 // Each build.zig module needs access to each of its
985 const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);1163 // dependencies' build.zig modules by name.
986 const config_tmp_path: Path = .{1164 for (fetches) |f| {
987 .root_dir = graph.local_cache_root,1165 const mod = f.cli_module orelse continue;
988 .sub_path = tmp_dir_sub_path,1166 if (!f.have_manifest) continue;
989 };1167 const man = &f.manifest;
990 const config_tmp_file: Io.File = try config_tmp_path.root_dir.handle.createFile(1168 const dep_names = man.dependencies.keys();
991 io,1169 try mod.deps.ensureUnusedCapacity(arena, @intCast(dep_names.len));
992 config_tmp_path.sub_path,1170 for (dep_names, man.dependencies.values()) |name, dep| {
993 .{ .read = true, .exclusive = true },1171 const dep_digest = Package.Fetch.depDigest(
994 );1172 f.package_root,
995 defer config_tmp_file.close(io);1173 graph.global_cache_root,
9961174 dep,
997 const term = term: {1175 ) orelse continue;
998 const child_node = main_progress_node.start("Run Configure Script", 0);1176 const dep_mod = job_queue.table.get(dep_digest).?.cli_module orelse continue;
999 defer child_node.end();1177 const name_cloned = try arena.dupe(u8, name);
1000 var child = process.spawn(io, .{1178 mod.deps.putAssumeCapacityNoClobber(name_cloned, dep_mod);
1001 .argv = configure_argv.items,1179 }
1002 .stdout = .{ .file = config_tmp_file },
1003 .progress_node = child_node,
1004 }) catch |err| fatal("failed to spawn configure script {q}: {t}", .{ configure_argv.items[0], err });
1005 defer child.kill(io);
1006 break :term child.wait(io) catch |err|
1007 fatal("failed to wait configure script {q}: {t}", .{ configure_argv.items[0], err });
1008 };
1009 if (!term.success()) {
1010 // Failure to produce the configuration file.
1011 fatal("configure command {f}: {f}", .{ term, @as(std.zig.SubprocessCommand, .{
1012 .argv = configure_argv.items,
1013 }) });
1014 }
1015 // Even though the file is designed to be sent directly to make
1016 // runner, we must load it now because:
1017 // * If it contains additional file dependencies, we need to
1018 // add them to `config_man` before obtaining the final digest.
1019 // * If it contains a set of lazy packages that need to be
1020 // fetched, we need to fetch those now and re-run configure.
1021 var configuration = Configuration.loadFile(arena, io, config_tmp_file) catch |err|
1022 fatal("failed to load configuration file {f}: {t}", .{ config_tmp_path, err });
1023
1024 if (configuration.unlazy_deps.len != 0) {
1025 var any_errors = false;
1026 for (configuration.unlazy_deps) |hash_string| {
1027 const hash = hash_string.slice(&configuration);
1028 assert(hash.len != 0);
1029 if (hash.len > Package.Hash.max_len) {
1030 log.err("invalid digest (length {d} exceeds maximum): {q}", .{ hash.len, hash });
1031 any_errors = true;
1032 continue;
1033 }1180 }
1034 try unlazy_set.put(arena, .fromSlice(hash), {});
1035 }1181 }
1036 if (any_errors) process.exit(1);1182
1037 if (system_pkg_dir_path) |p| {1183 // Lower module dependencies to CLI argv.
1038 // In this mode, the system needs to provide these packages; they1184 build_configurer_argv.shrinkRetainingCapacity(argv_deps_index);
1039 // cannot be fetched by Zig.1185 for (deps_mod.deps.values()) |dep| {
1040 const s = Dir.path.sep_str;1186 try build_configurer_argv.ensureUnusedCapacity(gpa, 2 * dep.deps.count() + 1);
1041 for (unlazy_set.keys()) |*hash| {1187 for (dep.deps.keys(), dep.deps.values()) |name, sub| {
1042 log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{ p, hash.toSlice() });1188 build_configurer_argv.appendAssumeCapacity("--dep");
1189 if (mem.eql(u8, name, sub.name)) {
1190 build_configurer_argv.appendAssumeCapacity(sub.name);
1191 } else {
1192 build_configurer_argv.appendAssumeCapacity(try allocPrint(arena, "{s}={s}", .{
1193 name, sub.name,
1194 }));
1195 }
1043 }1196 }
1044 log.info("remote package fetching disabled due to --system mode", .{});1197 build_configurer_argv.appendAssumeCapacity(try allocPrint(arena, "-M{s}={s}/{s}", .{
1045 log.info("dependencies might be avoidable depending on build configuration", .{});1198 dep.name, dep.root_path, std.zig.build_zig_basename,
1046 process.exit(1);1199 }));
1047 }1200 }
1048 continue :cp;1201 try deps_mod.lower(arena, gpa, &build_configurer_argv);
1049 }1202 try build_mod.lower(arena, gpa, &build_configurer_argv);
10501203
1051 for (configuration.path_deps) |path_dep| {1204 try build_configurer_argv.append(gpa, "--listen=-");
1052 try config_man.addPathPost(path_dep.toCachePath(&configuration, arena));
1053 }1205 }
10541206
1055 // If it is poisoned, there is no point in moving it to cached1207 const compile_prog_node = options.parent_progress_node.start("Compile Configure Script", 0);
1056 // location. Just leave it in the tmp directory.1208 defer compile_prog_node.end();
1057 if (configuration.poisoned) {1209
1058 break :cp .{ config_tmp_path, true };1210 switch (options.cache_poison) {
1059 } else {1211 .pure, .disallowed, .ignored => if (try config_man.hit()) {
1060 const digest = config_man.final();1212 const digest = config_man.final();
1061 const final_path: Path = .{1213 break :cp .{
1062 .root_dir = graph.local_cache_root,1214 .{
1063 .sub_path = try allocPrint(arena, "c/{s}", .{&digest}),1215 .root_dir = graph.local_cache_root,
1064 };1216 .sub_path = try allocPrint(arena, "c/{s}", .{&digest}),
1065 Io.Dir.rename(
1066 config_tmp_path.root_dir.handle,
1067 config_tmp_path.sub_path,
1068 final_path.root_dir.handle,
1069 final_path.sub_path,
1070 io,
1071 ) catch |err| retry: {
1072 const e = switch (err) {
1073 error.FileNotFound => e: {
1074 const dir_path = final_path.dirname().?;
1075 dir_path.root_dir.handle.createDirPath(io, dir_path.sub_path) catch |e|
1076 fatal("failed to create directory {f}: {t}", .{ dir_path, e });
1077 if (Io.Dir.rename(
1078 config_tmp_path.root_dir.handle,
1079 config_tmp_path.sub_path,
1080 final_path.root_dir.handle,
1081 final_path.sub_path,
1082 io,
1083 )) |_| break :retry else |e| break :e e;
1084 },1217 },
1085 else => |e| e,1218 false,
1086 };1219 };
1087 fatal("failed to rename configuration file from {f} into {f}: {t}", .{1220 },
1088 config_tmp_path, final_path, e,1221 .poisoned => {}, // Don't bother checking for cache hit.
1089 });
1090 };
1091 config_man.writeManifest() catch |err| log.warn("failed to write cache manifest: {t}", .{err});
1092 break :cp .{ final_path, false };
1093 }1222 }
1094 };
1095
1096 // Hang on to the configuration file lock until we finish loading the configuration file.
1097 var configuration_lock = if (!poisoned) config_man.toOwnedLock() else null;
1098 defer if (configuration_lock) |*l| l.release(io);
10991223
1100 switch (print_configuration) {1224 const configure_exe_path: Path = if (std.zig.buildExeSubprocess(gpa, io, .{
1101 .path => {1225 .argv = build_configurer_argv.items,
1102 initStdoutWriter(io).print("{f}\n", .{configuration_path}) catch1226 .cache_root = graph.local_cache_root,
1103 fatal("failed printing cache file path: {t}", .{stdout_writer_allocation.err.?});1227 .root_name = configurer_exe_name,
1104 stdout_writer_allocation.flush() catch |err|1228 .environ_map = &graph.environ_map,
1105 fatal("failed printing cache file path: {t}", .{err});1229 .cache_manifest = &config_man,
1106 return process.cleanExit(io);1230 .arch_os_abi = target_arch_os_abi,
1107 },1231 .progress_node = compile_prog_node,
1108 .none, .zon => {},1232 })) |r| r.path else |err| return err;
1233 defer gpa.free(configure_exe_path.sub_path);
1234
1235 configure_argv[0] = try configure_exe_path.toString(arena);
1109 }1236 }
11101237
1111 const configuration = c: {1238 if (!process.can_spawn) {
1112 var file = configuration_path.root_dir.handle.openFile(io, configuration_path.sub_path, .{}) catch |err|1239 fatal("cannot spawn command on {t}: {f}", .{ native_os, @as(std.zig.SubprocessCommand, .{
1113 fatal("failed to open configuration file {f}: {t}", .{ configuration_path, err });1240 .argv = configure_argv,
1114 defer file.close(io);1241 }) });
1115 break :c Configuration.loadFile(arena, io, file) catch |err|
1116 fatal("failed to load configuration file {f}: {t}", .{ configuration_path, err });
1117 };
1118 // Technically if the configuration is marked as poisoned, we could
1119 // already delete the file now, but we leave it around in case the
1120 // maker process fails or crashes and it's helpful to be able to repeat
1121 // execution of the command line or otherwise inspect the configuration file.
1122 const c = &configuration;
1123 var top_level_steps: std.array_hash_map.String(Configuration.Step.Index) = .empty;
1124 for (configuration.steps, 0..) |*conf_step, step_index_usize| {
1125 if (conf_step.owner != .root) continue;
1126 const step_index: Configuration.Step.Index = @enumFromInt(step_index_usize);
1127 const flags = conf_step.flags(c);
1128 switch (flags.tag) {
1129 .top_level => {
1130 const name = step_index.ptr(c).name.slice(c);
1131 try top_level_steps.put(arena, name, step_index);
1132 },
1133 else => {},
1134 }
1135 }
1136 for (c.search_prefixes) |search_prefix| {
1137 try graph.search_prefixes.append(arena, search_prefix.slice(c));
1138 }1242 }
1139 break :sc .{
1140 .configuration = configuration,
1141 .top_level_steps = top_level_steps,
1142 .path = configuration_path,
1143 };
1144 };
11451243
1146 if (help_menu) {1244 const rand_int = randInt(io, u64);
1147 scanned_config.printUsage(&graph, initStdoutWriter(io)) catch |err| switch (err) {1245 const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
1148 error.WriteFailed => return stdout_writer_allocation.err.?,1246 const config_tmp_path: Path = .{
1149 else => |e| return e,1247 .root_dir = graph.local_cache_root,
1248 .sub_path = tmp_dir_sub_path,
1150 };1249 };
1151 try stdout_writer_allocation.flush();1250 const config_tmp_file: Io.File = try config_tmp_path.root_dir.handle.createFile(
1152 return cleanExit(io, &scanned_config);1251 io,
1153 } else if (steps_menu) {1252 config_tmp_path.sub_path,
1154 scanned_config.printSteps(&graph, initStdoutWriter(io)) catch |err| switch (err) {1253 .{ .read = true, .exclusive = true },
1155 error.WriteFailed => return stdout_writer_allocation.err.?,1254 );
1156 else => |e| return e,1255 defer config_tmp_file.close(io);
1256
1257 const term = term: {
1258 const child_node = options.parent_progress_node.start("Run Configure Script", 0);
1259 defer child_node.end();
1260 var child = process.spawn(io, .{
1261 .argv = configure_argv,
1262 .stdout = .{ .file = config_tmp_file },
1263 .progress_node = child_node,
1264 }) catch |err| fatal("failed to spawn configure script {q}: {t}", .{ configure_argv[0], err });
1265 defer child.kill(io);
1266 break :term child.wait(io) catch |err|
1267 fatal("failed to wait configure script {q}: {t}", .{ configure_argv[0], err });
1157 };1268 };
1158 try stdout_writer_allocation.flush();1269 if (!term.success()) {
1159 return cleanExit(io, &scanned_config);1270 // Failure to produce the configuration file.
1160 } else switch (print_configuration) {1271 fatal("configure command {f}: {f}", .{ term, @as(std.zig.SubprocessCommand, .{
1161 .none => {},1272 .argv = configure_argv,
1162 .zon => {1273 }) });
1163 scanned_config.print(initStdoutWriter(io)) catch return stdout_writer_allocation.err.?;1274 }
1164 try stdout_writer_allocation.flush();1275 // Even though the file is designed to be sent directly to make
1165 return cleanExit(io, &scanned_config);1276 // runner, we must load it now because:
1166 },1277 // * If it contains additional file dependencies, we need to
1167 .path => unreachable,1278 // add them to `config_man` before obtaining the final digest.
1168 }1279 // * If it contains a set of lazy packages that need to be
1280 // fetched, we need to fetch those now and re-run configure.
1281 var configuration = Configuration.loadFile(arena, io, config_tmp_file) catch |err|
1282 fatal("failed to load configuration file {f}: {t}", .{ config_tmp_path, err });
1283
1284 if (configuration.unlazy_deps.len != 0) {
1285 var any_errors = false;
1286 for (configuration.unlazy_deps) |hash_string| {
1287 const hash = hash_string.slice(&configuration);
1288 assert(hash.len != 0);
1289 if (hash.len > Package.Hash.max_len) {
1290 log.err("invalid digest (length {d} exceeds maximum): {q}", .{ hash.len, hash });
1291 any_errors = true;
1292 continue;
1293 }
1294 try unlazy_set.put(arena, .fromSlice(hash), {});
1295 }
1296 if (any_errors) process.exit(1);
1297 if (options.system_pkg_dir_path) |p| {
1298 // In this mode, the system needs to provide these packages; they
1299 // cannot be fetched by Zig.
1300 const s = Dir.path.sep_str;
1301 for (unlazy_set.keys()) |*hash| {
1302 log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{ p, hash.toSlice() });
1303 }
1304 log.info("remote package fetching disabled due to --system mode", .{});
1305 log.info("dependencies might be avoidable depending on build configuration", .{});
1306 process.exit(1);
1307 }
1308 continue :cp;
1309 }
11691310
1170 if (webui_listen != null) {1311 for (configuration.path_deps) |path_dep| {
1171 if (watch) fatal("using '--webui' and '--watch' together is not yet supported; consider omitting '--watch' in favour of the web UI \"Rebuild\" button", .{});1312 try config_man.addPathPost(path_dep.toCachePath(&configuration, arena));
1172 if (builtin.single_threaded) fatal("'--webui' is not yet supported on single-threaded hosts", .{});1313 }
1173 }
11741314
1175 const install_prefix_path: Path = if (graph.environ_map.get("DESTDIR")) |dest_dir| .{1315 // If it is poisoned, there is no point in moving it to cached
1176 .root_dir = .cwd(),1316 // location. Just leave it in the tmp directory.
1177 .sub_path = try Dir.path.join(arena, &.{ dest_dir, override_install_prefix orelse "/usr" }),1317 if (configuration.poisoned) {
1178 } else if (override_install_prefix) |cwd_relative| .{1318 break :cp .{ config_tmp_path, true };
1179 .root_dir = .cwd(),1319 } else {
1180 .sub_path = cwd_relative,1320 const digest = config_man.final();
1181 } else .{1321 const final_path: Path = .{
1182 .root_dir = graph.build_root_directory,1322 .root_dir = graph.local_cache_root,
1183 .sub_path = "zig-out",1323 .sub_path = try allocPrint(arena, "c/{s}", .{&digest}),
1324 };
1325 Io.Dir.rename(
1326 config_tmp_path.root_dir.handle,
1327 config_tmp_path.sub_path,
1328 final_path.root_dir.handle,
1329 final_path.sub_path,
1330 io,
1331 ) catch |err| retry: {
1332 const e = switch (err) {
1333 error.FileNotFound => e: {
1334 const dir_path = final_path.dirname().?;
1335 dir_path.root_dir.handle.createDirPath(io, dir_path.sub_path) catch |e|
1336 fatal("failed to create directory {f}: {t}", .{ dir_path, e });
1337 if (Io.Dir.rename(
1338 config_tmp_path.root_dir.handle,
1339 config_tmp_path.sub_path,
1340 final_path.root_dir.handle,
1341 final_path.sub_path,
1342 io,
1343 )) |_| break :retry else |e| break :e e;
1344 },
1345 else => |e| e,
1346 };
1347 fatal("failed to rename configuration file from {f} into {f}: {t}", .{
1348 config_tmp_path, final_path, e,
1349 });
1350 };
1351 config_man.writeManifest() catch |err| log.warn("failed to write cache manifest: {t}", .{err});
1352 break :cp .{ final_path, false };
1353 }
1184 };1354 };
11851355
1186 const install_lib_path: Path = if (override_lib_dir) |cwd_relative| .{1356 // Hang on to the configuration file lock until we finish loading the configuration file.
1187 .root_dir = .cwd(),1357 var configuration_lock = if (!poisoned) config_man.toOwnedLock() else null;
1188 .sub_path = cwd_relative,1358 defer if (configuration_lock) |*l| l.release(io);
1189 } else try install_prefix_path.join(arena, "lib");1359
11901360 switch (options.print_configuration) {
1191 const install_bin_path: Path = if (override_bin_dir) |cwd_relative| .{1361 .path => {
1192 .root_dir = .cwd(),1362 initStdoutWriter(io).print("{f}\n", .{configuration_path}) catch
1193 .sub_path = cwd_relative,1363 fatal("failed printing cache file path: {t}", .{stdout_writer_allocation.err.?});
1194 } else try install_prefix_path.join(arena, "bin");1364 stdout_writer_allocation.flush() catch |err|
11951365 fatal("failed printing cache file path: {t}", .{err});
1196 const install_include_path: Path = if (override_include_dir) |cwd_relative| .{1366 _ = io.lockStderr(&.{}, .no_color) catch {};
1197 .root_dir = .cwd(),1367 process.exit(0);
1198 .sub_path = cwd_relative,
1199 } else try install_prefix_path.join(arena, "include");
1200
1201 var maker: Maker = .{
1202 .gpa = gpa,
1203 .graph = &graph,
1204 .scanned_config = &scanned_config,
1205 .install_paths = .{
1206 .prefix = install_prefix_path,
1207 .lib = install_lib_path,
1208 .bin = install_bin_path,
1209 .include = install_include_path,
1210 },1368 },
12111369 .none, .zon => {},
1212 .steps = try arena.alloc(Step, scanned_config.configuration.steps.len),
1213 .generated_files = try arena.alloc(Path, scanned_config.configuration.generated_files_len),
1214 .run_args = run_args,
1215
1216 .available_rss = max_rss,
1217 .max_rss_is_default = false,
1218 .max_rss_mutex = .init,
1219 .skip_oom_steps = skip_oom_steps,
1220 .unit_test_timeout_ns = test_timeout_ns,
1221
1222 .watch = watch,
1223 .web_server = undefined, // set after `prepare`
1224 .memory_blocked_steps = .empty,
1225 .step_stack = .empty,
1226 .pkg_config = .{ .debug = debug_pkg_config },
1227
1228 .error_style = error_style,
1229 .multiline_errors = multiline_errors,
1230 .summary = summary orelse if (watch or webui_listen != null) .new else .failures,
1231 };
1232 defer {
1233 maker.memory_blocked_steps.deinit(gpa);
1234 maker.step_stack.deinit(gpa);
1235 }
1236
1237 if (maker.available_rss == 0) {
1238 maker.available_rss = process.totalSystemMemory() catch std.math.maxInt(u64);
1239 maker.max_rss_is_default = true;
1240 }1370 }
12411371
1242 maker.prepare(step_names.items) catch |err| switch (err) {1372 const configuration = c: {
1243 error.DependencyLoopDetected, error.InsufficientMemory => {1373 var file = configuration_path.root_dir.handle.openFile(io, configuration_path.sub_path, .{}) catch |err|
1244 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};1374 fatal("failed to open configuration file {f}: {t}", .{ configuration_path, err });
1245 process.exit(1);1375 defer file.close(io);
1246 },1376 break :c Configuration.loadFile(arena, io, file) catch |err|
1247 else => |e| return e,1377 fatal("failed to load configuration file {f}: {t}", .{ configuration_path, err });
1248 };1378 };
12491379 // Technically if the configuration is marked as poisoned, we could
1250 var w: Watch = w: {1380 // already delete the file now, but we leave it around in case the
1251 if (!watch) break :w undefined;1381 // maker process fails or crashes and it's helpful to be able to repeat
1252 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{native_os});1382 // execution of the command line or otherwise inspect the configuration file.
1253 break :w try .init(&maker);1383 const c = &configuration;
1254 };1384 var top_level_steps: std.array_hash_map.String(Configuration.Step.Index) = .empty;
12551385 for (configuration.steps, 0..) |*conf_step, step_index_usize| {
1256 const now = Io.Clock.Timestamp.now(io, .awake);1386 if (conf_step.owner != .root) continue;
12571387 const step_index: Configuration.Step.Index = @enumFromInt(step_index_usize);
1258 maker.web_server = if (webui_listen) |listen_address| ws: {1388 const flags = conf_step.flags(c);
1259 if (builtin.single_threaded) unreachable; // `fatal` above1389 switch (flags.tag) {
1260 break :ws .init(.{1390 .top_level => {
1261 .maker = &maker,1391 const name = step_index.ptr(c).name.slice(c);
1262 .root_prog_node = main_progress_node,1392 try top_level_steps.put(arena, name, step_index);
1263 .listen_address = listen_address,
1264 .base_timestamp = now,
1265 });
1266 } else null;
1267
1268 if (maker.web_server) |*ws| {
1269 ws.start() catch |err| fatal("failed to start web server: {t}", .{err});
1270 }
1271
1272 rebuild: while (true) : (if (maker.error_style.clearOnUpdate()) {
1273 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
1274 defer io.unlockStderr();
1275 stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H") catch |err| switch (err) {
1276 error.WriteFailed => return stderr.file_writer.err.?,
1277 };
1278 }) {
1279 if (maker.web_server) |*ws| ws.startBuild();
1280
1281 try maker.makeStepNames(step_names.items, main_progress_node, fuzz);
1282
1283 if (maker.web_server) |*web_server| {
1284 if (fuzz) |mode| if (mode != .forever) fatal(
1285 "error: limited fuzzing is not implemented yet for --webui",
1286 .{},
1287 );
1288
1289 web_server.finishBuild(.{ .fuzz = fuzz != null });
1290 }
1291
1292 if (maker.web_server) |*web_server| {
1293 const c = &scanned_config.configuration;
1294 assert(!watch); // fatal error after CLI parsing
1295 while (true) switch (try web_server.wait()) {
1296 .rebuild => {
1297 for (maker.step_stack.keys()) |step_index| {
1298 const step = maker.stepByIndex(step_index);
1299 step.state = .precheck_done;
1300 const deps = step_index.ptr(c).deps.slice(c);
1301 step.pending_deps = @intCast(deps.len);
1302 step.reset(&maker);
1303 }
1304 continue :rebuild;
1305 },
1306 };
1307 }
1308
1309 if (!maker.watch) return;
1310
1311 // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.
1312 if (!Watch.have_impl) unreachable;
1313
1314 try w.update(maker.step_stack.keys());
1315
1316 // Wait until a file system notification arrives. Read all such events
1317 // until the buffer is empty. Then wait for a debounce interval, resetting
1318 // if any more events come in. After the debounce interval has passed,
1319 // trigger a rebuild on all steps with modified inputs, as well as their
1320 // recursive dependants.
1321 var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined;
1322 const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{
1323 w.dir_count, countSubProcesses(&maker),
1324 }) catch &caption_buf;
1325 var debouncing_node = main_progress_node.start(caption, 0);
1326 var in_debounce = false;
1327 while (true) switch (try w.wait(if (in_debounce) .{ .ms = debounce_interval_ms } else .none)) {
1328 .timeout => {
1329 assert(in_debounce);
1330 debouncing_node.end();
1331 markFailedStepsDirty(&maker);
1332 continue :rebuild;
1333 },
1334 .dirty => if (!in_debounce) {
1335 in_debounce = true;
1336 debouncing_node.end();
1337 debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0);
1338 },1393 },
1339 .clean => {},1394 else => {},
1340 };1395 }
1396 }
1397 for (c.search_prefixes) |search_prefix| {
1398 try graph.search_prefixes.append(arena, search_prefix.slice(c));
1341 }1399 }
1400 return .{
1401 .configuration = configuration,
1402 .top_level_steps = top_level_steps,
1403 .path = configuration_path,
1404 };
1342}1405}
13431406
1344fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {1407fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
...@@ -1894,7 +1957,7 @@ fn prepare(maker: *Maker, step_names: []const []const u8) !void {...@@ -1894,7 +1957,7 @@ fn prepare(maker: *Maker, step_names: []const []const u8) !void {
1894fn makeStepNames(1957fn makeStepNames(
1895 maker: *Maker,1958 maker: *Maker,
1896 step_names: []const []const u8,1959 step_names: []const []const u8,
1897 parent_prog_node: std.Progress.Node,1960 parent_progress_node: std.Progress.Node,
1898 fuzz: ?Fuzz.Mode,1961 fuzz: ?Fuzz.Mode,
1899) !void {1962) !void {
1900 const graph = maker.graph;1963 const graph = maker.graph;
...@@ -1918,7 +1981,7 @@ fn makeStepNames(...@@ -1918,7 +1981,7 @@ fn makeStepNames(
1918 }1981 }
1919 }1982 }
19201983
1921 const step_prog = parent_prog_node.start("steps", step_stack.count());1984 const step_prog = parent_progress_node.start("steps", step_stack.count());
1922 defer step_prog.end();1985 defer step_prog.end();
19231986
1924 var group: Io.Group = .init;1987 var group: Io.Group = .init;
...@@ -1998,7 +2061,7 @@ fn makeStepNames(...@@ -1998,7 +2061,7 @@ fn makeStepNames(
1998 }2061 }
19992062
2000 assert(mode == .limit);2063 assert(mode == .limit);
2001 var f = Fuzz.init(maker, step_stack.keys(), parent_prog_node, mode) catch |err|2064 var f = Fuzz.init(maker, step_stack.keys(), parent_progress_node, mode) catch |err|
2002 fatal("failed to start fuzzer: {t}", .{err});2065 fatal("failed to start fuzzer: {t}", .{err});
2003 defer f.deinit();2066 defer f.deinit();
20042067
...@@ -2189,7 +2252,7 @@ fn makeStep(...@@ -2189,7 +2252,7 @@ fn makeStep(
2189 const step_prog_node = root_prog_node.start(step_name, 0);2252 const step_prog_node = root_prog_node.start(step_name, 0);
2190 defer step_prog_node.end();2253 defer step_prog_node.end();
21912254
2192 if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .wip);2255 if (maker.web_server) |ws| ws.updateStepStatus(step_index, .wip);
21932256
2194 const new_state: Step.State = for (deps) |dep_index| {2257 const new_state: Step.State = for (deps) |dep_index| {
2195 const dep_make_step = maker.stepByIndex(dep_index);2258 const dep_make_step = maker.stepByIndex(dep_index);
...@@ -2224,14 +2287,14 @@ fn makeStep(...@@ -2224,14 +2287,14 @@ fn makeStep(
2224 .dependency_failure,2287 .dependency_failure,
2225 .skipped_oom,2288 .skipped_oom,
2226 => {2289 => {
2227 if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .failure);2290 if (maker.web_server) |ws| ws.updateStepStatus(step_index, .failure);
2228 std.Progress.setStatus(.failure_working);2291 std.Progress.setStatus(.failure_working);
2229 },2292 },
22302293
2231 .success,2294 .success,
2232 .skipped,2295 .skipped,
2233 => {2296 => {
2234 if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .success);2297 if (maker.web_server) |ws| ws.updateStepStatus(step_index, .success);
2235 },2298 },
2236 }2299 }
2237 }2300 }
lib/compiler/Maker/Fuzz.zig+4-4
...@@ -166,9 +166,9 @@ pub fn deinit(fuzz: *Fuzz) void {...@@ -166,9 +166,9 @@ pub fn deinit(fuzz: *Fuzz) void {
166fn rebuildTestsWorkerRun(166fn rebuildTestsWorkerRun(
167 maker: *Maker,167 maker: *Maker,
168 run_index: Configuration.Step.Index,168 run_index: Configuration.Step.Index,
169 parent_prog_node: std.Progress.Node,169 parent_progress_node: std.Progress.Node,
170) void {170) void {
171 rebuildTestsWorkerRunFallible(maker, run_index, parent_prog_node) catch |err| {171 rebuildTestsWorkerRunFallible(maker, run_index, parent_progress_node) catch |err| {
172 const conf = &maker.scanned_config.configuration;172 const conf = &maker.scanned_config.configuration;
173 const conf_run = run_index.ptr(conf).extended.cast(conf, Configuration.Step.Run).?;173 const conf_run = run_index.ptr(conf).extended.cast(conf, Configuration.Step.Run).?;
174 const comp_index = conf_run.producer.value.?;174 const comp_index = conf_run.producer.value.?;
...@@ -180,7 +180,7 @@ fn rebuildTestsWorkerRun(...@@ -180,7 +180,7 @@ fn rebuildTestsWorkerRun(
180fn rebuildTestsWorkerRunFallible(180fn rebuildTestsWorkerRunFallible(
181 maker: *Maker,181 maker: *Maker,
182 run_index: Configuration.Step.Index,182 run_index: Configuration.Step.Index,
183 parent_prog_node: std.Progress.Node,183 parent_progress_node: std.Progress.Node,
184) !void {184) !void {
185 const graph = maker.graph;185 const graph = maker.graph;
186 const io = graph.io;186 const io = graph.io;
...@@ -196,7 +196,7 @@ fn rebuildTestsWorkerRunFallible(...@@ -196,7 +196,7 @@ fn rebuildTestsWorkerRunFallible(
196 const root_module = conf_comp.root_module.get(conf);196 const root_module = conf_comp.root_module.get(conf);
197 const target = root_module.resolved_target.get(conf).?.result.get(conf);197 const target = root_module.resolved_target.get(conf).?.result.get(conf);
198198
199 const prog_node = parent_prog_node.start(conf_comp_step.name.slice(conf), 0);199 const prog_node = parent_progress_node.start(conf_comp_step.name.slice(conf), 0);
200 defer prog_node.end();200 defer prog_node.end();
201201
202 const result = comp.rebuildInFuzzMode(maker, comp_index, prog_node);202 const result = comp.rebuildInFuzzMode(maker, comp_index, prog_node);
lib/compiler/Maker/Step.zig+1-1
...@@ -654,7 +654,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi...@@ -654,7 +654,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi
654 }654 }
655 }655 }
656 },656 },
657 .time_report => if (maker.web_server) |*ws| {657 .time_report => if (maker.web_server) |ws| {
658 const TimeReport = std.zig.Server.Message.TimeReport;658 const TimeReport = std.zig.Server.Message.TimeReport;
659 const tr: *align(1) const TimeReport = @ptrCast(body[0..@sizeOf(TimeReport)]);659 const tr: *align(1) const TimeReport = @ptrCast(body[0..@sizeOf(TimeReport)]);
660 ws.updateTimeReportCompile(.{660 ws.updateTimeReportCompile(.{
lib/compiler/Maker/Step/Run.zig+1-1
...@@ -1242,7 +1242,7 @@ fn evalZigTest(...@@ -1242,7 +1242,7 @@ fn evalZigTest(
1242 step.test_results = test_results;1242 step.test_results = test_results;
1243 if (test_metadata) |tm| {1243 if (test_metadata) |tm| {
1244 run.cached_test_metadata = tm.toCachedTestMetadata();1244 run.cached_test_metadata = tm.toCachedTestMetadata();
1245 if (maker.web_server) |*ws| {1245 if (maker.web_server) |ws| {
1246 if (graph.time_report) {1246 if (graph.time_report) {
1247 ws.updateTimeReportRunTest(1247 ws.updateTimeReportRunTest(
1248 run_index,1248 run_index,
lib/compiler/Maker/WebServer.zig+167-135
...@@ -19,7 +19,7 @@ const Fuzz = @import("Fuzz.zig");...@@ -19,7 +19,7 @@ const Fuzz = @import("Fuzz.zig");
19const Graph = @import("Graph.zig");19const Graph = @import("Graph.zig");
20const Step = @import("Step.zig");20const Step = @import("Step.zig");
2121
22maker: *Maker,22graph: *const Graph,
23listen_address: net.IpAddress,23listen_address: net.IpAddress,
24root_prog_node: std.Progress.Node,24root_prog_node: std.Progress.Node,
2525
...@@ -28,17 +28,8 @@ serve_task: ?Io.Future(Io.Cancelable!void),...@@ -28,17 +28,8 @@ serve_task: ?Io.Future(Io.Cancelable!void),
2828
29/// Uses `Io.Clock.awake`.29/// Uses `Io.Clock.awake`.
30base_timestamp: Io.Timestamp,30base_timestamp: Io.Timestamp,
31/// The "step name" data which trails `abi.Hello`, for the steps in `all_steps`.
32step_names_trailing: []u8,
33
34/// The bit-packed "step status" data. Values are `abi.StepUpdate.Status`. LSBs are earlier steps.
35/// Accessed atomically.
36step_status_bits: []u8,
3731
38fuzz: ?Fuzz,32fuzz: ?Fuzz,
39time_report_mutex: Io.Mutex,
40time_report_msgs: [][]u8,
41time_report_update_times: []i64,
4233
43build_status: std.atomic.Value(abi.BuildStatus),34build_status: std.atomic.Value(abi.BuildStatus),
44/// When an event occurs which means WebSocket clients should be sent updates, call `notifyUpdate`35/// When an event occurs which means WebSocket clients should be sent updates, call `notifyUpdate`
...@@ -55,6 +46,21 @@ runner_request_ready_cond: Io.Condition,...@@ -55,6 +46,21 @@ runner_request_ready_cond: Io.Condition,
55runner_request_empty_cond: Io.Condition,46runner_request_empty_cond: Io.Condition,
56runner_request: ?RunnerRequest,47runner_request: ?RunnerRequest,
5748
49configured: ?Configured,
50
51const Configured = struct {
52 maker: *Maker,
53 /// The "step name" data which trails `abi.Hello`, for the steps in `all_steps`.
54 step_names_trailing: []u8,
55 /// The bit-packed "step status" data. Values are `abi.StepUpdate.Status`. LSBs are earlier steps.
56 /// Accessed atomically.
57 step_status_bits: []u8,
58
59 time_report_mutex: Io.Mutex,
60 time_report_msgs: [][]u8,
61 time_report_update_times: []i64,
62};
63
58/// If a client is not explicitly notified of changes with `notifyUpdate`, it will be sent updates64/// If a client is not explicitly notified of changes with `notifyUpdate`, it will be sent updates
59/// on a fixed interval of this many milliseconds.65/// on a fixed interval of this many milliseconds.
60const default_update_interval_ms = 500;66const default_update_interval_ms = 500;
...@@ -63,34 +69,88 @@ pub const base_clock: Io.Clock = .awake;...@@ -63,34 +69,88 @@ pub const base_clock: Io.Clock = .awake;
6369
64/// Thread-safe. Triggers updates to be sent to connected WebSocket clients; see `update_id`.70/// Thread-safe. Triggers updates to be sent to connected WebSocket clients; see `update_id`.
65pub fn notifyUpdate(ws: *WebServer) void {71pub fn notifyUpdate(ws: *WebServer) void {
66 const io = ws.maker.graph.io;72 const io = ws.graph.io;
67 _ = ws.update_id.rmw(.Add, 1, .release);73 _ = ws.update_id.rmw(.Add, 1, .release);
68 io.futexWake(u32, &ws.update_id.raw, 16);74 io.futexWake(u32, &ws.update_id.raw, 16);
69}75}
7076
71pub const Options = struct {77pub const Options = struct {
72 maker: *Maker,78 graph: *const Graph,
73 root_prog_node: std.Progress.Node,79 root_prog_node: std.Progress.Node,
74 listen_address: net.IpAddress,80 listen_address: net.IpAddress,
75 base_timestamp: Io.Clock.Timestamp,81 base_timestamp: Io.Clock.Timestamp,
76};82};
83
77pub fn init(opts: Options) WebServer {84pub fn init(opts: Options) WebServer {
78 // The upcoming `Io` interface should allow us to use `Io.async` and `Io.concurrent`85 // The upcoming `Io` interface should allow us to use `Io.async` and `Io.concurrent`
79 // instead of threads, so that the web server can function in single-threaded builds.86 // instead of threads, so that the web server can function in single-threaded builds.
80 comptime assert(!builtin.single_threaded);87 comptime assert(!builtin.single_threaded);
81 assert(opts.base_timestamp.clock == base_clock);88 assert(opts.base_timestamp.clock == base_clock);
89 return .{
90 .graph = opts.graph,
91 .listen_address = opts.listen_address,
92 .root_prog_node = opts.root_prog_node,
93
94 .tcp_server = null,
95 .serve_task = null,
96
97 .base_timestamp = opts.base_timestamp.raw,
98
99 .fuzz = null,
100
101 .build_status = .init(.idle),
102 .update_id = .init(0),
103
104 .runner_request_mutex = .init,
105 .runner_request_ready_cond = .init,
106 .runner_request_empty_cond = .init,
107 .runner_request = null,
108
109 .configured = null,
110 };
111}
112
113pub fn deinit(ws: *WebServer) void {
114 const graph = ws.graph;
115 const io = graph.io;
116
117 if (ws.fuzz) |*f| f.deinit();
118
119 ws.releaseConfigured();
82120
83 const maker = opts.maker;121 if (ws.serve_task) |t| {
122 if (ws.tcp_server) |*s| s.stream.close(io);
123 t.await();
124 }
125 if (ws.tcp_server) |*s| s.deinit();
126}
127
128fn releaseConfigured(ws: *WebServer) void {
129 if (ws.configured) |*configured| {
130 const gpa = configured.maker.gpa;
131 gpa.free(configured.step_names_trailing);
132 gpa.free(configured.step_status_bits);
133 for (configured.time_report_msgs) |msg| gpa.free(msg);
134 gpa.free(configured.time_report_msgs);
135 gpa.free(configured.time_report_update_times);
136 gpa.free(configured.step_names_trailing);
137 ws.configured = null;
138 }
139}
140
141pub fn updateConfiguration(ws: *WebServer, maker: *Maker) !void {
142 const graph = ws.graph;
143 const gpa = maker.gpa;
84 const all_steps = maker.step_stack.keys();144 const all_steps = maker.step_stack.keys();
85 const c = &maker.scanned_config.configuration;145 const c = &maker.scanned_config.configuration;
86 const gpa = maker.gpa;
87 const graph = maker.graph;
88146
89 const step_names_trailing = gpa.alloc(u8, len: {147 const step_names_trailing = try gpa.alloc(u8, len: {
90 var name_bytes: usize = 0;148 var name_bytes: usize = 0;
91 for (all_steps) |step_index| name_bytes += step_index.ptr(c).name.slice(c).len;149 for (all_steps) |step_index| name_bytes += step_index.ptr(c).name.slice(c).len;
92 break :len name_bytes + all_steps.len * 4;150 break :len name_bytes + all_steps.len * 4;
93 }) catch @panic("out of memory");151 });
152 errdefer gpa.free(step_names_trailing);
153
94 {154 {
95 const step_name_lens: []align(1) u32 = @ptrCast(step_names_trailing[0 .. all_steps.len * 4]);155 const step_name_lens: []align(1) u32 = @ptrCast(step_names_trailing[0 .. all_steps.len * 4]);
96 var idx: usize = all_steps.len * 4;156 var idx: usize = all_steps.len * 4;
...@@ -103,71 +163,35 @@ pub fn init(opts: Options) WebServer {...@@ -103,71 +163,35 @@ pub fn init(opts: Options) WebServer {
103 assert(idx == step_names_trailing.len);163 assert(idx == step_names_trailing.len);
104 }164 }
105165
106 const step_status_bits = gpa.alloc(166 const step_status_bits = try gpa.alloc(u8, std.math.divCeil(usize, all_steps.len, 4) catch unreachable);
107 u8,167 errdefer gpa.free(step_status_bits);
108 std.math.divCeil(usize, all_steps.len, 4) catch unreachable,
109 ) catch @panic("out of memory");
110 @memset(step_status_bits, 0);168 @memset(step_status_bits, 0);
111169
112 const time_reports_len: usize = if (graph.time_report) all_steps.len else 0;170 const time_reports_len: usize = if (graph.time_report) all_steps.len else 0;
113 const time_report_msgs = gpa.alloc([]u8, time_reports_len) catch @panic("out of memory");171 const time_report_msgs = try gpa.alloc([]u8, time_reports_len);
114 const time_report_update_times = gpa.alloc(i64, time_reports_len) catch @panic("out of memory");172 errdefer gpa.free(time_report_msgs);
173 const time_report_update_times = try gpa.alloc(i64, time_reports_len);
174 errdefer gpa.free(time_report_update_times);
115 @memset(time_report_msgs, &.{});175 @memset(time_report_msgs, &.{});
116 @memset(time_report_update_times, std.math.minInt(i64));176 @memset(time_report_update_times, std.math.minInt(i64));
117177
118 return .{178 ws.releaseConfigured();
119 .maker = maker,
120 .listen_address = opts.listen_address,
121 .root_prog_node = opts.root_prog_node,
122
123 .tcp_server = null,
124 .serve_task = null,
125179
126 .base_timestamp = opts.base_timestamp.raw,180 ws.configured = .{
181 .maker = maker,
127 .step_names_trailing = step_names_trailing,182 .step_names_trailing = step_names_trailing,
128
129 .step_status_bits = step_status_bits,183 .step_status_bits = step_status_bits,
130
131 .fuzz = null,
132 .time_report_mutex = .init,184 .time_report_mutex = .init,
133 .time_report_msgs = time_report_msgs,185 .time_report_msgs = time_report_msgs,
134 .time_report_update_times = time_report_update_times,186 .time_report_update_times = time_report_update_times,
135
136 .build_status = .init(.idle),
137 .update_id = .init(0),
138
139 .runner_request_mutex = .init,
140 .runner_request_ready_cond = .init,
141 .runner_request_empty_cond = .init,
142 .runner_request = null,
143 };187 };
144}188}
145pub fn deinit(ws: *WebServer) void {
146 const maker = ws.maker;
147 const gpa = maker.gpa;
148 const io = maker.graph.io;
149
150 gpa.free(ws.step_names_trailing);
151 gpa.free(ws.step_status_bits);
152
153 if (ws.fuzz) |*f| f.deinit();
154 for (ws.time_report_msgs) |msg| gpa.free(msg);
155 gpa.free(ws.time_report_msgs);
156 gpa.free(ws.time_report_update_times);
157189
158 if (ws.serve_task) |t| {
159 if (ws.tcp_server) |*s| s.stream.close(io);
160 t.await();
161 }
162 if (ws.tcp_server) |*s| s.deinit();
163
164 gpa.free(ws.step_names_trailing);
165}
166pub fn start(ws: *WebServer) error{AlreadyReported}!void {190pub fn start(ws: *WebServer) error{AlreadyReported}!void {
167 assert(ws.tcp_server == null);191 assert(ws.tcp_server == null);
168 assert(ws.serve_task == null);192 assert(ws.serve_task == null);
169 const maker = ws.maker;193 const graph = ws.graph;
170 const io = maker.graph.io;194 const io = graph.io;
171195
172 ws.tcp_server = ws.listen_address.listen(io, .{ .reuse_address = true }) catch |err| {196 ws.tcp_server = ws.listen_address.listen(io, .{ .reuse_address = true }) catch |err| {
173 log.err("failed to listen to port {d}: {t}", .{ ws.listen_address.getPort(), err });197 log.err("failed to listen to port {d}: {t}", .{ ws.listen_address.getPort(), err });
...@@ -186,8 +210,8 @@ pub fn start(ws: *WebServer) error{AlreadyReported}!void {...@@ -186,8 +210,8 @@ pub fn start(ws: *WebServer) error{AlreadyReported}!void {
186 }210 }
187}211}
188fn serve(ws: *WebServer) Io.Cancelable!void {212fn serve(ws: *WebServer) Io.Cancelable!void {
189 const maker = ws.maker;213 const graph = ws.graph;
190 const io = maker.graph.io;214 const io = graph.io;
191215
192 var group: Io.Group = .init;216 var group: Io.Group = .init;
193 defer group.cancel(io);217 defer group.cancel(io);
...@@ -213,7 +237,8 @@ pub fn startBuild(ws: *WebServer) void {...@@ -213,7 +237,8 @@ pub fn startBuild(ws: *WebServer) void {
213 fuzz.deinit();237 fuzz.deinit();
214 ws.fuzz = null;238 ws.fuzz = null;
215 }239 }
216 for (ws.step_status_bits) |*bits| @atomicStore(u8, bits, 0, .monotonic);240 const configured = &ws.configured.?;
241 for (configured.step_status_bits) |*bits| @atomicStore(u8, bits, 0, .monotonic);
217 ws.build_status.store(.running, .monotonic);242 ws.build_status.store(.running, .monotonic);
218 ws.notifyUpdate();243 ws.notifyUpdate();
219}244}
...@@ -223,12 +248,13 @@ pub fn updateStepStatus(...@@ -223,12 +248,13 @@ pub fn updateStepStatus(
223 step_index: Configuration.Step.Index,248 step_index: Configuration.Step.Index,
224 new_status: abi.StepUpdate.Status,249 new_status: abi.StepUpdate.Status,
225) void {250) void {
226 const maker = ws.maker;251 const configured = &ws.configured.?;
252 const maker = configured.maker;
227 const all_steps = maker.step_stack.keys();253 const all_steps = maker.step_stack.keys();
228 const step_idx: u32 = for (all_steps, 0..) |s, i| {254 const step_idx: u32 = for (all_steps, 0..) |s, i| {
229 if (s == step_index) break @intCast(i);255 if (s == step_index) break @intCast(i);
230 } else unreachable;256 } else unreachable;
231 const ptr = &ws.step_status_bits[step_idx / 4];257 const ptr = &configured.step_status_bits[step_idx / 4];
232 const bit_offset: u3 = @intCast((step_idx % 4) * 2);258 const bit_offset: u3 = @intCast((step_idx % 4) * 2);
233 const old_bits: u2 = @truncate(@atomicLoad(u8, ptr, .monotonic) >> bit_offset);259 const old_bits: u2 = @truncate(@atomicLoad(u8, ptr, .monotonic) >> bit_offset);
234 const mask = @as(u8, @intFromEnum(new_status) ^ old_bits) << bit_offset;260 const mask = @as(u8, @intFromEnum(new_status) ^ old_bits) << bit_offset;
...@@ -239,7 +265,8 @@ pub fn updateStepStatus(...@@ -239,7 +265,8 @@ pub fn updateStepStatus(
239pub fn finishBuild(ws: *WebServer, opts: struct {265pub fn finishBuild(ws: *WebServer, opts: struct {
240 fuzz: bool,266 fuzz: bool,
241}) void {267}) void {
242 const maker = ws.maker;268 const configured = &ws.configured.?;
269 const maker = configured.maker;
243 const all_steps = maker.step_stack.keys();270 const all_steps = maker.step_stack.keys();
244271
245 if (opts.fuzz) {272 if (opts.fuzz) {
...@@ -274,15 +301,15 @@ pub fn finishBuild(ws: *WebServer, opts: struct {...@@ -274,15 +301,15 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
274}301}
275302
276pub fn now(ws: *const WebServer) i64 {303pub fn now(ws: *const WebServer) i64 {
277 const maker = ws.maker;304 const graph = ws.graph;
278 const io = maker.graph.io;305 const io = graph.io;
279 const ts = base_clock.now(io);306 const ts = base_clock.now(io);
280 return @intCast(ws.base_timestamp.durationTo(ts).toNanoseconds());307 return @intCast(ws.base_timestamp.durationTo(ts).toNanoseconds());
281}308}
282309
283fn accept(ws: *WebServer, stream: net.Stream) void {310fn accept(ws: *WebServer, stream: net.Stream) void {
284 const maker = ws.maker;311 const graph = ws.graph;
285 const io = maker.graph.io;312 const io = graph.io;
286313
287 defer {314 defer {
288 // `net.Stream.close` wants to helpfully overwrite `stream` with315 // `net.Stream.close` wants to helpfully overwrite `stream` with
...@@ -328,17 +355,19 @@ fn accept(ws: *WebServer, stream: net.Stream) void {...@@ -328,17 +355,19 @@ fn accept(ws: *WebServer, stream: net.Stream) void {
328}355}
329356
330fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {357fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
331 const maker = ws.maker;358 const graph = ws.graph;
332 const gpa = maker.gpa;359 const gpa = graph.cache.gpa;
333 const graph = maker.graph;
334 const io = graph.io;360 const io = graph.io;
361 log.err("TODO serve a different message when the configuration changes", .{});
362 const configured = &ws.configured.?;
363 const maker = configured.maker;
335 const all_steps = maker.step_stack.keys();364 const all_steps = maker.step_stack.keys();
336365
337 var prev_build_status = ws.build_status.load(.monotonic);366 var prev_build_status = ws.build_status.load(.monotonic);
338367
339 const prev_step_status_bits = try gpa.alloc(u8, ws.step_status_bits.len);368 const prev_step_status_bits = try gpa.alloc(u8, configured.step_status_bits.len);
340 defer gpa.free(prev_step_status_bits);369 defer gpa.free(prev_step_status_bits);
341 for (prev_step_status_bits, ws.step_status_bits) |*copy, *shared| {370 for (prev_step_status_bits, configured.step_status_bits) |*copy, *shared| {
342 copy.* = @atomicLoad(u8, shared, .monotonic);371 copy.* = @atomicLoad(u8, shared, .monotonic);
343 }372 }
344373
...@@ -354,7 +383,7 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {...@@ -354,7 +383,7 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
354 .timestamp = ws.now(),383 .timestamp = ws.now(),
355 .steps_len = @intCast(all_steps.len),384 .steps_len = @intCast(all_steps.len),
356 };385 };
357 var bufs: [3][]const u8 = .{ @ptrCast(&hello_header), ws.step_names_trailing, prev_step_status_bits };386 var bufs: [3][]const u8 = .{ @ptrCast(&hello_header), configured.step_names_trailing, prev_step_status_bits };
358 try sock.writeMessageVec(&bufs, .binary);387 try sock.writeMessageVec(&bufs, .binary);
359 }388 }
360389
...@@ -369,17 +398,17 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {...@@ -369,17 +398,17 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
369 }398 }
370399
371 {400 {
372 try ws.time_report_mutex.lock(io);401 try configured.time_report_mutex.lock(io);
373 defer ws.time_report_mutex.unlock(io);402 defer configured.time_report_mutex.unlock(io);
374 for (ws.time_report_msgs, ws.time_report_update_times) |msg, update_time| {403 for (configured.time_report_msgs, configured.time_report_update_times) |msg, update_time| {
375 if (update_time <= prev_time) continue;404 if (update_time <= prev_time) continue;
376 // We want to send `msg`, but shouldn't block `ws.time_report_mutex` while we do, so405 // We want to send `msg`, but shouldn't block `configured.time_report_mutex` while we do, so
377 // that we don't hold up the build system on the client accepting this packet.406 // that we don't hold up the build system on the client accepting this packet.
378 const owned_msg = try gpa.dupe(u8, msg);407 const owned_msg = try gpa.dupe(u8, msg);
379 defer gpa.free(owned_msg);408 defer gpa.free(owned_msg);
380 // Temporarily unlock, then re-lock after the message is sent.409 // Temporarily unlock, then re-lock after the message is sent.
381 ws.time_report_mutex.unlock(io);410 configured.time_report_mutex.unlock(io);
382 defer ws.time_report_mutex.lockUncancelable(io);411 defer configured.time_report_mutex.lockUncancelable(io);
383 try sock.writeMessage(owned_msg, .binary);412 try sock.writeMessage(owned_msg, .binary);
384 }413 }
385 }414 }
...@@ -393,7 +422,7 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {...@@ -393,7 +422,7 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
393 }422 }
394 }423 }
395424
396 for (prev_step_status_bits, ws.step_status_bits, 0..) |*prev_byte, *shared, byte_idx| {425 for (prev_step_status_bits, configured.step_status_bits, 0..) |*prev_byte, *shared, byte_idx| {
397 const cur_byte = @atomicLoad(u8, shared, .monotonic);426 const cur_byte = @atomicLoad(u8, shared, .monotonic);
398 if (prev_byte.* == cur_byte) continue;427 if (prev_byte.* == cur_byte) continue;
399 const cur: [4]abi.StepUpdate.Status = .{428 const cur: [4]abi.StepUpdate.Status = .{
...@@ -433,8 +462,8 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {...@@ -433,8 +462,8 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
433 }462 }
434}463}
435fn recvWebSocketMessages(ws: *WebServer, sock: *http.Server.WebSocket) void {464fn recvWebSocketMessages(ws: *WebServer, sock: *http.Server.WebSocket) void {
436 const maker = ws.maker;465 const graph = ws.graph;
437 const io = maker.graph.io;466 const io = graph.io;
438467
439 while (true) {468 while (true) {
440 const msg = sock.readSmallMessage() catch return;469 const msg = sock.readSmallMessage() catch return;
...@@ -492,8 +521,7 @@ fn serveLibFile(...@@ -492,8 +521,7 @@ fn serveLibFile(
492 sub_path: []const u8,521 sub_path: []const u8,
493 content_type: []const u8,522 content_type: []const u8,
494) !void {523) !void {
495 const maker = ws.maker;524 const graph = ws.graph;
496 const graph = maker.graph;
497525
498 return serveFile(ws, request, .{526 return serveFile(ws, request, .{
499 .root_dir = graph.zig_lib_directory,527 .root_dir = graph.zig_lib_directory,
...@@ -505,7 +533,7 @@ fn serveClientWasm(...@@ -505,7 +533,7 @@ fn serveClientWasm(
505 req: *http.Server.Request,533 req: *http.Server.Request,
506 optimize_mode: std.builtin.OptimizeMode,534 optimize_mode: std.builtin.OptimizeMode,
507) !void {535) !void {
508 const gpa = ws.maker.gpa;536 const gpa = ws.graph.cache.gpa;
509537
510 var arena_state: std.heap.ArenaAllocator = .init(gpa);538 var arena_state: std.heap.ArenaAllocator = .init(gpa);
511 defer arena_state.deinit();539 defer arena_state.deinit();
...@@ -522,9 +550,9 @@ pub fn serveFile(...@@ -522,9 +550,9 @@ pub fn serveFile(
522 path: Cache.Path,550 path: Cache.Path,
523 content_type: []const u8,551 content_type: []const u8,
524) !void {552) !void {
525 const maker = ws.maker;553 const graph = ws.graph;
526 const gpa = ws.maker.gpa;554 const gpa = graph.cache.gpa;
527 const io = maker.graph.io;555 const io = graph.io;
528556
529 // The desired API is actually sendfile, which will require enhancing http.Server.557 // The desired API is actually sendfile, which will require enhancing http.Server.
530 // We load the file with every request so that the user can make changes to the file558 // We load the file with every request so that the user can make changes to the file
...@@ -542,8 +570,7 @@ pub fn serveFile(...@@ -542,8 +570,7 @@ pub fn serveFile(
542 });570 });
543}571}
544pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void {572pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void {
545 const maker = ws.maker;573 const graph = ws.graph;
546 const graph = maker.graph;
547 const io = graph.io;574 const io = graph.io;
548575
549 var send_buffer: [0x4000]u8 = undefined;576 var send_buffer: [0x4000]u8 = undefined;
...@@ -581,9 +608,8 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -581,9 +608,8 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
581 const arch_os_abi = "wasm32-freestanding";608 const arch_os_abi = "wasm32-freestanding";
582 const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext";609 const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext";
583610
584 const maker = ws.maker;611 const graph = ws.graph;
585 const graph = maker.graph;612 const gpa = graph.cache.gpa;
586 const gpa = maker.gpa;
587 const io = graph.io;613 const io = graph.io;
588614
589 const main_src_path: Cache.Path = .{615 const main_src_path: Cache.Path = .{
...@@ -651,9 +677,11 @@ pub fn updateTimeReportCompile(ws: *WebServer, opts: struct {...@@ -651,9 +677,11 @@ pub fn updateTimeReportCompile(ws: *WebServer, opts: struct {
651 /// The trailing data of `abi.time_report.CompileResult`, except the step name.677 /// The trailing data of `abi.time_report.CompileResult`, except the step name.
652 trailing: []const u8,678 trailing: []const u8,
653}) void {679}) void {
654 const maker = ws.maker;680 const graph = ws.graph;
681 const io = graph.io;
682 const configured = &ws.configured.?;
683 const maker = configured.maker;
655 const gpa = maker.gpa;684 const gpa = maker.gpa;
656 const io = maker.graph.io;
657 const all_steps = maker.step_stack.keys();685 const all_steps = maker.step_stack.keys();
658686
659 const step_idx: u32 = for (all_steps, 0..) |s, i| {687 const step_idx: u32 = for (all_steps, 0..) |s, i| {
...@@ -661,10 +689,10 @@ pub fn updateTimeReportCompile(ws: *WebServer, opts: struct {...@@ -661,10 +689,10 @@ pub fn updateTimeReportCompile(ws: *WebServer, opts: struct {
661 } else unreachable;689 } else unreachable;
662690
663 const old_buf = old: {691 const old_buf = old: {
664 ws.time_report_mutex.lock(io) catch return;692 configured.time_report_mutex.lock(io) catch return;
665 defer ws.time_report_mutex.unlock(io);693 defer configured.time_report_mutex.unlock(io);
666 const old = ws.time_report_msgs[step_idx];694 const old = configured.time_report_msgs[step_idx];
667 ws.time_report_msgs[step_idx] = &.{};695 configured.time_report_msgs[step_idx] = &.{};
668 break :old old;696 break :old old;
669 };697 };
670 const buf = gpa.realloc(old_buf, @sizeOf(abi.time_report.CompileResult) + opts.trailing.len) catch @panic("out of memory");698 const buf = gpa.realloc(old_buf, @sizeOf(abi.time_report.CompileResult) + opts.trailing.len) catch @panic("out of memory");
...@@ -684,19 +712,21 @@ pub fn updateTimeReportCompile(ws: *WebServer, opts: struct {...@@ -684,19 +712,21 @@ pub fn updateTimeReportCompile(ws: *WebServer, opts: struct {
684 @memcpy(buf[@sizeOf(abi.time_report.CompileResult)..], opts.trailing);712 @memcpy(buf[@sizeOf(abi.time_report.CompileResult)..], opts.trailing);
685713
686 {714 {
687 ws.time_report_mutex.lock(io) catch return;715 configured.time_report_mutex.lock(io) catch return;
688 defer ws.time_report_mutex.unlock(io);716 defer configured.time_report_mutex.unlock(io);
689 assert(ws.time_report_msgs[step_idx].len == 0);717 assert(configured.time_report_msgs[step_idx].len == 0);
690 ws.time_report_msgs[step_idx] = buf;718 configured.time_report_msgs[step_idx] = buf;
691 ws.time_report_update_times[step_idx] = ws.now();719 configured.time_report_update_times[step_idx] = ws.now();
692 }720 }
693 ws.notifyUpdate();721 ws.notifyUpdate();
694}722}
695723
696pub fn updateTimeReportGeneric(ws: *WebServer, step_index: Configuration.Step.Index, duration: Io.Duration) void {724pub fn updateTimeReportGeneric(ws: *WebServer, step_index: Configuration.Step.Index, duration: Io.Duration) void {
697 const maker = ws.maker;725 const graph = ws.graph;
726 const io = graph.io;
727 const configured = &ws.configured.?;
728 const maker = configured.maker;
698 const gpa = maker.gpa;729 const gpa = maker.gpa;
699 const io = maker.graph.io;
700 const all_steps = maker.step_stack.keys();730 const all_steps = maker.step_stack.keys();
701731
702 const step_idx: u32 = for (all_steps, 0..) |s, i| {732 const step_idx: u32 = for (all_steps, 0..) |s, i| {
...@@ -704,10 +734,10 @@ pub fn updateTimeReportGeneric(ws: *WebServer, step_index: Configuration.Step.In...@@ -704,10 +734,10 @@ pub fn updateTimeReportGeneric(ws: *WebServer, step_index: Configuration.Step.In
704 } else unreachable;734 } else unreachable;
705735
706 const old_buf = old: {736 const old_buf = old: {
707 ws.time_report_mutex.lock(io) catch return;737 configured.time_report_mutex.lock(io) catch return;
708 defer ws.time_report_mutex.unlock(io);738 defer configured.time_report_mutex.unlock(io);
709 const old = ws.time_report_msgs[step_idx];739 const old = configured.time_report_msgs[step_idx];
710 ws.time_report_msgs[step_idx] = &.{};740 configured.time_report_msgs[step_idx] = &.{};
711 break :old old;741 break :old old;
712 };742 };
713 const buf = gpa.realloc(old_buf, @sizeOf(abi.time_report.GenericResult)) catch @panic("out of memory");743 const buf = gpa.realloc(old_buf, @sizeOf(abi.time_report.GenericResult)) catch @panic("out of memory");
...@@ -717,11 +747,11 @@ pub fn updateTimeReportGeneric(ws: *WebServer, step_index: Configuration.Step.In...@@ -717,11 +747,11 @@ pub fn updateTimeReportGeneric(ws: *WebServer, step_index: Configuration.Step.In
717 .ns_total = @intCast(duration.toNanoseconds()),747 .ns_total = @intCast(duration.toNanoseconds()),
718 };748 };
719 {749 {
720 ws.time_report_mutex.lock(io) catch return;750 configured.time_report_mutex.lock(io) catch return;
721 defer ws.time_report_mutex.unlock(io);751 defer configured.time_report_mutex.unlock(io);
722 assert(ws.time_report_msgs[step_idx].len == 0);752 assert(configured.time_report_msgs[step_idx].len == 0);
723 ws.time_report_msgs[step_idx] = buf;753 configured.time_report_msgs[step_idx] = buf;
724 ws.time_report_update_times[step_idx] = ws.now();754 configured.time_report_update_times[step_idx] = ws.now();
725 }755 }
726 ws.notifyUpdate();756 ws.notifyUpdate();
727}757}
...@@ -732,9 +762,11 @@ pub fn updateTimeReportRunTest(...@@ -732,9 +762,11 @@ pub fn updateTimeReportRunTest(
732 tests: *const Step.Run.CachedTestMetadata,762 tests: *const Step.Run.CachedTestMetadata,
733 ns_per_test: []const u64,763 ns_per_test: []const u64,
734) void {764) void {
735 const maker = ws.maker;765 const graph = ws.graph;
766 const io = graph.io;
767 const configured = &ws.configured.?;
768 const maker = configured.maker;
736 const gpa = maker.gpa;769 const gpa = maker.gpa;
737 const io = maker.graph.io;
738 const all_steps = maker.step_stack.keys();770 const all_steps = maker.step_stack.keys();
739771
740 const step_idx: u32 = for (all_steps, 0..) |s, i| {772 const step_idx: u32 = for (all_steps, 0..) |s, i| {
...@@ -752,10 +784,10 @@ pub fn updateTimeReportRunTest(...@@ -752,10 +784,10 @@ pub fn updateTimeReportRunTest(
752 break :len @sizeOf(abi.time_report.RunTestResult) + names_len + 8 * tests_len;784 break :len @sizeOf(abi.time_report.RunTestResult) + names_len + 8 * tests_len;
753 };785 };
754 const old_buf = old: {786 const old_buf = old: {
755 ws.time_report_mutex.lock(io) catch return;787 configured.time_report_mutex.lock(io) catch return;
756 defer ws.time_report_mutex.unlock(io);788 defer configured.time_report_mutex.unlock(io);
757 const old = ws.time_report_msgs[step_idx];789 const old = configured.time_report_msgs[step_idx];
758 ws.time_report_msgs[step_idx] = &.{};790 configured.time_report_msgs[step_idx] = &.{};
759 break :old old;791 break :old old;
760 };792 };
761 const buf = gpa.realloc(old_buf, new_len) catch @panic("out of memory");793 const buf = gpa.realloc(old_buf, new_len) catch @panic("out of memory");
...@@ -778,11 +810,11 @@ pub fn updateTimeReportRunTest(...@@ -778,11 +810,11 @@ pub fn updateTimeReportRunTest(
778 assert(offset == buf.len);810 assert(offset == buf.len);
779811
780 {812 {
781 ws.time_report_mutex.lock(io) catch return;813 configured.time_report_mutex.lock(io) catch return;
782 defer ws.time_report_mutex.unlock(io);814 defer configured.time_report_mutex.unlock(io);
783 assert(ws.time_report_msgs[step_idx].len == 0);815 assert(configured.time_report_msgs[step_idx].len == 0);
784 ws.time_report_msgs[step_idx] = buf;816 configured.time_report_msgs[step_idx] = buf;
785 ws.time_report_update_times[step_idx] = ws.now();817 configured.time_report_update_times[step_idx] = ws.now();
786 }818 }
787 ws.notifyUpdate();819 ws.notifyUpdate();
788}820}
...@@ -791,7 +823,7 @@ const RunnerRequest = union(enum) {...@@ -791,7 +823,7 @@ const RunnerRequest = union(enum) {
791 rebuild,823 rebuild,
792};824};
793pub fn getRunnerRequest(ws: *WebServer) ?RunnerRequest {825pub fn getRunnerRequest(ws: *WebServer) ?RunnerRequest {
794 const io = ws.maker.graph.io;826 const io = ws.graph.io;
795 ws.runner_request_mutex.lock(io) catch return;827 ws.runner_request_mutex.lock(io) catch return;
796 defer ws.runner_request_mutex.unlock(io);828 defer ws.runner_request_mutex.unlock(io);
797 if (ws.runner_request) |req| {829 if (ws.runner_request) |req| {
...@@ -802,7 +834,7 @@ pub fn getRunnerRequest(ws: *WebServer) ?RunnerRequest {...@@ -802,7 +834,7 @@ pub fn getRunnerRequest(ws: *WebServer) ?RunnerRequest {
802 return null;834 return null;
803}835}
804pub fn wait(ws: *WebServer) Io.Cancelable!RunnerRequest {836pub fn wait(ws: *WebServer) Io.Cancelable!RunnerRequest {
805 const io = ws.maker.graph.io;837 const io = ws.graph.io;
806 try ws.runner_request_mutex.lock(io);838 try ws.runner_request_mutex.lock(io);
807 defer ws.runner_request_mutex.unlock(io);839 defer ws.runner_request_mutex.unlock(io);
808 while (true) {840 while (true) {