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::libc::{Libc, LibcDetectionError, LibcVersion};
12pub use crate::os::Os;
13
14mod arch;
15mod cpuinfo;
16mod libc;
17mod os;
18
19#[derive(Error, Debug)]
20pub enum Error {
21 #[error("Unknown operating system: {0}")]
22 UnknownOs(String),
23 #[error("Unknown architecture: {0}")]
24 UnknownArch(String),
25 #[error("Unknown libc environment: {0}")]
26 UnknownLibc(String),
27 #[error("Unsupported variant `{0}` for architecture `{1}`")]
28 UnsupportedVariant(String, String),
29 #[error(transparent)]
30 LibcDetectionError(#[from] crate::libc::LibcDetectionError),
31 #[error("Invalid platform format: {0}")]
32 InvalidPlatformFormat(String),
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
37pub struct Platform {
38 pub os: Os,
39 pub arch: Arch,
40 pub libc: Libc,
41}
42
43impl Platform {
44 pub fn new(os: Os, arch: Arch, libc: Libc) -> Self {
46 Self { os, arch, libc }
47 }
48
49 pub fn from_parts(os: &str, arch: &str, libc: &str) -> Result<Self, Error> {
51 Ok(Self {
52 os: Os::from_str(os)?,
53 arch: Arch::from_str(arch)?,
54 libc: Libc::from_str(libc)?,
55 })
56 }
57
58 pub fn from_env() -> Result<Self, Error> {
60 let os = Os::from_env();
61 let arch = Arch::from_env();
62 let libc = Libc::from_env()?;
63 Ok(Self { os, arch, libc })
64 }
65
66 pub fn supports(&self, other: &Self) -> bool {
68 if self == other {
70 return true;
71 }
72
73 if !self.os.supports(other.os) {
74 trace!(
75 "Operating system `{}` is not compatible with `{}`",
76 self.os, other.os
77 );
78 return false;
79 }
80
81 if self.libc != other.libc && !(other.os.is_emscripten() || self.os.is_emscripten()) {
83 trace!(
84 "Libc `{}` is not compatible with `{}`",
85 self.libc, other.libc
86 );
87 return false;
88 }
89
90 if self.arch == other.arch {
92 return true;
93 }
94
95 #[allow(clippy::unnested_or_patterns)]
96 if self.os.is_windows()
97 && matches!(
98 (self.arch.family(), other.arch.family()),
99 (Architecture::X86_64, Architecture::X86_32(_)) |
101 (Architecture::Aarch64(_), Architecture::X86_64) |
103 (Architecture::Aarch64(_), Architecture::X86_32(_))
104 )
105 {
106 return true;
107 }
108
109 if self.os.is_macos()
110 && matches!(
111 (self.arch.family(), other.arch.family()),
112 (Architecture::Aarch64(_), Architecture::X86_64)
116 )
117 {
118 return true;
119 }
120
121 if other.arch.is_wasm() {
123 return true;
124 }
125
126 if self.arch.family() != other.arch.family() {
130 return false;
131 }
132
133 true
134 }
135
136 pub fn as_cargo_dist_triple(&self) -> String {
138 use target_lexicon::{
139 Architecture, ArmArchitecture, OperatingSystem, Riscv64Architecture, X86_32Architecture,
140 };
141
142 let Self { os, arch, libc } = &self;
143
144 let arch_name = match arch.family() {
145 Architecture::X86_32(X86_32Architecture::I686) => "i686".to_string(),
147 Architecture::Riscv64(Riscv64Architecture::Riscv64) => "riscv64gc".to_string(),
148 _ => arch.to_string(),
149 };
150 let vendor = match &**os {
151 OperatingSystem::Darwin(_) => "apple",
152 OperatingSystem::Windows => "pc",
153 _ => "unknown",
154 };
155 let os_name = match &**os {
156 OperatingSystem::Darwin(_) => "darwin",
157 _ => &os.to_string(),
158 };
159
160 let abi = match (&**os, libc) {
161 (OperatingSystem::Windows, _) => Some("msvc".to_string()),
162 (OperatingSystem::Linux, Libc::Some(env)) => Some({
163 if matches!(arch.family(), Architecture::Arm(ArmArchitecture::Armv7)) {
165 format!("{env}eabihf")
166 } else {
167 env.to_string()
168 }
169 }),
170 _ => None,
171 };
172
173 format!(
174 "{arch_name}-{vendor}-{os_name}{abi}",
175 abi = abi.map(|abi| format!("-{abi}")).unwrap_or_default()
176 )
177 }
178}
179
180impl fmt::Display for Platform {
181 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182 write!(f, "{}-{}-{}", self.os, self.arch, self.libc)
183 }
184}
185
186impl FromStr for Platform {
187 type Err = Error;
188
189 fn from_str(s: &str) -> Result<Self, Self::Err> {
190 let parts: Vec<&str> = s.split('-').collect();
191
192 if parts.len() != 3 {
193 return Err(Error::InvalidPlatformFormat(format!(
194 "expected exactly 3 parts separated by '-', got {}",
195 parts.len()
196 )));
197 }
198
199 Self::from_parts(parts[0], parts[1], parts[2])
200 }
201}
202
203impl Ord for Platform {
204 fn cmp(&self, other: &Self) -> cmp::Ordering {
205 self.os
206 .to_string()
207 .cmp(&other.os.to_string())
208 .then_with(|| {
210 if self.arch.family == other.arch.family {
211 return self.arch.variant.cmp(&other.arch.variant);
212 }
213
214 let preferred = if self.os.is_windows() {
225 Arch {
226 family: target_lexicon::Architecture::X86_64,
227 variant: None,
228 }
229 } else {
230 Arch::from_env()
232 };
233
234 match (
235 self.arch.family == preferred.family,
236 other.arch.family == preferred.family,
237 ) {
238 (true, true) => unreachable!(),
239 (true, false) => cmp::Ordering::Less,
240 (false, true) => cmp::Ordering::Greater,
241 (false, false) => {
242 self.arch
244 .family
245 .to_string()
246 .cmp(&other.arch.family.to_string())
247 }
248 }
249 })
250 .then_with(|| self.libc.to_string().cmp(&other.libc.to_string()))
252 }
253}
254
255impl PartialOrd for Platform {
256 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
257 Some(self.cmp(other))
258 }
259}
260
261impl From<&uv_platform_tags::Platform> for Platform {
262 fn from(value: &uv_platform_tags::Platform) -> Self {
263 Self {
264 os: Os::from(value.os()),
265 arch: Arch::from(&value.arch()),
266 libc: Libc::from(value.os()),
267 }
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn test_platform_display() {
277 let platform = Platform {
278 os: Os::from_str("linux").unwrap(),
279 arch: Arch::from_str("x86_64").unwrap(),
280 libc: Libc::from_str("gnu").unwrap(),
281 };
282 assert_eq!(platform.to_string(), "linux-x86_64-gnu");
283 }
284
285 #[test]
286 fn test_platform_from_str() {
287 let platform = Platform::from_str("macos-aarch64-none").unwrap();
288 assert_eq!(platform.os.to_string(), "macos");
289 assert_eq!(platform.arch.to_string(), "aarch64");
290 assert_eq!(platform.libc.to_string(), "none");
291 }
292
293 #[test]
294 fn test_platform_from_parts() {
295 let platform = Platform::from_parts("linux", "x86_64", "gnu").unwrap();
296 assert_eq!(platform.os.to_string(), "linux");
297 assert_eq!(platform.arch.to_string(), "x86_64");
298 assert_eq!(platform.libc.to_string(), "gnu");
299
300 let platform = Platform::from_parts("linux", "x86_64_v3", "musl").unwrap();
302 assert_eq!(platform.os.to_string(), "linux");
303 assert_eq!(platform.arch.to_string(), "x86_64_v3");
304 assert_eq!(platform.libc.to_string(), "musl");
305
306 assert!(Platform::from_parts("invalid_os", "x86_64", "gnu").is_err());
308 assert!(Platform::from_parts("linux", "invalid_arch", "gnu").is_err());
309 assert!(Platform::from_parts("linux", "x86_64", "invalid_libc").is_err());
310 }
311
312 #[test]
313 fn test_platform_from_str_with_arch_variant() {
314 let platform = Platform::from_str("linux-x86_64_v3-gnu").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(), "gnu");
318 }
319
320 #[test]
321 fn test_platform_from_str_error() {
322 assert!(Platform::from_str("linux-x86_64").is_err());
324 assert!(Platform::from_str("invalid").is_err());
325
326 assert!(Platform::from_str("linux-x86-64-gnu").is_err());
328 assert!(Platform::from_str("linux-x86_64-gnu-extra").is_err());
329 }
330
331 #[test]
332 fn test_platform_sorting_os_precedence() {
333 let linux = Platform::from_str("linux-x86_64-gnu").unwrap();
334 let macos = Platform::from_str("macos-x86_64-none").unwrap();
335 let windows = Platform::from_str("windows-x86_64-none").unwrap();
336
337 assert!(linux < macos);
339 assert!(macos < windows);
340 }
341
342 #[test]
343 fn test_platform_sorting_libc() {
344 let gnu = Platform::from_str("linux-x86_64-gnu").unwrap();
345 let musl = Platform::from_str("linux-x86_64-musl").unwrap();
346
347 assert!(gnu < musl);
349 }
350
351 #[test]
352 fn test_platform_sorting_arch_linux() {
353 use crate::arch::test_support::{aarch64, run_with_arch, x86_64};
355
356 let linux_x86_64 = Platform::from_str("linux-x86_64-gnu").unwrap();
357 let linux_aarch64 = Platform::from_str("linux-aarch64-gnu").unwrap();
358
359 run_with_arch(x86_64(), || {
361 assert!(linux_x86_64 < linux_aarch64);
362 });
363
364 run_with_arch(aarch64(), || {
366 assert!(linux_aarch64 < linux_x86_64);
367 });
368 }
369
370 #[test]
371 fn test_platform_sorting_arch_macos() {
372 use crate::arch::test_support::{aarch64, run_with_arch, x86_64};
373
374 let macos_x86_64 = Platform::from_str("macos-x86_64-none").unwrap();
375 let macos_aarch64 = Platform::from_str("macos-aarch64-none").unwrap();
376
377 run_with_arch(x86_64(), || {
379 assert!(macos_x86_64 < macos_aarch64);
380 });
381
382 run_with_arch(aarch64(), || {
384 assert!(macos_aarch64 < macos_x86_64);
385 });
386 }
387
388 #[test]
389 fn test_platform_supports() {
390 let native = Platform::from_str("linux-x86_64-gnu").unwrap();
391 let same = Platform::from_str("linux-x86_64-gnu").unwrap();
392 let different_arch = Platform::from_str("linux-aarch64-gnu").unwrap();
393 let different_os = Platform::from_str("macos-x86_64-none").unwrap();
394 let different_libc = Platform::from_str("linux-x86_64-musl").unwrap();
395
396 assert!(native.supports(&same));
398
399 assert!(!native.supports(&different_os));
401
402 assert!(!native.supports(&different_libc));
404
405 assert!(!native.supports(&different_arch));
408
409 let x86_64_v2 = Platform::from_str("linux-x86_64_v2-gnu").unwrap();
411 let x86_64_v3 = Platform::from_str("linux-x86_64_v3-gnu").unwrap();
412
413 assert_eq!(native.arch.family(), x86_64_v2.arch.family());
415 assert_eq!(native.arch.family(), x86_64_v3.arch.family());
416
417 assert!(native.supports(&x86_64_v2));
419 assert!(native.supports(&x86_64_v3));
420 }
421
422 #[test]
423 fn test_windows_aarch64_platform_sorting() {
424 let windows_x86_64 = Platform::from_str("windows-x86_64-none").unwrap();
426 let windows_aarch64 = Platform::from_str("windows-aarch64-none").unwrap();
427
428 assert!(windows_x86_64 < windows_aarch64);
430
431 let mut platforms = [
433 Platform::from_str("windows-aarch64-none").unwrap(),
434 Platform::from_str("windows-x86_64-none").unwrap(),
435 Platform::from_str("windows-x86-none").unwrap(),
436 ];
437
438 platforms.sort();
439
440 assert_eq!(platforms[0].arch.to_string(), "x86_64");
443 assert_eq!(platforms[1].arch.to_string(), "aarch64");
444 assert_eq!(platforms[2].arch.to_string(), "x86");
445 }
446
447 #[test]
448 fn test_windows_sorting_always_prefers_x86_64() {
449 use crate::arch::test_support::{aarch64, run_with_arch, x86_64};
451
452 let windows_x86_64 = Platform::from_str("windows-x86_64-none").unwrap();
453 let windows_aarch64 = Platform::from_str("windows-aarch64-none").unwrap();
454
455 run_with_arch(aarch64(), || {
457 assert!(windows_x86_64 < windows_aarch64);
458 });
459
460 run_with_arch(x86_64(), || {
462 assert!(windows_x86_64 < windows_aarch64);
463 });
464 }
465
466 #[test]
467 fn test_windows_aarch64_supports() {
468 let windows_aarch64 = Platform::from_str("windows-aarch64-none").unwrap();
470 let windows_x86_64 = Platform::from_str("windows-x86_64-none").unwrap();
471
472 assert!(windows_aarch64.supports(&windows_x86_64));
474
475 assert!(!windows_x86_64.supports(&windows_aarch64));
477
478 assert!(windows_aarch64.supports(&windows_aarch64));
480 assert!(windows_x86_64.supports(&windows_x86_64));
481 }
482
483 #[test]
484 fn test_from_platform_tags_platform() {
485 let tags_platform = uv_platform_tags::Platform::new(
487 uv_platform_tags::Os::Windows,
488 uv_platform_tags::Arch::X86_64,
489 );
490 let platform = Platform::from(&tags_platform);
491
492 assert_eq!(platform.os.to_string(), "windows");
493 assert_eq!(platform.arch.to_string(), "x86_64");
494 assert_eq!(platform.libc.to_string(), "none");
495
496 let tags_platform_linux = uv_platform_tags::Platform::new(
498 uv_platform_tags::Os::Manylinux {
499 major: 2,
500 minor: 17,
501 },
502 uv_platform_tags::Arch::Aarch64,
503 );
504 let platform_linux = Platform::from(&tags_platform_linux);
505
506 assert_eq!(platform_linux.os.to_string(), "linux");
507 assert_eq!(platform_linux.arch.to_string(), "aarch64");
508 assert_eq!(platform_linux.libc.to_string(), "gnu");
509 }
510}