From 14e88a61f0fa890ff9936de5eb8bc1aa7bea5244 Mon Sep 17 00:00:00 2001 From: "Calvin A. Allen" Date: Wed, 13 May 2026 11:43:02 -0400 Subject: [PATCH] fix(runner): preserve internal quotes in cmd strings on Windows Rust's default Command arg escaping uses MSVCRT backslash-escape rules that cmd.exe does not understand, so a cmd value like `go build -ldflags="-s -w"` reached the target program with the inner quotes mistokenized into separate argv entries. Use `raw_arg` to write the cmd.exe command line directly and pass `/S` so cmd.exe deterministically strips only the outermost pair of quotes, leaving internal quotes intact. --- src/runner.rs | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/runner.rs b/src/runner.rs index 0c31cb5..8de2a2c 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -176,15 +176,7 @@ fn execute_step_def( fn execute_command(cmd: &str, work_dir: &Path, env: &HashMap) -> Result<()> { println!("$ {}", cmd); - let mut command = if cfg!(target_os = "windows") { - let mut c = Command::new("cmd"); - c.args(["/C", cmd]); - c - } else { - let mut c = Command::new("sh"); - c.args(["-c", cmd]); - c - }; + let mut command = build_shell_command(cmd); command.current_dir(work_dir); command.envs(env); @@ -200,3 +192,25 @@ fn execute_command(cmd: &str, work_dir: &Path, env: &HashMap) -> Ok(()) } + +#[cfg(not(target_os = "windows"))] +fn build_shell_command(cmd: &str) -> Command { + let mut c = Command::new("sh"); + c.args(["-c", cmd]); + c +} + +// On Windows, bypass Rust's MSVCRT-style arg escaping (which uses backslash +// escapes that cmd.exe does not understand) and write the command line for +// cmd.exe directly. `/S` makes cmd.exe deterministically strip exactly the +// outermost pair of quotes, preserving any internal quotes — so a cmd like +// `go build -ldflags="-s -w"` reaches its target program intact. +#[cfg(target_os = "windows")] +fn build_shell_command(cmd: &str) -> Command { + use std::os::windows::process::CommandExt; + let mut c = Command::new("cmd"); + c.raw_arg("/S"); + c.raw_arg("/C"); + c.raw_arg(format!("\"{cmd}\"")); + c +}