Skip to main content

riscfetch_core/
lib.rs

1//! RISC-V system information library
2//!
3//! Provides functions to detect and query RISC-V specific system information
4//! including ISA extensions, hardware IDs, vector capabilities, and more.
5//!
6//! # Example
7//!
8//! ```no_run
9//! use riscfetch_core::*;
10//!
11//! if is_riscv() {
12//!     println!("ISA: {}", get_isa_string());
13//!     println!("Extensions: {}", get_extensions_compact());
14//! }
15//! ```
16
17mod extensions;
18mod hardware;
19mod implications;
20mod parsing;
21mod system;
22mod types;
23
24// Re-export types
25pub use types::{CacheInfo, ExtensionEntry, HardwareIds, RiscvInfo, SystemInfo, VectorInfo};
26
27// Re-export extension definitions
28pub use extensions::{
29    STANDARD_EXTENSIONS, S_CATEGORY_NAMES, S_EXTENSIONS, Z_CATEGORY_NAMES, Z_EXTENSIONS,
30};
31
32// Re-export parsing functions and types
33pub use parsing::{
34    compute_derived_extension_names, get_all_s_extensions_with_status,
35    get_all_standard_extensions_with_status, get_all_z_extensions_with_status,
36    get_extensions_with_derived, get_s_category_name, get_z_category_name, group_by_category,
37    parse_extensions_compact, parse_extensions_explained, parse_s_extensions,
38    parse_s_extensions_explained, parse_s_extensions_with_category,
39    parse_s_extensions_with_category_and_derived, parse_vector_from_isa, parse_z_extensions,
40    parse_z_extensions_explained, parse_z_extensions_with_category,
41    parse_z_extensions_with_category_and_derived, ExtensionInfo,
42};
43
44// Re-export hardware functions
45pub use hardware::{
46    get_board_info, get_cache_info, get_hardware_ids, get_hart_count, get_hart_count_num,
47    get_isa_string, get_vector_detail,
48};
49
50// Re-export system functions
51pub use system::{
52    get_kernel_info, get_memory_bytes, get_memory_info, get_os_info, get_uptime, get_uptime_seconds,
53};
54
55use std::fs;
56use std::process::Command;
57use sysinfo::System;
58
59/// Check if the current system is RISC-V architecture
60#[must_use]
61pub fn is_riscv() -> bool {
62    if let Ok(output) = Command::new("uname").arg("-m").output() {
63        let arch = String::from_utf8_lossy(&output.stdout);
64        if arch.contains("riscv") {
65            return true;
66        }
67    }
68
69    if let Ok(content) = fs::read_to_string("/proc/cpuinfo") {
70        if content.contains("riscv") || content.contains("RISC-V") {
71            return true;
72        }
73    }
74
75    false
76}
77
78/// Get compact extension list (e.g., "I M A F D C V")
79#[must_use]
80pub fn get_extensions_compact() -> String {
81    parse_extensions_compact(&get_isa_string())
82}
83
84/// Get Z-extensions as compact string
85#[must_use]
86pub fn get_z_extensions() -> String {
87    parse_z_extensions(&get_isa_string())
88}
89
90/// Get extensions with explanations
91#[must_use]
92pub fn get_extensions_explained() -> Vec<(String, String)> {
93    parse_extensions_explained(&get_isa_string())
94}
95
96/// Get Z-extensions with explanations
97#[must_use]
98pub fn get_z_extensions_explained() -> Vec<(String, String)> {
99    parse_z_extensions_explained(&get_isa_string())
100}
101
102/// Get S-extensions as compact string
103#[must_use]
104pub fn get_s_extensions() -> String {
105    parse_s_extensions(&get_isa_string())
106}
107
108/// Get S-extensions with explanations
109#[must_use]
110pub fn get_s_extensions_explained() -> Vec<(String, String)> {
111    parse_s_extensions_explained(&get_isa_string())
112}
113
114/// Get Z-extensions with category info
115#[must_use]
116pub fn get_z_extensions_with_category() -> Vec<ExtensionInfo> {
117    parse_z_extensions_with_category(&get_isa_string())
118}
119
120/// Get S-extensions with category info
121#[must_use]
122pub fn get_s_extensions_with_category() -> Vec<ExtensionInfo> {
123    parse_s_extensions_with_category(&get_isa_string())
124}
125
126/// Get standard extensions, including ones inferred via implication/composition
127/// (issue #10). Inferred entries have `derived: true`.
128#[must_use]
129pub fn get_extensions_with_derived_for_system() -> Vec<ExtensionInfo> {
130    get_extensions_with_derived(&get_isa_string())
131}
132
133/// Get Z-extensions with category info, including ones inferred via
134/// implication/composition (issue #10). Inferred entries have `derived: true`.
135#[must_use]
136pub fn get_z_extensions_with_category_and_derived() -> Vec<ExtensionInfo> {
137    parse_z_extensions_with_category_and_derived(&get_isa_string())
138}
139
140/// Get S-extensions with category info, including ones inferred via
141/// implication/composition (issue #10). Inferred entries have `derived: true`.
142#[must_use]
143pub fn get_s_extensions_with_category_and_derived() -> Vec<ExtensionInfo> {
144    parse_s_extensions_with_category_and_derived(&get_isa_string())
145}
146
147/// Collect RISC-V specific information only (excludes generic system info)
148#[must_use]
149pub fn collect_riscv_info() -> RiscvInfo {
150    use types::ExtensionEntry;
151
152    let mut sys = System::new();
153    sys.refresh_cpu_all();
154
155    let isa = get_isa_string();
156    let exts: Vec<ExtensionEntry> = get_extensions_with_derived(&isa)
157        .into_iter()
158        .map(|e| ExtensionEntry {
159            name: e.name,
160            description: e.description,
161            derived: e.derived,
162        })
163        .collect();
164    let z_exts: Vec<ExtensionEntry> = parse_z_extensions_with_category_and_derived(&isa)
165        .into_iter()
166        .map(|e| ExtensionEntry {
167            name: e.name,
168            description: e.description,
169            derived: e.derived,
170        })
171        .collect();
172
173    let hw_ids = get_hardware_ids();
174    let isa_lower = isa.to_lowercase();
175    let base = isa_lower.split('_').next().unwrap_or(&isa_lower);
176
177    RiscvInfo {
178        isa,
179        extensions: exts,
180        z_extensions: z_exts,
181        vector: VectorInfo {
182            enabled: base.contains('v') || isa_lower.contains("zve"),
183            vlen: None,
184            elen: None,
185        },
186        hart_count: sys.cpus().len(),
187        hardware_ids: hw_ids,
188        cache: CacheInfo::default(),
189    }
190}
191
192/// Collect all information into a single struct
193#[must_use]
194pub fn collect_all_info() -> SystemInfo {
195    use types::ExtensionEntry;
196
197    let mut sys = System::new();
198    sys.refresh_memory();
199    sys.refresh_cpu_all();
200
201    let isa = get_isa_string();
202    let exts: Vec<ExtensionEntry> = get_extensions_with_derived(&isa)
203        .into_iter()
204        .map(|e| ExtensionEntry {
205            name: e.name,
206            description: e.description,
207            derived: e.derived,
208        })
209        .collect();
210    let z_exts: Vec<ExtensionEntry> = parse_z_extensions_with_category_and_derived(&isa)
211        .into_iter()
212        .map(|e| ExtensionEntry {
213            name: e.name,
214            description: e.description,
215            derived: e.derived,
216        })
217        .collect();
218    let s_exts: Vec<ExtensionEntry> = parse_s_extensions_with_category_and_derived(&isa)
219        .into_iter()
220        .map(|e| ExtensionEntry {
221            name: e.name,
222            description: e.description,
223            derived: e.derived,
224        })
225        .collect();
226
227    let hw_ids = get_hardware_ids();
228    let isa_lower = isa.to_lowercase();
229    let base = isa_lower.split('_').next().unwrap_or(&isa_lower);
230
231    SystemInfo {
232        isa,
233        extensions: exts,
234        z_extensions: z_exts,
235        s_extensions: s_exts,
236        vector: VectorInfo {
237            enabled: base.contains('v') || isa_lower.contains("zve"),
238            vlen: None,
239            elen: None,
240        },
241        hart_count: sys.cpus().len(),
242        hardware_ids: hw_ids,
243        cache: CacheInfo::default(),
244        board: get_board_info(),
245        memory_used_bytes: sys.used_memory(),
246        memory_total_bytes: sys.total_memory(),
247        kernel: get_kernel_info(),
248        os: get_os_info(),
249        uptime_seconds: System::uptime(),
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    // === System Info Tests (work on any system) ===
258
259    #[test]
260    fn test_get_uptime() {
261        let uptime = get_uptime();
262        assert!(!uptime.is_empty());
263    }
264
265    #[test]
266    fn test_get_uptime_seconds() {
267        let secs = get_uptime_seconds();
268        assert!(secs > 0);
269    }
270
271    #[test]
272    fn test_get_memory_bytes() {
273        let (used, total) = get_memory_bytes();
274        assert!(total > 0);
275        assert!(used <= total);
276    }
277
278    #[test]
279    fn test_get_kernel_info() {
280        let kernel = get_kernel_info();
281        assert!(!kernel.is_empty());
282    }
283
284    #[test]
285    fn test_get_os_info() {
286        let os = get_os_info();
287        assert!(!os.is_empty());
288    }
289
290    #[test]
291    fn test_hardware_ids_default() {
292        let ids = HardwareIds::default();
293        assert!(ids.mvendorid.is_empty());
294        assert!(ids.marchid.is_empty());
295        assert!(ids.mimpid.is_empty());
296    }
297
298    // === RISC-V Hardware Tests (only run on actual RISC-V) ===
299
300    #[cfg(target_arch = "riscv64")]
301    mod riscv_hardware_tests {
302        use super::*;
303
304        #[test]
305        fn hw_is_riscv() {
306            assert!(is_riscv());
307        }
308
309        #[test]
310        fn hw_isa_string_valid() {
311            let isa = get_isa_string();
312            assert!(isa.starts_with("rv64") || isa.starts_with("rv32"));
313        }
314
315        #[test]
316        fn hw_isa_string_has_base() {
317            let isa = get_isa_string();
318            assert!(isa.contains('i') || isa.contains('e'));
319        }
320
321        #[test]
322        fn hw_extensions_not_empty() {
323            let ext = get_extensions_compact();
324            assert!(!ext.is_empty());
325            assert!(ext.contains('I') || ext.contains('E'));
326        }
327
328        #[test]
329        fn hw_hart_count_positive() {
330            let hart_str = get_hart_count();
331            assert!(hart_str.contains("hart"));
332            let num: String = hart_str
333                .chars()
334                .take_while(|c| c.is_ascii_digit())
335                .collect();
336            let count: usize = num.parse().unwrap_or(0);
337            assert!(count > 0);
338        }
339
340        #[test]
341        fn hw_hardware_ids_present() {
342            let ids = get_hardware_ids();
343            let has_any =
344                !ids.mvendorid.is_empty() || !ids.marchid.is_empty() || !ids.mimpid.is_empty();
345            assert!(has_any);
346        }
347
348        #[test]
349        fn hw_collect_all_info() {
350            let info = collect_all_info();
351            assert!(!info.isa.is_empty());
352            assert!(!info.extensions.is_empty());
353            assert!(info.hart_count > 0);
354        }
355
356        #[test]
357        fn hw_collect_riscv_info() {
358            let info = collect_riscv_info();
359            assert!(!info.isa.is_empty());
360            assert!(!info.extensions.is_empty());
361            assert!(info.hart_count > 0);
362        }
363
364        #[test]
365        fn hw_riscv_info_excludes_system_fields() {
366            let riscv_info = collect_riscv_info();
367            let all_info = collect_all_info();
368
369            // RiscvInfo should have the same RISC-V specific fields
370            assert_eq!(riscv_info.isa, all_info.isa);
371            assert_eq!(riscv_info.extensions, all_info.extensions);
372            assert_eq!(riscv_info.z_extensions, all_info.z_extensions);
373            assert_eq!(riscv_info.hart_count, all_info.hart_count);
374
375            // SystemInfo has additional fields that RiscvInfo doesn't have
376            // (board, memory_*, kernel, os, uptime_seconds)
377            // This is verified by the type system - RiscvInfo simply doesn't have these fields
378        }
379    }
380}