use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::{LazyLock, RwLock};
use crate::pre::*;
static BIN_PATHS: LazyLock<RwLock<BTreeMap<String, PathBuf>>> =
LazyLock::new(|| RwLock::new(BTreeMap::new()));
#[inline(always)]
pub fn clear(name: impl AsRef<str>) {
clear_impl(name.as_ref())
}
fn clear_impl(name: &str) {
BIN_PATHS
.write()
.expect("could not lock global bin path map")
.remove(name);
}
#[inline(always)]
pub fn get(name: impl AsRef<str>) -> Option<PathBuf> {
get_impl(name.as_ref())
}
fn get_impl(name: &str) -> Option<PathBuf> {
let guard = BIN_PATHS
.read()
.expect("could not lock global bin path map");
let path = guard.get(name)?;
if !path.exists() {
clear_impl(name);
return None;
}
Some(path.clone())
}
#[inline(always)]
pub fn set(name: impl AsRef<str>, path: impl AsRef<Path>) -> crate::Result<PathBuf> {
set_impl(name.as_ref(), path.as_ref())
}
fn set_impl(name: &str, path: &Path) -> crate::Result<PathBuf> {
let path = location(path).find(name)?;
crate::trace!("setting bin path: '{name}' -> '{}'", path.display());
let mut paths = BIN_PATHS
.write()
.expect("could not lock global bin path map");
let old = paths.insert(name.to_string(), path.clone());
if let Some(old) = old {
crate::trace!("replacing old bin path: '{}'", old.display());
}
Ok(path)
}
#[inline(always)]
pub fn which(name: impl AsRef<str>) -> crate::Result<PathBuf> {
find_impl(name.as_ref(), std::iter::once(in_PATH()))
}
#[inline(always)]
pub fn resolve(name: impl AsRef<str>, path: impl AsRef<Path>) -> crate::Result<PathBuf> {
find_impl(name.as_ref(), std::iter::once(location(path.as_ref())))
}
#[inline(always)]
pub fn find<'a, I: IntoIterator<Item = Strategy<'a>>>(
name: impl AsRef<str>,
strats: I,
) -> crate::Result<PathBuf> {
find_impl(name.as_ref(), strats)
}
fn find_impl<'a, I: IntoIterator<Item = Strategy<'a>>>(
name: &str,
strats: I,
) -> crate::Result<PathBuf> {
{
let Ok(paths) = BIN_PATHS.read() else {
crate::bail!("could not lock global bin path map");
};
if let Some(x) = paths.get(name)
&& x.exists()
{
crate::trace!("found executable '{name}' from cache: {}", x.display());
return Ok(x.clone());
}
}
let Ok(mut paths) = BIN_PATHS.write() else {
crate::bail!("could not lock global bin path map");
};
if let Some(x) = paths.get(name)
&& x.exists()
{
crate::trace!("found executable '{name}' from cache: {}", x.display());
return Ok(x.clone());
}
let path = find_with_strats(name, strats)?;
crate::trace!("found executable '{name}': {}", path.display());
paths.insert(name.to_string(), path.clone());
Ok(path)
}
fn find_with_strats<'a, I: IntoIterator<Item = Strategy<'a>>>(
name: &str,
strats: I,
) -> crate::Result<PathBuf> {
let mut strats = strats.into_iter();
let Some(first) = strats.next() else {
crate::bail!("must provide at least one strategy to cu::bin::find");
};
let mut error = match first.find(name) {
Ok(x) => return Ok(x),
Err(e) => e,
};
let mut prev = first;
for strat in strats {
crate::trace!("could not finding '{name}' {prev}: {error}");
crate::trace!("falling back to finding '{name}' {strat}");
error = match strat.find(name) {
Ok(x) => return Ok(x),
Err(e) => e,
};
prev = strat;
}
crate::trace!("could not finding '{name}' {prev}: {error}");
crate::bail!("could not find program '{name}'");
}
pub fn location<'a>(path: &'a Path) -> Strategy<'a> {
Strategy::Resolve(path)
}
#[allow(non_snake_case)]
pub fn in_PATH() -> Strategy<'static> {
Strategy::Which
}
pub fn from_env<'a>(env: &'a str) -> Strategy<'a> {
Strategy::EnvVar(env)
}
pub enum Strategy<'a> {
Which,
Resolve(&'a Path),
EnvVar(&'a str),
}
impl Strategy<'_> {
fn find(&self, name: &str) -> crate::Result<PathBuf> {
match self {
Self::Which => {
crate::trace!("finding executable '{name}' in PATH");
match which::which(name) {
Ok(x) => Ok(x.simplified().to_path_buf()),
Err(e) => Err(e)?,
}
}
Self::Resolve(path) => {
crate::trace!("finding executable '{name}' at '{}'", path.display());
path.normalize_executable()
}
Self::EnvVar(v) => {
crate::trace!("finding executable '{name}' using environment variable '{v}'");
match std::env::var(v) {
Ok(x) if !x.is_empty() => Path::new(&x).normalize_executable(),
_ => crate::bail!("environment variable '{v}' not found"),
}
}
}
}
}
impl std::fmt::Display for Strategy<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Strategy::Which => write!(f, "in PATH"),
Strategy::Resolve(path) => write!(f, " at '{}'", path.display()),
Strategy::EnvVar(env) => write!(f, "using envvar '{env}'"),
}
}
}