| ... | ... | @@ -52,7 +52,7 @@ max_rss_mutex: Io.Mutex, |
| 52 | 52 | skip_oom_steps: bool, |
| 53 | 53 | unit_test_timeout_ns: ?u64, |
| 54 | 54 | watch: bool, |
| 55 | | web_server: if (!builtin.single_threaded) ?WebServer else ?noreturn, |
| 55 | web_server: ?*AvoidableWebServer, |
| 56 | 56 | /// Allocated into `gpa`. |
| 57 | 57 | memory_blocked_steps: std.ArrayList(Configuration.Step.Index), |
| 58 | 58 | /// Allocated into `gpa`. |
| ... | ... | @@ -68,6 +68,8 @@ var stdio_buffer_allocation: [256]u8 = undefined; |
| 68 | 68 | var stdout_writer_allocation: Io.File.Writer = undefined; |
| 69 | 69 | var debug_maker_leaks: bool = false; |
| 70 | 70 | |
| 71 | const AvoidableWebServer = if (builtin.single_threaded) void else WebServer; |
| 72 | |
| 71 | 73 | const is_debug_mode = builtin.mode == .Debug; |
| 72 | 74 | const use_safe_allocator = switch (builtin.mode) { |
| 73 | 75 | .Debug, .ReleaseSafe => true, |
| ... | ... | @@ -106,6 +108,7 @@ const ErrorStyle = enum { |
| 106 | 108 | }; |
| 107 | 109 | const MultilineErrors = enum { indent, newline, none }; |
| 108 | 110 | const Summary = enum { all, new, failures, line, none }; |
| 111 | const PrintConfiguration = enum { none, zon, path }; |
| 109 | 112 | |
| 110 | 113 | /// Used to build the -M flags to pass to build-exe. |
| 111 | 114 | pub const CliModule = struct { |
| ... | ... | @@ -195,7 +198,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 195 | 198 | var step_names: std.ArrayList([]const u8) = .empty; |
| 196 | 199 | var help_menu = false; |
| 197 | 200 | var steps_menu = false; |
| 198 | | var print_configuration: enum { none, zon, path } = .none; |
| 201 | var print_configuration: PrintConfiguration = .none; |
| 199 | 202 | var override_install_prefix: ?[]const u8 = null; |
| 200 | 203 | var override_lib_dir: ?[]const u8 = null; |
| 201 | 204 | var override_bin_dir: ?[]const u8 = null; |
| ... | ... | @@ -546,6 +549,9 @@ pub fn main(init: process.Init.Minimal) !void { |
| 546 | 549 | } |
| 547 | 550 | } |
| 548 | 551 | |
| 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 | 555 | const cwd_path = std.zig.getResolvedCwd(io, arena) catch |err| |
| 550 | 556 | fatal("resolving current directory path failed: {t}", .{err}); |
| 551 | 557 | |
| ... | ... | @@ -593,752 +599,809 @@ pub fn main(init: process.Init.Minimal) !void { |
| 593 | 599 | .off => .no_color, |
| 594 | 600 | }; |
| 595 | 601 | |
| 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 | 612 | const main_progress_node = std.Progress.start(io, .{ |
| 597 | 613 | .disable_printing = (graph.stderr_mode.? == .no_color), |
| 598 | 614 | }); |
| 599 | 615 | defer main_progress_node.end(); |
| 600 | 616 | |
| 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); |
| 626 | 758 | }; |
| 627 | 759 | |
| 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); |
| 629 | 761 | |
| 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(); |
| 632 | 770 | |
| 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); |
| 635 | 772 | |
| 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"); |
| 654 | 846 | } |
| 655 | 847 | } |
| 656 | | defer Fork.deinitList(forks.items); |
| 848 | } |
| 849 | } |
| 657 | 850 | |
| 658 | | var build_configurer_argv: std.ArrayList([]const u8) = .empty; |
| 659 | | defer build_configurer_argv.deinit(gpa); |
| 851 | const ConfigureOptions = struct { |
| 852 | configure_argv: [][]const u8, |
| 853 | conf_argv_index_build_root: usize, |
| 854 | cached_passthru_configure: []const u32, |
| 660 | 855 | |
| 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 | }; |
| 663 | 869 | |
| 664 | | const configurer_root_src_path: Cache.Path = .{ |
| 665 | | .root_dir = graph.zig_lib_directory, |
| 666 | | .sub_path = "compiler/configurer.zig", |
| 667 | | }; |
| 870 | fn 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; |
| 668 | 875 | |
| 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(); |
| 673 | 884 | |
| 674 | | const configurer_exe_name = "configurer"; |
| 885 | for (options.cached_passthru_configure) |i| |
| 886 | config_man.hash.addBytes(configure_argv[i]); |
| 675 | 887 | |
| 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); |
| 684 | 891 | |
| 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 | }, {}); |
| 702 | 918 | } |
| 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); |
| 708 | 921 | |
| 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); |
| 713 | 924 | |
| 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); |
| 719 | 927 | |
| 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 | }; |
| 725 | 932 | |
| 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 | }; |
| 731 | 937 | |
| 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"; |
| 759 | 939 | |
| 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 | }); |
| 763 | 948 | |
| 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; |
| 797 | 957 | |
| 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 | }); |
| 799 | 972 | |
| 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; |
| 804 | 977 | |
| 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 | }; |
| 807 | 983 | |
| 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 | }; |
| 827 | 989 | |
| 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(); |
| 829 | 995 | |
| 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(); |
| 836 | 1023 | |
| 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 | } |
| 838 | 1027 | |
| 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 | }; |
| 872 | 1061 | |
| 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); |
| 895 | 1063 | |
| 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 | ); |
| 916 | 1068 | |
| 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 | }); |
| 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); |
| 936 | | try build_mod.lower(arena, gpa, &build_configurer_argv); |
| 1089 | if (any_unused) process.exit(1); |
| 1090 | } |
| 1091 | |
| 1092 | try job_queue.consolidateErrors(); |
| 937 | 1093 | |
| 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 | } |
| 940 | 1099 | |
| 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); |
| 956 | 1103 | } |
| 957 | 1104 | |
| 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 }); |
| 974 | 1135 | |
| 975 | | configure_argv.items[0] = try configure_exe_path.toString(arena); |
| 976 | | } |
| 1136 | deps_mod.root_path = try dependencies_zig_path.toString(arena); |
| 1137 | } |
| 977 | 1138 | |
| 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 | } |
| 983 | 1161 | |
| 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 | } |
| 1033 | 1180 | } |
| 1034 | | try unlazy_set.put(arena, .fromSlice(hash), {}); |
| 1035 | 1181 | } |
| 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 | } |
| 1043 | 1196 | } |
| 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 | })); |
| 1047 | 1200 | } |
| 1048 | | continue :cp; |
| 1049 | | } |
| 1201 | try deps_mod.lower(arena, gpa, &build_configurer_argv); |
| 1202 | try build_mod.lower(arena, gpa, &build_configurer_argv); |
| 1050 | 1203 | |
| 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=-"); |
| 1053 | 1205 | } |
| 1054 | 1206 | |
| 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}), |
| 1084 | 1217 | }, |
| 1085 | | else => |e| e, |
| 1218 | false, |
| 1086 | 1219 | }; |
| 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. |
| 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); |
| 1099 | 1223 | |
| 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); |
| 1109 | 1236 | } |
| 1110 | 1237 | |
| 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 | }) }); |
| 1138 | 1242 | } |
| 1139 | | break :sc .{ |
| 1140 | | .configuration = configuration, |
| 1141 | | .top_level_steps = top_level_steps, |
| 1142 | | .path = configuration_path, |
| 1143 | | }; |
| 1144 | | }; |
| 1145 | 1243 | |
| 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, |
| 1150 | 1249 | }; |
| 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 }); |
| 1157 | 1268 | }; |
| 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 | } |
| 1169 | 1310 | |
| 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 | } |
| 1174 | 1314 | |
| 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 | } |
| 1184 | 1354 | }; |
| 1185 | 1355 | |
| 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); |
| 1210 | 1368 | }, |
| 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 => {}, |
| 1240 | 1370 | } |
| 1241 | 1371 | |
| 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 }); |
| 1248 | 1378 | }; |
| 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); |
| 1338 | 1393 | }, |
| 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)); |
| 1341 | 1399 | } |
| 1400 | return .{ |
| 1401 | .configuration = configuration, |
| 1402 | .top_level_steps = top_level_steps, |
| 1403 | .path = configuration_path, |
| 1404 | }; |
| 1342 | 1405 | } |
| 1343 | 1406 | |
| 1344 | 1407 | fn 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 | 1957 | fn makeStepNames( |
| 1895 | 1958 | maker: *Maker, |
| 1896 | 1959 | step_names: []const []const u8, |
| 1897 | | parent_prog_node: std.Progress.Node, |
| 1960 | parent_progress_node: std.Progress.Node, |
| 1898 | 1961 | fuzz: ?Fuzz.Mode, |
| 1899 | 1962 | ) !void { |
| 1900 | 1963 | const graph = maker.graph; |
| ... | ... | @@ -1918,7 +1981,7 @@ fn makeStepNames( |
| 1918 | 1981 | } |
| 1919 | 1982 | } |
| 1920 | 1983 | |
| 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 | 1985 | defer step_prog.end(); |
| 1923 | 1986 | |
| 1924 | 1987 | var group: Io.Group = .init; |
| ... | ... | @@ -1998,7 +2061,7 @@ fn makeStepNames( |
| 1998 | 2061 | } |
| 1999 | 2062 | |
| 2000 | 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 | 2065 | fatal("failed to start fuzzer: {t}", .{err}); |
| 2003 | 2066 | defer f.deinit(); |
| 2004 | 2067 | |
| ... | ... | @@ -2189,7 +2252,7 @@ fn makeStep( |
| 2189 | 2252 | const step_prog_node = root_prog_node.start(step_name, 0); |
| 2190 | 2253 | defer step_prog_node.end(); |
| 2191 | 2254 | |
| 2192 | | if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .wip); |
| 2255 | if (maker.web_server) |ws| ws.updateStepStatus(step_index, .wip); |
| 2193 | 2256 | |
| 2194 | 2257 | const new_state: Step.State = for (deps) |dep_index| { |
| 2195 | 2258 | const dep_make_step = maker.stepByIndex(dep_index); |
| ... | ... | @@ -2224,14 +2287,14 @@ fn makeStep( |
| 2224 | 2287 | .dependency_failure, |
| 2225 | 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 | 2291 | std.Progress.setStatus(.failure_working); |
| 2229 | 2292 | }, |
| 2230 | 2293 | |
| 2231 | 2294 | .success, |
| 2232 | 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 | } |