use std::sync::OnceLock;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
mod unknown;
#[cfg(target_os = "windows")]
mod windows;
#[cfg(target_os = "linux")]
use self::linux as platform;
#[cfg(target_os = "macos")]
use self::macos as platform;
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
use self::unknown as platform;
#[cfg(target_os = "windows")]
use self::windows as platform;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum OsFamily {
Unix,
Windows,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum OsKind {
Linux,
Macos,
Windows,
FreeBsd,
OpenBsd,
NetBsd,
Other(&'static str),
}
impl OsKind {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
OsKind::Linux => "linux",
OsKind::Macos => "macos",
OsKind::Windows => "windows",
OsKind::FreeBsd => "freebsd",
OsKind::OpenBsd => "openbsd",
OsKind::NetBsd => "netbsd",
OsKind::Other(name) => name,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Arch {
X86,
X86_64,
Aarch64,
Arm,
Riscv64,
Wasm32,
Other(&'static str),
}
impl Arch {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Arch::X86 => "x86",
Arch::X86_64 => "x86_64",
Arch::Aarch64 => "aarch64",
Arch::Arm => "arm",
Arch::Riscv64 => "riscv64",
Arch::Wasm32 => "wasm32",
Arch::Other(name) => name,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Endianness {
Little,
Big,
}
#[derive(Debug, Clone)]
pub struct OsInfo {
pub family: OsFamily,
pub kind: OsKind,
pub distro: Option<String>,
pub version: String,
pub arch: Arch,
pub endianness: Endianness,
pub page_size: usize,
}
static OS_INFO: OnceLock<OsInfo> = OnceLock::new();
#[must_use]
pub fn info() -> &'static OsInfo {
OS_INFO.get_or_init(probe)
}
#[must_use]
pub fn name() -> &'static str {
info().kind.as_str()
}
#[must_use]
pub const fn is_linux() -> bool {
cfg!(target_os = "linux")
}
#[must_use]
pub const fn is_macos() -> bool {
cfg!(target_os = "macos")
}
#[must_use]
pub const fn is_windows() -> bool {
cfg!(target_os = "windows")
}
fn probe() -> OsInfo {
OsInfo {
family: current_family(),
kind: current_kind(),
distro: platform::probe_distro(),
version: platform::probe_version(),
arch: current_arch(),
endianness: current_endianness(),
page_size: default_page_size(),
}
}
fn current_family() -> OsFamily {
if cfg!(unix) {
OsFamily::Unix
} else if cfg!(windows) {
OsFamily::Windows
} else {
OsFamily::Other
}
}
fn current_kind() -> OsKind {
if cfg!(target_os = "linux") {
OsKind::Linux
} else if cfg!(target_os = "macos") {
OsKind::Macos
} else if cfg!(target_os = "windows") {
OsKind::Windows
} else if cfg!(target_os = "freebsd") {
OsKind::FreeBsd
} else if cfg!(target_os = "openbsd") {
OsKind::OpenBsd
} else if cfg!(target_os = "netbsd") {
OsKind::NetBsd
} else {
OsKind::Other(std::env::consts::OS)
}
}
fn current_arch() -> Arch {
if cfg!(target_arch = "x86_64") {
Arch::X86_64
} else if cfg!(target_arch = "x86") {
Arch::X86
} else if cfg!(target_arch = "aarch64") {
Arch::Aarch64
} else if cfg!(target_arch = "arm") {
Arch::Arm
} else if cfg!(target_arch = "riscv64") {
Arch::Riscv64
} else if cfg!(target_arch = "wasm32") {
Arch::Wasm32
} else {
Arch::Other(std::env::consts::ARCH)
}
}
fn current_endianness() -> Endianness {
if cfg!(target_endian = "little") {
Endianness::Little
} else {
Endianness::Big
}
}
fn default_page_size() -> usize {
#[cfg(unix)]
{
let v = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
if v > 0 {
return v as usize;
}
}
#[cfg(target_os = "windows")]
{
use windows_sys::Win32::System::SystemInformation::{GetSystemInfo, SYSTEM_INFO};
let mut info: SYSTEM_INFO = unsafe { std::mem::zeroed() };
unsafe { GetSystemInfo(&mut info) };
let p = info.dwPageSize as usize;
if p > 0 {
return p;
}
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
16_384
}
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
{
4_096
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_info_returns_non_empty_version() {
let os = info();
assert!(!os.version.is_empty());
}
#[test]
fn test_info_caches_same_reference_on_repeat_call() {
let a = info() as *const OsInfo;
let b = info() as *const OsInfo;
assert_eq!(a, b);
}
#[test]
fn test_name_returns_lowercase_kind_string() {
let n = name();
assert!(!n.is_empty());
assert_eq!(n, n.to_lowercase());
}
#[test]
fn test_kind_as_str_round_trips_known_variants() {
assert_eq!(OsKind::Linux.as_str(), "linux");
assert_eq!(OsKind::Macos.as_str(), "macos");
assert_eq!(OsKind::Windows.as_str(), "windows");
assert_eq!(OsKind::FreeBsd.as_str(), "freebsd");
assert_eq!(OsKind::OpenBsd.as_str(), "openbsd");
assert_eq!(OsKind::NetBsd.as_str(), "netbsd");
assert_eq!(OsKind::Other("haiku").as_str(), "haiku");
}
#[test]
fn test_arch_as_str_round_trips_known_variants() {
assert_eq!(Arch::X86_64.as_str(), "x86_64");
assert_eq!(Arch::X86.as_str(), "x86");
assert_eq!(Arch::Aarch64.as_str(), "aarch64");
assert_eq!(Arch::Arm.as_str(), "arm");
assert_eq!(Arch::Riscv64.as_str(), "riscv64");
assert_eq!(Arch::Wasm32.as_str(), "wasm32");
assert_eq!(Arch::Other("loongarch64").as_str(), "loongarch64");
}
#[test]
fn test_predicates_are_mutually_exclusive() {
let n = u8::from(is_linux()) + u8::from(is_macos()) + u8::from(is_windows());
assert!(n <= 1, "at most one OS predicate may be true at a time");
}
#[test]
fn test_page_size_is_a_power_of_two() {
let p = info().page_size;
assert!(p > 0);
assert_eq!(p & (p - 1), 0, "page size must be a power of two");
}
#[test]
fn test_endianness_matches_target_endian_cfg() {
let info = info();
if cfg!(target_endian = "little") {
assert_eq!(info.endianness, Endianness::Little);
} else {
assert_eq!(info.endianness, Endianness::Big);
}
}
#[test]
fn test_kind_matches_active_target_os() {
let info = info();
if cfg!(target_os = "linux") {
assert_eq!(info.kind, OsKind::Linux);
} else if cfg!(target_os = "macos") {
assert_eq!(info.kind, OsKind::Macos);
} else if cfg!(target_os = "windows") {
assert_eq!(info.kind, OsKind::Windows);
}
}
#[test]
fn test_distro_is_none_on_non_linux() {
if !cfg!(target_os = "linux") {
assert!(info().distro.is_none());
}
}
}