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,
5252skip_oom_steps: bool,
5353unit_test_timeout_ns: ?u64,
5454watch: bool,
55web_server: if (!builtin.single_threaded) ?WebServer else ?noreturn,
55web_server: ?*AvoidableWebServer,
5656/// Allocated into `gpa`.
5757memory_blocked_steps: std.ArrayList(Configuration.Step.Index),
5858/// Allocated into `gpa`.
......@@ -68,6 +68,8 @@ var stdio_buffer_allocation: [256]u8 = undefined;
6868var stdout_writer_allocation: Io.File.Writer = undefined;
6969var debug_maker_leaks: bool = false;
7070
71const AvoidableWebServer = if (builtin.single_threaded) void else WebServer;
72
7173const is_debug_mode = builtin.mode == .Debug;
7274const use_safe_allocator = switch (builtin.mode) {
7375 .Debug, .ReleaseSafe => true,
......@@ -106,6 +108,7 @@ const ErrorStyle = enum {
106108};
107109const MultilineErrors = enum { indent, newline, none };
108110const Summary = enum { all, new, failures, line, none };
111const PrintConfiguration = enum { none, zon, path };
109112
110113/// Used to build the -M flags to pass to build-exe.
111114pub const CliModule = struct {
......@@ -195,7 +198,7 @@ pub fn main(init: process.Init.Minimal) !void {
195198 var step_names: std.ArrayList([]const u8) = .empty;
196199 var help_menu = false;
197200 var steps_menu = false;
198 var print_configuration: enum { none, zon, path } = .none;
201 var print_configuration: PrintConfiguration = .none;
199202 var override_install_prefix: ?[]const u8 = null;
200203 var override_lib_dir: ?[]const u8 = null;
201204 var override_bin_dir: ?[]const u8 = null;
......@@ -546,6 +549,9 @@ pub fn main(init: process.Init.Minimal) !void {
546549 }
547550 }
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
549555 const cwd_path = std.zig.getResolvedCwd(io, arena) catch |err|
550556 fatal("resolving current directory path failed: {t}", .{err});
551557
......@@ -593,752 +599,809 @@ pub fn main(init: process.Init.Minimal) !void {
593599 .off => .no_color,
594600 };
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
596612 const main_progress_node = std.Progress.start(io, .{
597613 .disable_printing = (graph.stderr_mode.? == .no_color),
598614 });
599615 defer main_progress_node.end();
600616
601 const scanned_config: ScannedConfig = sc: {
602 // Cache lookup for configure options. If we get a match, we can skip
603 // execution of the configure script. If not, we get the file path to pass
604 // to the configure process.
605 //
606 // In the hot path, we only check this cache, which means that also
607 // configure source files need to go in here.
608 var config_man = graph.cache.obtain();
609 defer config_man.deinit();
610
611 for (cached_passthru_configure.items) |i|
612 config_man.hash.addBytes(configure_argv.items[i]);
613
614 // Prevents a `zig build` from getting a false positive cache hit following
615 // a `zig build --cache-poison=ignored`.
616 config_man.hash.add(cache_poison == .ignored);
617
618 const pkg_root: Path = if (override_pkg_dir) |p|
619 .initCwd(p)
620 else if (system_pkg_dir_path) |p|
621 .initCwd(p)
622 else
623 .{
624 .root_dir = build_root.directory,
625 .sub_path = "zig-pkg",
617 const install_prefix_path: Path = if (graph.environ_map.get("DESTDIR")) |dest_dir| .{
618 .root_dir = .cwd(),
619 .sub_path = try Dir.path.join(arena, &.{ dest_dir, override_install_prefix orelse "/usr" }),
620 } else if (override_install_prefix) |cwd_relative| .{
621 .root_dir = .cwd(),
622 .sub_path = cwd_relative,
623 } else .{
624 .root_dir = graph.build_root_directory,
625 .sub_path = "zig-out",
626 };
627
628 const install_lib_path: Path = if (override_lib_dir) |cwd_relative| .{
629 .root_dir = .cwd(),
630 .sub_path = cwd_relative,
631 } else try install_prefix_path.join(arena, "lib");
632
633 const install_bin_path: Path = if (override_bin_dir) |cwd_relative| .{
634 .root_dir = .cwd(),
635 .sub_path = cwd_relative,
636 } else try install_prefix_path.join(arena, "bin");
637
638 const install_include_path: Path = if (override_include_dir) |cwd_relative| .{
639 .root_dir = .cwd(),
640 .sub_path = cwd_relative,
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);
626758 };
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 };
631 defer http_client.deinit();
762 rebuild: while (true) : (if (maker.error_style.clearOnUpdate()) {
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 = .{};
634 var fork_set: Package.Fetch.JobQueue.ForkSet = .{};
771 try maker.makeStepNames(step_names.items, main_progress_node, fuzz);
635772
636 {
637 // Populate fork_set.
638 var group: Io.Group = .init;
639 defer group.cancel(io);
640
641 for (forks.items) |*fork|
642 group.async(io, Fork.load, .{ io, gpa, fork, color });
643
644 try group.await(io);
645
646 for (forks.items) |*fork| {
647 if (fork.failed) process.exit(1);
648 try fork_set.put(arena, .{
649 .path = fork.path,
650 .manifest_ast = fork.manifest_ast,
651 .manifest = fork.manifest,
652 .uses = 0,
653 }, {});
773 if (web_server) |ws| {
774 if (fuzz) |mode| if (mode != .forever) fatal(
775 "error: limited fuzzing is not implemented yet for --webui",
776 .{},
777 );
778
779 ws.finishBuild(.{ .fuzz = fuzz != null });
780 }
781
782 if (web_server) |ws| {
783 const c = &scanned_config.configuration;
784 assert(!watch); // fatal error after CLI parsing
785 while (true) switch (try ws.wait()) {
786 .rebuild => {
787 for (maker.step_stack.keys()) |step_index| {
788 const step = maker.stepByIndex(step_index);
789 step.state = .precheck_done;
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");
654846 }
655847 }
656 defer Fork.deinitList(forks.items);
848 }
849}
657850
658 var build_configurer_argv: std.ArrayList([]const u8) = .empty;
659 defer build_configurer_argv.deinit(gpa);
851const ConfigureOptions = struct {
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;
662 defer dependencies_source.deinit(gpa);
856 cache_poison: std.Build.Graph.CachePoison,
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 = .{
665 .root_dir = graph.zig_lib_directory,
666 .sub_path = "compiler/configurer.zig",
667 };
870fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
871 const configure_argv = options.configure_argv;
872 const gpa = graph.cache.gpa;
873 const io = graph.io;
874 const arena = graph.arena;
668875
669 const root_build_src_path: Cache.Path = .{
670 .root_dir = build_root.directory,
671 .sub_path = build_root.build_zig_basename,
672 };
876 // Cache lookup for configure options. If we get a match, we can skip
877 // execution of the configure script. If not, we get the file path to pass
878 // to the configure process.
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, &.{
677 graph.zig_exe, "build-exe", //
678 "--cache-dir", graph.local_cache_root.path orelse ".", //
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 });
888 // Prevents a `zig build` from getting a false positive cache hit following
889 // a `zig build --cache-poison=ignored`.
890 config_man.hash.add(options.cache_poison == .ignored);
684891
685 // Normally the build runner is compiled for the host target but here is
686 // some code to help when debugging edits to the build runner so that you
687 // can make sure it compiles successfully on other targets.
688 const target_arch_os_abi: ?[]const u8 = if (debug_target) |triple| t: {
689 config_man.hash.addBytes(triple);
690 try build_configurer_argv.appendSlice(gpa, &.{ "-target", triple });
691 break :t triple;
692 } else null;
693
694 if (graph.libc_file) |libc_file| {
695 try build_configurer_argv.appendSlice(gpa, &.{ "--libc", libc_file });
696 }
697 if (graph.reference_trace) |n| {
698 try build_configurer_argv.append(gpa, try allocPrint(arena, "-freference-trace={d}", .{n}));
699 }
700 if (graph.debug_compile_errors) {
701 try build_configurer_argv.append(gpa, "--debug-compile-errors");
892 configure_argv[options.conf_argv_index_build_root] = options.build_root.directory.path orelse options.cwd_path;
893
894 var http_client: std.http.Client = .{ .allocator = gpa, .io = io };
895 defer http_client.deinit();
896
897 var unlazy_set: Package.Fetch.JobQueue.UnlazySet = .{};
898 var fork_set: Package.Fetch.JobQueue.ForkSet = .{};
899
900 {
901 // Populate fork_set.
902 var group: Io.Group = .init;
903 defer group.cancel(io);
904
905 for (options.forks) |*fork|
906 group.async(io, Fork.load, .{ io, gpa, fork, options.color });
907
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 }, {});
702918 }
703 try build_configurer_argv.appendSlice(gpa, &.{
704 "--dep", "@build", //
705 "--dep", "@dependencies", //
706 try allocPrint(arena, "-Mroot={f}", .{configurer_root_src_path}), //
707 });
919 }
920 defer Fork.deinitList(options.forks);
708921
709 // In the loop below, after doing the fetch operation, the argv will be
710 // truncated at this point, dependencies added, and then the
711 // "--listen=-" arg appended at the end.
712 const argv_deps_index = build_configurer_argv.items.len;
922 var build_configurer_argv: std.ArrayList([]const u8) = .empty;
923 defer build_configurer_argv.deinit(gpa);
713924
714 const build_mod = try arena.create(CliModule);
715 build_mod.* = .{
716 .name = "@build",
717 .root_path = try root_build_src_path.toString(arena),
718 };
925 var dependencies_source: std.ArrayList(u8) = .empty;
926 defer dependencies_source.deinit(gpa);
719927
720 const deps_mod = try arena.create(CliModule);
721 deps_mod.* = .{
722 .name = "@dependencies",
723 .root_path = undefined,
724 };
928 const configurer_root_src_path: Cache.Path = .{
929 .root_dir = graph.zig_lib_directory,
930 .sub_path = "compiler/configurer.zig",
931 };
725932
726 // This loop is re-evaluated when the build script exits with an indication that it
727 // could not continue due to missing lazy dependencies.
728 const configuration_path: Path, const poisoned: bool = cp: while (true) {
729 build_mod.deps.clearRetainingCapacity();
730 deps_mod.deps.clearRetainingCapacity();
933 const root_build_src_path: Cache.Path = .{
934 .root_dir = options.build_root.directory,
935 .sub_path = options.build_root.build_zig_basename,
936 };
731937
732 // We want to release all the locks before executing the child process, so we make a nice
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();
938 const configurer_exe_name = "configurer";
759939
760 if (system_pkg_dir_path == null) {
761 try http_client.initDefaultProxies(arena, &graph.environ_map);
762 }
940 try build_configurer_argv.appendSlice(gpa, &.{
941 graph.zig_exe, "build-exe", //
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);
765 try job_queue.table.ensureUnusedCapacity(gpa, 1);
766
767 const phantom_package_root: Cache.Path = .{ .root_dir = build_root.directory };
768
769 var fetch: Package.Fetch = .{
770 .arena = std.heap.ArenaAllocator.init(gpa),
771 .location = .{ .relative_path = phantom_package_root },
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 };
949 // Normally the build runner is compiled for the host target but here is
950 // some code to help when debugging edits to the build runner so that you
951 // can make sure it compiles successfully on other targets.
952 const target_arch_os_abi: ?[]const u8 = if (options.debug_target) |triple| t: {
953 config_man.hash.addBytes(triple);
954 try build_configurer_argv.appendSlice(gpa, &.{ "-target", triple });
955 break :t triple;
956 } else null;
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(
801 Package.Fetch.relativePathDigest(phantom_package_root, graph.global_cache_root),
802 &fetch,
803 );
973 // In the loop below, after doing the fetch operation, the argv will be
974 // truncated at this point, dependencies added, and then the
975 // "--listen=-" arg appended at the end.
976 const argv_deps_index = build_configurer_argv.items.len;
804977
805 job_queue.group.async(io, Package.Fetch.workerRun, .{ &fetch, "root" });
806 try job_queue.group.await(io);
978 const build_mod = try arena.create(CliModule);
979 build_mod.* = .{
980 .name = "@build",
981 .root_path = try root_build_src_path.toString(arena),
982 };
807983
808 {
809 // Ensure that forks were actually used. This is done
810 // before printing manifest errors because using a fork can
811 // prevent them.
812 var any_unused = false;
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 }
984 const deps_mod = try arena.create(CliModule);
985 deps_mod.* = .{
986 .name = "@dependencies",
987 .root_path = undefined,
988 };
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) {
831 var errors = try fetch.error_bundle.toOwnedBundle("");
832 // TODO when watching, watch and rebuild configure script rather than exit here
833 errors.renderToStderr(io, .{}, color) catch {};
834 process.exit(1);
835 }
996 // We want to release all the locks before executing the child process, so we make a nice
997 // big block here to ensure the cleanup gets run when we extract out our argv.
998 {
999 {
1000 const fetch_prog_node = options.parent_progress_node.start("Fetch Packages", 0);
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 to
840 // obtain via `@import("@dependencies")`.
841 {
842 {
843 dependencies_source.clearRetainingCapacity();
844 var source_writer: Io.Writer.Allocating = .fromArrayList(gpa, &dependencies_source);
845 defer dependencies_source = source_writer.toArrayList();
846 job_queue.createDependenciesSource(&source_writer.writer) catch |err| switch (err) {
847 error.WriteFailed => return error.OutOfMemory,
848 };
849 }
850 // Atomically create the file in a directory named after the hash of its contents.
851 var hh: Cache.HashHelper = .{};
852 hh.addBytes(builtin.zig_version_string);
853 hh.addBytes(dependencies_source.items);
854 const hex_digest = hh.final();
855 const dependencies_zig_path: Path = .{
856 .root_dir = graph.local_cache_root,
857 .sub_path = try allocPrint(arena, "o/{s}/dependencies.zig", .{&hex_digest}),
858 };
859 var atomic_file = try dependencies_zig_path.root_dir.handle.createFileAtomic(
860 io,
861 dependencies_zig_path.sub_path,
862 .{ .make_path = true, .replace = true },
863 );
864 defer atomic_file.deinit(io);
865 atomic_file.file.writeStreamingAll(io, dependencies_source.items) catch |err|
866 fatal("writing dependencies.zig contents: {t}", .{err});
867 atomic_file.replace(io) catch |err|
868 fatal("replacing {f}: {t}", .{ dependencies_zig_path, err });
869
870 deps_mod.root_path = try dependencies_zig_path.toString(arena);
871 }
1028 try job_queue.all_fetches.ensureUnusedCapacity(gpa, 1);
1029 try job_queue.table.ensureUnusedCapacity(gpa, 1);
1030
1031 const phantom_package_root: Cache.Path = .{ .root_dir = options.build_root.directory };
1032
1033 var fetch: Package.Fetch = .{
1034 .arena = std.heap.ArenaAllocator.init(gpa),
1035 .location = .{ .relative_path = phantom_package_root },
1036 .location_tok = 0,
1037 .hash_tok = .none,
1038 .name_tok = 0,
1039 .lazy_status = .eager,
1040 .remote_package_root = phantom_package_root,
1041 .parent_package_root = phantom_package_root,
1042 .parent_manifest_ast = null,
1043 .prog_node = fetch_prog_node,
1044 .job_queue = &job_queue,
1045 .omit_missing_hash_error = true,
1046 .allow_missing_paths_field = false,
1047 .use_latest_commit = false,
1048
1049 .package_root = undefined,
1050 .error_bundle = undefined,
1051 .manifest = undefined,
1052 .manifest_ast = undefined,
1053 .have_manifest = false,
1054 .computed_hash = undefined,
1055 .has_build_zig = true,
1056 .oom_flag = false,
1057 .latest_commit = null,
1058
1059 .cli_module = build_mod,
1060 };
8721061
873 {
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 }
1062 job_queue.all_fetches.appendAssumeCapacity(&fetch);
8951063
896 // Each build.zig module needs access to each of its
897 // dependencies' build.zig modules by name.
898 for (fetches) |f| {
899 const mod = f.cli_module orelse continue;
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 }
1064 job_queue.table.putAssumeCapacityNoClobber(
1065 Package.Fetch.relativePathDigest(phantom_package_root, graph.global_cache_root),
1066 &fetch,
1067 );
9161068
917 // Lower module dependencies to CLI argv.
918 build_configurer_argv.shrinkRetainingCapacity(argv_deps_index);
919 for (deps_mod.deps.values()) |dep| {
920 try build_configurer_argv.ensureUnusedCapacity(gpa, 2 * dep.deps.count() + 1);
921 for (dep.deps.keys(), dep.deps.values()) |name, sub| {
922 build_configurer_argv.appendAssumeCapacity("--dep");
923 if (mem.eql(u8, name, sub.name)) {
924 build_configurer_argv.appendAssumeCapacity(sub.name);
925 } else {
926 build_configurer_argv.appendAssumeCapacity(try allocPrint(arena, "{s}={s}", .{
927 name, sub.name,
928 }));
929 }
1069 job_queue.group.async(io, Package.Fetch.workerRun, .{ &fetch, "root" });
1070 try job_queue.group.await(io);
1071
1072 {
1073 // Ensure that forks were actually used. This is done
1074 // before printing manifest errors because using a fork can
1075 // prevent them.
1076 var any_unused = false;
1077 for (fork_set.keys()) |*fork| {
1078 if (fork.uses == 0) {
1079 log.err("fork {f} matched no {s} packages", .{
1080 fork.path, fork.manifest.name,
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 });
9301087 }
931 build_configurer_argv.appendAssumeCapacity(try allocPrint(arena, "-M{s}={s}/{s}", .{
932 dep.name, dep.root_path, std.zig.build_zig_basename,
933 }));
9341088 }
935 try deps_mod.lower(arena, gpa, &build_configurer_argv);
936 try build_mod.lower(arena, gpa, &build_configurer_argv);
1089 if (any_unused) process.exit(1);
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;
9391098 }
9401099
941 const compile_prog_node = main_progress_node.start("Compile Configure Script", 0);
942 defer compile_prog_node.end();
943
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.
1100 if (options.fetch_only) {
1101 _ = io.lockStderr(&.{}, .no_color) catch {};
1102 process.exit(0);
9561103 }
9571104
958 const configure_exe_path: Path = if (std.zig.buildExeSubprocess(gpa, io, .{
959 .argv = build_configurer_argv.items,
960 .cache_root = graph.local_cache_root,
961 .root_name = configurer_exe_name,
962 .environ_map = &graph.environ_map,
963 .cache_manifest = &config_man,
964 .arch_os_abi = target_arch_os_abi,
965 .progress_node = compile_prog_node,
966 })) |r| r.path else |err| switch (err) {
967 error.AlreadyReported => process.exit(1),
968 // If the file system inputs are populated, we can
969 // still watch for changes and try again.
970 error.FailedButCacheIntact => @panic("TODO"),
971 error.Canceled, error.OutOfMemory => |e| return e,
972 };
973 defer gpa.free(configure_exe_path.sub_path);
1105 // Create the dependencies.zig file for configurer to
1106 // obtain via `@import("@dependencies")`.
1107 {
1108 {
1109 dependencies_source.clearRetainingCapacity();
1110 var source_writer: Io.Writer.Allocating = .fromArrayList(gpa, &dependencies_source);
1111 defer dependencies_source = source_writer.toArrayList();
1112 job_queue.createDependenciesSource(&source_writer.writer) catch |err| switch (err) {
1113 error.WriteFailed => return error.OutOfMemory,
1114 };
1115 }
1116 // Atomically create the file in a directory named after the hash of its contents.
1117 var hh: Cache.HashHelper = .{};
1118 hh.addBytes(builtin.zig_version_string);
1119 hh.addBytes(dependencies_source.items);
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);
976 }
1136 deps_mod.root_path = try dependencies_zig_path.toString(arena);
1137 }
9771138
978 if (!process.can_spawn) {
979 fatal("cannot spawn command on {t}: {f}", .{ native_os, @as(std.zig.SubprocessCommand, .{
980 .argv = configure_argv.items,
981 }) });
982 }
1139 {
1140 // Add a CliModule for each package's build.zig.
1141 const hashes = job_queue.table.keys();
1142 const fetches = job_queue.table.values();
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);
985 const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
986 const config_tmp_path: Path = .{
987 .root_dir = graph.local_cache_root,
988 .sub_path = tmp_dir_sub_path,
989 };
990 const config_tmp_file: Io.File = try config_tmp_path.root_dir.handle.createFile(
991 io,
992 config_tmp_path.sub_path,
993 .{ .read = true, .exclusive = true },
994 );
995 defer config_tmp_file.close(io);
996
997 const term = term: {
998 const child_node = main_progress_node.start("Run Configure Script", 0);
999 defer child_node.end();
1000 var child = process.spawn(io, .{
1001 .argv = configure_argv.items,
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;
1162 // Each build.zig module needs access to each of its
1163 // dependencies' build.zig modules by name.
1164 for (fetches) |f| {
1165 const mod = f.cli_module orelse continue;
1166 if (!f.have_manifest) continue;
1167 const man = &f.manifest;
1168 const dep_names = man.dependencies.keys();
1169 try mod.deps.ensureUnusedCapacity(arena, @intCast(dep_names.len));
1170 for (dep_names, man.dependencies.values()) |name, dep| {
1171 const dep_digest = Package.Fetch.depDigest(
1172 f.package_root,
1173 graph.global_cache_root,
1174 dep,
1175 ) orelse continue;
1176 const dep_mod = job_queue.table.get(dep_digest).?.cli_module orelse continue;
1177 const name_cloned = try arena.dupe(u8, name);
1178 mod.deps.putAssumeCapacityNoClobber(name_cloned, dep_mod);
1179 }
10331180 }
1034 try unlazy_set.put(arena, .fromSlice(hash), {});
10351181 }
1036 if (any_errors) process.exit(1);
1037 if (system_pkg_dir_path) |p| {
1038 // In this mode, the system needs to provide these packages; they
1039 // cannot be fetched by Zig.
1040 const s = Dir.path.sep_str;
1041 for (unlazy_set.keys()) |*hash| {
1042 log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{ p, hash.toSlice() });
1182
1183 // Lower module dependencies to CLI argv.
1184 build_configurer_argv.shrinkRetainingCapacity(argv_deps_index);
1185 for (deps_mod.deps.values()) |dep| {
1186 try build_configurer_argv.ensureUnusedCapacity(gpa, 2 * dep.deps.count() + 1);
1187 for (dep.deps.keys(), dep.deps.values()) |name, sub| {
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 }
10431196 }
1044 log.info("remote package fetching disabled due to --system mode", .{});
1045 log.info("dependencies might be avoidable depending on build configuration", .{});
1046 process.exit(1);
1197 build_configurer_argv.appendAssumeCapacity(try allocPrint(arena, "-M{s}={s}/{s}", .{
1198 dep.name, dep.root_path, std.zig.build_zig_basename,
1199 }));
10471200 }
1048 continue :cp;
1049 }
1201 try deps_mod.lower(arena, gpa, &build_configurer_argv);
1202 try build_mod.lower(arena, gpa, &build_configurer_argv);
10501203
1051 for (configuration.path_deps) |path_dep| {
1052 try config_man.addPathPost(path_dep.toCachePath(&configuration, arena));
1204 try build_configurer_argv.append(gpa, "--listen=-");
10531205 }
10541206
1055 // If it is poisoned, there is no point in moving it to cached
1056 // location. Just leave it in the tmp directory.
1057 if (configuration.poisoned) {
1058 break :cp .{ config_tmp_path, true };
1059 } else {
1060 const digest = config_man.final();
1061 const final_path: Path = .{
1062 .root_dir = graph.local_cache_root,
1063 .sub_path = try allocPrint(arena, "c/{s}", .{&digest}),
1064 };
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;
1207 const compile_prog_node = options.parent_progress_node.start("Compile Configure Script", 0);
1208 defer compile_prog_node.end();
1209
1210 switch (options.cache_poison) {
1211 .pure, .disallowed, .ignored => if (try config_man.hit()) {
1212 const digest = config_man.final();
1213 break :cp .{
1214 .{
1215 .root_dir = graph.local_cache_root,
1216 .sub_path = try allocPrint(arena, "c/{s}", .{&digest}),
10841217 },
1085 else => |e| e,
1218 false,
10861219 };
1087 fatal("failed to rename configuration file from {f} into {f}: {t}", .{
1088 config_tmp_path, final_path, e,
1089 });
1090 };
1091 config_man.writeManifest() catch |err| log.warn("failed to write cache manifest: {t}", .{err});
1092 break :cp .{ final_path, false };
1220 },
1221 .poisoned => {}, // Don't bother checking for cache hit.
10931222 }
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) {
1101 .path => {
1102 initStdoutWriter(io).print("{f}\n", .{configuration_path}) catch
1103 fatal("failed printing cache file path: {t}", .{stdout_writer_allocation.err.?});
1104 stdout_writer_allocation.flush() catch |err|
1105 fatal("failed printing cache file path: {t}", .{err});
1106 return process.cleanExit(io);
1107 },
1108 .none, .zon => {},
1224 const configure_exe_path: Path = if (std.zig.buildExeSubprocess(gpa, io, .{
1225 .argv = build_configurer_argv.items,
1226 .cache_root = graph.local_cache_root,
1227 .root_name = configurer_exe_name,
1228 .environ_map = &graph.environ_map,
1229 .cache_manifest = &config_man,
1230 .arch_os_abi = target_arch_os_abi,
1231 .progress_node = compile_prog_node,
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);
11091236 }
11101237
1111 const configuration = c: {
1112 var file = configuration_path.root_dir.handle.openFile(io, configuration_path.sub_path, .{}) catch |err|
1113 fatal("failed to open configuration file {f}: {t}", .{ configuration_path, err });
1114 defer file.close(io);
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));
1238 if (!process.can_spawn) {
1239 fatal("cannot spawn command on {t}: {f}", .{ native_os, @as(std.zig.SubprocessCommand, .{
1240 .argv = configure_argv,
1241 }) });
11381242 }
1139 break :sc .{
1140 .configuration = configuration,
1141 .top_level_steps = top_level_steps,
1142 .path = configuration_path,
1143 };
1144 };
11451243
1146 if (help_menu) {
1147 scanned_config.printUsage(&graph, initStdoutWriter(io)) catch |err| switch (err) {
1148 error.WriteFailed => return stdout_writer_allocation.err.?,
1149 else => |e| return e,
1244 const rand_int = randInt(io, u64);
1245 const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
1246 const config_tmp_path: Path = .{
1247 .root_dir = graph.local_cache_root,
1248 .sub_path = tmp_dir_sub_path,
11501249 };
1151 try stdout_writer_allocation.flush();
1152 return cleanExit(io, &scanned_config);
1153 } else if (steps_menu) {
1154 scanned_config.printSteps(&graph, initStdoutWriter(io)) catch |err| switch (err) {
1155 error.WriteFailed => return stdout_writer_allocation.err.?,
1156 else => |e| return e,
1250 const config_tmp_file: Io.File = try config_tmp_path.root_dir.handle.createFile(
1251 io,
1252 config_tmp_path.sub_path,
1253 .{ .read = true, .exclusive = true },
1254 );
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 });
11571268 };
1158 try stdout_writer_allocation.flush();
1159 return cleanExit(io, &scanned_config);
1160 } else switch (print_configuration) {
1161 .none => {},
1162 .zon => {
1163 scanned_config.print(initStdoutWriter(io)) catch return stdout_writer_allocation.err.?;
1164 try stdout_writer_allocation.flush();
1165 return cleanExit(io, &scanned_config);
1166 },
1167 .path => unreachable,
1168 }
1269 if (!term.success()) {
1270 // Failure to produce the configuration file.
1271 fatal("configure command {f}: {f}", .{ term, @as(std.zig.SubprocessCommand, .{
1272 .argv = configure_argv,
1273 }) });
1274 }
1275 // Even though the file is designed to be sent directly to make
1276 // runner, we must load it now because:
1277 // * If it contains additional file dependencies, we need to
1278 // add them to `config_man` before obtaining the final digest.
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) {
1171 if (watch) fatal("using '--webui' and '--watch' together is not yet supported; consider omitting '--watch' in favour of the web UI \"Rebuild\" button", .{});
1172 if (builtin.single_threaded) fatal("'--webui' is not yet supported on single-threaded hosts", .{});
1173 }
1311 for (configuration.path_deps) |path_dep| {
1312 try config_man.addPathPost(path_dep.toCachePath(&configuration, arena));
1313 }
11741314
1175 const install_prefix_path: Path = if (graph.environ_map.get("DESTDIR")) |dest_dir| .{
1176 .root_dir = .cwd(),
1177 .sub_path = try Dir.path.join(arena, &.{ dest_dir, override_install_prefix orelse "/usr" }),
1178 } else if (override_install_prefix) |cwd_relative| .{
1179 .root_dir = .cwd(),
1180 .sub_path = cwd_relative,
1181 } else .{
1182 .root_dir = graph.build_root_directory,
1183 .sub_path = "zig-out",
1315 // If it is poisoned, there is no point in moving it to cached
1316 // location. Just leave it in the tmp directory.
1317 if (configuration.poisoned) {
1318 break :cp .{ config_tmp_path, true };
1319 } else {
1320 const digest = config_man.final();
1321 const final_path: Path = .{
1322 .root_dir = graph.local_cache_root,
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 }
11841354 };
11851355
1186 const install_lib_path: Path = if (override_lib_dir) |cwd_relative| .{
1187 .root_dir = .cwd(),
1188 .sub_path = cwd_relative,
1189 } else try install_prefix_path.join(arena, "lib");
1190
1191 const install_bin_path: Path = if (override_bin_dir) |cwd_relative| .{
1192 .root_dir = .cwd(),
1193 .sub_path = cwd_relative,
1194 } else try install_prefix_path.join(arena, "bin");
1195
1196 const install_include_path: Path = if (override_include_dir) |cwd_relative| .{
1197 .root_dir = .cwd(),
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,
1356 // Hang on to the configuration file lock until we finish loading the configuration file.
1357 var configuration_lock = if (!poisoned) config_man.toOwnedLock() else null;
1358 defer if (configuration_lock) |*l| l.release(io);
1359
1360 switch (options.print_configuration) {
1361 .path => {
1362 initStdoutWriter(io).print("{f}\n", .{configuration_path}) catch
1363 fatal("failed printing cache file path: {t}", .{stdout_writer_allocation.err.?});
1364 stdout_writer_allocation.flush() catch |err|
1365 fatal("failed printing cache file path: {t}", .{err});
1366 _ = io.lockStderr(&.{}, .no_color) catch {};
1367 process.exit(0);
12101368 },
1211
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;
1369 .none, .zon => {},
12401370 }
12411371
1242 maker.prepare(step_names.items) catch |err| switch (err) {
1243 error.DependencyLoopDetected, error.InsufficientMemory => {
1244 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
1245 process.exit(1);
1246 },
1247 else => |e| return e,
1372 const configuration = c: {
1373 var file = configuration_path.root_dir.handle.openFile(io, configuration_path.sub_path, .{}) catch |err|
1374 fatal("failed to open configuration file {f}: {t}", .{ configuration_path, err });
1375 defer file.close(io);
1376 break :c Configuration.loadFile(arena, io, file) catch |err|
1377 fatal("failed to load configuration file {f}: {t}", .{ configuration_path, err });
12481378 };
1249
1250 var w: Watch = w: {
1251 if (!watch) break :w undefined;
1252 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{native_os});
1253 break :w try .init(&maker);
1254 };
1255
1256 const now = Io.Clock.Timestamp.now(io, .awake);
1257
1258 maker.web_server = if (webui_listen) |listen_address| ws: {
1259 if (builtin.single_threaded) unreachable; // `fatal` above
1260 break :ws .init(.{
1261 .maker = &maker,
1262 .root_prog_node = main_progress_node,
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);
1379 // Technically if the configuration is marked as poisoned, we could
1380 // already delete the file now, but we leave it around in case the
1381 // maker process fails or crashes and it's helpful to be able to repeat
1382 // execution of the command line or otherwise inspect the configuration file.
1383 const c = &configuration;
1384 var top_level_steps: std.array_hash_map.String(Configuration.Step.Index) = .empty;
1385 for (configuration.steps, 0..) |*conf_step, step_index_usize| {
1386 if (conf_step.owner != .root) continue;
1387 const step_index: Configuration.Step.Index = @enumFromInt(step_index_usize);
1388 const flags = conf_step.flags(c);
1389 switch (flags.tag) {
1390 .top_level => {
1391 const name = step_index.ptr(c).name.slice(c);
1392 try top_level_steps.put(arena, name, step_index);
13381393 },
1339 .clean => {},
1340 };
1394 else => {},
1395 }
1396 }
1397 for (c.search_prefixes) |search_prefix| {
1398 try graph.search_prefixes.append(arena, search_prefix.slice(c));
13411399 }
1400 return .{
1401 .configuration = configuration,
1402 .top_level_steps = top_level_steps,
1403 .path = configuration_path,
1404 };
13421405}
13431406
13441407fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
......@@ -1894,7 +1957,7 @@ fn prepare(maker: *Maker, step_names: []const []const u8) !void {
18941957fn makeStepNames(
18951958 maker: *Maker,
18961959 step_names: []const []const u8,
1897 parent_prog_node: std.Progress.Node,
1960 parent_progress_node: std.Progress.Node,
18981961 fuzz: ?Fuzz.Mode,
18991962) !void {
19001963 const graph = maker.graph;
......@@ -1918,7 +1981,7 @@ fn makeStepNames(
19181981 }
19191982 }
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());
19221985 defer step_prog.end();
19231986
19241987 var group: Io.Group = .init;
......@@ -1998,7 +2061,7 @@ fn makeStepNames(
19982061 }
19992062
20002063 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|
20022065 fatal("failed to start fuzzer: {t}", .{err});
20032066 defer f.deinit();
20042067
......@@ -2189,7 +2252,7 @@ fn makeStep(
21892252 const step_prog_node = root_prog_node.start(step_name, 0);
21902253 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
21942257 const new_state: Step.State = for (deps) |dep_index| {
21952258 const dep_make_step = maker.stepByIndex(dep_index);
......@@ -2224,14 +2287,14 @@ fn makeStep(
22242287 .dependency_failure,
22252288 .skipped_oom,
22262289 => {
2227 if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .failure);
2290 if (maker.web_server) |ws| ws.updateStepStatus(step_index, .failure);
22282291 std.Progress.setStatus(.failure_working);
22292292 },
22302293
22312294 .success,
22322295 .skipped,
22332296 => {
2234 if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .success);
2297 if (maker.web_server) |ws| ws.updateStepStatus(step_index, .success);
22352298 },
22362299 }
22372300 }
lib/compiler/Maker/Fuzz.zig+4-4
......@@ -166,9 +166,9 @@ pub fn deinit(fuzz: *Fuzz) void {
166166fn rebuildTestsWorkerRun(
167167 maker: *Maker,
168168 run_index: Configuration.Step.Index,
169 parent_prog_node: std.Progress.Node,
169 parent_progress_node: std.Progress.Node,
170170) void {
171 rebuildTestsWorkerRunFallible(maker, run_index, parent_prog_node) catch |err| {
171 rebuildTestsWorkerRunFallible(maker, run_index, parent_progress_node) catch |err| {
172172 const conf = &maker.scanned_config.configuration;
173173 const conf_run = run_index.ptr(conf).extended.cast(conf, Configuration.Step.Run).?;
174174 const comp_index = conf_run.producer.value.?;
......@@ -180,7 +180,7 @@ fn rebuildTestsWorkerRun(
180180fn rebuildTestsWorkerRunFallible(
181181 maker: *Maker,
182182 run_index: Configuration.Step.Index,
183 parent_prog_node: std.Progress.Node,
183 parent_progress_node: std.Progress.Node,
184184) !void {
185185 const graph = maker.graph;
186186 const io = graph.io;
......@@ -196,7 +196,7 @@ fn rebuildTestsWorkerRunFallible(
196196 const root_module = conf_comp.root_module.get(conf);
197197 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);
200200 defer prog_node.end();
201201
202202 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
654654 }
655655 }
656656 },
657 .time_report => if (maker.web_server) |*ws| {
657 .time_report => if (maker.web_server) |ws| {
658658 const TimeReport = std.zig.Server.Message.TimeReport;
659659 const tr: *align(1) const TimeReport = @ptrCast(body[0..@sizeOf(TimeReport)]);
660660 ws.updateTimeReportCompile(.{
lib/compiler/Maker/Step/Run.zig+1-1
......@@ -1242,7 +1242,7 @@ fn evalZigTest(
12421242 step.test_results = test_results;
12431243 if (test_metadata) |tm| {
12441244 run.cached_test_metadata = tm.toCachedTestMetadata();
1245 if (maker.web_server) |*ws| {
1245 if (maker.web_server) |ws| {
12461246 if (graph.time_report) {
12471247 ws.updateTimeReportRunTest(
12481248 run_index,
lib/compiler/Maker/WebServer.zig+167-135
......@@ -19,7 +19,7 @@ const Fuzz = @import("Fuzz.zig");
1919const Graph = @import("Graph.zig");
2020const Step = @import("Step.zig");
2121
22maker: *Maker,
22graph: *const Graph,
2323listen_address: net.IpAddress,
2424root_prog_node: std.Progress.Node,
2525
......@@ -28,17 +28,8 @@ serve_task: ?Io.Future(Io.Cancelable!void),
2828
2929/// Uses `Io.Clock.awake`.
3030base_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
3832fuzz: ?Fuzz,
39time_report_mutex: Io.Mutex,
40time_report_msgs: [][]u8,
41time_report_update_times: []i64,
4233
4334build_status: std.atomic.Value(abi.BuildStatus),
4435/// When an event occurs which means WebSocket clients should be sent updates, call `notifyUpdate`
......@@ -55,6 +46,21 @@ runner_request_ready_cond: Io.Condition,
5546runner_request_empty_cond: Io.Condition,
5647runner_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
5864/// If a client is not explicitly notified of changes with `notifyUpdate`, it will be sent updates
5965/// on a fixed interval of this many milliseconds.
6066const default_update_interval_ms = 500;
......@@ -63,34 +69,88 @@ pub const base_clock: Io.Clock = .awake;
6369
6470/// Thread-safe. Triggers updates to be sent to connected WebSocket clients; see `update_id`.
6571pub fn notifyUpdate(ws: *WebServer) void {
66 const io = ws.maker.graph.io;
72 const io = ws.graph.io;
6773 _ = ws.update_id.rmw(.Add, 1, .release);
6874 io.futexWake(u32, &ws.update_id.raw, 16);
6975}
7076
7177pub const Options = struct {
72 maker: *Maker,
78 graph: *const Graph,
7379 root_prog_node: std.Progress.Node,
7480 listen_address: net.IpAddress,
7581 base_timestamp: Io.Clock.Timestamp,
7682};
83
7784pub fn init(opts: Options) WebServer {
7885 // The upcoming `Io` interface should allow us to use `Io.async` and `Io.concurrent`
7986 // instead of threads, so that the web server can function in single-threaded builds.
8087 comptime assert(!builtin.single_threaded);
8188 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;
84144 const all_steps = maker.step_stack.keys();
85145 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: {
90148 var name_bytes: usize = 0;
91149 for (all_steps) |step_index| name_bytes += step_index.ptr(c).name.slice(c).len;
92150 break :len name_bytes + all_steps.len * 4;
93 }) catch @panic("out of memory");
151 });
152 errdefer gpa.free(step_names_trailing);
153
94154 {
95155 const step_name_lens: []align(1) u32 = @ptrCast(step_names_trailing[0 .. all_steps.len * 4]);
96156 var idx: usize = all_steps.len * 4;
......@@ -103,71 +163,35 @@ pub fn init(opts: Options) WebServer {
103163 assert(idx == step_names_trailing.len);
104164 }
105165
106 const step_status_bits = gpa.alloc(
107 u8,
108 std.math.divCeil(usize, all_steps.len, 4) catch unreachable,
109 ) catch @panic("out of memory");
166 const step_status_bits = try gpa.alloc(u8, std.math.divCeil(usize, all_steps.len, 4) catch unreachable);
167 errdefer gpa.free(step_status_bits);
110168 @memset(step_status_bits, 0);
111169
112170 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");
114 const time_report_update_times = gpa.alloc(i64, time_reports_len) catch @panic("out of memory");
171 const time_report_msgs = try gpa.alloc([]u8, time_reports_len);
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);
115175 @memset(time_report_msgs, &.{});
116176 @memset(time_report_update_times, std.math.minInt(i64));
117177
118 return .{
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,
178 ws.releaseConfigured();
125179
126 .base_timestamp = opts.base_timestamp.raw,
180 ws.configured = .{
181 .maker = maker,
127182 .step_names_trailing = step_names_trailing,
128
129183 .step_status_bits = step_status_bits,
130
131 .fuzz = null,
132184 .time_report_mutex = .init,
133185 .time_report_msgs = time_report_msgs,
134186 .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,
143187 };
144188}
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}
166190pub fn start(ws: *WebServer) error{AlreadyReported}!void {
167191 assert(ws.tcp_server == null);
168192 assert(ws.serve_task == null);
169 const maker = ws.maker;
170 const io = maker.graph.io;
193 const graph = ws.graph;
194 const io = graph.io;
171195
172196 ws.tcp_server = ws.listen_address.listen(io, .{ .reuse_address = true }) catch |err| {
173197 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 {
186210 }
187211}
188212fn serve(ws: *WebServer) Io.Cancelable!void {
189 const maker = ws.maker;
190 const io = maker.graph.io;
213 const graph = ws.graph;
214 const io = graph.io;
191215
192216 var group: Io.Group = .init;
193217 defer group.cancel(io);
......@@ -213,7 +237,8 @@ pub fn startBuild(ws: *WebServer) void {
213237 fuzz.deinit();
214238 ws.fuzz = null;
215239 }
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);
217242 ws.build_status.store(.running, .monotonic);
218243 ws.notifyUpdate();
219244}
......@@ -223,12 +248,13 @@ pub fn updateStepStatus(
223248 step_index: Configuration.Step.Index,
224249 new_status: abi.StepUpdate.Status,
225250) void {
226 const maker = ws.maker;
251 const configured = &ws.configured.?;
252 const maker = configured.maker;
227253 const all_steps = maker.step_stack.keys();
228254 const step_idx: u32 = for (all_steps, 0..) |s, i| {
229255 if (s == step_index) break @intCast(i);
230256 } else unreachable;
231 const ptr = &ws.step_status_bits[step_idx / 4];
257 const ptr = &configured.step_status_bits[step_idx / 4];
232258 const bit_offset: u3 = @intCast((step_idx % 4) * 2);
233259 const old_bits: u2 = @truncate(@atomicLoad(u8, ptr, .monotonic) >> bit_offset);
234260 const mask = @as(u8, @intFromEnum(new_status) ^ old_bits) << bit_offset;
......@@ -239,7 +265,8 @@ pub fn updateStepStatus(
239265pub fn finishBuild(ws: *WebServer, opts: struct {
240266 fuzz: bool,
241267}) void {
242 const maker = ws.maker;
268 const configured = &ws.configured.?;
269 const maker = configured.maker;
243270 const all_steps = maker.step_stack.keys();
244271
245272 if (opts.fuzz) {
......@@ -274,15 +301,15 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
274301}
275302
276303pub fn now(ws: *const WebServer) i64 {
277 const maker = ws.maker;
278 const io = maker.graph.io;
304 const graph = ws.graph;
305 const io = graph.io;
279306 const ts = base_clock.now(io);
280307 return @intCast(ws.base_timestamp.durationTo(ts).toNanoseconds());
281308}
282309
283310fn accept(ws: *WebServer, stream: net.Stream) void {
284 const maker = ws.maker;
285 const io = maker.graph.io;
311 const graph = ws.graph;
312 const io = graph.io;
286313
287314 defer {
288315 // `net.Stream.close` wants to helpfully overwrite `stream` with
......@@ -328,17 +355,19 @@ fn accept(ws: *WebServer, stream: net.Stream) void {
328355}
329356
330357fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
331 const maker = ws.maker;
332 const gpa = maker.gpa;
333 const graph = maker.graph;
358 const graph = ws.graph;
359 const gpa = graph.cache.gpa;
334360 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;
335364 const all_steps = maker.step_stack.keys();
336365
337366 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);
340369 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| {
342371 copy.* = @atomicLoad(u8, shared, .monotonic);
343372 }
344373
......@@ -354,7 +383,7 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
354383 .timestamp = ws.now(),
355384 .steps_len = @intCast(all_steps.len),
356385 };
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 };
358387 try sock.writeMessageVec(&bufs, .binary);
359388 }
360389
......@@ -369,17 +398,17 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
369398 }
370399
371400 {
372 try ws.time_report_mutex.lock(io);
373 defer ws.time_report_mutex.unlock(io);
374 for (ws.time_report_msgs, ws.time_report_update_times) |msg, update_time| {
401 try configured.time_report_mutex.lock(io);
402 defer configured.time_report_mutex.unlock(io);
403 for (configured.time_report_msgs, configured.time_report_update_times) |msg, update_time| {
375404 if (update_time <= prev_time) continue;
376 // We want to send `msg`, but shouldn't block `ws.time_report_mutex` while we do, so
405 // We want to send `msg`, but shouldn't block `configured.time_report_mutex` while we do, so
377406 // that we don't hold up the build system on the client accepting this packet.
378407 const owned_msg = try gpa.dupe(u8, msg);
379408 defer gpa.free(owned_msg);
380409 // Temporarily unlock, then re-lock after the message is sent.
381 ws.time_report_mutex.unlock(io);
382 defer ws.time_report_mutex.lockUncancelable(io);
410 configured.time_report_mutex.unlock(io);
411 defer configured.time_report_mutex.lockUncancelable(io);
383412 try sock.writeMessage(owned_msg, .binary);
384413 }
385414 }
......@@ -393,7 +422,7 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
393422 }
394423 }
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| {
397426 const cur_byte = @atomicLoad(u8, shared, .monotonic);
398427 if (prev_byte.* == cur_byte) continue;
399428 const cur: [4]abi.StepUpdate.Status = .{
......@@ -433,8 +462,8 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
433462 }
434463}
435464fn recvWebSocketMessages(ws: *WebServer, sock: *http.Server.WebSocket) void {
436 const maker = ws.maker;
437 const io = maker.graph.io;
465 const graph = ws.graph;
466 const io = graph.io;
438467
439468 while (true) {
440469 const msg = sock.readSmallMessage() catch return;
......@@ -492,8 +521,7 @@ fn serveLibFile(
492521 sub_path: []const u8,
493522 content_type: []const u8,
494523) !void {
495 const maker = ws.maker;
496 const graph = maker.graph;
524 const graph = ws.graph;
497525
498526 return serveFile(ws, request, .{
499527 .root_dir = graph.zig_lib_directory,
......@@ -505,7 +533,7 @@ fn serveClientWasm(
505533 req: *http.Server.Request,
506534 optimize_mode: std.builtin.OptimizeMode,
507535) !void {
508 const gpa = ws.maker.gpa;
536 const gpa = ws.graph.cache.gpa;
509537
510538 var arena_state: std.heap.ArenaAllocator = .init(gpa);
511539 defer arena_state.deinit();
......@@ -522,9 +550,9 @@ pub fn serveFile(
522550 path: Cache.Path,
523551 content_type: []const u8,
524552) !void {
525 const maker = ws.maker;
526 const gpa = ws.maker.gpa;
527 const io = maker.graph.io;
553 const graph = ws.graph;
554 const gpa = graph.cache.gpa;
555 const io = graph.io;
528556
529557 // The desired API is actually sendfile, which will require enhancing http.Server.
530558 // We load the file with every request so that the user can make changes to the file
......@@ -542,8 +570,7 @@ pub fn serveFile(
542570 });
543571}
544572pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void {
545 const maker = ws.maker;
546 const graph = maker.graph;
573 const graph = ws.graph;
547574 const io = graph.io;
548575
549576 var send_buffer: [0x4000]u8 = undefined;
......@@ -581,9 +608,8 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
581608 const arch_os_abi = "wasm32-freestanding";
582609 const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext";
583610
584 const maker = ws.maker;
585 const graph = maker.graph;
586 const gpa = maker.gpa;
611 const graph = ws.graph;
612 const gpa = graph.cache.gpa;
587613 const io = graph.io;
588614
589615 const main_src_path: Cache.Path = .{
......@@ -651,9 +677,11 @@ pub fn updateTimeReportCompile(ws: *WebServer, opts: struct {
651677 /// The trailing data of `abi.time_report.CompileResult`, except the step name.
652678 trailing: []const u8,
653679}) 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;
655684 const gpa = maker.gpa;
656 const io = maker.graph.io;
657685 const all_steps = maker.step_stack.keys();
658686
659687 const step_idx: u32 = for (all_steps, 0..) |s, i| {
......@@ -661,10 +689,10 @@ pub fn updateTimeReportCompile(ws: *WebServer, opts: struct {
661689 } else unreachable;
662690
663691 const old_buf = old: {
664 ws.time_report_mutex.lock(io) catch return;
665 defer ws.time_report_mutex.unlock(io);
666 const old = ws.time_report_msgs[step_idx];
667 ws.time_report_msgs[step_idx] = &.{};
692 configured.time_report_mutex.lock(io) catch return;
693 defer configured.time_report_mutex.unlock(io);
694 const old = configured.time_report_msgs[step_idx];
695 configured.time_report_msgs[step_idx] = &.{};
668696 break :old old;
669697 };
670698 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 {
684712 @memcpy(buf[@sizeOf(abi.time_report.CompileResult)..], opts.trailing);
685713
686714 {
687 ws.time_report_mutex.lock(io) catch return;
688 defer ws.time_report_mutex.unlock(io);
689 assert(ws.time_report_msgs[step_idx].len == 0);
690 ws.time_report_msgs[step_idx] = buf;
691 ws.time_report_update_times[step_idx] = ws.now();
715 configured.time_report_mutex.lock(io) catch return;
716 defer configured.time_report_mutex.unlock(io);
717 assert(configured.time_report_msgs[step_idx].len == 0);
718 configured.time_report_msgs[step_idx] = buf;
719 configured.time_report_update_times[step_idx] = ws.now();
692720 }
693721 ws.notifyUpdate();
694722}
695723
696724pub 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;
698729 const gpa = maker.gpa;
699 const io = maker.graph.io;
700730 const all_steps = maker.step_stack.keys();
701731
702732 const step_idx: u32 = for (all_steps, 0..) |s, i| {
......@@ -704,10 +734,10 @@ pub fn updateTimeReportGeneric(ws: *WebServer, step_index: Configuration.Step.In
704734 } else unreachable;
705735
706736 const old_buf = old: {
707 ws.time_report_mutex.lock(io) catch return;
708 defer ws.time_report_mutex.unlock(io);
709 const old = ws.time_report_msgs[step_idx];
710 ws.time_report_msgs[step_idx] = &.{};
737 configured.time_report_mutex.lock(io) catch return;
738 defer configured.time_report_mutex.unlock(io);
739 const old = configured.time_report_msgs[step_idx];
740 configured.time_report_msgs[step_idx] = &.{};
711741 break :old old;
712742 };
713743 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
717747 .ns_total = @intCast(duration.toNanoseconds()),
718748 };
719749 {
720 ws.time_report_mutex.lock(io) catch return;
721 defer ws.time_report_mutex.unlock(io);
722 assert(ws.time_report_msgs[step_idx].len == 0);
723 ws.time_report_msgs[step_idx] = buf;
724 ws.time_report_update_times[step_idx] = ws.now();
750 configured.time_report_mutex.lock(io) catch return;
751 defer configured.time_report_mutex.unlock(io);
752 assert(configured.time_report_msgs[step_idx].len == 0);
753 configured.time_report_msgs[step_idx] = buf;
754 configured.time_report_update_times[step_idx] = ws.now();
725755 }
726756 ws.notifyUpdate();
727757}
......@@ -732,9 +762,11 @@ pub fn updateTimeReportRunTest(
732762 tests: *const Step.Run.CachedTestMetadata,
733763 ns_per_test: []const u64,
734764) 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;
736769 const gpa = maker.gpa;
737 const io = maker.graph.io;
738770 const all_steps = maker.step_stack.keys();
739771
740772 const step_idx: u32 = for (all_steps, 0..) |s, i| {
......@@ -752,10 +784,10 @@ pub fn updateTimeReportRunTest(
752784 break :len @sizeOf(abi.time_report.RunTestResult) + names_len + 8 * tests_len;
753785 };
754786 const old_buf = old: {
755 ws.time_report_mutex.lock(io) catch return;
756 defer ws.time_report_mutex.unlock(io);
757 const old = ws.time_report_msgs[step_idx];
758 ws.time_report_msgs[step_idx] = &.{};
787 configured.time_report_mutex.lock(io) catch return;
788 defer configured.time_report_mutex.unlock(io);
789 const old = configured.time_report_msgs[step_idx];
790 configured.time_report_msgs[step_idx] = &.{};
759791 break :old old;
760792 };
761793 const buf = gpa.realloc(old_buf, new_len) catch @panic("out of memory");
......@@ -778,11 +810,11 @@ pub fn updateTimeReportRunTest(
778810 assert(offset == buf.len);
779811
780812 {
781 ws.time_report_mutex.lock(io) catch return;
782 defer ws.time_report_mutex.unlock(io);
783 assert(ws.time_report_msgs[step_idx].len == 0);
784 ws.time_report_msgs[step_idx] = buf;
785 ws.time_report_update_times[step_idx] = ws.now();
813 configured.time_report_mutex.lock(io) catch return;
814 defer configured.time_report_mutex.unlock(io);
815 assert(configured.time_report_msgs[step_idx].len == 0);
816 configured.time_report_msgs[step_idx] = buf;
817 configured.time_report_update_times[step_idx] = ws.now();
786818 }
787819 ws.notifyUpdate();
788820}
......@@ -791,7 +823,7 @@ const RunnerRequest = union(enum) {
791823 rebuild,
792824};
793825pub fn getRunnerRequest(ws: *WebServer) ?RunnerRequest {
794 const io = ws.maker.graph.io;
826 const io = ws.graph.io;
795827 ws.runner_request_mutex.lock(io) catch return;
796828 defer ws.runner_request_mutex.unlock(io);
797829 if (ws.runner_request) |req| {
......@@ -802,7 +834,7 @@ pub fn getRunnerRequest(ws: *WebServer) ?RunnerRequest {
802834 return null;
803835}
804836pub fn wait(ws: *WebServer) Io.Cancelable!RunnerRequest {
805 const io = ws.maker.graph.io;
837 const io = ws.graph.io;
806838 try ws.runner_request_mutex.lock(io);
807839 defer ws.runner_request_mutex.unlock(io);
808840 while (true) {