clack_host/entry/
library.rs1use crate::entry::PluginEntryError;
2use crate::entry::entry_provider::EntryProvider;
3use clack_common::entry::EntryDescriptor;
4use libloading::Library;
5use std::borrow::Cow;
6use std::ffi::{CStr, CString, OsStr};
7use std::path::Path;
8use std::ptr::NonNull;
9
10pub struct LibraryEntry {
15 _library: Library,
16 entry_ptr: NonNull<EntryDescriptor>,
17}
18
19const SYMBOL_NAME: &CStr = c"clap_entry";
20impl LibraryEntry {
21 #[cfg(feature = "libloading")]
22 pub(crate) fn resolve_path(path: &Path) -> Result<(Cow<'_, Path>, CString), PluginEntryError> {
23 #[cfg(target_os = "macos")]
24 {
25 macos_resolve::resolve_path(path)
26 }
27 #[cfg(not(target_os = "macos"))]
28 {
29 let bundle_path = CString::new(path.to_string_lossy().into_owned())?;
30
31 Ok((Cow::Borrowed(path), bundle_path))
32 }
33 }
34
35 pub unsafe fn load_from_path(path: impl AsRef<OsStr>) -> Result<Self, PluginEntryError> {
49 let library = Library::new(path).map_err(PluginEntryError::LibraryLoadingError)?;
50
51 Self::load_from_library(library)
52 }
53
54 pub unsafe fn load_from_library(library: Library) -> Result<Self, PluginEntryError> {
70 Self::load_from_symbol_in_library(library, SYMBOL_NAME)
71 }
72
73 pub unsafe fn load_from_symbol_in_library(
85 library: Library,
86 symbol_name: &CStr,
87 ) -> Result<Self, PluginEntryError> {
88 let symbol = library
89 .get::<*mut EntryDescriptor>(symbol_name.to_bytes_with_nul())
90 .map_err(PluginEntryError::LibraryLoadingError)?;
91
92 let entry_ptr = NonNull::new(*symbol).ok_or(PluginEntryError::NullEntryPointer)?;
93
94 Ok(Self {
95 _library: library,
96 entry_ptr,
97 })
98 }
99}
100
101unsafe impl EntryProvider for LibraryEntry {
104 #[inline]
105 fn entry_pointer(&self) -> NonNull<EntryDescriptor> {
106 self.entry_ptr
107 }
108}
109
110unsafe impl Send for LibraryEntry {}
112unsafe impl Sync for LibraryEntry {}
114
115#[cfg(target_os = "macos")]
116mod macos_resolve {
117 use super::*;
118 use std::path::{Path, PathBuf};
119
120 pub(super) fn resolve_path(path: &Path) -> Result<(Cow<'_, Path>, CString), PluginEntryError> {
121 if std::fs::metadata(path)?.is_dir() {
122 let executable_path =
124 executable_path_from_bundle(path).ok_or(PluginEntryError::ResolveFailed)?;
125
126 let bundle_path = CString::new(path.to_string_lossy().into_owned())?;
127
128 Ok((Cow::Owned(executable_path), bundle_path))
129 } else {
130 let bundle_path = bundle_path_from_executable(path);
132 let bundle_path = CString::new(bundle_path.to_string_lossy().into_owned())?;
133
134 Ok((Cow::Borrowed(path), bundle_path))
135 }
136 }
137
138 fn executable_path_from_bundle(path: &Path) -> Option<PathBuf> {
139 use objc2_foundation::{NSBundle, NSURL};
140
141 let url = NSURL::from_directory_path(path)?;
142 let bundle = NSBundle::bundleWithURL(&url)?;
143
144 let executable_url = bundle.executableURL()?.absoluteURL()?;
145 let executable_path = executable_url.to_file_path()?;
146
147 Some(executable_path)
148 }
149
150 fn bundle_path_from_executable(path: &Path) -> &Path {
151 if let Some(bundle_path) = bundle_path_from_structure(path) {
152 return bundle_path;
153 };
154
155 if let Some(bundle_path) = bundle_path_from_clap_package_ancestor(path) {
156 return bundle_path;
157 };
158
159 path
160 }
161
162 fn bundle_path_from_structure(path: &Path) -> Option<&Path> {
163 let mut ancestors = path.ancestors();
164 if ancestors.next()? != OsStr::new("MacOS") {
165 return None;
166 }
167
168 if ancestors.next()? != OsStr::new("Contents") {
169 return None;
170 }
171
172 ancestors.next()
173 }
174
175 fn bundle_path_from_clap_package_ancestor(path: &Path) -> Option<&Path> {
176 path.ancestors()
177 .find(|a| a.extension().is_some_and(|e| e == "clap"))
178 }
179}