use std::path::{Path, PathBuf};
use datafusion_common::{DataFusionError, Result};
use crate::tests::ForeignLibraryModule;
fn find_cdylib(deps_dir: &Path) -> Result<PathBuf> {
let lib_prefix = if cfg!(target_os = "windows") {
""
} else {
"lib"
};
let lib_ext = if cfg!(target_os = "macos") {
"dylib"
} else if cfg!(target_os = "windows") {
"dll"
} else {
"so"
};
let pattern = format!("{lib_prefix}datafusion_ffi.{lib_ext}");
let lib_path = deps_dir.join(&pattern);
if lib_path.exists() {
return Ok(lib_path);
}
Err(DataFusionError::External(
format!("Could not find library at {}", lib_path.display()).into(),
))
}
fn find_library() -> Result<PathBuf> {
let exe =
std::env::current_exe().map_err(|e| DataFusionError::External(Box::new(e)))?;
let deps_dir = exe.parent().ok_or_else(|| {
DataFusionError::External("Failed to find test binary directory".into())
})?;
find_cdylib(deps_dir)
}
fn load_module(lib_path: &Path) -> Result<ForeignLibraryModule> {
let expected_version = crate::version();
let lib = unsafe {
libloading::Library::new(lib_path)
.map_err(|e| DataFusionError::External(Box::new(e)))?
};
let get_module: libloading::Symbol<extern "C" fn() -> ForeignLibraryModule> = unsafe {
lib.get(b"datafusion_ffi_get_module")
.map_err(|e| DataFusionError::External(Box::new(e)))?
};
let module = get_module();
assert_eq!((module.version)(), expected_version);
std::mem::forget(lib);
Ok(module)
}
pub fn get_module() -> Result<ForeignLibraryModule> {
load_module(&find_library()?)
}
pub fn get_module_copy(name: &str) -> Result<ForeignLibraryModule> {
let source = find_library()?;
let file_name = source
.file_name()
.and_then(|name| name.to_str())
.ok_or_else(|| DataFusionError::External("Invalid cdylib filename".into()))?;
#[cfg(target_os = "windows")]
let destination = source.with_file_name(format!("{name}_{file_name}"));
#[cfg(not(target_os = "windows"))]
let destination =
source.with_file_name(format!("{}_{}_{}", std::process::id(), name, file_name));
std::fs::copy(&source, &destination)
.map_err(|e| DataFusionError::External(Box::new(e)))?;
match load_module(&destination) {
Ok(module) => {
#[cfg(not(target_os = "windows"))]
let _ = std::fs::remove_file(destination);
Ok(module)
}
Err(error) => {
let _ = std::fs::remove_file(destination);
Err(error)
}
}
}