1#![deny(missing_docs)]
2
3pub 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#[derive(Clone, Debug, PartialEq, Default)]
62pub enum Override {
63 #[default]
65 DefaultEnvVar,
66 EnvVar(String),
68 String(String),
70}
71
72pub trait EnvOverride: Sized {
75 fn parse_version(value: &str) -> Result<Self, ParseVersionError>;
77
78 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 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 const DEFAULT_ENV_NAME: &'static str;
111
112 fn detect_from_host() -> Result<Option<Self>, DetectVirtualPackageError>;
118
119 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 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#[derive(Clone, Eq, PartialEq, Hash, Debug)]
146pub enum VirtualPackage {
147 Win(Windows),
149
150 Unix,
152
153 Linux(Linux),
155
156 Osx(Osx),
158
159 LibC(LibC),
161
162 Cuda(Cuda),
164
165 Archspec(Archspec),
167}
168
169#[derive(Debug, Clone, Default)]
171pub struct VirtualPackages {
172 pub win: Option<Windows>,
174
175 pub unix: bool,
177
178 pub linux: Option<Linux>,
180
181 pub osx: Option<Osx>,
183
184 pub libc: Option<LibC>,
186
187 pub cuda: Option<Cuda>,
189
190 pub archspec: Option<Archspec>,
192}
193
194impl VirtualPackages {
195 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 pub fn into_generic_virtual_packages(self) -> impl Iterator<Item = GenericVirtualPackage> {
222 self.into_virtual_packages().map(Into::into)
223 }
224
225 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 #[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 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#[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#[derive(Default, Clone, Debug)]
307pub struct VirtualPackageOverrides {
308 pub win: Option<Override>,
310 pub osx: Option<Override>,
312 pub linux: Option<Override>,
314 pub libc: Option<Override>,
316 pub cuda: Option<Override>,
318 pub archspec: Option<Override>,
320}
321
322impl VirtualPackageOverrides {
323 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 pub fn from_env() -> Self {
339 Self::all(Override::DefaultEnvVar)
340 }
341}
342
343#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
345pub struct Linux {
346 pub version: Version,
348}
349
350impl Linux {
351 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#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
397pub struct LibC {
398 pub family: String,
400
401 pub version: Version,
403}
404
405impl LibC {
406 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 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#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
460pub struct Cuda {
461 pub version: Version,
463}
464
465impl Cuda {
466 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#[derive(Clone, Debug)]
506pub enum Archspec {
507 Microarchitecture(Arc<Microarchitecture>),
509
510 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 pub fn as_str(&self) -> &str {
566 match self {
567 Archspec::Microarchitecture(arch) => arch.name(),
568 Archspec::Unknown => "0",
569 }
570 }
571
572 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 #[allow(clippy::match_same_arms)]
585 pub fn from_platform(platform: Platform) -> Option<Self> {
586 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 Platform::ZosZ => return None,
603
604 Platform::WinArm64 => "aarch64",
606
607 Platform::OsxArm64 => "m1",
609
610 _ => return None,
612 };
613
614 Some(Self::from_name(archspec_name))
615 }
616
617 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#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
662pub struct Osx {
663 pub version: Version,
665}
666
667impl Osx {
668 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#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
711pub struct Windows {
712 pub version: Option<Version>,
714}
715
716impl Windows {
717 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}