diff --git a/Cargo.toml b/Cargo.toml index 86b022620..ec3db1cfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -159,5 +159,6 @@ url = { version = "2.5.4" } uuid = { version = "1.11.0", default-features = false } walkdir = "2.5.0" windows-sys = { version = "0.59.0", default-features = false } +winver = { version = "1.0.0" } zip = { version = "2.2.2", default-features = false } zstd = { version = "0.13.2", default-features = false } diff --git a/crates/rattler_virtual_packages/Cargo.toml b/crates/rattler_virtual_packages/Cargo.toml index 4ed6de9a8..ead7d5fdc 100644 --- a/crates/rattler_virtual_packages/Cargo.toml +++ b/crates/rattler_virtual_packages/Cargo.toml @@ -23,3 +23,6 @@ archspec = { workspace = true } [target.'cfg(target_os="macos")'.dependencies] plist = { workspace = true } + +[target.'cfg(target_os="windows")'.dependencies] +winver = { workspace = true } diff --git a/crates/rattler_virtual_packages/src/lib.rs b/crates/rattler_virtual_packages/src/lib.rs index 673bc64a1..45f7d004c 100644 --- a/crates/rattler_virtual_packages/src/lib.rs +++ b/crates/rattler_virtual_packages/src/lib.rs @@ -36,9 +36,12 @@ 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, @@ -142,7 +145,7 @@ pub trait EnvOverride: Sized { #[derive(Clone, Eq, PartialEq, Hash, Debug)] pub enum VirtualPackage { /// Available on windows - Win, + Win(Windows), /// Available on `Unix` based platforms Unix, @@ -163,19 +166,86 @@ pub enum VirtualPackage { Archspec(Archspec), } +/// A struct that represents all virtual packages provided by this library. +#[derive(Debug, Clone, Default)] +pub struct VirtualPackages { + /// Available on windows + pub win: Option, + + /// Available on `Unix` based platforms + pub unix: bool, + + /// Available when running on `Linux` + pub linux: Option, + + /// Available when running on `OSX` + pub osx: Option, + + /// Available `LibC` family and version + pub libc: Option, + + /// Available `Cuda` version + pub cuda: Option, + + /// The CPU architecture + pub archspec: Option, +} + +impl VirtualPackages { + /// Convert this struct into an iterator of [`VirtualPackage`]. + pub fn into_virtual_packages(self) -> impl Iterator { + 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() + } + + /// Convert this struct into an iterator of [`GenericVirtualPackage`]. + pub fn into_generic_virtual_packages(self) -> impl Iterator { + self.into_virtual_packages().map(Into::into) + } + + /// Detect the virtual packages of the current system with the given + /// overrides. + pub fn detect(overrides: &VirtualPackageOverrides) -> Result { + 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())?, + }) + } +} + impl From for GenericVirtualPackage { fn from(package: VirtualPackage) -> Self { match package { - VirtualPackage::Win => GenericVirtualPackage { - name: PackageName::new_unchecked("__win"), - version: Version::major(0), - build_string: "0".into(), - }, 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(), @@ -201,7 +271,9 @@ impl VirtualPackage { pub fn detect( overrides: &VirtualPackageOverrides, ) -> Result, DetectVirtualPackageError> { - try_detect_virtual_packages_with_overrides(overrides) + Ok(VirtualPackages::detect(overrides)? + .into_virtual_packages() + .collect()) } } @@ -233,12 +305,18 @@ pub enum DetectVirtualPackageError { /// struct with all overrides set to the default environment variables. #[derive(Default, Clone, Debug)] pub struct VirtualPackageOverrides { + /// The override for the win virtual package + pub win: Option, /// The override for the osx virtual package pub osx: Option, + /// The override for the linux virtual package + pub linux: Option, /// The override for the libc virtual package pub libc: Option, /// The override for the cuda virtual package pub cuda: Option, + /// The override for the archspec virtual package + pub archspec: Option, } impl VirtualPackageOverrides { @@ -246,9 +324,12 @@ impl VirtualPackageOverrides { /// to a given value. 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), + cuda: Some(ov.clone()), + archspec: Some(ov), } } @@ -259,47 +340,6 @@ impl VirtualPackageOverrides { } } -// Detect the available virtual packages on the system -fn try_detect_virtual_packages_with_overrides( - overrides: &VirtualPackageOverrides, -) -> Result, DetectVirtualPackageError> { - let mut result = Vec::new(); - let platform = Platform::current(); - - if platform.is_unix() { - result.push(VirtualPackage::Unix); - } - - if platform.is_windows() { - result.push(VirtualPackage::Win); - } - - if platform.is_linux() { - if let Some(linux_version) = Linux::current()? { - result.push(linux_version.into()); - } - if let Some(libc) = LibC::detect(overrides.libc.as_ref())? { - result.push(libc.into()); - } - } - - if platform.is_osx() { - if let Some(osx) = Osx::detect(overrides.osx.as_ref())? { - result.push(osx.into()); - } - } - - if let Some(cuda) = Cuda::detect(overrides.cuda.as_ref())? { - result.push(cuda.into()); - } - - if let Some(archspec) = Archspec::current() { - result.push(archspec.into()); - } - - Ok(result) -} - /// Linux virtual package description #[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)] pub struct Linux { @@ -340,6 +380,18 @@ impl From for Linux { } } +impl EnvOverride for Linux { + const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_LINUX"; + + fn parse_version(env_var_value: &str) -> Result { + Version::from_str(env_var_value).map(Self::from) + } + + fn detect_from_host() -> Result, DetectVirtualPackageError> { + Ok(Self::current()?) + } +} + /// `LibC` virtual package description #[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)] pub struct LibC { @@ -451,9 +503,12 @@ impl From for VirtualPackage { /// Archspec describes the CPU architecture #[derive(Clone, Debug)] -pub struct Archspec { - /// The associated microarchitecture - pub spec: Arc, +pub enum Archspec { + /// A microarchitecture from the archspec library. + Microarchitecture(Arc), + + /// An unknown microarchitecture + Unknown, } impl Serialize for Archspec { @@ -461,7 +516,7 @@ impl Serialize for Archspec { where S: Serializer, { - self.spec.name().serialize(serializer) + self.as_str().serialize(serializer) } } @@ -470,24 +525,24 @@ impl<'de> Deserialize<'de> for Archspec { where D: Deserializer<'de>, { - let name = String::deserialize(deserializer)?; - let spec = archspec::cpu::Microarchitecture::known_targets() - .get(&name) - .cloned() - .unwrap_or_else(|| Arc::new(archspec::cpu::Microarchitecture::generic(&name))); - Ok(Self { spec }) + 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(&self, state: &mut H) { - self.spec.name().hash(state); + self.as_str().hash(state); } } impl PartialEq for Archspec { fn eq(&self, other: &Self) -> bool { - self.spec.name() == other.spec.name() + self.as_str() == other.as_str() } } @@ -495,14 +550,33 @@ impl Eq for Archspec {} impl From> for Archspec { fn from(arch: Arc) -> Self { - Self { spec: arch } + Self::Microarchitecture(arch) + } +} + +impl Display for Archspec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) } } impl Archspec { - /// Returns the current CPU architecture - pub fn current() -> Option { - archspec::cpu::host().ok().map(Into::into) + /// Returns the string representation of the virtual package. + pub fn as_str(&self) -> &str { + match self { + Archspec::Microarchitecture(arch) => arch.name(), + Archspec::Unknown => "0", + } + } + + /// Returns the current CPU architecture or `Archspec::Unknown` if the + /// architecture could not be determined. + pub fn current() -> Self { + archspec::cpu::host() + .ok() + .map(Into::into) + .or_else(|| Self::from_platform(Platform::current())) + .unwrap_or(Archspec::Unknown) } /// Returns the minimal supported archspec architecture for the given @@ -533,15 +607,17 @@ impl Archspec { Platform::OsxArm64 => "m1", }; - Some( - archspec::cpu::Microarchitecture::known_targets() - .get(archspec_name) - .cloned() - .unwrap_or_else(|| { - Arc::new(archspec::cpu::Microarchitecture::generic(archspec_name)) - }) - .into(), - ) + Some(Self::from_name(archspec_name)) + } + + /// Constructs an `Archspec` from the given `archspec_name`. Creates a + /// "generic" architecture if the name is not known. + 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() } } @@ -550,7 +626,7 @@ impl From for GenericVirtualPackage { GenericVirtualPackage { name: PackageName::new_unchecked("__archspec"), version: Version::major(1), - build_string: archspec.spec.name().into(), + build_string: archspec.to_string(), } } } @@ -561,6 +637,22 @@ impl From for VirtualPackage { } } +impl EnvOverride for Archspec { + fn parse_version(value: &str) -> Result { + 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, DetectVirtualPackageError> { + Ok(Some(Self::current())) + } +} + /// OSX virtual package description #[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)] pub struct Osx { @@ -610,18 +702,77 @@ impl EnvOverride for Osx { const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_OSX"; } +/// Windows virtual package description +#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)] +pub struct Windows { + /// The version of windows + pub version: Option, +} + +impl Windows { + /// Returns the Windows version of the current platform. + /// + /// Returns `None` if the current platform is not a Windows based platform. + pub fn current() -> Option { + if cfg!(target_os = "windows") { + Some(Self { + version: win::windows_version(), + }) + } else { + None + } + } +} + +impl From 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 for VirtualPackage { + fn from(windows: Windows) -> Self { + VirtualPackage::Win(windows) + } +} + +impl From for Windows { + fn from(version: Version) -> Self { + Self { + version: Some(version), + } + } +} + +impl EnvOverride for Windows { + fn parse_version(env_var_value: &str) -> Result { + Version::from_str(env_var_value).map(|version| Self { + version: Some(version), + }) + } + fn detect_from_host() -> Result, 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 crate::{Cuda, EnvOverride, LibC, Osx, Override, VirtualPackage, VirtualPackageOverrides}; + use super::*; #[test] fn doesnt_crash() { - let virtual_packages = VirtualPackage::detect(&VirtualPackageOverrides::default()).unwrap(); - println!("{virtual_packages:?}"); + let virtual_packages = + VirtualPackages::detect(&VirtualPackageOverrides::default()).unwrap(); + println!("{virtual_packages:#?}"); } #[test] fn parse_libc() { diff --git a/crates/rattler_virtual_packages/src/linux.rs b/crates/rattler_virtual_packages/src/linux.rs index f02a1465f..b78eb5484 100644 --- a/crates/rattler_virtual_packages/src/linux.rs +++ b/crates/rattler_virtual_packages/src/linux.rs @@ -1,4 +1,4 @@ -//! Low-level functions to dect the linux version on the system. See [`linux_version`]. +//! Low-level functions to detect the linux version on the system. See [`linux_version`]. use once_cell::sync::OnceCell; use rattler_conda_types::{ParseVersionError, Version}; @@ -122,7 +122,6 @@ mod test { } #[test] - #[cfg(target_os = "linux")] pub fn doesnt_crash() { let version = super::try_detect_linux_version(); println!("Linux {version:?}"); diff --git a/crates/rattler_virtual_packages/src/win.rs b/crates/rattler_virtual_packages/src/win.rs new file mode 100644 index 000000000..45655b7b6 --- /dev/null +++ b/crates/rattler_virtual_packages/src/win.rs @@ -0,0 +1,40 @@ +//! Low-level functions to detect the Windows version on the system. See +//! [`windows_version`]. + +use once_cell::sync::OnceCell; +use rattler_conda_types::Version; + +/// Returns the Windows version of the current platform. +/// +/// Returns an error if determining the Windows version resulted in an error. +/// Returns `None` if the Windows version could not be determined. Note that +/// this does not mean the current platform is not Windows. +pub fn windows_version() -> Option { + static DETECTED_WINDOWS_VERSION: OnceCell> = OnceCell::new(); + DETECTED_WINDOWS_VERSION + .get_or_init(detect_windows_version) + .clone() +} + +#[cfg(target_os = "windows")] +fn detect_windows_version() -> Option { + let windows_version = winver::WindowsVersion::detect()?; + Some( + std::str::FromStr::from_str(&windows_version.to_string()) + .expect("WindowsVersion::to_string() should always return a valid version"), + ) +} + +#[cfg(not(target_os = "windows"))] +const fn detect_windows_version() -> Option { + None +} + +#[cfg(test)] +mod test { + #[test] + pub fn doesnt_crash() { + let version = super::detect_windows_version(); + println!("Windows {version:?}"); + } +}