openvino_finder/lib.rs
1//! This crate provides a mechanism for locating the OpenVINO files installed on a system.
2//!
3//! OpenVINO can be installed several ways: [from an archive][install-archive], [from an APT
4//! repository][install-apt], [via Python `pip`][install-pip]. The Rust bindings need to be able to:
5//! 1. locate the shared libraries (e.g., `libopenvino_c.so` on Linux) — see [`find`]
6//! 2. locate the plugin configuration file (i.e., `plugins.xml`) — see [`find_plugins_xml`].
7//!
8//! These files are located in different locations based on the installation method, so this crate
9//! encodes "how to find" OpenVINO files. This crate's goal is to locate __only the latest version__
10//! of OpenVINO; older versions may continue to be supported on a best-effort basis.
11//!
12//! [install-archive]: https://docs.openvino.ai/latest/openvino_docs_install_guides_installing_openvino_from_archive_linux.html
13//! [install-apt]: https://docs.openvino.ai/latest/openvino_docs_install_guides_installing_openvino_apt.html
14//! [install-pip]: https://docs.openvino.ai/latest/openvino_docs_install_guides_installing_openvino_pip.html
15//!
16//! Problems with the OpenVINO bindings are most likely to be due to "finding" the right files. Both
17//! [`find`] and [`find_plugins_xml`] provide various ways of configuring the search paths, first by
18//! examining _special environment variables_ and then by looking in _known installation locations_.
19//! When [installing from an archive][install-archive], OpenVINO provides a setup script (e.g.,
20//! `source /opt/intel/openvino/setupvars.sh`) that sets these special environment variables. Note
21//! that you may need to have the OpenVINO environment ready both when building (`cargo build`) and
22//! running (e.g., `cargo test`) when the libraries are linked at compile-time (the default). By
23//! using the `runtime-linking` feature, the libraries are only searched for at run-time.
24//!
25//! If you do run into problems, the following chart summarizes some of the known installation
26//! locations of the OpenVINO files as of version `2022.3.0`:
27//!
28//! | Installation Method | Path | Available on | Notes |
29//! | ------------------- | -------------------------------------------------- | ----------------------- | -------------------------------- |
30//! | Archive (`.tar.gz`) | `<extracted folder>/runtime/lib/<arch>` | Linux | `<arch>`: `intel64,armv7l,arm64` |
31//! | Archive (`.tar.gz`) | `<extracted folder>/runtime/lib/<arch>/Release` | `MacOS` | `<arch>`: `intel64,armv7l,arm64` |
32//! | Archive (`.zip`) | `<unzipped folder>/runtime/bin/<arch>/Release` | Windows | `<arch>`: `intel64,armv7l,arm64` |
33//! | `PyPI` | `<pip install folder>/site-packages/openvino/libs` | Linux, `MacOS`, Windows | Find install folder with `pip show openvino` |
34//! | DEB | `/usr/lib/x86_64-linux-gnu/openvino-<version>/` | Linux (APT-based) | This path is for plugins; the libraries are one directory above |
35//! | RPM | `/usr/lib64/` | Linux (YUM-based) | |
36
37#![deny(missing_docs)]
38#![deny(clippy::all)]
39#![warn(clippy::pedantic)]
40#![warn(clippy::cargo)]
41#![allow(clippy::must_use_candidate)]
42
43use cfg_if::cfg_if;
44use std::env;
45use std::fs;
46use std::path::{Path, PathBuf};
47
48// We search for the library in various different places and early-return if we find it.
49macro_rules! check_and_return {
50 ($path: expr) => {
51 log::debug!("Searching in: {}", $path.display());
52 if $path.is_file() {
53 log::info!("Found library at path: {}", $path.display());
54 return Some($path);
55 }
56 };
57}
58
59/// Distinguish which kind of library to link to.
60///
61/// The difference is important on Windows, e.g., which [requires] `*.lib` libraries when linking
62/// dependent libraries.
63///
64/// [requires]:
65/// https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-creation#creating-an-import-library
66#[derive(Clone, Copy, Debug, PartialEq)]
67pub enum Linking {
68 /// Find _static_ libraries: OpenVINO comes with static libraries on some platforms (e.g.,
69 /// Windows).
70 Static,
71 /// Find _dynamic_ libraries.
72 Dynamic,
73}
74
75/// Find the path to an OpenVINO library.
76///
77/// Because OpenVINO can be installed in quite a few ways (see module documentation), this function
78/// attempts the difficult and thankless task of locating the installation's shared libraries for
79/// use in the Rust bindings (i.e., [openvino] and [openvino-sys]). It uses observations from
80/// various OpenVINO releases across several operating systems and conversations with the OpenVINO
81/// development team, but it may not perfectly locate the libraries in every environment —
82/// hence the `Option<PathBuf>` return type.
83///
84/// [openvino]: https://docs.rs/openvino
85/// [openvino-sys]: https://docs.rs/openvino-sys
86///
87/// This function will probe:
88/// - the `OPENVINO_BUILD_DIR` environment variable with known build subdirectories appended —
89/// this is useful for finding libraries built from source
90/// - the `OPENVINO_INSTALL_DIR`, `INTEL_OPENVINO_DIR`, and `LD_LIBRARY_PATH` (or OS-equivalent)
91/// environment variables with known install subdirectories appended — one of these is set
92/// by a version of OpenVINO's environment script (e.g., `source
93/// /opt/intel/openvino/setupvars.sh`)
94/// - OpenVINO's package installation paths for the OS (e.g., `/usr/lib64`) — this is useful
95/// for DEB or RPM installations
96/// - OpenVINO's documented extract paths — this is useful for users who extract the TAR or
97/// ZIP archive to the default locations or use the Docker images
98///
99/// The locations above may change over time. As OpenVINO has released new versions, the documented
100/// locations of the shared libraries has changed. New versions of this function will reflect this,
101/// removing older, unused locations over time.
102///
103/// # Panics
104///
105/// Panics if it cannot list the contents of a search directory.
106pub fn find(library_name: &str, kind: Linking) -> Option<PathBuf> {
107 let suffix = if kind == Linking::Static {
108 // This is a bit rudimentary but works for the top three supported platforms: `linux`,
109 // `macos`, and `windows`.
110 if cfg!(target_os = "windows") {
111 ".lib"
112 } else {
113 ".a"
114 }
115 } else {
116 env::consts::DLL_SUFFIX
117 };
118 let file = format!("{}{}{}", env::consts::DLL_PREFIX, library_name, suffix);
119 log::info!("Attempting to find library: {file}");
120
121 // Search using the `OPENVINO_BUILD_DIR` environment variable; this may be set by users of the
122 // `openvino-rs` library.
123 if let Some(build_dir) = env::var_os(ENV_OPENVINO_BUILD_DIR) {
124 let install_dir = PathBuf::from(build_dir);
125 for lib_dir in KNOWN_BUILD_SUBDIRECTORIES {
126 let search_path = install_dir.join(lib_dir).join(&file);
127 check_and_return!(search_path);
128 }
129 }
130
131 // Search using the `OPENVINO_INSTALL_DIR` environment variable; this may be set by users of the
132 // `openvino-rs` library.
133 if let Some(install_dir) = env::var_os(ENV_OPENVINO_INSTALL_DIR) {
134 let install_dir = PathBuf::from(install_dir);
135 for lib_dir in KNOWN_INSTALLATION_SUBDIRECTORIES {
136 let search_path = install_dir.join(lib_dir).join(&file);
137 check_and_return!(search_path);
138 }
139 }
140
141 // Search using the `INTEL_OPENVINO_DIR` environment variable; this is set up by an OpenVINO
142 // installation (e.g. `source /opt/intel/openvino/setupvars.sh`).
143 if let Some(install_dir) = env::var_os(ENV_INTEL_OPENVINO_DIR) {
144 let install_dir = PathBuf::from(install_dir);
145 for lib_dir in KNOWN_INSTALLATION_SUBDIRECTORIES {
146 let search_path = install_dir.join(lib_dir).join(&file);
147 check_and_return!(search_path);
148 }
149 }
150
151 // Search in the OS library path (i.e. `LD_LIBRARY_PATH` on Linux, `PATH` on Windows, and
152 // `DYLD_LIBRARY_PATH` on MacOS).
153 if let Some(path) = env::var_os(ENV_LIBRARY_PATH) {
154 for lib_dir in env::split_paths(&path) {
155 let search_path = lib_dir.join(&file);
156 check_and_return!(search_path);
157 }
158 }
159
160 // Search in OpenVINO's installation directories; after v2022.3, Linux packages will be
161 // installed in the system's default library locations.
162 for install_dir in SYSTEM_INSTALLATION_DIRECTORIES
163 .iter()
164 .map(PathBuf::from)
165 .filter(|d| d.is_dir())
166 {
167 // Check if the file is located in the installation directory.
168 let search_path = install_dir.join(&file);
169 check_and_return!(search_path);
170
171 // Otherwise, check for version terminators: e.g., `libfoo.so.3.1.2`.
172 let filenames = list_directory(&install_dir).expect("cannot list installation directory");
173 let versions = get_suffixes(filenames, &file);
174 if let Some(path) = build_latest_version(&install_dir, &file, versions) {
175 check_and_return!(path);
176 }
177 }
178
179 // Search in OpenVINO's default installation directories (if they exist).
180 for default_dir in DEFAULT_INSTALLATION_DIRECTORIES
181 .iter()
182 .map(PathBuf::from)
183 .filter(|d| d.is_dir())
184 {
185 for lib_dir in KNOWN_INSTALLATION_SUBDIRECTORIES {
186 let search_path = default_dir.join(lib_dir).join(&file);
187 check_and_return!(search_path);
188 }
189 }
190
191 None
192}
193
194const ENV_OPENVINO_INSTALL_DIR: &str = "OPENVINO_INSTALL_DIR";
195const ENV_OPENVINO_BUILD_DIR: &str = "OPENVINO_BUILD_DIR";
196const ENV_INTEL_OPENVINO_DIR: &str = "INTEL_OPENVINO_DIR";
197const ENV_OPENVINO_PLUGINS_XML: &str = "OPENVINO_PLUGINS_XML";
198
199cfg_if! {
200 if #[cfg(any(target_os = "linux"))] {
201 const ENV_LIBRARY_PATH: &str = "LD_LIBRARY_PATH";
202 } else if #[cfg(target_os = "macos")] {
203 const ENV_LIBRARY_PATH: &str = "DYLD_LIBRARY_PATH";
204 } else if #[cfg(target_os = "windows")] {
205 const ENV_LIBRARY_PATH: &str = "PATH";
206 } else {
207 // This may not work but seems like a sane default for target OS' not listed above.
208 const ENV_LIBRARY_PATH: &str = "LD_LIBRARY_PATH";
209 }
210}
211
212cfg_if! {
213 if #[cfg(any(target_os = "linux", target_os = "macos"))] {
214 const DEFAULT_INSTALLATION_DIRECTORIES: &[&str] = &[
215 "/opt/intel/openvino_2022",
216 "/opt/intel/openvino",
217 ];
218 } else if #[cfg(target_os = "windows")] {
219 const DEFAULT_INSTALLATION_DIRECTORIES: &[&str] = &[
220 "C:\\Program Files (x86)\\Intel\\openvino_2022",
221 "C:\\Program Files (x86)\\Intel\\openvino",
222 ];
223 } else {
224 const DEFAULT_INSTALLATION_DIRECTORIES: &[&str] = &[];
225 }
226}
227
228cfg_if! {
229 if #[cfg(target_os = "linux")] {
230 const SYSTEM_INSTALLATION_DIRECTORIES: &[&str] = &[
231 "/lib", // DEB-installed package (OpenVINO >= 2023.2)
232 "/usr/lib/x86_64-linux-gnu", // DEB-installed package (OpenVINO >= 2022.3)
233 "/lib/x86_64-linux-gnu", // DEB-installed package (TBB)
234 "/usr/lib64", // RPM-installed package >= 2022.3
235 ];
236 } else {
237 const SYSTEM_INSTALLATION_DIRECTORIES: &[&str] = &[];
238 }
239}
240
241const KNOWN_INSTALLATION_SUBDIRECTORIES: &[&str] = &[
242 "runtime/lib/intel64/Release",
243 "runtime/lib/intel64",
244 "runtime/lib/arm64/Release",
245 "runtime/lib/arm64",
246 "runtime/lib/aarch64/Release",
247 "runtime/lib/aarch64",
248 "runtime/lib/armv7l",
249 "runtime/lib/armv7l/Release",
250 "runtime/bin/intel64/Release",
251 "runtime/bin/intel64",
252 "runtime/bin/arm64/Release",
253 "runtime/bin/arm64",
254 "runtime/3rdparty/tbb/bin",
255 "runtime/3rdparty/tbb/lib",
256];
257
258const KNOWN_BUILD_SUBDIRECTORIES: &[&str] = &[
259 "bin/intel64/Debug/lib",
260 "bin/intel64/Debug",
261 "bin/intel64/Release/lib",
262 "bin/arm64/Debug/lib",
263 "bin/arm64/Debug",
264 "bin/arm64/Release/lib",
265 "bin/aarch64/Debug/lib",
266 "bin/aarch64/Debug",
267 "bin/aarch64/Release/lib",
268 "bin/armv7l/Debug/lib",
269 "bin/armv7l/Debug",
270 "bin/armv7l/Release/lib",
271 "temp/tbb/lib",
272 "temp/tbb/bin",
273];
274
275/// Find the path to the `plugins.xml` configuration file.
276///
277/// OpenVINO records the location of its plugin libraries in a `plugins.xml` file. This file is
278/// examined by OpenVINO on initialization; not knowing the location to this file can lead to
279/// inference errors later (e.g., `Inference(GeneralError)`). OpenVINO uses the `plugins.xml` file
280/// to load target-specific libraries on demand for performing inference. The `plugins.xml` file
281/// maps targets (e.g., CPU) to their target-specific implementation library.
282///
283/// This file can be found in multiple locations, depending on the installation mechanism. For TAR
284/// installations, it is found in the same directory as the OpenVINO libraries themselves. For
285/// DEB/RPM installations, it is found in a version-suffixed directory beside the OpenVINO libraries
286/// (e.g., `openvino-2022.3.0/plugins.xml`).
287///
288/// This function will probe:
289/// - the `OPENVINO_PLUGINS_XML` environment variable — this is specific to this library
290/// - the same directory as the `openvino_c` shared library, as discovered by [find]
291/// - the latest version directory beside the `openvino_c` shared library (i.e.,
292/// `openvino-<latest version>/`)
293pub fn find_plugins_xml() -> Option<PathBuf> {
294 const FILE_NAME: &str = "plugins.xml";
295
296 // The `OPENVINO_PLUGINS_XML` should point directly to the file.
297 if let Some(path) = env::var_os(ENV_OPENVINO_PLUGINS_XML) {
298 return Some(PathBuf::from(path));
299 }
300
301 // Check in the same directory as the `openvino_c` library; e.g.,
302 // `/opt/intel/openvino_.../runtime/lib/intel64/plugins.xml`.
303 let library = find("openvino_c", Linking::Dynamic)?;
304 let library_parent_dir = library.parent()?;
305 check_and_return!(library_parent_dir.join(FILE_NAME));
306
307 // Check in OpenVINO's special system installation directory; e.g.,
308 // `/usr/lib/x86_64-linux-gnu/openvino-2022.3.0/plugins.xml`.
309 let filenames = list_directory(library_parent_dir)?;
310 let versions = get_suffixes(filenames, "openvino-");
311 let path = build_latest_version(library_parent_dir, "openvino-", versions)?.join("plugins.xml");
312 check_and_return!(path);
313
314 None
315}
316
317#[inline]
318fn list_directory(dir: &Path) -> Option<impl IntoIterator<Item = String>> {
319 let traversal = fs::read_dir(dir).ok()?;
320 Some(
321 traversal
322 .filter_map(Result::ok)
323 .filter_map(|f| f.file_name().to_str().map(ToString::to_string)),
324 )
325}
326
327#[inline]
328fn get_suffixes(filenames: impl IntoIterator<Item = String>, prefix: &str) -> Vec<String> {
329 filenames
330 .into_iter()
331 .filter_map(|f| f.strip_prefix(prefix).map(ToString::to_string))
332 .collect()
333}
334
335#[inline]
336fn build_latest_version(dir: &Path, prefix: &str, mut versions: Vec<String>) -> Option<PathBuf> {
337 if versions.is_empty() {
338 return None;
339 }
340 versions.sort();
341 versions.reverse();
342 let latest_version = versions
343 .first()
344 .expect("already checked that a version exists");
345 let filename = format!("{prefix}{latest_version}");
346 Some(dir.join(filename))
347}
348
349#[cfg(test)]
350mod test {
351 use super::*;
352
353 /// This test uses `find` to search for the `openvino_c` library on the local
354 /// system.
355 #[test]
356 fn find_openvino_c_locally() {
357 env_logger::init();
358 assert!(find("openvino_c", Linking::Dynamic).is_some());
359 }
360
361 /// This test shows how the finder would discover the latest shared library on an
362 /// APT installation.
363 #[test]
364 fn find_latest_library() {
365 let path = build_latest_version(
366 &PathBuf::from("/usr/lib/x86_64-linux-gnu"),
367 "libopenvino.so.",
368 vec!["2022.1.0".into(), "2022.3.0".into()],
369 );
370 assert_eq!(
371 path,
372 Some(PathBuf::from(
373 "/usr/lib/x86_64-linux-gnu/libopenvino.so.2022.3.0"
374 ))
375 );
376 }
377
378 /// This test shows how the finder would discover the latest `plugins.xml` directory on an
379 /// APT installation.
380 #[test]
381 fn find_latest_plugin_xml() {
382 let path = build_latest_version(
383 &PathBuf::from("/usr/lib/x86_64-linux-gnu"),
384 "openvino-",
385 vec!["2022.3.0".into(), "2023.1.0".into(), "2022.1.0".into()],
386 );
387 assert_eq!(
388 path,
389 Some(PathBuf::from("/usr/lib/x86_64-linux-gnu/openvino-2023.1.0"))
390 );
391 }
392}