#![deny(missing_docs)]
pub mod cuda;
pub mod libc;
pub mod linux;
pub mod osx;
pub mod win;
use std::{
borrow::Cow,
env, fmt,
fmt::Display,
hash::{Hash, Hasher},
str::FromStr,
sync::Arc,
};
use archspec::cpu::Microarchitecture;
use libc::DetectLibCError;
use linux::ParseLinuxVersionError;
use rattler_conda_types::{
GenericVirtualPackage, PackageName, ParseVersionError, Platform, Version,
};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::osx::ParseOsxVersionError;
#[derive(Clone, Debug, PartialEq, Default)]
pub enum Override {
#[default]
DefaultEnvVar,
EnvVar(String),
String(String),
}
pub trait EnvOverride: Sized {
fn parse_version(value: &str) -> Result<Self, ParseVersionError>;
fn parse_version_opt(value: &str) -> Result<Option<Self>, DetectVirtualPackageError> {
if value.is_empty() {
Ok(None)
} else {
Ok(Some(Self::parse_version(value)?))
}
}
fn from_env_var_name_or<F>(
env_var_name: &str,
fallback: F,
) -> Result<Option<Self>, DetectVirtualPackageError>
where
F: FnOnce() -> Result<Option<Self>, DetectVirtualPackageError>,
{
match env::var(env_var_name) {
Ok(var) => Self::parse_version_opt(&var),
Err(env::VarError::NotPresent) => fallback(),
Err(e) => Err(DetectVirtualPackageError::VarError(e)),
}
}
const DEFAULT_ENV_NAME: &'static str;
fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError>;
fn detect_with_fallback<F>(
ov: &Override,
fallback: F,
) -> Result<Option<Self>, DetectVirtualPackageError>
where
F: FnOnce() -> Result<Option<Self>, DetectVirtualPackageError>,
{
match ov {
Override::String(str) => Self::parse_version_opt(str),
Override::DefaultEnvVar => Self::from_env_var_name_or(Self::DEFAULT_ENV_NAME, fallback),
Override::EnvVar(name) => Self::from_env_var_name_or(name, fallback),
}
}
fn detect(ov: Option<&Override>) -> Result<Option<Self>, DetectVirtualPackageError> {
ov.map_or_else(Self::detect_from_host, |ov| {
Self::detect_with_fallback(ov, Self::detect_from_host)
})
}
}
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub enum VirtualPackage {
Win(Windows),
Unix,
Linux(Linux),
Osx(Osx),
LibC(LibC),
Cuda(Cuda),
Archspec(Archspec),
}
#[derive(Debug, Clone, Default)]
pub struct VirtualPackages {
pub win: Option<Windows>,
pub unix: bool,
pub linux: Option<Linux>,
pub osx: Option<Osx>,
pub libc: Option<LibC>,
pub cuda: Option<Cuda>,
pub archspec: Option<Archspec>,
}
impl VirtualPackages {
pub fn into_virtual_packages(self) -> impl Iterator<Item = VirtualPackage> {
let Self {
win,
unix,
linux,
osx,
libc,
cuda,
archspec,
} = self;
[
win.map(VirtualPackage::Win),
unix.then_some(VirtualPackage::Unix),
linux.map(VirtualPackage::Linux),
osx.map(VirtualPackage::Osx),
libc.map(VirtualPackage::LibC),
cuda.map(VirtualPackage::Cuda),
archspec.map(VirtualPackage::Archspec),
]
.into_iter()
.flatten()
}
pub fn into_generic_virtual_packages(self) -> impl Iterator<Item = GenericVirtualPackage> {
self.into_virtual_packages().map(Into::into)
}
pub fn detect(overrides: &VirtualPackageOverrides) -> Result<Self, DetectVirtualPackageError> {
Ok(Self {
win: Windows::detect(overrides.win.as_ref())?,
unix: Platform::current().is_unix(),
linux: Linux::detect(overrides.linux.as_ref())?,
osx: Osx::detect(overrides.osx.as_ref())?,
libc: LibC::detect(overrides.libc.as_ref())?,
cuda: Cuda::detect(overrides.cuda.as_ref())?,
archspec: Archspec::detect(overrides.archspec.as_ref())?,
})
}
pub fn detect_for_platform(
platform: Platform,
overrides: &VirtualPackageOverrides,
) -> Result<Self, DetectVirtualPackageError> {
let virtual_packages = Self::detect(overrides)?;
if platform == Platform::current() {
Ok(virtual_packages)
} else {
let win = if platform.is_windows() {
virtual_packages
.win
.or_else(|| Some(Windows { version: None }))
} else {
None
};
let linux = if platform.is_linux() {
virtual_packages.linux.or_else(|| {
Some(Linux {
version: Version::major(0),
})
})
} else {
None
};
let osx = if platform.is_osx() {
virtual_packages.osx.or_else(|| {
Some(Osx {
version: Version::major(0),
})
})
} else {
None
};
let libc = if platform.is_linux() {
virtual_packages.libc.or_else(|| {
Some(LibC {
family: "glibc".into(),
version: Version::major(0),
})
})
} else {
None
};
let archspec = Archspec::detect_with_fallback(
overrides
.archspec
.as_ref()
.unwrap_or(&Override::DefaultEnvVar),
|| Ok(Archspec::from_platform(platform)),
)?;
Ok(Self {
win,
unix: platform.is_unix(),
linux,
osx,
libc,
cuda: virtual_packages.cuda,
archspec,
})
}
}
}
impl From<VirtualPackage> for GenericVirtualPackage {
fn from(package: VirtualPackage) -> Self {
match package {
VirtualPackage::Unix => GenericVirtualPackage {
name: PackageName::new_unchecked("__unix"),
version: Version::major(0),
build_string: "0".into(),
},
VirtualPackage::Win(windows) => windows.into(),
VirtualPackage::Linux(linux) => linux.into(),
VirtualPackage::Osx(osx) => osx.into(),
VirtualPackage::LibC(libc) => libc.into(),
VirtualPackage::Cuda(cuda) => cuda.into(),
VirtualPackage::Archspec(spec) => spec.into(),
}
}
}
impl VirtualPackage {
#[deprecated(
since = "1.1.0",
note = "Use `VirtualPackage::detect(&VirtualPackageOverrides::default())` instead."
)]
pub fn current() -> Result<Vec<Self>, DetectVirtualPackageError> {
Self::detect(&VirtualPackageOverrides::default())
}
pub fn detect(
overrides: &VirtualPackageOverrides,
) -> Result<Vec<Self>, DetectVirtualPackageError> {
Ok(VirtualPackages::detect(overrides)?
.into_virtual_packages()
.collect())
}
}
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum DetectVirtualPackageError {
#[error(transparent)]
ParseLinuxVersion(#[from] ParseLinuxVersionError),
#[error(transparent)]
ParseMacOsVersion(#[from] ParseOsxVersionError),
#[error(transparent)]
DetectLibC(#[from] DetectLibCError),
#[error(transparent)]
VarError(#[from] env::VarError),
#[error(transparent)]
VersionParseError(#[from] ParseVersionError),
}
#[derive(Default, Clone, Debug)]
pub struct VirtualPackageOverrides {
pub win: Option<Override>,
pub osx: Option<Override>,
pub linux: Option<Override>,
pub libc: Option<Override>,
pub cuda: Option<Override>,
pub archspec: Option<Override>,
}
impl VirtualPackageOverrides {
pub fn all(ov: Override) -> Self {
Self {
win: Some(ov.clone()),
osx: Some(ov.clone()),
linux: Some(ov.clone()),
libc: Some(ov.clone()),
cuda: Some(ov.clone()),
archspec: Some(ov),
}
}
pub fn from_env() -> Self {
Self::all(Override::DefaultEnvVar)
}
}
#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
pub struct Linux {
pub version: Version,
}
impl Linux {
pub fn current() -> Result<Option<Self>, ParseLinuxVersionError> {
Ok(linux::linux_version()?.map(|version| Self { version }))
}
}
impl From<Linux> for GenericVirtualPackage {
fn from(linux: Linux) -> Self {
GenericVirtualPackage {
name: PackageName::new_unchecked("__linux"),
version: linux.version,
build_string: "0".into(),
}
}
}
impl From<Linux> for VirtualPackage {
fn from(linux: Linux) -> Self {
VirtualPackage::Linux(linux)
}
}
impl From<Version> for Linux {
fn from(version: Version) -> Self {
Linux { version }
}
}
impl EnvOverride for Linux {
const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_LINUX";
fn parse_version(env_var_value: &str) -> Result<Self, ParseVersionError> {
Version::from_str(env_var_value).map(Self::from)
}
fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
Ok(Self::current()?)
}
}
#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
pub struct LibC {
pub family: String,
pub version: Version,
}
impl LibC {
pub fn current() -> Result<Option<Self>, DetectLibCError> {
Ok(libc::libc_family_and_version()?.map(|(family, version)| Self { family, version }))
}
}
#[allow(clippy::fallible_impl_from)]
impl From<LibC> for GenericVirtualPackage {
fn from(libc: LibC) -> Self {
GenericVirtualPackage {
name: format!(
"__{}",
libc.family.to_lowercase().replace(
|c: char| !c.is_ascii_alphanumeric() && c != '-' && c != '_',
"_"
)
)
.try_into()
.unwrap(),
version: libc.version,
build_string: "0".into(),
}
}
}
impl From<LibC> for VirtualPackage {
fn from(libc: LibC) -> Self {
VirtualPackage::LibC(libc)
}
}
impl EnvOverride for LibC {
const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_GLIBC";
fn parse_version(env_var_value: &str) -> Result<Self, ParseVersionError> {
Version::from_str(env_var_value).map(|version| Self {
family: "glibc".into(),
version,
})
}
fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
Ok(Self::current()?)
}
}
impl fmt::Display for LibC {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}={}", self.family, self.version)
}
}
#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
pub struct Cuda {
pub version: Version,
}
impl Cuda {
pub fn current() -> Option<Self> {
cuda::cuda_version().map(|version| Self { version })
}
}
impl From<Version> for Cuda {
fn from(version: Version) -> Self {
Self { version }
}
}
impl EnvOverride for Cuda {
fn parse_version(env_var_value: &str) -> Result<Self, ParseVersionError> {
Version::from_str(env_var_value).map(|version| Self { version })
}
fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
Ok(Self::current())
}
const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_CUDA";
}
impl From<Cuda> for GenericVirtualPackage {
fn from(cuda: Cuda) -> Self {
GenericVirtualPackage {
name: PackageName::new_unchecked("__cuda"),
version: cuda.version,
build_string: "0".into(),
}
}
}
impl From<Cuda> for VirtualPackage {
fn from(cuda: Cuda) -> Self {
VirtualPackage::Cuda(cuda)
}
}
#[derive(Clone, Debug)]
pub enum Archspec {
Microarchitecture(Arc<Microarchitecture>),
Unknown,
}
impl Serialize for Archspec {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.as_str().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Archspec {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let name = Cow::<'de, str>::deserialize(deserializer)?;
if name == "0" {
Ok(Self::Unknown)
} else {
Ok(Self::from_name(&name))
}
}
}
impl Hash for Archspec {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl PartialEq<Self> for Archspec {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for Archspec {}
impl From<Arc<Microarchitecture>> for Archspec {
fn from(arch: Arc<Microarchitecture>) -> Self {
Self::Microarchitecture(arch)
}
}
impl Display for Archspec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl Archspec {
pub fn as_str(&self) -> &str {
match self {
Archspec::Microarchitecture(arch) => arch.name(),
Archspec::Unknown => "0",
}
}
pub fn current() -> Self {
archspec::cpu::host()
.ok()
.map(Into::into)
.or_else(|| Self::from_platform(Platform::current()))
.unwrap_or(Archspec::Unknown)
}
#[allow(clippy::match_same_arms)]
pub fn from_platform(platform: Platform) -> Option<Self> {
let archspec_name = match platform {
Platform::NoArch | Platform::Unknown => return None,
Platform::EmscriptenWasm32 | Platform::WasiWasm32 => return None,
Platform::Win32 | Platform::Linux32 => "x86",
Platform::Win64 | Platform::Osx64 | Platform::Linux64 => "x86_64",
Platform::LinuxAarch64 | Platform::LinuxArmV6l | Platform::LinuxArmV7l => "aarch64",
Platform::LinuxLoongArch64 => "loongarch64",
Platform::LinuxPpc64le => "ppc64le",
Platform::LinuxPpc64 => "ppc64",
Platform::LinuxPpc => "ppc",
Platform::LinuxS390X => "s390x",
Platform::LinuxRiscv32 => "riscv32",
Platform::LinuxRiscv64 => "riscv64",
Platform::ZosZ => return None,
Platform::WinArm64 => "aarch64",
Platform::OsxArm64 => "m1",
_ => return None,
};
Some(Self::from_name(archspec_name))
}
pub fn from_name(archspec_name: &str) -> Self {
Microarchitecture::known_targets()
.get(archspec_name)
.cloned()
.unwrap_or_else(|| Arc::new(archspec::cpu::Microarchitecture::generic(archspec_name)))
.into()
}
}
impl From<Archspec> for GenericVirtualPackage {
fn from(archspec: Archspec) -> Self {
GenericVirtualPackage {
name: PackageName::new_unchecked("__archspec"),
version: Version::major(1),
build_string: archspec.to_string(),
}
}
}
impl From<Archspec> for VirtualPackage {
fn from(archspec: Archspec) -> Self {
VirtualPackage::Archspec(archspec)
}
}
impl EnvOverride for Archspec {
fn parse_version(value: &str) -> Result<Self, ParseVersionError> {
if value == "0" {
Ok(Archspec::Unknown)
} else {
Ok(Self::from_name(value))
}
}
const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_ARCHSPEC";
fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
Ok(Some(Self::current()))
}
}
#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
pub struct Osx {
pub version: Version,
}
impl Osx {
pub fn current() -> Result<Option<Self>, ParseOsxVersionError> {
Ok(osx::osx_version()?.map(|version| Self { version }))
}
}
impl From<Osx> for GenericVirtualPackage {
fn from(osx: Osx) -> Self {
GenericVirtualPackage {
name: PackageName::new_unchecked("__osx"),
version: osx.version,
build_string: "0".into(),
}
}
}
impl From<Osx> for VirtualPackage {
fn from(osx: Osx) -> Self {
VirtualPackage::Osx(osx)
}
}
impl From<Version> for Osx {
fn from(version: Version) -> Self {
Self { version }
}
}
impl EnvOverride for Osx {
fn parse_version(env_var_value: &str) -> Result<Self, ParseVersionError> {
Version::from_str(env_var_value).map(|version| Self { version })
}
fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
Ok(Self::current()?)
}
const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_OSX";
}
#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
pub struct Windows {
pub version: Option<Version>,
}
impl Windows {
pub fn current() -> Option<Self> {
if cfg!(target_os = "windows") {
Some(Self {
version: win::windows_version(),
})
} else {
None
}
}
}
impl From<Windows> for GenericVirtualPackage {
fn from(windows: Windows) -> Self {
GenericVirtualPackage {
name: PackageName::new_unchecked("__win"),
version: windows.version.unwrap_or_else(|| Version::major(0)),
build_string: "0".into(),
}
}
}
impl From<Windows> for VirtualPackage {
fn from(windows: Windows) -> Self {
VirtualPackage::Win(windows)
}
}
impl From<Version> for Windows {
fn from(version: Version) -> Self {
Self {
version: Some(version),
}
}
}
impl EnvOverride for Windows {
fn parse_version(env_var_value: &str) -> Result<Self, ParseVersionError> {
Version::from_str(env_var_value).map(|version| Self {
version: Some(version),
})
}
fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
Ok(Self::current())
}
const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_WIN";
}
#[cfg(test)]
mod test {
use std::{env, str::FromStr};
use rattler_conda_types::Version;
use super::*;
#[test]
fn doesnt_crash() {
let virtual_packages =
VirtualPackages::detect(&VirtualPackageOverrides::default()).unwrap();
println!("{virtual_packages:#?}");
}
#[test]
fn parse_libc() {
let v = "1.23";
let res = LibC {
version: Version::from_str(v).unwrap(),
family: "glibc".into(),
};
let env_var_name = format!("{}_{}", LibC::DEFAULT_ENV_NAME, "12345511231");
unsafe {
env::set_var(env_var_name.clone(), v);
}
assert_eq!(
LibC::detect(Some(&Override::EnvVar(env_var_name.clone())))
.unwrap()
.unwrap(),
res
);
unsafe {
env::set_var(env_var_name.clone(), "");
}
assert_eq!(
LibC::detect(Some(&Override::EnvVar(env_var_name.clone()))).unwrap(),
None
);
unsafe {
env::remove_var(env_var_name.clone());
}
assert_eq!(
LibC::detect_with_fallback(&Override::DefaultEnvVar, || Ok(Some(res.clone())))
.unwrap()
.unwrap(),
res
);
assert_eq!(
LibC::detect_with_fallback(&Override::String(v.to_string()), || Ok(None))
.unwrap()
.unwrap(),
res
);
}
#[test]
fn parse_libc_invalid_family_chars() {
let libc = LibC {
family: "glibc 2.34 (Ubuntu)".into(),
version: Version::from_str("2.34").unwrap(),
};
let pkg: GenericVirtualPackage = libc.into();
assert_eq!(pkg.name.as_normalized(), "__glibc_2_34__ubuntu_");
}
#[test]
fn parse_cuda() {
let v = "1.234";
let res = Cuda {
version: Version::from_str(v).unwrap(),
};
let env_var_name = format!("{}_{}", Cuda::DEFAULT_ENV_NAME, "12345511231");
unsafe {
env::set_var(env_var_name.clone(), v);
}
assert_eq!(
Cuda::detect(Some(&Override::EnvVar(env_var_name.clone())))
.unwrap()
.unwrap(),
res
);
assert_eq!(
Cuda::detect(None).map_err(|_x| 1),
<Cuda as EnvOverride>::detect_from_host().map_err(|_x| 1)
);
unsafe {
env::remove_var(env_var_name.clone());
}
assert_eq!(
Cuda::detect(Some(&Override::String(v.to_string())))
.unwrap()
.unwrap(),
res
);
}
#[test]
fn parse_osx() {
let v = "2.345";
let res = Osx {
version: Version::from_str(v).unwrap(),
};
let env_var_name = format!("{}_{}", Osx::DEFAULT_ENV_NAME, "12345511231");
unsafe {
env::set_var(env_var_name.clone(), v);
}
assert_eq!(
Osx::detect(Some(&Override::EnvVar(env_var_name.clone())))
.unwrap()
.unwrap(),
res
);
}
#[test]
fn test_cross_platform_virtual_packages() {
let overrides = VirtualPackageOverrides::default();
let linux_packages =
VirtualPackages::detect_for_platform(Platform::Linux64, &overrides).unwrap();
let linux_names: Vec<String> = linux_packages
.into_generic_virtual_packages()
.map(|pkg| pkg.name.as_normalized().to_string())
.collect();
assert!(linux_names.contains(&"__linux".to_string()));
assert!(linux_names.contains(&"__glibc".to_string()));
assert!(linux_names.contains(&"__archspec".to_string()));
assert!(linux_names.contains(&"__unix".to_string()));
let osx_packages =
VirtualPackages::detect_for_platform(Platform::OsxArm64, &overrides).unwrap();
let osx_names: Vec<String> = osx_packages
.into_generic_virtual_packages()
.map(|pkg| pkg.name.as_normalized().to_string())
.collect();
assert!(osx_names.contains(&"__osx".to_string()));
assert!(osx_names.contains(&"__archspec".to_string()));
assert!(osx_names.contains(&"__unix".to_string()));
let win_packages =
VirtualPackages::detect_for_platform(Platform::Win64, &overrides).unwrap();
let win_names: Vec<String> = win_packages
.into_generic_virtual_packages()
.map(|pkg| pkg.name.as_normalized().to_string())
.collect();
assert!(win_names.contains(&"__win".to_string()));
assert!(!win_names.contains(&"__unix".to_string()));
assert!(win_names.contains(&"__archspec".to_string()));
}
}