| ... | ... | @@ -687,6 +687,7 @@ fn buildOutputType( |
| 687 | 687 | var function_sections = false; |
| 688 | 688 | var no_builtin = false; |
| 689 | 689 | var watch = false; |
| 690 | var listen_addr: ?std.net.Ip4Address = null; |
| 690 | 691 | var debug_compile_errors = false; |
| 691 | 692 | var verbose_link = (builtin.os.tag != .wasi or builtin.link_libc) and std.process.hasEnvVarConstant("ZIG_VERBOSE_LINK"); |
| 692 | 693 | var verbose_cc = (builtin.os.tag != .wasi or builtin.link_libc) and std.process.hasEnvVarConstant("ZIG_VERBOSE_CC"); |
| ... | ... | @@ -1144,6 +1145,17 @@ fn buildOutputType( |
| 1144 | 1145 | } else { |
| 1145 | 1146 | try log_scopes.append(gpa, args_iter.nextOrFatal()); |
| 1146 | 1147 | } |
| 1148 | } else if (mem.eql(u8, arg, "--listen")) { |
| 1149 | const next_arg = args_iter.nextOrFatal(); |
| 1150 | // example: --listen 127.0.0.1:9000 |
| 1151 | var it = std.mem.split(u8, next_arg, ":"); |
| 1152 | const host = it.next().?; |
| 1153 | const port_text = it.next() orelse "14735"; |
| 1154 | const port = std.fmt.parseInt(u16, port_text, 10) catch |err| |
| 1155 | fatal("invalid port number: '{s}': {s}", .{ port_text, @errorName(err) }); |
| 1156 | listen_addr = std.net.Ip4Address.parse(host, port) catch |err| |
| 1157 | fatal("invalid host: '{s}': {s}", .{ host, @errorName(err) }); |
| 1158 | watch = true; |
| 1147 | 1159 | } else if (mem.eql(u8, arg, "--debug-link-snapshot")) { |
| 1148 | 1160 | if (!build_options.enable_link_snapshots) { |
| 1149 | 1161 | std.log.warn("Zig was compiled without linker snapshots enabled (-Dlink-snapshot). --debug-link-snapshot has no effect.", .{}); |
| ... | ... | @@ -3353,6 +3365,125 @@ fn buildOutputType( |
| 3353 | 3365 | |
| 3354 | 3366 | var last_cmd: ReplCmd = .help; |
| 3355 | 3367 | |
| 3368 | if (listen_addr) |ip4_addr| { |
| 3369 | var server = std.net.StreamServer.init(.{ |
| 3370 | .reuse_address = true, |
| 3371 | }); |
| 3372 | defer server.deinit(); |
| 3373 | |
| 3374 | try server.listen(.{ .in = ip4_addr }); |
| 3375 | |
| 3376 | while (true) { |
| 3377 | const conn = try server.accept(); |
| 3378 | defer conn.stream.close(); |
| 3379 | |
| 3380 | var buf: [100]u8 = undefined; |
| 3381 | var child_pid: ?i32 = null; |
| 3382 | |
| 3383 | while (true) { |
| 3384 | try comp.makeBinFileExecutable(); |
| 3385 | |
| 3386 | const amt = try conn.stream.read(&buf); |
| 3387 | const line = buf[0..amt]; |
| 3388 | const actual_line = mem.trimRight(u8, line, "\r\n "); |
| 3389 | |
| 3390 | const cmd: ReplCmd = blk: { |
| 3391 | if (mem.eql(u8, actual_line, "update")) { |
| 3392 | break :blk .update; |
| 3393 | } else if (mem.eql(u8, actual_line, "exit")) { |
| 3394 | break; |
| 3395 | } else if (mem.eql(u8, actual_line, "help")) { |
| 3396 | break :blk .help; |
| 3397 | } else if (mem.eql(u8, actual_line, "run")) { |
| 3398 | break :blk .run; |
| 3399 | } else if (mem.eql(u8, actual_line, "update-and-run")) { |
| 3400 | break :blk .update_and_run; |
| 3401 | } else if (actual_line.len == 0) { |
| 3402 | break :blk last_cmd; |
| 3403 | } else { |
| 3404 | try stderr.print("unknown command: {s}\n", .{actual_line}); |
| 3405 | continue; |
| 3406 | } |
| 3407 | }; |
| 3408 | last_cmd = cmd; |
| 3409 | switch (cmd) { |
| 3410 | .update => { |
| 3411 | tracy.frameMark(); |
| 3412 | if (output_mode == .Exe) { |
| 3413 | try comp.makeBinFileWritable(); |
| 3414 | } |
| 3415 | updateModule(gpa, comp, hook) catch |err| switch (err) { |
| 3416 | error.SemanticAnalyzeFail => continue, |
| 3417 | else => |e| return e, |
| 3418 | }; |
| 3419 | }, |
| 3420 | .help => { |
| 3421 | try stderr.writeAll(repl_help); |
| 3422 | }, |
| 3423 | .run => { |
| 3424 | tracy.frameMark(); |
| 3425 | try runOrTest( |
| 3426 | comp, |
| 3427 | gpa, |
| 3428 | arena, |
| 3429 | test_exec_args.items, |
| 3430 | self_exe_path.?, |
| 3431 | arg_mode, |
| 3432 | target_info, |
| 3433 | watch, |
| 3434 | &comp_destroyed, |
| 3435 | all_args, |
| 3436 | runtime_args_start, |
| 3437 | link_libc, |
| 3438 | ); |
| 3439 | }, |
| 3440 | .update_and_run => { |
| 3441 | tracy.frameMark(); |
| 3442 | if (child_pid) |pid| { |
| 3443 | try conn.stream.writer().print("hot code swap requested for pid {d}", .{pid}); |
| 3444 | try comp.hotCodeSwap(pid); |
| 3445 | |
| 3446 | var errors = try comp.getAllErrorsAlloc(); |
| 3447 | defer errors.deinit(comp.gpa); |
| 3448 | |
| 3449 | if (errors.list.len != 0) { |
| 3450 | const ttyconf: std.debug.TTY.Config = switch (comp.color) { |
| 3451 | .auto => std.debug.detectTTYConfig(std.io.getStdErr()), |
| 3452 | .on => .escape_codes, |
| 3453 | .off => .no_color, |
| 3454 | }; |
| 3455 | for (errors.list) |full_err_msg| { |
| 3456 | try full_err_msg.renderToWriter(ttyconf, conn.stream.writer(), "error:", .Red, 0); |
| 3457 | } |
| 3458 | continue; |
| 3459 | } |
| 3460 | } else { |
| 3461 | if (output_mode == .Exe) { |
| 3462 | try comp.makeBinFileWritable(); |
| 3463 | } |
| 3464 | updateModule(gpa, comp, hook) catch |err| switch (err) { |
| 3465 | error.SemanticAnalyzeFail => continue, |
| 3466 | else => |e| return e, |
| 3467 | }; |
| 3468 | try comp.makeBinFileExecutable(); |
| 3469 | |
| 3470 | child_pid = try runOrTestHotSwap( |
| 3471 | comp, |
| 3472 | gpa, |
| 3473 | arena, |
| 3474 | test_exec_args.items, |
| 3475 | self_exe_path.?, |
| 3476 | arg_mode, |
| 3477 | all_args, |
| 3478 | runtime_args_start, |
| 3479 | ); |
| 3480 | } |
| 3481 | }, |
| 3482 | } |
| 3483 | } |
| 3484 | } |
| 3485 | } |
| 3486 | |
| 3356 | 3487 | while (watch) { |
| 3357 | 3488 | try stderr.print("(zig) ", .{}); |
| 3358 | 3489 | try comp.makeBinFileExecutable(); |
| ... | ... | @@ -3631,6 +3762,62 @@ fn runOrTest( |
| 3631 | 3762 | } |
| 3632 | 3763 | } |
| 3633 | 3764 | |
| 3765 | fn runOrTestHotSwap( |
| 3766 | comp: *Compilation, |
| 3767 | gpa: Allocator, |
| 3768 | arena: Allocator, |
| 3769 | test_exec_args: []const ?[]const u8, |
| 3770 | self_exe_path: []const u8, |
| 3771 | arg_mode: ArgMode, |
| 3772 | all_args: []const []const u8, |
| 3773 | runtime_args_start: ?usize, |
| 3774 | ) !i32 { |
| 3775 | const exe_emit = comp.bin_file.options.emit.?; |
| 3776 | // A naive `directory.join` here will indeed get the correct path to the binary, |
| 3777 | // however, in the case of cwd, we actually want `./foo` so that the path can be executed. |
| 3778 | const exe_path = try fs.path.join(arena, &[_][]const u8{ |
| 3779 | exe_emit.directory.path orelse ".", exe_emit.sub_path, |
| 3780 | }); |
| 3781 | |
| 3782 | var argv = std.ArrayList([]const u8).init(gpa); |
| 3783 | defer argv.deinit(); |
| 3784 | |
| 3785 | if (test_exec_args.len == 0) { |
| 3786 | // when testing pass the zig_exe_path to argv |
| 3787 | if (arg_mode == .zig_test) |
| 3788 | try argv.appendSlice(&[_][]const u8{ |
| 3789 | exe_path, self_exe_path, |
| 3790 | }) |
| 3791 | // when running just pass the current exe |
| 3792 | else |
| 3793 | try argv.appendSlice(&[_][]const u8{ |
| 3794 | exe_path, |
| 3795 | }); |
| 3796 | } else { |
| 3797 | for (test_exec_args) |arg| { |
| 3798 | if (arg) |a| { |
| 3799 | try argv.append(a); |
| 3800 | } else { |
| 3801 | try argv.appendSlice(&[_][]const u8{ |
| 3802 | exe_path, self_exe_path, |
| 3803 | }); |
| 3804 | } |
| 3805 | } |
| 3806 | } |
| 3807 | if (runtime_args_start) |i| { |
| 3808 | try argv.appendSlice(all_args[i..]); |
| 3809 | } |
| 3810 | var child = std.ChildProcess.init(argv.items, arena); |
| 3811 | |
| 3812 | child.stdin_behavior = .Inherit; |
| 3813 | child.stdout_behavior = .Inherit; |
| 3814 | child.stderr_behavior = .Inherit; |
| 3815 | |
| 3816 | try child.spawn(); |
| 3817 | |
| 3818 | return child.pid; |
| 3819 | } |
| 3820 | |
| 3634 | 3821 | const AfterUpdateHook = union(enum) { |
| 3635 | 3822 | none, |
| 3636 | 3823 | print_emit_bin_dir_path, |