Coverage Report

Created: 2026-08-20 05:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
src/oneapi.rs
Line
Count
Source
1
// SPDX-License-Identifier: Apache-2.0
2
//! Intel `oneAPI` detection.
3
//!
4
//! The Intel counterpart of [`crate::rocm_host`], and the same kind of probe: a
5
//! userspace install, not a driver. Intel exposes no single release marker the
6
//! way `ROCm` writes `.info/version`, so the release is taken from the
7
//! component layout the toolkit installs:
8
//!
9
//! ```text
10
//! /opt/intel/oneapi/compiler/latest -> 2024.2
11
//! ```
12
//!
13
//! `ONEAPI_ROOT` — which `setvars.sh` exports — is honoured first, so a
14
//! non-default or side-by-side install is picked over `/opt/intel/oneapi`.
15
//!
16
//! What this does **not** cover: the Level Zero loader and Intel's
17
//! compute-runtime, which distros package into `/usr/lib` with no version file.
18
//! Reading those means `dlopen` plus `zeInit`/`zeDriverGetProperties`, which is
19
//! a linked runtime call rather than a file read, so it stays out of scope
20
//! here. A host running GPU compute through a distro-packaged Level Zero with
21
//! no toolkit installed therefore reports `None`.
22
//!
23
//! **Unverified against a real install.** Written without Intel hardware or a
24
//! `oneAPI` install to test against; the parsing is unit-tested and the layout
25
//! above is the part to confirm first.
26
27
use crate::{OneApiHost, OneApiVersion};
28
use std::path::{Path, PathBuf};
29
30
/// Conventional install prefix, used when `ONEAPI_ROOT` is unset.
31
const DEFAULT_ROOT: &str = "/opt/intel/oneapi";
32
33
/// Component directories whose release the toolkit version is read from, in
34
/// preference order. `compiler` leads because it is the component a GPU build
35
/// actually needs; the rest are fallbacks for partial installs.
36
const COMPONENTS: &[&str] = &["compiler", "mkl", "tbb", "dpl", "ccl"];
37
38
/// Parse a version directory name (`2024.2`, `2024.2.1`).
39
7
fn parse_version(name: &str) -> Option<OneApiVersion> {
40
7
    let (
major3
,
minor3
,
patch3
) = crate::parse_dotted_version(name)
?4
;
41
3
    Some(OneApiVersion {
42
3
        major,
43
3
        minor,
44
3
        patch,
45
3
    })
46
7
}
47
48
/// Release installed for one component: the `latest` symlink's target, falling
49
/// back to the newest versioned directory when no such symlink exists.
50
26
fn component_version(component: &Path) -> Option<OneApiVersion> {
51
26
    let from_symlink = std::fs::read_link(component.join("latest"))
52
26
        .ok()
53
26
        .and_then(|target| parse_version(&
target.file_name()0
?0
.
to_string_lossy0
()));
54
26
    if let Some(
version0
) = from_symlink {
55
0
        return Some(version);
56
26
    }
57
    // No `latest`: take the highest version present rather than directory
58
    // order, which is arbitrary.
59
26
    let 
mut versions0
:
Vec<OneApiVersion>0
= std::fs::read_dir(component)
60
26
        .ok()?
61
0
        .flatten()
62
0
        .filter_map(|entry| parse_version(&entry.file_name().to_string_lossy()))
63
0
        .collect();
64
0
    versions.sort_unstable();
65
0
    versions.pop()
66
26
}
67
68
/// Read the toolkit release from one install root, if it holds one.
69
5
fn version_at(root: &Path) -> Option<OneApiVersion> {
70
5
    COMPONENTS
71
5
        .iter()
72
25
        .
find_map5
(|component| component_version(&root.join(component)))
73
5
}
74
75
/// Install roots to try, most specific first.
76
5
fn roots() -> Vec<PathBuf> {
77
5
    let mut roots = Vec::new();
78
    // `setvars.sh` exports this; an explicit value selects a side-by-side
79
    // install, so preferring the default prefix would report the wrong release.
80
5
    if let Some(
path0
) = std::env::var_os("ONEAPI_ROOT") {
81
0
        roots.push(PathBuf::from(path));
82
5
    }
83
5
    roots.push(PathBuf::from(DEFAULT_ROOT));
84
5
    roots
85
5
}
86
87
4
pub(crate) fn host() -> Option<OneApiHost> {
88
4
    let 
version0
= roots().iter().find_map(|root| version_at(root))?;
89
0
    Some(OneApiHost { version })
90
4
}
91
92
#[cfg(test)]
93
mod tests {
94
    use super::*;
95
96
    #[test]
97
1
    fn parses_release_directory_names() {
98
1
        assert_eq!(
99
1
            parse_version("2024.2"),
100
1
            Some(OneApiVersion::new(2024, 2, 0))
101
        );
102
1
        assert_eq!(
103
1
            parse_version("2024.2.1"),
104
1
            Some(OneApiVersion::new(2024, 2, 1))
105
        );
106
1
        assert_eq!(
107
1
            parse_version("2025.0"),
108
1
            Some(OneApiVersion::new(2025, 0, 0))
109
        );
110
1
    }
111
112
    #[test]
113
1
    fn rejects_non_version_directories() {
114
        // `latest` itself, and the component subdirectories beside it.
115
1
        assert_eq!(parse_version("latest"), None);
116
1
        assert_eq!(parse_version("bin"), None);
117
1
        assert_eq!(parse_version("2024"), None, "a bare year is not a version");
118
1
        assert_eq!(parse_version(""), None);
119
1
    }
120
121
    #[test]
122
1
    fn versions_order_by_release_year_first() {
123
1
        assert!(OneApiVersion::new(2025, 0, 0) > OneApiVersion::new(2024, 2, 1));
124
1
        assert!(OneApiVersion::new(2024, 2, 1) > OneApiVersion::new(2024, 2, 0));
125
1
        assert!(OneApiVersion::new(2024, 10, 0) > OneApiVersion::new(2024, 9, 0));
126
1
    }
127
128
    #[test]
129
1
    fn missing_install_root_reports_nothing() {
130
1
        assert_eq!(version_at(Path::new("/nonexistent-oneapi-root")), None);
131
1
        assert_eq!(component_version(Path::new("/nonexistent-component")), None);
132
1
    }
133
134
    #[test]
135
1
    fn compiler_is_the_preferred_component() {
136
1
        assert_eq!(
137
1
            COMPONENTS.first(),
138
            Some(&"compiler"),
139
            "a GPU build needs the compiler component, so it names the release",
140
        );
141
1
    }
142
143
    #[test]
144
1
    fn oneapi_root_is_searched_before_the_default() {
145
1
        let roots = roots();
146
1
        assert_eq!(
147
1
            roots.last().map(PathBuf::as_path),
148
1
            Some(Path::new(DEFAULT_ROOT)),
149
            "the conventional prefix is always the fallback",
150
        );
151
1
        if std::env::var_os("ONEAPI_ROOT").is_some() {
152
0
            assert_eq!(roots.len(), 2, "an explicit ONEAPI_ROOT is tried first");
153
1
        }
154
1
    }
155
156
    #[test]
157
1
    fn host_lookup_never_panics() {
158
        // Environment-dependent: most hosts have no oneAPI at all.
159
1
        if let Some(
oneapi0
) = host() {
160
0
            assert!(oneapi.version.major > 0);
161
1
        }
162
1
    }
163
}