116 lines
3.2 KiB
Rust
116 lines
3.2 KiB
Rust
use crate::subprocess::SubprocessExt;
|
|
use anyhow::Result;
|
|
use std::env;
|
|
use std::path::PathBuf;
|
|
use tokio::process::Command;
|
|
use tokio::sync::OnceCell;
|
|
|
|
static SHELL_PATH_DIRS: OnceCell<Result<Vec<PathBuf>, anyhow::Error>> = OnceCell::const_new();
|
|
|
|
pub async fn get_shell_path_dirs() -> Result<&'static Vec<PathBuf>> {
|
|
let result = SHELL_PATH_DIRS
|
|
.get_or_init(|| async {
|
|
get_shell_path_async()
|
|
.await
|
|
.map(|path| env::split_paths(&path).collect())
|
|
})
|
|
.await;
|
|
|
|
match result {
|
|
Ok(dirs) => Ok(dirs),
|
|
Err(e) => Err(anyhow::anyhow!(
|
|
"Failed to get shell PATH directories: {}",
|
|
e
|
|
)),
|
|
}
|
|
}
|
|
|
|
async fn get_shell_path_async() -> Result<String> {
|
|
let shell = env::var("SHELL").unwrap_or_else(|_| {
|
|
if cfg!(windows) {
|
|
"cmd".to_string()
|
|
} else {
|
|
"/bin/bash".to_string()
|
|
}
|
|
});
|
|
|
|
if cfg!(windows) {
|
|
get_windows_path_async(&shell).await
|
|
} else {
|
|
get_unix_path_async(&shell).await
|
|
}
|
|
.or_else(|e| {
|
|
tracing::warn!(
|
|
"Failed to get PATH from shell ({}), falling back to current PATH",
|
|
e
|
|
);
|
|
env::var("PATH").map_err(|_| anyhow::anyhow!("No PATH variable available"))
|
|
})
|
|
}
|
|
|
|
async fn get_unix_path_async(shell: &str) -> Result<String> {
|
|
let output = Command::new(shell)
|
|
.args(["-l", "-i", "-c", "echo $PATH"])
|
|
.output()
|
|
.await
|
|
.map_err(|e| anyhow::anyhow!("Failed to execute shell command: {}", e))?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
return Err(anyhow::anyhow!("Shell command failed: {}", stderr));
|
|
}
|
|
|
|
let path = String::from_utf8(output.stdout)
|
|
.map_err(|e| anyhow::anyhow!("Invalid UTF-8 in shell output: {}", e))?
|
|
.trim()
|
|
.to_string();
|
|
|
|
if path.is_empty() {
|
|
return Err(anyhow::anyhow!("Shell returned empty PATH"));
|
|
}
|
|
|
|
Ok(path)
|
|
}
|
|
|
|
async fn get_windows_path_async(shell: &str) -> Result<String> {
|
|
let shell_name = std::path::Path::new(shell)
|
|
.file_stem()
|
|
.and_then(|s| s.to_str())
|
|
.unwrap_or("cmd");
|
|
|
|
let output = match shell_name {
|
|
"pwsh" | "powershell" => {
|
|
Command::new(shell)
|
|
.args(["-NoLogo", "-Command", "$env:PATH"])
|
|
.set_no_window()
|
|
.output()
|
|
.await
|
|
}
|
|
_ => {
|
|
Command::new(shell)
|
|
.args(["/c", "echo %PATH%"])
|
|
.set_no_window()
|
|
.output()
|
|
.await
|
|
}
|
|
};
|
|
|
|
let output = output.map_err(|e| anyhow::anyhow!("Failed to execute shell command: {}", e))?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
return Err(anyhow::anyhow!("Shell command failed: {}", stderr));
|
|
}
|
|
|
|
let path = String::from_utf8(output.stdout)
|
|
.map_err(|e| anyhow::anyhow!("Invalid UTF-8 in shell output: {}", e))?
|
|
.trim()
|
|
.to_string();
|
|
|
|
if path.is_empty() {
|
|
return Err(anyhow::anyhow!("Shell returned empty PATH"));
|
|
}
|
|
|
|
Ok(path)
|
|
}
|