pub fn tool_command(program: &str) -> std::process::Command {
which::which(program).map_or_else(|_| std::process::Command::new(program), std::process::Command::new)
}
pub fn bash_command() -> std::process::Command {
if !cfg!(windows) {
return tool_command("bash");
}
for variable in ["ProgramFiles", "ProgramW6432", "ProgramFiles(x86)"] {
if let Some(root) = std::env::var_os(variable) {
let candidate = std::path::Path::new(&root).join("Git").join("bin").join("bash.exe");
if candidate.is_file() {
return std::process::Command::new(candidate);
}
}
}
if let Ok(candidates) = which::which_all("bash") {
for candidate in candidates {
if !is_wsl_launcher(&candidate) {
return std::process::Command::new(candidate);
}
}
}
std::process::Command::new("bash")
}
fn is_wsl_launcher(path: &std::path::Path) -> bool {
let text = path.to_string_lossy();
let mut segments = text.rsplit(['/', '\\']);
let _file_name = segments.next();
segments
.next()
.is_some_and(|parent| parent.eq_ignore_ascii_case("system32") || parent.eq_ignore_ascii_case("syswow64"))
}
#[cfg(test)]
mod tests {
use super::tool_command;
#[test]
fn a_resolvable_tool_is_spawned_by_its_resolved_path() {
let resolved = which::which("cargo").expect("cargo is on PATH for the test suite");
let command = tool_command("cargo");
assert_eq!(std::path::Path::new(command.get_program()), resolved);
assert!(std::path::Path::new(command.get_program()).is_absolute());
}
#[test]
fn an_unresolvable_tool_falls_back_to_the_bare_program_name() {
let command = tool_command("alef-no-such-toolchain-exists");
assert_eq!(command.get_program(), "alef-no-such-toolchain-exists");
}
}
#[cfg(test)]
mod bash_tests {
use super::{bash_command, is_wsl_launcher};
#[test]
fn the_system32_entry_is_recognised_as_the_wsl_launcher() {
assert!(is_wsl_launcher(std::path::Path::new(r"C:\Windows\System32\bash.exe")));
assert!(is_wsl_launcher(std::path::Path::new(r"C:\Windows\SysWOW64\bash.exe")));
}
#[test]
fn a_real_bash_is_not_mistaken_for_the_launcher() {
assert!(!is_wsl_launcher(std::path::Path::new(
r"C:\Program Files\Git\bin\bash.exe"
)));
assert!(!is_wsl_launcher(std::path::Path::new("/bin/bash")));
assert!(!is_wsl_launcher(std::path::Path::new("/usr/bin/bash")));
}
#[cfg(windows)]
#[test]
fn a_windows_host_never_resolves_the_wsl_launcher_as_bash() {
let command = bash_command();
assert!(!is_wsl_launcher(std::path::Path::new(command.get_program())));
}
#[cfg(not(windows))]
#[test]
fn a_unix_host_resolves_bash_the_same_way_every_other_tool_is_resolved() {
let resolved = which::which("bash").expect("bash is on PATH for the test suite");
assert_eq!(std::path::Path::new(bash_command().get_program()), resolved);
}
}