system_env/
env.rs

1use crate::helpers::find_command_on_path;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::env::consts;
5use std::fmt;
6use std::process::Command;
7
8/// Architecture of the system environment.
9#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
10#[cfg_attr(feature = "schematic", derive(schematic::Schematic))]
11#[serde(rename_all = "lowercase")]
12pub enum SystemArch {
13    X86,
14    #[serde(alias = "x86_64")]
15    X64,
16    Arm,
17    #[serde(alias = "aarch64")]
18    Arm64,
19    #[serde(alias = "loongarch64")]
20    LongArm64,
21    M68k,
22    Mips,
23    Mips64,
24    Powerpc,
25    Powerpc64,
26    Riscv64,
27    S390x,
28    Sparc64,
29}
30
31impl SystemArch {
32    /// Return an instance derived from [`std::env::costs::ARCH`].
33    pub fn from_env() -> SystemArch {
34        serde_json::from_value(Value::String(consts::ARCH.to_owned()))
35            .expect("Unknown architecture!")
36    }
37
38    /// Convert to a [`std::env::costs::ARCH`] compatible string.
39    pub fn to_rust_arch(&self) -> String {
40        match self {
41            Self::X64 => "x86_64".into(),
42            Self::Arm64 => "aarch64".into(),
43            Self::LongArm64 => "loongarch64".into(),
44            _ => self.to_string(),
45        }
46    }
47}
48
49impl Default for SystemArch {
50    #[cfg(target_arch = "wasm32")]
51    fn default() -> Self {
52        SystemArch::X64
53    }
54
55    #[cfg(not(target_arch = "wasm32"))]
56    fn default() -> Self {
57        SystemArch::from_env()
58    }
59}
60
61impl fmt::Display for SystemArch {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "{}", format!("{self:?}").to_lowercase())
64    }
65}
66
67/// Operating system of the current environment.
68#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
69#[cfg_attr(feature = "schematic", derive(schematic::Schematic))]
70#[serde(rename_all = "lowercase")]
71pub enum SystemOS {
72    Android,
73    Dragonfly,
74    FreeBSD,
75    IOS,
76    Linux,
77    #[serde(alias = "mac")]
78    MacOS,
79    NetBSD,
80    OpenBSD,
81    Solaris,
82    Windows,
83}
84
85impl SystemOS {
86    /// Return an instance derived from [`std::env::costs::OS`].
87    pub fn from_env() -> SystemOS {
88        serde_json::from_value(Value::String(consts::OS.to_owned()))
89            .expect("Unknown operating system!")
90    }
91
92    /// Return either a Unix or Windows value based on the current native system.
93    pub fn for_native<'value, T: AsRef<str> + ?Sized>(
94        &self,
95        unix: &'value T,
96        windows: &'value T,
97    ) -> &'value str {
98        if self.is_windows() {
99            windows.as_ref()
100        } else {
101            unix.as_ref()
102        }
103    }
104
105    /// Return the provided name as a system formatted file name for executables.
106    /// On Windows this will append an ".exe" extension. On Unix, no extension.
107    pub fn get_exe_name(&self, name: impl AsRef<str>) -> String {
108        self.get_file_name(name, "exe")
109    }
110
111    /// Return the provided file name formatted with the extension (without dot)
112    /// when on Windows. On Unix, returns the name as-is.
113    pub fn get_file_name(&self, name: impl AsRef<str>, windows_ext: impl AsRef<str>) -> String {
114        let name = name.as_ref();
115        let ext = windows_ext.as_ref();
116
117        if self.is_windows() && !name.ends_with(ext) {
118            format!("{name}.{ext}")
119        } else {
120            name.to_owned()
121        }
122    }
123
124    /// Return true if in the BSD family.
125    pub fn is_bsd(&self) -> bool {
126        matches!(
127            self,
128            Self::Dragonfly | Self::FreeBSD | Self::NetBSD | Self::OpenBSD
129        )
130    }
131
132    /// Return true if Linux.
133    pub fn is_linux(&self) -> bool {
134        matches!(self, Self::Linux)
135    }
136
137    /// Return true if MacOS.
138    pub fn is_mac(&self) -> bool {
139        matches!(self, Self::MacOS)
140    }
141
142    /// Return true if a Unix based OS.
143    pub fn is_unix(&self) -> bool {
144        self.is_bsd() || matches!(self, Self::Linux | Self::MacOS)
145    }
146
147    /// Return true if Windows.
148    pub fn is_windows(&self) -> bool {
149        matches!(self, Self::Windows)
150    }
151
152    /// Convert to a [`std::env::costs::OS`] compatible string.
153    pub fn to_rust_os(&self) -> String {
154        self.to_string()
155    }
156}
157
158impl Default for SystemOS {
159    #[cfg(target_arch = "wasm32")]
160    fn default() -> Self {
161        SystemOS::Linux
162    }
163
164    #[cfg(not(target_arch = "wasm32"))]
165    fn default() -> Self {
166        SystemOS::from_env()
167    }
168}
169
170impl fmt::Display for SystemOS {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        write!(f, "{}", format!("{self:?}").to_lowercase())
173    }
174}
175
176/// Libc being used in the system environment.
177#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
178#[cfg_attr(feature = "schematic", derive(schematic::Schematic))]
179#[serde(rename_all = "lowercase")]
180pub enum SystemLibc {
181    Gnu,
182    Musl,
183    #[default]
184    Unknown,
185}
186
187impl SystemLibc {
188    /// Detect the libc type from the current system environment.
189    pub fn detect(os: SystemOS) -> Self {
190        match os {
191            SystemOS::IOS | SystemOS::MacOS => Self::Gnu,
192            SystemOS::Windows => Self::Unknown,
193            _ => {
194                if Self::is_musl() {
195                    Self::Musl
196                } else {
197                    Self::Gnu
198                }
199            }
200        }
201    }
202
203    /// Check if musl is available on the current machine, by running the
204    /// `ldd --version` command, or the `uname` command. This will return false
205    /// on systems that have neither of those commands.
206    pub fn is_musl() -> bool {
207        let mut command = if let Some(ldd_path) = find_command_on_path("ldd") {
208            let mut cmd = Command::new(ldd_path);
209            cmd.arg("--version");
210            cmd
211        } else if let Some(uname_path) = find_command_on_path("uname") {
212            Command::new(uname_path)
213        } else {
214            return false;
215        };
216
217        if let Ok(result) = command.output() {
218            let output = if result.status.success() {
219                String::from_utf8_lossy(&result.stdout).to_lowercase()
220            } else {
221                // ldd on apline returns stderr with a 1 exit code
222                String::from_utf8_lossy(&result.stderr).to_lowercase()
223            };
224
225            return output.contains("musl") || output.contains("alpine");
226        }
227
228        false
229    }
230}
231
232impl fmt::Display for SystemLibc {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        write!(f, "{}", format!("{self:?}").to_lowercase())
235    }
236}