1use crate::{Error, Os};
2use std::str::FromStr;
3use target_lexicon::Architecture;
4
5#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd)]
7pub enum ArchVariant {
8 V2,
11 V3,
14 V4,
17}
18
19#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
20pub struct Arch {
21 family: target_lexicon::Architecture,
22 variant: Option<ArchVariant>,
23}
24
25impl Ord for Arch {
26 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
27 self.cmp_for_os(*other, Os::from_env(), Self::from_env())
28 }
29}
30
31impl Arch {
32 pub(crate) fn cmp_for_os(self, other: Self, os: Os, native: Self) -> std::cmp::Ordering {
34 if self.family == other.family {
35 return self.variant.cmp(&other.variant);
36 }
37
38 self.preference(os, native)
39 .cmp(&other.preference(os, native))
40 .then_with(|| self.family.to_string().cmp(&other.family.to_string()))
42 }
43
44 fn preference(self, os: Os, native: Self) -> u8 {
47 if self.family == native.family {
49 return 0;
50 }
51
52 if !os.is_windows() {
53 return 3;
54 }
55
56 match (native.family, self.family) {
60 (Architecture::Aarch64(_), Architecture::X86_64) => 1,
61 (Architecture::Aarch64(_) | Architecture::X86_64, Architecture::X86_32(_)) => 2,
62 _ => 3,
63 }
64 }
65}
66
67impl PartialOrd for Arch {
68 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
69 Some(self.cmp(other))
70 }
71}
72impl Arch {
73 pub fn new(family: target_lexicon::Architecture, variant: Option<ArchVariant>) -> Self {
74 Self { family, variant }
75 }
76
77 pub fn from_env() -> Self {
78 #[cfg(test)]
79 {
80 if let Some(arch) = test_support::get_mock_arch() {
81 return arch;
82 }
83 }
84
85 Self {
86 family: target_lexicon::HOST.architecture,
87 variant: None,
88 }
89 }
90
91 pub fn family(&self) -> target_lexicon::Architecture {
92 self.family
93 }
94
95 pub(crate) fn is_wasm(self) -> bool {
96 matches!(self.family, target_lexicon::Architecture::Wasm32)
97 }
98}
99
100impl std::fmt::Display for Arch {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 match self.family {
103 target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686) => {
104 write!(f, "x86")?;
105 }
106 inner => write!(f, "{inner}")?,
107 }
108 if let Some(variant) = self.variant {
109 write!(f, "_{variant}")?;
110 }
111 Ok(())
112 }
113}
114
115impl FromStr for Arch {
116 type Err = Error;
117
118 fn from_str(s: &str) -> Result<Self, Self::Err> {
119 fn parse_family(s: &str) -> Result<target_lexicon::Architecture, Error> {
120 let inner = match s {
121 "x86" => {
124 target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)
125 }
126 _ => target_lexicon::Architecture::from_str(s)
127 .map_err(|()| Error::UnknownArch(s.to_string()))?,
128 };
129 if matches!(inner, target_lexicon::Architecture::Unknown) {
130 return Err(Error::UnknownArch(s.to_string()));
131 }
132 Ok(inner)
133 }
134
135 if let Some((Ok(family), Ok(variant))) = s
137 .rsplit_once('_')
138 .map(|(family, variant)| (parse_family(family), ArchVariant::from_str(variant)))
139 {
140 if !matches!(family, target_lexicon::Architecture::X86_64) {
142 return Err(Error::UnsupportedVariant(
143 variant.to_string(),
144 family.to_string(),
145 ));
146 }
147 return Ok(Self {
148 family,
149 variant: Some(variant),
150 });
151 }
152
153 let family = parse_family(s)?;
154
155 Ok(Self {
156 family,
157 variant: None,
158 })
159 }
160}
161
162impl FromStr for ArchVariant {
163 type Err = ();
164
165 fn from_str(s: &str) -> Result<Self, Self::Err> {
166 match s {
167 "v2" => Ok(Self::V2),
168 "v3" => Ok(Self::V3),
169 "v4" => Ok(Self::V4),
170 _ => Err(()),
171 }
172 }
173}
174
175impl std::fmt::Display for ArchVariant {
176 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177 match self {
178 Self::V2 => write!(f, "v2"),
179 Self::V3 => write!(f, "v3"),
180 Self::V4 => write!(f, "v4"),
181 }
182 }
183}
184
185impl From<&uv_platform_tags::Arch> for Arch {
186 fn from(value: &uv_platform_tags::Arch) -> Self {
187 match value {
188 uv_platform_tags::Arch::Aarch64 => Self::new(
189 target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64),
190 None,
191 ),
192 uv_platform_tags::Arch::Armv5TEL => Self::new(
193 target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv5te),
194 None,
195 ),
196 uv_platform_tags::Arch::Armv6L => Self::new(
197 target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv6),
198 None,
199 ),
200 uv_platform_tags::Arch::Armv7L => Self::new(
201 target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7),
202 None,
203 ),
204 uv_platform_tags::Arch::S390X => Self::new(target_lexicon::Architecture::S390x, None),
205 uv_platform_tags::Arch::Powerpc => {
206 Self::new(target_lexicon::Architecture::Powerpc, None)
207 }
208 uv_platform_tags::Arch::Powerpc64 => {
209 Self::new(target_lexicon::Architecture::Powerpc64, None)
210 }
211 uv_platform_tags::Arch::Powerpc64Le => {
212 Self::new(target_lexicon::Architecture::Powerpc64le, None)
213 }
214 uv_platform_tags::Arch::X86 => Self::new(
215 target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686),
216 None,
217 ),
218 uv_platform_tags::Arch::X86_64 => Self::new(target_lexicon::Architecture::X86_64, None),
219 uv_platform_tags::Arch::LoongArch64 => {
220 Self::new(target_lexicon::Architecture::LoongArch64, None)
221 }
222 uv_platform_tags::Arch::Riscv64 => Self::new(
223 target_lexicon::Architecture::Riscv64(target_lexicon::Riscv64Architecture::Riscv64),
224 None,
225 ),
226 uv_platform_tags::Arch::Wasm32 => Self::new(target_lexicon::Architecture::Wasm32, None),
227 }
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use std::str::FromStr;
234
235 use super::test_support::{aarch64, run_with_arch, x86_64};
236 use super::{Arch, Os};
237
238 #[test]
239 fn test_arch_sorting_prefers_native() {
240 run_with_arch(aarch64(), || {
241 assert!(aarch64() < x86_64());
242 });
243 run_with_arch(x86_64(), || {
244 assert!(x86_64() < aarch64());
245 });
246 }
247
248 #[test]
249 fn test_arch_sorting_for_os() {
250 let x86 = Arch::from_str("x86").expect("valid architecture");
251 let windows = Os::from_str("windows").expect("valid operating system");
252 let linux = Os::from_str("linux").expect("valid operating system");
253
254 for (os, native, expected) in [
255 (windows, aarch64(), [aarch64(), x86_64(), x86]),
256 (windows, x86_64(), [x86_64(), x86, aarch64()]),
257 (windows, x86, [x86, aarch64(), x86_64()]),
258 (linux, aarch64(), [aarch64(), x86, x86_64()]),
259 (linux, x86_64(), [x86_64(), aarch64(), x86]),
260 (linux, x86, [x86, aarch64(), x86_64()]),
261 ] {
262 let mut architectures = [x86, x86_64(), aarch64()];
263 architectures.sort_by(|left, right| left.cmp_for_os(*right, os, native));
264 assert_eq!(architectures, expected, "{os} on {native}");
265 }
266 }
267}
268
269#[cfg(test)]
270pub(crate) mod test_support {
271 use super::*;
272 use std::cell::RefCell;
273
274 thread_local! {
275 static MOCK_ARCH: RefCell<Option<Arch>> = const { RefCell::new(None) };
276 }
277
278 pub(super) fn get_mock_arch() -> Option<Arch> {
279 MOCK_ARCH.with(|arch| *arch.borrow())
280 }
281
282 fn set_mock_arch(arch: Option<Arch>) {
283 MOCK_ARCH.with(|mock| *mock.borrow_mut() = arch);
284 }
285
286 struct MockArchGuard {
287 previous: Option<Arch>,
288 }
289
290 impl MockArchGuard {
291 fn new(arch: Arch) -> Self {
292 let previous = get_mock_arch();
293 set_mock_arch(Some(arch));
294 Self { previous }
295 }
296 }
297
298 impl Drop for MockArchGuard {
299 fn drop(&mut self) {
300 set_mock_arch(self.previous);
301 }
302 }
303
304 pub(crate) fn run_with_arch<F, R>(arch: Arch, f: F) -> R
307 where
308 F: FnOnce() -> R,
309 {
310 let _guard = MockArchGuard::new(arch);
311 f()
312 }
313
314 pub(crate) fn x86_64() -> Arch {
315 Arch::new(target_lexicon::Architecture::X86_64, None)
316 }
317
318 pub(crate) fn aarch64() -> Arch {
319 Arch::new(
320 target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64),
321 None,
322 )
323 }
324}