Loading...
Searching...
No Matches
PluginLoader.cpp
Go to the documentation of this file.
1/*
2** This file is part of eOn.
3**
4** SPDX-License-Identifier: BSD-3-Clause
5**
6** Copyright (c) 2010--present, eOn Development Team
7** All rights reserved.
8**
9** Repo:
10** https://github.com/TheochemUI/eOn
11*/
13
14#include <cstdlib>
15#include <filesystem>
16#include <sstream>
17
18namespace eonc {
19
21 static PluginLoader loader;
22 return loader;
23}
24
26 // Seed from EON_POTENTIALS_PATH env var (colon-separated on POSIX).
27 // Config-file paths are prepended later via add_config_paths().
28 const char *env = std::getenv("EON_POTENTIALS_PATH");
29 if (env && *env) {
30#ifdef _WIN32
31 append_paths(env, ';');
32#else
33 append_paths(env, ':');
34#endif
35 }
36}
37
39 for (auto &[name, handle] : m_handles) {
40 dynlib::close(handle);
41 }
42}
43
44void PluginLoader::append_paths(const std::string &path_str, char sep) {
45 std::string::size_type start = 0;
46 while (start < path_str.size()) {
47 auto pos = path_str.find(sep, start);
48 if (pos == std::string::npos)
49 pos = path_str.size();
50 if (pos > start) {
51 std::string p = path_str.substr(start, pos - start);
52 bool dup = false;
53 for (const auto &existing : m_search_paths) {
54 if (existing == p) {
55 dup = true;
56 break;
57 }
58 }
59 if (!dup)
60 m_search_paths.push_back(std::move(p));
61 }
62 start = pos + 1;
63 }
64}
65
66void PluginLoader::add_config_paths(const std::string &colon_paths) {
67 if (colon_paths.empty())
68 return;
69 std::lock_guard<std::mutex> lock(m_mutex);
70 // Config paths have higher priority than env-var paths: insert at front.
71 std::vector<std::string> new_paths;
72#ifdef _WIN32
73 const char sep = ';';
74#else
75 const char sep = ':';
76#endif
77 std::string::size_type start = 0;
78 while (start < colon_paths.size()) {
79 auto pos = colon_paths.find(sep, start);
80 if (pos == std::string::npos)
81 pos = colon_paths.size();
82 if (pos > start) {
83 std::string p = colon_paths.substr(start, pos - start);
84 bool dup = false;
85 for (const auto &existing : m_search_paths) {
86 if (existing == p) {
87 dup = true;
88 break;
89 }
90 }
91 if (!dup)
92 new_paths.push_back(std::move(p));
93 }
94 start = pos + 1;
95 }
96 m_search_paths.insert(m_search_paths.begin(), new_paths.begin(),
97 new_paths.end());
98}
99
100std::vector<std::string> PluginLoader::lib_names(const char *lib_base) const {
101 std::vector<std::string> names;
102#ifdef _WIN32
103 names.push_back(std::string(lib_base) + ".dll");
104 names.push_back(std::string("lib") + lib_base + ".dll");
105#elif defined(__APPLE__)
106 names.push_back(std::string("lib") + lib_base + ".dylib");
107#else
108 names.push_back(std::string("lib") + lib_base + ".so");
109#endif
110 return names;
111}
112
114 std::lock_guard<std::mutex> lock(m_mutex);
115
116 auto it = m_handles.find(lib_base);
117 if (it != m_handles.end()) {
118 return it->second;
119 }
120
121 auto names = lib_names(lib_base);
122 dynlib::Handle h{};
123 std::string last_err;
124 bool any_file = false;
125
126 // Probe the filesystem first. dlopen of a present plugin runs that
127 // library's static initializers (Fortran / LAMMPS banners), so skip
128 // names that are not on disk.
129 for (const auto &dir : m_search_paths) {
130 for (const auto &name : names) {
131 std::string full = (std::filesystem::path(dir) / name).string();
132 std::error_code ec;
133 if (!std::filesystem::exists(full, ec))
134 continue;
135 any_file = true;
136 h = dynlib::open(full.c_str());
137 if (h) {
138 m_handles[lib_base] = h;
139 return h;
140 }
141 // Capture immediately after LoadLibrary/dlopen (GetLastError/dlerror).
142 last_err = dynlib::error();
143 }
144 }
145
146 // Fall back to system search (LD_LIBRARY_PATH, etc.)
147 for (const auto &name : names) {
148 h = dynlib::open(name.c_str());
149 if (h) {
150 m_handles[lib_base] = h;
151 return h;
152 }
153 last_err = dynlib::error();
154 }
155
156 // Stash diagnostics for throw_not_found (thread-local; open is
157 // mutex-guarded).
158 m_last_load_error = last_err;
159 m_last_load_saw_file = any_file;
160 m_handles[lib_base] = nullptr;
161 return nullptr;
162}
163
164bool PluginLoader::lib_present(const char *lib_base) const {
165 std::lock_guard<std::mutex> lock(m_mutex);
166 auto names = lib_names(lib_base);
167 for (const auto &dir : m_search_paths) {
168 for (const auto &name : names) {
169 std::error_code ec;
170 if (std::filesystem::exists(std::filesystem::path(dir) / name, ec))
171 return true;
172 }
173 }
174 return false;
175}
176
177void PluginLoader::throw_not_found(const char *lib_base,
178 const char *description) const {
179 auto names = lib_names(lib_base);
180 std::ostringstream oss;
181 oss << description << " requested but library not found.\n"
182 << "Looked for: ";
183 for (size_t i = 0; i < names.size(); ++i) {
184 if (i > 0)
185 oss << ", ";
186 oss << names[i];
187 }
188 oss << "\n";
189 if (!m_search_paths.empty()) {
190 oss << "Search paths (config potentials_path + EON_POTENTIALS_PATH):\n";
191 for (const auto &p : m_search_paths) {
192 oss << " " << p << "\n";
193 }
194 }
196 oss << "A matching file was present but LoadLibrary/dlopen failed";
197 if (!m_last_load_error.empty())
198 oss << ": " << m_last_load_error;
199 oss << "\n(often a missing flang_rt / FortranRuntime dependency on "
200 "PATH).\n";
201 } else if (!m_last_load_error.empty()) {
202 oss << "Loader error: " << m_last_load_error << "\n";
203 }
204 oss << "Set [Potential] potentials_path in config.ini, "
205 "or set EON_POTENTIALS_PATH,\n"
206 "or ensure the libraries are in LD_LIBRARY_PATH.";
207 throw std::runtime_error(oss.str());
208}
209
210} // namespace eonc
void add_config_paths(const std::string &colon_paths)
Inject search paths from the eOn config file.
std::vector< std::string > m_search_paths
bool lib_present(const char *lib_base) const
Filesystem probe: a matching library file is on the configured search path.
static PluginLoader & instance()
Thread-safe singleton accessor (Meyer's pattern).
void append_paths(const std::string &path_str, char sep)
std::string m_last_load_error
Last dynlib::error() from a failed open (for throw_not_found).
PluginLoader(const PluginLoader &)=delete
std::vector< std::string > lib_names(const char *lib_base) const
dynlib::Handle open_lib(const char *lib_base)
std::unordered_map< std::string, dynlib::Handle > m_handles
void throw_not_found(const char *lib_base, const char *description) const
Throw a descriptive error for a missing potential library.
void * Handle
Definition DynLib.h:60
std::string error()
Definition DynLib.h:82
Handle open(const char *name) noexcept
Definition DynLib.h:62
void close(Handle h) noexcept
Definition DynLib.h:77
RAII resource manager for the ARTn C library with global synchronization.