|
1 | | -use std::{ |
2 | | - env, fs, |
3 | | - path::{Path, PathBuf}, |
4 | | - vec, |
5 | | -}; |
6 | | - |
7 | | -// Search for MetaCall libraries in platform-specific locations |
8 | | -// Handle custom installation paths via environment variables |
9 | | -// Find configuration files recursively |
10 | | -// Provide helpful error messages when things aren't found |
11 | | - |
12 | | -/// Represents the install paths for a platform |
13 | | -struct InstallPath { |
14 | | - paths: Vec<PathBuf>, |
15 | | - names: Vec<&'static str>, |
16 | | -} |
17 | | - |
18 | | -/// Represents the match of a library when it's found |
19 | | -struct LibraryPath { |
20 | | - path: PathBuf, |
21 | | - library: String, |
22 | | -} |
23 | | - |
24 | | -/// Find files recursively in a directory matching a pattern |
25 | | -fn find_files_recursively<P: AsRef<Path>>( |
26 | | - root_dir: P, |
27 | | - filename: &str, |
28 | | - max_depth: Option<usize>, |
29 | | -) -> Result<Vec<PathBuf>, Box<dyn std::error::Error>> { |
30 | | - let mut matches = Vec::new(); |
31 | | - let mut stack = vec![(root_dir.as_ref().to_path_buf(), 0)]; |
32 | | - |
33 | | - while let Some((current_dir, depth)) = stack.pop() { |
34 | | - if let Some(max) = max_depth { |
35 | | - if depth > max { |
36 | | - continue; |
37 | | - } |
38 | | - } |
39 | | - |
40 | | - if let Ok(entries) = fs::read_dir(¤t_dir) { |
41 | | - for entry in entries.flatten() { |
42 | | - let path = entry.path(); |
43 | | - |
44 | | - if path.is_file() { |
45 | | - // Simple filename comparison instead of regex |
46 | | - if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) { |
47 | | - if file_name == filename { |
48 | | - matches.push(path); |
49 | | - } |
50 | | - } |
51 | | - } else if path.is_dir() { |
52 | | - stack.push((path, depth + 1)); |
53 | | - } |
54 | | - } |
55 | | - } |
56 | | - } |
57 | | - |
58 | | - Ok(matches) |
59 | | -} |
60 | | - |
61 | | -fn platform_install_paths() -> Result<InstallPath, Box<dyn std::error::Error>> { |
62 | | - if cfg!(target_os = "windows") { |
63 | | - // Defaults to path: C:\Users\Default\AppData\Local |
64 | | - let local_app_data = env::var("LOCALAPPDATA") |
65 | | - .unwrap_or_else(|_| String::from("C:\\Users\\Default\\AppData\\Local")); |
66 | | - |
67 | | - Ok(InstallPath { |
68 | | - paths: vec![PathBuf::from(local_app_data) |
69 | | - .join("MetaCall") |
70 | | - .join("metacall")], |
71 | | - names: vec!["metacall.lib"], |
72 | | - }) |
73 | | - } else if cfg!(target_os = "macos") { |
74 | | - Ok(InstallPath { |
75 | | - paths: vec![ |
76 | | - PathBuf::from("/opt/homebrew/lib/"), |
77 | | - PathBuf::from("/usr/local/lib/"), |
78 | | - ], |
79 | | - names: vec!["libmetacall.dylib"], |
80 | | - }) |
81 | | - } else if cfg!(target_os = "linux") { |
82 | | - Ok(InstallPath { |
83 | | - paths: vec![PathBuf::from("/usr/local/lib/"), PathBuf::from("/gnu/lib/")], |
84 | | - names: vec!["libmetacall.so"], |
85 | | - }) |
86 | | - } else { |
87 | | - Err(format!("Platform {} not supported", env::consts::OS).into()) |
88 | | - } |
89 | | -} |
90 | | - |
91 | | -/// Get search paths, checking for custom installation path first |
92 | | -fn get_search_config() -> Result<InstallPath, Box<dyn std::error::Error>> { |
93 | | - // First, check if user specified a custom path |
94 | | - if let Ok(custom_path) = env::var("METACALL_INSTALL_PATH") { |
95 | | - // For custom paths, we need to search for any metacall library variant |
96 | | - return Ok(InstallPath { |
97 | | - paths: vec![PathBuf::from(custom_path)], |
98 | | - names: vec![ |
99 | | - "libmetacall.so", |
100 | | - "libmetacalld.so", |
101 | | - "libmetacall.dylib", |
102 | | - "libmetacalld.dylib", |
103 | | - "metacall.lib", |
104 | | - "metacalld.lib", |
105 | | - ], |
106 | | - }); |
107 | | - } |
108 | | - |
109 | | - // Fall back to platform-specific paths |
110 | | - platform_install_paths() |
111 | | -} |
112 | | - |
113 | | -/// Get the parent path and library name |
114 | | -fn get_parent_and_library(path: &Path) -> Option<(PathBuf, String)> { |
115 | | - let parent = path.parent()?.to_path_buf(); |
116 | | - |
117 | | - // Get the file stem (filename without extension) |
118 | | - let stem = path.file_stem()?.to_str()?; |
119 | | - |
120 | | - // Remove "lib" prefix if present |
121 | | - let cleaned_stem = stem.strip_prefix("lib").unwrap_or(stem).to_string(); |
122 | | - |
123 | | - Some((parent, cleaned_stem)) |
124 | | -} |
125 | | - |
126 | | -/// Find the MetaCall library |
127 | | -/// This orchestrates the search process |
128 | | -fn find_metacall_library() -> Result<LibraryPath, Box<dyn std::error::Error>> { |
129 | | - let search_config = get_search_config()?; |
130 | | - |
131 | | - // Search in each configured path |
132 | | - for search_path in &search_config.paths { |
133 | | - for name in &search_config.names { |
134 | | - // Search with no limit in depth |
135 | | - match find_files_recursively(search_path, name, None) { |
136 | | - Ok(files) if !files.is_empty() => { |
137 | | - let found_lib = fs::canonicalize(&files[0])?; |
138 | | - |
139 | | - match get_parent_and_library(&found_lib) { |
140 | | - Some((parent, name)) => { |
141 | | - return Ok(LibraryPath { |
142 | | - path: parent, |
143 | | - library: name, |
144 | | - }) |
145 | | - } |
146 | | - None => continue, |
147 | | - }; |
148 | | - } |
149 | | - Ok(_) => { |
150 | | - // No files found in this path, continue searching |
151 | | - continue; |
152 | | - } |
153 | | - Err(e) => { |
154 | | - eprintln!("Error searching in {}: {}", search_path.display(), e); |
155 | | - continue; |
156 | | - } |
157 | | - } |
158 | | - } |
159 | | - } |
160 | | - |
161 | | - // If we get here, library wasn't found |
162 | | - let search_paths: Vec<String> = search_config |
163 | | - .paths |
164 | | - .iter() |
165 | | - .map(|p| p.display().to_string()) |
166 | | - .collect(); |
167 | | - |
168 | | - Err(format!( |
169 | | - "MetaCall library not found. Searched in: {}. \ |
170 | | - If you have it installed elsewhere, set METACALL_INSTALL_PATH environment variable.", |
171 | | - search_paths.join(", ") |
172 | | - ) |
173 | | - .into()) |
174 | | -} |
175 | | - |
176 | | -fn define_library_search_path(env_var: &str, separator: &str, path: &Path) -> String { |
177 | | - // Get the current value of the env var, if any |
178 | | - let existing = env::var(env_var).unwrap_or_default(); |
179 | | - let path_str: String = String::from(path.to_str().unwrap()); |
180 | | - |
181 | | - // Append to it |
182 | | - let combined = if existing.is_empty() { |
183 | | - path_str |
184 | | - } else { |
185 | | - format!("{}{}{}", existing, separator, path_str) |
186 | | - }; |
187 | | - |
188 | | - format!("{}={}", env_var, combined) |
189 | | -} |
190 | | - |
191 | 1 | fn main() { |
192 | | - // When running tests from CMake |
193 | | - if let Ok(val) = env::var("PROJECT_OUTPUT_DIR") { |
194 | | - // Link search path to build folder |
195 | | - println!("cargo:rustc-link-search=native={val}"); |
196 | | - |
197 | | - // Link against correct version of metacall |
198 | | - match env::var("CMAKE_BUILD_TYPE") { |
199 | | - Ok(val) => { |
200 | | - if val == "Debug" { |
201 | | - // Try to link the debug version when running tests |
202 | | - println!("cargo:rustc-link-lib=dylib=metacalld"); |
203 | | - } else { |
204 | | - println!("cargo:rustc-link-lib=dylib=metacall"); |
205 | | - } |
206 | | - } |
207 | | - Err(_) => { |
208 | | - println!("cargo:rustc-link-lib=dylib=metacall"); |
209 | | - } |
210 | | - } |
211 | | - } else { |
212 | | - // When building from Cargo, try to find MetaCall |
213 | | - match find_metacall_library() { |
214 | | - Ok(lib_path) => { |
215 | | - // Define linker flags |
216 | | - println!("cargo:rustc-link-search=native={}", lib_path.path.display()); |
217 | | - println!("cargo:rustc-link-lib=dylib={}", lib_path.library); |
218 | | - |
219 | | - // Set the runtime environment variable for finding the library during tests |
220 | | - #[cfg(target_os = "linux")] |
221 | | - const ENV_VAR: &str = "LD_LIBRARY_PATH"; |
222 | | - |
223 | | - #[cfg(target_os = "macos")] |
224 | | - const ENV_VAR: &str = "DYLD_LIBRARY_PATH"; |
225 | | - |
226 | | - #[cfg(target_os = "windows")] |
227 | | - const ENV_VAR: &str = "PATH"; |
228 | | - |
229 | | - #[cfg(target_os = "aix")] |
230 | | - const ENV_VAR: &str = "LIBPATH"; |
231 | | - |
232 | | - #[cfg(any(target_os = "linux", target_os = "macos", target_os = "aix"))] |
233 | | - const SEPARATOR: &str = ":"; |
234 | | - |
235 | | - #[cfg(target_os = "windows")] |
236 | | - const SEPARATOR: &str = ";"; |
237 | | - |
238 | | - println!( |
239 | | - "cargo:rustc-env={}", |
240 | | - define_library_search_path(ENV_VAR, SEPARATOR, &lib_path.path) |
241 | | - ); |
242 | | - } |
243 | | - Err(e) => { |
244 | | - // Print the error |
245 | | - eprintln!( |
246 | | - "Failed to find MetaCall library with: {e} \ |
247 | | - Still trying to link in case the library is in system paths" |
248 | | - ); |
249 | | - |
250 | | - // Still try to link in case the library is in system paths |
251 | | - println!("cargo:rustc-link-lib=dylib=metacall") |
252 | | - } |
253 | | - } |
254 | | - } |
| 2 | + // Find MetaCall library |
| 3 | + metacall_sys::build(); |
255 | 4 | } |
0 commit comments