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