use std::{
env, fs,
path::{Path, PathBuf},
vec,
};
struct InstallPath {
paths: Vec<PathBuf>,
names: Vec<&'static str>,
}
pub struct LibraryPath {
pub path: PathBuf,
pub library: String,
pub search: PathBuf,
}
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() {
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") {
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.lib", "metacalld.lib"],
})
} else if cfg!(target_os = "macos") {
Ok(InstallPath {
paths: vec![
PathBuf::from("/opt/homebrew/lib/"),
PathBuf::from("/usr/local/lib/"),
],
names: vec!["libmetacall.dylib", "libmetacalld.dylib"],
})
} else if cfg!(target_os = "linux") {
Ok(InstallPath {
paths: vec![PathBuf::from("/usr/local/lib/"), PathBuf::from("/gnu/lib/")],
names: vec!["libmetacall.so", "libmetacalld.so"],
})
} else {
Err(format!("Platform {} not supported", env::consts::OS).into())
}
}
fn get_search_config() -> Result<InstallPath, Box<dyn std::error::Error>> {
if let Ok(custom_path) = env::var("METACALL_INSTALL_PATH") {
return Ok(InstallPath {
paths: vec![PathBuf::from(custom_path)],
names: vec![
"libmetacall.so",
"libmetacalld.so",
"libmetacall.dylib",
"libmetacalld.dylib",
"metacall.lib",
"metacalld.lib",
],
});
}
platform_install_paths()
}
fn get_parent_and_library(path: &Path) -> Option<(PathBuf, String)> {
let parent = path.parent()?.to_path_buf();
let stem = path.file_stem()?.to_str()?;
let cleaned_stem = stem.strip_prefix("lib").unwrap_or(stem).to_string();
Some((parent, cleaned_stem))
}
#[cfg(target_os = "windows")]
fn strip_extended_length_prefix(path: PathBuf) -> PathBuf {
let path_str = path.to_string_lossy();
if let Some(stripped) = path_str.strip_prefix(r"\\?\") {
PathBuf::from(stripped)
} else {
path
}
}
#[cfg(target_os = "windows")]
fn find_metacall_dll(
search_paths: &[PathBuf],
library_name: &str,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
let dll_name = format!("{}.dll", library_name);
for search_path in search_paths {
match find_files_recursively(search_path, &dll_name, None) {
Ok(files) if !files.is_empty() => {
let found_dll = fs::canonicalize(&files[0])?;
if let Some(parent) = found_dll.parent() {
return Ok(strip_extended_length_prefix(parent.to_path_buf()));
}
}
_ => continue,
}
}
Err(format!(
"MetaCall DLL ({}) not found. Searched in: {}",
dll_name,
search_paths
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join(", ")
)
.into())
}
pub fn find_metacall_library() -> Result<LibraryPath, Box<dyn std::error::Error>> {
let search_config = get_search_config()?;
for search_path in &search_config.paths {
for name in &search_config.names {
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, library_name)) => {
#[cfg(target_os = "windows")]
let (lib_path, search_path) = {
let cleaned_parent = strip_extended_length_prefix(parent);
let dll_search = match find_metacall_dll(
&search_config.paths,
&library_name,
) {
Ok(dll_path) => dll_path,
Err(e) => {
println!(
"cargo:warning=Could not find DLL, using library path: {}",
e
);
cleaned_parent.clone()
}
};
(cleaned_parent, dll_search)
};
#[cfg(not(target_os = "windows"))]
let (lib_path, search_path) = (parent.clone(), parent);
return Ok(LibraryPath {
path: lib_path,
library: library_name,
search: search_path,
});
}
None => continue,
};
}
Ok(_) => {
continue;
}
Err(e) => {
println!(
"cargo:warning=Error searching in {}: {}",
search_path.display(),
e
);
continue;
}
}
}
}
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 define_library_search_path(env_var: &str, separator: &str, path: &Path) -> String {
let existing = env::var(env_var).unwrap_or_default();
let path_str: String = String::from(path.to_str().unwrap());
let combined = if existing.is_empty() {
path_str
} else {
format!("{}{}{}", existing, separator, path_str)
};
format!("{}={}", env_var, combined)
}
fn set_rpath(lib_path: &Path) {
let path_str = lib_path.to_str().unwrap();
#[cfg(target_os = "linux")]
{
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", path_str);
println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN");
println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN/../lib");
}
#[cfg(target_os = "macos")]
{
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", path_str);
println!("cargo:rustc-link-arg=-Wl,-rpath,@loader_path");
println!("cargo:rustc-link-arg=-Wl,-rpath,@loader_path/../lib");
}
#[cfg(target_os = "aix")]
{
println!(
"cargo:rustc-link-arg=-Wl,-blibpath:{}:/usr/lib:/lib",
path_str
);
}
#[cfg(target_os = "windows")]
{
println!(
"cargo:warning=On Windows, make sure {} is in your PATH or next to your executable",
path_str
);
}
}
pub fn build() {
if let Ok(val) = env::var("PROJECT_OUTPUT_DIR") {
println!("cargo:rustc-link-search=native={val}");
match env::var("CMAKE_BUILD_TYPE") {
Ok(val) => {
if val == "Debug" {
println!("cargo:rustc-link-lib=dylib=metacalld");
} else {
println!("cargo:rustc-link-lib=dylib=metacall");
}
}
Err(_) => {
println!("cargo:rustc-link-lib=dylib=metacall");
}
}
} else {
match find_metacall_library() {
Ok(lib_path) => {
println!("cargo:rustc-link-search=native={}", lib_path.path.display());
println!("cargo:rustc-link-lib=dylib={}", lib_path.library);
set_rpath(&lib_path.path);
#[cfg(target_os = "linux")]
const ENV_VAR: &str = "LD_LIBRARY_PATH";
#[cfg(target_os = "macos")]
const ENV_VAR: &str = "DYLD_LIBRARY_PATH";
#[cfg(target_os = "windows")]
const ENV_VAR: &str = "PATH";
#[cfg(target_os = "aix")]
const ENV_VAR: &str = "LIBPATH";
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "aix"))]
const SEPARATOR: &str = ":";
#[cfg(target_os = "windows")]
const SEPARATOR: &str = ";";
println!(
"cargo:rustc-env={}",
define_library_search_path(ENV_VAR, SEPARATOR, &lib_path.search)
);
println!(
"Library {} found in: {} with runtime search path: {}",
lib_path.library,
lib_path.path.display(),
lib_path.search.display()
);
}
Err(e) => {
println!("cargo:warning={e}");
std::process::exit(1);
}
}
}
}