1#[cfg(feature = "session")]
10pub mod environment;
11#[cfg(feature = "session")]
12pub mod probe;
13#[cfg(feature = "session")]
14pub mod session;
15
16#[cfg(feature = "session")]
17pub use environment::{
18 cpu_thread_budget, init_environment, shared_pool_active, EnvironmentOptions, EnvironmentReport,
19 GlobalPool,
20};
21#[cfg(feature = "session")]
22pub use probe::{probe_providers, ProviderDiagnostic, ProviderKind};
23#[cfg(feature = "session")]
24pub use session::{BuiltSession, ExecutionProvider, SessionError, SessionOptions};
25
26use std::fmt;
27use std::path::{Path, PathBuf};
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum CandidateSource {
31 Explicit,
32 Bundled,
33 AppData,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct RuntimeCandidate {
38 pub path: PathBuf,
39 pub source: CandidateSource,
40}
41
42impl RuntimeCandidate {
43 pub fn new(path: impl Into<PathBuf>, source: CandidateSource) -> Self {
44 Self {
45 path: path.into(),
46 source,
47 }
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct CandidateDiagnostic {
53 pub path: PathBuf,
54 pub source: CandidateSource,
55 pub absolute: bool,
56 pub expected_filename: bool,
57 pub exists: bool,
58 pub is_file: bool,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct RuntimeSelection {
63 pub path: PathBuf,
64 pub source: CandidateSource,
65 pub diagnostics: Vec<CandidateDiagnostic>,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub enum RuntimeError {
70 NoCandidates,
71 UnsafePath {
72 path: PathBuf,
73 },
74 UnexpectedFilename {
75 path: PathBuf,
76 expected: &'static str,
77 },
78 NotFound {
79 diagnostics: Vec<CandidateDiagnostic>,
80 },
81 Canonicalize {
82 path: PathBuf,
83 message: String,
84 },
85 AlreadyConfigured {
86 configured: PathBuf,
87 selected: PathBuf,
88 },
89 SystemRuntimeRejected {
92 path: PathBuf,
93 },
94}
95
96impl fmt::Display for RuntimeError {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 match self {
99 Self::NoCandidates => write!(f, "no ONNX Runtime candidates were supplied"),
100 Self::UnsafePath { path } => {
101 write!(f, "runtime path must be absolute: {}", path.display())
102 }
103 Self::UnexpectedFilename { path, expected } => {
104 write!(f, "runtime path {} must end in {expected}", path.display())
105 }
106 Self::NotFound { .. } => {
107 write!(f, "no supplied ONNX Runtime candidate is a regular file")
108 }
109 Self::Canonicalize { path, message } => {
110 write!(f, "cannot canonicalize {}: {message}", path.display())
111 }
112 Self::SystemRuntimeRejected { path } => write!(
113 f,
114 "runtime {} is the OS-provided copy; bundle and supply an app runtime",
115 path.display()
116 ),
117 Self::AlreadyConfigured {
118 configured,
119 selected,
120 } => write!(
121 f,
122 "ORT_DYLIB_PATH is already {}, refusing replacement with {}",
123 configured.display(),
124 selected.display()
125 ),
126 }
127 }
128}
129
130impl std::error::Error for RuntimeError {}
131
132pub fn runtime_filename() -> &'static str {
133 #[cfg(target_os = "windows")]
134 {
135 "onnxruntime.dll"
136 }
137 #[cfg(target_os = "macos")]
138 {
139 "libonnxruntime.dylib"
140 }
141 #[cfg(all(not(target_os = "windows"), not(target_os = "macos")))]
142 {
143 "libonnxruntime.so"
144 }
145}
146
147pub fn inspect_candidate(candidate: &RuntimeCandidate) -> CandidateDiagnostic {
148 CandidateDiagnostic {
149 path: candidate.path.clone(),
150 source: candidate.source,
151 absolute: candidate.path.is_absolute(),
152 expected_filename: candidate.path.file_name().and_then(|name| name.to_str())
153 == Some(runtime_filename()),
154 exists: candidate.path.exists(),
155 is_file: candidate.path.is_file(),
156 }
157}
158
159pub fn resolve_runtime(
160 candidates: impl IntoIterator<Item = RuntimeCandidate>,
161) -> Result<RuntimeSelection, RuntimeError> {
162 let candidates: Vec<_> = candidates.into_iter().collect();
163 if candidates.is_empty() {
164 return Err(RuntimeError::NoCandidates);
165 }
166 let mut diagnostics = Vec::with_capacity(candidates.len());
167 for candidate in candidates {
168 let diagnostic = inspect_candidate(&candidate);
169 if !diagnostic.absolute {
170 return Err(RuntimeError::UnsafePath {
171 path: candidate.path,
172 });
173 }
174 if !diagnostic.expected_filename {
175 return Err(RuntimeError::UnexpectedFilename {
176 path: candidate.path,
177 expected: runtime_filename(),
178 });
179 }
180 if is_system_runtime(&candidate.path) {
181 return Err(RuntimeError::SystemRuntimeRejected {
182 path: candidate.path,
183 });
184 }
185 diagnostics.push(diagnostic.clone());
186 if diagnostic.is_file {
187 let path =
188 candidate
189 .path
190 .canonicalize()
191 .map_err(|error| RuntimeError::Canonicalize {
192 path: candidate.path,
193 message: error.to_string(),
194 })?;
195 return Ok(RuntimeSelection {
196 path,
197 source: candidate.source,
198 diagnostics,
199 });
200 }
201 }
202 Err(RuntimeError::NotFound { diagnostics })
203}
204
205pub fn is_system_runtime(path: &Path) -> bool {
209 path.components().any(|component| {
210 component
211 .as_os_str()
212 .to_str()
213 .map(|name| {
214 name.eq_ignore_ascii_case("system32") || name.eq_ignore_ascii_case("syswow64")
215 })
216 .unwrap_or(false)
217 })
218}
219
220pub fn installed_resource_dir(executable: &Path) -> Option<PathBuf> {
225 if !executable.is_absolute() {
226 return None;
227 }
228 let dir = executable.parent()?.to_path_buf();
229 let is = |p: &Path, name: &str| {
230 p.file_name()
231 .and_then(|n| n.to_str())
232 .map(|n| n.eq_ignore_ascii_case(name))
233 .unwrap_or(false)
234 };
235 #[cfg(target_os = "macos")]
236 {
237 if is(&dir, "MacOS") {
238 if let Some(contents) = dir.parent() {
239 if is(contents, "Contents") {
240 return Some(contents.join("Resources"));
241 }
242 }
243 }
244 if is(&dir, "Resources") {
245 return Some(dir);
246 }
247 Some(dir.join("Resources"))
248 }
249 #[cfg(target_os = "windows")]
250 {
251 let _ = is;
252 Some(dir)
253 }
254 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
255 {
256 if is(&dir, "resources") {
257 Some(dir)
258 } else {
259 Some(dir.join("resources"))
260 }
261 }
262}
263
264pub fn installed_runtime_candidate(executable: &Path, subdir: &str) -> Option<RuntimeCandidate> {
267 let dir = installed_resource_dir(executable)?;
268 Some(RuntimeCandidate::new(
269 dir.join(subdir).join(runtime_filename()),
270 CandidateSource::Bundled,
271 ))
272}
273
274pub fn configure_runtime(
277 candidates: impl IntoIterator<Item = RuntimeCandidate>,
278) -> Result<RuntimeSelection, RuntimeError> {
279 let selection = resolve_runtime(candidates)?;
280 if let Some(configured) = nonempty_env_path("ORT_DYLIB_PATH") {
281 let configured = canonical_if_file(&configured)?;
282 if configured != selection.path {
283 return Err(RuntimeError::AlreadyConfigured {
284 configured,
285 selected: selection.path,
286 });
287 }
288 return Ok(selection);
289 }
290 std::env::set_var("ORT_DYLIB_PATH", &selection.path);
291 Ok(selection)
292}
293
294fn nonempty_env_path(name: &str) -> Option<PathBuf> {
295 std::env::var_os(name)
296 .filter(|value| !value.is_empty())
297 .map(PathBuf::from)
298}
299
300fn canonical_if_file(path: &Path) -> Result<PathBuf, RuntimeError> {
301 if !path.is_absolute() {
302 return Err(RuntimeError::UnsafePath {
303 path: path.to_path_buf(),
304 });
305 }
306 path.canonicalize()
307 .map_err(|error| RuntimeError::Canonicalize {
308 path: path.to_path_buf(),
309 message: error.to_string(),
310 })
311}