rattler_virtual_packages/
lib.rs

1#![deny(missing_docs)]
2
3//! A library to detect Conda virtual packages present on a system.
4//!
5//! A virtual package represents a package that is injected into the solver to
6//! provide system information to packages. This allows packages to add
7//! dependencies on specific system features, like the platform version, the
8//! machines architecture, or the availability of a Cuda driver with a specific
9//! version.
10//!
11//! This library provides both a low- and high level API to detect versions of
12//! virtual packages for the host system.
13//!
14//! To detect all virtual packages for the host system use the
15//! [`VirtualPackage::detect`] method which will return a memoized slice of all
16//! detected virtual packages. The `VirtualPackage` enum represents all
17//! available virtual package types. Using it provides some flexibility to the
18//! user to not care about which exact virtual packages exist but still allows
19//! users to override specific virtual package behavior. Say for instance you
20//! just want to detect the capabilities of the host system but you still want
21//! to restrict the targeted linux version. You can convert an instance of
22//! `VirtualPackage` to `GenericVirtualPackage` which erases any typing for
23//! specific virtual packages.
24//!
25//! Each virtual package is also represented by a struct which can be used to
26//! detect the specifics of one virtual package. For instance the
27//! [`Linux::current`] method returns an instance of `Linux` which contains the
28//! current Linux version. It also provides conversions to the higher level API.
29//!
30//! Finally at the core of the library are detection functions to perform
31//! specific capability detections that are not tied to anything related to
32//! virtual packages. See [`cuda::detect_cuda_version_via_libcuda`] as an
33//! example.
34
35pub mod cuda;
36pub mod libc;
37pub mod linux;
38pub mod osx;
39pub mod win;
40
41use std::{
42    borrow::Cow,
43    env, fmt,
44    fmt::Display,
45    hash::{Hash, Hasher},
46    str::FromStr,
47    sync::Arc,
48};
49
50use archspec::cpu::Microarchitecture;
51use libc::DetectLibCError;
52use linux::ParseLinuxVersionError;
53use rattler_conda_types::{
54    GenericVirtualPackage, PackageName, ParseVersionError, Platform, Version,
55};
56use serde::{Deserialize, Deserializer, Serialize, Serializer};
57
58use crate::osx::ParseOsxVersionError;
59
60/// Configure the overrides used in in this crate.
61#[derive(Clone, Debug, PartialEq, Default)]
62pub enum Override {
63    /// Use the default override env var name
64    #[default]
65    DefaultEnvVar,
66    /// Use custom env var name
67    EnvVar(String),
68    /// Use a custom override directly
69    String(String),
70}
71
72/// Traits for overridable virtual packages
73/// Use as `Cuda::detect(override)`
74pub trait EnvOverride: Sized {
75    /// Parse `env_var_value`
76    fn parse_version(value: &str) -> Result<Self, ParseVersionError>;
77
78    /// Helper to convert the output of `parse_version` and handling empty
79    /// strings.
80    fn parse_version_opt(value: &str) -> Result<Option<Self>, DetectVirtualPackageError> {
81        if value.is_empty() {
82            Ok(None)
83        } else {
84            Ok(Some(Self::parse_version(value)?))
85        }
86    }
87
88    /// Read the environment variable and if it exists, try to parse it with
89    /// [`EnvOverride::parse_version`] If the output is:
90    /// - `None`, then the environment variable did not exist,
91    /// - `Some(Err(None))`, then the environment variable exist but was set to
92    ///   zero, so the package should be disabled
93    /// - `Some(Ok(pkg))`, then the override was for the package.
94    fn from_env_var_name_or<F>(
95        env_var_name: &str,
96        fallback: F,
97    ) -> Result<Option<Self>, DetectVirtualPackageError>
98    where
99        F: FnOnce() -> Result<Option<Self>, DetectVirtualPackageError>,
100    {
101        match env::var(env_var_name) {
102            Ok(var) => Self::parse_version_opt(&var),
103            Err(env::VarError::NotPresent) => fallback(),
104            Err(e) => Err(DetectVirtualPackageError::VarError(e)),
105        }
106    }
107
108    /// Default name of the environment variable that overrides the virtual
109    /// package.
110    const DEFAULT_ENV_NAME: &'static str;
111
112    /// Detect the virtual package for the current system.
113    /// This method is here so that `<Self as EnvOverride>::current` always
114    /// returns the same error type. `current` may return different types of
115    /// errors depending on the virtual package. This one always returns
116    /// `DetectVirtualPackageError`.
117    fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError>;
118
119    /// Apply the override to the current virtual package. If the override is
120    /// `None` then use the fallback
121    fn detect_with_fallback<F>(
122        ov: &Override,
123        fallback: F,
124    ) -> Result<Option<Self>, DetectVirtualPackageError>
125    where
126        F: FnOnce() -> Result<Option<Self>, DetectVirtualPackageError>,
127    {
128        match ov {
129            Override::String(str) => Self::parse_version_opt(str),
130            Override::DefaultEnvVar => Self::from_env_var_name_or(Self::DEFAULT_ENV_NAME, fallback),
131            Override::EnvVar(name) => Self::from_env_var_name_or(name, fallback),
132        }
133    }
134
135    /// Shortcut for `Self::detect_with_fallback` with `Self::detect_from_host`
136    /// as fallback
137    fn detect(ov: Option<&Override>) -> Result<Option<Self>, DetectVirtualPackageError> {
138        ov.map_or_else(Self::detect_from_host, |ov| {
139            Self::detect_with_fallback(ov, Self::detect_from_host)
140        })
141    }
142}
143
144/// An enum that represents all virtual package types provided by this library.
145#[derive(Clone, Eq, PartialEq, Hash, Debug)]
146pub enum VirtualPackage {
147    /// Available on windows
148    Win(Windows),
149
150    /// Available on `Unix` based platforms
151    Unix,
152
153    /// Available when running on `Linux`
154    Linux(Linux),
155
156    /// Available when running on `OSX`
157    Osx(Osx),
158
159    /// Available `LibC` family and version
160    LibC(LibC),
161
162    /// Available `Cuda` version
163    Cuda(Cuda),
164
165    /// The CPU architecture
166    Archspec(Archspec),
167}
168
169/// A struct that represents all virtual packages provided by this library.
170#[derive(Debug, Clone, Default)]
171pub struct VirtualPackages {
172    /// Available on windows
173    pub win: Option<Windows>,
174
175    /// Available on `Unix` based platforms
176    pub unix: bool,
177
178    /// Available when running on `Linux`
179    pub linux: Option<Linux>,
180
181    /// Available when running on `OSX`
182    pub osx: Option<Osx>,
183
184    /// Available `LibC` family and version
185    pub libc: Option<LibC>,
186
187    /// Available `Cuda` version
188    pub cuda: Option<Cuda>,
189
190    /// The CPU architecture
191    pub archspec: Option<Archspec>,
192}
193
194impl VirtualPackages {
195    /// Convert this struct into an iterator of [`VirtualPackage`].
196    pub fn into_virtual_packages(self) -> impl Iterator<Item = VirtualPackage> {
197        let Self {
198            win,
199            unix,
200            linux,
201            osx,
202            libc,
203            cuda,
204            archspec,
205        } = self;
206
207        [
208            win.map(VirtualPackage::Win),
209            unix.then_some(VirtualPackage::Unix),
210            linux.map(VirtualPackage::Linux),
211            osx.map(VirtualPackage::Osx),
212            libc.map(VirtualPackage::LibC),
213            cuda.map(VirtualPackage::Cuda),
214            archspec.map(VirtualPackage::Archspec),
215        ]
216        .into_iter()
217        .flatten()
218    }
219
220    /// Convert this struct into an iterator of [`GenericVirtualPackage`].
221    pub fn into_generic_virtual_packages(self) -> impl Iterator<Item = GenericVirtualPackage> {
222        self.into_virtual_packages().map(Into::into)
223    }
224
225    /// Detect the virtual packages of the current system with the given
226    /// overrides.
227    pub fn detect(overrides: &VirtualPackageOverrides) -> Result<Self, DetectVirtualPackageError> {
228        Ok(Self {
229            win: Windows::detect(overrides.win.as_ref())?,
230            unix: Platform::current().is_unix(),
231            linux: Linux::detect(overrides.linux.as_ref())?,
232            osx: Osx::detect(overrides.osx.as_ref())?,
233            libc: LibC::detect(overrides.libc.as_ref())?,
234            cuda: Cuda::detect(overrides.cuda.as_ref())?,
235            archspec: Archspec::detect(overrides.archspec.as_ref())?,
236        })
237    }
238}
239
240impl From<VirtualPackage> for GenericVirtualPackage {
241    fn from(package: VirtualPackage) -> Self {
242        match package {
243            VirtualPackage::Unix => GenericVirtualPackage {
244                name: PackageName::new_unchecked("__unix"),
245                version: Version::major(0),
246                build_string: "0".into(),
247            },
248            VirtualPackage::Win(windows) => windows.into(),
249            VirtualPackage::Linux(linux) => linux.into(),
250            VirtualPackage::Osx(osx) => osx.into(),
251            VirtualPackage::LibC(libc) => libc.into(),
252            VirtualPackage::Cuda(cuda) => cuda.into(),
253            VirtualPackage::Archspec(spec) => spec.into(),
254        }
255    }
256}
257
258impl VirtualPackage {
259    /// Returns virtual packages detected for the current system or an error if
260    /// the versions could not be properly detected.
261    #[deprecated(
262        since = "1.1.0",
263        note = "Use `VirtualPackage::detect(&VirtualPackageOverrides::default())` instead."
264    )]
265    pub fn current() -> Result<Vec<Self>, DetectVirtualPackageError> {
266        Self::detect(&VirtualPackageOverrides::default())
267    }
268
269    /// Detect the virtual packages of the current system with the given
270    /// overrides.
271    pub fn detect(
272        overrides: &VirtualPackageOverrides,
273    ) -> Result<Vec<Self>, DetectVirtualPackageError> {
274        Ok(VirtualPackages::detect(overrides)?
275            .into_virtual_packages()
276            .collect())
277    }
278}
279
280/// An error that might be returned by [`VirtualPackage::current`].
281#[derive(Debug, thiserror::Error)]
282#[allow(missing_docs)]
283pub enum DetectVirtualPackageError {
284    #[error(transparent)]
285    ParseLinuxVersion(#[from] ParseLinuxVersionError),
286
287    #[error(transparent)]
288    ParseMacOsVersion(#[from] ParseOsxVersionError),
289
290    #[error(transparent)]
291    DetectLibC(#[from] DetectLibCError),
292
293    #[error(transparent)]
294    VarError(#[from] env::VarError),
295
296    #[error(transparent)]
297    VersionParseError(#[from] ParseVersionError),
298}
299/// Configure the overrides used in this crate.
300///
301/// The default value is `None` for all overrides which means that by default
302/// none of the virtual packages are overridden.
303///
304/// Use `VirtualPackageOverrides::from_env()` to create an instance of this
305/// struct with all overrides set to the default environment variables.
306#[derive(Default, Clone, Debug)]
307pub struct VirtualPackageOverrides {
308    /// The override for the win virtual package
309    pub win: Option<Override>,
310    /// The override for the osx virtual package
311    pub osx: Option<Override>,
312    /// The override for the linux virtual package
313    pub linux: Option<Override>,
314    /// The override for the libc virtual package
315    pub libc: Option<Override>,
316    /// The override for the cuda virtual package
317    pub cuda: Option<Override>,
318    /// The override for the archspec virtual package
319    pub archspec: Option<Override>,
320}
321
322impl VirtualPackageOverrides {
323    /// Returns an instance of `VirtualPackageOverrides` with all overrides set
324    /// to a given value.
325    pub fn all(ov: Override) -> Self {
326        Self {
327            win: Some(ov.clone()),
328            osx: Some(ov.clone()),
329            linux: Some(ov.clone()),
330            libc: Some(ov.clone()),
331            cuda: Some(ov.clone()),
332            archspec: Some(ov),
333        }
334    }
335
336    /// Returns an instance of `VirtualPackageOverrides` where all overrides are
337    /// taken from default environment variables.
338    pub fn from_env() -> Self {
339        Self::all(Override::DefaultEnvVar)
340    }
341}
342
343/// Linux virtual package description
344#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
345pub struct Linux {
346    /// The version of linux
347    pub version: Version,
348}
349
350impl Linux {
351    /// Returns the Linux version of the current platform.
352    ///
353    /// Returns an error if determining the Linux version resulted in an error.
354    /// Returns `None` if the current platform is not a Linux based
355    /// platform.
356    pub fn current() -> Result<Option<Self>, ParseLinuxVersionError> {
357        Ok(linux::linux_version()?.map(|version| Self { version }))
358    }
359}
360
361impl From<Linux> for GenericVirtualPackage {
362    fn from(linux: Linux) -> Self {
363        GenericVirtualPackage {
364            name: PackageName::new_unchecked("__linux"),
365            version: linux.version,
366            build_string: "0".into(),
367        }
368    }
369}
370
371impl From<Linux> for VirtualPackage {
372    fn from(linux: Linux) -> Self {
373        VirtualPackage::Linux(linux)
374    }
375}
376
377impl From<Version> for Linux {
378    fn from(version: Version) -> Self {
379        Linux { version }
380    }
381}
382
383impl EnvOverride for Linux {
384    const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_LINUX";
385
386    fn parse_version(env_var_value: &str) -> Result<Self, ParseVersionError> {
387        Version::from_str(env_var_value).map(Self::from)
388    }
389
390    fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
391        Ok(Self::current()?)
392    }
393}
394
395/// `LibC` virtual package description
396#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
397pub struct LibC {
398    /// The family of `LibC`. This could be glibc for instance.
399    pub family: String,
400
401    /// The version of the libc distribution.
402    pub version: Version,
403}
404
405impl LibC {
406    /// Returns the `LibC` family and version of the current platform.
407    ///
408    /// Returns an error if determining the `LibC` family and version resulted
409    /// in an error. Returns `None` if the current platform does not have an
410    /// available version of `LibC`.
411    pub fn current() -> Result<Option<Self>, DetectLibCError> {
412        Ok(libc::libc_family_and_version()?.map(|(family, version)| Self { family, version }))
413    }
414}
415
416#[allow(clippy::fallible_impl_from)]
417impl From<LibC> for GenericVirtualPackage {
418    fn from(libc: LibC) -> Self {
419        GenericVirtualPackage {
420            // TODO: Convert the family to a valid package name. We can simply replace invalid
421            // characters.
422            name: format!("__{}", libc.family.to_lowercase())
423                .try_into()
424                .unwrap(),
425            version: libc.version,
426            build_string: "0".into(),
427        }
428    }
429}
430
431impl From<LibC> for VirtualPackage {
432    fn from(libc: LibC) -> Self {
433        VirtualPackage::LibC(libc)
434    }
435}
436
437impl EnvOverride for LibC {
438    const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_GLIBC";
439
440    fn parse_version(env_var_value: &str) -> Result<Self, ParseVersionError> {
441        Version::from_str(env_var_value).map(|version| Self {
442            family: "glibc".into(),
443            version,
444        })
445    }
446
447    fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
448        Ok(Self::current()?)
449    }
450}
451
452impl fmt::Display for LibC {
453    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
454        write!(f, "{}={}", self.family, self.version)
455    }
456}
457
458/// Cuda virtual package description
459#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
460pub struct Cuda {
461    /// The maximum supported Cuda version.
462    pub version: Version,
463}
464
465impl Cuda {
466    /// Returns the maximum Cuda version available on the current platform.
467    pub fn current() -> Option<Self> {
468        cuda::cuda_version().map(|version| Self { version })
469    }
470}
471
472impl From<Version> for Cuda {
473    fn from(version: Version) -> Self {
474        Self { version }
475    }
476}
477
478impl EnvOverride for Cuda {
479    fn parse_version(env_var_value: &str) -> Result<Self, ParseVersionError> {
480        Version::from_str(env_var_value).map(|version| Self { version })
481    }
482    fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
483        Ok(Self::current())
484    }
485    const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_CUDA";
486}
487
488impl From<Cuda> for GenericVirtualPackage {
489    fn from(cuda: Cuda) -> Self {
490        GenericVirtualPackage {
491            name: PackageName::new_unchecked("__cuda"),
492            version: cuda.version,
493            build_string: "0".into(),
494        }
495    }
496}
497
498impl From<Cuda> for VirtualPackage {
499    fn from(cuda: Cuda) -> Self {
500        VirtualPackage::Cuda(cuda)
501    }
502}
503
504/// Archspec describes the CPU architecture
505#[derive(Clone, Debug)]
506pub enum Archspec {
507    /// A microarchitecture from the archspec library.
508    Microarchitecture(Arc<Microarchitecture>),
509
510    /// An unknown microarchitecture
511    Unknown,
512}
513
514impl Serialize for Archspec {
515    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
516    where
517        S: Serializer,
518    {
519        self.as_str().serialize(serializer)
520    }
521}
522
523impl<'de> Deserialize<'de> for Archspec {
524    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
525    where
526        D: Deserializer<'de>,
527    {
528        let name = Cow::<'de, str>::deserialize(deserializer)?;
529        if name == "0" {
530            Ok(Self::Unknown)
531        } else {
532            Ok(Self::from_name(&name))
533        }
534    }
535}
536
537impl Hash for Archspec {
538    fn hash<H: Hasher>(&self, state: &mut H) {
539        self.as_str().hash(state);
540    }
541}
542
543impl PartialEq<Self> for Archspec {
544    fn eq(&self, other: &Self) -> bool {
545        self.as_str() == other.as_str()
546    }
547}
548
549impl Eq for Archspec {}
550
551impl From<Arc<Microarchitecture>> for Archspec {
552    fn from(arch: Arc<Microarchitecture>) -> Self {
553        Self::Microarchitecture(arch)
554    }
555}
556
557impl Display for Archspec {
558    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
559        write!(f, "{}", self.as_str())
560    }
561}
562
563impl Archspec {
564    /// Returns the string representation of the virtual package.
565    pub fn as_str(&self) -> &str {
566        match self {
567            Archspec::Microarchitecture(arch) => arch.name(),
568            Archspec::Unknown => "0",
569        }
570    }
571
572    /// Returns the current CPU architecture or `Archspec::Unknown` if the
573    /// architecture could not be determined.
574    pub fn current() -> Self {
575        archspec::cpu::host()
576            .ok()
577            .map(Into::into)
578            .or_else(|| Self::from_platform(Platform::current()))
579            .unwrap_or(Archspec::Unknown)
580    }
581
582    /// Returns the minimal supported archspec architecture for the given
583    /// platform.
584    #[allow(clippy::match_same_arms)]
585    pub fn from_platform(platform: Platform) -> Option<Self> {
586        // The values are taken from the archspec-json library.
587        // See: https://github.com/archspec/archspec-json/blob/master/cpu/microarchitectures.json
588        let archspec_name = match platform {
589            Platform::NoArch | Platform::Unknown => return None,
590            Platform::EmscriptenWasm32 | Platform::WasiWasm32 => return None,
591            Platform::Win32 | Platform::Linux32 => "x86",
592            Platform::Win64 | Platform::Osx64 | Platform::Linux64 => "x86_64",
593            Platform::LinuxAarch64 | Platform::LinuxArmV6l | Platform::LinuxArmV7l => "aarch64",
594            Platform::LinuxLoong64 => "loong64",
595            Platform::LinuxPpc64le => "ppc64le",
596            Platform::LinuxPpc64 => "ppc64",
597            Platform::LinuxPpc => "ppc",
598            Platform::LinuxS390X => "s390x",
599            Platform::LinuxRiscv32 => "riscv32",
600            Platform::LinuxRiscv64 => "riscv64",
601            // IBM Zos is a special case. It is not supported by archspec as far as I can see.
602            Platform::ZosZ => return None,
603
604            // TODO: There must be a minimal aarch64 version that windows supports.
605            Platform::WinArm64 => "aarch64",
606
607            // The first every Apple Silicon Macs are based on m1.
608            Platform::OsxArm64 => "m1",
609
610            // Otherwise, we assume that the architecture is unknown.
611            _ => return None,
612        };
613
614        Some(Self::from_name(archspec_name))
615    }
616
617    /// Constructs an `Archspec` from the given `archspec_name`. Creates a
618    /// "generic" architecture if the name is not known.
619    pub fn from_name(archspec_name: &str) -> Self {
620        Microarchitecture::known_targets()
621            .get(archspec_name)
622            .cloned()
623            .unwrap_or_else(|| Arc::new(archspec::cpu::Microarchitecture::generic(archspec_name)))
624            .into()
625    }
626}
627
628impl From<Archspec> for GenericVirtualPackage {
629    fn from(archspec: Archspec) -> Self {
630        GenericVirtualPackage {
631            name: PackageName::new_unchecked("__archspec"),
632            version: Version::major(1),
633            build_string: archspec.to_string(),
634        }
635    }
636}
637
638impl From<Archspec> for VirtualPackage {
639    fn from(archspec: Archspec) -> Self {
640        VirtualPackage::Archspec(archspec)
641    }
642}
643
644impl EnvOverride for Archspec {
645    fn parse_version(value: &str) -> Result<Self, ParseVersionError> {
646        if value == "0" {
647            Ok(Archspec::Unknown)
648        } else {
649            Ok(Self::from_name(value))
650        }
651    }
652
653    const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_ARCHSPEC";
654
655    fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
656        Ok(Some(Self::current()))
657    }
658}
659
660/// OSX virtual package description
661#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
662pub struct Osx {
663    /// The OSX version
664    pub version: Version,
665}
666
667impl Osx {
668    /// Returns the OSX version of the current platform.
669    ///
670    /// Returns an error if determining the OSX version resulted in an error.
671    /// Returns `None` if the current platform is not an OSX based platform.
672    pub fn current() -> Result<Option<Self>, ParseOsxVersionError> {
673        Ok(osx::osx_version()?.map(|version| Self { version }))
674    }
675}
676
677impl From<Osx> for GenericVirtualPackage {
678    fn from(osx: Osx) -> Self {
679        GenericVirtualPackage {
680            name: PackageName::new_unchecked("__osx"),
681            version: osx.version,
682            build_string: "0".into(),
683        }
684    }
685}
686
687impl From<Osx> for VirtualPackage {
688    fn from(osx: Osx) -> Self {
689        VirtualPackage::Osx(osx)
690    }
691}
692
693impl From<Version> for Osx {
694    fn from(version: Version) -> Self {
695        Self { version }
696    }
697}
698
699impl EnvOverride for Osx {
700    fn parse_version(env_var_value: &str) -> Result<Self, ParseVersionError> {
701        Version::from_str(env_var_value).map(|version| Self { version })
702    }
703    fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
704        Ok(Self::current()?)
705    }
706    const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_OSX";
707}
708
709/// Windows virtual package description
710#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
711pub struct Windows {
712    /// The version of windows
713    pub version: Option<Version>,
714}
715
716impl Windows {
717    /// Returns the Windows version of the current platform.
718    ///
719    /// Returns `None` if the current platform is not a Windows based platform.
720    pub fn current() -> Option<Self> {
721        if cfg!(target_os = "windows") {
722            Some(Self {
723                version: win::windows_version(),
724            })
725        } else {
726            None
727        }
728    }
729}
730
731impl From<Windows> for GenericVirtualPackage {
732    fn from(windows: Windows) -> Self {
733        GenericVirtualPackage {
734            name: PackageName::new_unchecked("__win"),
735            version: windows.version.unwrap_or_else(|| Version::major(0)),
736            build_string: "0".into(),
737        }
738    }
739}
740
741impl From<Windows> for VirtualPackage {
742    fn from(windows: Windows) -> Self {
743        VirtualPackage::Win(windows)
744    }
745}
746
747impl From<Version> for Windows {
748    fn from(version: Version) -> Self {
749        Self {
750            version: Some(version),
751        }
752    }
753}
754
755impl EnvOverride for Windows {
756    fn parse_version(env_var_value: &str) -> Result<Self, ParseVersionError> {
757        Version::from_str(env_var_value).map(|version| Self {
758            version: Some(version),
759        })
760    }
761    fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError> {
762        Ok(Self::current())
763    }
764    const DEFAULT_ENV_NAME: &'static str = "CONDA_OVERRIDE_WIN";
765}
766
767#[cfg(test)]
768mod test {
769    use std::{env, str::FromStr};
770
771    use rattler_conda_types::Version;
772
773    use super::*;
774
775    #[test]
776    fn doesnt_crash() {
777        let virtual_packages =
778            VirtualPackages::detect(&VirtualPackageOverrides::default()).unwrap();
779        println!("{virtual_packages:#?}");
780    }
781    #[test]
782    fn parse_libc() {
783        let v = "1.23";
784        let res = LibC {
785            version: Version::from_str(v).unwrap(),
786            family: "glibc".into(),
787        };
788        let env_var_name = format!("{}_{}", LibC::DEFAULT_ENV_NAME, "12345511231");
789        env::set_var(env_var_name.clone(), v);
790        assert_eq!(
791            LibC::detect(Some(&Override::EnvVar(env_var_name.clone())))
792                .unwrap()
793                .unwrap(),
794            res
795        );
796        env::set_var(env_var_name.clone(), "");
797        assert_eq!(
798            LibC::detect(Some(&Override::EnvVar(env_var_name.clone()))).unwrap(),
799            None
800        );
801        env::remove_var(env_var_name.clone());
802        assert_eq!(
803            LibC::detect_with_fallback(&Override::DefaultEnvVar, || Ok(Some(res.clone())))
804                .unwrap()
805                .unwrap(),
806            res
807        );
808        assert_eq!(
809            LibC::detect_with_fallback(&Override::String(v.to_string()), || Ok(None))
810                .unwrap()
811                .unwrap(),
812            res
813        );
814    }
815
816    #[test]
817    fn parse_cuda() {
818        let v = "1.234";
819        let res = Cuda {
820            version: Version::from_str(v).unwrap(),
821        };
822        let env_var_name = format!("{}_{}", Cuda::DEFAULT_ENV_NAME, "12345511231");
823        env::set_var(env_var_name.clone(), v);
824        assert_eq!(
825            Cuda::detect(Some(&Override::EnvVar(env_var_name.clone())))
826                .unwrap()
827                .unwrap(),
828            res
829        );
830        assert_eq!(
831            Cuda::detect(None).map_err(|_x| 1),
832            <Cuda as EnvOverride>::detect_from_host().map_err(|_x| 1)
833        );
834        env::remove_var(env_var_name.clone());
835        assert_eq!(
836            Cuda::detect(Some(&Override::String(v.to_string())))
837                .unwrap()
838                .unwrap(),
839            res
840        );
841    }
842
843    #[test]
844    fn parse_osx() {
845        let v = "2.345";
846        let res = Osx {
847            version: Version::from_str(v).unwrap(),
848        };
849        let env_var_name = format!("{}_{}", Osx::DEFAULT_ENV_NAME, "12345511231");
850        env::set_var(env_var_name.clone(), v);
851        assert_eq!(
852            Osx::detect(Some(&Override::EnvVar(env_var_name.clone())))
853                .unwrap()
854                .unwrap(),
855            res
856        );
857    }
858}