| ... | ... | @@ -578,6 +578,7 @@ fn buildOutputType( |
| 578 | 578 | var strip = false; |
| 579 | 579 | var function_sections = false; |
| 580 | 580 | var watch = false; |
| 581 | var listen_addr: ?std.net.Ip4Address = null; |
| 581 | 582 | var debug_compile_errors = false; |
| 582 | 583 | var verbose_link = std.process.hasEnvVarConstant("ZIG_VERBOSE_LINK"); |
| 583 | 584 | var verbose_cc = std.process.hasEnvVarConstant("ZIG_VERBOSE_CC"); |
| ... | ... | @@ -994,6 +995,18 @@ fn buildOutputType( |
| 994 | 995 | } else { |
| 995 | 996 | try log_scopes.append(gpa, args[i]); |
| 996 | 997 | } |
| 998 | } else if (mem.eql(u8, arg, "--listen")) { |
| 999 | if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); |
| 1000 | i += 1; |
| 1001 | // example: --listen 127.0.0.1:9000 |
| 1002 | var it = std.mem.split(u8, args[i], ":"); |
| 1003 | const host = it.next().?; |
| 1004 | const port_text = it.next() orelse "14735"; |
| 1005 | const port = std.fmt.parseInt(u16, port_text, 10) catch |err| |
| 1006 | fatal("invalid port number: '{s}': {s}", .{ port_text, @errorName(err) }); |
| 1007 | listen_addr = std.net.Ip4Address.parse(host, port) catch |err| |
| 1008 | fatal("invalid host: '{s}': {s}", .{ host, @errorName(err) }); |
| 1009 | watch = true; |
| 997 | 1010 | } else if (mem.eql(u8, arg, "--debug-link-snapshot")) { |
| 998 | 1011 | if (!build_options.enable_link_snapshots) { |
| 999 | 1012 | std.log.warn("Zig was compiled without linker snapshots enabled (-Dlink-snapshot). --debug-link-snapshot has no effect.", .{}); |
| ... | ... | @@ -2649,6 +2662,125 @@ fn buildOutputType( |
| 2649 | 2662 | |
| 2650 | 2663 | var last_cmd: ReplCmd = .help; |
| 2651 | 2664 | |
| 2665 | if (listen_addr) |ip4_addr| { |
| 2666 | var server = std.net.StreamServer.init(.{ |
| 2667 | .reuse_address = true, |
| 2668 | }); |
| 2669 | defer server.deinit(); |
| 2670 | |
| 2671 | try server.listen(.{ .in = ip4_addr }); |
| 2672 | |
| 2673 | while (true) { |
| 2674 | const conn = try server.accept(); |
| 2675 | defer conn.stream.close(); |
| 2676 | |
| 2677 | var buf: [100]u8 = undefined; |
| 2678 | var child_pid: ?i32 = null; |
| 2679 | |
| 2680 | while (true) { |
| 2681 | try comp.makeBinFileExecutable(); |
| 2682 | |
| 2683 | const amt = try conn.stream.read(&buf); |
| 2684 | const line = buf[0..amt]; |
| 2685 | const actual_line = mem.trimRight(u8, line, "\r\n "); |
| 2686 | |
| 2687 | const cmd: ReplCmd = blk: { |
| 2688 | if (mem.eql(u8, actual_line, "update")) { |
| 2689 | break :blk .update; |
| 2690 | } else if (mem.eql(u8, actual_line, "exit")) { |
| 2691 | break; |
| 2692 | } else if (mem.eql(u8, actual_line, "help")) { |
| 2693 | break :blk .help; |
| 2694 | } else if (mem.eql(u8, actual_line, "run")) { |
| 2695 | break :blk .run; |
| 2696 | } else if (mem.eql(u8, actual_line, "update-and-run")) { |
| 2697 | break :blk .update_and_run; |
| 2698 | } else if (actual_line.len == 0) { |
| 2699 | break :blk last_cmd; |
| 2700 | } else { |
| 2701 | try stderr.print("unknown command: {s}\n", .{actual_line}); |
| 2702 | continue; |
| 2703 | } |
| 2704 | }; |
| 2705 | last_cmd = cmd; |
| 2706 | switch (cmd) { |
| 2707 | .update => { |
| 2708 | tracy.frameMark(); |
| 2709 | if (output_mode == .Exe) { |
| 2710 | try comp.makeBinFileWritable(); |
| 2711 | } |
| 2712 | updateModule(gpa, comp, hook) catch |err| switch (err) { |
| 2713 | error.SemanticAnalyzeFail => continue, |
| 2714 | else => |e| return e, |
| 2715 | }; |
| 2716 | }, |
| 2717 | .help => { |
| 2718 | try stderr.writeAll(repl_help); |
| 2719 | }, |
| 2720 | .run => { |
| 2721 | tracy.frameMark(); |
| 2722 | try runOrTest( |
| 2723 | comp, |
| 2724 | gpa, |
| 2725 | arena, |
| 2726 | test_exec_args.items, |
| 2727 | self_exe_path, |
| 2728 | arg_mode, |
| 2729 | target_info, |
| 2730 | watch, |
| 2731 | &comp_destroyed, |
| 2732 | all_args, |
| 2733 | runtime_args_start, |
| 2734 | link_libc, |
| 2735 | ); |
| 2736 | }, |
| 2737 | .update_and_run => { |
| 2738 | tracy.frameMark(); |
| 2739 | if (child_pid) |pid| { |
| 2740 | try conn.stream.writer().print("hot code swap requested for pid {d}", .{pid}); |
| 2741 | try comp.hotCodeSwap(pid); |
| 2742 | |
| 2743 | var errors = try comp.getAllErrorsAlloc(); |
| 2744 | defer errors.deinit(comp.gpa); |
| 2745 | |
| 2746 | if (errors.list.len != 0) { |
| 2747 | const ttyconf: std.debug.TTY.Config = switch (comp.color) { |
| 2748 | .auto => std.debug.detectTTYConfig(), |
| 2749 | .on => .escape_codes, |
| 2750 | .off => .no_color, |
| 2751 | }; |
| 2752 | for (errors.list) |full_err_msg| { |
| 2753 | try full_err_msg.renderToStdErrInner(ttyconf, conn.stream, "error:", .Red, 0); |
| 2754 | } |
| 2755 | continue; |
| 2756 | } |
| 2757 | } else { |
| 2758 | if (output_mode == .Exe) { |
| 2759 | try comp.makeBinFileWritable(); |
| 2760 | } |
| 2761 | updateModule(gpa, comp, hook) catch |err| switch (err) { |
| 2762 | error.SemanticAnalyzeFail => continue, |
| 2763 | else => |e| return e, |
| 2764 | }; |
| 2765 | try comp.makeBinFileExecutable(); |
| 2766 | |
| 2767 | child_pid = try runOrTestHotSwap( |
| 2768 | comp, |
| 2769 | gpa, |
| 2770 | arena, |
| 2771 | test_exec_args.items, |
| 2772 | self_exe_path, |
| 2773 | arg_mode, |
| 2774 | all_args, |
| 2775 | runtime_args_start, |
| 2776 | ); |
| 2777 | } |
| 2778 | }, |
| 2779 | } |
| 2780 | } |
| 2781 | } |
| 2782 | } |
| 2783 | |
| 2652 | 2784 | while (watch) { |
| 2653 | 2785 | try stderr.print("(zig) ", .{}); |
| 2654 | 2786 | try comp.makeBinFileExecutable(); |
| ... | ... | @@ -2901,6 +3033,62 @@ fn runOrTest( |
| 2901 | 3033 | } |
| 2902 | 3034 | } |
| 2903 | 3035 | |
| 3036 | fn runOrTestHotSwap( |
| 3037 | comp: *Compilation, |
| 3038 | gpa: Allocator, |
| 3039 | arena: Allocator, |
| 3040 | test_exec_args: []const ?[]const u8, |
| 3041 | self_exe_path: []const u8, |
| 3042 | arg_mode: ArgMode, |
| 3043 | all_args: []const []const u8, |
| 3044 | runtime_args_start: ?usize, |
| 3045 | ) !i32 { |
| 3046 | const exe_emit = comp.bin_file.options.emit.?; |
| 3047 | // A naive `directory.join` here will indeed get the correct path to the binary, |
| 3048 | // however, in the case of cwd, we actually want `./foo` so that the path can be executed. |
| 3049 | const exe_path = try fs.path.join(arena, &[_][]const u8{ |
| 3050 | exe_emit.directory.path orelse ".", exe_emit.sub_path, |
| 3051 | }); |
| 3052 | |
| 3053 | var argv = std.ArrayList([]const u8).init(gpa); |
| 3054 | defer argv.deinit(); |
| 3055 | |
| 3056 | if (test_exec_args.len == 0) { |
| 3057 | // when testing pass the zig_exe_path to argv |
| 3058 | if (arg_mode == .zig_test) |
| 3059 | try argv.appendSlice(&[_][]const u8{ |
| 3060 | exe_path, self_exe_path, |
| 3061 | }) |
| 3062 | // when running just pass the current exe |
| 3063 | else |
| 3064 | try argv.appendSlice(&[_][]const u8{ |
| 3065 | exe_path, |
| 3066 | }); |
| 3067 | } else { |
| 3068 | for (test_exec_args) |arg| { |
| 3069 | if (arg) |a| { |
| 3070 | try argv.append(a); |
| 3071 | } else { |
| 3072 | try argv.appendSlice(&[_][]const u8{ |
| 3073 | exe_path, self_exe_path, |
| 3074 | }); |
| 3075 | } |
| 3076 | } |
| 3077 | } |
| 3078 | if (runtime_args_start) |i| { |
| 3079 | try argv.appendSlice(all_args[i..]); |
| 3080 | } |
| 3081 | const child = try std.ChildProcess.init(argv.items, arena); |
| 3082 | |
| 3083 | child.stdin_behavior = .Inherit; |
| 3084 | child.stdout_behavior = .Inherit; |
| 3085 | child.stderr_behavior = .Inherit; |
| 3086 | |
| 3087 | try child.spawn(); |
| 3088 | |
| 3089 | return child.pid; |
| 3090 | } |
| 3091 | |
| 2904 | 3092 | const AfterUpdateHook = union(enum) { |
| 2905 | 3093 | none, |
| 2906 | 3094 | print_emit_bin_dir_path, |