use std::path::Path;
use std::process::ExitCode;
use super::super::util::exit_code_from_i32;
use super::tool_resolution::resolve_compiler_path;
fn run_with_released_cwd(
cmd: &mut std::process::Command,
) -> std::io::Result<std::process::ExitStatus> {
if let Ok(cwd) = std::env::current_dir() {
cmd.current_dir(&cwd);
let _ = std::env::set_current_dir(std::env::temp_dir());
}
cmd.status()
}
pub(super) fn run_passthrough(args: &[String]) -> ExitCode {
let tool = &args[0];
let tool_args = args.get(1..).unwrap_or(&[]);
let resolved = resolve_compiler_path(tool);
let mut cmd = std::process::Command::new(&resolved);
cmd.args(tool_args);
match run_with_released_cwd(&mut cmd) {
Ok(status) => exit_code_from_i32(status.code().unwrap_or(1)),
Err(e) => {
eprintln!("zccache: failed to run {}: {e}", resolved.display());
ExitCode::FAILURE
}
}
}
pub(super) fn run_tool_direct(tool: &Path, args: &[String]) -> ExitCode {
let mut cmd = std::process::Command::new(tool);
cmd.args(args);
match run_with_released_cwd(&mut cmd) {
Ok(status) => exit_code_from_i32(status.code().unwrap_or(1)),
Err(e) => {
eprintln!("zccache: failed to run {}: {e}", tool.display());
ExitCode::FAILURE
}
}
}
#[cfg(test)]
mod tests {
use super::*;
static CWD_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn noop_tool() -> std::path::PathBuf {
if cfg!(windows) {
std::path::PathBuf::from("cmd.exe")
} else {
std::path::PathBuf::from("true")
}
}
fn noop_args() -> Vec<String> {
if cfg!(windows) {
vec!["/c".to_string(), "exit".to_string(), "0".to_string()]
} else {
Vec::new()
}
}
#[test]
fn run_passthrough_releases_wrapper_cwd() {
let _guard = CWD_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let original_cwd = std::env::current_dir().ok();
let build_dir = tempfile::tempdir().unwrap();
let canonical_build_dir = std::fs::canonicalize(build_dir.path()).unwrap();
std::env::set_current_dir(&canonical_build_dir).unwrap();
let mut args = vec![noop_tool().to_string_lossy().into_owned()];
args.extend(noop_args());
let _ = run_passthrough(&args);
let after = std::env::current_dir().unwrap();
let after_canonical = std::fs::canonicalize(&after).unwrap_or(after);
assert_ne!(
after_canonical, canonical_build_dir,
"issue #555: run_passthrough must release the wrapper's CWD \
before returning so the build dir is not pinned by the wrapper's \
kernel handle on Windows",
);
if let Some(cwd) = original_cwd {
let _ = std::env::set_current_dir(cwd);
}
}
#[test]
fn run_tool_direct_releases_wrapper_cwd() {
let _guard = CWD_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let original_cwd = std::env::current_dir().ok();
let build_dir = tempfile::tempdir().unwrap();
let canonical_build_dir = std::fs::canonicalize(build_dir.path()).unwrap();
std::env::set_current_dir(&canonical_build_dir).unwrap();
let tool = noop_tool();
let args: Vec<String> = noop_args();
let _ = run_tool_direct(&tool, &args);
let after = std::env::current_dir().unwrap();
let after_canonical = std::fs::canonicalize(&after).unwrap_or(after);
assert_ne!(
after_canonical, canonical_build_dir,
"issue #555: run_tool_direct must release the wrapper's CWD",
);
if let Some(cwd) = original_cwd {
let _ = std::env::set_current_dir(cwd);
}
}
}