use std::{env, fs, io::Write, path::PathBuf, sync::OnceLock};
use blake3::Hasher;
use libloading::Library;
use crate::{api::Api, Error, Result};
#[cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))]
use velr_runtime_linux_x86_64 as selected_runtime;
#[cfg(all(target_os = "linux", target_arch = "aarch64", target_env = "gnu"))]
use velr_runtime_linux_aarch64 as selected_runtime;
#[cfg(all(
target_os = "macos",
any(target_arch = "aarch64", target_arch = "x86_64")
))]
use velr_runtime_macos_universal as selected_runtime;
#[cfg(all(target_os = "windows", target_arch = "x86_64", target_env = "msvc"))]
use velr_runtime_windows_x86_64 as selected_runtime;
#[cfg(not(any(
all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"),
all(target_os = "linux", target_arch = "aarch64", target_env = "gnu"),
all(
target_os = "macos",
any(target_arch = "aarch64", target_arch = "x86_64")
),
all(target_os = "windows", target_arch = "x86_64", target_env = "msvc"),
)))]
compile_error!("No bundled Velr runtime for this target.");
static RUNTIME: OnceLock<Result<Runtime>> = OnceLock::new();
pub struct Runtime {
_lib: Library,
pub api: Api,
pub path: PathBuf,
}
pub fn runtime() -> Result<&'static Runtime> {
match RUNTIME.get_or_init(Runtime::init) {
Ok(rt) => Ok(rt),
Err(e) => Err(Error {
code: e.code,
message: e.message.clone(),
}),
}
}
impl Runtime {
fn init() -> Result<Self> {
if let Ok(p) = env::var("VELR_RUNTIME_PATH") {
let path = PathBuf::from(p);
return unsafe { Self::load_from_path(path) };
}
let (bytes, filename) = bundled_runtime_bytes_and_name();
let path = materialize(bytes, filename)?;
unsafe { Self::load_from_path(path) }
}
unsafe fn load_from_path(path: PathBuf) -> Result<Self> {
let lib = Library::new(&path).map_err(|e| Error {
code: -1,
message: format!("Failed to load Velr runtime '{}': {e}", path.display()),
})?;
let api = Api::load(&lib).map_err(|e| Error {
code: -1,
message: format!(
"Failed to resolve Velr ABI symbols from '{}': {e}",
path.display()
),
})?;
Ok(Self {
_lib: lib,
api,
path,
})
}
}
fn bundled_runtime_bytes_and_name() -> (&'static [u8], &'static str) {
selected_runtime::bytes_and_name()
}
fn materialize(bytes: &[u8], filename: &str) -> Result<PathBuf> {
let dir = runtime_cache_dir()?.join("velr").join("runtime");
fs::create_dir_all(&dir).map_err(|e| Error {
code: -1,
message: format!(
"Failed to create runtime cache dir '{}': {e}",
dir.display()
),
})?;
let mut h = Hasher::new();
h.update(bytes);
let hash = h.finalize().to_hex();
let out = dir.join(format!("{hash}-{filename}"));
if out.exists() {
return Ok(out);
}
let tmp = dir.join(format!(
"{}.tmp",
out.file_name().unwrap().to_string_lossy()
));
{
let mut f = fs::File::create(&tmp).map_err(|e| Error {
code: -1,
message: format!("Failed to create '{}': {e}", tmp.display()),
})?;
f.write_all(bytes).map_err(|e| Error {
code: -1,
message: format!("Failed to write '{}': {e}", tmp.display()),
})?;
let _ = f.flush();
}
match fs::rename(&tmp, &out) {
Ok(()) => Ok(out),
Err(e) => {
if out.exists() {
let _ = fs::remove_file(&tmp);
Ok(out)
} else {
Err(Error {
code: -1,
message: format!(
"Failed to rename '{}' -> '{}': {e}",
tmp.display(),
out.display()
),
})
}
}
}
}
fn runtime_cache_dir() -> Result<PathBuf> {
if let Ok(d) = env::var("VELR_CACHE_DIR") {
return Ok(PathBuf::from(d));
}
#[cfg(target_os = "windows")]
{
if let Ok(d) = env::var("LOCALAPPDATA") {
return Ok(PathBuf::from(d));
}
if let Ok(d) = env::var("TEMP") {
return Ok(PathBuf::from(d));
}
}
#[cfg(target_os = "macos")]
{
if let Ok(home) = env::var("HOME") {
return Ok(PathBuf::from(home).join("Library").join("Caches"));
}
}
if let Ok(xdg) = env::var("XDG_CACHE_HOME") {
return Ok(PathBuf::from(xdg));
}
if let Ok(home) = env::var("HOME") {
return Ok(PathBuf::from(home).join(".cache"));
}
Ok(env::temp_dir())
}