system_env/
pm.rs

1use crate::error::Error;
2use crate::pm_vendor::*;
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6/// Package manager of the system environment.
7#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
8#[cfg_attr(feature = "schematic", derive(schematic::Schematic))]
9#[serde(rename_all = "kebab-case")]
10pub enum SystemPackageManager {
11    // BSD
12    Pkg,
13    Pkgin,
14
15    // Linux
16    Apk,
17    Apt,
18    Dnf,
19    Pacman,
20    Yum,
21
22    // MacOS
23    #[serde(alias = "homebrew")]
24    Brew,
25
26    // Windows
27    #[serde(alias = "chocolatey")]
28    Choco,
29    Scoop,
30
31    // Used for name indexing
32    #[serde(alias = "*")]
33    All,
34}
35
36impl SystemPackageManager {
37    /// Detect the package manager from the current system environment
38    /// using the following rules:
39    ///
40    /// - On Linux, parses `/etc/os-release`.
41    /// - On MacOS and BSD, checks for commands on `PATH`.
42    /// - On Windows, checks for programs on `PATH`, using `PATHEXT`.
43    pub fn detect() -> Result<Self, Error> {
44        #[cfg(target_os = "linux")]
45        {
46            let release = std::fs::read_to_string("/etc/os-release").unwrap_or_default();
47
48            if let Some(id) = release.lines().find(|l| l.starts_with("ID=")) {
49                return match id[3..].trim_matches('"') {
50                    "debian" | "ubuntu" | "pop-os" | "deepin" | "elementary OS" | "kali"
51                    | "linuxmint" => Ok(SystemPackageManager::Apt),
52                    "arch" | "manjaro" => Ok(SystemPackageManager::Pacman),
53                    "centos" | "redhat" | "rhel" => Ok(SystemPackageManager::Yum),
54                    "fedora" => Ok(SystemPackageManager::Dnf),
55                    "alpine" => Ok(SystemPackageManager::Apk),
56                    name => Err(Error::UnknownPackageManager(name.to_owned())),
57                };
58            }
59        }
60
61        #[cfg(any(
62            target_os = "dragonfly",
63            target_os = "freebsd",
64            target_os = "netbsd",
65            target_os = "openbsd"
66        ))]
67        {
68            use crate::is_command_on_path;
69
70            if is_command_on_path("pkg") {
71                return Ok(SystemPackageManager::Pkg);
72            }
73
74            if is_command_on_path("pkgin") {
75                return Ok(SystemPackageManager::Pkgin);
76            }
77        }
78
79        #[cfg(target_os = "macos")]
80        {
81            use crate::is_command_on_path;
82
83            if is_command_on_path("brew") {
84                return Ok(SystemPackageManager::Brew);
85            }
86        }
87
88        #[cfg(target_os = "windows")]
89        {
90            use crate::is_command_on_path;
91
92            if is_command_on_path("choco") {
93                return Ok(SystemPackageManager::Choco);
94            }
95
96            if is_command_on_path("scoop") {
97                return Ok(SystemPackageManager::Scoop);
98            }
99        }
100
101        Err(Error::MissingPackageManager)
102    }
103
104    /// Return vendor configuration for the current package manager.
105    pub fn get_config(&self) -> PackageManagerConfig {
106        match self {
107            Self::Apk => apk(),
108            Self::Apt => apt(),
109            Self::Dnf => dnf(),
110            Self::Pacman => pacman(),
111            Self::Pkg => pkg(),
112            Self::Pkgin => pkgin(),
113            Self::Yum => yum(),
114            Self::Brew => brew(),
115            Self::Choco => choco(),
116            Self::Scoop => scoop(),
117            Self::All => unreachable!(),
118        }
119    }
120
121    /// Return the command to use for elevated access. On Unix, this will use
122    /// "doas" or "sudo", and on Windows or WASM this does nothing.
123    pub fn get_elevated_command(&self) -> Option<&str> {
124        // Does not support sudo!
125        if matches!(self, Self::Brew | Self::All) {
126            return None;
127        }
128
129        #[cfg(unix)]
130        {
131            use crate::is_command_on_path;
132
133            if is_command_on_path("doas") {
134                Some("doas")
135            } else if is_command_on_path("sudo") {
136                Some("sudo")
137            } else {
138                None
139            }
140        }
141
142        #[cfg(any(windows, target_arch = "wasm32"))]
143        None
144    }
145}
146
147impl fmt::Display for SystemPackageManager {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        write!(f, "{}", format!("{self:?}").to_lowercase())
150    }
151}