Skip to main content

osdk_core/
platform.rs

1//! OS / architecture detection and target-triple normalization.
2//!
3//! Different SDKs name their platform assets differently (node uses
4//! `darwin-arm64`, go uses `darwin-arm64` too but `linux-amd64`, python-build-
5//! standalone uses full LLVM triples like `x86_64-unknown-linux-gnu`). We detect
6//! the host once here and let each backend map it to its own naming scheme.
7
8use std::fmt;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum Os {
12    Linux,
13    Macos,
14    Windows,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Arch {
19    X64,
20    Arm64,
21    X86,
22    Arm,
23}
24
25/// C library flavor, relevant on Linux (glibc vs musl).
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum Libc {
28    Glibc,
29    Musl,
30    None,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct Platform {
35    pub os: Os,
36    pub arch: Arch,
37    pub libc: Libc,
38}
39
40impl Os {
41    pub fn current() -> Os {
42        #[cfg(target_os = "linux")]
43        {
44            Os::Linux
45        }
46        #[cfg(target_os = "macos")]
47        {
48            Os::Macos
49        }
50        #[cfg(target_os = "windows")]
51        {
52            Os::Windows
53        }
54        #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
55        {
56            compile_error!("unsupported target_os")
57        }
58    }
59
60    /// Whether executables carry the `.exe` suffix on this OS.
61    pub fn exe_suffix(self) -> &'static str {
62        match self {
63            Os::Windows => ".exe",
64            _ => "",
65        }
66    }
67
68    /// node's platform token, e.g. `linux`, `darwin`, `win`.
69    pub fn node_token(self) -> &'static str {
70        match self {
71            Os::Linux => "linux",
72            Os::Macos => "darwin",
73            Os::Windows => "win",
74        }
75    }
76
77    /// go's platform token, e.g. `linux`, `darwin`, `windows`.
78    pub fn go_token(self) -> &'static str {
79        match self {
80            Os::Linux => "linux",
81            Os::Macos => "darwin",
82            Os::Windows => "windows",
83        }
84    }
85}
86
87impl Arch {
88    pub fn current() -> Arch {
89        #[cfg(target_arch = "x86_64")]
90        {
91            Arch::X64
92        }
93        #[cfg(target_arch = "aarch64")]
94        {
95            Arch::Arm64
96        }
97        #[cfg(target_arch = "x86")]
98        {
99            Arch::X86
100        }
101        #[cfg(target_arch = "arm")]
102        {
103            Arch::Arm
104        }
105        #[cfg(not(any(
106            target_arch = "x86_64",
107            target_arch = "aarch64",
108            target_arch = "x86",
109            target_arch = "arm"
110        )))]
111        {
112            compile_error!("unsupported target_arch")
113        }
114    }
115
116    /// node's arch token, e.g. `x64`, `arm64`.
117    pub fn node_token(self) -> &'static str {
118        match self {
119            Arch::X64 => "x64",
120            Arch::Arm64 => "arm64",
121            Arch::X86 => "x86",
122            Arch::Arm => "armv7l",
123        }
124    }
125
126    pub fn parse_node(value: &str) -> Option<Arch> {
127        match value.trim().to_ascii_lowercase().as_str() {
128            "x64" | "amd64" | "x86_64" => Some(Arch::X64),
129            "arm64" | "aarch64" => Some(Arch::Arm64),
130            "x86" | "ia32" | "i386" | "i686" => Some(Arch::X86),
131            "arm" | "armv7" | "armv7l" => Some(Arch::Arm),
132            _ => None,
133        }
134    }
135
136    /// go's arch token, e.g. `amd64`, `arm64`.
137    pub fn go_token(self) -> &'static str {
138        match self {
139            Arch::X64 => "amd64",
140            Arch::Arm64 => "arm64",
141            Arch::X86 => "386",
142            Arch::Arm => "armv6l",
143        }
144    }
145
146    /// The CPU part of an LLVM target triple, e.g. `x86_64`, `aarch64`.
147    pub fn llvm_token(self) -> &'static str {
148        match self {
149            Arch::X64 => "x86_64",
150            Arch::Arm64 => "aarch64",
151            Arch::X86 => "i686",
152            Arch::Arm => "armv7",
153        }
154    }
155}
156
157impl Libc {
158    /// Detect libc flavor. Only meaningful on Linux; elsewhere returns `None`.
159    ///
160    /// We detect musl by checking whether the dynamic loader path or ldd output
161    /// mentions musl. This is best-effort; backends can override.
162    pub fn current() -> Libc {
163        #[cfg(target_os = "linux")]
164        {
165            if cfg!(target_env = "musl") {
166                return Libc::Musl;
167            }
168            // Best-effort runtime detection: musl systems ship `ld-musl-*.so`.
169            if std::path::Path::new("/lib/ld-musl-x86_64.so.1").exists()
170                || std::path::Path::new("/lib/ld-musl-aarch64.so.1").exists()
171            {
172                return Libc::Musl;
173            }
174            Libc::Glibc
175        }
176        #[cfg(not(target_os = "linux"))]
177        {
178            Libc::None
179        }
180    }
181}
182
183impl Platform {
184    pub fn current() -> Platform {
185        Platform {
186            os: Os::current(),
187            arch: Arch::current(),
188            libc: Libc::current(),
189        }
190    }
191
192    /// python-build-standalone / rustup style LLVM triple for this host,
193    /// e.g. `x86_64-unknown-linux-gnu`, `aarch64-apple-darwin`,
194    /// `x86_64-pc-windows-msvc`.
195    pub fn llvm_triple(&self) -> String {
196        let cpu = self.arch.llvm_token();
197        match self.os {
198            Os::Linux => {
199                let libc = match self.libc {
200                    Libc::Musl => "musl",
201                    _ => "gnu",
202                };
203                format!("{cpu}-unknown-linux-{libc}")
204            }
205            Os::Macos => format!("{cpu}-apple-darwin"),
206            Os::Windows => format!("{cpu}-pc-windows-msvc"),
207        }
208    }
209}
210
211impl fmt::Display for Platform {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        let os = match self.os {
214            Os::Linux => "linux",
215            Os::Macos => "macos",
216            Os::Windows => "windows",
217        };
218        let arch = match self.arch {
219            Arch::X64 => "x64",
220            Arch::Arm64 => "arm64",
221            Arch::X86 => "x86",
222            Arch::Arm => "arm",
223        };
224        write!(f, "{os}-{arch}")?;
225        if matches!(self.libc, Libc::Musl) {
226            write!(f, "-musl")?;
227        }
228        Ok(())
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    #[test]
237    fn llvm_triple_shape() {
238        let p = Platform {
239            os: Os::Linux,
240            arch: Arch::X64,
241            libc: Libc::Glibc,
242        };
243        assert_eq!(p.llvm_triple(), "x86_64-unknown-linux-gnu");
244
245        let p = Platform {
246            os: Os::Macos,
247            arch: Arch::Arm64,
248            libc: Libc::None,
249        };
250        assert_eq!(p.llvm_triple(), "aarch64-apple-darwin");
251
252        let p = Platform {
253            os: Os::Windows,
254            arch: Arch::X64,
255            libc: Libc::None,
256        };
257        assert_eq!(p.llvm_triple(), "x86_64-pc-windows-msvc");
258    }
259
260    #[test]
261    fn tokens() {
262        assert_eq!(Os::Windows.exe_suffix(), ".exe");
263        assert_eq!(Os::Linux.exe_suffix(), "");
264        assert_eq!(Arch::X64.go_token(), "amd64");
265        assert_eq!(Arch::X64.node_token(), "x64");
266    }
267}