use std::cmp;
use std::fmt;
use std::str::FromStr;
use target_lexicon::Architecture;
use thiserror::Error;
use tracing::trace;
pub use crate::arch::{Arch, ArchVariant};
pub use crate::libc::{Libc, LibcDetectionError, LibcVersion};
pub use crate::os::Os;
mod arch;
mod cpuinfo;
pub mod host;
mod libc;
mod os;
#[derive(Error, Debug)]
pub enum Error {
#[error("Unknown operating system: {0}")]
UnknownOs(String),
#[error("Unknown architecture: {0}")]
UnknownArch(String),
#[error("Unknown libc environment: {0}")]
UnknownLibc(String),
#[error("Unsupported variant `{0}` for architecture `{1}`")]
UnsupportedVariant(String, String),
#[error(transparent)]
LibcDetectionError(#[from] crate::libc::LibcDetectionError),
#[error("Invalid platform format: {0}")]
InvalidPlatformFormat(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Platform {
pub os: Os,
pub arch: Arch,
pub libc: Libc,
}
impl Platform {
pub fn new(os: Os, arch: Arch, libc: Libc) -> Self {
Self { os, arch, libc }
}
pub fn from_parts(os: &str, arch: &str, libc: &str) -> Result<Self, Error> {
Ok(Self {
os: Os::from_str(os)?,
arch: Arch::from_str(arch)?,
libc: Libc::from_str(libc)?,
})
}
pub fn from_env() -> Result<Self, Error> {
let os = Os::from_env();
let arch = Arch::from_env();
let libc = Libc::from_env()?;
Ok(Self { os, arch, libc })
}
pub fn supports(&self, other: &Self) -> bool {
if self == other {
return true;
}
if !self.os.supports(other.os) {
trace!(
"Operating system `{}` is not compatible with `{}`",
self.os, other.os
);
return false;
}
if self.libc != other.libc && !(other.os.is_emscripten() || self.os.is_emscripten()) {
trace!(
"Libc `{}` is not compatible with `{}`",
self.libc, other.libc
);
return false;
}
if self.arch == other.arch {
return true;
}
#[expect(clippy::unnested_or_patterns)]
if self.os.is_windows()
&& matches!(
(self.arch.family(), other.arch.family()),
(Architecture::X86_64, Architecture::X86_32(_)) |
(Architecture::Aarch64(_), Architecture::X86_64) |
(Architecture::Aarch64(_), Architecture::X86_32(_))
)
{
return true;
}
if self.os.is_macos()
&& matches!(
(self.arch.family(), other.arch.family()),
(Architecture::Aarch64(_), Architecture::X86_64)
)
{
return true;
}
if other.arch.is_wasm() {
return true;
}
if self.arch.family() != other.arch.family() {
return false;
}
true
}
pub fn as_cargo_dist_triple(&self) -> String {
use target_lexicon::{
Architecture, ArmArchitecture, Environment, OperatingSystem, Riscv64Architecture,
X86_32Architecture,
};
let Self { os, arch, libc } = &self;
let arch_name = match arch.family() {
Architecture::X86_32(X86_32Architecture::I686) => "i686".to_string(),
Architecture::Riscv64(Riscv64Architecture::Riscv64) => "riscv64gc".to_string(),
_ => arch.to_string(),
};
let vendor = match &**os {
OperatingSystem::Darwin(_) => "apple",
OperatingSystem::Windows => "pc",
_ => "unknown",
};
let os_name = match &**os {
OperatingSystem::Darwin(_) => "darwin",
_ => &os.to_string(),
};
let abi = match (&**os, libc) {
(OperatingSystem::Windows, _) => Some("msvc".to_string()),
(OperatingSystem::Linux, Libc::Some(env)) => Some({
if matches!(arch.family(), Architecture::Arm(ArmArchitecture::Armv7))
&& matches!(env, Environment::Gnu | Environment::Musl)
{
format!("{env}eabihf")
} else {
env.to_string()
}
}),
_ => None,
};
format!(
"{arch_name}-{vendor}-{os_name}{abi}",
abi = abi.map(|abi| format!("-{abi}")).unwrap_or_default()
)
}
}
impl fmt::Display for Platform {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}-{}-{}", self.os, self.arch, self.libc)
}
}
impl FromStr for Platform {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split('-').collect();
if parts.len() != 3 {
return Err(Error::InvalidPlatformFormat(format!(
"expected exactly 3 parts separated by '-', got {}",
parts.len()
)));
}
Self::from_parts(parts[0], parts[1], parts[2])
}
}
impl Ord for Platform {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.os
.to_string()
.cmp(&other.os.to_string())
.then_with(|| {
if self.arch.family == other.arch.family {
return self.arch.variant.cmp(&other.arch.variant);
}
let preferred = if self.os.is_windows() {
Arch {
family: target_lexicon::Architecture::X86_64,
variant: None,
}
} else {
Arch::from_env()
};
match (
self.arch.family == preferred.family,
other.arch.family == preferred.family,
) {
(true, true) => unreachable!(),
(true, false) => cmp::Ordering::Less,
(false, true) => cmp::Ordering::Greater,
(false, false) => {
self.arch
.family
.to_string()
.cmp(&other.arch.family.to_string())
}
}
})
.then_with(|| self.libc.to_string().cmp(&other.libc.to_string()))
}
}
impl PartialOrd for Platform {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<&uv_platform_tags::Platform> for Platform {
fn from(value: &uv_platform_tags::Platform) -> Self {
Self {
os: Os::from(value.os()),
arch: Arch::from(&value.arch()),
libc: Libc::from(value.os()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_platform_display() {
let platform = Platform {
os: Os::from_str("linux").unwrap(),
arch: Arch::from_str("x86_64").unwrap(),
libc: Libc::from_str("gnu").unwrap(),
};
assert_eq!(platform.to_string(), "linux-x86_64-gnu");
}
#[test]
fn test_platform_from_str() {
let platform = Platform::from_str("macos-aarch64-none").unwrap();
assert_eq!(platform.os.to_string(), "macos");
assert_eq!(platform.arch.to_string(), "aarch64");
assert_eq!(platform.libc.to_string(), "none");
}
#[test]
fn test_platform_from_parts() {
let platform = Platform::from_parts("linux", "x86_64", "gnu").unwrap();
assert_eq!(platform.os.to_string(), "linux");
assert_eq!(platform.arch.to_string(), "x86_64");
assert_eq!(platform.libc.to_string(), "gnu");
let platform = Platform::from_parts("linux", "x86_64_v3", "musl").unwrap();
assert_eq!(platform.os.to_string(), "linux");
assert_eq!(platform.arch.to_string(), "x86_64_v3");
assert_eq!(platform.libc.to_string(), "musl");
assert!(Platform::from_parts("invalid_os", "x86_64", "gnu").is_err());
assert!(Platform::from_parts("linux", "invalid_arch", "gnu").is_err());
assert!(Platform::from_parts("linux", "x86_64", "invalid_libc").is_err());
}
#[test]
fn test_platform_from_str_with_arch_variant() {
let platform = Platform::from_str("linux-x86_64_v3-gnu").unwrap();
assert_eq!(platform.os.to_string(), "linux");
assert_eq!(platform.arch.to_string(), "x86_64_v3");
assert_eq!(platform.libc.to_string(), "gnu");
}
#[test]
fn test_platform_from_str_error() {
assert!(Platform::from_str("linux-x86_64").is_err());
assert!(Platform::from_str("invalid").is_err());
assert!(Platform::from_str("linux-x86-64-gnu").is_err());
assert!(Platform::from_str("linux-x86_64-gnu-extra").is_err());
}
#[test]
fn test_platform_sorting_os_precedence() {
let linux = Platform::from_str("linux-x86_64-gnu").unwrap();
let macos = Platform::from_str("macos-x86_64-none").unwrap();
let windows = Platform::from_str("windows-x86_64-none").unwrap();
assert!(linux < macos);
assert!(macos < windows);
}
#[test]
fn test_platform_sorting_libc() {
let gnu = Platform::from_str("linux-x86_64-gnu").unwrap();
let musl = Platform::from_str("linux-x86_64-musl").unwrap();
assert!(gnu < musl);
}
#[test]
fn test_platform_sorting_arch_linux() {
use crate::arch::test_support::{aarch64, run_with_arch, x86_64};
let linux_x86_64 = Platform::from_str("linux-x86_64-gnu").unwrap();
let linux_aarch64 = Platform::from_str("linux-aarch64-gnu").unwrap();
run_with_arch(x86_64(), || {
assert!(linux_x86_64 < linux_aarch64);
});
run_with_arch(aarch64(), || {
assert!(linux_aarch64 < linux_x86_64);
});
}
#[test]
fn test_platform_sorting_arch_macos() {
use crate::arch::test_support::{aarch64, run_with_arch, x86_64};
let macos_x86_64 = Platform::from_str("macos-x86_64-none").unwrap();
let macos_aarch64 = Platform::from_str("macos-aarch64-none").unwrap();
run_with_arch(x86_64(), || {
assert!(macos_x86_64 < macos_aarch64);
});
run_with_arch(aarch64(), || {
assert!(macos_aarch64 < macos_x86_64);
});
}
#[test]
fn test_platform_supports() {
let native = Platform::from_str("linux-x86_64-gnu").unwrap();
let same = Platform::from_str("linux-x86_64-gnu").unwrap();
let different_arch = Platform::from_str("linux-aarch64-gnu").unwrap();
let different_os = Platform::from_str("macos-x86_64-none").unwrap();
let different_libc = Platform::from_str("linux-x86_64-musl").unwrap();
assert!(native.supports(&same));
assert!(!native.supports(&different_os));
assert!(!native.supports(&different_libc));
assert!(!native.supports(&different_arch));
let x86_64_v2 = Platform::from_str("linux-x86_64_v2-gnu").unwrap();
let x86_64_v3 = Platform::from_str("linux-x86_64_v3-gnu").unwrap();
assert_eq!(native.arch.family(), x86_64_v2.arch.family());
assert_eq!(native.arch.family(), x86_64_v3.arch.family());
assert!(native.supports(&x86_64_v2));
assert!(native.supports(&x86_64_v3));
}
#[test]
fn test_windows_aarch64_platform_sorting() {
let windows_x86_64 = Platform::from_str("windows-x86_64-none").unwrap();
let windows_aarch64 = Platform::from_str("windows-aarch64-none").unwrap();
assert!(windows_x86_64 < windows_aarch64);
let mut platforms = [
Platform::from_str("windows-aarch64-none").unwrap(),
Platform::from_str("windows-x86_64-none").unwrap(),
Platform::from_str("windows-x86-none").unwrap(),
];
platforms.sort();
assert_eq!(platforms[0].arch.to_string(), "x86_64");
assert_eq!(platforms[1].arch.to_string(), "aarch64");
assert_eq!(platforms[2].arch.to_string(), "x86");
}
#[test]
fn test_windows_sorting_always_prefers_x86_64() {
use crate::arch::test_support::{aarch64, run_with_arch, x86_64};
let windows_x86_64 = Platform::from_str("windows-x86_64-none").unwrap();
let windows_aarch64 = Platform::from_str("windows-aarch64-none").unwrap();
run_with_arch(aarch64(), || {
assert!(windows_x86_64 < windows_aarch64);
});
run_with_arch(x86_64(), || {
assert!(windows_x86_64 < windows_aarch64);
});
}
#[test]
fn test_windows_aarch64_supports() {
let windows_aarch64 = Platform::from_str("windows-aarch64-none").unwrap();
let windows_x86_64 = Platform::from_str("windows-x86_64-none").unwrap();
assert!(windows_aarch64.supports(&windows_x86_64));
assert!(!windows_x86_64.supports(&windows_aarch64));
assert!(windows_aarch64.supports(&windows_aarch64));
assert!(windows_x86_64.supports(&windows_x86_64));
}
#[test]
fn test_from_platform_tags_platform() {
let tags_platform = uv_platform_tags::Platform::new(
uv_platform_tags::Os::Windows,
uv_platform_tags::Arch::X86_64,
);
let platform = Platform::from(&tags_platform);
assert_eq!(platform.os.to_string(), "windows");
assert_eq!(platform.arch.to_string(), "x86_64");
assert_eq!(platform.libc.to_string(), "none");
let tags_platform_linux = uv_platform_tags::Platform::new(
uv_platform_tags::Os::Manylinux {
major: 2,
minor: 17,
},
uv_platform_tags::Arch::Aarch64,
);
let platform_linux = Platform::from(&tags_platform_linux);
assert_eq!(platform_linux.os.to_string(), "linux");
assert_eq!(platform_linux.arch.to_string(), "aarch64");
assert_eq!(platform_linux.libc.to_string(), "gnu");
}
#[test]
fn test_as_cargo_dist_triple_armv7_libc_handling() {
for (arch, libc, expected) in [
("armv7", "gnueabihf", "armv7-unknown-linux-gnueabihf"),
("armv7", "gnueabi", "armv7-unknown-linux-gnueabi"),
("armv7", "musleabihf", "armv7-unknown-linux-musleabihf"),
("armv7", "musleabi", "armv7-unknown-linux-musleabi"),
("armv7", "gnu", "armv7-unknown-linux-gnueabihf"),
("armv7", "musl", "armv7-unknown-linux-musleabihf"),
("aarch64", "gnu", "aarch64-unknown-linux-gnu"),
("x86_64", "musl", "x86_64-unknown-linux-musl"),
] {
let platform = Platform::from_parts("linux", arch, libc).unwrap();
assert_eq!(
platform.as_cargo_dist_triple(),
expected,
"linux-{arch}-{libc}"
);
}
}
}