1use std::{
2 env, fs,
3 path::{Path, PathBuf},
4 vec,
5};
6
7struct InstallPath {
14 paths: Vec<PathBuf>,
15 names: Vec<&'static str>,
16}
17
18pub struct LibraryPath {
20 pub path: PathBuf,
22 pub library: String,
24 pub search: PathBuf,
26}
27
28fn find_files_recursively<P: AsRef<Path>>(
30 root_dir: P,
31 filename: &str,
32 max_depth: Option<usize>,
33) -> Result<Vec<PathBuf>, Box<dyn std::error::Error>> {
34 let mut matches = Vec::new();
35 let mut stack = vec![(root_dir.as_ref().to_path_buf(), 0)];
36
37 while let Some((current_dir, depth)) = stack.pop() {
38 if let Some(max) = max_depth {
39 if depth > max {
40 continue;
41 }
42 }
43
44 if let Ok(entries) = fs::read_dir(¤t_dir) {
45 for entry in entries.flatten() {
46 let path = entry.path();
47
48 if path.is_file() {
49 if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
51 if file_name == filename {
52 matches.push(path);
53 }
54 }
55 } else if path.is_dir() {
56 stack.push((path, depth + 1));
57 }
58 }
59 }
60 }
61
62 Ok(matches)
63}
64
65fn platform_install_paths() -> Result<InstallPath, Box<dyn std::error::Error>> {
66 if cfg!(target_os = "windows") {
67 let local_app_data = env::var("LOCALAPPDATA")
69 .unwrap_or_else(|_| String::from("C:\\Users\\Default\\AppData\\Local"));
70
71 Ok(InstallPath {
72 paths: vec![PathBuf::from(local_app_data)
73 .join("MetaCall")
74 .join("metacall")],
75 names: vec!["metacall.lib", "metacalld.lib"],
76 })
77 } else if cfg!(target_os = "macos") {
78 Ok(InstallPath {
79 paths: vec![
80 PathBuf::from("/opt/homebrew/lib/"),
81 PathBuf::from("/usr/local/lib/"),
82 ],
83 names: vec!["libmetacall.dylib", "libmetacalld.dylib"],
84 })
85 } else if cfg!(target_os = "linux") {
86 Ok(InstallPath {
87 paths: vec![PathBuf::from("/usr/local/lib/"), PathBuf::from("/gnu/lib/")],
88 names: vec!["libmetacall.so", "libmetacalld.so"],
89 })
90 } else {
91 Err(format!("Platform {} not supported", env::consts::OS).into())
92 }
93}
94
95fn get_search_config() -> Result<InstallPath, Box<dyn std::error::Error>> {
97 if let Ok(custom_path) = env::var("METACALL_INSTALL_PATH") {
99 return Ok(InstallPath {
101 paths: vec![PathBuf::from(custom_path)],
102 names: vec![
103 "libmetacall.so",
104 "libmetacalld.so",
105 "libmetacall.dylib",
106 "libmetacalld.dylib",
107 "metacall.lib",
108 "metacalld.lib",
109 ],
110 });
111 }
112
113 platform_install_paths()
115}
116
117fn get_parent_and_library(path: &Path) -> Option<(PathBuf, String)> {
119 let parent = path.parent()?.to_path_buf();
120
121 let stem = path.file_stem()?.to_str()?;
123
124 let cleaned_stem = stem.strip_prefix("lib").unwrap_or(stem).to_string();
126
127 Some((parent, cleaned_stem))
128}
129
130#[cfg(target_os = "windows")]
133fn strip_extended_length_prefix(path: PathBuf) -> PathBuf {
134 let path_str = path.to_string_lossy();
135 if let Some(stripped) = path_str.strip_prefix(r"\\?\") {
136 PathBuf::from(stripped)
137 } else {
138 path
139 }
140}
141
142#[cfg(target_os = "windows")]
145fn find_metacall_dll(
146 search_paths: &[PathBuf],
147 library_name: &str,
148) -> Result<PathBuf, Box<dyn std::error::Error>> {
149 let dll_name = format!("{}.dll", library_name);
151
152 for search_path in search_paths {
153 match find_files_recursively(search_path, &dll_name, None) {
154 Ok(files) if !files.is_empty() => {
155 let found_dll = fs::canonicalize(&files[0])?;
156 if let Some(parent) = found_dll.parent() {
157 return Ok(strip_extended_length_prefix(parent.to_path_buf()));
158 }
159 }
160 _ => continue,
161 }
162 }
163
164 Err(format!(
165 "MetaCall DLL ({}) not found. Searched in: {}",
166 dll_name,
167 search_paths
168 .iter()
169 .map(|p| p.display().to_string())
170 .collect::<Vec<_>>()
171 .join(", ")
172 )
173 .into())
174}
175
176pub fn find_metacall_library() -> Result<LibraryPath, Box<dyn std::error::Error>> {
179 let search_config = get_search_config()?;
180
181 for search_path in &search_config.paths {
183 for name in &search_config.names {
184 match find_files_recursively(search_path, name, None) {
186 Ok(files) if !files.is_empty() => {
187 let found_lib = fs::canonicalize(&files[0])?;
188
189 match get_parent_and_library(&found_lib) {
190 Some((parent, library_name)) => {
191 #[cfg(target_os = "windows")]
193 let (lib_path, search_path) = {
194 let cleaned_parent = strip_extended_length_prefix(parent);
195 let dll_search = match find_metacall_dll(
196 &search_config.paths,
197 &library_name,
198 ) {
199 Ok(dll_path) => dll_path,
200 Err(e) => {
201 println!(
202 "cargo:warning=Could not find DLL, using library path: {}",
203 e
204 );
205 cleaned_parent.clone()
206 }
207 };
208 (cleaned_parent, dll_search)
209 };
210
211 #[cfg(not(target_os = "windows"))]
214 let (lib_path, search_path) = (parent.clone(), parent);
215
216 return Ok(LibraryPath {
217 path: lib_path,
218 library: library_name,
219 search: search_path,
220 });
221 }
222 None => continue,
223 };
224 }
225 Ok(_) => {
226 continue;
228 }
229 Err(e) => {
230 println!(
231 "cargo:warning=Error searching in {}: {}",
232 search_path.display(),
233 e
234 );
235 continue;
236 }
237 }
238 }
239 }
240
241 let search_paths: Vec<String> = search_config
243 .paths
244 .iter()
245 .map(|p| p.display().to_string())
246 .collect();
247
248 Err(format!(
249 "MetaCall library not found. Searched in: {}. \
250 If you have it installed elsewhere, set METACALL_INSTALL_PATH environment variable.",
251 search_paths.join(", ")
252 )
253 .into())
254}
255
256fn define_library_search_path(env_var: &str, separator: &str, path: &Path) -> String {
257 let existing = env::var(env_var).unwrap_or_default();
259 let path_str: String = String::from(path.to_str().unwrap());
260
261 let combined = if existing.is_empty() {
263 path_str
264 } else {
265 format!("{}{}{}", existing, separator, path_str)
266 };
267
268 format!("{}={}", env_var, combined)
269}
270
271fn set_rpath(lib_path: &Path) {
274 let path_str = lib_path.to_str().unwrap();
275
276 #[cfg(target_os = "linux")]
277 {
278 println!("cargo:rustc-link-arg=-Wl,-rpath,{}", path_str);
280 println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN");
282 println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN/../lib");
283 }
284
285 #[cfg(target_os = "macos")]
286 {
287 println!("cargo:rustc-link-arg=-Wl,-rpath,{}", path_str);
289 println!("cargo:rustc-link-arg=-Wl,-rpath,@loader_path");
291 println!("cargo:rustc-link-arg=-Wl,-rpath,@loader_path/../lib");
292 }
293
294 #[cfg(target_os = "aix")]
295 {
296 println!(
298 "cargo:rustc-link-arg=-Wl,-blibpath:{}:/usr/lib:/lib",
299 path_str
300 );
301 }
302
303 #[cfg(target_os = "windows")]
304 {
305 println!(
307 "cargo:warning=On Windows, make sure {} is in your PATH or next to your executable",
308 path_str
309 );
310 }
311}
312
313pub fn build() {
314 if let Ok(val) = env::var("PROJECT_OUTPUT_DIR") {
316 println!("cargo:rustc-link-search=native={val}");
318
319 match env::var("CMAKE_BUILD_TYPE") {
321 Ok(val) => {
322 if val == "Debug" {
323 println!("cargo:rustc-link-lib=dylib=metacalld");
325 } else {
326 println!("cargo:rustc-link-lib=dylib=metacall");
327 }
328 }
329 Err(_) => {
330 println!("cargo:rustc-link-lib=dylib=metacall");
331 }
332 }
333 } else {
334 match find_metacall_library() {
336 Ok(lib_path) => {
337 println!("cargo:rustc-link-search=native={}", lib_path.path.display());
339 println!("cargo:rustc-link-lib=dylib={}", lib_path.library);
340
341 set_rpath(&lib_path.path);
343
344 #[cfg(target_os = "linux")]
346 const ENV_VAR: &str = "LD_LIBRARY_PATH";
347
348 #[cfg(target_os = "macos")]
349 const ENV_VAR: &str = "DYLD_LIBRARY_PATH";
350
351 #[cfg(target_os = "windows")]
352 const ENV_VAR: &str = "PATH";
353
354 #[cfg(target_os = "aix")]
355 const ENV_VAR: &str = "LIBPATH";
356
357 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "aix"))]
358 const SEPARATOR: &str = ":";
359
360 #[cfg(target_os = "windows")]
361 const SEPARATOR: &str = ";";
362
363 println!(
364 "cargo:rustc-env={}",
365 define_library_search_path(ENV_VAR, SEPARATOR, &lib_path.search)
366 );
367
368 println!(
369 "Library {} found in: {} with runtime search path: {}",
370 lib_path.library,
371 lib_path.path.display(),
372 lib_path.search.display()
373 );
374 }
375 Err(e) => {
376 println!("cargo:warning={e}");
378 std::process::exit(1);
379 }
380 }
381 }
382}