feat(plugins): Allow kebab-case plugin names, e.g. libfairmq-plugin-pmix

Camel+snake-case plugin names are still allowed! e.g. `libFairMQPlugin_pmix`
This commit is contained in:
Dennis Klein 2022-03-24 16:01:47 +01:00
parent 5c53c5aa22
commit 3590a079b8
4 changed files with 140 additions and 83 deletions

View File

@ -35,7 +35,7 @@ Plugin Manager:
see man ld.so(8) for details. see man ld.so(8) for details.
-P [ --plugin ] arg List of plugin names to load in -P [ --plugin ] arg List of plugin names to load in
order,e.g. if the file is called order,e.g. if the file is called
'libFairMQPlugin_example.so', just list 'libfairmq-plugin-example.so', just list
'example' or 'd:example' here.To load a 'example' or 'd:example' here.To load a
prelinked plugin, list 'p:example' prelinked plugin, list 'p:example'
here. here.

View File

@ -1,16 +1,17 @@
/******************************************************************************** /********************************************************************************
* Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * * Copyright (C) 2017-2022 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* * * *
* This software is distributed under the terms of the * * This software is distributed under the terms of the *
* GNU Lesser General Public Licence (LGPL) version 3, * * GNU Lesser General Public Licence (LGPL) version 3, *
* copied verbatim in the file "LICENSE" * * copied verbatim in the file "LICENSE" *
********************************************************************************/ ********************************************************************************/
#include <fairmq/plugins/Builtin.h>
#include <fairmq/PluginManager.h>
#include <fairmq/tools/Strings.h>
#include <boost/program_options.hpp>
#include <algorithm> #include <algorithm>
#include <boost/program_options.hpp>
#include <fairlogger/Logger.h>
#include <fairmq/PluginManager.h>
#include <fairmq/plugins/Builtin.h>
#include <fairmq/tools/Strings.h>
#include <iterator> #include <iterator>
#include <memory> #include <memory>
#include <sstream> #include <sstream>
@ -18,22 +19,23 @@
#include <utility> #include <utility>
using namespace std; using namespace std;
using fair::mq::Plugin;
using fair::mq::tools::ToString; using fair::mq::tools::ToString;
using fair::mq::tools::ToStrVector; using fair::mq::tools::ToStrVector;
using fair::mq::Plugin;
namespace fs = boost::filesystem; namespace fs = boost::filesystem;
namespace po = boost::program_options; namespace po = boost::program_options;
namespace dll = boost::dll; namespace dll = boost::dll;
using boost::optional; using boost::optional;
const std::string fair::mq::PluginManager::fgkLibPrefix = "FairMQPlugin_"; const std::string fair::mq::PluginManager::fgkLibPrefix = "fairmq-plugin-";
const std::string fair::mq::PluginManager::fgkLibPrefixAlt = "FairMQPlugin_";
std::vector<boost::dll::shared_library> fair::mq::PluginManager::fgDLLKeepAlive = std::vector<boost::dll::shared_library>(); std::vector<boost::dll::shared_library> fair::mq::PluginManager::fgDLLKeepAlive =
std::vector<boost::dll::shared_library>();
fair::mq::PluginManager::PluginManager() fair::mq::PluginManager::PluginManager()
: fPluginServices() : fPluginServices()
{ {}
}
fair::mq::PluginManager::PluginManager(const vector<string>& args) fair::mq::PluginManager::PluginManager(const vector<string>& args)
: fPluginServices() : fPluginServices()
@ -46,7 +48,8 @@ fair::mq::PluginManager::PluginManager(const vector<string>& args)
po::store(parsed, vm); po::store(parsed, vm);
po::notify(vm); po::notify(vm);
} catch (const po::error& e) { } catch (const po::error& e) {
throw ProgramOptionsParseError{ToString("Error occured while parsing the 'Plugin Manager' program options: ", e.what())}; throw ProgramOptionsParseError{ToString(
"Error occured while parsing the 'Plugin Manager' program options: ", e.what())};
} }
// Process plugin search paths // Process plugin search paths
@ -55,24 +58,38 @@ fair::mq::PluginManager::PluginManager(const vector<string>& args)
auto searchPaths = vector<fs::path>{}; auto searchPaths = vector<fs::path>{};
if (vm.count("plugin-search-path")) { if (vm.count("plugin-search-path")) {
for (const auto& path : vm["plugin-search-path"].as<vector<string>>()) { for (const auto& path : vm["plugin-search-path"].as<vector<string>>()) {
if (path.substr(0, 1) == "<") { prepend.emplace_back(path.substr(1)); } if (path.substr(0, 1) == "<") {
else if (path.substr(0, 1) == ">") { append.emplace_back(path.substr(1)); } prepend.emplace_back(path.substr(1));
else { searchPaths.emplace_back(path); } } else if (path.substr(0, 1) == ">") {
append.emplace_back(path.substr(1));
} else {
searchPaths.emplace_back(path);
}
} }
} }
// Set supplied options // Set supplied options
SetSearchPaths(searchPaths); SetSearchPaths(searchPaths);
for(const auto& path : prepend) { PrependSearchPath(path); } for (const auto& path : prepend) {
for(const auto& path : append) { AppendSearchPath(path); } PrependSearchPath(path);
if (vm.count("plugin")) { LoadPlugins(vm["plugin"].as<vector<string>>()); } }
for (const auto& path : append) {
AppendSearchPath(path);
}
if (vm.count("plugin")) {
LoadPlugins(vm["plugin"].as<vector<string>>());
}
} }
auto fair::mq::PluginManager::ValidateSearchPath(const fs::path& path) -> void auto fair::mq::PluginManager::ValidateSearchPath(const fs::path& path) -> void
{ {
if (path.empty()) throw BadSearchPath{"Specified path is empty."}; if (path.empty()) {
throw BadSearchPath{"Specified path is empty."};
}
// we ignore non-existing search paths // we ignore non-existing search paths
if (fs::exists(path) && !fs::is_directory(path)) throw BadSearchPath{ToString(path, " is not a directory.")}; if (fs::exists(path) && !fs::is_directory(path)) {
throw BadSearchPath{ToString(path, " is not a directory.")};
}
} }
auto fair::mq::PluginManager::SetSearchPaths(const vector<fs::path>& searchPaths) -> void auto fair::mq::PluginManager::SetSearchPaths(const vector<fs::path>& searchPaths) -> void
@ -96,17 +113,24 @@ auto fair::mq::PluginManager::PrependSearchPath(const fs::path& path) -> void
auto fair::mq::PluginManager::ProgramOptions() -> po::options_description auto fair::mq::PluginManager::ProgramOptions() -> po::options_description
{ {
auto plugin_options = po::options_description{"Plugin Manager"}; auto plugin_options = po::options_description{"Plugin Manager"};
plugin_options.add_options() plugin_options.add_options()("plugin-search-path,S",
("plugin-search-path,S", po::value<vector<string>>()->multitoken(), "List of plugin search paths.\n\n" po::value<vector<string>>()->multitoken(),
"List of plugin search paths.\n\n"
"* Override default search path, e.g.\n" "* Override default search path, e.g.\n"
" -S /home/user/lib /lib\n" " -S /home/user/lib /lib\n"
"* Append(>) or prepend(<) to default search path, e.g.\n" "* Append(>) or prepend(<) to default search path, e.g.\n"
" -S >/lib </home/user/lib\n" " -S >/lib </home/user/lib\n"
"* If you mix the overriding and appending/prepending syntaxes, the overriding paths act as default search path, e.g.\n" "* If you mix the overriding and appending/prepending syntaxes, "
" -S /usr/lib >/lib </home/user/lib /usr/local/lib results in /home/user/lib,/usr/local/lib,/usr/lib/,/lib\n" "the overriding paths act as default search path, e.g.\n"
"If nothing is found, the default dynamic library lookup is performed, see man ld.so(8) for details.") " -S /usr/lib >/lib </home/user/lib /usr/local/lib results in "
("plugin,P", po::value<vector<string>>(), "List of plugin names to load in order," "/home/user/lib,/usr/local/lib,/usr/lib/,/lib\n"
"e.g. if the file is called 'libFairMQPlugin_example.so', just list 'example' or 'd:example' here." "If nothing is found, the default dynamic library lookup is "
"performed, see man ld.so(8) for details.")(
"plugin,P",
po::value<vector<string>>(),
"List of plugin names to load in order,"
"e.g. if the file is called 'libfairmq-plugin-example.so', just list 'example' or "
"'d:example' here."
"To load a prelinked plugin, list 'p:example' here."); "To load a prelinked plugin, list 'p:example' here.");
return plugin_options; return plugin_options;
@ -114,13 +138,13 @@ auto fair::mq::PluginManager::ProgramOptions() -> po::options_description
auto fair::mq::PluginManager::LoadPlugin(const string& pluginName) -> void auto fair::mq::PluginManager::LoadPlugin(const string& pluginName) -> void
{ {
if (pluginName.substr(0,2) == "p:") { if (pluginName.substr(0, 2) == "p:") {
// Mechanism A: prelinked dynamic // Mechanism A: prelinked dynamic
LoadPluginPrelinkedDynamic(pluginName.substr(2)); LoadPluginPrelinkedDynamic(pluginName.substr(2));
} else if (pluginName.substr(0,2) == "d:") { } else if (pluginName.substr(0, 2) == "d:") {
// Mechanism B: dynamic // Mechanism B: dynamic
LoadPluginDynamic(pluginName.substr(2)); LoadPluginDynamic(pluginName.substr(2));
} else if (pluginName.substr(0,2) == "s:") { } else if (pluginName.substr(0, 2) == "s:") {
// Mechanism C: static (builtin) // Mechanism C: static (builtin)
LoadPluginStatic(pluginName.substr(2)); LoadPluginStatic(pluginName.substr(2));
} else { } else {
@ -137,33 +161,54 @@ auto fair::mq::PluginManager::LoadPluginPrelinkedDynamic(const string& pluginNam
LoadSymbols(pluginName, dll::program_location()); LoadSymbols(pluginName, dll::program_location());
fPluginOrder.push_back(pluginName); fPluginOrder.push_back(pluginName);
} catch (boost::system::system_error& e) { } catch (boost::system::system_error& e) {
throw PluginLoadError(ToString("An error occurred while loading prelinked dynamic plugin ", pluginName, ": ", e.what())); throw PluginLoadError(
ToString("An error occurred while loading prelinked dynamic plugin ",
pluginName,
": ",
e.what()));
} }
} }
} }
auto fair::mq::PluginManager::SearchPluginFile(const string& pluginName) const
-> boost::filesystem::path
{
for (const auto& searchPath : SearchPaths()) {
for (const auto& libPrefix : {fgkLibPrefix, fgkLibPrefixAlt}) {
auto const file =
searchPath
/ ToString("lib",
libPrefix,
pluginName,
boost::dll::detail::shared_library_impl::suffix().native());
auto const found = boost::filesystem::exists(file);
if (found) {
return file;
}
}
}
throw PluginNotFound(ToString("Could not find plugin file for plugin ", pluginName));
}
auto fair::mq::PluginManager::LoadPluginDynamic(const string& pluginName) -> void auto fair::mq::PluginManager::LoadPluginDynamic(const string& pluginName) -> void
{ {
// Search plugin and load, if found if (fPluginFactories.find(pluginName) != fPluginFactories.end()) {
if (fPluginFactories.find(pluginName) == fPluginFactories.end()) { return; // already loaded, nothing to do
auto success = false;
for (const auto& searchPath : SearchPaths()) {
try {
LoadSymbols(pluginName, searchPath / ToString(LibPrefix(), pluginName),
dll::load_mode::append_decorations | dll::load_mode::rtld_global);
fPluginOrder.push_back(pluginName);
success = true;
break;
} catch (boost::system::system_error& e) {
if (string{e.what()}.find("No such file or directory") == string::npos) {
throw PluginLoadError(
ToString("An error occurred while loading dynamic plugin ",
pluginName, ": ", e.what()));
}
}
} }
if (!success) { // Search in configured search path
try {
LoadSymbols(pluginName, SearchPluginFile(pluginName), dll::load_mode::rtld_global);
fPluginOrder.push_back(pluginName);
return;
} catch (PluginNotFound& e) {
// ignore
} catch (boost::system::system_error& e) {
// ignore
}
// Search with system logic (including RPATH, LD_LIBRARY_PATH)
for (const auto& libPrefix : {fgkLibPrefix, fgkLibPrefixAlt}) {
try { try {
// LoadSymbols(pluginName, // LoadSymbols(pluginName,
// ToString(LibPrefix(), pluginName), // ToString(LibPrefix(), pluginName),
@ -171,18 +216,21 @@ auto fair::mq::PluginManager::LoadPluginDynamic(const string& pluginName) -> voi
// Not sure, why the above does not work. Workaround for now: // Not sure, why the above does not work. Workaround for now:
LoadSymbols(pluginName, LoadSymbols(pluginName,
ToString("lib", ToString("lib",
LibPrefix(), libPrefix,
pluginName, pluginName,
boost::dll::detail::shared_library_impl::suffix().native()), boost::dll::detail::shared_library_impl::suffix().native()),
dll::load_mode::search_system_folders | dll::load_mode::rtld_global); dll::load_mode::search_system_folders | dll::load_mode::rtld_global);
fPluginOrder.push_back(pluginName); fPluginOrder.push_back(pluginName);
return;
} catch (boost::system::system_error& e) { } catch (boost::system::system_error& e) {
throw PluginLoadError( // ignore
ToString("An error occurred while loading dynamic plugin ",
pluginName, ": ", e.what()));
}
} }
} }
throw PluginLoadError(ToString("Could not load dynamic plugin ",
pluginName,
" from configured search paths (-S) nor with ",
"system lookup (system libdirs, RUNPATH, $LD_LIBRARY_PATH)."));
} }
auto fair::mq::PluginManager::LoadPluginStatic(const string& pluginName) -> void auto fair::mq::PluginManager::LoadPluginStatic(const string& pluginName) -> void
@ -192,20 +240,25 @@ auto fair::mq::PluginManager::LoadPluginStatic(const string& pluginName) -> void
try { try {
if ("control" == pluginName) { if ("control" == pluginName) {
try { try {
fPluginProgOptions.insert({pluginName, plugins::ControlPluginProgramOptions().value()}); fPluginProgOptions.insert(
{pluginName, plugins::ControlPluginProgramOptions().value()});
} catch (const boost::bad_optional_access&) {
/* just ignore, if no prog options are declared */
} }
catch (const boost::bad_optional_access& e) { /* just ignore, if no prog options are declared */ }
} else if ("config" == pluginName) { } else if ("config" == pluginName) {
try { try {
fPluginProgOptions.insert({pluginName, plugins::ConfigPluginProgramOptions().value()}); fPluginProgOptions.insert(
{pluginName, plugins::ConfigPluginProgramOptions().value()});
} catch (const boost::bad_optional_access&) {
/* just ignore, if no prog options are declared */
} }
catch (const boost::bad_optional_access& e) { /* just ignore, if no prog options are declared */ }
} else { } else {
LoadSymbols(pluginName, dll::program_location()); LoadSymbols(pluginName, dll::program_location());
} }
fPluginOrder.push_back(pluginName); fPluginOrder.push_back(pluginName);
} catch (boost::system::system_error& e) { } catch (boost::system::system_error& e) {
throw PluginLoadError(ToString("An error occurred while loading static plugin ", pluginName, ": ", e.what())); throw PluginLoadError(ToString(
"An error occurred while loading static plugin ", pluginName, ": ", e.what()));
} }
} }
} }
@ -225,11 +278,12 @@ auto fair::mq::PluginManager::InstantiatePlugin(const string& pluginName) -> voi
auto fair::mq::PluginManager::InstantiatePlugins() -> void auto fair::mq::PluginManager::InstantiatePlugins() -> void
{ {
for(const auto& pluginName : fPluginOrder) { for (const auto& pluginName : fPluginOrder) {
try { try {
InstantiatePlugin(pluginName); InstantiatePlugin(pluginName);
} catch (std::exception& e) { } catch (std::exception& e) {
throw PluginInstantiationError(ToString("An error occurred while instantiating plugin ", pluginName, ": ", e.what())); throw PluginInstantiationError(ToString(
"An error occurred while instantiating plugin ", pluginName, ": ", e.what()));
} }
} }
} }

View File

@ -66,6 +66,8 @@ class PluginManager
auto SearchPaths() const -> const std::vector<boost::filesystem::path>& { return fSearchPaths; } auto SearchPaths() const -> const std::vector<boost::filesystem::path>& { return fSearchPaths; }
struct BadSearchPath : std::invalid_argument { using std::invalid_argument::invalid_argument; }; struct BadSearchPath : std::invalid_argument { using std::invalid_argument::invalid_argument; };
auto SearchPluginFile(const std::string&) const -> boost::filesystem::path;
struct PluginNotFound : std::runtime_error { using std::runtime_error::runtime_error; };
auto LoadPlugin(const std::string& pluginName) -> void; auto LoadPlugin(const std::string& pluginName) -> void;
auto LoadPlugins(const std::vector<std::string>& pluginNames) -> void { for(const auto& pluginName : pluginNames) { LoadPlugin(pluginName); } } auto LoadPlugins(const std::vector<std::string>& pluginNames) -> void { for(const auto& pluginName : pluginNames) { LoadPlugin(pluginName); } }
struct PluginLoadError : std::runtime_error { using std::runtime_error::runtime_error; }; struct PluginLoadError : std::runtime_error { using std::runtime_error::runtime_error; };
@ -118,6 +120,7 @@ class PluginManager
auto InstantiatePlugin(const std::string& pluginName) -> void; auto InstantiatePlugin(const std::string& pluginName) -> void;
static const std::string fgkLibPrefix; static const std::string fgkLibPrefix;
static const std::string fgkLibPrefixAlt;
std::vector<boost::filesystem::path> fSearchPaths; std::vector<boost::filesystem::path> fSearchPaths;
static std::vector<boost::dll::shared_library> fgDLLKeepAlive; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static std::vector<boost::dll::shared_library> fgDLLKeepAlive; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
std::map<std::string, std::function<PluginFactory>> fPluginFactories; std::map<std::string, std::function<PluginFactory>> fPluginFactories;

View File

@ -151,7 +151,7 @@ set(VERSION_MINOR 2)
set(VERSION_PATCH 0) set(VERSION_PATCH 0)
set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/helper/plugins/dummy2.h.in ${CMAKE_CURRENT_BINARY_DIR}/helper/plugins/dummy2.h) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/helper/plugins/dummy2.h.in ${CMAKE_CURRENT_BINARY_DIR}/helper/plugins/dummy2.h)
add_testlib(FairMQPlugin_test_dummy2 add_testlib(fairmq-plugin-test_dummy2
SOURCES SOURCES
${CMAKE_CURRENT_BINARY_DIR}/helper/plugins/dummy2.h ${CMAKE_CURRENT_BINARY_DIR}/helper/plugins/dummy2.h
helper/plugins/dummy2.cxx helper/plugins/dummy2.cxx
@ -171,7 +171,7 @@ add_testsuite(Plugins
LINKS FairMQ LINKS FairMQ
INCLUDES ${CMAKE_CURRENT_SOURCE_DIR} INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS FairMQPlugin_test_dummy FairMQPlugin_test_dummy2 DEPENDS FairMQPlugin_test_dummy fairmq-plugin-test_dummy2
TIMEOUT 20 TIMEOUT 20
) )
@ -180,7 +180,7 @@ add_testsuite(PluginsPrelinked
${CMAKE_CURRENT_BINARY_DIR}/runner.cxx ${CMAKE_CURRENT_BINARY_DIR}/runner.cxx
plugins/_plugin_manager_prelink.cxx plugins/_plugin_manager_prelink.cxx
LINKS FairMQ FairMQPlugin_test_dummy FairMQPlugin_test_dummy2 LINKS FairMQ FairMQPlugin_test_dummy fairmq-plugin-test_dummy2
INCLUDES ${CMAKE_CURRENT_SOURCE_DIR} INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
TIMEOUT 20 TIMEOUT 20