use super::manifest::{LibCSystemRequirement, SystemRequirements};
use crate::project::errors::UnsupportedPlatformError;
use crate::project::Environment;
use itertools::Itertools;
use miette::Diagnostic;
use rattler_conda_types::{GenericVirtualPackage, Platform, Version};
use rattler_virtual_packages::{
Archspec, Cuda, DetectVirtualPackageError, LibC, Linux, Osx, VirtualPackage,
};
use std::collections::HashMap;
use thiserror::Error;
pub fn default_glibc_version() -> Version {
"2.17".parse().unwrap()
}
pub fn default_linux_version() -> Version {
"5.10".parse().unwrap()
}
pub fn default_mac_os_version(platform: Platform) -> Version {
match platform {
Platform::OsxArm64 => "11.0".parse().unwrap(),
Platform::Osx64 => "10.15".parse().unwrap(),
_ => panic!(
"default_mac_os_version() called with non-osx platform: {}",
platform
),
}
}
pub fn get_minimal_virtual_packages(
platform: Platform,
system_requirements: &SystemRequirements,
) -> Vec<VirtualPackage> {
let mut virtual_packages: Vec<VirtualPackage> = vec![];
if platform.is_unix() {
virtual_packages.push(VirtualPackage::Unix);
}
if platform.is_linux() {
let version = system_requirements
.linux
.clone()
.unwrap_or(default_linux_version());
virtual_packages.push(VirtualPackage::Linux(Linux { version }));
let (family, version) = system_requirements
.libc
.as_ref()
.map(LibCSystemRequirement::family_and_version)
.map(|(family, version)| (family.to_string(), version.clone()))
.unwrap_or(("glibc".parse().unwrap(), default_glibc_version()));
virtual_packages.push(VirtualPackage::LibC(LibC { family, version }));
}
if platform.is_windows() {
virtual_packages.push(VirtualPackage::Win);
}
if let Some(archspec) = system_requirements.archspec.clone() {
virtual_packages.push(VirtualPackage::Archspec(Archspec { spec: archspec }))
} else if let Some(archspec) = Archspec::from_platform(platform) {
virtual_packages.push(archspec.into())
}
if platform.is_osx() {
let version = system_requirements
.macos
.clone()
.unwrap_or_else(|| default_mac_os_version(platform));
virtual_packages.push(VirtualPackage::Osx(Osx { version }));
}
if let Some(version) = system_requirements.cuda.clone() {
virtual_packages.push(VirtualPackage::Cuda(Cuda { version }));
}
virtual_packages
}
impl Environment<'_> {
pub fn virtual_packages(&self, platform: Platform) -> Vec<GenericVirtualPackage> {
get_minimal_virtual_packages(platform, &self.system_requirements())
.into_iter()
.map(GenericVirtualPackage::from)
.collect()
}
}
#[derive(Debug, Error, Diagnostic)]
pub enum VerifyCurrentPlatformError {
#[error("The current platform does not satisfy the minimal virtual package requirements")]
UnsupportedPlatform(#[from] Box<UnsupportedPlatformError>),
#[error(transparent)]
DetectionVirtualPackagesError(#[from] DetectVirtualPackageError),
#[error("The current system has a mismatching virtual package. The project requires '{required}' to be on build '{required_build_string}' but the system has build '{local_build_string}'")]
MismatchingBuildString {
required: String,
required_build_string: String,
local_build_string: String,
},
#[error("The current system has a mismatching virtual package. The project requires '{required}' to be at least version '{required_version}' but the system has version '{local_version}'")]
MismatchingVersion {
required: String,
required_version: Box<Version>,
local_version: Box<Version>,
},
#[error("The platform you are running on should at least have the virtual package {required} on version {required_version}, build_string: {required_build_string}")]
MissingVirtualPackage {
required: String,
required_version: Box<Version>,
required_build_string: String,
},
}
pub fn verify_current_platform_has_required_virtual_packages(
environment: &Environment<'_>,
) -> Result<(), VerifyCurrentPlatformError> {
let current_platform = Platform::current();
if !environment.platforms().contains(¤t_platform) {
return Err(VerifyCurrentPlatformError::from(Box::new(
UnsupportedPlatformError {
environments_platforms: environment.platforms().into_iter().collect_vec(),
platform: current_platform,
environment: environment.name().clone(),
},
)));
}
let system_virtual_packages = VirtualPackage::current()?
.iter()
.cloned()
.map(GenericVirtualPackage::from)
.map(|vpkg| (vpkg.name.clone(), vpkg))
.collect::<HashMap<_, _>>();
let required_pkgs = environment.virtual_packages(current_platform);
for req_pkg in required_pkgs {
if let Some(local_vpkg) = system_virtual_packages.get(&req_pkg.name) {
if req_pkg.build_string != local_vpkg.build_string {
return Err(VerifyCurrentPlatformError::MismatchingBuildString {
required: req_pkg.name.as_source().to_string(),
required_build_string: req_pkg.build_string.clone(),
local_build_string: local_vpkg.build_string.clone(),
});
}
if req_pkg.version > local_vpkg.version {
return Err(VerifyCurrentPlatformError::MismatchingVersion {
required: req_pkg.name.as_source().to_string(),
required_version: Box::from(req_pkg.version),
local_version: Box::from(local_vpkg.version.clone()),
});
}
} else {
return Err(VerifyCurrentPlatformError::MissingVirtualPackage {
required: req_pkg.name.as_source().to_string(),
required_version: Box::from(req_pkg.version),
required_build_string: req_pkg.build_string.clone(),
});
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::project::manifest::SystemRequirements;
use insta::assert_debug_snapshot;
use rattler_conda_types::Platform;
#[test]
fn test_get_minimal_virtual_packages() {
let platforms = vec![
Platform::NoArch,
Platform::Linux64,
Platform::LinuxAarch64,
Platform::LinuxPpc64le,
Platform::Osx64,
Platform::OsxArm64,
Platform::Win64,
];
let system_requirements = SystemRequirements::default();
for platform in platforms {
let packages = get_minimal_virtual_packages(platform, &system_requirements);
let snapshot_name = format!("test_get_minimal_virtual_packages.{}", platform);
assert_debug_snapshot!(snapshot_name, packages);
}
}
}