1use std::cmp;
4use std::fmt;
5use std::str::FromStr;
6use target_lexicon::Architecture;
7use thiserror::Error;
8use tracing::trace;
9
10pub use crate::arch::{Arch, ArchVariant};
11pub use crate::host::{LinuxOsRelease, OsRelease, OsType};
12pub use crate::libc::{Libc, LibcDetectionError, LibcVersion};
13pub use crate::os::Os;
14
15mod arch;
16mod cpuinfo;
17mod host;
18mod libc;
19mod os;
20
21#[derive(Error, Debug)]
22pub enum Error {
23 #[error("Unknown operating system: {0}")]
24 UnknownOs(String),
25 #[error("Unknown architecture: {0}")]
26 UnknownArch(String),
27 #[error("Unknown libc environment: {0}")]
28 UnknownLibc(String),
29 #[error("Unsupported variant `{0}` for architecture `{1}`")]
30 UnsupportedVariant(String, String),
31 #[error(transparent)]
32 LibcDetectionError(#[from] crate::libc::LibcDetectionError),
33 #[error("Invalid platform format: {0}")]
34 InvalidPlatformFormat(String),
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Hash)]
39pub struct Platform {
40 pub os: Os,
41 pub arch: Arch,
42 pub libc: Libc,
43}
44
45impl Platform {
46 pub fn new(os: Os, arch: Arch, libc: Libc) -> Self {
48 Self { os, arch, libc }
49 }
50
51 pub fn from_parts(os: &str, arch: &str, libc: &str) -> Result<Self, Error> {
53 Ok(Self {
54 os: Os::from_str(os)?,
55 arch: Arch::from_str(arch)?,
56 libc: Libc::from_str(libc)?,
57 })
58 }
59
60 pub fn from_env() -> Result<Self, Error> {
62 let os = Os::from_env();
63 let arch = Arch::from_env();
64 let libc = Libc::from_env()?;
65 Ok(Self { os, arch, libc })
66 }
67
68 pub fn supports(&self, other: &Self) -> bool {
70 if self == other {
72 return true;
73 }
74
75 if !self.os.supports(other.os) {
76 trace!(
77 "Operating system `{}` is not compatible with `{}`",
78 self.os, other.os
79 );
80 return false;
81 }
82
83 if self.libc != other.libc && !(other.os.is_emscripten() || self.os.is_emscripten()) {
85 trace!(
86 "Libc `{}` is not compatible with `{}`",
87 self.libc, other.libc
88 );
89 return false;
90 }
91
92 if self.arch == other.arch {
94 return true;
95 }
96
97 #[expect(clippy::unnested_or_patterns)]
98 if self.os.is_windows()
99 && matches!(
100 (self.arch.family(), other.arch.family()),
101 (Architecture::X86_64, Architecture::X86_32(_)) |
103 (Architecture::Aarch64(_), Architecture::X86_64) |
105 (Architecture::Aarch64(_), Architecture::X86_32(_))
106 )
107 {
108 return true;
109 }
110
111 if self.os.is_macos()
112 && matches!(
113 (self.arch.family(), other.arch.family()),
114 (Architecture::Aarch64(_), Architecture::X86_64)
118 )
119 {
120 return true;
121 }
122
123 if other.arch.is_wasm() {
125 return true;
126 }
127
128 if self.arch.family() != other.arch.family() {
132 return false;
133 }
134
135 true
136 }
137
138 pub fn as_cargo_dist_triple(&self) -> String {
140 use target_lexicon::{
141 Architecture, ArmArchitecture, Environment, OperatingSystem, Riscv64Architecture,
142 X86_32Architecture,
143 };
144
145 let Self { os, arch, libc } = &self;
146
147 let arch_name = match arch.family() {
148 Architecture::X86_32(X86_32Architecture::I686) => "i686".to_string(),
150 Architecture::Riscv64(Riscv64Architecture::Riscv64) => "riscv64gc".to_string(),
151 _ => arch.to_string(),
152 };
153 let vendor = match &**os {
154 OperatingSystem::Darwin(_) => "apple",
155 OperatingSystem::Windows => "pc",
156 _ => "unknown",
157 };
158 let os_name = match &**os {
159 OperatingSystem::Darwin(_) => "darwin",
160 _ => &os.to_string(),
161 };
162
163 let abi = match (&**os, libc) {
164 (OperatingSystem::Windows, _) => Some("msvc".to_string()),
165 (OperatingSystem::Linux, Libc::Some(env)) => Some({
166 if matches!(arch.family(), Architecture::Arm(ArmArchitecture::Armv7))
176 && matches!(env, Environment::Gnu | Environment::Musl)
177 {
178 format!("{env}eabihf")
179 } else {
180 env.to_string()
181 }
182 }),
183 _ => None,
184 };
185
186 format!(
187 "{arch_name}-{vendor}-{os_name}{abi}",
188 abi = abi.map(|abi| format!("-{abi}")).unwrap_or_default()
189 )
190 }
191}
192
193impl fmt::Display for Platform {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 write!(f, "{}-{}-{}", self.os, self.arch, self.libc)
196 }
197}
198
199impl FromStr for Platform {
200 type Err = Error;
201
202 fn from_str(s: &str) -> Result<Self, Self::Err> {
203 let parts: Vec<&str> = s.split('-').collect();
204
205 if parts.len() != 3 {
206 return Err(Error::InvalidPlatformFormat(format!(
207 "expected exactly 3 parts separated by '-', got {}",
208 parts.len()
209 )));
210 }
211
212 Self::from_parts(parts[0], parts[1], parts[2])
213 }
214}
215
216impl Ord for Platform {
217 fn cmp(&self, other: &Self) -> cmp::Ordering {
218 self.os
219 .to_string()
220 .cmp(&other.os.to_string())
221 .then_with(|| {
223 if self.arch.family == other.arch.family {
224 return self.arch.variant.cmp(&other.arch.variant);
225 }
226
227 let preferred = if self.os.is_windows() {
238 Arch {
239 family: target_lexicon::Architecture::X86_64,
240 variant: None,
241 }
242 } else {
243 Arch::from_env()
245 };
246
247 match (
248 self.arch.family == preferred.family,
249 other.arch.family == preferred.family,
250 ) {
251 (true, true) => unreachable!(),
252 (true, false) => cmp::Ordering::Less,
253 (false, true) => cmp::Ordering::Greater,
254 (false, false) => {
255 self.arch
257 .family
258 .to_string()
259 .cmp(&other.arch.family.to_string())
260 }
261 }
262 })
263 .then_with(|| self.libc.to_string().cmp(&other.libc.to_string()))
265 }
266}
267
268impl PartialOrd for Platform {
269 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
270 Some(self.cmp(other))
271 }
272}
273
274impl From<&uv_platform_tags::Platform> for Platform {
275 fn from(value: &uv_platform_tags::Platform) -> Self {
276 Self {
277 os: Os::from(value.os()),
278 arch: Arch::from(&value.arch()),
279 libc: Libc::from(value.os()),
280 }
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn test_platform_display() {
290 let platform = Platform {
291 os: Os::from_str("linux").unwrap(),
292 arch: Arch::from_str("x86_64").unwrap(),
293 libc: Libc::from_str("gnu").unwrap(),
294 };
295 assert_eq!(platform.to_string(), "linux-x86_64-gnu");
296 }
297
298 #[test]
299 fn test_platform_from_str() {
300 let platform = Platform::from_str("macos-aarch64-none").unwrap();
301 assert_eq!(platform.os.to_string(), "macos");
302 assert_eq!(platform.arch.to_string(), "aarch64");
303 assert_eq!(platform.libc.to_string(), "none");
304 }
305
306 #[test]
307 fn test_platform_from_parts() {
308 let platform = Platform::from_parts("linux", "x86_64", "gnu").unwrap();
309 assert_eq!(platform.os.to_string(), "linux");
310 assert_eq!(platform.arch.to_string(), "x86_64");
311 assert_eq!(platform.libc.to_string(), "gnu");
312
313 let platform = Platform::from_parts("linux", "x86_64_v3", "musl").unwrap();
315 assert_eq!(platform.os.to_string(), "linux");
316 assert_eq!(platform.arch.to_string(), "x86_64_v3");
317 assert_eq!(platform.libc.to_string(), "musl");
318
319 assert!(Platform::from_parts("invalid_os", "x86_64", "gnu").is_err());
321 assert!(Platform::from_parts("linux", "invalid_arch", "gnu").is_err());
322 assert!(Platform::from_parts("linux", "x86_64", "invalid_libc").is_err());
323 }
324
325 #[test]
326 fn test_platform_from_str_with_arch_variant() {
327 let platform = Platform::from_str("linux-x86_64_v3-gnu").unwrap();
328 assert_eq!(platform.os.to_string(), "linux");
329 assert_eq!(platform.arch.to_string(), "x86_64_v3");
330 assert_eq!(platform.libc.to_string(), "gnu");
331 }
332
333 #[test]
334 fn test_platform_from_str_error() {
335 assert!(Platform::from_str("linux-x86_64").is_err());
337 assert!(Platform::from_str("invalid").is_err());
338
339 assert!(Platform::from_str("linux-x86-64-gnu").is_err());
341 assert!(Platform::from_str("linux-x86_64-gnu-extra").is_err());
342 }
343
344 #[test]
345 fn test_platform_sorting_os_precedence() {
346 let linux = Platform::from_str("linux-x86_64-gnu").unwrap();
347 let macos = Platform::from_str("macos-x86_64-none").unwrap();
348 let windows = Platform::from_str("windows-x86_64-none").unwrap();
349
350 assert!(linux < macos);
352 assert!(macos < windows);
353 }
354
355 #[test]
356 fn test_platform_sorting_libc() {
357 let gnu = Platform::from_str("linux-x86_64-gnu").unwrap();
358 let musl = Platform::from_str("linux-x86_64-musl").unwrap();
359
360 assert!(gnu < musl);
362 }
363
364 #[test]
365 fn test_platform_sorting_arch_linux() {
366 use crate::arch::test_support::{aarch64, run_with_arch, x86_64};
368
369 let linux_x86_64 = Platform::from_str("linux-x86_64-gnu").unwrap();
370 let linux_aarch64 = Platform::from_str("linux-aarch64-gnu").unwrap();
371
372 run_with_arch(x86_64(), || {
374 assert!(linux_x86_64 < linux_aarch64);
375 });
376
377 run_with_arch(aarch64(), || {
379 assert!(linux_aarch64 < linux_x86_64);
380 });
381 }
382
383 #[test]
384 fn test_platform_sorting_arch_macos() {
385 use crate::arch::test_support::{aarch64, run_with_arch, x86_64};
386
387 let macos_x86_64 = Platform::from_str("macos-x86_64-none").unwrap();
388 let macos_aarch64 = Platform::from_str("macos-aarch64-none").unwrap();
389
390 run_with_arch(x86_64(), || {
392 assert!(macos_x86_64 < macos_aarch64);
393 });
394
395 run_with_arch(aarch64(), || {
397 assert!(macos_aarch64 < macos_x86_64);
398 });
399 }
400
401 #[test]
402 fn test_platform_supports() {
403 let native = Platform::from_str("linux-x86_64-gnu").unwrap();
404 let same = Platform::from_str("linux-x86_64-gnu").unwrap();
405 let different_arch = Platform::from_str("linux-aarch64-gnu").unwrap();
406 let different_os = Platform::from_str("macos-x86_64-none").unwrap();
407 let different_libc = Platform::from_str("linux-x86_64-musl").unwrap();
408
409 assert!(native.supports(&same));
411
412 assert!(!native.supports(&different_os));
414
415 assert!(!native.supports(&different_libc));
417
418 assert!(!native.supports(&different_arch));
421
422 let x86_64_v2 = Platform::from_str("linux-x86_64_v2-gnu").unwrap();
424 let x86_64_v3 = Platform::from_str("linux-x86_64_v3-gnu").unwrap();
425
426 assert_eq!(native.arch.family(), x86_64_v2.arch.family());
428 assert_eq!(native.arch.family(), x86_64_v3.arch.family());
429
430 assert!(native.supports(&x86_64_v2));
432 assert!(native.supports(&x86_64_v3));
433 }
434
435 #[test]
436 fn test_windows_aarch64_platform_sorting() {
437 let windows_x86_64 = Platform::from_str("windows-x86_64-none").unwrap();
439 let windows_aarch64 = Platform::from_str("windows-aarch64-none").unwrap();
440
441 assert!(windows_x86_64 < windows_aarch64);
443
444 let mut platforms = [
446 Platform::from_str("windows-aarch64-none").unwrap(),
447 Platform::from_str("windows-x86_64-none").unwrap(),
448 Platform::from_str("windows-x86-none").unwrap(),
449 ];
450
451 platforms.sort();
452
453 assert_eq!(platforms[0].arch.to_string(), "x86_64");
456 assert_eq!(platforms[1].arch.to_string(), "aarch64");
457 assert_eq!(platforms[2].arch.to_string(), "x86");
458 }
459
460 #[test]
461 fn test_windows_sorting_always_prefers_x86_64() {
462 use crate::arch::test_support::{aarch64, run_with_arch, x86_64};
464
465 let windows_x86_64 = Platform::from_str("windows-x86_64-none").unwrap();
466 let windows_aarch64 = Platform::from_str("windows-aarch64-none").unwrap();
467
468 run_with_arch(aarch64(), || {
470 assert!(windows_x86_64 < windows_aarch64);
471 });
472
473 run_with_arch(x86_64(), || {
475 assert!(windows_x86_64 < windows_aarch64);
476 });
477 }
478
479 #[test]
480 fn test_windows_aarch64_supports() {
481 let windows_aarch64 = Platform::from_str("windows-aarch64-none").unwrap();
483 let windows_x86_64 = Platform::from_str("windows-x86_64-none").unwrap();
484
485 assert!(windows_aarch64.supports(&windows_x86_64));
487
488 assert!(!windows_x86_64.supports(&windows_aarch64));
490
491 assert!(windows_aarch64.supports(&windows_aarch64));
493 assert!(windows_x86_64.supports(&windows_x86_64));
494 }
495
496 #[test]
497 fn test_from_platform_tags_platform() {
498 let tags_platform = uv_platform_tags::Platform::new(
500 uv_platform_tags::Os::Windows,
501 uv_platform_tags::Arch::X86_64,
502 );
503 let platform = Platform::from(&tags_platform);
504
505 assert_eq!(platform.os.to_string(), "windows");
506 assert_eq!(platform.arch.to_string(), "x86_64");
507 assert_eq!(platform.libc.to_string(), "none");
508
509 let tags_platform_linux = uv_platform_tags::Platform::new(
511 uv_platform_tags::Os::Manylinux {
512 major: 2,
513 minor: 17,
514 },
515 uv_platform_tags::Arch::Aarch64,
516 );
517 let platform_linux = Platform::from(&tags_platform_linux);
518
519 assert_eq!(platform_linux.os.to_string(), "linux");
520 assert_eq!(platform_linux.arch.to_string(), "aarch64");
521 assert_eq!(platform_linux.libc.to_string(), "gnu");
522 }
523
524 #[test]
525 fn test_as_cargo_dist_triple_armv7_libc_handling() {
526 for (arch, libc, expected) in [
531 ("armv7", "gnueabihf", "armv7-unknown-linux-gnueabihf"),
533 ("armv7", "gnueabi", "armv7-unknown-linux-gnueabi"),
534 ("armv7", "musleabihf", "armv7-unknown-linux-musleabihf"),
535 ("armv7", "musleabi", "armv7-unknown-linux-musleabi"),
536 ("armv7", "gnu", "armv7-unknown-linux-gnueabihf"),
538 ("armv7", "musl", "armv7-unknown-linux-musleabihf"),
539 ("aarch64", "gnu", "aarch64-unknown-linux-gnu"),
541 ("x86_64", "musl", "x86_64-unknown-linux-musl"),
542 ] {
543 let platform = Platform::from_parts("linux", arch, libc).unwrap();
544 assert_eq!(
545 platform.as_cargo_dist_triple(),
546 expected,
547 "linux-{arch}-{libc}"
548 );
549 }
550 }
551}