use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use crate::{ChildOutput, Plan};
use super::resolve::{resolve, ProgramKind};
use crate::error::{EngineError, Result};
pub fn command_for(plan: &Plan, cwd: &Path, child_output: ChildOutput) -> Result<Command> {
let cwd = plan.cwd.as_deref().unwrap_or(cwd);
let resolved = resolve(&plan.program, cwd)?;
let mut cmd = match resolved.kind {
ProgramKind::Native | ProgramKind::CmdShim => {
let mut c = Command::new(&resolved.program);
c.args(&plan.args);
c
}
ProgramKind::PowerShellShim => {
let interpreter = resolve_powershell()?;
let mut c = Command::new(interpreter);
c.arg("-NoProfile").arg("-File").arg(&resolved.program);
c.args(&plan.args);
c
}
};
cmd.current_dir(cwd);
cmd.stdin(Stdio::inherit())
.stdout(match child_output {
ChildOutput::Inherit => Stdio::inherit(),
ChildOutput::OnStderr => our_stderr()?,
})
.stderr(Stdio::inherit());
Ok(cmd)
}
fn our_stderr() -> Result<Stdio> {
#[cfg(unix)]
{
use std::os::fd::AsFd;
return Ok(Stdio::from(
std::io::stderr()
.as_fd()
.try_clone_to_owned()
.map_err(|error| {
EngineError::Setup(format!("cannot redirect the backend output: {error}"))
})?,
));
}
#[cfg(windows)]
{
use std::os::windows::io::AsHandle;
return Ok(Stdio::from(
std::io::stderr()
.as_handle()
.try_clone_to_owned()
.map_err(|error| {
EngineError::Setup(format!("cannot redirect the backend output: {error}"))
})?,
));
}
#[allow(unreachable_code)]
Ok(Stdio::inherit())
}
fn resolve_powershell() -> Result<PathBuf> {
for name in ["pwsh", "powershell"] {
if let Ok(path) = which::which(name) {
return Ok(path);
}
}
Err(EngineError::not_found(
"pmpx runs .ps1 shims with PowerShell, and neither `pwsh` nor `powershell` is on PATH."
.to_string(),
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_missing_program_fails_before_spawning() {
let tmp = tempfile::tempdir().unwrap();
let spec = Plan::new("pmpx-definitely-not-a-real-program-xyz");
assert!(command_for(&spec, tmp.path(), ChildOutput::Inherit).is_err());
}
#[test]
fn a_cwd_in_the_spec_overrides_the_callers_directory() {
let outer = tempfile::tempdir().unwrap();
let inner = tempfile::tempdir().unwrap();
let spec = Plan::new("cargo").arg("--version").cwd(inner.path());
let cmd = command_for(&spec, outer.path(), ChildOutput::Inherit).unwrap();
assert_eq!(cmd.get_current_dir(), Some(inner.path()));
}
#[test]
fn without_a_cwd_in_the_spec_the_callers_directory_is_used() {
let outer = tempfile::tempdir().unwrap();
let spec = Plan::new("cargo").arg("--version");
let cmd = command_for(&spec, outer.path(), ChildOutput::Inherit).unwrap();
assert_eq!(cmd.get_current_dir(), Some(outer.path()));
}
}