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 &mdash;
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 &mdash;
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 &mdash; 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`) &mdash; this is useful
95///   for DEB or RPM installations
96/// - OpenVINO's documented extract paths &mdash; 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 &mdash; 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}