From 16dc7bcf8cba9729ffc787e4d676defee8a7c39a Mon Sep 17 00:00:00 2001
From: Stephane Del Pino <stephane.delpino44@gmail.com>
Date: Sun, 29 Dec 2024 22:00:11 +0100
Subject: [PATCH] Add plugin loading mechanism

Plugins are loaded through environment variables. Two environment
variables are read: PUGS_PLUGIN and PUGS_PLUGIN_DIR

- PUGS_PLUGIN is a string literal that contains the filename of the
dynamic library that contains plugins. One can provide multiple
filename using semicolumn separators.
  ex. PUGS_PLUGIN="/path/to/my/libplugin1.so;/anotherpath/to/another/libplugin2.so"

- PUGS_PLUGIN_DIR is a string literal that contains directory path
where plugins (dynamic libraries) are.  One can provide multiple path
using semicolumn separators. All the dynamic libraries present at the
locations are loaded!
  ex. PUGS_PLUGIN="/path/to/a/plugin/list/;/anotherpath/to/another/plugin/list/"
---
 CMakeLists.txt                            | 18 +++--
 src/language/modules/ModuleRepository.cpp | 21 +++++
 src/language/modules/ModuleRepository.hpp | 11 ++-
 src/main.cpp                              |  7 ++
 src/utils/CMakeLists.txt                  |  1 +
 src/utils/PluginsLoader.cpp               | 96 +++++++++++++++++++++++
 src/utils/PluginsLoader.hpp               | 23 ++++++
 7 files changed, 164 insertions(+), 13 deletions(-)
 create mode 100644 src/utils/PluginsLoader.cpp
 create mode 100644 src/utils/PluginsLoader.hpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 66f561885..08782444a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,12 +22,19 @@ if("${PUGS_SHORT_VERSION}" STREQUAL "")
   message(FATAL_ERROR "Unable to compute short version from PUGS_VERSION=${PUGS_VERSION}")
 endif()
 
+# set project version as PUGS_SHORT_VERSION
+project (Pugs VERSION ${PUGS_SHORT_VERSION})
+
+# -----------------------------------------------------
+# dynamic libraries
+
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+link_libraries("-rdynamic")
 set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
 
-set(CMAKE_CONFIGURATION_TYPES "Release;Debug;Coverage" CACHE STRING INTERNAL FORCE )
+#------------------------------------------------------
 
-# set project version as PUGS_SHORT_VERSION
-project (Pugs VERSION ${PUGS_SHORT_VERSION})
+set(CMAKE_CONFIGURATION_TYPES "Release;Debug;Coverage" CACHE STRING INTERNAL FORCE )
 
 #------------------------------------------------------
 
@@ -380,6 +387,7 @@ include_directories(SYSTEM "${PUGS_SOURCE_DIR}/packages/rang/include")
 include_directories(SYSTEM "${PUGS_SOURCE_DIR}/packages/CLI11/include")
 
 # PEGTL
+add_subdirectory(${PUGS_SOURCE_DIR}/packages/PEGTL/)
 include_directories(SYSTEM "${PUGS_SOURCE_DIR}/packages/PEGTL/include/tao")
 
 # Pugs src
@@ -597,10 +605,6 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Coverage")
   endif()
 endif()
 
-# -----------------------------------------------------
-
-link_libraries("-rdynamic")
-
 # ------------------- Source files --------------------
 # Pugs binary
 add_executable(
diff --git a/src/language/modules/ModuleRepository.cpp b/src/language/modules/ModuleRepository.cpp
index 42d62fa0a..c9c487b67 100644
--- a/src/language/modules/ModuleRepository.cpp
+++ b/src/language/modules/ModuleRepository.cpp
@@ -13,6 +13,27 @@
 
 #include <set>
 
+ModuleRepository* ModuleRepository::m_instance = nullptr;
+
+ModuleRepository&
+ModuleRepository::getInstance()
+{
+  if (m_instance == nullptr) {
+    m_instance = new ModuleRepository;
+  }
+
+  return *m_instance;
+}
+
+void
+ModuleRepository::destroy()
+{
+  if (m_instance != nullptr) {
+    delete m_instance;
+    m_instance = nullptr;
+  }
+}
+
 void
 ModuleRepository::_subscribe(std::unique_ptr<IModule> m)
 {
diff --git a/src/language/modules/ModuleRepository.hpp b/src/language/modules/ModuleRepository.hpp
index b2e217b93..35bf6f7aa 100644
--- a/src/language/modules/ModuleRepository.hpp
+++ b/src/language/modules/ModuleRepository.hpp
@@ -31,13 +31,12 @@ class ModuleRepository
                             const IModule::NameValueMap& name_value_map,
                             SymbolTable& symbol_table);
 
+  static ModuleRepository* m_instance;
+
  public:
-  static ModuleRepository&
-  getInstance()
-  {
-    static ModuleRepository m_instance;
-    return m_instance;
-  }
+  static ModuleRepository& getInstance();
+
+  static void destroy();
 
   template <typename ModuleT>
   struct Subscribe
diff --git a/src/main.cpp b/src/main.cpp
index a373bd2cd..cd576a9f6 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,6 +1,7 @@
 #include <analysis/QuadratureManager.hpp>
 #include <dev/ParallelChecker.hpp>
 #include <language/PugsParser.hpp>
+#include <language/modules/ModuleRepository.hpp>
 #include <mesh/DualConnectivityManager.hpp>
 #include <mesh/DualMeshManager.hpp>
 #include <mesh/MeshDataManager.hpp>
@@ -11,6 +12,8 @@
 #include <utils/RandomEngine.hpp>
 #include <utils/checkpointing/ResumingManager.hpp>
 
+#include <utils/PluginsLoader.hpp>
+
 int
 main(int argc, char* argv[])
 {
@@ -20,6 +23,8 @@ main(int argc, char* argv[])
 
   std::string filename = initialize(argc, argv);
 
+  PluginsLoader plugins_loader;
+
   SynchronizerManager::create();
   RandomEngine::create();
   QuadratureManager::create();
@@ -32,6 +37,8 @@ main(int argc, char* argv[])
   parser(filename);
   ExecutionStatManager::printInfo();
 
+  ModuleRepository::destroy();
+
   GlobalVariableManager::destroy();
 
   DualMeshManager::destroy();
diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt
index 13b0db808..39e8cb359 100644
--- a/src/utils/CMakeLists.txt
+++ b/src/utils/CMakeLists.txt
@@ -16,6 +16,7 @@ add_library(
   Messenger.cpp
   Partitioner.cpp
   PETScWrapper.cpp
+  PluginsLoader.cpp
   PugsUtils.cpp
   RandomEngine.cpp
   ReproducibleSumManager.cpp
diff --git a/src/utils/PluginsLoader.cpp b/src/utils/PluginsLoader.cpp
new file mode 100644
index 000000000..8616c7a68
--- /dev/null
+++ b/src/utils/PluginsLoader.cpp
@@ -0,0 +1,96 @@
+#include <utils/PluginsLoader.hpp>
+
+#include <dlfcn.h>
+
+#include <rang.hpp>
+
+#include <filesystem>
+#include <iostream>
+#include <sstream>
+#include <unistd.h>
+#include <vector>
+
+std::vector<std::string>
+split(const std::string& full_string)
+{
+  std::vector<std::string> split_string;
+
+  std::stringstream is(full_string);
+
+  std::string segment;
+  while (std::getline(is, segment, ';')) {
+    if (segment.size() > 0) {
+      split_string.push_back(segment);
+    }
+  }
+  return split_string;
+}
+
+void
+PluginsLoader::_open(const std::string& plugin)
+{
+  auto handle = dlopen(plugin.c_str(), RTLD_NOW);
+  if (handle != nullptr) {
+    m_dl_handler_stack.push(handle);
+    std::cout << " * \"" << rang::fgB::green << plugin << rang::fg::reset << "\"\n";
+  } else {
+    std::cout << "   " << rang::fgB::red << "cannot load " << rang::fg::reset << '\"' << rang::fgB::yellow << plugin
+              << rang::fg::reset << "\"\n";
+  }
+}
+
+PluginsLoader::PluginsLoader()
+{
+  std::vector<std::string> plugin_vector;
+
+  {
+    char* env = getenv("PUGS_PLUGIN");
+    if (env != nullptr) {
+      std::string plugins = env;
+      plugin_vector       = split(plugins);
+    }
+  }
+
+  {
+    char* env = getenv("PUGS_PLUGIN_DIR");
+    if (env != nullptr) {
+      std::string paths                    = env;
+      std::vector<std::string> path_vector = split(paths);
+      for (auto&& path : path_vector) {
+        if (access(path.c_str(), R_OK) == -1) {
+          std::cout << ' ' << rang::fgB::red << 'X' << rang::fg::reset << " cannot access plugin dir \""
+                    << rang::fgB::yellow << path << rang::fg::reset << "\"\n";
+        } else {
+          for (auto&& entry :
+               std::filesystem::directory_iterator(path,
+                                                   (std::filesystem::directory_options::follow_directory_symlink |
+                                                    std::filesystem::directory_options::skip_permission_denied))) {
+            if (entry.path().extension() == ".so") {
+              plugin_vector.push_back(entry.path().string());
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // keep unique entries
+  std::sort(plugin_vector.begin(), plugin_vector.end());
+  plugin_vector.resize(std::distance(plugin_vector.begin(), std::unique(plugin_vector.begin(), plugin_vector.end())));
+
+  if (plugin_vector.size() > 0) {
+    std::cout << rang::style::bold << "Loading plugins" << rang::style::reset << '\n';
+    for (auto&& plugin : plugin_vector) {
+      this->_open(plugin);
+    }
+    std::cout << "-------------------------------------------------------\n";
+  }
+}
+
+PluginsLoader::~PluginsLoader()
+{
+  while (not m_dl_handler_stack.empty()) {
+    dlclose(m_dl_handler_stack.top());
+    m_dl_handler_stack.pop();
+  }
+}
diff --git a/src/utils/PluginsLoader.hpp b/src/utils/PluginsLoader.hpp
new file mode 100644
index 000000000..76be25c69
--- /dev/null
+++ b/src/utils/PluginsLoader.hpp
@@ -0,0 +1,23 @@
+#ifndef PLUGINS_LOADER_HPP
+#define PLUGINS_LOADER_HPP
+
+#include <stack>
+#include <string>
+
+class PluginsLoader
+{
+ private:
+  std::stack<void*> m_dl_handler_stack;
+
+  void _open(const std::string& filename);
+
+ public:
+  PluginsLoader();
+
+  PluginsLoader(const PluginsLoader&) = delete;
+  PluginsLoader(PluginsLoader&&)      = delete;
+
+  ~PluginsLoader();
+};
+
+#endif   // PLUGINS_LOADER_HPP
-- 
GitLab