Coverage Report

Created: 2026-08-20 05:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
src/rocm.rs
Line
Count
Source
1
// SPDX-License-Identifier: Apache-2.0
2
//! `ROCm` userspace detection.
3
//!
4
//! Unlike the rest of this crate, `ROCm` has no driver-side version to read: KFD
5
//! sysfs exposes a topology generation counter and nothing else, and `amdgpu`
6
//! declares no `MODULE_VERSION`. The release version lives in the userspace
7
//! install instead, in a plain text file the `rocm-core` package writes:
8
//!
9
//! ```text
10
//! /opt/rocm/.info/version   →   6.2.4-123
11
//! ```
12
//!
13
//! So this is a filesystem probe, not a library call — nothing links, and a
14
//! host without `ROCm` simply reports `None`. `ROCM_PATH` is honoured first,
15
//! which is how side-by-side installs (`/opt/rocm-6.2.4`) are selected.
16
//!
17
//! Note what this does *not* mean: `ROCm` being absent says nothing about
18
//! whether the GPU works for compute. The kernel side is independent, and is
19
//! what [`crate::GpuInfo::gfx_target`] reports.
20
21
use crate::{RocmHost, RocmVersion};
22
use std::path::{Path, PathBuf};
23
24
/// Conventional install prefix, used when `ROCM_PATH` is unset.
25
const DEFAULT_ROOT: &str = "/opt/rocm";
26
27
/// Path of the version file relative to an install root.
28
const VERSION_FILE: &str = ".info/version";
29
30
/// Parse a `.info/version` file: `major.minor.patch`, followed by a build
31
/// suffix the shared parser ignores (`6.2.4-123` → `6.2.4`).
32
11
fn parse_version(content: &str) -> Option<RocmVersion> {
33
11
    let (
major6
,
minor6
,
patch6
) = crate::parse_dotted_version(content)
?5
;
34
6
    Some(RocmVersion {
35
6
        major,
36
6
        minor,
37
6
        patch,
38
6
    })
39
11
}
40
41
/// Read the `ROCm` version from one install root, if it holds one.
42
5
fn version_at(root: &Path) -> Option<RocmVersion> {
43
5
    std::fs::read_to_string(root.join(VERSION_FILE))
44
5
        .ok()
45
5
        .and_then(|content| 
parse_version0
(
&content0
))
46
5
}
47
48
/// Install roots to try, most specific first.
49
5
fn roots() -> Vec<PathBuf> {
50
5
    let mut roots = Vec::new();
51
    // An explicit `ROCM_PATH` wins: it is how a side-by-side install is picked,
52
    // so honouring `/opt/rocm` over it would report the wrong version.
53
5
    if let Some(
path0
) = std::env::var_os("ROCM_PATH") {
54
0
        roots.push(PathBuf::from(path));
55
5
    }
56
5
    roots.push(PathBuf::from(DEFAULT_ROOT));
57
5
    roots
58
5
}
59
60
4
pub(crate) fn host() -> Option<RocmHost> {
61
4
    let 
version0
= roots().iter().find_map(|root| version_at(root))?;
62
0
    Some(RocmHost { version })
63
4
}
64
65
#[cfg(test)]
66
mod tests {
67
    use super::*;
68
69
    #[test]
70
1
    fn parses_release_with_build_suffix() {
71
        // The exact form `rocm-core` writes.
72
1
        assert_eq!(
73
1
            parse_version("6.2.4-123\n"),
74
1
            Some(RocmVersion::new(6, 2, 4))
75
        );
76
1
        assert_eq!(parse_version("5.7.1-63"), Some(RocmVersion::new(5, 7, 1)));
77
1
        assert_eq!(
78
1
            parse_version("  6.0.0-91  "),
79
1
            Some(RocmVersion::new(6, 0, 0))
80
        );
81
1
    }
82
83
    #[test]
84
1
    fn parses_release_without_suffix_or_patch() {
85
1
        assert_eq!(parse_version("6.2.4"), Some(RocmVersion::new(6, 2, 4)));
86
1
        assert_eq!(parse_version("6.2"), Some(RocmVersion::new(6, 2, 0)));
87
1
        assert_eq!(parse_version("6.2+build"), Some(RocmVersion::new(6, 2, 0)));
88
1
    }
89
90
    #[test]
91
1
    fn rejects_malformed_versions() {
92
1
        assert_eq!(parse_version(""), None);
93
1
        assert_eq!(parse_version("6"), None, "a bare major is not a version");
94
1
        assert_eq!(parse_version("six.two.four"), None);
95
1
        assert_eq!(parse_version("6.2.x"), None);
96
1
        assert_eq!(parse_version("-6.2.4"), None);
97
1
    }
98
99
    #[test]
100
1
    fn versions_order_major_first() {
101
1
        assert!(RocmVersion::new(6, 2, 4) > RocmVersion::new(6, 2, 0));
102
1
        assert!(RocmVersion::new(6, 0, 0) > RocmVersion::new(5, 7, 1));
103
1
        assert!(RocmVersion::new(6, 10, 0) > RocmVersion::new(6, 9, 9));
104
1
    }
105
106
    #[test]
107
1
    fn missing_install_root_reports_nothing() {
108
1
        assert_eq!(version_at(Path::new("/nonexistent-rocm-root")), None);
109
1
    }
110
111
    #[test]
112
1
    fn rocm_path_is_searched_before_the_default() {
113
1
        let roots = roots();
114
1
        assert_eq!(
115
1
            roots.last().map(PathBuf::as_path),
116
1
            Some(Path::new(DEFAULT_ROOT)),
117
            "the conventional prefix is always the fallback",
118
        );
119
1
        if std::env::var_os("ROCM_PATH").is_some() {
120
0
            assert_eq!(roots.len(), 2, "an explicit ROCM_PATH is tried first");
121
1
        }
122
1
    }
123
124
    #[test]
125
1
    fn host_lookup_never_panics() {
126
        // Environment-dependent: most hosts have no ROCm at all.
127
1
        if let Some(
rocm0
) = host() {
128
0
            assert!(rocm.version.major > 0);
129
1
        }
130
1
    }
131
}