Skip to content

Commit 9070d7f

Browse files
committed
Exposed the rs port find method for metacall library.
1 parent 8dd3f55 commit 9070d7f

File tree

6 files changed

+288
-254
lines changed

6 files changed

+288
-254
lines changed

source/ports/rs_port/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ license = "Apache-2.0"
77
name = "metacall"
88
readme = "README.md"
99
repository = "https://github.com/metacall/core/tree/develop/source/ports/rs_port"
10-
version = "0.5.0"
10+
version = "0.5.1"
1111

1212
[lib]
1313
crate-type = ["lib"]
@@ -18,3 +18,6 @@ path = "src/lib.rs"
1818

1919
[dependencies]
2020
metacall-inline = { path = "./inline", version = "0.2.0" }
21+
22+
[build-dependencies]
23+
metacall-sys = { path = "./sys", version = "0.1.0" }

source/ports/rs_port/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,24 @@ MetaCall is a C plugin based library. This crate wraps the C library into Rust,
1515
curl -sL https://raw.githubusercontent.com/metacall/install/master/install.sh | sh
1616
```
1717

18+
# Linking
19+
20+
If your project uses MetaCall in a folder that is not in the system path, we encourage to use `metacall-sys` crate as a `build-dependecy`. By this way you will be able to locate and link MetaCall directly in your build system. For example:
21+
22+
`Cargo.toml`:
23+
```toml
24+
[build-dependencies]
25+
metacall-sys = { path = "./sys", version = "0.1.0" }
26+
```
27+
28+
`build.rs`:
29+
```rust
30+
fn main() {
31+
// Find MetaCall library
32+
metacall_sys::build();
33+
}
34+
```
35+
1836
# Example
1937

2038
`sum.ts`

source/ports/rs_port/build.rs

Lines changed: 2 additions & 253 deletions
Original file line numberDiff line numberDiff line change
@@ -1,255 +1,4 @@
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(&current_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-
1911
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();
2554
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "metacall-sys"
3+
version = "0.1.0"
4+
repository = "https://github.com/metacall/core/tree/develop/source/ports/rs_port"
5+
edition = "2021"
6+
license = "Apache-2.0"
7+
description = "Crate for finding metacall library in the system."

0 commit comments

Comments
 (0)