AutoAPMS
Resilient Robot Mission Management
Loading...
Searching...
No Matches
create_node_manifest.cpp
1// Copyright 2024 Robin Müller
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include <filesystem>
16#include <iostream>
17#include <set>
18
19#include "auto_apms_behavior_tree_core/node/node_manifest.hpp"
20#include "auto_apms_behavior_tree_core/node/node_registration_loader.hpp"
21#include "auto_apms_util/exceptions.hpp"
22#include "auto_apms_util/resource.hpp"
23#include "auto_apms_util/string.hpp"
24
25using namespace auto_apms_behavior_tree;
26
27int main(int argc, char ** argv)
28{
29 if (argc < 5) {
30 std::cerr << "create_node_manifest: Missing inputs! The program requires: \n\t1.) the yaml "
31 "node manifest files (separated by ';').\n\t2.) Build information for nodes supposed to be "
32 "registered during build time (List of '<class_name>@<library_build_path>' "
33 "separated by ';').\n\t3.) The name of the package that provides the build targets.\n\t4.) Output "
34 "file for the complete node plugin manifest.\n\t";
35 std::cerr << "Usage: create_node_manifest <manifest_files> <build_infos> <build_package_name> "
36 "<output_file>\n";
37 return EXIT_FAILURE;
38 }
39
40 try {
41 std::vector<std::string> manifest_files;
42 for (const auto & path : auto_apms_util::splitString(argv[1], ";")) {
43 manifest_files.push_back(std::filesystem::absolute(path).string());
44 }
45 const std::vector<std::string> build_infos = auto_apms_util::splitString(argv[2], ";");
46 const std::string build_package_name = argv[3];
47 const std::filesystem::path output_file = std::filesystem::absolute(auto_apms_util::trimWhitespaces(argv[4]));
48
49 // Ensure that arguments are not empty
50 if (manifest_files.empty()) {
51 throw std::runtime_error("Argument manifest_files must not be empty");
52 }
53 if (output_file.empty()) {
54 throw std::runtime_error("Argument output_file must not be empty.");
55 }
56
57 // Ensure correct extensions
58 if (output_file.extension() != ".yaml") {
59 throw std::runtime_error("Output file '" + output_file.string() + "' has wrong extension. Must be '.yaml'.");
60 }
61
62 // Interpret build info
63 std::map<std::string, std::string> library_paths_build_package;
64 std::map<std::string, std::string> reserved_names;
65 for (const auto & build_info : build_infos) {
66 std::vector<std::string> parts = auto_apms_util::splitString(build_info, "@");
67 if (parts.size() != 2) {
68 throw std::runtime_error("Invalid build info entry ('" + build_info + "').");
69 }
70 const std::string & class_name = parts[0];
71 const std::string & build_path = parts[1];
72 if (library_paths_build_package.find(class_name) != library_paths_build_package.end()) {
73 throw std::runtime_error("Node class '" + class_name + "' is specified multiple times in build infos.");
74 }
75 library_paths_build_package[class_name] = build_path; // {class_name: build_path}
76 reserved_names[class_name] = build_package_name; // Additional names for the ambiguity check
77 }
78
79 // Construct manifest object from input files
80 const core::NodeManifest output_manifest = core::NodeManifest::fromFiles(manifest_files);
81
91
93
94 std::set<std::string> exclude_build_package{build_package_name};
95 std::set<std::string> other_packages_with_node_plugins;
96 try {
97 // Exclude the build package from the list of packages to be searched for resources, because resources from this
98 // package are not installed yet. Instead we hold additional build information in the build_infos variable.
99 other_packages_with_node_plugins = auto_apms_util::getPackagesWithPluginResources(exclude_build_package);
100 } catch (const auto_apms_util::exceptions::ResourceError & e) {
101 // Allow assembling library paths also if no other packages register node resources (Only using resources of the
102 // build package).
103 other_packages_with_node_plugins = {};
104 }
105 std::unique_ptr<Loader> loader_ptr = nullptr;
106 if (!other_packages_with_node_plugins.empty()) {
107 // When creating the node plugin loader, reserve the class names this package is building when checking for
108 // ambiguous declarations. This forces us to use the auto_apms_util::PluginClassLoader constructor.
109 Loader * ptr = new Loader(Loader::makeUnambiguousPluginClassLoader(
111 exclude_build_package, reserved_names));
112 loader_ptr = std::unique_ptr<Loader>(ptr);
113 }
114
115 // Determine shared libraries associated with the node classes required by the manifest files
116 std::set<std::string> library_paths;
117 for (const auto & [node_name, params] : output_manifest.map()) {
118 // Look for class in the build package
119 if (library_paths_build_package.find(params.class_name) != library_paths_build_package.end()) {
120 library_paths.insert(library_paths_build_package[params.class_name]);
121 continue;
122 }
123
124 // Look for class in other already installed packages if it is not provided by the build package
125 if (loader_ptr != nullptr && loader_ptr->isClassAvailable(params.class_name)) {
126 library_paths.insert(loader_ptr->getClassLibraryPath(params.class_name));
127 continue;
128 }
129
130 // There is no shared library associated with the class name we're looking for
131 throw std::runtime_error(
132 "Node '" + node_name + "' (Class '" + params.class_name +
133 "') cannot be found. It's not being built by this package (" + build_package_name +
134 ") and is also not provided by any other package. For a node to be discoverable, "
135 "one must register it using auto_apms_behavior_tree_declare_nodes() in the "
136 "CMakeLists.txt of a ROS 2 package.");
137 }
138
139 // Save the manifest (Merged multiple files into s single one)
140 output_manifest.toFile(output_file);
141
142 // Print the shared library paths associated with the node manifest to stdout (The trailing ';' is automatically
143 // removed when interpreted as a CMake list)
144 for (const auto & path : library_paths) std::cout << path << ';';
145 } catch (const std::exception & e) {
146 std::cerr << "ERROR (create_node_manifest): " << e.what() << "\n";
147 return EXIT_FAILURE;
148 }
149 return EXIT_SUCCESS;
150}
Data structure for information about which behavior tree node plugin to load and how to configure the...
static NodeManifest fromFiles(const std::vector< std::string > &paths)
Create a node plugin manifest from multiple files. They are loaded in the given order.
const Map & map() const
Get a view of the internal map.
void toFile(const std::string &file_path) const
Write the node manifest to a file.
static const std::string BASE_CLASS_NAME
Name of the base class of all plugins to be loaded.
static const std::string BASE_PACKAGE_NAME
Name of the package that contains the base class for this plugin loader.
Class for loading plugin resources registered according to the conventions defined by the pluginlib p...
Definition resource.hpp:97
std::string trimWhitespaces(const std::string &str)
Trim whitespaces from both ends of a string.
Definition string.cpp:84
std::vector< std::string > splitString(const std::string &str, const std::string &delimiter, bool remove_empty=true)
Split a string into multiple tokens using a specific delimiter string (Delimiter may consist of multi...
Definition string.cpp:24
std::set< std::string > getPackagesWithPluginResources(const std::set< std::string > &exclude_packages={})
Get a list of all package names that register AutoAPMS plugin resources.
Definition resource.cpp:52
Useful tooling for incorporating behavior trees for task development.
Definition builder.hpp:30