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