diff --git a/tools/third-party-licenses/src/cargo.rs b/tools/third-party-licenses/src/cargo.rs index 7b10a4fa43..084b5d837a 100644 --- a/tools/third-party-licenses/src/cargo.rs +++ b/tools/third-party-licenses/src/cargo.rs @@ -4,6 +4,7 @@ use std::fs; use std::hash::{Hash, Hasher}; use std::path::PathBuf; use std::process::Command; +use std::time::{SystemTime, UNIX_EPOCH}; pub struct CargoLicenseSource {} @@ -84,13 +85,67 @@ fn parse(parsed: Output) -> Vec { .collect() } +// Guard to ensure temp file cleanup even on early returns or panics +struct TempFileGuard { + path: PathBuf, +} + +impl TempFileGuard { + fn new(path: PathBuf) -> Self { + Self { path } + } +} + +impl Drop for TempFileGuard { + fn drop(&mut self) { + let _ = fs::remove_file(&self.path); + } +} + fn run() -> Result { + // Try normal stdout capture first let output = Command::new("cargo") .args(["about", "generate", "--format", "json", "--frozen"]) .current_dir(env!("CARGO_WORKSPACE_DIR")) .output() .map_err(|e| Error::Io(e, "Failed to run cargo about generate".into()))?; + // On Windows, if cargo-about fails (often due to PowerShell detection in process ancestry), + // fall back to using a temporary file to work around the issue. + // TODO: Add an option to cargo-about to disable the PowerShell check (see issue: https://discord.com/channels/731730685944922173/731738914812854303/1479960786871779459) + #[cfg(target_os = "windows")] + if !output.status.success() { + eprintln!("cargo-about failed with stdout capture, retrying with temporary file..."); + + // Generate unique temp filename to avoid race conditions + let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_nanos(); + let pid = std::process::id(); + let temp_filename = format!(".cargo-about-temp-{}-{}.json", pid, timestamp); + let temp_file = PathBuf::from(env!("CARGO_WORKSPACE_DIR")).join(&temp_filename); + + // Ensure cleanup even if we return early or panic + let _guard = TempFileGuard::new(temp_file.clone()); + + let status = Command::new("cargo") + .args(["about", "generate", "--format", "json", "--frozen", "--output-file"]) + .arg(&temp_file) + .current_dir(env!("CARGO_WORKSPACE_DIR")) + .status() + .map_err(|e| Error::Io(e, "Failed to run cargo about generate with temp file".into()))?; + + if !status.success() { + return Err(Error::Command(format!( + "cargo about generate failed:\nOriginal error: {}\nTemp file error: command exited with non-zero status", + String::from_utf8_lossy(&output.stderr) + ))); + } + + let json_content = fs::read_to_string(&temp_file).map_err(|e| Error::Io(e, format!("Failed to read cargo about output from {}", temp_file.display())))?; + + return serde_json::from_str(&json_content).map_err(|e| Error::Json(e, "Failed to parse cargo about generate JSON from temp file".into())); + } + + // Handle other error cases if !output.status.success() { return Err(Error::Command(format!("cargo about generate failed:\n{}", String::from_utf8_lossy(&output.stderr)))); }