Skip to content

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.

fairmq/PluginManager.cxx285 lines · C++GitHub ↗
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}