Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 47 additions & 11 deletions dftd3/src/ffi_dynamic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,28 @@
//! order:
//!
//! 1. User-defined candidates via environment variables `DFTD3_DYLOAD`.
//! Multiple paths can be separated by `:` (Unix/macOS) or `;` (Windows).
//! 2. LD_LIBRARY_PATH style discovery via environment variables
//! `LD_LIBRARY_PATH` (Linux), `DYLD_LIBRARY_PATH` and
//! `DYLD_FALLBACK_LIBRARY_PATH` (macOS), `PATH` (Windows). Note we are not
//! distinguishing different operating systems, so all these environment
//! variables will be checked on all platforms.
//! variables will be checked on all platforms. Path lists are split by `:`
//! on Unix/macOS and `;` on Windows.
//! 3. Python interpreter path discovery: For each python interpreter found, the
//! library is expected to be at the `lib` directory of the python
//! installation. For example, if python is at `/path/bin/python`, the
//! library is expected at `/path/lib/libs-dftd3.so`.
//! - The python interpreter path of `DFTD3_PYTHON_PATH` environment
//! variable, if set.
//! - The conda prefix path of `CONDA_PREFIX` environment variable, if set.
//! On Windows also checks `{CONDA_PREFIX}/Library/bin` and
//! `{CONDA_PREFIX}/Library/lib`.
//! - The python interpreter path in `PATH` environment variable, if exists.
//! Will first check `python`, then `python3`.
//! 4. Standard system candidates, such as `lib{LIB_NAME_LINK}.so` in some
//! common library directories such as `/usr/lib`, `/usr/local/lib`, and
//! `/lib`.
//! `/lib`. These are Unix-specific and silently skipped when absent on
//! Windows.
//!
//! For API developer, if you want to check the library `libs-dftd3.so` loading
//! sequence, you can try the following code:
Expand All @@ -44,6 +49,11 @@
pub const MOD_NAME: &str = module_path!();
pub const LIB_NAME: &str = "DFTD3";
pub const LIB_NAME_SHOW: &str = "s-dftd3";
#[cfg(windows)]
/// On Windows (MinGW), the library includes a "-1" SO version suffix.
/// The "-1" may need updating when upstream bumps ABI.
pub const LIB_NAME_LINK: &str = "s-dftd3-1";
#[cfg(not(windows))]
pub const LIB_NAME_LINK: &str = "s-dftd3";

#[cfg(feature = "dynamic_loading")]
Expand All @@ -53,8 +63,21 @@ mod dynamic_loading_specific {
use std::fmt::Debug;
use std::sync::OnceLock;

#[cfg(not(windows))]
use std::env::consts::{DLL_PREFIX, DLL_SUFFIX};

#[cfg(windows)]
use std::env::consts::DLL_SUFFIX;
#[cfg(windows)]
/// MinGW convention uses "lib" prefix, but std::env::consts::DLL_PREFIX
/// returns "" on all Windows targets regardless of toolchain.
const DLL_PREFIX: &str = "lib";

#[cfg(windows)]
const PATH_LIST_SEPARATOR: char = ';';
#[cfg(not(windows))]
const PATH_LIST_SEPARATOR: char = ':';

/// Detect Python interpreter path and return the corresponding lib
/// directory. Uses OnceLock pattern for lazy initialization.
static PYTHON_LIB_PATH: OnceLock<Vec<String>> = OnceLock::new();
Expand All @@ -71,26 +94,37 @@ mod dynamic_loading_specific {
}
}

// 2. Check conda prefix exists
// 2. Check conda prefix exists (on Windows also checks Library/{bin,lib})
if let Ok(conda_prefix) = std::env::var("CONDA_PREFIX") {
let conda_lib_path = format!("{conda_prefix}/lib");
if std::path::Path::new(&conda_lib_path).exists() {
lib_paths.push(conda_lib_path);
}
#[cfg(windows)]
{
let conda_library_bin = format!("{conda_prefix}/Library/bin");
if std::path::Path::new(&conda_library_bin).exists() {
lib_paths.push(conda_library_bin);
}
let conda_library_lib = format!("{conda_prefix}/Library/lib");
if std::path::Path::new(&conda_library_lib).exists() {
lib_paths.push(conda_library_lib);
}
}
}

// 3. Try to find python in PATH
if let Ok(paths) = std::env::var("PATH") {
// first check python, then python3
for path in paths.split(":") {
for path in paths.split(PATH_LIST_SEPARATOR) {
let python_bin = format!("{path}/python");
if std::path::Path::new(&python_bin).exists() {
if let Some(lib_path) = extract_lib_from_python_bin(&python_bin) {
lib_paths.push(lib_path);
}
}
}
for path in paths.split(":") {
for path in paths.split(PATH_LIST_SEPARATOR) {
let python_bin = format!("{path}/python3");
if std::path::Path::new(&python_bin).exists() {
if let Some(lib_path) = extract_lib_from_python_bin(&python_bin) {
Expand Down Expand Up @@ -122,22 +156,23 @@ mod dynamic_loading_specific {
fn get_lib_candidates() -> Vec<String> {
let mut candidates = vec![];

// User-defined candidates via environment variables
// User-defined candidates via environment variables (paths split by platform
// separator)
for env_var in [format!("DFTD3_DYLOAD_{LIB_NAME}").as_str(), "DFTD3_DYLOAD"] {
if let Ok(path) = std::env::var(env_var) {
candidates.extend(path.split(":").map(|s| s.to_string()));
candidates.extend(path.split(PATH_LIST_SEPARATOR).map(|s| s.to_string()));
}
}

// LD_LIBRARY_PATH style discovery
// LD_LIBRARY_PATH style discovery (paths split by platform separator)
for env_var in [
"LD_LIBRARY_PATH", // linux
"DYLD_LIBRARY_PATH", // macos
"DYLD_FALLBACK_LIBRARY_PATH", // macos
"PATH", // windows
] {
if let Ok(paths) = std::env::var(env_var) {
for path in paths.split(":") {
for path in paths.split(PATH_LIST_SEPARATOR) {
candidates.push(format!("{path}/{DLL_PREFIX}{LIB_NAME_LINK}{DLL_SUFFIX}"));
}
}
Expand Down Expand Up @@ -173,10 +208,11 @@ Candidates: {candidates:#?}
Please check:
- If dynamic-loading is not desired, disable the `dynamic_loading` feature in Cargo.toml.
- Use environment variable `DFTD3_DYLOAD_{LIB_NAME}` or `DFTD3_DYLOAD` to specify the library path.
- If `lib{LIB_NAME_LINK}.so` is installed on your system.
- If `LD_LIBRARY_PATH` is set correctly.
- If `lib{LIB_NAME_LINK}.so` (or `{LIB_NAME_LINK}.dll` on Windows) is installed on your system.
- If `LD_LIBRARY_PATH` (Unix/macOS) or `PATH` (Windows) is set correctly.
- Python interpreter path discovery: if Python is at `/path/bin/python`,
the library is expected at `/path/lib/libs-dftd3.so`.
- On Windows with conda, try `{LIB_NAME_LINK}` from `<CONDA_PREFIX>/Library/bin`.

Error message(s):
{err_msg}
Expand Down
Loading