HAL
plugin_manager.cpp
Go to the documentation of this file.
2 
11 #include "hal_core/utilities/log.h"
13 
14 #include <vector>
15 
16 #ifdef _WIN32
17 #include <tchar.h>
18 #include <windows.h>
19 #else
20 #include <dirent.h>
21 #include <sys/stat.h>
22 #endif
23 
24 namespace hal
25 {
26  namespace plugin_manager
27  {
28  namespace
29  {
30  // stores library and factory identified by plugin name)
31  std::unordered_map<std::string, std::tuple<std::unique_ptr<BasePluginInterface>, std::unique_ptr<RuntimeLibrary>>> m_loaded_plugins;
32 
33  // stores special features offered by plugin
34  std::unordered_map<std::string, std::vector<PluginFeature>> m_plugin_features;
35 
36  // stores the CLI parser option for plugins [0=base_plugin] [1=UI_plugin]
37  std::unordered_map<std::string, std::string> m_cli_option_to_plugin_name[2];
38 
39  // stores the GUI callback
40  CallbackHook<void(bool, std::string const&, std::string const&)> m_hook;
41 
42  // stores the generic option parser
43  ProgramOptions m_existing_options("existing options");
44 
45  // stores the plugin option description
46  ProgramOptions m_plugin_options("plugin options");
47 
48  // stores the plugin folder
49  std::vector<std::filesystem::path> m_plugin_folders = utils::get_plugin_directories();
50 
51  // stores name of plugin while loading
52  std::string m_current_loading;
53 
54  bool solve_dependencies(std::string plugin_name, std::set<std::string> dep_file_name)
55  {
56  if (plugin_name.empty())
57  {
58  log_error("core", "parameter 'plugin_name' is empty");
59  return false;
60  }
61  if (dep_file_name.empty())
62  {
63  log_debug("core", "no dependency for plugin '{}'", plugin_name);
64  return true;
65  }
66  for (const auto& file_name : dep_file_name)
67  {
68  auto dep_plugin_name = std::filesystem::path(file_name).stem().string();
69 
70  // dependency already loaded
71  if (m_loaded_plugins.find(dep_plugin_name) != m_loaded_plugins.end())
72  {
73  continue;
74  }
75 
76  // search in plugin directories
77  std::filesystem::path file_path = get_plugin_path(dep_plugin_name);
78  if (file_path.empty() || !load(dep_plugin_name, file_path))
79  {
80  log_error("core", "cannot solve dependency '{}' for plugin '{}'", dep_plugin_name, plugin_name);
81  return false;
82  }
83 
84  log_debug("core", "solved dependency '{}' for plugin '{}'", dep_plugin_name, plugin_name);
85  }
86  log_debug("core", "solved {} dependencies for plugin '{}'", dep_file_name.size(), plugin_name);
87  return true;
88  }
89 
90  } // namespace
91 
92  std::filesystem::path get_plugin_path(std::string plugin_name)
93  {
94  std::filesystem::path retval;
95  if (plugin_name.empty()) return retval;
96  std::string file_name = plugin_name + "." + LIBRARY_FILE_EXTENSION;
97  retval = utils::get_file(file_name, m_plugin_folders);
98  if (!retval.empty() || !strlen(ALTERNATE_LIBRARY_FILE_EXTENSION)) return retval;
99  file_name = plugin_name + "." + ALTERNATE_LIBRARY_FILE_EXTENSION;
100  return utils::get_file(file_name, m_plugin_folders);
101  }
102 
103  bool has_valid_file_extension(std::filesystem::path file_name)
104  {
105 #if defined(__APPLE__) && defined(__MACH__)
106  if (utils::ends_with(file_name.string(), std::string(".so")))
107  return true;
108  if (file_name.string().find(".so") != std::string::npos)
109  return true;
110  if (utils::ends_with(file_name.string(), std::string(".icloud")))
111  return false;
112 #endif
113  // file has regular shared library extension
114  if (utils::ends_with(file_name.string(), std::string(".") + std::string(LIBRARY_FILE_EXTENSION))) return true;
115 
116  // there is no alternate extension
117  if (!strlen(ALTERNATE_LIBRARY_FILE_EXTENSION)) return false;
118 
119  // test whether file has alternate extension
120  return (utils::ends_with(file_name.string(), std::string(".") + std::string(ALTERNATE_LIBRARY_FILE_EXTENSION)));
121  }
122 
123  std::set<std::string> get_plugin_names()
124  {
125  std::set<std::string> names;
126  for (const auto& it : m_loaded_plugins)
127  {
128  names.insert(it.first);
129  }
130  return names;
131  }
132 
133  std::vector<PluginFeature> get_plugin_features(std::string name)
134  {
135  auto it = m_plugin_features.find(name);
136  if (it == m_plugin_features.end()) return std::vector<PluginFeature>();
137  return it->second;
138  }
139 
140  std::unordered_map<std::string, std::string> get_cli_plugin_flags()
141  {
142  return m_cli_option_to_plugin_name[0];
143  }
144 
145  std::unordered_map<std::string, std::string> get_ui_plugin_flags()
146  {
147  return m_cli_option_to_plugin_name[1];
148  }
149 
151  {
152  return m_plugin_options;
153  }
154 
155  bool load_all_plugins(const std::vector<std::filesystem::path>& directory_names)
156  {
157  auto directories = (!directory_names.empty()) ? directory_names : m_plugin_folders;
158  for (const auto& directory : directories)
159  {
160  if (!std::filesystem::exists(directory))
161  {
162  continue;
163  }
164  u32 num_of_loaded_plugins = 0;
165  for (const auto& file : utils::DirectoryRange(directory))
166  {
167  if (!has_valid_file_extension(file) || std::filesystem::is_directory(file))
168  continue;
169 
170  auto plugin_name = file.path().stem().string();
171  if (load(plugin_name, file.path()))
172  {
173  num_of_loaded_plugins++;
174  }
175  }
176  log_debug("core", "loaded {} plugins from '{}'", num_of_loaded_plugins, directory.string());
177  }
178  return true;
179  }
180 
181  bool load(const std::string& plugin_name, const std::filesystem::path& file_path_or_empty)
182  {
183  if (plugin_name.empty())
184  {
185  log_error("core", "parameter 'plugin_name' is empty");
186  return false;
187  }
188 
189  std::filesystem::path file_path = file_path_or_empty;
190  if (file_path.empty())
191  {
192  // file name not provided, search plugin folders
193  file_path = get_plugin_path(plugin_name);
194 
195  if (file_path.empty())
196  {
197  log_error("core", "path for plugin '{}' not found", plugin_name);
198  return false;
199  }
200  }
201  else
202  log_info("core", "loading plugin '{}'...", file_path.string());
203 
204  if (m_loaded_plugins.find(plugin_name) != m_loaded_plugins.end())
205  {
206  log_debug("core", "plugin '{}' is already loaded", plugin_name);
207  return true;
208  }
209 
210  /* load library */
211  auto lib = std::make_unique<RuntimeLibrary>();
212  if (!lib->load_library(file_path.string()))
213  {
214  return false;
215  }
216 
217  /* get factory of library */
218  auto factory = (instantiate_plugin_function)lib->get_function("create_plugin_instance");
219  if (factory == nullptr)
220  {
221  log_error("core", "file does not seem to be a HAL plugin since it does not contain a factory function 'create_plugin_instance'.");
222  return false;
223  }
224  auto instance = factory();
225  if (instance == nullptr)
226  {
227  log_error("core", "factory constructor for plugin '{}' returned a nullptr", plugin_name);
228  return false;
229  }
230 
231  /* solve dependencies */
232  std::set<std::string> dep_file_name = instance->get_dependencies();
233  if (!solve_dependencies(plugin_name, dep_file_name))
234  {
235  return false;
236  }
237 
238  /* add cli options */
239  CliExtensionInterface* ceif = instance->get_first_extension<CliExtensionInterface>();
240  if (ceif)
241  {
242  BasePluginInterface* plugin = instance.get();
243  if (plugin == nullptr)
244  {
245  return false;
246  }
247 
248  auto cli_options = ceif->get_cli_options();
249  UIPluginInterface* ui_plugin = dynamic_cast<UIPluginInterface*>(plugin);
250  for (const auto& cli_option : cli_options.get_options())
251  {
252  for (const auto& flag : std::get<0>(cli_option))
253  {
254  if (m_existing_options.is_registered(flag))
255  {
256  log_error("core", "command line option '{}' is already used by generic program options -- use another one.", flag);
257  return false;
258  }
259 
260  for (int iplugType = 0; iplugType<2; iplugType++)
261  if (m_cli_option_to_plugin_name[iplugType].find(flag) != m_cli_option_to_plugin_name[iplugType].end())
262  {
263  log_error("core", "command line option '{}' is already used by plugin '{}' -- use another option in plugin '{}'.", flag, m_cli_option_to_plugin_name[iplugType][flag], plugin_name);
264  return false;
265  }
266  m_cli_option_to_plugin_name[ui_plugin?1:0][flag] = plugin_name;
267  log_debug("core", "registered command line option '{}' for plugin '{}'.", flag, plugin->get_name());
268  }
269  }
270  m_plugin_options.add(cli_options);
271  }
272 
273  instance->initialize_logging();
274 
275  m_current_loading = plugin_name;
276  instance->on_load();
277 
278  for (AbstractExtensionInterface* aeif : instance->get_extensions())
279  {
280  FacExtensionInterface* feif = dynamic_cast<FacExtensionInterface*>(aeif);
281  if (!feif) continue;
283  m_plugin_features[plugin_name].push_back({feif->get_feature(),
285  feif->get_description()});
286  switch (feif->get_feature())
287  {
289  {
292  break;
293  }
295  {
298  break;
299  }
301  {
304  break;
305  }
307  {
310  break;
311  }
312  default:
313  break;
314  }
315  }
316  m_current_loading.clear();
317 
318  m_loaded_plugins[plugin_name] = std::make_tuple(std::move(instance), std::move(lib));
319 
320  /* notify callback that a plugin was loaded*/
321  m_hook(true, plugin_name, file_path.string());
322 
323  log_debug("core", "loaded plugin '{}'.", plugin_name);
324  return true;
325  }
326 
328  {
329  if (m_loaded_plugins.size() == 0)
330  {
331  log_debug("core", "no plugins to unload");
332  return true;
333  }
334  auto loaded_plugin_names = get_plugin_names();
335  for (const auto& name : loaded_plugin_names)
336  {
337  if (!unload(name))
338  {
339  log_error("core", "could not unload plugin '{}'", name);
340  }
341  }
342  log_info("core", "unloaded all {} plugins", loaded_plugin_names.size());
343  return true;
344  }
345 
346  bool unload(const std::string& plugin_name)
347  {
348  auto loaded_it = m_loaded_plugins.find(plugin_name);
349  if (loaded_it == m_loaded_plugins.end())
350  {
351  log_debug("core", "cannot find plugin '{}' to unload it", plugin_name);
352  return true;
353  }
354 
355  log_info("core", "unloading plugin '{}'...", plugin_name);
356 
357 
358  auto rt_library = std::move(std::get<1>(loaded_it->second));
359  auto plugin_inst = std::move(std::get<0>(loaded_it->second));
360 
361  {
362  auto iplugType = dynamic_cast<UIPluginInterface*>(plugin_inst.get()) ? 1 : 0;
363  auto it = m_cli_option_to_plugin_name[iplugType].begin();
364  while (it != m_cli_option_to_plugin_name[iplugType].end())
365  {
366  auto flag = it->first;
367  auto name = it->second;
368  if (name == plugin_name)
369  {
370  m_plugin_options.remove(flag);
371  it = m_cli_option_to_plugin_name[iplugType].erase(it);
372  }
373  else
374  {
375  ++it;
376  }
377  }
378  }
379 
380  m_loaded_plugins.erase(loaded_it);
381 
382  for (AbstractExtensionInterface* aeif : plugin_inst->get_extensions())
383  {
384  FacExtensionInterface* feif = dynamic_cast<FacExtensionInterface*>(aeif);
385  if (!feif) continue;
386  switch (feif->get_feature())
387  {
390  break;
393  break;
396  break;
399  break;
400  default:
401  break;
402  }
403  }
404 
405  auto file_name = rt_library->get_file_name();
406  plugin_inst->on_unload();
407 
408  // actual unloading
409  // order of unloading is important, so we do it manually here:
410  // first destroy the plugin instance, then destroy the runtime library
411  plugin_inst.reset();
412  rt_library.reset();
413 
414  /* notify callback that a plugin was unloaded */
415  m_hook(false, plugin_name, file_name);
416 
417  log_debug("core", "unloaded plugin '{}'", plugin_name);
418  return true;
419  }
420 
421  BasePluginInterface* get_plugin_instance(const std::string& plugin_name, bool initialize, bool silent)
422  {
423  auto it = m_loaded_plugins.find(plugin_name);
424  if (it == m_loaded_plugins.end())
425  {
426  if (!silent)
427  log_error("core", "plugin '{}' is not loaded", plugin_name);
428  return nullptr;
429  }
430 
431  auto instance = std::get<0>(it->second).get();
432  if (instance != nullptr && initialize)
433  {
434  instance->initialize();
435  }
436  return instance;
437  }
438 
439  u64 add_model_changed_callback(std::function<void(bool, std::string const&, std::string const&)> callback)
440  {
441  if (callback == nullptr)
442  {
443  log_error("core", "parameter 'callback' is nullptr");
444  return 0;
445  }
446  return m_hook.add_callback(callback);
447  }
448 
450  {
451  m_hook.remove_callback(id);
452  }
453 
454  void add_existing_options_description(const ProgramOptions& existing_options)
455  {
456  m_existing_options.add(existing_options);
457  }
458  } // namespace plugin_manager
459 } // namespace hal
within a display generated by the Derivative if and wherever such third party notices normally appear The contents of the NOTICE file are for informational purposes only and do not modify the License You may add Your own attribution notices within Derivative Works that You alongside or as an addendum to the NOTICE text from the provided that such additional attribution notices cannot be construed as modifying the License You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for or distribution of Your or for any such Derivative Works as a provided Your and distribution of the Work otherwise complies with the conditions stated in this License Submission of Contributions Unless You explicitly state any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this without any additional terms or conditions Notwithstanding the nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions Trademarks This License does not grant permission to use the trade names
then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file
#define LIBRARY_FILE_EXTENSION
Definition: arch_linux.h:34
#define ALTERNATE_LIBRARY_FILE_EXTENSION
Definition: arch_linux.h:36
virtual std::string get_name() const =0
virtual ProgramOptions get_cli_options() const =0
AbstractFactoryProvider * factory_provider
std::vector< std::string > get_supported_file_extensions() const
File-Access Factory class.
std::function< std::unique_ptr< T >)> m_factory
uint64_t u64
Definition: defines.h:42
#define log_error(channel,...)
Definition: log.h:78
#define log_debug(channel,...)
Definition: log.h:74
#define log_info(channel,...)
Definition: log.h:70
void unregister_parser(const std::string &name)
void register_parser(const std::string &name, const ParserFactory &parser_factory, const std::vector< std::string > &supported_file_extensions)
void unregister_writer(const std::string &name)
void register_writer(const std::string &name, const WriterFactory &writer_factory, const std::vector< std::string > &supported_file_extensions)
void unregister_parser(const std::string &name)
void register_parser(const std::string &name, const ParserFactory &parser_factory, const std::vector< std::string > &supported_file_extensions)
void unregister_writer(const std::string &name)
void register_writer(const std::string &name, const WriterFactory &writer_factory, const std::vector< std::string > &supported_file_extensions)
void add_existing_options_description(const ProgramOptions &existing_options)
u64 add_model_changed_callback(std::function< void(bool, std::string const &, std::string const &)> callback)
bool has_valid_file_extension(std::filesystem::path file_name)
bool unload(const std::string &plugin_name)
std::set< std::string > get_plugin_names()
BasePluginInterface * get_plugin_instance(const std::string &plugin_name, bool initialize, bool silent)
std::unordered_map< std::string, std::string > get_cli_plugin_flags()
std::unordered_map< std::string, std::string > get_ui_plugin_flags()
std::filesystem::path get_plugin_path(std::string plugin_name)
ProgramOptions get_cli_plugin_options()
void remove_model_changed_callback(u64 id)
bool load_all_plugins(const std::vector< std::filesystem::path > &directory_names)
std::vector< PluginFeature > get_plugin_features(std::string name)
bool load(const std::string &plugin_name, const std::filesystem::path &file_path_or_empty)
CORE_API bool ends_with(const T &s, const T &end)
Definition: utils.h:147
std::vector< std::filesystem::path > get_plugin_directories()
Definition: utils.cpp:194
std::filesystem::path get_file(std::string file_name, std::vector< std::filesystem::path > path_hints)
Definition: utils.cpp:219
void initialize()
Definition: event_log.cpp:302
std::unique_ptr< BasePluginInterface >(*)() instantiate_plugin_function
quint32 u32
std::string name