Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add utility for add library to environment variable using anchor #9

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/boost_plugin_loader/macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@
extern "C" BOOST_SYMBOL_EXPORT DERIVED_CLASS ALIAS; \
BOOST_DLL_SECTION(SECTION, read) BOOST_DLL_SELECTANY DERIVED_CLASS ALIAS;

#endif // BOOST_PLUGIN_LOADER_MACROS_H
#endif // BOOST_PLUGIN_LOADER_MACROS_H
91 changes: 77 additions & 14 deletions include/boost_plugin_loader/plugin_loader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,57 @@ static std::shared_ptr<ClassBase> createSharedInstance(const std::string& symbol
return std::shared_ptr<ClassBase>(plugin.get(), [plugin](ClassBase*) mutable { plugin.reset(); });
}

/**
* @brief This will remove libraries with full path in the provided library_names and return them.
* @param library_names The set to search and remove libraries with full paths
* @return A set of the libraries provided as full path
*/
inline std::set<std::string> extractLibrariesWithFullPath(std::set<std::string>& library_names)
{
std::set<std::string> libraries_with_fullpath;
for (auto it = library_names.begin(); it != library_names.end();)
{
if (boost::filesystem::exists(*it))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also check to make sure the file is an absolute path using boost::filesystem::is_absolute()

{
libraries_with_fullpath.insert(*it);
it = library_names.erase(it);
}
else
{
++it;
}
}

return libraries_with_fullpath;
}

template <class PluginBase>
std::shared_ptr<PluginBase> PluginLoader::createInstance(const std::string& plugin_name) const
{
// Check for environment variable for plugin definitions
std::set<std::string> plugins_local = getAllLibraryNames(search_libraries_env, search_libraries);
if (plugins_local.empty())
std::set<std::string> library_names = getAllLibraryNames(search_libraries_env, search_libraries);
if (library_names.empty())
throw PluginLoaderException("No plugin libraries were provided!");

// Check for libraries provided as full paths. These are searched first
std::set<std::string> libraries_with_fullpath = extractLibrariesWithFullPath(library_names);
for (const auto& library_fullpath : libraries_with_fullpath)
{
try
{
return createSharedInstance<PluginBase>(plugin_name, library_fullpath);
}
catch (...)
{
continue;
}
}

// Check for environment variable for search paths
std::set<std::string> search_paths_local = getAllSearchPaths(search_paths_env, search_paths);
for (const auto& path : search_paths_local)
{
for (const auto& library : search_libraries)
for (const auto& library : library_names)
{
try
{
Expand All @@ -84,7 +122,7 @@ std::shared_ptr<PluginBase> PluginLoader::createInstance(const std::string& plug
// If not found in any of the provided search paths then search system folders if allowed
if (search_system_folders)
{
for (const auto& library : search_libraries)
for (const auto& library : library_names)
{
try
{
Expand All @@ -107,7 +145,7 @@ std::shared_ptr<PluginBase> PluginLoader::createInstance(const std::string& plug
msg << " - " + path << std::endl;

msg << "Search Libraries:" << std::endl;
for (const auto& library : search_libraries)
for (const auto& library : library_names)
msg << " - " + decorate(library) << std::endl;

throw PluginLoaderException(msg.str());
Expand All @@ -116,15 +154,23 @@ std::shared_ptr<PluginBase> PluginLoader::createInstance(const std::string& plug
bool PluginLoader::isPluginAvailable(const std::string& plugin_name) const
{
// Check for environment variable for plugin definitions
std::set<std::string> plugins_local = getAllLibraryNames(search_libraries_env, search_libraries);
if (plugins_local.empty())
std::set<std::string> library_names = getAllLibraryNames(search_libraries_env, search_libraries);
if (library_names.empty())
throw PluginLoaderException("No plugin libraries were provided!");

// Check for libraries provided as full paths. These are searched first
std::set<std::string> libraries_with_fullpath = extractLibrariesWithFullPath(library_names);
for (const auto& library_fullpath : libraries_with_fullpath)
{
if (isSymbolAvailable(plugin_name, library_fullpath))
return true;
}

// Check for environment variable to override default library
std::set<std::string> search_paths_local = getAllSearchPaths(search_paths_env, search_paths);
for (const auto& path : search_paths_local)
{
for (const auto& library : search_libraries)
for (const auto& library : library_names)
{
if (isSymbolAvailable(plugin_name, library, path))
return true;
Expand All @@ -134,7 +180,7 @@ bool PluginLoader::isPluginAvailable(const std::string& plugin_name) const
// If not found in any of the provided search paths then search system folders if allowed
if (search_system_folders)
{
for (const auto& library : search_libraries)
for (const auto& library : library_names)
{
if (isSymbolAvailable(plugin_name, library))
return true;
Expand All @@ -158,8 +204,17 @@ std::vector<std::string> PluginLoader::getAvailablePlugins(const std::string& se
if (library_names.empty())
throw PluginLoaderException("No plugin libraries were provided!");

// Check for environment variable to override default library
std::vector<std::string> plugins;

// Check for libraries provided as full paths. These are searched first
std::set<std::string> libraries_with_fullpath = extractLibrariesWithFullPath(library_names);
for (const auto& library_fullpath : libraries_with_fullpath)
{
std::vector<std::string> lib_plugins = getAllAvailableSymbols(section, library_fullpath);
plugins.insert(plugins.end(), lib_plugins.begin(), lib_plugins.end());
}

// Check for environment variable to override default library
std::set<std::string> search_paths_local = getAllSearchPaths(search_paths_env, search_paths);
if (search_paths_local.empty())
{
Expand All @@ -172,7 +227,7 @@ std::vector<std::string> PluginLoader::getAvailablePlugins(const std::string& se

for (const auto& path : search_paths_local)
{
for (const auto& library : search_libraries)
for (const auto& library : library_names)
{
std::vector<std::string> lib_plugins = getAllAvailableSymbols(section, library, path);
plugins.insert(plugins.end(), lib_plugins.begin(), lib_plugins.end());
Expand All @@ -187,15 +242,23 @@ std::vector<std::string> PluginLoader::getAvailableSections(bool include_hidden)
std::vector<std::string> sections;

// Check for environment variable for plugin definitions
std::set<std::string> plugins_local = getAllLibraryNames(search_libraries_env, search_libraries);
if (plugins_local.empty())
std::set<std::string> library_names = getAllLibraryNames(search_libraries_env, search_libraries);
if (library_names.empty())
throw PluginLoaderException("No plugin libraries were provided!");

// Check for libraries provided as full paths. These are searched first
std::set<std::string> libraries_with_fullpath = extractLibrariesWithFullPath(library_names);
for (const auto& library_fullpath : libraries_with_fullpath)
{
std::vector<std::string> lib_sections = getAllAvailableSections(library_fullpath, "", include_hidden);
sections.insert(sections.end(), lib_sections.begin(), lib_sections.end());
}

// Check for environment variable to override default library
std::set<std::string> search_paths_local = getAllSearchPaths(search_paths_env, search_paths);
for (const auto& path : search_paths_local)
{
for (const auto& library : search_libraries)
for (const auto& library : library_names)
{
std::vector<std::string> lib_sections = getAllAvailableSections(library, path, include_hidden);
sections.insert(sections.end(), lib_sections.begin(), lib_sections.end());
Expand Down
17 changes: 16 additions & 1 deletion include/boost_plugin_loader/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ bool isSymbolAvailable(const std::string& symbol_name, const std::string& librar
std::vector<std::string> getAllAvailableSymbols(const std::string& section, const std::string& library_name,
const std::string& library_directory = "");

/**
* @brief Utility function to add library containing symbol to the search env variable
*
* In some cases the name and location of a library is unknown at runtime, but a symbol can
* be linked at compile time. This is true for Python auditwheel distributions. This
* utility function will determine the location of the library, and add it to the library search
* environment variable so it can be found.
* @note If search_paths_env is empty it will add the library full path to the search_libraries_env.
* @param symbol_ptr Pointer to the symbol to find
* @param search_libraries_env The environmental variable to modify with library name
* @param search_paths_env The environment variable to modify with library location
*/
void addSymbolLibraryToSearchLibrariesEnv(const void* symbol_ptr, const std::string& search_libraries_env,
const std::string& search_paths_env = "");

/**
* @brief Get a list of available sections
* @param library_name The library name to load which does not include the prefix 'lib' or suffix '.so'
Expand Down Expand Up @@ -90,7 +105,7 @@ std::string decorate(const std::string& library_name, const std::string& library

/**
* @brief Extract list form environment variable
* @details The environment variables should be separated by a colon (":")
* @details The environment variables should be separated by a colon (":") on linux and semi-colon (";") on windows
* @param env_variable The environment variable name to extract list from
* @return A list extracted from variable name
*/
Expand Down
64 changes: 63 additions & 1 deletion src/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <boost/dll/alias.hpp>
#include <boost/dll/import_class.hpp>
#include <boost/dll/shared_library.hpp>
#include <boost/dll/runtime_symbol_info.hpp>
#include <boost/algorithm/string.hpp>

#include <boost_plugin_loader/utils.h>
Expand Down Expand Up @@ -82,6 +83,61 @@ std::vector<std::string> getAllAvailableSymbols(const std::string& section, cons
return inf.symbols(section);
}

void addSymbolLibraryToSearchLibrariesEnv(const void* symbol_ptr, const std::string& search_libraries_env,
const std::string& search_paths_env)
{
boost::dll::fs::path lib_path = boost::dll::symbol_location_ptr(symbol_ptr);

std::string separator{ ":" };
#ifdef _WIN32
separator = ";";
#endif

std::string search_libraries_env_var_str;
char* search_libraries_env_var = std::getenv(search_libraries_env.c_str());
if (search_libraries_env_var != nullptr)
search_libraries_env_var_str = search_libraries_env_var;

if (search_paths_env.empty())
{
if (search_libraries_env_var_str.empty())
search_libraries_env_var_str = lib_path.string();
else
search_libraries_env_var_str = lib_path.string() + separator + search_libraries_env_var_str;

#ifndef _WIN32
setenv(search_libraries_env.c_str(), search_libraries_env_var_str.c_str(), 1);
#else
_putenv_s(search_libraries_env.c_str(), search_libraries_env_var_str.c_str());
#endif
}
else
{
std::string search_paths_env_var_str;
char* search_paths_env_var = std::getenv(search_paths_env.c_str());
if (search_paths_env_var != nullptr)
search_paths_env_var_str = search_paths_env_var;

if (search_libraries_env_var_str.empty())
search_libraries_env_var_str = lib_path.filename().string();
else
search_libraries_env_var_str = lib_path.filename().string() + separator + search_libraries_env_var_str;

if (search_paths_env_var_str.empty())
search_paths_env_var_str = lib_path.parent_path().string();
else
search_paths_env_var_str = lib_path.parent_path().string() + separator + search_paths_env_var_str;

#ifndef _WIN32
setenv(search_libraries_env.c_str(), search_libraries_env_var_str.c_str(), 1);
setenv(search_paths_env.c_str(), search_paths_env_var_str.c_str(), 1);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't be updating the search paths, we should use absolute paths for the libraries. Adding the entire path to be searched may have unpredictable results.

#else
_putenv_s(search_libraries_env.c_str(), search_libraries_env_var_str.c_str());
_putenv_s(search_paths_env.c_str(), search_paths_env_var_str.c_str());
#endif
}
}

std::vector<std::string> getAllAvailableSections(const std::string& library_name, const std::string& library_directory,
bool include_hidden)
{
Expand Down Expand Up @@ -122,7 +178,9 @@ std::string decorate(const std::string& library_name, const std::string& library
sl.filename().native()) :
sl);

actual_path += boost::dll::shared_library::suffix();
if (actual_path.extension().empty())
actual_path += boost::dll::shared_library::suffix();

return actual_path.string();
}

Expand All @@ -134,7 +192,11 @@ std::set<std::string> parseEnvironmentVariableList(const std::string& env_variab
return list;

std::string evn_str = std::string(env_var);
#ifndef _WIN32
boost::split(list, evn_str, boost::is_any_of(":"), boost::token_compress_on);
#else
boost::split(list, evn_str, boost::is_any_of(";"), boost::token_compress_on);
#endif
return list;
}

Expand Down
17 changes: 16 additions & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ target_cxx_version(${PROJECT_NAME}_test_plugin_multiply PUBLIC VERSION 17)

add_executable(${PROJECT_NAME}_plugin_loader_unit plugin_loader_unit.cpp)
target_link_libraries(${PROJECT_NAME}_plugin_loader_unit PRIVATE GTest::GTest GTest::Main ${PROJECT_NAME})
target_compile_definitions(${PROJECT_NAME}_test_plugin_multiply PRIVATE ${COMPILE_DEFINITIONS})
target_compile_definitions(${PROJECT_NAME}_plugin_loader_unit PRIVATE ${COMPILE_DEFINITIONS})
target_compile_definitions(${PROJECT_NAME}_plugin_loader_unit PRIVATE PLUGIN_DIR="${CMAKE_CURRENT_BINARY_DIR}"
PLUGINS="${PROJECT_NAME}_test_plugin_multiply")
target_clang_tidy(${PROJECT_NAME}_plugin_loader_unit ENABLE ${ENABLE_CLANG_TIDY})
Expand All @@ -18,6 +18,21 @@ add_gtest_discover_tests(${PROJECT_NAME}_plugin_loader_unit)
add_dependencies(${PROJECT_NAME}_plugin_loader_unit ${PROJECT_NAME})
add_dependencies(run_tests ${PROJECT_NAME}_plugin_loader_unit)

add_executable(${PROJECT_NAME}_plugin_loader_anchor_unit plugin_loader_anchor_unit.cpp)
target_link_libraries(
${PROJECT_NAME}_plugin_loader_anchor_unit
PRIVATE GTest::GTest
GTest::Main
${PROJECT_NAME}
${PROJECT_NAME}_test_plugin_multiply)
target_compile_definitions(${PROJECT_NAME}_plugin_loader_anchor_unit PRIVATE ${COMPILE_DEFINITIONS})
target_clang_tidy(${PROJECT_NAME}_plugin_loader_anchor_unit ENABLE ${ENABLE_CLANG_TIDY})
target_cxx_version(${PROJECT_NAME}_plugin_loader_anchor_unit PUBLIC VERSION 17)
target_code_coverage(${PROJECT_NAME}_plugin_loader_anchor_unit ALL ENABLE ${ENABLE_CODE_COVERAGE})
add_gtest_discover_tests(${PROJECT_NAME}_plugin_loader_anchor_unit)
add_dependencies(${PROJECT_NAME}_plugin_loader_anchor_unit ${PROJECT_NAME} ${PROJECT_NAME}_test_plugin_multiply)
add_dependencies(run_tests ${PROJECT_NAME}_plugin_loader_anchor_unit)

install(
TARGETS ${PROJECT_NAME}_test_plugin_multiply
RUNTIME DESTINATION bin
Expand Down
Loading