| ... | @@ -11,6 +11,7 @@ const File = std.Io.File; | ... | @@ -11,6 +11,7 @@ const File = std.Io.File; |
| 11 | const Io = std.Io; | 11 | const Io = std.Io; |
| 12 | const Dir = std.Io.Dir; | 12 | const Dir = std.Io.Dir; |
| 13 | const Path = std.Build.Cache.Path; | 13 | const Path = std.Build.Cache.Path; |
| | 14 | const Reader = std.Io.Reader; |
| 14 | const Writer = std.Io.Writer; | 15 | const Writer = std.Io.Writer; |
| 15 | const assert = std.debug.assert; | 16 | const assert = std.debug.assert; |
| 16 | const fatal = std.process.fatal; | 17 | const fatal = std.process.fatal; |
| ... | @@ -19,6 +20,8 @@ const log = std.log; | ... | @@ -19,6 +20,8 @@ const log = std.log; |
| 19 | const mem = std.mem; | 20 | const mem = std.mem; |
| 20 | const process = std.process; | 21 | const process = std.process; |
| 21 | const Color = std.zig.Color; | 22 | const Color = std.zig.Color; |
| | 23 | const Client = std.zig.Client; |
| | 24 | const Server = std.zig.Server; |
| 22 | const EnvVar = std.zig.EnvVar; | 25 | const EnvVar = std.zig.EnvVar; |
| 23 | const default_local_zig_cache_basename = std.zig.default_local_zig_cache_basename; | 26 | const default_local_zig_cache_basename = std.zig.default_local_zig_cache_basename; |
| 24 | const stringToEnum = std.meta.stringToEnum; | 27 | const stringToEnum = std.meta.stringToEnum; |
| ... | @@ -51,6 +54,8 @@ max_rss_mutex: Io.Mutex, | ... | @@ -51,6 +54,8 @@ max_rss_mutex: Io.Mutex, |
| 51 | skip_oom_steps: bool, | 54 | skip_oom_steps: bool, |
| 52 | unit_test_timeout_ns: ?u64, | 55 | unit_test_timeout_ns: ?u64, |
| 53 | watch: bool, | 56 | watch: bool, |
| | 57 | protocol_server: ?*AvoidableServer, |
| | 58 | protocol_server_mutex: Io.Mutex, |
| 54 | web_server: ?*AvoidableWebServer, | 59 | web_server: ?*AvoidableWebServer, |
| 55 | /// Allocated into `gpa`. | 60 | /// Allocated into `gpa`. |
| 56 | memory_blocked_steps: std.ArrayList(Configuration.Step.Index), | 61 | memory_blocked_steps: std.ArrayList(Configuration.Step.Index), |
| ... | @@ -67,6 +72,7 @@ var stdio_buffer_allocation: [256]u8 = undefined; | ... | @@ -67,6 +72,7 @@ var stdio_buffer_allocation: [256]u8 = undefined; |
| 67 | var stdout_writer_allocation: Io.File.Writer = undefined; | 72 | var stdout_writer_allocation: Io.File.Writer = undefined; |
| 68 | var debug_maker_leaks: bool = false; | 73 | var debug_maker_leaks: bool = false; |
| 69 | | 74 | |
| | 75 | const AvoidableServer = if (builtin.single_threaded) void else Server; |
| 70 | const AvoidableWebServer = if (builtin.single_threaded) void else WebServer; | 76 | const AvoidableWebServer = if (builtin.single_threaded) void else WebServer; |
| 71 | | 77 | |
| 72 | const is_debug_mode = builtin.mode == .debug; | 78 | const is_debug_mode = builtin.mode == .debug; |
| ... | @@ -216,6 +222,7 @@ pub fn main(init: process.Init.Minimal) !void { | ... | @@ -216,6 +222,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 216 | var watch = false; | 222 | var watch = false; |
| 217 | var fuzz: ?Fuzz.Mode = null; | 223 | var fuzz: ?Fuzz.Mode = null; |
| 218 | var debounce_interval_ms: u16 = 50; | 224 | var debounce_interval_ms: u16 = 50; |
| | 225 | var listen: bool = false; |
| 219 | var webui_listen: ?Io.net.IpAddress = null; | 226 | var webui_listen: ?Io.net.IpAddress = null; |
| 220 | var debug_pkg_config = false; | 227 | var debug_pkg_config = false; |
| 221 | var run_args: ?[]const []const u8 = null; | 228 | var run_args: ?[]const []const u8 = null; |
| ... | @@ -416,6 +423,8 @@ pub fn main(init: process.Init.Minimal) !void { | ... | @@ -416,6 +423,8 @@ pub fn main(init: process.Init.Minimal) !void { |
| 416 | next_arg, err, | 423 | next_arg, err, |
| 417 | }); | 424 | }); |
| 418 | }; | 425 | }; |
| | 426 | } else if (mem.eql(u8, arg, "--listen=-")) { |
| | 427 | listen = true; |
| 419 | } else if (mem.eql(u8, arg, "--webui")) { | 428 | } else if (mem.eql(u8, arg, "--webui")) { |
| 420 | if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) }; | 429 | if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) }; |
| 421 | } else if (mem.startsWith(u8, arg, "--webui=")) { | 430 | } else if (mem.startsWith(u8, arg, "--webui=")) { |
| ... | @@ -553,7 +562,7 @@ pub fn main(init: process.Init.Minimal) !void { | ... | @@ -553,7 +562,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 553 | } | 562 | } |
| 554 | | 563 | |
| 555 | const early_exit_mode = fetch_only or help_menu or steps_menu or print_configuration != .none; | 564 | const early_exit_mode = fetch_only or help_menu or steps_menu or print_configuration != .none; |
| 556 | const server_mode = !early_exit_mode and (watch or webui_listen != null or fuzz != null); | 565 | const server_mode = !early_exit_mode and (watch or webui_listen != null or fuzz != null or listen); |
| 557 | | 566 | |
| 558 | process.raiseFileDescriptorLimit(); | 567 | process.raiseFileDescriptorLimit(); |
| 559 | | 568 | |
| ... | @@ -661,6 +670,25 @@ pub fn main(init: process.Init.Minimal) !void { | ... | @@ -661,6 +670,25 @@ pub fn main(init: process.Init.Minimal) !void { |
| 661 | break :ws &web_server_allocation; | 670 | break :ws &web_server_allocation; |
| 662 | } else null; | 671 | } else null; |
| 663 | | 672 | |
| | 673 | var stdin_buffer: [256]u8 = undefined; |
| | 674 | var stdout_buffer: [256]u8 = undefined; |
| | 675 | var stdin_reader = Io.File.stdin().reader(io, &stdin_buffer); |
| | 676 | var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); |
| | 677 | |
| | 678 | var protocol_server_allocation: AvoidableServer = undefined; |
| | 679 | const protocol_server: ?*AvoidableServer = if (listen) s: { |
| | 680 | if (builtin.single_threaded) fatal("--listen is not yet supported on single-threaded hosts", .{}); |
| | 681 | if (watch) fatal("using '--watch' and '--listen' together is not supported", .{}); |
| | 682 | if (fuzz != null) fatal("using '--fuzz' and '--listen' together is not supported", .{}); |
| | 683 | if (step_names.items.len > 0) fatal("build steps must be provided over the protocol instead of using CLI arguments", .{}); |
| | 684 | protocol_server_allocation = .{ |
| | 685 | .in = &stdin_reader.interface, |
| | 686 | .out = &stdout_writer.interface, |
| | 687 | }; |
| | 688 | try serveBSPHandshake(&protocol_server_allocation); |
| | 689 | break :s &protocol_server_allocation; |
| | 690 | } else null; |
| | 691 | |
| 664 | while (true) { | 692 | while (true) { |
| 665 | // If this fails, we can still start the server and wait for user | 693 | // If this fails, we can still start the server and wait for user |
| 666 | // to request a rebuild. If it returns error.FailedButCacheIntact | 694 | // to request a rebuild. If it returns error.FailedButCacheIntact |
| ... | @@ -731,13 +759,20 @@ pub fn main(init: process.Init.Minimal) !void { | ... | @@ -731,13 +759,20 @@ pub fn main(init: process.Init.Minimal) !void { |
| 731 | | 759 | |
| 732 | .watch = watch, | 760 | .watch = watch, |
| 733 | .web_server = web_server, | 761 | .web_server = web_server, |
| | 762 | .protocol_server = protocol_server, |
| | 763 | .protocol_server_mutex = .init, |
| 734 | .memory_blocked_steps = .empty, | 764 | .memory_blocked_steps = .empty, |
| 735 | .step_stack = .empty, | 765 | .step_stack = .empty, |
| 736 | .pkg_config = .{ .debug = debug_pkg_config }, | 766 | .pkg_config = .{ .debug = debug_pkg_config }, |
| 737 | | 767 | |
| 738 | .error_style = error_style, | 768 | .error_style = error_style, |
| 739 | .multiline_errors = multiline_errors, | 769 | .multiline_errors = multiline_errors, |
| 740 | .summary = summary orelse if (watch or webui_listen != null) .new else .failures, | 770 | .summary = summary orelse if (listen) |
| | 771 | .none |
| | 772 | else if (watch or webui_listen != null) |
| | 773 | .new |
| | 774 | else |
| | 775 | .failures, |
| 741 | }; | 776 | }; |
| 742 | defer { | 777 | defer { |
| 743 | maker.memory_blocked_steps.deinit(gpa); | 778 | maker.memory_blocked_steps.deinit(gpa); |
| ... | @@ -749,6 +784,52 @@ pub fn main(init: process.Init.Minimal) !void { | ... | @@ -749,6 +784,52 @@ pub fn main(init: process.Init.Minimal) !void { |
| 749 | maker.max_rss_is_default = true; | 784 | maker.max_rss_is_default = true; |
| 750 | } | 785 | } |
| 751 | | 786 | |
| | 787 | if (protocol_server) |s| { |
| | 788 | try s.serveStringMessage(.bsp_configuration, try arena.print("{f}", .{scanned_config.path})); |
| | 789 | |
| | 790 | var w: ?Watch = null; |
| | 791 | |
| | 792 | const Event = union(enum) { |
| | 793 | message: Reader.Error!Client.Message.Header, |
| | 794 | fs_event: if (Watch.have_impl) @typeInfo(@TypeOf(Watch.wait)).@"fn".return_type.? else noreturn, |
| | 795 | }; |
| | 796 | |
| | 797 | var select_buffer: [2]Event = undefined; |
| | 798 | var select: Io.Select(Event) = .init(io, &select_buffer); |
| | 799 | defer select.cancelDiscard(); |
| | 800 | |
| | 801 | try select.concurrent(.message, Server.receiveMessage, .{s}); |
| | 802 | |
| | 803 | var in_debounce = false; |
| | 804 | loop: switch (try select.await()) { |
| | 805 | .message => |payload| { |
| | 806 | const header: Client.Message.Header = try payload; |
| | 807 | switch (header.tag) { |
| | 808 | .exit => { |
| | 809 | cleanExit(io, &scanned_config); |
| | 810 | process.exit(0); |
| | 811 | }, |
| | 812 | else => fatal("unsupported message: {t}", .{header.tag}), |
| | 813 | } |
| | 814 | }, |
| | 815 | .fs_event => |payload| { |
| | 816 | if (!Watch.have_impl) unreachable; |
| | 817 | switch (try payload) { |
| | 818 | .timeout => { |
| | 819 | assert(in_debounce); |
| | 820 | markFailedStepsDirty(&maker); |
| | 821 | if (true) @panic("TODO run steps that were previous specified over the build system protocol"); |
| | 822 | in_debounce = false; |
| | 823 | }, |
| | 824 | .dirty => in_debounce = true, |
| | 825 | .clean => {}, |
| | 826 | } |
| | 827 | try select.concurrent(.fs_event, Watch.wait, .{ &w.?, if (in_debounce) .{ .ms = debounce_interval_ms } else .none }); |
| | 828 | continue :loop try select.await(); |
| | 829 | }, |
| | 830 | } |
| | 831 | } |
| | 832 | |
| 752 | maker.prepare(step_names.items) catch |err| switch (err) { | 833 | maker.prepare(step_names.items) catch |err| switch (err) { |
| 753 | error.DependencyLoopDetected, error.InsufficientMemory => { | 834 | error.DependencyLoopDetected, error.InsufficientMemory => { |
| 754 | // TODO handle DependencyLoopDetected as error.FailedButCacheIntact | 835 | // TODO handle DependencyLoopDetected as error.FailedButCacheIntact |
| ... | @@ -850,6 +931,9 @@ pub fn main(init: process.Init.Minimal) !void { | ... | @@ -850,6 +931,9 @@ pub fn main(init: process.Init.Minimal) !void { |
| 850 | _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; | 931 | _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; |
| 851 | process.exit(1); | 932 | process.exit(1); |
| 852 | } | 933 | } |
| | 934 | if (protocol_server != null) { |
| | 935 | fatal("(zig build system) TODO send error messages to client when build.zig compilation fails", .{}); |
| | 936 | } |
| 853 | if (watch and can_fs_watch) { | 937 | if (watch and can_fs_watch) { |
| 854 | fatal("(zig build system) TODO set up fs watching even when build.zig compilation fails", .{}); | 938 | fatal("(zig build system) TODO set up fs watching even when build.zig compilation fails", .{}); |
| 855 | } else { | 939 | } else { |
| ... | @@ -2990,6 +3074,21 @@ fn cleanTmpFiles(maker: *Maker, steps: []const Configuration.Step.Index) void { | ... | @@ -2990,6 +3074,21 @@ fn cleanTmpFiles(maker: *Maker, steps: []const Configuration.Step.Index) void { |
| 2990 | } | 3074 | } |
| 2991 | } | 3075 | } |
| 2992 | | 3076 | |
| | 3077 | fn serveBSPHandshake(s: *const std.zig.Server) !void { |
| | 3078 | const handshake_header: Server.Message.Handshake = .{ |
| | 3079 | .version = Server.build_system_version, |
| | 3080 | .flags = .{ |
| | 3081 | .file_system_watch_supported = Watch.have_impl, |
| | 3082 | }, |
| | 3083 | }; |
| | 3084 | try s.serveMessageHeader(.{ |
| | 3085 | .tag = .bsp_handshake, |
| | 3086 | .bytes_len = @sizeOf(Server.Message.Handshake), |
| | 3087 | }); |
| | 3088 | try s.out.writeStruct(handshake_header, .little); |
| | 3089 | try s.out.flush(); |
| | 3090 | } |
| | 3091 | |
| 2993 | fn initStdoutWriter(io: Io) *Writer { | 3092 | fn initStdoutWriter(io: Io) *Writer { |
| 2994 | stdout_writer_allocation = Io.File.stdout().writerStreaming(io, &stdio_buffer_allocation); | 3093 | stdout_writer_allocation = Io.File.stdout().writerStreaming(io, &stdio_buffer_allocation); |
| 2995 | return &stdout_writer_allocation.interface; | 3094 | return &stdout_writer_allocation.interface; |