Skip to main content

cargo_reclaim/active_process/
sysinfo_provider.rs

1use std::path::Path;
2
3use crate::planner::{ActiveObservation, ObservedCargoProcess};
4
5use super::common::{detect_tool, extract_referenced_paths, process_matches_scope};
6use super::foundation::{ActiveObservationProvider, ActiveObservationScope};
7
8#[derive(Debug, Clone, Default)]
9pub struct SysinfoActiveObservationProvider;
10
11impl ActiveObservationProvider for SysinfoActiveObservationProvider {
12    fn observe(&self, scope: &ActiveObservationScope) -> ActiveObservation {
13        let processes = observe_sysinfo_processes(scope);
14        ActiveObservation::complete(processes)
15    }
16}
17
18fn observe_sysinfo_processes(scope: &ActiveObservationScope) -> Vec<ObservedCargoProcess> {
19    use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, System, UpdateKind};
20
21    let mut system = System::new();
22    system.refresh_processes_specifics(
23        ProcessesToUpdate::All,
24        true,
25        ProcessRefreshKind::nothing()
26            .with_cmd(UpdateKind::Always)
27            .with_cwd(UpdateKind::Always),
28    );
29
30    system
31        .processes()
32        .values()
33        .filter_map(|process| {
34            let name = process.name().to_string_lossy();
35            let cmdline = process
36                .cmd()
37                .iter()
38                .map(|arg| arg.to_string_lossy().into_owned())
39                .collect::<Vec<_>>();
40            observed_process_from_parts(&name, &cmdline, process.cwd())
41        })
42        .filter(|process| process_matches_scope(process, scope))
43        .collect()
44}
45
46fn observed_process_from_parts(
47    name: &str,
48    cmdline: &[String],
49    cwd: Option<&Path>,
50) -> Option<ObservedCargoProcess> {
51    let tool = detect_tool(name, cmdline)?;
52    let mut process = ObservedCargoProcess::new(tool);
53    if let Some(cwd) = cwd {
54        process.cwd = Some(cwd.to_path_buf());
55    }
56    process.referenced_paths = extract_referenced_paths(cmdline, cwd);
57    Some(process)
58}
59
60#[cfg(test)]
61mod tests {
62    use std::path::PathBuf;
63
64    use crate::planner::{CargoTool, TargetContext};
65
66    use super::*;
67
68    #[test]
69    fn sysinfo_parts_match_cargo_by_cwd_scope() {
70        let scope = ActiveObservationScope::from_target_contexts([TargetContext::new(
71            "/work/crate/target",
72        )
73        .with_project_root("/work/crate")]);
74
75        let process = observed_process_from_parts(
76            "cargo",
77            &["cargo".to_string(), "test".to_string()],
78            Some(Path::new("/work/crate")),
79        )
80        .expect("cargo should be detected");
81
82        assert_eq!(process.tool, CargoTool::Cargo);
83        assert!(process_matches_scope(&process, &scope));
84    }
85
86    #[test]
87    fn sysinfo_parts_match_rustc_by_referenced_path_scope() {
88        let scope = ActiveObservationScope::from_target_contexts([TargetContext::new(
89            "/work/crate/target",
90        )
91        .with_build_root("/work/crate/target/debug")]);
92
93        let process = observed_process_from_parts(
94            "rustc",
95            &[
96                "rustc".to_string(),
97                "--out-dir".to_string(),
98                "target/debug/deps".to_string(),
99            ],
100            Some(Path::new("/work/crate")),
101        )
102        .expect("rustc should be detected");
103
104        assert_eq!(process.tool, CargoTool::Rustc);
105        assert_eq!(
106            process.referenced_paths,
107            vec![PathBuf::from("/work/crate/target/debug/deps")]
108        );
109        assert!(process_matches_scope(&process, &scope));
110    }
111
112    #[test]
113    fn sysinfo_parts_ignore_unrelated_processes() {
114        assert!(observed_process_from_parts("bash", &["bash".to_string()], None).is_none());
115    }
116}