pub mod pinned;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use cljrs_eval::GlobalEnv;
pub fn native_lib_path(crate_dir: &Path, crate_name: &str, release: bool) -> PathBuf {
let profile = if release { "release" } else { "debug" };
let lib_file = if cfg!(target_os = "windows") {
format!("{crate_name}.dll")
} else if cfg!(target_os = "macos") {
format!("lib{crate_name}.dylib")
} else {
format!("lib{crate_name}.so")
};
let target_dir = cargo_target_dir(crate_dir).unwrap_or_else(|| crate_dir.join("target"));
target_dir.join(profile).join(lib_file)
}
fn cargo_target_dir(crate_dir: &Path) -> Option<PathBuf> {
let output = std::process::Command::new("cargo")
.args([
"metadata",
"--format-version",
"1",
"--no-deps",
"--offline",
])
.current_dir(crate_dir)
.output()
.ok()?;
if !output.status.success() {
return None;
}
target_dir_from_metadata(std::str::from_utf8(&output.stdout).ok()?)
}
fn target_dir_from_metadata(json: &str) -> Option<PathBuf> {
let meta: serde_json::Value = serde_json::from_str(json).ok()?;
Some(PathBuf::from(meta.get("target_directory")?.as_str()?))
}
pub fn load_project_lib(rust_config: &cljrs_project::config::RustConfig, globals: &Arc<GlobalEnv>) {
let Some(init_fn) = rust_config.init_fn.as_deref() else {
return;
};
let Some(crate_name) = rust_config.crate_name() else {
return;
};
let sym_name = init_fn.rsplit("::").next().unwrap_or(init_fn);
let lib_path = native_lib_path(&rust_config.crate_dir, crate_name, false);
if !lib_path.exists() {
eprintln!(
"cljrs: native library not found at {} — run `cljrs build-native` first",
lib_path.display()
);
return;
}
unsafe {
let lib = match libloading::Library::new(&lib_path) {
Ok(l) => l,
Err(e) => {
eprintln!("cljrs: could not load {}: {e}", lib_path.display());
return;
}
};
let sym_bytes: Vec<u8> = format!("{sym_name}\0").into_bytes();
let init: libloading::Symbol<unsafe extern "C" fn(*mut cljrs_interop::Registry)> =
match lib.get(&sym_bytes) {
Ok(s) => s,
Err(e) => {
eprintln!(
"cljrs: could not find symbol {sym_name} in {}: {e}",
lib_path.display()
);
return;
}
};
let mut registry = cljrs_interop::Registry::new(globals.clone());
init(&mut registry as *mut _);
std::mem::forget(lib);
}
eprintln!(
"[build-native] loaded {} ({})",
lib_path.display(),
sym_name
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reads_target_directory_from_compact_metadata() {
let json =
r#"{"packages":[],"workspace_root":"/w","target_directory":"/w/target","version":1}"#;
assert_eq!(
target_dir_from_metadata(json),
Some(PathBuf::from("/w/target"))
);
}
#[test]
fn reads_target_directory_from_pretty_printed_metadata() {
let json = r#"{
"version": 1,
"target_directory": "/w/target",
"workspace_root": "/w"
}"#;
assert_eq!(
target_dir_from_metadata(json),
Some(PathBuf::from("/w/target"))
);
}
#[test]
fn unescapes_a_windows_path() {
let json = r#"{"target_directory":"C:\\Users\\dev\\my crate\\target"}"#;
assert_eq!(
target_dir_from_metadata(json),
Some(PathBuf::from(r"C:\Users\dev\my crate\target"))
);
}
#[test]
fn unescapes_quotes_and_backslashes() {
let json = r#"{"target_directory":"/w/a\"b\\c/target"}"#;
assert_eq!(
target_dir_from_metadata(json),
Some(PathBuf::from("/w/a\"b\\c/target"))
);
}
#[test]
fn absent_or_unusable_metadata_is_none() {
assert_eq!(target_dir_from_metadata(r#"{"version":1}"#), None);
assert_eq!(target_dir_from_metadata(r#"{"target_directory":7}"#), None);
assert_eq!(target_dir_from_metadata("not json at all"), None);
assert_eq!(target_dir_from_metadata(""), None);
}
#[test]
fn native_lib_path_falls_back_to_crate_local_target() {
let dir = std::env::temp_dir().join("cljrs-no-manifest-here");
let path = native_lib_path(&dir, "mylib", false);
assert!(path.starts_with(&dir), "{}", path.display());
assert!(path.to_string_lossy().contains("debug"));
}
}