Source · fairmq/
PluginManager.cxxsource
Described in Plugins and control, custom-controller: control from code, dds: run with DDS, Control a device from code, Write a plugin, Command-line options.
1/********************************************************************************2 * Copyright (C) 2017-2022 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *3 * *4 * This software is distributed under the terms of the *5 * GNU Lesser General Public Licence (LGPL) version 3, *6 * copied verbatim in the file "LICENSE" *7 ********************************************************************************/8 9#include <algorithm>10#include <boost/program_options.hpp>11#include <fairlogger/Logger.h>12#include <fairmq/PluginManager.h>13#include <fairmq/plugins/Builtin.h>14#include <fairmq/tools/Strings.h>15#include <iterator>16#include <memory>17#include <sstream>18#include <string>19#include <utility>20 21using namespace std;22using fair::mq::Plugin;23using fair::mq::tools::ToString;24using fair::mq::tools::ToStrVector;25namespace po = boost::program_options;26namespace dll = boost::dll;27using boost::optional;28 29const std::string fair::mq::PluginManager::fgkLibPrefix = "fairmq-plugin-";30const std::string fair::mq::PluginManager::fgkLibPrefixAlt = "FairMQPlugin_";31 32std::vector<boost::dll::shared_library> fair::mq::PluginManager::fgDLLKeepAlive =33 std::vector<boost::dll::shared_library>();34 35fair::mq::PluginManager::PluginManager() {}36 37fair::mq::PluginManager::PluginManager(const vector<string>& args)38{39 // Parse command line options40 auto options = ProgramOptions();41 auto vm = po::variables_map{};42 try {43 auto parsed = po::command_line_parser(args).options(options).allow_unregistered().run();44 po::store(parsed, vm);45 po::notify(vm);46 } catch (const po::error& e) {47 throw ProgramOptionsParseError{ToString(48 "Error occured while parsing the 'Plugin Manager' program options: ", e.what())};49 }50 51 // Process plugin search paths52 auto append = vector<fs::path>{};53 auto prepend = vector<fs::path>{};54 auto searchPaths = vector<fs::path>{};55 if (vm.count("plugin-search-path")) {56 for (const auto& path : vm["plugin-search-path"].as<vector<string>>()) {57 if (path.substr(0, 1) == "<") {58 prepend.emplace_back(path.substr(1));59 } else if (path.substr(0, 1) == ">") {60 append.emplace_back(path.substr(1));61 } else {62 searchPaths.emplace_back(path);63 }64 }65 }66 67 // Set supplied options68 SetSearchPaths(searchPaths);69 for (const auto& path : prepend) {70 PrependSearchPath(path);71 }72 for (const auto& path : append) {73 AppendSearchPath(path);74 }75 if (vm.count("plugin")) {76 LoadPlugins(vm["plugin"].as<vector<string>>());77 }78}79 80auto fair::mq::PluginManager::ValidateSearchPath(const fs::path& path) -> void81{82 if (path.empty()) {83 throw BadSearchPath{"Specified path is empty."};84 }85 // we ignore non-existing search paths86 if (fs::exists(path) && !fs::is_directory(path)) {87 throw BadSearchPath{ToString(path, " is not a directory.")};88 }89}90 91auto fair::mq::PluginManager::SetSearchPaths(const vector<fs::path>& searchPaths) -> void92{93 for_each(begin(searchPaths), end(searchPaths), ValidateSearchPath);94 fSearchPaths = searchPaths;95}96 97auto fair::mq::PluginManager::AppendSearchPath(const fs::path& path) -> void98{99 ValidateSearchPath(path);100 fSearchPaths.push_back(path);101}102 103auto fair::mq::PluginManager::PrependSearchPath(const fs::path& path) -> void104{105 ValidateSearchPath(path);106 fSearchPaths.insert(begin(fSearchPaths), path);107}108 109auto fair::mq::PluginManager::ProgramOptions() -> po::options_description110{111 auto plugin_options = po::options_description{"Plugin Manager"};112 plugin_options.add_options()("plugin-search-path,S",113 po::value<vector<string>>()->multitoken(),114 "List of plugin search paths.\n\n"115 "* Override default search path, e.g.\n"116 " -S /home/user/lib /lib\n"117 "* Append(>) or prepend(<) to default search path, e.g.\n"118 " -S >/lib </home/user/lib\n"119 "* If you mix the overriding and appending/prepending syntaxes, "120 "the overriding paths act as default search path, e.g.\n"121 " -S /usr/lib >/lib </home/user/lib /usr/local/lib results in "122 "/home/user/lib,/usr/local/lib,/usr/lib/,/lib\n"123 "If nothing is found, the default dynamic library lookup is "124 "performed, see man ld.so(8) for details.")(125 "plugin,P",126 po::value<vector<string>>(),127 "List of plugin names to load in order,"128 "e.g. if the file is called 'libfairmq-plugin-example.so', just list 'example' or "129 "'d:example' here."130 "To load a prelinked plugin, list 'p:example' here.");131 132 return plugin_options;133}134 135auto fair::mq::PluginManager::LoadPlugin(const string& pluginName) -> void136{137 if (pluginName.substr(0, 2) == "p:") {138 // Mechanism A: prelinked dynamic139 LoadPluginPrelinkedDynamic(pluginName.substr(2));140 } else if (pluginName.substr(0, 2) == "d:") {141 // Mechanism B: dynamic142 LoadPluginDynamic(pluginName.substr(2));143 } else if (pluginName.substr(0, 2) == "s:") {144 // Mechanism C: static (builtin)145 LoadPluginStatic(pluginName.substr(2));146 } else {147 // Mechanism B: dynamic (default)148 LoadPluginDynamic(pluginName);149 }150}151 152auto fair::mq::PluginManager::LoadPluginPrelinkedDynamic(const string& pluginName) -> void153{154 // Load symbol155 if (fPluginFactories.find(pluginName) == fPluginFactories.end()) {156 try {157 LoadSymbols(pluginName, dll::program_location());158 fPluginOrder.push_back(pluginName);159 } catch (boost::system::system_error& e) {160 throw PluginLoadError(161 ToString("An error occurred while loading prelinked dynamic plugin ",162 pluginName,163 ": ",164 e.what()));165 }166 }167}168 169auto fair::mq::PluginManager::SearchPluginFile(const string& pluginName) const170 -> fs::path171{172 for (const auto& searchPath : SearchPaths()) {173 for (const auto& libPrefix : {fgkLibPrefix, fgkLibPrefixAlt}) {174 auto const file =175 searchPath176 / ToString("lib",177 libPrefix,178 pluginName,179 boost::dll::detail::shared_library_impl::suffix().native());180 auto const found = fs::exists(file);181 if (found) {182 return file;183 }184 }185 }186 throw PluginNotFound(ToString("Could not find plugin file for plugin ", pluginName));187}188 189auto fair::mq::PluginManager::LoadPluginDynamic(const string& pluginName) -> void190{191 if (fPluginFactories.find(pluginName) != fPluginFactories.end()) {192 return; // already loaded, nothing to do193 }194 195 // Search in configured search path196 try {197 LoadSymbols(pluginName, SearchPluginFile(pluginName), dll::load_mode::rtld_global);198 fPluginOrder.push_back(pluginName);199 return;200 } catch (PluginNotFound& e) {201 // ignore202 } catch (boost::system::system_error& e) {203 // ignore204 }205 206 // Search with system logic (including RPATH, LD_LIBRARY_PATH)207 for (const auto& libPrefix : {fgkLibPrefix, fgkLibPrefixAlt}) {208 try {209 // LoadSymbols(pluginName,210 // ToString(LibPrefix(), pluginName),211 // dll::load_mode::search_system_folders | dll::load_mode::append_decorations);212 // Not sure, why the above does not work. Workaround for now:213 LoadSymbols(pluginName,214 ToString("lib",215 libPrefix,216 pluginName,217 boost::dll::detail::shared_library_impl::suffix().native()),218 dll::load_mode::search_system_folders | dll::load_mode::rtld_global);219 fPluginOrder.push_back(pluginName);220 return;221 } catch (boost::system::system_error& e) {222 // ignore223 }224 }225 226 throw PluginLoadError(ToString("Could not load dynamic plugin ",227 pluginName,228 " from configured search paths (-S) nor with ",229 "system lookup (system libdirs, RUNPATH, $LD_LIBRARY_PATH)."));230}231 232auto fair::mq::PluginManager::LoadPluginStatic(const string& pluginName) -> void233{234 // Load symbol235 if (fPluginFactories.find(pluginName) == fPluginFactories.end()) {236 try {237 if ("control" == pluginName) {238 try {239 fPluginProgOptions.insert(240 {pluginName, plugins::ControlPluginProgramOptions().value()});241 } catch (const boost::bad_optional_access&) {242 /* just ignore, if no prog options are declared */243 }244 } else if ("config" == pluginName) {245 try {246 fPluginProgOptions.insert(247 {pluginName, plugins::ConfigPluginProgramOptions().value()});248 } catch (const boost::bad_optional_access&) {249 /* just ignore, if no prog options are declared */250 }251 } else {252 LoadSymbols(pluginName, dll::program_location());253 }254 fPluginOrder.push_back(pluginName);255 } catch (boost::system::system_error& e) {256 throw PluginLoadError(ToString(257 "An error occurred while loading static plugin ", pluginName, ": ", e.what()));258 }259 }260}261 262auto fair::mq::PluginManager::InstantiatePlugin(const string& pluginName) -> void263{264 if (fPlugins.find(pluginName) == fPlugins.end()) {265 if ("control" == pluginName) {266 fPlugins[pluginName] = plugins::Make_control_Plugin(fPluginServices.get());267 } else if ("config" == pluginName) {268 fPlugins[pluginName] = plugins::Make_config_Plugin(fPluginServices.get());269 } else {270 fPlugins[pluginName] = fPluginFactories[pluginName](*fPluginServices);271 }272 }273}274 275auto fair::mq::PluginManager::InstantiatePlugins() -> void276{277 for (const auto& pluginName : fPluginOrder) {278 try {279 InstantiatePlugin(pluginName);280 } catch (std::exception& e) {281 throw PluginInstantiationError(ToString(282 "An error occurred while instantiating plugin ", pluginName, ": ", e.what()));283 }284 }285}