1use camino::Utf8PathBuf;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
24pub enum Os {
25 MacOs,
27 Linux,
29 Windows,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
35pub enum Arch {
36 X64,
38 Arm64,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
50pub enum Platform {
51 MacosArm64,
53 MacosX64,
55 LinuxX64,
57 LinuxArm64,
59 WindowsX64,
61}
62
63impl Platform {
64 pub const ALL: [Platform; 5] = [
66 Platform::MacosArm64,
67 Platform::MacosX64,
68 Platform::LinuxX64,
69 Platform::LinuxArm64,
70 Platform::WindowsX64,
71 ];
72
73 pub fn id(self) -> &'static str {
77 match self {
78 Platform::MacosArm64 => "darwin-arm64",
79 Platform::MacosX64 => "darwin-x64",
80 Platform::LinuxX64 => "linux-x64",
81 Platform::LinuxArm64 => "linux-arm64",
82 Platform::WindowsX64 => "windows-x64",
83 }
84 }
85
86 pub fn from_id(s: &str) -> Option<Platform> {
89 Platform::ALL.into_iter().find(|p| p.id() == s)
90 }
91
92 pub fn os(self) -> Os {
94 match self {
95 Platform::MacosArm64 | Platform::MacosX64 => Os::MacOs,
96 Platform::LinuxX64 | Platform::LinuxArm64 => Os::Linux,
97 Platform::WindowsX64 => Os::Windows,
98 }
99 }
100
101 pub fn arch(self) -> Arch {
103 match self {
104 Platform::MacosArm64 | Platform::LinuxArm64 => Arch::Arm64,
105 Platform::MacosX64 | Platform::LinuxX64 | Platform::WindowsX64 => Arch::X64,
106 }
107 }
108
109 pub fn rust_target(self) -> &'static str {
112 match self {
113 Platform::MacosArm64 => "aarch64-apple-darwin",
114 Platform::MacosX64 => "x86_64-apple-darwin",
115 Platform::LinuxX64 => "x86_64-unknown-linux-gnu",
116 Platform::LinuxArm64 => "aarch64-unknown-linux-gnu",
117 Platform::WindowsX64 => "x86_64-pc-windows-msvc",
118 }
119 }
120
121 pub fn from_rust_target(triple: &str) -> Option<Platform> {
124 Platform::ALL
125 .into_iter()
126 .find(|p| p.rust_target() == triple)
127 }
128
129 pub fn lib_prefix(self) -> &'static str {
131 match self.os() {
132 Os::MacOs | Os::Linux => "lib",
133 Os::Windows => "",
134 }
135 }
136
137 pub fn lib_extension(self) -> &'static str {
140 match self.os() {
141 Os::MacOs => "dylib",
142 Os::Linux => "so",
143 Os::Windows => "dll",
144 }
145 }
146
147 pub fn lib_filename(self, base: &str) -> String {
152 format!("{}{base}.{}", self.lib_prefix(), self.lib_extension())
153 }
154
155 pub fn nuget_rid(self) -> &'static str {
158 match self {
159 Platform::MacosArm64 => "osx-arm64",
160 Platform::MacosX64 => "osx-x64",
161 Platform::LinuxX64 => "linux-x64",
162 Platform::LinuxArm64 => "linux-arm64",
163 Platform::WindowsX64 => "win-x64",
164 }
165 }
166
167 pub fn node_os(self) -> &'static str {
169 match self.os() {
170 Os::MacOs => "darwin",
171 Os::Linux => "linux",
172 Os::Windows => "win32",
173 }
174 }
175
176 pub fn node_cpu(self) -> &'static str {
178 match self.arch() {
179 Arch::Arm64 => "arm64",
180 Arch::X64 => "x64",
181 }
182 }
183
184 pub fn python_platform_tag(self) -> &'static str {
187 match self {
188 Platform::MacosArm64 => "macosx_11_0_arm64",
189 Platform::MacosX64 => "macosx_10_12_x86_64",
190 Platform::LinuxX64 => "manylinux2014_x86_64",
191 Platform::LinuxArm64 => "manylinux2014_aarch64",
192 Platform::WindowsX64 => "win_amd64",
193 }
194 }
195
196 pub fn ruby_platform(self) -> &'static str {
199 match self {
200 Platform::MacosArm64 => "arm64-darwin",
201 Platform::MacosX64 => "x86_64-darwin",
202 Platform::LinuxX64 => "x86_64-linux",
203 Platform::LinuxArm64 => "aarch64-linux",
204 Platform::WindowsX64 => "x64-mingw-ucrt",
205 }
206 }
207
208 pub fn display_name(self) -> &'static str {
211 match self {
212 Platform::MacosArm64 => "macOS arm64",
213 Platform::MacosX64 => "macOS x64",
214 Platform::LinuxX64 => "Linux x64",
215 Platform::LinuxArm64 => "Linux arm64",
216 Platform::WindowsX64 => "Windows x64",
217 }
218 }
219}
220
221#[derive(Debug, Clone, PartialEq, Eq)]
224pub struct NativeBinary {
225 pub platform: Platform,
227 pub source: Utf8PathBuf,
229}
230
231#[derive(Debug, Clone, PartialEq, Eq)]
239pub struct BinarySet {
240 pub lib_name: String,
242 pub binaries: Vec<NativeBinary>,
244}
245
246impl BinarySet {
247 pub fn new(lib_name: impl Into<String>) -> Self {
249 Self {
250 lib_name: lib_name.into(),
251 binaries: Vec::new(),
252 }
253 }
254
255 pub fn insert(&mut self, platform: Platform, source: impl Into<Utf8PathBuf>) {
258 let source = source.into();
259 if let Some(existing) = self.binaries.iter_mut().find(|b| b.platform == platform) {
260 existing.source = source;
261 } else {
262 self.binaries.push(NativeBinary { platform, source });
263 }
264 }
265
266 pub fn get(&self, platform: Platform) -> Option<&NativeBinary> {
268 self.binaries.iter().find(|b| b.platform == platform)
269 }
270
271 pub fn platforms(&self) -> impl Iterator<Item = Platform> + '_ {
273 self.binaries.iter().map(|b| b.platform)
274 }
275
276 pub fn is_empty(&self) -> bool {
278 self.binaries.is_empty()
279 }
280
281 pub fn bundled_filename(&self, platform: Platform) -> String {
284 platform.lib_filename(&self.lib_name)
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn ids_round_trip() {
294 for p in Platform::ALL {
295 assert_eq!(Platform::from_id(p.id()), Some(p));
296 }
297 assert_eq!(Platform::from_id("nonsense"), None);
298 }
299
300 #[test]
301 fn rust_targets_round_trip() {
302 for p in Platform::ALL {
303 assert_eq!(Platform::from_rust_target(p.rust_target()), Some(p));
304 }
305 assert_eq!(Platform::from_rust_target("mips-unknown-linux-gnu"), None);
306 }
307
308 #[test]
309 fn lib_filenames_are_platform_correct() {
310 assert_eq!(
311 Platform::MacosArm64.lib_filename("contacts"),
312 "libcontacts.dylib"
313 );
314 assert_eq!(
315 Platform::LinuxX64.lib_filename("contacts"),
316 "libcontacts.so"
317 );
318 assert_eq!(
319 Platform::WindowsX64.lib_filename("contacts"),
320 "contacts.dll"
321 );
322 }
323
324 #[test]
325 fn ecosystem_identifiers() {
326 assert_eq!(Platform::MacosArm64.nuget_rid(), "osx-arm64");
327 assert_eq!(Platform::WindowsX64.nuget_rid(), "win-x64");
328 assert_eq!(Platform::MacosX64.node_os(), "darwin");
329 assert_eq!(Platform::MacosX64.node_cpu(), "x64");
330 assert_eq!(Platform::WindowsX64.node_os(), "win32");
331 assert_eq!(
332 Platform::LinuxArm64.python_platform_tag(),
333 "manylinux2014_aarch64"
334 );
335 assert_eq!(Platform::MacosArm64.ruby_platform(), "arm64-darwin");
336 assert_eq!(Platform::LinuxX64.ruby_platform(), "x86_64-linux");
337 }
338
339 #[test]
340 fn binary_set_insert_get_and_replace() {
341 let mut set = BinarySet::new("contacts");
342 assert!(set.is_empty());
343 set.insert(Platform::MacosArm64, "/tmp/a/libcontacts.dylib");
344 set.insert(Platform::LinuxX64, "/tmp/b/libcontacts.so");
345 assert_eq!(set.binaries.len(), 2);
346
347 set.insert(Platform::MacosArm64, "/tmp/c/libcontacts.dylib");
349 assert_eq!(set.binaries.len(), 2);
350 assert_eq!(
351 set.get(Platform::MacosArm64).unwrap().source.as_str(),
352 "/tmp/c/libcontacts.dylib"
353 );
354
355 let platforms: Vec<Platform> = set.platforms().collect();
356 assert_eq!(platforms, vec![Platform::MacosArm64, Platform::LinuxX64]);
357 assert_eq!(set.bundled_filename(Platform::WindowsX64), "contacts.dll");
358 }
359}