From 41689624a24954d19062baeab3a9c9bdbef33e92 Mon Sep 17 00:00:00 2001 From: Alejandro Hernandez Cordero Date: Fri, 17 Jul 2026 11:55:16 +0200 Subject: [PATCH] Move implementation from hpp to cpp Signed-off-by: Alejandro Hernandez Cordero --- pluginlib/CMakeLists.txt | 22 +- pluginlib/include/pluginlib/class_desc.hpp | 14 +- pluginlib/include/pluginlib/class_loader.hpp | 211 +----- .../include/pluginlib/class_loader_base.hpp | 6 +- .../include/pluginlib/class_loader_imp.hpp | 690 +----------------- .../include/pluginlib/class_loader_impl.hpp | 155 ++++ pluginlib/include/pluginlib/exceptions.hpp | 37 +- .../include/pluginlib/visibility_control.hpp | 63 ++ pluginlib/src/class_desc.cpp | 50 ++ pluginlib/src/class_loader_base.cpp | 36 + pluginlib/src/class_loader_impl.cpp | 661 +++++++++++++++++ pluginlib/src/exceptions.cpp | 54 ++ 12 files changed, 1083 insertions(+), 916 deletions(-) create mode 100644 pluginlib/include/pluginlib/class_loader_impl.hpp create mode 100644 pluginlib/include/pluginlib/visibility_control.hpp create mode 100644 pluginlib/src/class_desc.cpp create mode 100644 pluginlib/src/class_loader_base.cpp create mode 100644 pluginlib/src/class_loader_impl.cpp create mode 100644 pluginlib/src/exceptions.cpp diff --git a/pluginlib/CMakeLists.txt b/pluginlib/CMakeLists.txt index 4290196..8c3ef2f 100644 --- a/pluginlib/CMakeLists.txt +++ b/pluginlib/CMakeLists.txt @@ -13,16 +13,27 @@ find_package(rcutils REQUIRED) find_package(rcpputils REQUIRED) find_package(TinyXML2 REQUIRED) -add_library(${PROJECT_NAME} INTERFACE) -target_include_directories(${PROJECT_NAME} INTERFACE +# Everything that does not depend on the plugin base class T is compiled once here, +# rather than re-instantiated in every consumer for every ClassLoader. Only the +# genuinely T-dependent members remain in the headers. +add_library(${PROJECT_NAME} SHARED + src/class_desc.cpp + src/class_loader_base.cpp + src/class_loader_impl.cpp + src/exceptions.cpp) +target_compile_definitions(${PROJECT_NAME} PRIVATE "PLUGINLIB_BUILDING_LIBRARY") +target_include_directories(${PROJECT_NAME} PUBLIC "$" "$") -target_link_libraries(${PROJECT_NAME} INTERFACE +target_link_libraries(${PROJECT_NAME} PUBLIC ament_cmake_ros_core::ament_ros_defaults ament_index_cpp::ament_index_cpp class_loader::class_loader rcutils::rcutils - rcpputils::rcpputils + rcpputils::rcpputils) +# tinyxml2 is only used by the XML parsing in class_loader_impl.cpp; it is no longer +# reachable from any public header, so consumers do not need it. +target_link_libraries(${PROJECT_NAME} PRIVATE tinyxml2::tinyxml2) add_executable(list_plugins src/list_plugins.cpp) @@ -48,6 +59,9 @@ ament_export_targets(export_${PROJECT_NAME}) install( TARGETS ${PROJECT_NAME} EXPORT export_${PROJECT_NAME} + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin ) install( diff --git a/pluginlib/include/pluginlib/class_desc.hpp b/pluginlib/include/pluginlib/class_desc.hpp index 3aee0f2..dac765b 100644 --- a/pluginlib/include/pluginlib/class_desc.hpp +++ b/pluginlib/include/pluginlib/class_desc.hpp @@ -31,11 +31,13 @@ #include +#include "pluginlib/visibility_control.hpp" + namespace pluginlib { /// Storage for information about a given class. -class ClassDesc +class PLUGINLIB_PUBLIC ClassDesc { public: /** @@ -51,15 +53,7 @@ class ClassDesc const std::string & lookup_name, const std::string & derived_class, const std::string & base_class, const std::string & package, const std::string & description, const std::string & library_name, - const std::string & plugin_manifest_path) - : lookup_name_(lookup_name), - derived_class_(derived_class), - base_class_(base_class), - package_(package), - description_(description), - library_name_(library_name), - resolved_library_path_("UNRESOLVED"), - plugin_manifest_path_(plugin_manifest_path) {} + const std::string & plugin_manifest_path); std::string lookup_name_; std::string derived_class_; diff --git a/pluginlib/include/pluginlib/class_loader.hpp b/pluginlib/include/pluginlib/class_loader.hpp index 2a28cb4..08def17 100644 --- a/pluginlib/include/pluginlib/class_loader.hpp +++ b/pluginlib/include/pluginlib/class_loader.hpp @@ -29,17 +29,15 @@ #ifndef PLUGINLIB__CLASS_LOADER_HPP_ #define PLUGINLIB__CLASS_LOADER_HPP_ -#include #include #include +#include #include #include "class_loader/interface_traits.hpp" #include "class_loader/multi_library_class_loader.hpp" -#include "pluginlib/class_desc.hpp" -#include "pluginlib/class_loader_base.hpp" +#include "pluginlib/class_loader_impl.hpp" #include "pluginlib/exceptions.hpp" -#include "tinyxml2.h" // NOLINT namespace pluginlib { @@ -56,12 +54,15 @@ template concept InterfaceConstructible = class_loader::is_interface_constructible_v; /// A class to help manage and load classes. +/** + * Everything that does not depend on the plugin base class T lives in ClassLoaderImpl, + * which is compiled once into libpluginlib. Only the operations below genuinely need T, + * so only they are instantiated per plugin base class. + */ template -class ClassLoader : public ClassLoaderBase +class ClassLoader : public ClassLoaderImpl { public: - using ClassMapIterator = typename std::map::iterator; - /** * \param package The package containing the base class * \param base_class The type of the base class for classes to be loaded @@ -74,9 +75,12 @@ class ClassLoader : public ClassLoaderBase std::string package, std::string base_class, std::string attrib_name = std::string("plugin"), - std::vector plugin_xml_paths = std::vector()); - - ~ClassLoader(); + std::vector plugin_xml_paths = std::vector()) + : ClassLoaderImpl( + std::move(package), std::move(base_class), std::move(attrib_name), + std::move(plugin_xml_paths)) + { + } /// Create an instance of a desired class. /** @@ -138,197 +142,12 @@ class ClassLoader : public ClassLoaderBase requires InterfaceConstructible T * createUnmanagedInstance(const std::string & lookup_name, Args && ... args); - /// Return a list of all available plugin manifest paths for this ClassLoader's base class type. - /** - * \return A vector of strings corresponding to the paths of all available plugin manifests - */ - std::vector getPluginXmlPaths(); - - /// Return a list of all available classes for this ClassLoader's base class type. - /** - * \return A vector of strings corresponding to the names of all available classes - */ - std::vector getDeclaredClasses(); - - /// Strip the package name off of a lookup name. - /** - * \param lookup_name The name of the plugin - * \return The name of the plugin stripped of the package name - */ - virtual std::string getName(const std::string & lookup_name); - - /// Given the lookup name of a class, return the type of the associated base class. - /** - * \return The type of the associated base class - */ - virtual std::string getBaseClassType() const; - - /// Given the lookup name of a class, return the type of the derived class associated with it. - /** - * \param lookup_name The name of the class - * \return The name of the associated derived class - */ - virtual std::string getClassType(const std::string & lookup_name); - - /// Given the lookup name of a class, return its description. - /** - * \param lookup_name The lookup name of the class - * \return The description of the class - */ - virtual std::string getClassDescription(const std::string & lookup_name); - - /// Given the name of a class, return the path to its associated library. - /** - * \param lookup_name The name of the class - * \return The path to the associated library - */ - virtual std::string getClassLibraryPath(const std::string & lookup_name); - - /// Given the name of a class, return name of the containing package. - /** - * \param lookup_name The name of the class - * \return The name of the containing package - */ - virtual std::string getClassPackage(const std::string & lookup_name); - - /// Given the name of a class, return the path of the associated plugin manifest. - /** - * \param lookup_name The name of the class - * \return The path of the associated plugin manifest - */ - virtual std::string getPluginManifestPath(const std::string & lookup_name); - - /// Return the libraries that are registered and can be loaded. - /** - * \return A vector of strings corresponding to the names of registered libraries - */ - virtual std::vector getRegisteredLibraries(); - /// Check if the library for a given class is currently loaded. /** * \param lookup_name The lookup name of the class to query * \return True if the class is loaded, false otherwise */ - bool isClassLoaded(const std::string & lookup_name); - - /// Check if the class associated with a plugin name is available to be loaded. - /** - * \param lookup_name The name of the plugin - * \return true if the plugin is available, false otherwise - */ - virtual bool isClassAvailable(const std::string & lookup_name); - - /// Attempt to load the library containing a class with a given name. - /** - * The counter for the library uses (refcount) is also incremented. - * - * \param lookup_name The lookup name of the class to load - * \throws pluginlib::LibraryLoadException if the library for the - * class cannot be loaded - */ - virtual void loadLibraryForClass(const std::string & lookup_name); - - /// Refresh the list of all available classes for this ClassLoader's base class type. - /** - * \throws pluginlib::LibraryLoadException if package manifest cannot be found - */ - virtual void refreshDeclaredClasses(); - - /// Decrement the counter for the library containing a class with a given name. - /** - * Also try to unload the library, If the counter reaches zero. - * - * \param lookup_name The lookup name of the class to unload - * \throws pluginlib::LibraryUnloadException if the library for the - * class cannot be unloaded - * \return The number of pending unloads until the library is removed from memory - */ - virtual int unloadLibraryForClass(const std::string & lookup_name); - -private: - /// Return the paths to plugin.xml files. - /** - * \throws pluginlib::LibraryLoadException if package manifest cannot be found - * \return A vector of paths - */ - std::vector getPluginXmlPaths( - const std::string & package, - const std::string & attrib_name); - - /// Return the available classes. - /** - * \param plugin_xml_paths The vector of paths of plugin.xml files - * \throws pluginlib::LibraryLoadException if package manifest cannot be found - * \return A map of class names and the corresponding descriptions - */ - std::map determineAvailableClasses( - const std::vector & plugin_xml_paths); - - /// Open a package.xml file and extract the package name (i.e. contents of tag). - /** - * \param package_xml_path The path to the package.xml file - * \return The name of the package if successful, otherwise an empty string - */ - std::string extractPackageNameFromPackageXML(const std::string & package_xml_path); - - /// Get a list of paths to try to find a library. - /** - * As we transition from rosbuild to Catkin build systems, plugins can be - * found in the old rosbuild place (pkg_name/lib usually) or somewhere in the - * Catkin build space. - */ - std::vector getAllLibraryPathsToTry( - const std::string & library_name, - const std::string & exporting_package_name); - - /// Return an error message for an unknown class. - /** - * \param lookup_name The name of the class - * \return The error message - */ - std::string getErrorStringForUnknownClass(const std::string & lookup_name); - - /// Get the standard path separator for the native OS (e.g. "/" on *nix, "\" on Windows). - std::string getPathSeparator(); - - /// Get the package name from a path to a plugin XML file. - std::string getPackageFromPluginXMLFilePath(const std::string & path); - - /// Join two filesystem paths together utilizing appropriate path separator. - std::string joinPaths(const std::string & path1, const std::string & path2); - - /// Parse a plugin XML file. - /** - * Also insert the appropriate ClassDesc entries into the passes - * classes_available map. - */ - void processSingleXMLPluginFile( - const std::string & xml_file, std::map & class_available); - - /// Strip all but the filename from an explicit file path. - /** - * \param path The path to strip - * \return The basename of the path - */ - std::string stripAllButFileFromPath(const std::string & path); - - - /// Helper function for unloading a shared library. - /** - * \param library_path The exact path to the library to unload - * \return The number of pending unloads until the library is removed from memory - */ - int unloadClassLibraryInternal(const std::string & library_path); - -private: - std::vector plugin_xml_paths_; - // Map from library to class's descriptions described in XML. - std::map classes_available_; - std::string package_; - std::string base_class_; - std::string attrib_name_; - class_loader::MultiLibraryClassLoader lowlevel_class_loader_; // The underlying classloader + bool isClassLoaded(const std::string & lookup_name) override; }; } // namespace pluginlib diff --git a/pluginlib/include/pluginlib/class_loader_base.hpp b/pluginlib/include/pluginlib/class_loader_base.hpp index 21e2f14..cc504ac 100644 --- a/pluginlib/include/pluginlib/class_loader_base.hpp +++ b/pluginlib/include/pluginlib/class_loader_base.hpp @@ -32,6 +32,8 @@ #include #include +#include "pluginlib/visibility_control.hpp" + namespace pluginlib { /// Pure virtual base class of pluginlib::ClassLoader which is not templated. @@ -40,11 +42,11 @@ namespace pluginlib * which can call all the administrative functions of ClassLoaders - * everything except createInstance() and createUnmanagedInstance(). */ -class ClassLoaderBase +class PLUGINLIB_PUBLIC ClassLoaderBase { public: /// Empty virtual destructor. - virtual ~ClassLoaderBase() {} + virtual ~ClassLoaderBase(); /// Return a list of all available plugin manifest paths. /** diff --git a/pluginlib/include/pluginlib/class_loader_imp.hpp b/pluginlib/include/pluginlib/class_loader_imp.hpp index 6e80731..2e3f871 100644 --- a/pluginlib/include/pluginlib/class_loader_imp.hpp +++ b/pluginlib/include/pluginlib/class_loader_imp.hpp @@ -29,26 +29,14 @@ #ifndef PLUGINLIB__CLASS_LOADER_IMP_HPP_ #define PLUGINLIB__CLASS_LOADER_IMP_HPP_ -#include -#include -#include -#include -#include +// Only the ClassLoader members that genuinely depend on the plugin base class T are +// defined here; they must stay in a header because T is chosen by the consumer. +// Everything else is compiled once into libpluginlib via ClassLoaderImpl. + #include -#include // NOLINT(build/include_order) cpplint misclassifies as a C header -#include -#include #include -#include #include -#include -#include "ament_index_cpp/get_package_prefix.hpp" -#include "ament_index_cpp/get_resource.hpp" -#include "ament_index_cpp/get_resources.hpp" -#include "class_loader/interface_traits.hpp" -#include "class_loader/class_loader.hpp" -#include "rcpputils/shared_library.hpp" #include "rcutils/logging_macros.h" #include "./class_loader.hpp" @@ -56,58 +44,12 @@ namespace pluginlib { -template -ClassLoader::ClassLoader( - std::string package, - std::string base_class, - std::string attrib_name, - std::vector plugin_xml_paths) -: plugin_xml_paths_(plugin_xml_paths), - package_(package), - base_class_(base_class), - attrib_name_(attrib_name), - // NOTE: The parameter to the class loader enables/disables on-demand class - // loading/unloading. - // Leaving it off for now... libraries will be loaded immediately and won't - // be unloaded until class loader is destroyed or force unload. - lowlevel_class_loader_(false) - /***************************************************************************/ -{ - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", "Creating ClassLoader, base = %s, address = %p", - base_class.c_str(), static_cast(this)); - try { - std::filesystem::path package_path; - ament_index_cpp::get_package_prefix(package_, package_path); - } catch (const ament_index_cpp::PackageNotFoundError & exception) { - // rethrow as class loader exception, package name is in the error message already. - throw pluginlib::ClassLoaderException(exception.what()); - } - - if (0 == plugin_xml_paths_.size()) { - plugin_xml_paths_ = getPluginXmlPaths(package_, attrib_name_); - } - classes_available_ = determineAvailableClasses(plugin_xml_paths_); - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", - "Finished constructing ClassLoader, base = %s, address = %p", - base_class.c_str(), static_cast(this)); -} - -template -ClassLoader::~ClassLoader() -/***************************************************************************/ -{ - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", - "Destroying ClassLoader, base = %s, address = %p", - getBaseClassType().c_str(), static_cast(this)); -} - template template requires InterfaceConstructible std::shared_ptr ClassLoader::createSharedInstance( const std::string & lookup_name, Args &&... args) -/***************************************************************************/ { return createUniqueInstance(lookup_name, std::forward(args)...); } @@ -151,7 +93,6 @@ template template requires InterfaceConstructible T * ClassLoader::createUnmanagedInstance(const std::string & lookup_name, Args &&... args) -/***************************************************************************/ { RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", "Attempting to create UNMANAGED instance for class %s.", @@ -183,635 +124,12 @@ T * ClassLoader::createUnmanagedInstance(const std::string & lookup_name, Arg return instance; } -template -std::vector ClassLoader::getPluginXmlPaths( - const std::string & package, - const std::string & attrib_name) -/***************************************************************************/ -{ - // Pull possible files from manifests of packages which depend on this package and export class - std::vector paths; - { - // the convention is to create an ament resource which a concatenation of - // the package name, "pluginlib", and the attribute being exported - // __ is used as the concatenation delimiter because it cannot be in a - // package name - std::string resource_name = package + "__pluginlib__" + attrib_name; - std::map plugin_packages_with_prefixes = - ament_index_cpp::get_resources_by_name(resource_name); - for (const auto & package_prefix_pair : plugin_packages_with_prefixes) { - // it is also convention to place the relative path to the plugin xml in - // the ament resource file - auto result = ament_index_cpp::get_resource( - resource_name, package_prefix_pair.first); - if (result.resourcePath == std::nullopt) { - RCUTILS_LOG_WARN_NAMED("pluginlib.ClassLoader", - "unexpectedly not able to find ament resource '%s' for package '%s'", - resource_name.c_str(), - package_prefix_pair.first.c_str() - ); - continue; - } - // the content may contain multiple plugin description files - std::stringstream ss(result.contents); - std::string line; - while (std::getline(ss, line, '\n')) { - if (!line.empty()) { - // store the prefix for the package with a plugin and the relative path - // to the plugin xml file - paths.push_back((package_prefix_pair.second / line).string()); - } - } - } - } - return paths; -} - -template -std::map ClassLoader::determineAvailableClasses( - const std::vector & plugin_xml_paths) -/***************************************************************************/ -{ - // mas - This method requires major refactoring... - // not only is it really long and confusing but a lot of the comments do not - // seem to be correct. - // With time I keep correcting small things, but a good rewrite is needed. - - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", "Entering determineAvailableClasses()..."); - std::map classes_available; - - // Walk the list of all plugin XML files (variable "paths") that are exported by the build system - for (const auto & xml_path : plugin_xml_paths) { - try { - processSingleXMLPluginFile(xml_path, classes_available); - } catch (const pluginlib::InvalidXMLException & e) { - RCUTILS_LOG_ERROR_NAMED("pluginlib.ClassLoader", - "Skipped loading plugin with error: %s.", - e.what()); - } - } - - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", "Exiting determineAvailableClasses()..."); - return classes_available; -} - -template -std::string ClassLoader::extractPackageNameFromPackageXML(const std::string & package_xml_path) -/***************************************************************************/ -{ - tinyxml2::XMLDocument document; - document.LoadFile(package_xml_path.c_str()); - tinyxml2::XMLElement * doc_root_node = document.FirstChildElement("package"); - if (nullptr == doc_root_node) { - RCUTILS_LOG_ERROR_NAMED("pluginlib.ClassLoader", - "Could not find a root element for package manifest at %s.", - package_xml_path.c_str()); - return ""; - } - - assert(document.RootElement() == doc_root_node); - - tinyxml2::XMLElement * package_name_node = doc_root_node->FirstChildElement("name"); - if (nullptr == package_name_node) { - RCUTILS_LOG_ERROR_NAMED("pluginlib.ClassLoader", - "package.xml at %s does not have a tag! Cannot determine package " - "which exports plugin.", - package_xml_path.c_str()); - return ""; - } - - const char * package_name_node_txt = package_name_node->GetText(); - if (nullptr == package_name_node_txt) { - RCUTILS_LOG_ERROR_NAMED("pluginlib.ClassLoader", - "package.xml at %s has an invalid tag! Cannot determine package " - "which exports plugin.", - package_xml_path.c_str()); - return ""; - } - - return package_name_node_txt; -} - -template -std::vector ClassLoader::getAllLibraryPathsToTry( - const std::string & library_name, - const std::string & exporting_package_name) -/***************************************************************************/ -{ - // To determine the common prefix of the paths to try, the prefix of the - // exporting package is retrieved. - // To that, various library folder names are added (lib, lib64, etc...) - // Additionally, "libexec" like folders are checked, using the package name - // as the libexec folder name within the library name. - // Finally the library name (just the file name, stripped of extra relative) - // with various extensions is concatenated to the various library directories. - // - // For example, if the package was 'rviz' and the library_name was - // 'librviz_default_plugins', these paths might be tried: - // - // - /lib/librviz_default_plugins.so - // - /lib64/librviz_default_plugins.so - // - /bin/rviz_default_plugins.dll - // - /lib/rviz/librviz_default_plugins.so - // - /lib64/rviz/librviz_default_plugins.so - // - // The extension, e.g. `.so`, might be different based on the operating - // system, e.g. it might be `.dylib` on macOS or `.dll` on Windows. - // Similarly, the library might have the `lib` prefix added or removed. - // Also, the library name might have a `d` added if the library is built - // debug, depending on the system. - - // TODO(wjwwood): probably should avoid "searching" and just embed the - // relative path to the libraries in the ament index, since CMake knows it - // at build time... - - std::vector all_paths; // result of all pairs to search - - std::filesystem::path package_prefix; - ament_index_cpp::get_package_prefix(exporting_package_name, package_prefix); - - // Setup the directories to look in. - std::vector all_search_paths = { - // for now just try lib and lib64 (and their respective "libexec" directories) - package_prefix / "lib", - package_prefix / "lib64", - package_prefix / "bin", // also look in bin, for dll's on Windows - package_prefix / "lib" / exporting_package_name, - package_prefix / "lib64" / exporting_package_name, - package_prefix / "bin" / exporting_package_name, - }; - - std::string stripped_library_name = stripAllButFileFromPath(library_name); - - std::string library_name_alternative; // either lib or without lib prefix - const char * lib_prefix = "lib"; - if (library_name.rfind(lib_prefix, 0) == 0) { - library_name_alternative = library_name.substr(strlen(lib_prefix)); - RCUTILS_LOG_WARN_NAMED("pluginlib.ClassLoader", - "given plugin name '%s' should be '%s' for better portability", - library_name.c_str(), - library_name_alternative.c_str()); - } else { - library_name_alternative = lib_prefix + library_name; - } - std::string stripped_library_name_alternative = stripAllButFileFromPath(library_name_alternative); - - // Setup the relative file paths to pair with the search directories above. - std::vector all_relative_library_paths = { - rcpputils::get_platform_library_name(library_name), - rcpputils::get_platform_library_name(library_name_alternative), - rcpputils::get_platform_library_name(stripped_library_name), - rcpputils::get_platform_library_name(stripped_library_name_alternative) - }; - std::vector all_relative_debug_library_paths = { - rcpputils::get_platform_library_name(library_name, true), - rcpputils::get_platform_library_name(library_name_alternative, true), - rcpputils::get_platform_library_name(stripped_library_name, true), - rcpputils::get_platform_library_name(stripped_library_name_alternative, true) - }; - - for (auto && current_search_path : all_search_paths) { - for (auto && current_library_path : all_relative_library_paths) { - all_paths.push_back((current_search_path / current_library_path).string()); - } - for (auto && current_library_path : all_relative_debug_library_paths) { - all_paths.push_back((current_search_path / current_library_path).string()); - } - } - - for (auto && path : all_paths) { - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", - "[search path for '%s']: '%s'", - library_name.c_str(), - path.c_str()); - } - - return all_paths; -} - template bool ClassLoader::isClassLoaded(const std::string & lookup_name) -/***************************************************************************/ { return lowlevel_class_loader_.isClassAvailable(getClassType(lookup_name)); } -template -std::string ClassLoader::getBaseClassType() const -/***************************************************************************/ -{ - return base_class_; -} - -template -std::string ClassLoader::getClassDescription(const std::string & lookup_name) -/***************************************************************************/ -{ - ClassMapIterator it = classes_available_.find(lookup_name); - if (it != classes_available_.end()) { - return it->second.description_; - } - return ""; -} - -template -std::string ClassLoader::getClassType(const std::string & lookup_name) -/***************************************************************************/ -{ - ClassMapIterator it = classes_available_.find(lookup_name); - if (it != classes_available_.end()) { - return it->second.derived_class_; - } - return ""; -} - -template -std::string ClassLoader::getClassLibraryPath(const std::string & lookup_name) -/***************************************************************************/ -{ - ClassMapIterator it = classes_available_.find(lookup_name); - if (it == classes_available_.end()) { - std::ostringstream error_msg; - error_msg << "Could not find library corresponding to plugin " << lookup_name << - ". Make sure the plugin description XML file has the correct name of the library."; - throw pluginlib::LibraryLoadException(error_msg.str()); - } - std::string library_name = it->second.library_name_; - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", - "Class %s maps to library %s in classes_available_.", - lookup_name.c_str(), library_name.c_str()); - - std::vector paths_to_try = - getAllLibraryPathsToTry(library_name, it->second.package_); - - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", - "Iterating through all possible paths where %s could be located...", - library_name.c_str()); - for (const auto & path : paths_to_try) { - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", "Checking path %s ", path.c_str()); - if (std::filesystem::exists(path)) { - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", "Library %s found at explicit path %s.", - library_name.c_str(), path.c_str()); - return path; - } - } - std::ostringstream error_msg; - error_msg << "Could not find library corresponding to plugin " << lookup_name << - ". Make sure that the library '" << library_name << "' actually exists."; - throw pluginlib::LibraryLoadException(error_msg.str()); -} - -template -std::string ClassLoader::getClassPackage(const std::string & lookup_name) -/***************************************************************************/ -{ - ClassMapIterator it = classes_available_.find(lookup_name); - if (it != classes_available_.end()) { - return it->second.package_; - } - return ""; -} - -template -std::vector ClassLoader::getPluginXmlPaths() -/***************************************************************************/ -{ - return plugin_xml_paths_; -} - -template -std::vector ClassLoader::getDeclaredClasses() -/***************************************************************************/ -{ - auto keys = classes_available_ | std::views::keys; - return {keys.begin(), keys.end()}; -} - -template -std::string ClassLoader::getErrorStringForUnknownClass(const std::string & lookup_name) -/***************************************************************************/ -{ - std::string declared_types; - for (const auto & type : getDeclaredClasses()) { - declared_types += ' ' + type; - } - return "According to the loaded plugin descriptions the class " + lookup_name + - " with base class type " + base_class_ + " does not exist. Declared types are " + - declared_types; -} - -template -std::string ClassLoader::getName(const std::string & lookup_name) -/***************************************************************************/ -{ - // remove the package name to get the raw plugin name - auto last_slash = lookup_name.find_last_of('/'); - auto last_colon = lookup_name.find_last_of(':'); - if (last_slash == std::string::npos && last_colon == std::string::npos) { - // no matches - return lookup_name; - } - if (last_slash == std::string::npos) { - // only colon matched - return lookup_name.substr(last_colon + 1); - } - if (last_colon == std::string::npos) { - // only slash matched - return lookup_name.substr(last_slash + 1); - } - // both matched, return shorter suffix - return lookup_name.substr(std::max(last_slash, last_colon) + 1); -} - -template -std::string -ClassLoader::getPackageFromPluginXMLFilePath(const std::string & plugin_xml_file_path) -/***************************************************************************/ -{ - // Note: This method takes an input a path to a plugin xml file and must determine which - // package the XML file came from. This is not necessarily the same thing as the member - // variable "package_". The plugin xml file can be located anywhere in the source tree for a - // package - - // catkin and ament: - // 1. Find nearest encasing package.xml - // 2. Extract name of package from package.xml - - std::string package_name; - std::filesystem::path p(plugin_xml_file_path); - std::filesystem::path parent = p.parent_path(); - - // Figure out exactly which package the passed XML file is exported by. - while (true) { - if (std::filesystem::exists(parent / "package.xml")) { - std::string package_file_path = (parent / "package.xml").string(); - return extractPackageNameFromPackageXML(package_file_path); - } - - // Recursive case - hop one folder up and store current parent - // parent_path() returns the current path if we reached the root. - p = parent; - parent = parent.parent_path(); - - // Base case - reached root and cannot find what we're looking for - if (parent.string().empty() || (p == parent)) { - return ""; - } - } - - return package_name; -} - -template -std::string ClassLoader::getPathSeparator() -/***************************************************************************/ -{ - return std::string(1, std::filesystem::path::preferred_separator); -} - - -template -std::string ClassLoader::getPluginManifestPath(const std::string & lookup_name) -/***************************************************************************/ -{ - ClassMapIterator it = classes_available_.find(lookup_name); - if (it != classes_available_.end()) { - return it->second.plugin_manifest_path_; - } - return ""; -} - - -template -std::vector ClassLoader::getRegisteredLibraries() -/***************************************************************************/ -{ - return lowlevel_class_loader_.getRegisteredLibraries(); -} - -template -bool ClassLoader::isClassAvailable(const std::string & lookup_name) -/***************************************************************************/ -{ - return classes_available_.contains(lookup_name); -} - -template -std::string ClassLoader::joinPaths(const std::string & path1, const std::string & path2) -/***************************************************************************/ -{ - std::filesystem::path p1(path1); - return (p1 / path2).string(); -} - -template -void ClassLoader::loadLibraryForClass(const std::string & lookup_name) -/***************************************************************************/ -{ - ClassMapIterator it = classes_available_.find(lookup_name); - if (it == classes_available_.end()) { - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", - "Class %s has no mapping in classes_available_.", - lookup_name.c_str()); - throw pluginlib::LibraryLoadException(getErrorStringForUnknownClass(lookup_name)); - } - - std::string library_path = getClassLibraryPath(lookup_name); - - try { - lowlevel_class_loader_.loadLibrary(library_path); - it->second.resolved_library_path_ = library_path; - } catch (const class_loader::LibraryLoadException & ex) { - std::string error_string = - "Failed to load library " + library_path + ". " - "Make sure that you are calling the PLUGINLIB_EXPORT_CLASS macro in the " - "library code, and that names are consistent between this macro and your XML. " - "Error string: " + ex.what(); - throw pluginlib::LibraryLoadException(error_string); - } -} - -template -void ClassLoader::processSingleXMLPluginFile( - const std::string & xml_file, std::map & classes_available) -/***************************************************************************/ -{ - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", "Processing xml file %s...", xml_file.c_str()); - tinyxml2::XMLDocument document; - document.LoadFile(xml_file.c_str()); - tinyxml2::XMLElement * config = document.RootElement(); - if (nullptr == config) { - throw pluginlib::InvalidXMLException( - "XML Document '" + xml_file + - "' has no Root Element. This likely means the XML is malformed or missing."); - return; - } - const char * config_value = config->Value(); - if (nullptr == config_value) { - throw pluginlib::InvalidXMLException( - "XML Document '" + xml_file + - "' has an invalid Root Element. This likely means the XML is malformed or missing."); - return; - } - const std::string_view root_tag{config_value}; - if (root_tag != "library" && root_tag != "class_libraries") { - throw pluginlib::InvalidXMLException( - "The XML document '" + xml_file + "' given to add must have either \"library\" or " - "\"class_libraries\" as the root tag"); - return; - } - // Step into the filter list if necessary - if (root_tag == "class_libraries") { - config = config->FirstChildElement("library"); - } - - tinyxml2::XMLElement * library = config; - while (library != nullptr) { - const char * path = library->Attribute("path"); - if (nullptr == path) { - RCUTILS_LOG_ERROR_NAMED("pluginlib.ClassLoader", - "Attribute 'path' in 'library' tag is missing in %s.", xml_file.c_str()); - continue; - } - std::string library_path(path); - if (0 == library_path.size()) { - RCUTILS_LOG_ERROR_NAMED("pluginlib.ClassLoader", - "Failed to find Path Attribute in library element in %s", xml_file.c_str()); - continue; - } - - std::string package_name = getPackageFromPluginXMLFilePath(xml_file); - if ("" == package_name) { - RCUTILS_LOG_ERROR_NAMED("pluginlib.ClassLoader", - "Could not find package manifest (neither package.xml or deprecated " - "manifest.xml) at same directory level as the plugin XML file %s. " - "Plugins will likely not be exported properly.\n)", - xml_file.c_str()); - } - - tinyxml2::XMLElement * class_element = library->FirstChildElement("class"); - while (class_element) { - std::string derived_class; - if (class_element->Attribute("type") != nullptr) { - derived_class = std::string(class_element->Attribute("type")); - } else { - throw pluginlib::ClassLoaderException( - "Class could not be loaded. Attribute 'type' in class tag is missing."); - } - - std::string base_class_type; - if (class_element->Attribute("base_class_type") != nullptr) { - base_class_type = std::string(class_element->Attribute("base_class_type")); - } else { - throw pluginlib::ClassLoaderException( - "Class could not be loaded. Attribute 'base_class_type' in class tag is missing."); - } - - std::string lookup_name; - if (class_element->Attribute("name") != nullptr) { - lookup_name = class_element->Attribute("name"); - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", - "XML file specifies lookup name (i.e. magic name) = %s.", - lookup_name.c_str()); - } else { - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", - "XML file has no lookup name (i.e. magic name) for class %s, " - "assuming lookup_name == real class name.", - derived_class.c_str()); - lookup_name = derived_class; - } - - // make sure that this class is of the right type before registering it - if (base_class_type == base_class_) { - // register class here - tinyxml2::XMLElement * description = class_element->FirstChildElement("description"); - std::string description_str; - if (description) { - description_str = description->GetText() ? description->GetText() : ""; - } else { - description_str = "No 'description' tag for this plugin in plugin description file."; - } - - // try_emplace keeps the first entry for a duplicate lookup name, matching the - // previous insert(), but builds the ClassDesc only when the key is new. - classes_available.try_emplace(lookup_name, - lookup_name, derived_class, base_class_type, package_name, description_str, - library_path, xml_file); - } - - // step to next class_element - class_element = class_element->NextSiblingElement("class"); - } - library = library->NextSiblingElement("library"); - } -} - -template -void ClassLoader::refreshDeclaredClasses() -/***************************************************************************/ -{ - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", "Refreshing declared classes."); - // determine classes not currently loaded for removal - // The registered library list is fixed for the duration of this scan, so query it once - // rather than rebuilding the vector for every declared class. - const std::vector open_libs = lowlevel_class_loader_.getRegisteredLibraries(); - std::list remove_classes; - for (const auto & [lookup_name, desc] : classes_available_) { - if (std::ranges::find(open_libs, desc.resolved_library_path_) != open_libs.end()) { - remove_classes.push_back(lookup_name); - } - } - - while (!remove_classes.empty()) { - classes_available_.erase(remove_classes.front()); - remove_classes.pop_front(); - } - - // add new classes - plugin_xml_paths_ = getPluginXmlPaths(package_, attrib_name_); - std::map updated_classes = determineAvailableClasses(plugin_xml_paths_); - // Range insert keeps any entry already present, which is what the previous - // contains()-guarded insert did one lookup at a time. - classes_available_.insert(updated_classes.begin(), updated_classes.end()); -} - -template -std::string ClassLoader::stripAllButFileFromPath(const std::string & path) -/***************************************************************************/ -{ - std::string only_file; - size_t c = path.find_last_of(getPathSeparator()); - if (std::string::npos == c) { - return path; - } else { - return path.substr(c, path.size()); - } -} - -template -int ClassLoader::unloadLibraryForClass(const std::string & lookup_name) -/***************************************************************************/ -{ - ClassMapIterator it = classes_available_.find(lookup_name); - if (it != classes_available_.end() && it->second.resolved_library_path_ != "UNRESOLVED") { - std::string library_path = it->second.resolved_library_path_; - RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", - "Attempting to unload library %s for class %s", - library_path.c_str(), lookup_name.c_str()); - return unloadClassLibraryInternal(library_path); - } else { - throw pluginlib::LibraryUnloadException(getErrorStringForUnknownClass(lookup_name)); - } -} - -template -int ClassLoader::unloadClassLibraryInternal(const std::string & library_path) -/***************************************************************************/ -{ - return lowlevel_class_loader_.unloadLibrary(library_path); -} - } // namespace pluginlib #endif // PLUGINLIB__CLASS_LOADER_IMP_HPP_ diff --git a/pluginlib/include/pluginlib/class_loader_impl.hpp b/pluginlib/include/pluginlib/class_loader_impl.hpp new file mode 100644 index 0000000..602c35f --- /dev/null +++ b/pluginlib/include/pluginlib/class_loader_impl.hpp @@ -0,0 +1,155 @@ +// Copyright 2008, Willow Garage, Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the Willow Garage nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef PLUGINLIB__CLASS_LOADER_IMPL_HPP_ +#define PLUGINLIB__CLASS_LOADER_IMPL_HPP_ + +#include +#include +#include + +#include "class_loader/multi_library_class_loader.hpp" +#include "pluginlib/class_desc.hpp" +#include "pluginlib/class_loader_base.hpp" +#include "pluginlib/visibility_control.hpp" + +namespace pluginlib +{ + +/// Non-templated implementation shared by every pluginlib::ClassLoader. +/** + * None of this class's state or behaviour depends on the plugin base class, so it is + * compiled once into libpluginlib rather than re-instantiated for every T. Only the + * handful of genuinely T-dependent operations (creating instances, and querying whether + * a class of type T is loaded) remain in the ClassLoader template. + * + * This class stays abstract: isClassLoaded() is T-dependent and is left for + * ClassLoader to implement. + */ +class PLUGINLIB_PUBLIC ClassLoaderImpl : public ClassLoaderBase +{ +public: + using ClassMapIterator = std::map::iterator; + + /** + * \param package The package containing the base class + * \param base_class The type of the base class for classes to be loaded + * \param attrib_name The attribute to search for in manifest.xml files, defaults to "plugin" + * \param plugin_xml_paths The list of paths of plugin.xml files, defaults to be crawled + * \throws pluginlib::ClassLoaderException if package manifest cannot be found + */ + ClassLoaderImpl( + std::string package, + std::string base_class, + std::string attrib_name = std::string("plugin"), + std::vector plugin_xml_paths = std::vector()); + + ~ClassLoaderImpl() override; + + std::vector getPluginXmlPaths() override; + + std::vector getDeclaredClasses() override; + + std::string getName(const std::string & lookup_name) override; + + std::string getBaseClassType() const override; + + std::string getClassType(const std::string & lookup_name) override; + + std::string getClassDescription(const std::string & lookup_name) override; + + std::string getClassLibraryPath(const std::string & lookup_name) override; + + std::string getClassPackage(const std::string & lookup_name) override; + + std::string getPluginManifestPath(const std::string & lookup_name) override; + + std::vector getRegisteredLibraries() override; + + bool isClassAvailable(const std::string & lookup_name) override; + + void loadLibraryForClass(const std::string & lookup_name) override; + + void refreshDeclaredClasses() override; + + int unloadLibraryForClass(const std::string & lookup_name) override; + +protected: + /// Return an error message for an unknown class. + std::string getErrorStringForUnknownClass(const std::string & lookup_name); + + std::vector plugin_xml_paths_; + // Map from library to class's descriptions described in XML. + std::map classes_available_; + std::string package_; + std::string base_class_; + std::string attrib_name_; + class_loader::MultiLibraryClassLoader lowlevel_class_loader_; // The underlying classloader + +private: + /// Return the paths to plugin.xml files. + std::vector getPluginXmlPaths( + const std::string & package, + const std::string & attrib_name); + + /// Return the available classes. + std::map determineAvailableClasses( + const std::vector & plugin_xml_paths); + + /// Open a package.xml file and extract the package name. + std::string extractPackageNameFromPackageXML(const std::string & package_xml_path); + + /// Get a list of paths to try to find a library. + std::vector getAllLibraryPathsToTry( + const std::string & library_name, + const std::string & exporting_package_name); + + /// Get the standard path separator for the native OS. + std::string getPathSeparator(); + + /// Get the package name from a path to a plugin XML file. + std::string getPackageFromPluginXMLFilePath(const std::string & path); + + /// Join two filesystem paths together utilizing appropriate path separator. + std::string joinPaths(const std::string & path1, const std::string & path2); + + /// Parse a plugin XML file. + void processSingleXMLPluginFile( + const std::string & xml_file, + std::map & class_available); + + /// Strip all but the filename from an explicit file path. + std::string stripAllButFileFromPath(const std::string & path); + + /// Helper function for unloading a shared library. + int unloadClassLibraryInternal(const std::string & library_path); +}; + +} // namespace pluginlib + +#endif // PLUGINLIB__CLASS_LOADER_IMPL_HPP_ diff --git a/pluginlib/include/pluginlib/exceptions.hpp b/pluginlib/include/pluginlib/exceptions.hpp index a1c8e7d..8439e82 100644 --- a/pluginlib/include/pluginlib/exceptions.hpp +++ b/pluginlib/include/pluginlib/exceptions.hpp @@ -32,73 +32,74 @@ #include #include +#include "pluginlib/visibility_control.hpp" + namespace pluginlib { +// Note: each of these types defines its constructor out of line, in exceptions.cpp. +// That gives the class a key function, so its vtable and typeinfo are emitted once in +// libpluginlib instead of weakly in every consumer. Exceptions must be catchable across +// shared library boundaries, which relies on a single exported typeinfo. + /// A base class for all pluginlib exceptions that inherits from std::runtime_exception. /** * \class PluginlibException */ -class PluginlibException : public std::runtime_error +class PLUGINLIB_PUBLIC PluginlibException : public std::runtime_error { public: - explicit PluginlibException(const std::string & error_desc) - : std::runtime_error(error_desc) {} + explicit PluginlibException(const std::string & error_desc); }; /// Thrown when pluginlib is unable to load a plugin XML file. /** * \class InvalidXMLException */ -class InvalidXMLException : public PluginlibException +class PLUGINLIB_PUBLIC InvalidXMLException : public PluginlibException { public: - explicit InvalidXMLException(const std::string & error_desc) - : PluginlibException(error_desc) {} + explicit InvalidXMLException(const std::string & error_desc); }; /// Thrown when pluginlib is unable to load the library associated with a given plugin. /** * \class LibraryLoadException */ -class LibraryLoadException : public PluginlibException +class PLUGINLIB_PUBLIC LibraryLoadException : public PluginlibException { public: - explicit LibraryLoadException(const std::string & error_desc) - : PluginlibException(error_desc) {} + explicit LibraryLoadException(const std::string & error_desc); }; /// Thrown when pluginlib is unable to instantiate a class loader. /** * \class ClassLoaderException */ -class ClassLoaderException : public PluginlibException +class PLUGINLIB_PUBLIC ClassLoaderException : public PluginlibException { public: - explicit ClassLoaderException(const std::string & error_desc) - : PluginlibException(error_desc) {} + explicit ClassLoaderException(const std::string & error_desc); }; /// Thrown when pluginlib is unable to unload the library associated with a given plugin. /** * \class LibraryUnloadException */ -class LibraryUnloadException : public PluginlibException +class PLUGINLIB_PUBLIC LibraryUnloadException : public PluginlibException { public: - explicit LibraryUnloadException(const std::string & error_desc) - : PluginlibException(error_desc) {} + explicit LibraryUnloadException(const std::string & error_desc); }; /// Thrown when pluginlib is unable to create the class associated with a given plugin. /** * \class CreateClassException */ -class CreateClassException : public PluginlibException +class PLUGINLIB_PUBLIC CreateClassException : public PluginlibException { public: - explicit CreateClassException(const std::string & error_desc) - : PluginlibException(error_desc) {} + explicit CreateClassException(const std::string & error_desc); }; } // namespace pluginlib diff --git a/pluginlib/include/pluginlib/visibility_control.hpp b/pluginlib/include/pluginlib/visibility_control.hpp new file mode 100644 index 0000000..004c6cc --- /dev/null +++ b/pluginlib/include/pluginlib/visibility_control.hpp @@ -0,0 +1,63 @@ +// Copyright 2026, Open Source Robotics Foundation, Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef PLUGINLIB__VISIBILITY_CONTROL_HPP_ +#define PLUGINLIB__VISIBILITY_CONTROL_HPP_ + +// This logic was borrowed (then namespaced) from the examples on the gcc wiki: +// https://gcc.gnu.org/wiki/Visibility + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef __GNUC__ + #define PLUGINLIB_EXPORT __attribute__ ((dllexport)) + #define PLUGINLIB_IMPORT __attribute__ ((dllimport)) + #else + #define PLUGINLIB_EXPORT __declspec(dllexport) + #define PLUGINLIB_IMPORT __declspec(dllimport) + #endif + #ifdef PLUGINLIB_BUILDING_LIBRARY + #define PLUGINLIB_PUBLIC PLUGINLIB_EXPORT + #else + #define PLUGINLIB_PUBLIC PLUGINLIB_IMPORT + #endif + #define PLUGINLIB_PUBLIC_TYPE PLUGINLIB_PUBLIC + #define PLUGINLIB_LOCAL +#else + #define PLUGINLIB_EXPORT __attribute__ ((visibility("default"))) + #define PLUGINLIB_IMPORT + #if __GNUC__ >= 4 + #define PLUGINLIB_PUBLIC __attribute__ ((visibility("default"))) + #define PLUGINLIB_LOCAL __attribute__ ((visibility("hidden"))) + #else + #define PLUGINLIB_PUBLIC + #define PLUGINLIB_LOCAL + #endif + #define PLUGINLIB_PUBLIC_TYPE +#endif + +#endif // PLUGINLIB__VISIBILITY_CONTROL_HPP_ diff --git a/pluginlib/src/class_desc.cpp b/pluginlib/src/class_desc.cpp new file mode 100644 index 0000000..838d9f3 --- /dev/null +++ b/pluginlib/src/class_desc.cpp @@ -0,0 +1,50 @@ +// Copyright 2008, Willow Garage, Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the Willow Garage nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include "pluginlib/class_desc.hpp" + +#include + +namespace pluginlib +{ + +ClassDesc::ClassDesc( + const std::string & lookup_name, const std::string & derived_class, + const std::string & base_class, const std::string & package, + const std::string & description, const std::string & library_name, + const std::string & plugin_manifest_path) +: lookup_name_(lookup_name), + derived_class_(derived_class), + base_class_(base_class), + package_(package), + description_(description), + library_name_(library_name), + resolved_library_path_("UNRESOLVED"), + plugin_manifest_path_(plugin_manifest_path) {} + +} // namespace pluginlib diff --git a/pluginlib/src/class_loader_base.cpp b/pluginlib/src/class_loader_base.cpp new file mode 100644 index 0000000..04f7fe4 --- /dev/null +++ b/pluginlib/src/class_loader_base.cpp @@ -0,0 +1,36 @@ +// Copyright 2012, Willow Garage, Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the Willow Garage nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include "pluginlib/class_loader_base.hpp" + +namespace pluginlib +{ + +ClassLoaderBase::~ClassLoaderBase() = default; + +} // namespace pluginlib diff --git a/pluginlib/src/class_loader_impl.cpp b/pluginlib/src/class_loader_impl.cpp new file mode 100644 index 0000000..4b988ff --- /dev/null +++ b/pluginlib/src/class_loader_impl.cpp @@ -0,0 +1,661 @@ +// Copyright 2008, Willow Garage, Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the Willow Garage nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include "pluginlib/class_loader_impl.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include // NOLINT(build/include_order) cpplint misclassifies as a C header +#include +#include +#include +#include +#include + +#include "ament_index_cpp/get_package_prefix.hpp" +#include "ament_index_cpp/get_resource.hpp" +#include "ament_index_cpp/get_resources.hpp" +#include "rcpputils/shared_library.hpp" +#include "rcutils/logging_macros.h" +#include "tinyxml2.h" // NOLINT + +#include "pluginlib/exceptions.hpp" + +namespace pluginlib +{ + +ClassLoaderImpl::ClassLoaderImpl( + std::string package, + std::string base_class, + std::string attrib_name, + std::vector plugin_xml_paths) +: plugin_xml_paths_(std::move(plugin_xml_paths)), + package_(std::move(package)), + base_class_(std::move(base_class)), + attrib_name_(std::move(attrib_name)), + // NOTE: The parameter to the class loader enables/disables on-demand class + // loading/unloading. + // Leaving it off for now... libraries will be loaded immediately and won't + // be unloaded until class loader is destroyed or force unload. + lowlevel_class_loader_(false) +{ + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", "Creating ClassLoader, base = %s, address = %p", + base_class_.c_str(), static_cast(this)); + try { + std::filesystem::path package_path; + ament_index_cpp::get_package_prefix(package_, package_path); + } catch (const ament_index_cpp::PackageNotFoundError & exception) { + // rethrow as class loader exception, package name is in the error message already. + throw pluginlib::ClassLoaderException(exception.what()); + } + + if (0 == plugin_xml_paths_.size()) { + plugin_xml_paths_ = getPluginXmlPaths(package_, attrib_name_); + } + classes_available_ = determineAvailableClasses(plugin_xml_paths_); + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", + "Finished constructing ClassLoader, base = %s, address = %p", + base_class_.c_str(), static_cast(this)); +} + +ClassLoaderImpl::~ClassLoaderImpl() +{ + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", + "Destroying ClassLoader, base = %s, address = %p", + getBaseClassType().c_str(), static_cast(this)); +} + +std::vector ClassLoaderImpl::getPluginXmlPaths( + const std::string & package, + const std::string & attrib_name) +{ + // Pull possible files from manifests of packages which depend on this package and export class + std::vector paths; + { + // the convention is to create an ament resource which a concatenation of + // the package name, "pluginlib", and the attribute being exported + // __ is used as the concatenation delimiter because it cannot be in a + // package name + std::string resource_name = package + "__pluginlib__" + attrib_name; + std::map plugin_packages_with_prefixes = + ament_index_cpp::get_resources_by_name(resource_name); + for (const auto & package_prefix_pair : plugin_packages_with_prefixes) { + // it is also convention to place the relative path to the plugin xml in + // the ament resource file + auto result = ament_index_cpp::get_resource( + resource_name, package_prefix_pair.first); + if (result.resourcePath == std::nullopt) { + RCUTILS_LOG_WARN_NAMED("pluginlib.ClassLoader", + "unexpectedly not able to find ament resource '%s' for package '%s'", + resource_name.c_str(), + package_prefix_pair.first.c_str() + ); + continue; + } + // the content may contain multiple plugin description files + std::stringstream ss(result.contents); + std::string line; + while (std::getline(ss, line, '\n')) { + if (!line.empty()) { + // store the prefix for the package with a plugin and the relative path + // to the plugin xml file + paths.push_back((package_prefix_pair.second / line).string()); + } + } + } + } + return paths; +} + +std::map ClassLoaderImpl::determineAvailableClasses( + const std::vector & plugin_xml_paths) +{ + // mas - This method requires major refactoring... + // not only is it really long and confusing but a lot of the comments do not + // seem to be correct. + // With time I keep correcting small things, but a good rewrite is needed. + + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", "Entering determineAvailableClasses()..."); + std::map classes_available; + + // Walk the list of all plugin XML files (variable "paths") that are exported by the build system + for (const auto & xml_path : plugin_xml_paths) { + try { + processSingleXMLPluginFile(xml_path, classes_available); + } catch (const pluginlib::InvalidXMLException & e) { + RCUTILS_LOG_ERROR_NAMED("pluginlib.ClassLoader", + "Skipped loading plugin with error: %s.", + e.what()); + } + } + + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", "Exiting determineAvailableClasses()..."); + return classes_available; +} + +std::string ClassLoaderImpl::extractPackageNameFromPackageXML(const std::string & package_xml_path) +{ + tinyxml2::XMLDocument document; + document.LoadFile(package_xml_path.c_str()); + tinyxml2::XMLElement * doc_root_node = document.FirstChildElement("package"); + if (nullptr == doc_root_node) { + RCUTILS_LOG_ERROR_NAMED("pluginlib.ClassLoader", + "Could not find a root element for package manifest at %s.", + package_xml_path.c_str()); + return ""; + } + + assert(document.RootElement() == doc_root_node); + + tinyxml2::XMLElement * package_name_node = doc_root_node->FirstChildElement("name"); + if (nullptr == package_name_node) { + RCUTILS_LOG_ERROR_NAMED("pluginlib.ClassLoader", + "package.xml at %s does not have a tag! Cannot determine package " + "which exports plugin.", + package_xml_path.c_str()); + return ""; + } + + const char * package_name_node_txt = package_name_node->GetText(); + if (nullptr == package_name_node_txt) { + RCUTILS_LOG_ERROR_NAMED("pluginlib.ClassLoader", + "package.xml at %s has an invalid tag! Cannot determine package " + "which exports plugin.", + package_xml_path.c_str()); + return ""; + } + + return package_name_node_txt; +} + +std::vector ClassLoaderImpl::getAllLibraryPathsToTry( + const std::string & library_name, + const std::string & exporting_package_name) +{ + // To determine the common prefix of the paths to try, the prefix of the + // exporting package is retrieved. + // To that, various library folder names are added (lib, lib64, etc...) + // Additionally, "libexec" like folders are checked, using the package name + // as the libexec folder name within the library name. + // Finally the library name (just the file name, stripped of extra relative) + // with various extensions is concatenated to the various library directories. + // + // For example, if the package was 'rviz' and the library_name was + // 'librviz_default_plugins', these paths might be tried: + // + // - /lib/librviz_default_plugins.so + // - /lib64/librviz_default_plugins.so + // - /bin/rviz_default_plugins.dll + // - /lib/rviz/librviz_default_plugins.so + // - /lib64/rviz/librviz_default_plugins.so + // + // The extension, e.g. `.so`, might be different based on the operating + // system, e.g. it might be `.dylib` on macOS or `.dll` on Windows. + // Similarly, the library might have the `lib` prefix added or removed. + // Also, the library name might have a `d` added if the library is built + // debug, depending on the system. + + // TODO(wjwwood): probably should avoid "searching" and just embed the + // relative path to the libraries in the ament index, since CMake knows it + // at build time... + + std::vector all_paths; // result of all pairs to search + + std::filesystem::path package_prefix; + ament_index_cpp::get_package_prefix(exporting_package_name, package_prefix); + + // Setup the directories to look in. + std::vector all_search_paths = { + // for now just try lib and lib64 (and their respective "libexec" directories) + package_prefix / "lib", + package_prefix / "lib64", + package_prefix / "bin", // also look in bin, for dll's on Windows + package_prefix / "lib" / exporting_package_name, + package_prefix / "lib64" / exporting_package_name, + package_prefix / "bin" / exporting_package_name, + }; + + std::string stripped_library_name = stripAllButFileFromPath(library_name); + + std::string library_name_alternative; // either lib or without lib prefix + const char * lib_prefix = "lib"; + if (library_name.rfind(lib_prefix, 0) == 0) { + library_name_alternative = library_name.substr(strlen(lib_prefix)); + RCUTILS_LOG_WARN_NAMED("pluginlib.ClassLoader", + "given plugin name '%s' should be '%s' for better portability", + library_name.c_str(), + library_name_alternative.c_str()); + } else { + library_name_alternative = lib_prefix + library_name; + } + std::string stripped_library_name_alternative = stripAllButFileFromPath(library_name_alternative); + + // Setup the relative file paths to pair with the search directories above. + std::vector all_relative_library_paths = { + rcpputils::get_platform_library_name(library_name), + rcpputils::get_platform_library_name(library_name_alternative), + rcpputils::get_platform_library_name(stripped_library_name), + rcpputils::get_platform_library_name(stripped_library_name_alternative) + }; + std::vector all_relative_debug_library_paths = { + rcpputils::get_platform_library_name(library_name, true), + rcpputils::get_platform_library_name(library_name_alternative, true), + rcpputils::get_platform_library_name(stripped_library_name, true), + rcpputils::get_platform_library_name(stripped_library_name_alternative, true) + }; + + for (auto && current_search_path : all_search_paths) { + for (auto && current_library_path : all_relative_library_paths) { + all_paths.push_back((current_search_path / current_library_path).string()); + } + for (auto && current_library_path : all_relative_debug_library_paths) { + all_paths.push_back((current_search_path / current_library_path).string()); + } + } + + for (auto && path : all_paths) { + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", + "[search path for '%s']: '%s'", + library_name.c_str(), + path.c_str()); + } + + return all_paths; +} + +std::string ClassLoaderImpl::getBaseClassType() const +{ + return base_class_; +} + +std::string ClassLoaderImpl::getClassDescription(const std::string & lookup_name) +{ + ClassMapIterator it = classes_available_.find(lookup_name); + if (it != classes_available_.end()) { + return it->second.description_; + } + return ""; +} + +std::string ClassLoaderImpl::getClassType(const std::string & lookup_name) +{ + ClassMapIterator it = classes_available_.find(lookup_name); + if (it != classes_available_.end()) { + return it->second.derived_class_; + } + return ""; +} + +std::string ClassLoaderImpl::getClassLibraryPath(const std::string & lookup_name) +{ + ClassMapIterator it = classes_available_.find(lookup_name); + if (it == classes_available_.end()) { + std::ostringstream error_msg; + error_msg << "Could not find library corresponding to plugin " << lookup_name << + ". Make sure the plugin description XML file has the correct name of the library."; + throw pluginlib::LibraryLoadException(error_msg.str()); + } + std::string library_name = it->second.library_name_; + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", + "Class %s maps to library %s in classes_available_.", + lookup_name.c_str(), library_name.c_str()); + + std::vector paths_to_try = + getAllLibraryPathsToTry(library_name, it->second.package_); + + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", + "Iterating through all possible paths where %s could be located...", + library_name.c_str()); + for (const auto & path : paths_to_try) { + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", "Checking path %s ", path.c_str()); + if (std::filesystem::exists(path)) { + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", "Library %s found at explicit path %s.", + library_name.c_str(), path.c_str()); + return path; + } + } + std::ostringstream error_msg; + error_msg << "Could not find library corresponding to plugin " << lookup_name << + ". Make sure that the library '" << library_name << "' actually exists."; + throw pluginlib::LibraryLoadException(error_msg.str()); +} + +std::string ClassLoaderImpl::getClassPackage(const std::string & lookup_name) +{ + ClassMapIterator it = classes_available_.find(lookup_name); + if (it != classes_available_.end()) { + return it->second.package_; + } + return ""; +} + +std::vector ClassLoaderImpl::getPluginXmlPaths() +{ + return plugin_xml_paths_; +} + +std::vector ClassLoaderImpl::getDeclaredClasses() +{ + auto keys = classes_available_ | std::views::keys; + return {keys.begin(), keys.end()}; +} + +std::string ClassLoaderImpl::getErrorStringForUnknownClass(const std::string & lookup_name) +{ + std::string declared_types; + for (const auto & type : getDeclaredClasses()) { + declared_types += ' ' + type; + } + return "According to the loaded plugin descriptions the class " + lookup_name + + " with base class type " + base_class_ + " does not exist. Declared types are " + + declared_types; +} + +std::string ClassLoaderImpl::getName(const std::string & lookup_name) +{ + // remove the package name to get the raw plugin name + auto last_slash = lookup_name.find_last_of('/'); + auto last_colon = lookup_name.find_last_of(':'); + if (last_slash == std::string::npos && last_colon == std::string::npos) { + // no matches + return lookup_name; + } + if (last_slash == std::string::npos) { + // only colon matched + return lookup_name.substr(last_colon + 1); + } + if (last_colon == std::string::npos) { + // only slash matched + return lookup_name.substr(last_slash + 1); + } + // both matched, return shorter suffix + return lookup_name.substr(std::max(last_slash, last_colon) + 1); +} + +std::string +ClassLoaderImpl::getPackageFromPluginXMLFilePath(const std::string & plugin_xml_file_path) +{ + // Note: This method takes an input a path to a plugin xml file and must determine which + // package the XML file came from. This is not necessarily the same thing as the member + // variable "package_". The plugin xml file can be located anywhere in the source tree for a + // package + + // catkin and ament: + // 1. Find nearest encasing package.xml + // 2. Extract name of package from package.xml + + std::filesystem::path p(plugin_xml_file_path); + std::filesystem::path parent = p.parent_path(); + + // Figure out exactly which package the passed XML file is exported by. + while (true) { + if (std::filesystem::exists(parent / "package.xml")) { + std::string package_file_path = (parent / "package.xml").string(); + return extractPackageNameFromPackageXML(package_file_path); + } + + // Recursive case - hop one folder up and store current parent + // parent_path() returns the current path if we reached the root. + p = parent; + parent = parent.parent_path(); + + // Base case - reached root and cannot find what we're looking for + if (parent.string().empty() || (p == parent)) { + return ""; + } + } +} + +std::string ClassLoaderImpl::getPathSeparator() +{ + return std::string(1, std::filesystem::path::preferred_separator); +} + +std::string ClassLoaderImpl::getPluginManifestPath(const std::string & lookup_name) +{ + ClassMapIterator it = classes_available_.find(lookup_name); + if (it != classes_available_.end()) { + return it->second.plugin_manifest_path_; + } + return ""; +} + +std::vector ClassLoaderImpl::getRegisteredLibraries() +{ + return lowlevel_class_loader_.getRegisteredLibraries(); +} + +bool ClassLoaderImpl::isClassAvailable(const std::string & lookup_name) +{ + return classes_available_.contains(lookup_name); +} + +std::string ClassLoaderImpl::joinPaths(const std::string & path1, const std::string & path2) +{ + std::filesystem::path p1(path1); + return (p1 / path2).string(); +} + +void ClassLoaderImpl::loadLibraryForClass(const std::string & lookup_name) +{ + ClassMapIterator it = classes_available_.find(lookup_name); + if (it == classes_available_.end()) { + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", + "Class %s has no mapping in classes_available_.", + lookup_name.c_str()); + throw pluginlib::LibraryLoadException(getErrorStringForUnknownClass(lookup_name)); + } + + std::string library_path = getClassLibraryPath(lookup_name); + + try { + lowlevel_class_loader_.loadLibrary(library_path); + it->second.resolved_library_path_ = library_path; + } catch (const class_loader::LibraryLoadException & ex) { + std::string error_string = + "Failed to load library " + library_path + ". " + "Make sure that you are calling the PLUGINLIB_EXPORT_CLASS macro in the " + "library code, and that names are consistent between this macro and your XML. " + "Error string: " + ex.what(); + throw pluginlib::LibraryLoadException(error_string); + } +} + +void ClassLoaderImpl::processSingleXMLPluginFile( + const std::string & xml_file, + std::map & classes_available) +{ + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", "Processing xml file %s...", xml_file.c_str()); + tinyxml2::XMLDocument document; + document.LoadFile(xml_file.c_str()); + tinyxml2::XMLElement * config = document.RootElement(); + if (nullptr == config) { + throw pluginlib::InvalidXMLException( + "XML Document '" + xml_file + + "' has no Root Element. This likely means the XML is malformed or missing."); + } + const char * config_value = config->Value(); + if (nullptr == config_value) { + throw pluginlib::InvalidXMLException( + "XML Document '" + xml_file + + "' has an invalid Root Element. This likely means the XML is malformed or missing."); + } + const std::string_view root_tag{config_value}; + if (root_tag != "library" && root_tag != "class_libraries") { + throw pluginlib::InvalidXMLException( + "The XML document '" + xml_file + "' given to add must have either \"library\" or " + "\"class_libraries\" as the root tag"); + } + // Step into the filter list if necessary + if (root_tag == "class_libraries") { + config = config->FirstChildElement("library"); + } + + tinyxml2::XMLElement * library = config; + while (library != nullptr) { + const char * path = library->Attribute("path"); + if (nullptr == path) { + RCUTILS_LOG_ERROR_NAMED("pluginlib.ClassLoader", + "Attribute 'path' in 'library' tag is missing in %s.", xml_file.c_str()); + continue; + } + std::string library_path(path); + if (0 == library_path.size()) { + RCUTILS_LOG_ERROR_NAMED("pluginlib.ClassLoader", + "Failed to find Path Attribute in library element in %s", xml_file.c_str()); + continue; + } + + std::string package_name = getPackageFromPluginXMLFilePath(xml_file); + if ("" == package_name) { + RCUTILS_LOG_ERROR_NAMED("pluginlib.ClassLoader", + "Could not find package manifest (neither package.xml or deprecated " + "manifest.xml) at same directory level as the plugin XML file %s. " + "Plugins will likely not be exported properly.\n)", + xml_file.c_str()); + } + + tinyxml2::XMLElement * class_element = library->FirstChildElement("class"); + while (class_element) { + std::string derived_class; + if (class_element->Attribute("type") != nullptr) { + derived_class = std::string(class_element->Attribute("type")); + } else { + throw pluginlib::ClassLoaderException( + "Class could not be loaded. Attribute 'type' in class tag is missing."); + } + + std::string base_class_type; + if (class_element->Attribute("base_class_type") != nullptr) { + base_class_type = std::string(class_element->Attribute("base_class_type")); + } else { + throw pluginlib::ClassLoaderException( + "Class could not be loaded. Attribute 'base_class_type' in class tag is missing."); + } + + std::string lookup_name; + if (class_element->Attribute("name") != nullptr) { + lookup_name = class_element->Attribute("name"); + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", + "XML file specifies lookup name (i.e. magic name) = %s.", + lookup_name.c_str()); + } else { + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", + "XML file has no lookup name (i.e. magic name) for class %s, " + "assuming lookup_name == real class name.", + derived_class.c_str()); + lookup_name = derived_class; + } + + // make sure that this class is of the right type before registering it + if (base_class_type == base_class_) { + // register class here + tinyxml2::XMLElement * description = class_element->FirstChildElement("description"); + std::string description_str; + if (description) { + description_str = description->GetText() ? description->GetText() : ""; + } else { + description_str = "No 'description' tag for this plugin in plugin description file."; + } + + // try_emplace keeps the first entry for a duplicate lookup name, matching the + // previous insert(), but builds the ClassDesc only when the key is new. + classes_available.try_emplace(lookup_name, + lookup_name, derived_class, base_class_type, package_name, description_str, + library_path, xml_file); + } + + // step to next class_element + class_element = class_element->NextSiblingElement("class"); + } + library = library->NextSiblingElement("library"); + } +} + +void ClassLoaderImpl::refreshDeclaredClasses() +{ + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", "Refreshing declared classes."); + // determine classes not currently loaded for removal + // The registered library list is fixed for the duration of this scan, so query it once + // rather than rebuilding the vector for every declared class. + const std::vector open_libs = lowlevel_class_loader_.getRegisteredLibraries(); + std::list remove_classes; + for (const auto & [lookup_name, desc] : classes_available_) { + if (std::ranges::find(open_libs, desc.resolved_library_path_) != open_libs.end()) { + remove_classes.push_back(lookup_name); + } + } + + while (!remove_classes.empty()) { + classes_available_.erase(remove_classes.front()); + remove_classes.pop_front(); + } + + // add new classes + plugin_xml_paths_ = getPluginXmlPaths(package_, attrib_name_); + std::map updated_classes = determineAvailableClasses(plugin_xml_paths_); + // Range insert keeps any entry already present, which is what the previous + // contains()-guarded insert did one lookup at a time. + classes_available_.insert(updated_classes.begin(), updated_classes.end()); +} + +std::string ClassLoaderImpl::stripAllButFileFromPath(const std::string & path) +{ + size_t c = path.find_last_of(getPathSeparator()); + if (std::string::npos == c) { + return path; + } else { + return path.substr(c, path.size()); + } +} + +int ClassLoaderImpl::unloadLibraryForClass(const std::string & lookup_name) +{ + ClassMapIterator it = classes_available_.find(lookup_name); + if (it != classes_available_.end() && it->second.resolved_library_path_ != "UNRESOLVED") { + std::string library_path = it->second.resolved_library_path_; + RCUTILS_LOG_DEBUG_NAMED("pluginlib.ClassLoader", + "Attempting to unload library %s for class %s", + library_path.c_str(), lookup_name.c_str()); + return unloadClassLibraryInternal(library_path); + } else { + throw pluginlib::LibraryUnloadException(getErrorStringForUnknownClass(lookup_name)); + } +} + +int ClassLoaderImpl::unloadClassLibraryInternal(const std::string & library_path) +{ + return lowlevel_class_loader_.unloadLibrary(library_path); +} + +} // namespace pluginlib diff --git a/pluginlib/src/exceptions.cpp b/pluginlib/src/exceptions.cpp new file mode 100644 index 0000000..535a662 --- /dev/null +++ b/pluginlib/src/exceptions.cpp @@ -0,0 +1,54 @@ +// Copyright 2012, Willow Garage, Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the Willow Garage nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include "pluginlib/exceptions.hpp" + +#include + +namespace pluginlib +{ + +PluginlibException::PluginlibException(const std::string & error_desc) +: std::runtime_error(error_desc) {} + +InvalidXMLException::InvalidXMLException(const std::string & error_desc) +: PluginlibException(error_desc) {} + +LibraryLoadException::LibraryLoadException(const std::string & error_desc) +: PluginlibException(error_desc) {} + +ClassLoaderException::ClassLoaderException(const std::string & error_desc) +: PluginlibException(error_desc) {} + +LibraryUnloadException::LibraryUnloadException(const std::string & error_desc) +: PluginlibException(error_desc) {} + +CreateClassException::CreateClassException(const std::string & error_desc) +: PluginlibException(error_desc) {} + +} // namespace pluginlib