#![allow(clippy::result_large_err)]
#[cfg(feature = "onnx-runtime")]
use std::collections::HashMap;
#[cfg(feature = "onnx-runtime")]
use std::ffi::CStr;
#[cfg(feature = "onnx-runtime")]
use std::path::Path;
use std::path::PathBuf;
#[cfg(feature = "onnx-runtime")]
use std::sync::{Arc, Condvar, Mutex, OnceLock};
pub const ORT_VERSION: &str = "1.23.2";
const MODEL_DIR_ENV: &str = "PDFLUENT_MODEL_DIR";
const ORT_DYLIB_ENV: &str = "ORT_DYLIB_PATH";
#[cfg(feature = "onnx-runtime")]
const ORT_MODEL_NAME: &str = "onnxruntime-1.23.2";
#[non_exhaustive]
pub struct ModelSpec {
pub name: &'static str,
pub file: &'static str,
pub sha256: &'static str,
pub install_command: &'static str,
}
impl ModelSpec {
pub const fn new(
name: &'static str,
file: &'static str,
sha256: &'static str,
install_command: &'static str,
) -> Self {
Self {
name,
file,
sha256,
install_command,
}
}
}
#[non_exhaustive]
pub struct RuntimeConfig {
pub model_dir: Option<PathBuf>,
pub ort_dylib: Option<PathBuf>,
pub intra_threads: usize,
pub sessions_per_model: usize,
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self {
model_dir: None,
ort_dylib: None,
intra_threads: 4,
sessions_per_model: 1,
}
}
}
impl RuntimeConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_model_dir(mut self, dir: impl Into<PathBuf>) -> Self {
self.model_dir = Some(dir.into());
self
}
pub fn with_ort_dylib(mut self, lib: impl Into<PathBuf>) -> Self {
self.ort_dylib = Some(lib.into());
self
}
pub fn with_intra_threads(mut self, threads: usize) -> Self {
self.intra_threads = threads;
self
}
pub fn with_sessions_per_model(mut self, sessions: usize) -> Self {
self.sessions_per_model = sessions;
self
}
#[cfg(feature = "onnx-runtime")]
fn effective_pool(&self) -> usize {
self.sessions_per_model.max(1)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModelErrorKind {
ModelNotAvailable,
ChecksumMismatch,
VersionMismatch,
ConflictingInit,
Runtime,
}
impl ModelErrorKind {
pub fn code(self) -> &'static str {
match self {
Self::ModelNotAvailable => "MODEL_NOT_AVAILABLE",
Self::ChecksumMismatch => "MODEL_CHECKSUM_MISMATCH",
Self::VersionMismatch => "VERSION_MISMATCH",
Self::ConflictingInit => "RUNTIME_CONFLICTING_INIT",
Self::Runtime => "MODEL_RUNTIME",
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct ModelError {
pub kind: ModelErrorKind,
pub model: Option<String>,
pub expected_sha256: Option<String>,
pub path: Option<PathBuf>,
pub install_command: Option<String>,
pub expected_ort: Option<String>,
pub found_ort: Option<String>,
detail: String,
}
impl ModelError {
pub fn feature_not_compiled(spec: &ModelSpec, feature: &'static str) -> Self {
Self {
kind: ModelErrorKind::ModelNotAvailable,
model: Some(spec.name.to_string()),
expected_sha256: None,
path: None,
install_command: Some(spec.install_command.to_string()),
expected_ort: None,
found_ort: None,
detail: format!(
"model '{}' is not available: this build lacks the `{feature}` cargo feature \
(rebuild with `--features {feature}`)",
spec.name
),
}
}
#[cfg(feature = "onnx-runtime")]
fn model_missing(spec: &ModelSpec, path: &Path) -> Self {
Self {
kind: ModelErrorKind::ModelNotAvailable,
model: Some(spec.name.to_string()),
expected_sha256: None,
path: Some(path.to_path_buf()),
install_command: Some(spec.install_command.to_string()),
expected_ort: None,
found_ort: None,
detail: format!(
"model '{}' is not available: {} was not found",
spec.name,
path.display()
),
}
}
#[cfg(feature = "onnx-runtime")]
fn runtime_missing(path: &Path) -> Self {
Self {
kind: ModelErrorKind::ModelNotAvailable,
model: Some(ORT_MODEL_NAME.to_string()),
expected_sha256: None,
path: Some(path.to_path_buf()),
install_command: None,
expected_ort: None,
found_ort: None,
detail: format!(
"model '{ORT_MODEL_NAME}' is not available: {} was not found. \
Place ONNX Runtime {ORT_VERSION} there, or point {ORT_DYLIB_ENV} \
at the library",
path.display()
),
}
}
#[cfg(feature = "onnx-runtime")]
fn checksum_mismatch(spec: &ModelSpec, path: &Path, actual_sha256: &str) -> Self {
Self {
kind: ModelErrorKind::ChecksumMismatch,
model: Some(spec.name.to_string()),
expected_sha256: Some(spec.sha256.to_string()),
path: Some(path.to_path_buf()),
install_command: Some(spec.install_command.to_string()),
expected_ort: None,
found_ort: None,
detail: actual_sha256.to_string(),
}
}
#[cfg(feature = "onnx-runtime")]
fn version_mismatch(path: &Path, found: &str) -> Self {
Self {
kind: ModelErrorKind::VersionMismatch,
model: Some(ORT_MODEL_NAME.to_string()),
expected_sha256: None,
path: Some(path.to_path_buf()),
install_command: None,
expected_ort: Some(ORT_VERSION.to_string()),
found_ort: Some(found.to_string()),
detail: format!(
"expected ONNX Runtime {ORT_VERSION}, found '{found}' at {}",
path.display()
),
}
}
#[cfg(feature = "onnx-runtime")]
fn conflicting_init(requested: &Path, current: &Path) -> Self {
Self {
kind: ModelErrorKind::ConflictingInit,
model: Some(ORT_MODEL_NAME.to_string()),
expected_sha256: None,
path: Some(requested.to_path_buf()),
install_command: None,
expected_ort: None,
found_ort: None,
detail: format!(
"ONNX Runtime is already initialised from {} and cannot also use {} \
in this process",
current.display(),
requested.display()
),
}
}
#[cfg(feature = "onnx-runtime")]
fn runtime(detail: impl Into<String>) -> Self {
Self {
kind: ModelErrorKind::Runtime,
model: None,
expected_sha256: None,
path: None,
install_command: None,
expected_ort: None,
found_ort: None,
detail: detail.into(),
}
}
}
impl std::fmt::Display for ModelError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.kind {
ModelErrorKind::ModelNotAvailable => {
write!(f, "{}", self.detail)?;
if let Some(cmd) = &self.install_command {
write!(f, ". Install it with: {cmd}")?;
}
Ok(())
}
ModelErrorKind::ChecksumMismatch => {
let expected = self.expected_sha256.as_deref().unwrap_or("<unknown>");
let path = self
.path
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "<unknown>".to_string());
write!(
f,
"model '{}' failed its checksum: {path} has sha256 {}, expected {expected}",
self.model.as_deref().unwrap_or("<unknown>"),
self.detail,
)?;
if let Some(cmd) = &self.install_command {
write!(f, ". Reinstall it with: {cmd}")?;
}
Ok(())
}
ModelErrorKind::VersionMismatch
| ModelErrorKind::ConflictingInit
| ModelErrorKind::Runtime => {
write!(f, "{}", self.detail)
}
}
}
}
impl std::error::Error for ModelError {}
impl From<ModelError> for std::io::Error {
fn from(e: ModelError) -> Self {
std::io::Error::other(e)
}
}
pub fn model_error<'a>(mut e: &'a (dyn std::error::Error + 'static)) -> Option<&'a ModelError> {
loop {
if let Some(found) = e.downcast_ref::<ModelError>() {
return Some(found);
}
if let Some(io) = e.downcast_ref::<std::io::Error>() {
if let Some(inner) = io.get_ref() {
if let Some(found) = model_error(inner) {
return Some(found);
}
}
}
e = e.source()?;
}
}
#[cfg(feature = "onnx-runtime")]
static INITIALIZED_DYLIB: OnceLock<PathBuf> = OnceLock::new();
#[cfg(feature = "onnx-runtime")]
static INIT_LOCK: Mutex<()> = Mutex::new(());
#[cfg(feature = "onnx-runtime")]
pub fn init(config: &RuntimeConfig) -> Result<(), ModelError> {
let _held = match INIT_LOCK.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
let resolved = resolve_ort_dylib(config);
if let Some(current) = INITIALIZED_DYLIB.get() {
if same_library(&resolved, current) {
return Ok(());
}
if !resolved.exists() {
return Err(ModelError::runtime_missing(&resolved));
}
return Err(ModelError::conflicting_init(&resolved, current));
}
if !resolved.exists() {
return Err(ModelError::runtime_missing(&resolved));
}
let found = probe_ort_version(&resolved)?;
if check_ort_version(&found).is_err() {
return Err(ModelError::version_mismatch(&resolved, &found));
}
ort::init_from(&resolved)
.map_err(|e| {
ModelError::runtime(format!(
"could not load ONNX Runtime from {}: {e}",
resolved.display()
))
})?
.commit();
match INITIALIZED_DYLIB.set(canonical_or_raw(&resolved)) {
Ok(()) => Ok(()),
Err(_) => match INITIALIZED_DYLIB.get() {
Some(current) if same_library(&resolved, current) => Ok(()),
Some(current) => Err(ModelError::conflicting_init(&resolved, current)),
None => Ok(()),
},
}
}
#[cfg(feature = "onnx-runtime")]
pub fn builder(
config: &RuntimeConfig,
) -> Result<ort::session::builder::SessionBuilder, ModelError> {
init(config)?;
let builder = ort::session::Session::builder().map_err(|e| {
ModelError::runtime(format!("could not create an ONNX session builder: {e}"))
})?;
let builder = builder
.with_optimization_level(ort::session::builder::GraphOptimizationLevel::Level3)
.map_err(|e| {
ModelError::runtime(format!("could not set graph optimisation Level3: {e}"))
})?;
builder
.with_intra_threads(config.intra_threads)
.map_err(|e| {
ModelError::runtime(format!(
"could not set intra_threads={}: {e}",
config.intra_threads
))
})
}
#[cfg(feature = "onnx-runtime")]
pub fn session(spec: &ModelSpec, config: &RuntimeConfig) -> Result<Arc<SharedSession>, ModelError> {
let model_path = resolve_model_file(spec, config);
let key = CacheKey::for_request(spec, config, &model_path);
if let Some(hit) = cache_get(&key) {
return Ok(hit);
}
if !model_path.exists() {
return Err(ModelError::model_missing(spec, &model_path));
}
verify_model_file(spec, &model_path)?;
init(config)?;
let pool = build_pool(&model_path, spec, config)?;
Ok(cache_insert(key, pool))
}
#[cfg(feature = "onnx-runtime")]
fn verify_model_file(spec: &ModelSpec, path: &Path) -> Result<(), ModelError> {
let mut file = std::fs::File::open(path).map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
ModelError::model_missing(spec, path)
} else {
ModelError::runtime(format!("could not read {}: {e}", path.display()))
}
})?;
match crate::integrity::verify_reader_sha256(&mut file, spec.sha256) {
Ok(()) => Ok(()),
Err(crate::integrity::StreamIntegrityError::Io(e)) => Err(ModelError::runtime(format!(
"could not read {}: {e}",
path.display()
))),
Err(crate::integrity::StreamIntegrityError::Integrity(
crate::integrity::IntegrityError::DigestMismatch { actual, .. },
)) => Err(ModelError::checksum_mismatch(spec, path, &actual)),
Err(crate::integrity::StreamIntegrityError::Integrity(
crate::integrity::IntegrityError::MalformedDigest(digest),
)) => Err(ModelError::runtime(format!(
"model '{}' pins a malformed sha256 {digest:?}: a sha256 is 64 hex characters",
spec.name
))),
}
}
#[cfg(feature = "onnx-runtime")]
fn build_pool(
model_path: &Path,
spec: &ModelSpec,
config: &RuntimeConfig,
) -> Result<SharedSession, ModelError> {
let mut sessions = Vec::with_capacity(config.effective_pool());
for _ in 0..config.effective_pool() {
let mut session_builder = builder(config)?;
let session = session_builder.commit_from_file(model_path).map_err(|e| {
ModelError::runtime(format!(
"could not load model '{}' from {}: {e}",
spec.name,
model_path.display()
))
})?;
sessions.push(session);
}
Ok(SharedSession::new(sessions, spec.sha256))
}
#[cfg(feature = "onnx-runtime")]
pub struct SharedSession {
pool: Mutex<Vec<ort::session::Session>>,
available: Condvar,
model_sha256: String,
}
#[cfg(feature = "onnx-runtime")]
impl std::fmt::Debug for SharedSession {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SharedSession")
.field("model_sha256", &self.model_sha256)
.finish_non_exhaustive()
}
}
#[cfg(feature = "onnx-runtime")]
impl SharedSession {
fn new(sessions: Vec<ort::session::Session>, sha256: &str) -> Self {
Self {
pool: Mutex::new(sessions),
available: Condvar::new(),
model_sha256: sha256.to_string(),
}
}
pub fn run<R>(
&self,
f: impl FnOnce(&mut ort::session::Session) -> ort::Result<R>,
) -> Result<R, ModelError> {
let session = self.take();
let mut returned = SessionReturn {
pool: &self.pool,
notify: &self.available,
session: Some(session),
};
let slot = match returned.session.as_mut() {
Some(slot) => slot,
None => unreachable!("the taken session is held until release"),
};
let result = f(slot).map_err(|e| ModelError::runtime(e.to_string()));
returned.release();
result
}
pub fn model_sha256(&self) -> &str {
&self.model_sha256
}
fn take(&self) -> ort::session::Session {
let mut guard = match self.pool.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
loop {
if let Some(session) = guard.pop() {
return session;
}
guard = match self.available.wait(guard) {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
}
}
}
#[cfg(feature = "onnx-runtime")]
struct SessionReturn<'a> {
pool: &'a Mutex<Vec<ort::session::Session>>,
notify: &'a Condvar,
session: Option<ort::session::Session>,
}
#[cfg(feature = "onnx-runtime")]
impl SessionReturn<'_> {
fn release(&mut self) {
if let Some(session) = self.session.take() {
let mut guard = match self.pool.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
guard.push(session);
self.notify.notify_one();
}
}
}
#[cfg(feature = "onnx-runtime")]
impl Drop for SessionReturn<'_> {
fn drop(&mut self) {
self.release();
}
}
#[cfg(feature = "onnx-runtime")]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct CacheKey {
path: PathBuf,
sha256: String,
intra_threads: usize,
sessions_per_model: usize,
}
#[cfg(feature = "onnx-runtime")]
impl CacheKey {
fn for_request(spec: &ModelSpec, config: &RuntimeConfig, model_path: &Path) -> Self {
Self {
path: canonical_or_raw(model_path),
sha256: spec.sha256.trim().to_ascii_lowercase(),
intra_threads: config.intra_threads,
sessions_per_model: config.effective_pool(),
}
}
}
#[cfg(feature = "onnx-runtime")]
static SESSION_CACHE: std::sync::LazyLock<Mutex<HashMap<CacheKey, Arc<SharedSession>>>> =
std::sync::LazyLock::new(|| Mutex::new(HashMap::new()));
#[cfg(feature = "onnx-runtime")]
fn cache_lock() -> std::sync::MutexGuard<'static, HashMap<CacheKey, Arc<SharedSession>>> {
match SESSION_CACHE.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
}
}
#[cfg(feature = "onnx-runtime")]
fn cache_get(key: &CacheKey) -> Option<Arc<SharedSession>> {
cache_lock().get(key).cloned()
}
#[cfg(feature = "onnx-runtime")]
fn cache_insert(key: CacheKey, pool: SharedSession) -> Arc<SharedSession> {
let mut cache = cache_lock();
cache.entry(key).or_insert_with(|| Arc::new(pool)).clone()
}
pub fn resolve_model_root(config: &RuntimeConfig) -> PathBuf {
if let Some(dir) = &config.model_dir {
dir.clone()
} else if let Some(dir) = env_dir(MODEL_DIR_ENV) {
dir
} else {
default_model_root()
}
}
pub fn resolve_model_file(spec: &ModelSpec, config: &RuntimeConfig) -> PathBuf {
resolve_model_root(config).join(spec.name).join(spec.file)
}
pub fn resolve_ort_dylib(config: &RuntimeConfig) -> PathBuf {
if let Some(lib) = &config.ort_dylib {
return lib.clone();
}
if let Ok(path) = std::env::var(ORT_DYLIB_ENV) {
if !path.is_empty() {
return PathBuf::from(path);
}
}
let root = env_dir(MODEL_DIR_ENV).unwrap_or_else(default_model_root);
root.join(format!("onnxruntime-{ORT_VERSION}"))
.join(platform_lib_name())
}
fn env_dir(var: &str) -> Option<PathBuf> {
match std::env::var(var) {
Ok(dir) if !dir.is_empty() => Some(PathBuf::from(dir)),
_ => None,
}
}
pub fn default_model_root() -> PathBuf {
user_data_dir().join("pdfluent").join("models")
}
fn user_data_dir() -> PathBuf {
#[cfg(target_os = "windows")]
{
if let Ok(dir) = std::env::var("LOCALAPPDATA") {
if !dir.is_empty() {
return PathBuf::from(dir);
}
}
}
#[cfg(target_os = "macos")]
{
if let Ok(home) = std::env::var("HOME") {
if !home.is_empty() {
return PathBuf::from(home)
.join("Library")
.join("Application Support");
}
}
}
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
{
if let Ok(dir) = std::env::var("XDG_DATA_HOME") {
if !dir.is_empty() {
return PathBuf::from(dir);
}
}
if let Ok(home) = std::env::var("HOME") {
if !home.is_empty() {
return PathBuf::from(home).join(".local").join("share");
}
}
}
std::env::temp_dir()
}
fn platform_lib_name() -> &'static str {
#[cfg(target_os = "windows")]
{
"onnxruntime.dll"
}
#[cfg(target_os = "macos")]
{
"libonnxruntime.dylib"
}
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
{
"libonnxruntime.so"
}
}
#[cfg(feature = "onnx-runtime")]
fn same_library(a: &Path, b: &Path) -> bool {
match (std::fs::canonicalize(a), std::fs::canonicalize(b)) {
(Ok(a), Ok(b)) => a == b,
_ => a == b,
}
}
#[cfg(feature = "onnx-runtime")]
fn canonical_or_raw(path: &Path) -> PathBuf {
match std::fs::canonicalize(path) {
Ok(canonical) => canonical,
Err(_) => path.to_path_buf(),
}
}
#[cfg(feature = "onnx-runtime")]
fn probe_ort_version(dylib: &Path) -> Result<String, ModelError> {
let lib = unsafe { libloading::Library::new(dylib) }.map_err(|e| {
ModelError::runtime(format!(
"could not load ONNX Runtime from {}: {e}",
dylib.display()
))
})?;
type ApiBaseGetter = unsafe extern "C" fn() -> *const ort::sys::OrtApiBase;
let getter: libloading::Symbol<ApiBaseGetter> =
unsafe { lib.get(b"OrtGetApiBase") }.map_err(|e| {
ModelError::runtime(format!(
"{} is not an ONNX Runtime library (no OrtGetApiBase): {e}",
dylib.display()
))
})?;
let base = unsafe { getter() };
if base.is_null() {
return Err(ModelError::runtime(format!(
"{} returned a null OrtApiBase",
dylib.display()
)));
}
let raw = unsafe { ((*base).GetVersionString)() };
if raw.is_null() {
return Err(ModelError::runtime(format!(
"{} returned a null version string",
dylib.display()
)));
}
Ok(unsafe { CStr::from_ptr(raw) }
.to_string_lossy()
.into_owned())
}
#[cfg(feature = "onnx-runtime")]
fn check_ort_version(found: &str) -> Result<(), ModelErrorKind> {
if found == ORT_VERSION {
Ok(())
} else {
Err(ModelErrorKind::VersionMismatch)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "onnx-runtime")]
#[test]
fn the_pinned_version_passes_and_everything_else_fails() {
assert_eq!(check_ort_version("1.23.2"), Ok(()));
for found in [
"1.23.1", "1.24.0", "1.23.20", "2.0.0", "", "1.23.2\n", " 1.23.2",
] {
assert_eq!(
check_ort_version(found),
Err(ModelErrorKind::VersionMismatch),
"{found:?} must not pass as {ORT_VERSION}"
);
}
}
#[test]
fn error_codes_are_the_published_strings() {
assert_eq!(
ModelErrorKind::ModelNotAvailable.code(),
"MODEL_NOT_AVAILABLE"
);
assert_eq!(
ModelErrorKind::ChecksumMismatch.code(),
"MODEL_CHECKSUM_MISMATCH"
);
assert_eq!(ModelErrorKind::VersionMismatch.code(), "VERSION_MISMATCH");
assert_eq!(
ModelErrorKind::ConflictingInit.code(),
"RUNTIME_CONFLICTING_INIT"
);
assert_eq!(ModelErrorKind::Runtime.code(), "MODEL_RUNTIME");
}
#[cfg(feature = "onnx-runtime")]
#[test]
fn a_zero_pool_is_clamped_to_one() {
let config = RuntimeConfig::new().with_sessions_per_model(0);
assert_eq!(config.effective_pool(), 1);
let config = RuntimeConfig::new().with_sessions_per_model(3);
assert_eq!(config.effective_pool(), 3);
}
#[test]
fn a_build_without_the_tier_names_the_feature_and_the_install() {
let spec = ModelSpec::new(
"layout-heron-ep03",
"heron_ep03.onnx",
"b7cc5d31f3223c74cc477a1386b894793618aeb398ca59cf5eb7264ec61d8afa",
"pdfluent models install layout",
);
let error = ModelError::feature_not_compiled(&spec, "ort-layout");
assert_eq!(error.kind, ModelErrorKind::ModelNotAvailable);
assert_eq!(error.kind.code(), "MODEL_NOT_AVAILABLE");
let message = error.to_string();
assert!(
message.contains("`ort-layout`"),
"the message names the cargo feature: {message}"
);
assert!(
message.contains("pdfluent models install layout"),
"the message names the install command: {message}"
);
let io = std::io::Error::from(error);
let back = model_error(&io).expect("the chain carries the ModelError");
assert_eq!(back.kind, ModelErrorKind::ModelNotAvailable);
}
#[test]
fn resolution_honours_the_explicit_dir_over_everything() {
let spec = ModelSpec::new("layout-heron-ep03", "heron_ep03.onnx", "0", "install");
let config = RuntimeConfig::new().with_model_dir("/tmp/ort-explicit-test");
assert_eq!(
resolve_model_file(&spec, &config),
std::path::PathBuf::from("/tmp/ort-explicit-test/layout-heron-ep03/heron_ep03.onnx")
);
}
#[test]
fn the_dylib_fallback_reads_the_model_dir_env() {
struct Restore {
key: &'static str,
previous: Option<String>,
}
impl Drop for Restore {
fn drop(&mut self) {
match &self.previous {
Some(value) => std::env::set_var(self.key, value),
None => std::env::remove_var(self.key),
}
}
}
let _model_dir = Restore {
key: MODEL_DIR_ENV,
previous: std::env::var(MODEL_DIR_ENV).ok(),
};
let _dylib = Restore {
key: ORT_DYLIB_ENV,
previous: std::env::var(ORT_DYLIB_ENV).ok(),
};
std::env::remove_var(ORT_DYLIB_ENV);
let from_env = std::env::temp_dir().join("ort-env-dylib-test");
std::env::set_var(MODEL_DIR_ENV, &from_env);
let lib = format!("onnxruntime-{ORT_VERSION}");
assert_eq!(
resolve_ort_dylib(&RuntimeConfig::new()),
from_env.join(&lib).join(platform_lib_name()),
"the env root carries the library dir"
);
std::env::set_var(ORT_DYLIB_ENV, "/tmp/ort-explicit-test/libonnxruntime.so");
assert_eq!(
resolve_ort_dylib(&RuntimeConfig::new()),
std::path::PathBuf::from("/tmp/ort-explicit-test/libonnxruntime.so"),
"ORT_DYLIB_PATH wins over the model root"
);
std::env::remove_var(ORT_DYLIB_ENV);
std::env::set_var(MODEL_DIR_ENV, "");
assert_eq!(
resolve_ort_dylib(&RuntimeConfig::new()),
default_model_root().join(&lib).join(platform_lib_name()),
"an empty model dir falls back to the default"
);
std::env::remove_var(MODEL_DIR_ENV);
let config = RuntimeConfig::new().with_model_dir("/tmp/ort-explicit-test");
assert_eq!(
resolve_ort_dylib(&config),
default_model_root().join(&lib).join(platform_lib_name()),
"the explicit model dir leaves the library at its default"
);
}
}