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
191pub fn build() {
192 if let Ok(val) = env::var("PROJECT_OUTPUT_DIR") {
194 println!("cargo:rustc-link-search=native={val}");
196
197 match env::var("CMAKE_BUILD_TYPE") {
199 Ok(val) => {
200 if val == "Debug" {
201 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 match find_metacall_library() {
214 Ok(lib_path) => {
215 println!("cargo:rustc-link-search=native={}", lib_path.path.display());
217 println!("cargo:rustc-link-lib=dylib={}", lib_path.library);
218
219 #[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 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 println!("cargo:rustc-link-lib=dylib=metacall")
252 }
253 }
254 }
255}