use std::path::PathBuf;
use boxlite_shared::errors::{BoxliteError, BoxliteResult};
#[derive(Default)]
pub struct BinaryFinderBuilder {
search_paths: Vec<PathBuf>,
}
impl BinaryFinderBuilder {
pub fn with_path(mut self, path: impl Into<PathBuf>) -> Self {
self.search_paths.push(path.into());
self
}
pub fn build(self) -> RuntimeBinaryFinder {
RuntimeBinaryFinder {
search_paths: self.search_paths,
}
}
}
pub struct RuntimeBinaryFinder {
search_paths: Vec<PathBuf>,
}
impl RuntimeBinaryFinder {
pub fn builder() -> BinaryFinderBuilder {
BinaryFinderBuilder::default()
}
pub fn from_env() -> Self {
let mut builder = Self::builder();
if let Ok(runtime_dir) = std::env::var("BOXLITE_RUNTIME_DIR") {
for path in runtime_dir.split(':').filter(|s| !s.is_empty()) {
builder = builder.with_path(path);
}
}
#[cfg(feature = "embedded-runtime")]
if let Some(runtime) = crate::runtime::embedded::EmbeddedRuntime::get() {
builder = builder.with_path(runtime.dir());
}
#[cfg(target_os = "macos")]
{
if let Ok(dyld_path) = std::env::var("DYLD_LIBRARY_PATH") {
for path in dyld_path.split(':').filter(|s| !s.is_empty()) {
builder = builder.with_path(path);
}
}
if let Ok(dyld_fallback) = std::env::var("DYLD_FALLBACK_LIBRARY_PATH") {
for path in dyld_fallback.split(':').filter(|s| !s.is_empty()) {
builder = builder.with_path(path);
}
}
}
#[cfg(target_os = "linux")]
{
if let Ok(ld_path) = std::env::var("LD_LIBRARY_PATH") {
for path in ld_path.split(':').filter(|s| !s.is_empty()) {
builder = builder.with_path(path);
}
}
}
if let Some(lib_dir) =
super::LibraryLoadPath::get(None).and_then(|p| p.parent().map(|d| d.to_path_buf()))
{
builder = builder.with_path(&lib_dir);
builder = builder.with_path(lib_dir.join("runtime"));
}
builder.build()
}
pub fn find(&self, binary_name: &str) -> BoxliteResult<PathBuf> {
for search_path in &self.search_paths {
let candidate = search_path.join(binary_name);
tracing::debug!("Finding binary {:?} in path: {:?}", binary_name, candidate);
if candidate.exists() {
tracing::debug!(binary = %candidate.display(), "Found binary");
return Ok(candidate);
}
}
let locations = self
.search_paths
.iter()
.map(|p| format!(" - {}", p.join(binary_name).display()))
.collect::<Vec<_>>()
.join("\n");
Err(BoxliteError::Storage(format!(
"Binary '{}' not found.\nSearched locations:\n{}",
binary_name, locations
)))
}
}
pub fn find_binary(binary_name: &str) -> BoxliteResult<PathBuf> {
RuntimeBinaryFinder::from_env().find(binary_name)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_builder_with_path() {
let finder = RuntimeBinaryFinder::builder()
.with_path("/path/one")
.with_path("/path/two")
.build();
assert_eq!(finder.search_paths.len(), 2);
assert_eq!(finder.search_paths[0], PathBuf::from("/path/one"));
assert_eq!(finder.search_paths[1], PathBuf::from("/path/two"));
}
#[test]
fn test_find_binary_success() {
let temp_dir = TempDir::new().unwrap();
let binary_path = temp_dir.path().join("test-binary");
fs::write(&binary_path, "fake binary").unwrap();
let finder = RuntimeBinaryFinder::builder()
.with_path(temp_dir.path())
.build();
let result = finder.find("test-binary");
assert!(result.is_ok());
assert_eq!(result.unwrap(), binary_path);
}
#[test]
fn test_find_binary_not_found() {
let finder = RuntimeBinaryFinder::builder()
.with_path("/nonexistent/path")
.build();
let result = finder.find("nonexistent-binary");
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Binary 'nonexistent-binary' not found")
);
}
#[test]
fn test_find_binary_priority_order() {
let temp_dir1 = TempDir::new().unwrap();
let temp_dir2 = TempDir::new().unwrap();
fs::write(temp_dir1.path().join("test-binary"), "binary1").unwrap();
fs::write(temp_dir2.path().join("test-binary"), "binary2").unwrap();
let finder = RuntimeBinaryFinder::builder()
.with_path(temp_dir1.path())
.with_path(temp_dir2.path())
.build();
let result = finder.find("test-binary").unwrap();
assert_eq!(result, temp_dir1.path().join("test-binary"));
}
}