Skip to main content

pro_core/python/
platform.rs

1//! Platform detection for Python installations
2//!
3//! Detects the current operating system and architecture to determine
4//! the correct python-build-standalone release to download.
5
6use std::fmt;
7
8// Error is only used in cfg blocks for unsupported platforms
9#[allow(unused_imports)]
10use crate::Error;
11use crate::Result;
12
13/// Operating system
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum Os {
16    Linux,
17    MacOS,
18    Windows,
19}
20
21impl Os {
22    /// Detect the current operating system
23    pub fn current() -> Result<Self> {
24        #[cfg(target_os = "linux")]
25        return Ok(Os::Linux);
26
27        #[cfg(target_os = "macos")]
28        return Ok(Os::MacOS);
29
30        #[cfg(target_os = "windows")]
31        return Ok(Os::Windows);
32
33        #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
34        return Err(Error::UnsupportedPlatform(format!(
35            "unsupported operating system: {}",
36            std::env::consts::OS
37        )));
38    }
39
40    /// Get the OS string used in python-build-standalone releases
41    pub fn as_pbs_str(&self) -> &'static str {
42        match self {
43            Os::Linux => "unknown-linux-gnu",
44            Os::MacOS => "apple-darwin",
45            Os::Windows => "pc-windows-msvc",
46        }
47    }
48}
49
50impl fmt::Display for Os {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {
53            Os::Linux => write!(f, "linux"),
54            Os::MacOS => write!(f, "macos"),
55            Os::Windows => write!(f, "windows"),
56        }
57    }
58}
59
60/// CPU architecture
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62pub enum Arch {
63    X86_64,
64    Aarch64,
65}
66
67impl Arch {
68    /// Detect the current architecture
69    pub fn current() -> Result<Self> {
70        #[cfg(target_arch = "x86_64")]
71        return Ok(Arch::X86_64);
72
73        #[cfg(target_arch = "aarch64")]
74        return Ok(Arch::Aarch64);
75
76        #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
77        return Err(Error::UnsupportedPlatform(format!(
78            "unsupported architecture: {}",
79            std::env::consts::ARCH
80        )));
81    }
82
83    /// Get the architecture string used in python-build-standalone releases
84    pub fn as_pbs_str(&self) -> &'static str {
85        match self {
86            Arch::X86_64 => "x86_64",
87            Arch::Aarch64 => "aarch64",
88        }
89    }
90}
91
92impl fmt::Display for Arch {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        match self {
95            Arch::X86_64 => write!(f, "x86_64"),
96            Arch::Aarch64 => write!(f, "aarch64"),
97        }
98    }
99}
100
101/// Platform (OS + Architecture combination)
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
103pub struct Platform {
104    pub os: Os,
105    pub arch: Arch,
106}
107
108impl Platform {
109    /// Create a new platform
110    pub fn new(os: Os, arch: Arch) -> Self {
111        Self { os, arch }
112    }
113
114    /// Detect the current platform
115    pub fn current() -> Result<Self> {
116        Ok(Self {
117            os: Os::current()?,
118            arch: Arch::current()?,
119        })
120    }
121
122    /// Get the platform triple for python-build-standalone releases
123    ///
124    /// Example: "x86_64-unknown-linux-gnu", "aarch64-apple-darwin"
125    pub fn triple(&self) -> String {
126        format!("{}-{}", self.arch.as_pbs_str(), self.os.as_pbs_str())
127    }
128
129    /// Get the archive extension for this platform
130    pub fn archive_ext(&self) -> &'static str {
131        match self.os {
132            Os::Windows => "zip",
133            _ => "tar.zst",
134        }
135    }
136
137    /// Check if this platform supports the pgo+lto optimized builds
138    pub fn supports_optimized(&self) -> bool {
139        // Most platforms support optimized builds now
140        true
141    }
142}
143
144impl fmt::Display for Platform {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        write!(f, "{}-{}", self.arch, self.os)
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn test_platform_current() {
156        let platform = Platform::current();
157        assert!(platform.is_ok(), "Should detect current platform");
158    }
159
160    #[test]
161    fn test_platform_triple() {
162        let platform = Platform::new(Os::Linux, Arch::X86_64);
163        assert_eq!(platform.triple(), "x86_64-unknown-linux-gnu");
164
165        let platform = Platform::new(Os::MacOS, Arch::Aarch64);
166        assert_eq!(platform.triple(), "aarch64-apple-darwin");
167
168        let platform = Platform::new(Os::Windows, Arch::X86_64);
169        assert_eq!(platform.triple(), "x86_64-pc-windows-msvc");
170    }
171
172    #[test]
173    fn test_archive_ext() {
174        let linux = Platform::new(Os::Linux, Arch::X86_64);
175        assert_eq!(linux.archive_ext(), "tar.zst");
176
177        let windows = Platform::new(Os::Windows, Arch::X86_64);
178        assert_eq!(windows.archive_ext(), "zip");
179    }
180}