Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions tools/third-party-licenses/src/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}

Expand Down Expand Up @@ -84,13 +85,67 @@ fn parse(parsed: Output) -> Vec<LicenseEntry> {
.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<Output, Error> {
// 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))));
}
Expand Down
Loading