From 95d68f725fc2ed082c4d3dad4bd283203d72ac30 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Fri, 28 Nov 2025 15:05:35 +1100 Subject: [PATCH] support passing args to roc run using `--` --- src/cli/main.zig | 63 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/src/cli/main.zig b/src/cli/main.zig index 43c44e8025..2405afb976 100644 --- a/src/cli/main.zig +++ b/src/cli/main.zig @@ -967,13 +967,13 @@ fn rocRun(allocs: *Allocators, args: cli_args.RunArgs) !void { if (comptime is_windows) { // Windows: Use handle inheritance approach std.log.debug("Using Windows handle inheritance approach", .{}); - runWithWindowsHandleInheritance(allocs, exe_path, shm_handle) catch |err| { + runWithWindowsHandleInheritance(allocs, exe_path, shm_handle, args.app_args) catch |err| { return err; }; } else { // POSIX: Use existing file descriptor inheritance approach std.log.debug("Using POSIX file descriptor inheritance approach", .{}); - runWithPosixFdInheritance(allocs, exe_path, shm_handle, &cache_manager) catch |err| { + runWithPosixFdInheritance(allocs, exe_path, shm_handle, &cache_manager, args.app_args) catch |err| { return err; }; } @@ -981,7 +981,7 @@ fn rocRun(allocs: *Allocators, args: cli_args.RunArgs) !void { } /// Run child process using Windows handle inheritance (idiomatic Windows approach) -fn runWithWindowsHandleInheritance(allocs: *Allocators, exe_path: []const u8, shm_handle: SharedMemoryHandle) !void { +fn runWithWindowsHandleInheritance(allocs: *Allocators, exe_path: []const u8, shm_handle: SharedMemoryHandle, app_args: []const []const u8) !void { // Make the shared memory handle inheritable if (windows.SetHandleInformation(@ptrCast(shm_handle.fd), windows.HANDLE_FLAG_INHERIT, windows.HANDLE_FLAG_INHERIT) == 0) { std.log.err("Failed to set handle as inheritable", .{}); @@ -994,9 +994,33 @@ fn runWithWindowsHandleInheritance(allocs: *Allocators, exe_path: []const u8, sh const cwd = try std.fs.cwd().realpathAlloc(allocs.arena, "."); const cwd_w = try std.unicode.utf8ToUtf16LeAllocZ(allocs.arena, cwd); - // Create command line with handle and size as arguments + // Create command line with handle and size as arguments, plus any app arguments const handle_uint = @intFromPtr(shm_handle.fd); - const cmd_line = try std.fmt.allocPrintSentinel(allocs.arena, "\"{s}\" {} {}", .{ exe_path, handle_uint, shm_handle.size }, 0); + + // Filter out "--" separator if it's the first app argument (common shell convention) + const filtered_app_args = if (app_args.len > 0 and std.mem.eql(u8, app_args[0], "--")) + app_args[1..] + else + app_args; + + // Build command line string with proper quoting for Windows + var cmd_builder = std.array_list.Managed(u8).initCapacity(allocs.gpa, 256) catch |err| { + std.log.err("Failed to allocate command line builder: {}", .{err}); + return err; + }; + defer cmd_builder.deinit(); + try cmd_builder.writer().print("\"{s}\" {} {}", .{ exe_path, handle_uint, shm_handle.size }); + for (filtered_app_args) |arg| { + // Quote arguments that contain spaces or special characters + if (std.mem.indexOfAny(u8, arg, " \t\"") != null) { + try cmd_builder.writer().print(" \"{s}\"", .{arg}); + } else { + try cmd_builder.writer().print(" {s}", .{arg}); + } + } + try cmd_builder.append(0); // null terminator for sentinel + + const cmd_line = cmd_builder.items[0 .. cmd_builder.items.len - 1 :0]; const cmd_line_w = try std.unicode.utf8ToUtf16LeAllocZ(allocs.arena, cmd_line); // Set up process creation structures @@ -1068,7 +1092,7 @@ fn runWithWindowsHandleInheritance(allocs: *Allocators, exe_path: []const u8, sh } /// Run child process using POSIX file descriptor inheritance (existing approach for Unix) -fn runWithPosixFdInheritance(allocs: *Allocators, exe_path: []const u8, shm_handle: SharedMemoryHandle, cache_manager: *CacheManager) !void { +fn runWithPosixFdInheritance(allocs: *Allocators, exe_path: []const u8, shm_handle: SharedMemoryHandle, cache_manager: *CacheManager, app_args: []const []const u8) !void { // Get cache directory for temporary files const temp_cache_dir = cache_manager.config.getTempDir(allocs.arena) catch |err| { std.log.err("Failed to get temp cache directory: {}", .{err}); @@ -1106,8 +1130,31 @@ fn runWithPosixFdInheritance(allocs: *Allocators, exe_path: []const u8, shm_hand return error.FdConfigFailed; } + // Build argv array: executable path + app arguments + // Filter out "--" separator if it's the first app argument (common shell convention) + const filtered_app_args = if (app_args.len > 0 and std.mem.eql(u8, app_args[0], "--")) + app_args[1..] + else + app_args; + + var argv = std.array_list.Managed([]const u8).initCapacity(allocs.gpa, 1 + filtered_app_args.len) catch |err| { + std.log.err("Failed to allocate argv: {}", .{err}); + return err; + }; + defer argv.deinit(); + argv.append(temp_exe_path) catch |err| { + std.log.err("Failed to append exe path to argv: {}", .{err}); + return err; + }; + for (filtered_app_args) |arg| { + argv.append(arg) catch |err| { + std.log.err("Failed to append app arg: {}", .{err}); + return err; + }; + } + // Run the interpreter as a child process from the temp directory - var child = std.process.Child.init(&.{temp_exe_path}, allocs.gpa); + var child = std.process.Child.init(argv.items, allocs.gpa); child.cwd = std.fs.cwd().realpathAlloc(allocs.arena, ".") catch |err| { std.log.err("Failed to get current directory: {}", .{err}); return err; @@ -1118,7 +1165,7 @@ fn runWithPosixFdInheritance(allocs: *Allocators, exe_path: []const u8, shm_hand child.stderr_behavior = .Inherit; // Spawn the child process - std.log.debug("Spawning child process: {s}", .{temp_exe_path}); + std.log.debug("Spawning child process: {s} with {} app args", .{ temp_exe_path, filtered_app_args.len }); std.log.debug("Child process working directory: {s}", .{child.cwd.?}); child.spawn() catch |err| { std.log.err("Failed to spawn {s}: {}", .{ temp_exe_path, err });