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
18struct LibraryPath {
20 path: PathBuf,
21 library: String,
22}
23
24fn 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 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
61fn platform_install_paths() -> Result<InstallPath, Box<dyn std::error::Error>> {
62 if cfg!(target_os = "windows") {
63 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
91fn get_search_config() -> Result<InstallPath, Box<dyn std::error::Error>> {
93 if let Ok(custom_path) = env::var("METACALL_INSTALL_PATH") {
95 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 platform_install_paths()
111}
112
113fn get_parent_and_library(path: &Path) -> Option<(PathBuf, String)> {
115 let parent = path.parent()?.to_path_buf();
116
117 let stem = path.file_stem()?.to_str()?;
119
120 let cleaned_stem = stem.strip_prefix("lib").unwrap_or(stem).to_string();
122
123 Some((parent, cleaned_stem))
124}
125
126fn find_metacall_library() -> Result<LibraryPath, Box<dyn std::error::Error>> {
129 let search_config = get_search_config()?;
130
131 for search_path in &search_config.paths {
133 for name in &search_config.names {
134 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 continue;
152 }
153 Err(e) => {
154 eprintln!("Error searching in {}: {}", search_path.display(), e);
155 continue;
156 }
157 }
158 }
159 }
160
161 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
176fn define_library_search_path(env_var: &str, separator: &str, path: &Path) -> String {
177 let existing = env::var(env_var).unwrap_or_default();
179 let path_str: String = String::from(path.to_str().unwrap());
180
181 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
191fn set_rpath(lib_path: &Path) {
194 let path_str = lib_path.to_str().unwrap();
195
196 #[cfg(target_os = "linux")]
197 {
198 println!("cargo:rustc-link-arg=-Wl,-rpath,{}", path_str);
200 println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN");
202 println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN/../lib");
203 }
204
205 #[cfg(target_os = "macos")]
206 {
207 println!("cargo:rustc-link-arg=-Wl,-rpath,{}", path_str);
209 println!("cargo:rustc-link-arg=-Wl,-rpath,@loader_path");
211 println!("cargo:rustc-link-arg=-Wl,-rpath,@loader_path/../lib");
212 }
213
214 #[cfg(target_os = "aix")]
215 {
216 println!("cargo:rustc-link-arg=-Wl,-blibpath:{}:/usr/lib:/lib", path_str);
218 }
219
220 #[cfg(target_os = "windows")]
221 {
222 println!(
224 "cargo:warning=On Windows, make sure {} is in your PATH or next to your executable",
225 path_str
226 );
227 }
228}
229
230pub fn build() {
231 if let Ok(val) = env::var("PROJECT_OUTPUT_DIR") {
233 println!("cargo:rustc-link-search=native={val}");
235
236 match env::var("CMAKE_BUILD_TYPE") {
238 Ok(val) => {
239 if val == "Debug" {
240 println!("cargo:rustc-link-lib=dylib=metacalld");
242 } else {
243 println!("cargo:rustc-link-lib=dylib=metacall");
244 }
245 }
246 Err(_) => {
247 println!("cargo:rustc-link-lib=dylib=metacall");
248 }
249 }
250 } else {
251 match find_metacall_library() {
253 Ok(lib_path) => {
254 println!("cargo:rustc-link-search=native={}", lib_path.path.display());
256 println!("cargo:rustc-link-lib=dylib={}", lib_path.library);
257
258 set_rpath(&lib_path.path);
260
261 #[cfg(target_os = "linux")]
263 const ENV_VAR: &str = "LD_LIBRARY_PATH";
264
265 #[cfg(target_os = "macos")]
266 const ENV_VAR: &str = "DYLD_LIBRARY_PATH";
267
268 #[cfg(target_os = "windows")]
269 const ENV_VAR: &str = "PATH";
270
271 #[cfg(target_os = "aix")]
272 const ENV_VAR: &str = "LIBPATH";
273
274 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "aix"))]
275 const SEPARATOR: &str = ":";
276
277 #[cfg(target_os = "windows")]
278 const SEPARATOR: &str = ";";
279
280 println!(
281 "cargo:rustc-env={}",
282 define_library_search_path(ENV_VAR, SEPARATOR, &lib_path.path)
283 );
284 }
285 Err(e) => {
286 eprintln!(
288 "Failed to find MetaCall library with: {e} \
289 Still trying to link in case the library is in system paths"
290 );
291
292 println!("cargo:rustc-link-lib=dylib=metacall")
294 }
295 }
296 }
297}