uv_platform/
arch.rs

1use crate::Error;
2use std::str::FromStr;
3
4/// Architecture variants, e.g., with support for different instruction sets
5#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd)]
6pub enum ArchVariant {
7    /// Targets 64-bit Intel/AMD CPUs newer than Nehalem (2008).
8    /// Includes SSE3, SSE4 and other post-2003 CPU instructions.
9    V2,
10    /// Targets 64-bit Intel/AMD CPUs newer than Haswell (2013) and Excavator (2015).
11    /// Includes AVX, AVX2, MOVBE and other newer CPU instructions.
12    V3,
13    /// Targets 64-bit Intel/AMD CPUs with AVX-512 instructions (post-2017 Intel CPUs).
14    /// Many post-2017 Intel CPUs do not support AVX-512.
15    V4,
16}
17
18#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
19pub struct Arch {
20    pub(crate) family: target_lexicon::Architecture,
21    pub(crate) variant: Option<ArchVariant>,
22}
23
24impl Ord for Arch {
25    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
26        if self.family == other.family {
27            return self.variant.cmp(&other.variant);
28        }
29
30        // For the time being, manually make aarch64 windows disfavored
31        // on its own host platform, because most packages don't have wheels for
32        // aarch64 windows, making emulation more useful than native execution!
33        //
34        // The reason we do this in "sorting" and not "supports" is so that we don't
35        // *refuse* to use an aarch64 windows pythons if they happen to be installed
36        // and nothing else is available.
37        //
38        // Similarly if someone manually requests an aarch64 windows install, we
39        // should respect that request (this is the way users should "override"
40        // this behaviour).
41        let preferred = if cfg!(all(windows, target_arch = "aarch64")) {
42            Self {
43                family: target_lexicon::Architecture::X86_64,
44                variant: None,
45            }
46        } else {
47            // Prefer native architectures
48            Self::from_env()
49        };
50
51        match (
52            self.family == preferred.family,
53            other.family == preferred.family,
54        ) {
55            (true, true) => unreachable!(),
56            (true, false) => std::cmp::Ordering::Less,
57            (false, true) => std::cmp::Ordering::Greater,
58            (false, false) => {
59                // Both non-preferred, fallback to lexicographic order
60                self.family.to_string().cmp(&other.family.to_string())
61            }
62        }
63    }
64}
65
66impl PartialOrd for Arch {
67    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
68        Some(self.cmp(other))
69    }
70}
71impl Arch {
72    pub fn new(family: target_lexicon::Architecture, variant: Option<ArchVariant>) -> Self {
73        Self { family, variant }
74    }
75
76    pub fn from_env() -> Self {
77        #[cfg(test)]
78        {
79            if let Some(arch) = test_support::get_mock_arch() {
80                return arch;
81            }
82        }
83
84        Self {
85            family: target_lexicon::HOST.architecture,
86            variant: None,
87        }
88    }
89
90    pub fn family(&self) -> target_lexicon::Architecture {
91        self.family
92    }
93
94    pub fn is_arm(&self) -> bool {
95        matches!(self.family, target_lexicon::Architecture::Arm(_))
96    }
97
98    pub fn is_wasm(&self) -> bool {
99        matches!(self.family, target_lexicon::Architecture::Wasm32)
100    }
101}
102
103impl std::fmt::Display for Arch {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        match self.family {
106            target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686) => {
107                write!(f, "x86")?;
108            }
109            inner => write!(f, "{inner}")?,
110        }
111        if let Some(variant) = self.variant {
112            write!(f, "_{variant}")?;
113        }
114        Ok(())
115    }
116}
117
118impl FromStr for Arch {
119    type Err = Error;
120
121    fn from_str(s: &str) -> Result<Self, Self::Err> {
122        fn parse_family(s: &str) -> Result<target_lexicon::Architecture, Error> {
123            let inner = match s {
124                // Allow users to specify "x86" as a shorthand for the "i686" variant, they should not need
125                // to specify the exact architecture and this variant is what we have downloads for.
126                "x86" => {
127                    target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)
128                }
129                _ => target_lexicon::Architecture::from_str(s)
130                    .map_err(|()| Error::UnknownArch(s.to_string()))?,
131            };
132            if matches!(inner, target_lexicon::Architecture::Unknown) {
133                return Err(Error::UnknownArch(s.to_string()));
134            }
135            Ok(inner)
136        }
137
138        // First check for a variant
139        if let Some((Ok(family), Ok(variant))) = s
140            .rsplit_once('_')
141            .map(|(family, variant)| (parse_family(family), ArchVariant::from_str(variant)))
142        {
143            // We only support variants for `x86_64` right now
144            if !matches!(family, target_lexicon::Architecture::X86_64) {
145                return Err(Error::UnsupportedVariant(
146                    variant.to_string(),
147                    family.to_string(),
148                ));
149            }
150            return Ok(Self {
151                family,
152                variant: Some(variant),
153            });
154        }
155
156        let family = parse_family(s)?;
157
158        Ok(Self {
159            family,
160            variant: None,
161        })
162    }
163}
164
165impl FromStr for ArchVariant {
166    type Err = ();
167
168    fn from_str(s: &str) -> Result<Self, Self::Err> {
169        match s {
170            "v2" => Ok(Self::V2),
171            "v3" => Ok(Self::V3),
172            "v4" => Ok(Self::V4),
173            _ => Err(()),
174        }
175    }
176}
177
178impl std::fmt::Display for ArchVariant {
179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180        match self {
181            Self::V2 => write!(f, "v2"),
182            Self::V3 => write!(f, "v3"),
183            Self::V4 => write!(f, "v4"),
184        }
185    }
186}
187
188impl From<&uv_platform_tags::Arch> for Arch {
189    fn from(value: &uv_platform_tags::Arch) -> Self {
190        match value {
191            uv_platform_tags::Arch::Aarch64 => Self::new(
192                target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64),
193                None,
194            ),
195            uv_platform_tags::Arch::Armv5TEL => Self::new(
196                target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv5te),
197                None,
198            ),
199            uv_platform_tags::Arch::Armv6L => Self::new(
200                target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv6),
201                None,
202            ),
203            uv_platform_tags::Arch::Armv7L => Self::new(
204                target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7),
205                None,
206            ),
207            uv_platform_tags::Arch::S390X => Self::new(target_lexicon::Architecture::S390x, None),
208            uv_platform_tags::Arch::Powerpc => {
209                Self::new(target_lexicon::Architecture::Powerpc, None)
210            }
211            uv_platform_tags::Arch::Powerpc64 => {
212                Self::new(target_lexicon::Architecture::Powerpc64, None)
213            }
214            uv_platform_tags::Arch::Powerpc64Le => {
215                Self::new(target_lexicon::Architecture::Powerpc64le, None)
216            }
217            uv_platform_tags::Arch::X86 => Self::new(
218                target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686),
219                None,
220            ),
221            uv_platform_tags::Arch::X86_64 => Self::new(target_lexicon::Architecture::X86_64, None),
222            uv_platform_tags::Arch::LoongArch64 => {
223                Self::new(target_lexicon::Architecture::LoongArch64, None)
224            }
225            uv_platform_tags::Arch::Riscv64 => Self::new(
226                target_lexicon::Architecture::Riscv64(target_lexicon::Riscv64Architecture::Riscv64),
227                None,
228            ),
229            uv_platform_tags::Arch::Wasm32 => Self::new(target_lexicon::Architecture::Wasm32, None),
230        }
231    }
232}
233
234#[cfg(test)]
235pub(crate) mod test_support {
236    use super::*;
237    use std::cell::RefCell;
238
239    thread_local! {
240        static MOCK_ARCH: RefCell<Option<Arch>> = const { RefCell::new(None) };
241    }
242
243    pub(crate) fn get_mock_arch() -> Option<Arch> {
244        MOCK_ARCH.with(|arch| *arch.borrow())
245    }
246
247    fn set_mock_arch(arch: Option<Arch>) {
248        MOCK_ARCH.with(|mock| *mock.borrow_mut() = arch);
249    }
250
251    pub(crate) struct MockArchGuard {
252        previous: Option<Arch>,
253    }
254
255    impl MockArchGuard {
256        pub(crate) fn new(arch: Arch) -> Self {
257            let previous = get_mock_arch();
258            set_mock_arch(Some(arch));
259            Self { previous }
260        }
261    }
262
263    impl Drop for MockArchGuard {
264        fn drop(&mut self) {
265            set_mock_arch(self.previous);
266        }
267    }
268
269    /// Run a function with a mocked architecture.
270    /// The mock is automatically cleaned up after the function returns.
271    pub(crate) fn run_with_arch<F, R>(arch: Arch, f: F) -> R
272    where
273        F: FnOnce() -> R,
274    {
275        let _guard = MockArchGuard::new(arch);
276        f()
277    }
278
279    pub(crate) fn x86_64() -> Arch {
280        Arch::new(target_lexicon::Architecture::X86_64, None)
281    }
282
283    pub(crate) fn aarch64() -> Arch {
284        Arch::new(
285            target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64),
286            None,
287        )
288    }
289}