From 24c1da400f286ed5736b7d53f779b23be717b85a Mon Sep 17 00:00:00 2001 From: subotac <73706465+subotac@users.noreply.github.com> Date: Thu, 1 Oct 2026 21:56:56 +0300 Subject: [PATCH] fix(cli): escape Windows paths in linker response files --- packages/cli/src/build/link.rs | 48 ++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/build/link.rs b/packages/cli/src/build/link.rs index 8107fe62e0..9183b770c8 100644 --- a/packages/cli/src/build/link.rs +++ b/packages/cli/src/build/link.rs @@ -265,9 +265,10 @@ impl BuildRequest { out_args.extend(out_arg.iter().map(Into::into)); if cfg!(windows) { + let linker_flavor = self.linker_flavor(); let cmd_contents: String = out_args .iter() - .map(|s| format!("\"{}\"", s.to_string_lossy())) + .map(|s| quote_linker_command_file_argument(&s.to_string_lossy(), &linker_flavor)) .join(" "); std::fs::write(self.windows_command_file(), cmd_contents) .context("Failed to write linker command file")?; @@ -1144,7 +1145,11 @@ impl BuildRequest { // Handle windows command files let mut out_args = args.clone(); if cfg!(windows) { - let cmd_contents: String = out_args.iter().map(|f| format!("\"{f}\"")).join(" "); + let linker_flavor = self.linker_flavor(); + let cmd_contents: String = out_args + .iter() + .map(|arg| quote_linker_command_file_argument(arg, &linker_flavor)) + .join(" "); std::fs::write(self.windows_command_file(), cmd_contents) .context("Failed to write linker command file")?; out_args = vec![format!("@{}", self.windows_command_file().display())]; @@ -1485,6 +1490,45 @@ impl BuildRequest { } } +fn quote_linker_command_file_argument(arg: &str, linker_flavor: &LinkerFlavor) -> String { + let arg = if linker_flavor == &LinkerFlavor::Msvc { + arg.to_owned() + } else { + arg.replace('\\', "\\\\").replace('"', "\\\"") + }; + format!("\"{arg}\"") +} + +#[cfg(test)] +mod linker_response_file_tests { + use super::*; + + #[test] + fn response_file_arguments_use_the_target_linker_quoting() { + assert_eq!( + quote_linker_command_file_argument( + r#"C:\Users\me\objects with spaces\file.o"#, + &LinkerFlavor::Gnu, + ), + r#""C:\\Users\\me\\objects with spaces\\file.o""# + ); + assert_eq!( + quote_linker_command_file_argument( + r#"C:\path\with spaces "quoted".o"#, + &LinkerFlavor::Darwin, + ), + r#""C:\\path\\with spaces \"quoted\".o""# + ); + assert_eq!( + quote_linker_command_file_argument( + r#"C:\Users\me\objects with spaces\file.o"#, + &LinkerFlavor::Msvc, + ), + r#""C:\Users\me\objects with spaces\file.o""# + ); + } +} + /// Reconstruct the dep-info `.d` path that rustc will write for an invocation, by parsing the /// `--out-dir`, `--crate-name`, and `-C extra-filename=` from the captured args. This mirrors /// rustc's own naming convention: `/.d`.