1use std::fmt;
19use std::hash::Hash;
20use std::str::FromStr;
21
22use crate::Error;
23use gentoo_interner::{DefaultInterner, Interned, Interner};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52pub enum KnownArch {
53 Arm,
54 AArch64,
55 X86,
56 X86_64,
57 Riscv32,
58 Riscv64,
59 Powerpc,
60 Powerpc64,
61 Mips,
62 Mips64,
63 Sparc,
64 Sparc64,
65 S390x,
66 M68k,
67 LoongArch64,
68 Alpha,
69 Hppa,
70 Ia64,
71}
72
73impl KnownArch {
74 pub fn as_keyword(&self) -> &'static str {
76 match self {
77 KnownArch::Arm => "arm",
78 KnownArch::AArch64 => "arm64",
79 KnownArch::X86 => "x86",
80 KnownArch::X86_64 => "amd64",
81 KnownArch::Riscv32 | KnownArch::Riscv64 => "riscv",
82 KnownArch::Powerpc => "ppc",
83 KnownArch::Powerpc64 => "ppc64",
84 KnownArch::Mips | KnownArch::Mips64 => "mips",
85 KnownArch::Sparc | KnownArch::Sparc64 => "sparc",
86 KnownArch::S390x => "s390",
87 KnownArch::M68k => "m68k",
88 KnownArch::LoongArch64 => "loong",
89 KnownArch::Alpha => "alpha",
90 KnownArch::Hppa => "hppa",
91 KnownArch::Ia64 => "ia64",
92 }
93 }
94
95 pub fn parse(arch: &str) -> Result<Self, Error> {
97 match arch.to_lowercase().as_str() {
98 "arm" | "armv7" | "armv7a" | "armv7l" | "armv7hl" => Ok(KnownArch::Arm),
99 "aarch64" | "arm64" | "armv8" | "armv8a" => Ok(KnownArch::AArch64),
100 "x86" | "i386" | "i486" | "i586" | "i686" => Ok(KnownArch::X86),
101 "x86_64" | "amd64" => Ok(KnownArch::X86_64),
102 "riscv32" => Ok(KnownArch::Riscv32),
103 "riscv64" | "riscv" => Ok(KnownArch::Riscv64),
104 "powerpc" | "ppc" => Ok(KnownArch::Powerpc),
105 "powerpc64" | "ppc64" => Ok(KnownArch::Powerpc64),
106 "mips" => Ok(KnownArch::Mips),
107 "mips64" => Ok(KnownArch::Mips64),
108 "sparc" => Ok(KnownArch::Sparc),
109 "sparc64" => Ok(KnownArch::Sparc64),
110 "s390" | "s390x" => Ok(KnownArch::S390x),
111 "m68k" => Ok(KnownArch::M68k),
112 "loong" | "loongarch64" => Ok(KnownArch::LoongArch64),
113 "alpha" => Ok(KnownArch::Alpha),
114 "hppa" => Ok(KnownArch::Hppa),
115 "ia64" => Ok(KnownArch::Ia64),
116 _ => Err(Error::ParseError(format!("Unknown architecture: {arch}"))),
117 }
118 }
119
120 pub fn bitness(&self) -> u32 {
122 match self {
123 KnownArch::Arm
124 | KnownArch::X86
125 | KnownArch::Riscv32
126 | KnownArch::Powerpc
127 | KnownArch::Mips
128 | KnownArch::Sparc
129 | KnownArch::M68k
130 | KnownArch::Hppa => 32,
131 KnownArch::AArch64
132 | KnownArch::X86_64
133 | KnownArch::Riscv64
134 | KnownArch::Powerpc64
135 | KnownArch::Mips64
136 | KnownArch::Sparc64
137 | KnownArch::S390x
138 | KnownArch::LoongArch64
139 | KnownArch::Alpha
140 | KnownArch::Ia64 => 64,
141 }
142 }
143
144 pub fn current() -> Result<Self, Error> {
146 Self::parse(std::env::consts::ARCH)
147 }
148}
149
150impl fmt::Display for KnownArch {
151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 let name = match self {
153 KnownArch::Arm => "arm",
154 KnownArch::AArch64 => "aarch64",
155 KnownArch::X86 => "x86",
156 KnownArch::X86_64 => "x86_64",
157 KnownArch::Riscv32 => "riscv32",
158 KnownArch::Riscv64 => "riscv64",
159 KnownArch::Powerpc => "powerpc",
160 KnownArch::Powerpc64 => "powerpc64",
161 KnownArch::Mips => "mips",
162 KnownArch::Mips64 => "mips64",
163 KnownArch::Sparc => "sparc",
164 KnownArch::Sparc64 => "sparc64",
165 KnownArch::S390x => "s390x",
166 KnownArch::M68k => "m68k",
167 KnownArch::LoongArch64 => "loongarch64",
168 KnownArch::Alpha => "alpha",
169 KnownArch::Hppa => "hppa",
170 KnownArch::Ia64 => "ia64",
171 };
172 write!(f, "{name}")
173 }
174}
175
176impl FromStr for KnownArch {
177 type Err = Error;
178 fn from_str(s: &str) -> Result<Self, Self::Err> {
179 Self::parse(s)
180 }
181}
182
183pub type ExoticKey<I> = Interned<I>;
190
191#[derive(Debug, PartialEq, Eq, Hash)]
222pub enum Arch<I = DefaultInterner>
223where
224 I: Interner,
225{
226 Known(KnownArch),
228 Exotic(ExoticKey<I>),
230}
231
232impl<I: Interner> Clone for Arch<I> {
233 fn clone(&self) -> Self {
234 match self {
235 Self::Known(arch) => Self::Known(*arch),
236 Self::Exotic(key) => Self::Exotic(key.clone()),
237 }
238 }
239}
240
241impl<I: Interner> Copy for Arch<I> where Interned<I>: Copy {}
242
243impl<I: Interner> Arch<I> {
244 pub fn intern(keyword: &str) -> Self {
246 if let Ok(known) = KnownArch::parse(keyword) {
247 Self::Known(known)
248 } else {
249 Self::Exotic(ExoticKey::intern(keyword))
250 }
251 }
252
253 pub fn current() -> Self {
257 Self::intern(std::env::consts::ARCH)
258 }
259
260 pub fn from_chost(chost: &str) -> Option<Self> {
264 let cpu = chost.split('-').next().filter(|s| !s.is_empty())?;
265 Some(Self::intern(&normalize_chost_cpu(cpu)))
266 }
267
268 pub fn as_str(&self) -> &str {
270 match self {
271 Self::Known(arch) => arch.as_keyword(),
272 Self::Exotic(key) => key.resolve(),
273 }
274 }
275
276 pub fn as_keyword(&self) -> &str {
281 self.as_str()
282 }
283}
284
285impl<I: Interner> fmt::Display for Arch<I> {
286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 f.write_str(self.as_str())
288 }
289}
290
291impl<I: Interner> PartialEq<str> for Arch<I> {
292 fn eq(&self, other: &str) -> bool {
293 self.as_str() == other
294 }
295}
296
297impl<I: Interner> PartialEq<&str> for Arch<I> {
298 fn eq(&self, other: &&str) -> bool {
299 self.as_str() == *other
300 }
301}
302
303impl<I: Interner> PartialEq<String> for Arch<I> {
304 fn eq(&self, other: &String) -> bool {
305 self.as_str() == other.as_str()
306 }
307}
308
309impl<I: Interner> FromStr for Arch<I> {
310 type Err = std::convert::Infallible;
311
312 fn from_str(s: &str) -> Result<Self, Self::Err> {
313 match KnownArch::from_str(s) {
314 Ok(known) => Ok(Self::Known(known)),
315 Err(_) => Ok(Self::Exotic(ExoticKey::intern(s))),
316 }
317 }
318}
319
320fn normalize_chost_cpu(cpu: &str) -> String {
322 let s = cpu.to_lowercase();
323
324 for suffix in &["le", "be"] {
326 if let Some(base) = s.strip_suffix(suffix)
327 && base == "powerpc64"
328 {
329 return base.to_string();
330 }
331 }
332
333 for suffix in &["el", "eb"] {
335 if let Some(base) = s.strip_suffix(suffix)
336 && (base == "mips" || base == "mips64")
337 {
338 return base.to_string();
339 }
340 }
341
342 if let Some(after_riscv) = s.strip_prefix("riscv") {
344 if let Some(end) = after_riscv.find(|c: char| !c.is_ascii_digit())
345 && end > 0
346 {
347 return format!("riscv{}", &after_riscv[..end]);
348 }
349 return s;
350 }
351
352 if s.starts_with("hppa") && s.len() > "hppa".len() {
354 return "hppa".to_string();
355 }
356
357 s
358}
359
360#[cfg(feature = "serde")]
365impl<I: Interner> serde::Serialize for Arch<I> {
366 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
367 serializer.serialize_str(self.as_str())
368 }
369}
370
371#[cfg(feature = "serde")]
373impl<'de, I: Interner> serde::Deserialize<'de> for Arch<I> {
374 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
375 let s = <String as serde::Deserialize<'de>>::deserialize(deserializer)?;
376 Ok(Self::intern(&s))
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383
384 #[test]
387 fn known_arch_keywords() {
388 assert_eq!(KnownArch::Arm.as_keyword(), "arm");
389 assert_eq!(KnownArch::AArch64.as_keyword(), "arm64");
390 assert_eq!(KnownArch::X86.as_keyword(), "x86");
391 assert_eq!(KnownArch::X86_64.as_keyword(), "amd64");
392 assert_eq!(KnownArch::Riscv32.as_keyword(), "riscv");
393 assert_eq!(KnownArch::Riscv64.as_keyword(), "riscv");
394 assert_eq!(KnownArch::Powerpc.as_keyword(), "ppc");
395 assert_eq!(KnownArch::Powerpc64.as_keyword(), "ppc64");
396 assert_eq!(KnownArch::LoongArch64.as_keyword(), "loong");
397 assert_eq!(KnownArch::Hppa.as_keyword(), "hppa");
398 }
399
400 #[test]
401 fn known_arch_parsing() {
402 assert!(KnownArch::parse("arm").is_ok());
403 assert!(KnownArch::parse("amd64").is_ok());
404 assert!(KnownArch::parse("AMD64").is_ok());
405 assert!(KnownArch::parse("invalid").is_err());
406 }
407
408 #[test]
409 fn known_arch_from_str() {
410 assert_eq!("amd64".parse::<KnownArch>().unwrap(), KnownArch::X86_64);
411 assert!("invalid".parse::<KnownArch>().is_err());
412 }
413
414 #[test]
417 fn arch_intern_known() {
418 assert!(matches!(<Arch>::intern("amd64"), Arch::Known(_)));
419 assert!(matches!(<Arch>::intern("arm64"), Arch::Known(_)));
420 assert!(matches!(<Arch>::intern("loong"), Arch::Known(_)));
421 assert!(matches!(<Arch>::intern("hppa"), Arch::Known(_)));
422 }
423
424 #[test]
425 fn arch_intern_exotic() {
426 let a1: Arch = Arch::intern("mymachine");
427 assert!(matches!(a1, Arch::Exotic(_)));
428 assert_eq!(Arch::intern("mymachine"), a1); assert_eq!(a1.as_str(), "mymachine");
430 }
431
432 #[test]
433 fn arch_from_chost_known() {
434 let cases = [
435 ("x86_64-pc-linux-gnu", "amd64"),
436 ("aarch64-unknown-linux-gnu", "arm64"),
437 ("i686-pc-linux-gnu", "x86"),
438 ("powerpc-unknown-linux-gnu", "ppc"),
439 ("s390x-linux-gnu", "s390"),
440 ];
441 for (chost, expected) in cases {
442 let arch: Arch = Arch::from_chost(chost).unwrap();
443 assert_eq!(arch.as_str(), expected, "chost={chost}");
444 assert!(
445 matches!(arch, Arch::Known(_)),
446 "chost={chost} should be Known"
447 );
448 }
449 }
450
451 #[test]
452 fn arch_chost_normalization() {
453 let cases = [
454 ("powerpc64le-unknown-linux-gnu", "ppc64"),
455 ("riscv64gc-unknown-linux-gnu", "riscv"),
456 ("mipsel-unknown-linux-gnu", "mips"),
457 ("mips64el-unknown-linux-gnu", "mips"),
458 ("hppa2.0w-hp-linux-gnu", "hppa"),
459 ];
460 for (chost, expected) in cases {
461 assert_eq!(
462 <Arch>::from_chost(chost).unwrap().as_str(),
463 expected,
464 "chost={chost}"
465 );
466 }
467 }
468
469 #[test]
470 fn arch_empty_chost() {
471 assert!(<Arch>::from_chost("").is_none());
472 }
473
474 #[test]
475 fn arch_from_str_known() {
476 let arch: Arch = "amd64".parse().unwrap();
477 assert!(matches!(arch, Arch::Known(KnownArch::X86_64)));
478 assert_eq!(arch.as_str(), "amd64");
479
480 let arch: Arch = "arm64".parse().unwrap();
481 assert!(matches!(arch, Arch::Known(KnownArch::AArch64)));
482 assert_eq!(arch.as_str(), "arm64");
483 }
484
485 #[test]
486 fn arch_from_str_exotic() {
487 let arch: Arch = "mymachine".parse().unwrap();
488 assert!(matches!(arch, Arch::Exotic(_)));
489 assert_eq!(arch.as_str(), "mymachine");
490 }
491
492 #[cfg(feature = "serde")]
495 mod serde {
496 use super::*;
497
498 #[test]
499 fn arch_known_serializes_as_keyword() {
500 let arch: Arch = Arch::intern("amd64");
501 let json = serde_json::to_string(&arch).unwrap();
502 assert_eq!(json, r#""amd64""#);
503 }
504
505 #[test]
506 fn arch_exotic_serializes_as_string() {
507 let arch: Arch = Arch::intern("mymachine");
508 let json = serde_json::to_string(&arch).unwrap();
509 assert_eq!(json, r#""mymachine""#);
510 }
511
512 #[test]
513 fn arch_known_roundtrip() {
514 let original = Arch::intern("arm64");
515 let json = serde_json::to_string(&original).unwrap();
516 let restored: Arch = serde_json::from_str(&json).unwrap();
517 assert_eq!(original, restored);
518 assert!(matches!(restored, Arch::Known(KnownArch::AArch64)));
519 }
520
521 #[test]
522 fn arch_exotic_roundtrip() {
523 let original = Arch::intern("mymachine");
524 let json = serde_json::to_string(&original).unwrap();
525 let restored: Arch = serde_json::from_str(&json).unwrap();
526 assert_eq!(original, restored);
527 assert!(matches!(restored, Arch::Exotic(_)));
528 }
529
530 #[test]
531 fn arch_deserialize_known_from_alias() {
532 let restored: Arch = serde_json::from_str(r#""x86_64""#).unwrap();
533 assert_eq!(restored, Arch::intern("amd64"));
534 }
535 }
536}