#![deny(missing_docs)]
pub mod cuda;
pub mod defaults;
pub mod libc;
pub mod linux;
pub mod osx;
pub mod win;
use std::{
borrow::Cow,
env, fmt,
fmt::Display,
hash::{Hash, Hasher},
path::Path,
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(Debug, Clone, thiserror::Error)]
pub enum ParseVirtualPackageOverrideError {
#[error(transparent)]
ParseVersion(#[from] ParseVersionError),
#[error("{0}")]
ValidationError(String),
}
impl ParseVirtualPackageOverrideError {
pub fn validation_error(message: impl Into<String>) -> Self {
Self::ValidationError(message.into())
}
}
#[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, ParseVirtualPackageOverrideError>;
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)
})
}
fn detect_from_host_with_cache_dir(
cache_dir: Option<&Path>,
) -> Result<Option<Self>, DetectVirtualPackageError> {
let _ = cache_dir;
Self::detect_from_host()
}
fn detect_cached(
ov: Option<&Override>,
cache_dir: Option<&Path>,
) -> Result<Option<Self>, DetectVirtualPackageError> {
ov.map_or_else(
|| Self::detect_from_host_with_cache_dir(cache_dir),
|ov| {
Self::detect_with_fallback(ov, || Self::detect_from_host_with_cache_dir(cache_dir))
},
)
}
}
#[non_exhaustive]
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub enum VirtualPackage {
Win(Windows),
Unix,
Linux(Linux),
Osx(Osx),
Ios(Ios),
Android(Android),
LibC(LibC),
Cuda(Cuda),
CudaArch(CudaArch),
Archspec(Archspec),
}
#[non_exhaustive]
#[derive(Debug, Clone, Default)]
pub struct VirtualPackages {
pub win: Option<Windows>,
pub unix: bool,
pub linux: Option<Linux>,
pub osx: Option<Osx>,
pub ios: Option<Ios>,
pub android: Option<Android>,
pub libc: Option<LibC>,
pub cuda: Option<Cuda>,
pub cuda_arch: Option<CudaArch>,
pub archspec: Option<Archspec>,
}
impl VirtualPackages {
pub fn into_virtual_packages(self) -> impl Iterator<Item = VirtualPackage> {
let Self {
win,
unix,
linux,
osx,
ios,
android,
libc,
cuda,
cuda_arch,
archspec,
} = self;
[
win.map(VirtualPackage::Win),
unix.then_some(VirtualPackage::Unix),
linux.map(VirtualPackage::Linux),
osx.map(VirtualPackage::Osx),
ios.map(VirtualPackage::Ios),
android.map(VirtualPackage::Android),
libc.map(VirtualPackage::LibC),
cuda.map(VirtualPackage::Cuda),
cuda_arch.map(VirtualPackage::CudaArch),
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,
cache_dir: Option<&Path>,
) -> Result<Self, DetectVirtualPackageError> {
tracing::trace!(
cache_dir = %cache_dir.map_or_else(
|| "<disabled>".to_string(),
|path| path.display().to_string()
),
"detecting virtual packages"
);
let cuda = Cuda::detect_with_cache_dir(overrides.cuda.as_ref(), cache_dir)?;
tracing::trace!(?cuda, "detected CUDA virtual package");
let mut cuda_arch =
CudaArch::detect_with_cache_dir(overrides.cuda_arch.as_ref(), cache_dir)?;
tracing::trace!(?cuda_arch, "detected CUDA architecture virtual package");
if cuda.is_none() {
if cuda_arch.is_some() {
tracing::debug!("dropping __cuda_arch because __cuda was not detected");
}
cuda_arch = None;
}
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())?,
ios: Ios::detect(overrides.ios.as_ref())?,
android: Android::detect(overrides.android.as_ref())?,
libc: LibC::detect(overrides.libc.as_ref())?,
cuda,
cuda_arch,
archspec: Archspec::detect(overrides.archspec.as_ref())?,
})
}
pub fn detect_for_platform(
platform: Platform,
overrides: &VirtualPackageOverrides,
cache_dir: Option<&Path>,
) -> Result<Self, DetectVirtualPackageError> {
let virtual_packages = Self::detect(overrides, cache_dir)?;
if platform == Platform::current() {
return Ok(virtual_packages);
}
let Self {
win: baseline_win,
unix: baseline_unix,
linux: baseline_linux,
osx: baseline_osx,
ios: baseline_ios,
android: baseline_android,
libc: baseline_libc,
cuda: _,
cuda_arch: _,
archspec: baseline_archspec,
} = Self::baseline_for_platform(platform);
let win = platform
.is_windows()
.then(|| {
virtual_packages.win.or_else(|| {
let win = baseline_win?;
if let Some(version) = win.version.as_ref() {
log_default_virtual_package(
"__win",
platform,
version,
Windows::DEFAULT_ENV_NAME,
);
}
Some(win)
})
})
.flatten();
let linux = platform
.is_linux()
.then(|| {
virtual_packages.linux.or_else(|| {
let linux = baseline_linux?;
log_default_virtual_package(
"__linux",
platform,
&linux.version,
Linux::DEFAULT_ENV_NAME,
);
Some(linux)
})
})
.flatten();
let osx = platform
.is_osx()
.then(|| {
virtual_packages.osx.or_else(|| {
let osx = baseline_osx?;
log_default_virtual_package(
"__osx",
platform,
&osx.version,
Osx::DEFAULT_ENV_NAME,
);
Some(osx)
})
})
.flatten();
let libc = platform
.is_linux()
.then(|| {
virtual_packages.libc.or_else(|| {
let libc = baseline_libc?;
log_default_virtual_package(
"__glibc",
platform,
&libc.version,
LibC::DEFAULT_ENV_NAME,
);
Some(libc)
})
})
.flatten();
let archspec = Archspec::detect_with_fallback(
overrides
.archspec
.as_ref()
.unwrap_or(&Override::DefaultEnvVar),
|| Ok(baseline_archspec),
)?;
Ok(Self {
win,
unix: baseline_unix,
linux,
osx,
ios: platform
.is_ios()
.then(|| virtual_packages.ios.or(baseline_ios))
.flatten(),
android: platform
.is_android()
.then(|| virtual_packages.android.or(baseline_android))
.flatten(),
libc,
cuda: virtual_packages.cuda,
cuda_arch: virtual_packages.cuda_arch,
archspec,
})
}
pub fn baseline_for_platform(platform: Platform) -> Self {
Self {
win: platform.is_windows().then(|| Windows {
version: Some(defaults::default_windows_version()),
}),
unix: platform.is_unix(),
linux: platform.is_linux().then(|| Linux {
version: defaults::default_linux_version(),
}),
osx: platform
.is_osx()
.then(|| defaults::default_mac_os_version(platform).map(|version| Osx { version }))
.flatten(),
ios: platform.is_ios().then(|| Ios {
version: Version::major(0),
}),
android: platform.is_android().then(|| Android {
version: Version::major(0),
}),
libc: platform.is_linux().then(|| LibC {
family: "glibc".into(),
version: defaults::default_glibc_version(),
}),
cuda: None,
cuda_arch: None,
archspec: Archspec::from_platform(platform),
}
}
}
fn log_default_virtual_package(
name: &str,
platform: Platform,
version: &Version,
env_var_name: &str,
) {
tracing::info!(
"cannot detect the version of the virtual package '{name}' when targeting '{platform}', assuming version {version}; set the {env_var_name} environment variable to override"
);
}
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::Ios(ios) => ios.into(),
VirtualPackage::Android(android) => android.into(),
VirtualPackage::LibC(libc) => libc.into(),
VirtualPackage::Cuda(cuda) => cuda.into(),
VirtualPackage::CudaArch(cuda_arch) => cuda_arch.into(),
VirtualPackage::Archspec(spec) => spec.into(),
}
}
}
impl VirtualPackage {
#[deprecated(
since = "1.1.0",
note = "Use `VirtualPackage::detect(&VirtualPackageOverrides::default(), None)` instead."
)]
pub fn current() -> Result<Vec<Self>, DetectVirtualPackageError> {
Self::detect(&VirtualPackageOverrides::default(), None)
}
pub fn detect(
overrides: &VirtualPackageOverrides,
cache_dir: Option<&Path>,
) -> Result<Vec<Self>, DetectVirtualPackageError> {
Ok(VirtualPackages::detect(overrides, cache_dir)?
.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),
#[error(transparent)]
ParseOverride(#[from] ParseVirtualPackageOverrideError),
}
#[non_exhaustive]
#[derive(Default, Clone, Debug)]
pub struct VirtualPackageOverrides {
pub win: Option<Override>,
pub osx: Option<Override>,
pub ios: Option<Override>,
pub android: Option<Override>,
pub linux: Option<Override>,
pub libc: Option<Override>,
pub cuda: Option<Override>,
pub cuda_arch: Option<Override>,
pub archspec: Option<Override>,
}
impl VirtualPackageOverrides {
pub fn all(ov: Override) -> Self {
Self {
win: Some(ov.clone()),
osx: Some(ov.clone()),
ios: Some(ov.clone()),
android: Some(ov.clone()),
linux: Some(ov.clone()),
libc: Some(ov.clone()),
cuda: Some(ov.clone()),
cuda_arch: 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, ParseVirtualPackageOverrideError> {
Ok(Self::from(Version::from_str(env_var_value)?))
}
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, ParseVirtualPackageOverrideError> {
Ok(Self {
family: "glibc".into(),
version: Version::from_str(env_var_value)?,
})
}
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(cache_dir: Option<&Path>) -> Option<Self> {
cuda::cuda_version(cache_dir).map(|version| Self { version })
}
pub fn detect_with_cache_dir(
ov: Option<&Override>,
cache_dir: Option<&Path>,
) -> Result<Option<Self>, DetectVirtualPackageError> {
<Self as EnvOverride>::detect_cached(ov, cache_dir)
}
}
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, ParseVirtualPackageOverrideError> {
Ok(Self {
version: Version::from_str(env_var_value)?,
})
}
fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
Ok(Self::current(None))
}
fn detect_from_host_with_cache_dir(
cache_dir: Option<&Path>,
) -> Result<Option<Self>, DetectVirtualPackageError> {
Ok(Self::current(cache_dir))
}
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, Eq, PartialEq, Hash, Debug, Deserialize)]
pub struct CudaArch {
pub version: Version,
}
impl CudaArch {
pub fn current(cache_dir: Option<&Path>) -> Option<Self> {
cuda::cuda_arch(cache_dir).map(|arch_info| Self {
version: Version::from_str(&format!("{}.{}", arch_info.major, arch_info.minor))
.unwrap_or_else(|_| Version::major(u64::from(arch_info.major))),
})
}
pub fn detect_with_cache_dir(
ov: Option<&Override>,
cache_dir: Option<&Path>,
) -> Result<Option<Self>, DetectVirtualPackageError> {
<Self as EnvOverride>::detect_cached(ov, cache_dir)
}
}
impl EnvOverride for CudaArch {
fn parse_version(env_var_value: &str) -> Result<Self, ParseVirtualPackageOverrideError> {
if !cuda::is_valid_cuda_version_format(env_var_value) {
return Err(ParseVirtualPackageOverrideError::validation_error(format!(
"invalid CUDA compute capability format '{env_var_value}': expected 'major.minor' where both are digits (e.g., '8.6')"
)));
}
let version = Version::from_str(env_var_value)?;
Ok(Self { version })
}
fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
Ok(Self::current(None))
}
fn detect_from_host_with_cache_dir(
cache_dir: Option<&Path>,
) -> Result<Option<Self>, DetectVirtualPackageError> {
Ok(Self::current(cache_dir))
}
const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_CUDA_ARCH";
}
impl From<CudaArch> for GenericVirtualPackage {
fn from(cuda_arch: CudaArch) -> Self {
GenericVirtualPackage {
name: PackageName::new_unchecked("__cuda_arch"),
version: cuda_arch.version,
build_string: "0".into(),
}
}
}
impl From<CudaArch> for VirtualPackage {
fn from(cuda_arch: CudaArch) -> Self {
VirtualPackage::CudaArch(cuda_arch)
}
}
#[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",
Platform::IosArm64 | Platform::IosSimulatorArm64 | Platform::AndroidAarch64 => {
"aarch64"
}
Platform::IosSimulator64 | Platform::Android64 => "x86_64",
Platform::Android32 => "x86",
Platform::AndroidArmV7a => return None,
_ => return None,
};
Some(Self::from_name(archspec_name))
}
pub fn from_name(archspec_name: &str) -> Self {
Self::from_known_name(archspec_name).unwrap_or_else(|| {
Arc::new(archspec::cpu::Microarchitecture::generic(archspec_name)).into()
})
}
pub fn from_known_name(archspec_name: &str) -> Option<Self> {
Microarchitecture::known_targets()
.get(archspec_name)
.cloned()
.map(Into::into)
}
pub fn is_compatible_with(&self, required: &Self) -> bool {
match (self, required) {
(_, Archspec::Unknown) => true,
(Archspec::Unknown, Archspec::Microarchitecture(_)) => false,
(Archspec::Microarchitecture(host), Archspec::Microarchitecture(required)) => {
host.name() == required.name() || host.is_strict_superset(required)
}
}
}
}
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, ParseVirtualPackageOverrideError> {
if value == "0" {
return Ok(Archspec::Unknown);
}
if let Some(archspec) = Self::from_known_name(value) {
return Ok(archspec);
}
let underscored = value.replace('-', "_");
let suggestion = if Self::from_known_name(&underscored).is_some() {
format!(", did you mean '{underscored}'?")
} else {
String::from(
"; use a known microarchitecture name (e.g. 'x86_64_v3') or '0' to disable",
)
};
Err(ParseVirtualPackageOverrideError::validation_error(format!(
"'{value}' is not a known archspec microarchitecture{suggestion}"
)))
}
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, ParseVirtualPackageOverrideError> {
Ok(Self {
version: Version::from_str(env_var_value)?,
})
}
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 Ios {
pub version: Version,
}
impl Ios {
pub fn current() -> Option<Self> {
None
}
}
impl From<Ios> for GenericVirtualPackage {
fn from(ios: Ios) -> Self {
GenericVirtualPackage {
name: PackageName::new_unchecked("__ios"),
version: ios.version,
build_string: "0".into(),
}
}
}
impl From<Ios> for VirtualPackage {
fn from(ios: Ios) -> Self {
VirtualPackage::Ios(ios)
}
}
impl From<Version> for Ios {
fn from(version: Version) -> Self {
Self { version }
}
}
impl EnvOverride for Ios {
fn parse_version(env_var_value: &str) -> Result<Self, ParseVirtualPackageOverrideError> {
Ok(Self {
version: Version::from_str(env_var_value)?,
})
}
fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
Ok(Self::current())
}
const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_IOS";
}
#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
pub struct Android {
pub version: Version,
}
impl Android {
pub fn current() -> Option<Self> {
None
}
}
impl From<Android> for GenericVirtualPackage {
fn from(android: Android) -> Self {
GenericVirtualPackage {
name: PackageName::new_unchecked("__android"),
version: android.version,
build_string: "0".into(),
}
}
}
impl From<Android> for VirtualPackage {
fn from(android: Android) -> Self {
VirtualPackage::Android(android)
}
}
impl From<Version> for Android {
fn from(version: Version) -> Self {
Self { version }
}
}
impl EnvOverride for Android {
fn parse_version(env_var_value: &str) -> Result<Self, ParseVirtualPackageOverrideError> {
Ok(Self {
version: Version::from_str(env_var_value)?,
})
}
fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
Ok(Self::current())
}
const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_ANDROID";
}
#[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, ParseVirtualPackageOverrideError> {
Ok(Self {
version: Some(Version::from_str(env_var_value)?),
})
}
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(), None).unwrap();
println!("{virtual_packages:#?}");
}
#[test]
fn archspec_compatibility() {
let arch = Archspec::from_name;
assert!(arch("skylake").is_compatible_with(&arch("x86_64_v3")));
assert!(arch("m2").is_compatible_with(&arch("m1")));
assert!(arch("m2").is_compatible_with(&arch("armv8.5a")));
assert!(arch("x86_64_v3").is_compatible_with(&arch("x86_64_v3")));
assert!(!arch("haswell").is_compatible_with(&arch("skylake")));
assert!(!arch("x86_64").is_compatible_with(&arch("x86_64_v3")));
assert!(!arch("m2").is_compatible_with(&arch("x86_64")));
assert!(!arch("skylake").is_compatible_with(&arch("aarch64")));
assert!(arch("not-a-real-arch").is_compatible_with(&arch("not-a-real-arch")));
assert!(!arch("skylake").is_compatible_with(&arch("not-a-real-arch")));
assert!(!Archspec::Unknown.is_compatible_with(&arch("x86_64")));
assert!(Archspec::Unknown.is_compatible_with(&Archspec::Unknown));
assert!(arch("skylake").is_compatible_with(&Archspec::Unknown));
}
#[test]
fn archspec_from_known_name() {
assert_eq!(
Archspec::from_known_name("skylake").map(|a| a.as_str().to_string()),
Some("skylake".to_string())
);
assert_eq!(Archspec::from_known_name("x86-64-v3"), None);
assert_eq!(Archspec::from_known_name("nonsense"), None);
}
#[test]
fn archspec_override_rejects_unknown_names() {
assert_eq!(
Archspec::parse_version("skylake").unwrap().as_str(),
"skylake"
);
assert_eq!(Archspec::parse_version("0").unwrap(), Archspec::Unknown);
let error = Archspec::parse_version("x86-64-v3").unwrap_err();
assert!(error.to_string().contains("did you mean 'x86_64_v3'"));
let error = Archspec::parse_version("nonsense").unwrap_err();
assert!(
error
.to_string()
.contains("'nonsense' is not a known archspec microarchitecture")
);
}
#[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, None).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, None).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, None).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()));
}
#[test]
fn baseline_only_fills_slots_the_platform_carries() {
let linux = VirtualPackages::baseline_for_platform(Platform::Linux64);
assert!(linux.linux.is_some());
assert!(linux.libc.is_some());
assert!(linux.unix);
assert!(linux.win.is_none());
assert!(linux.osx.is_none());
let win = VirtualPackages::baseline_for_platform(Platform::Win64);
assert!(win.win.is_some());
assert!(!win.unix);
assert!(win.linux.is_none());
assert!(win.libc.is_none());
assert!(win.osx.is_none());
let osx = VirtualPackages::baseline_for_platform(Platform::OsxArm64);
assert!(osx.osx.is_some());
assert!(osx.unix);
assert!(osx.linux.is_none());
assert!(osx.libc.is_none());
assert!(osx.win.is_none());
}
#[test]
fn baseline_assumes_no_gpu() {
for platform in [Platform::Linux64, Platform::Win64, Platform::OsxArm64] {
let baseline = VirtualPackages::baseline_for_platform(platform);
assert!(baseline.cuda.is_none(), "{platform}");
assert!(baseline.cuda_arch.is_none(), "{platform}");
}
}
#[test]
fn baseline_uses_the_documented_default_versions() {
let linux = VirtualPackages::baseline_for_platform(Platform::Linux64);
assert_eq!(
linux.linux.unwrap().version,
defaults::default_linux_version()
);
let libc = linux.libc.unwrap();
assert_eq!(libc.family, "glibc");
assert_eq!(libc.version, defaults::default_glibc_version());
let osx = VirtualPackages::baseline_for_platform(Platform::OsxArm64);
assert_eq!(
osx.osx.unwrap().version,
defaults::default_mac_os_version(Platform::OsxArm64).unwrap()
);
let win = VirtualPackages::baseline_for_platform(Platform::Win64);
assert_eq!(
win.win.unwrap().version.unwrap(),
defaults::default_windows_version()
);
assert_eq!(
VirtualPackages::baseline_for_platform(Platform::Linux64).archspec,
Archspec::from_platform(Platform::Linux64)
);
}
#[test]
fn baseline_matches_cross_compiled_detection() {
let current = Platform::current();
let target = if current.is_linux() {
Platform::Win64
} else {
Platform::Linux64
};
let baseline = VirtualPackages::baseline_for_platform(target);
let detected =
VirtualPackages::detect_for_platform(target, &VirtualPackageOverrides::default(), None)
.unwrap();
assert_eq!(baseline.win, detected.win);
assert_eq!(baseline.unix, detected.unix);
assert_eq!(baseline.linux, detected.linux);
assert_eq!(baseline.osx, detected.osx);
assert_eq!(baseline.libc, detected.libc);
}
#[test]
fn test_cross_platform_default_versions() {
let overrides = VirtualPackageOverrides::default();
let current = Platform::current();
if !current.is_linux() {
let packages =
VirtualPackages::detect_for_platform(Platform::Linux64, &overrides, None).unwrap();
assert_eq!(
packages.linux.expect("__linux should be present").version,
defaults::default_linux_version()
);
let libc = packages.libc.expect("__glibc should be present");
assert_eq!(libc.family, "glibc");
assert_eq!(libc.version, defaults::default_glibc_version());
}
if !current.is_osx() {
let packages =
VirtualPackages::detect_for_platform(Platform::OsxArm64, &overrides, None).unwrap();
assert_eq!(
packages.osx.expect("__osx should be present").version,
defaults::default_mac_os_version(Platform::OsxArm64).unwrap()
);
}
if !current.is_windows() {
let packages =
VirtualPackages::detect_for_platform(Platform::Win64, &overrides, None).unwrap();
assert_eq!(
packages.win.expect("__win should be present").version,
Some(defaults::default_windows_version())
);
}
}
#[test]
fn test_ios_android_virtual_packages() {
let ios_packages = VirtualPackages::detect_for_platform(
Platform::IosArm64,
&VirtualPackageOverrides::default(),
None,
)
.unwrap();
let ios_names: Vec<String> = ios_packages
.into_generic_virtual_packages()
.map(|pkg| pkg.name.as_normalized().to_string())
.collect();
assert!(ios_names.contains(&"__ios".to_string()));
assert!(ios_names.contains(&"__unix".to_string()));
assert!(!ios_names.contains(&"__osx".to_string()));
let overrides = VirtualPackageOverrides {
ios: Some(Override::String("15.0".to_string())),
..Default::default()
};
let ios_packages =
VirtualPackages::detect_for_platform(Platform::IosSimulatorArm64, &overrides, None)
.unwrap();
let ios = ios_packages
.into_generic_virtual_packages()
.find(|pkg| pkg.name.as_normalized() == "__ios")
.expect("__ios should be present");
assert_eq!(ios.version, Version::from_str("15.0").unwrap());
let android_packages = VirtualPackages::detect_for_platform(
Platform::AndroidAarch64,
&VirtualPackageOverrides::default(),
None,
)
.unwrap();
let android_names: Vec<String> = android_packages
.into_generic_virtual_packages()
.map(|pkg| pkg.name.as_normalized().to_string())
.collect();
assert!(android_names.contains(&"__android".to_string()));
assert!(android_names.contains(&"__unix".to_string()));
assert!(!android_names.contains(&"__linux".to_string()));
let overrides = VirtualPackageOverrides {
android: Some(Override::String("21".to_string())),
..Default::default()
};
let android_packages =
VirtualPackages::detect_for_platform(Platform::AndroidArmV7a, &overrides, None)
.unwrap();
let android = android_packages
.into_generic_virtual_packages()
.find(|pkg| pkg.name.as_normalized() == "__android")
.expect("__android should be present");
assert_eq!(android.version, Version::from_str("21").unwrap());
}
#[test]
fn test_is_valid_cuda_version_format() {
assert!(cuda::is_valid_cuda_version_format("8.6"));
assert!(cuda::is_valid_cuda_version_format("7.5"));
assert!(cuda::is_valid_cuda_version_format("10.2"));
assert!(cuda::is_valid_cuda_version_format("0.0"));
assert!(cuda::is_valid_cuda_version_format("12.0"));
assert!(!cuda::is_valid_cuda_version_format("8"));
assert!(!cuda::is_valid_cuda_version_format("8.6.1"));
assert!(!cuda::is_valid_cuda_version_format("8.6.1.0"));
assert!(!cuda::is_valid_cuda_version_format(""));
assert!(!cuda::is_valid_cuda_version_format(".6"));
assert!(!cuda::is_valid_cuda_version_format("8."));
assert!(!cuda::is_valid_cuda_version_format("."));
assert!(!cuda::is_valid_cuda_version_format("8.6a"));
assert!(!cuda::is_valid_cuda_version_format("a.6"));
assert!(!cuda::is_valid_cuda_version_format("8.b"));
assert!(!cuda::is_valid_cuda_version_format("eight.six"));
assert!(!cuda::is_valid_cuda_version_format("8-6"));
assert!(!cuda::is_valid_cuda_version_format("8_6"));
}
#[test]
fn test_parse_cuda_arch() {
let cuda_arch = CudaArch::parse_version("8.6").unwrap();
assert_eq!(cuda_arch.version, Version::from_str("8.6").unwrap());
let cuda_arch = CudaArch::parse_version("7.5").unwrap();
assert_eq!(cuda_arch.version, Version::from_str("7.5").unwrap());
let cuda_arch = CudaArch::parse_version("9.0").unwrap();
assert_eq!(cuda_arch.version, Version::from_str("9.0").unwrap());
assert!(CudaArch::parse_version("invalid").is_err());
assert!(CudaArch::parse_version("8").is_err());
assert!(CudaArch::parse_version("8.6.1").is_err());
let env_var_name = format!("{}_{}", CudaArch::DEFAULT_ENV_NAME, "test123");
temp_env::with_var(&env_var_name, Some("7.5"), || {
let result = CudaArch::detect(Some(&Override::EnvVar(env_var_name.clone()))).unwrap();
assert!(result.is_some());
let cuda_arch = result.unwrap();
assert_eq!(cuda_arch.version, Version::from_str("7.5").unwrap());
});
}
#[test]
fn test_cuda_arch_coupling() {
let overrides = VirtualPackageOverrides::default();
let packages = VirtualPackages::detect(&overrides, None).unwrap();
if packages.cuda.is_none() {
assert!(
packages.cuda_arch.is_none(),
"cuda_arch should be None when cuda is None"
);
}
let cuda_arch_override = Override::String("8.6".to_string());
let overrides = VirtualPackageOverrides {
cuda: None, cuda_arch: Some(cuda_arch_override),
..Default::default()
};
let packages = VirtualPackages::detect(&overrides, None).unwrap();
if packages.cuda.is_none() {
assert!(
packages.cuda_arch.is_none(),
"cuda_arch should be None when cuda is None, even with override"
);
}
let cuda_override = Override::String("12.0".to_string());
let cuda_arch_override = Override::String("8.6".to_string());
let overrides = VirtualPackageOverrides {
cuda: Some(cuda_override),
cuda_arch: Some(cuda_arch_override),
..Default::default()
};
let packages = VirtualPackages::detect(&overrides, None).unwrap();
assert!(
packages.cuda.is_some(),
"cuda should be present with override"
);
assert!(
packages.cuda_arch.is_some(),
"cuda_arch should be present when cuda is present"
);
let cuda_arch = packages.cuda_arch.unwrap();
assert_eq!(cuda_arch.version, Version::from_str("8.6").unwrap());
let cuda_override = Override::String("".to_string());
let cuda_arch_override = Override::String("8.6".to_string());
let overrides = VirtualPackageOverrides {
cuda: Some(cuda_override),
cuda_arch: Some(cuda_arch_override),
..Default::default()
};
let packages = VirtualPackages::detect(&overrides, None).unwrap();
assert!(
packages.cuda.is_none(),
"cuda should be None with empty string override"
);
assert!(
packages.cuda_arch.is_none(),
"cuda_arch should be None when cuda is disabled via empty string"
);
}
}