AutoAPMS
Streamlining behaviors in ROS 2
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// http://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>@<registration_type>' "
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\t5.) (optional) Previously generated manifest "
35 "entries from the same package for build-time parent resolution "
36 "(List of '<metadata_id>|<absolute_path>' separated by ';').\n\t";
37 std::cerr << "Usage: create_node_manifest <manifest_files> <build_infos> <build_package_name> "
38 "<output_file> [<local_manifest_entries>]\n";
39 return EXIT_FAILURE;
40 }
41
42 try {
43 std::vector<std::string> manifest_files;
44 for (const auto & path : auto_apms_util::splitString(argv[1], ";")) {
45 manifest_files.push_back(std::filesystem::absolute(path).string());
46 }
47 const std::vector<std::string> build_infos = auto_apms_util::splitString(argv[2], ";");
48 const std::string build_package_name = argv[3];
49 const std::filesystem::path output_file = std::filesystem::absolute(auto_apms_util::trimWhitespaces(argv[4]));
50
51 // Ensure that arguments are not empty
52 if (manifest_files.empty()) {
53 throw std::runtime_error("Argument manifest_files must not be empty");
54 }
55 if (output_file.empty()) {
56 throw std::runtime_error("Argument output_file must not be empty.");
57 }
58
59 // Ensure correct extensions
60 if (output_file.extension() != ".yaml") {
61 throw std::runtime_error("Output file '" + output_file.string() + "' has wrong extension. Must be '.yaml'.");
62 }
63
64 // Interpret build info
65 std::map<std::string, std::string> library_paths_build_package;
66 std::map<std::string, std::string> registration_types_build_package;
67 std::map<std::string, std::string> reserved_names;
68 for (const auto & build_info : build_infos) {
69 std::vector<std::string> parts = auto_apms_util::splitString(build_info, "@");
70 if (parts.size() != 3) {
71 throw std::runtime_error(
72 "Invalid build info entry ('" + build_info +
73 "'). Expected format: '<class_name>@<library_path>@<registration_type>'.");
74 }
75 const std::string & class_name = parts[0];
76 const std::string & build_path = parts[1];
77 const std::string & registration_type = parts[2];
78 if (library_paths_build_package.find(class_name) != library_paths_build_package.end()) {
79 throw std::runtime_error("Node class '" + class_name + "' is specified multiple times in build infos.");
80 }
81 library_paths_build_package[class_name] = build_path; // {class_name: build_path}
82 registration_types_build_package[class_name] = registration_type; // {class_name: registration_type}
83 reserved_names[class_name] = build_package_name; // Additional names for the ambiguity check
84 }
85
86 // Pre-register same-package manifests generated in earlier calls so that decode() can resolve
87 // parent references to them without hitting the ament index (which doesn't have them yet).
88 if (argc >= 6) {
89 for (const auto & entry : auto_apms_util::splitString(argv[5], ";")) {
90 const auto parts = auto_apms_util::splitString(entry, "|", false);
91 if (parts.size() != 2) continue;
92 const std::string & metadata_id = parts[0];
93 const std::string & file_path = parts[1];
94 if (!std::filesystem::exists(file_path)) continue;
95 try {
96 const std::string identity_str =
97 build_package_name + _AUTO_APMS_BEHAVIOR_TREE_CORE__RESOURCE_IDENTITY_ALIAS_SEP + metadata_id;
99 core::NodeManifestResourceIdentity(identity_str), core::NodeManifest::fromFile(file_path));
100 } catch (const std::exception &) {
101 // Skip manifests that fail to load — they'll produce an error later if actually needed.
102 }
103 }
104 }
105
106 // Construct manifest object from input files
107 core::NodeManifest output_manifest = core::NodeManifest::fromFiles(manifest_files);
108
118
120
121 std::set<std::string> exclude_build_package{build_package_name};
122 std::set<std::string> other_packages_with_node_plugins;
123 try {
124 // Exclude the build package from the list of packages to be searched for resources, because resources from this
125 // package are not installed yet. Instead we hold additional build information in the build_infos variable.
126 other_packages_with_node_plugins = auto_apms_util::getPackagesWithPluginResources(exclude_build_package);
127 } catch (const auto_apms_util::exceptions::ResourceError & e) {
128 // Allow assembling library paths also if no other packages register node resources (Only using resources of the
129 // build package).
130 other_packages_with_node_plugins = {};
131 }
132 std::unique_ptr<Loader> loader_ptr = nullptr;
133 if (!other_packages_with_node_plugins.empty()) {
134 // When creating the node plugin loader, reserve the class names this package is building when checking for
135 // ambiguous declarations. This forces us to use the auto_apms_util::PluginClassLoader constructor.
136 Loader * ptr = new Loader(
137 Loader::makeUnambiguousPluginClassLoader(
139 exclude_build_package, reserved_names));
140 loader_ptr = std::unique_ptr<Loader>(ptr);
141 }
142
143 // Determine shared libraries associated with the node classes required by the manifest files
144 std::set<std::string> library_paths;
145 std::map<std::string, std::string> registration_types; // class_name -> registration_type
146 for (const auto & [node_name, params] : output_manifest.map()) {
147 // Look for class in the build package
148 if (library_paths_build_package.find(params.class_name) != library_paths_build_package.end()) {
149 library_paths.insert(library_paths_build_package[params.class_name]);
150 registration_types[params.class_name] = registration_types_build_package[params.class_name];
151 continue;
152 }
153
154 // Look for class in other already installed packages if it is not provided by the build package
155 if (loader_ptr != nullptr && loader_ptr->isClassAvailable(params.class_name)) {
156 library_paths.insert(loader_ptr->getClassLibraryPath(params.class_name));
157 registration_types[params.class_name] = loader_ptr->getClassType(params.class_name);
158 continue;
159 }
160
161 // There is no shared library associated with the class name we're looking for
162 throw std::runtime_error(
163 "Class '" + params.class_name + "' (associated with node '" + node_name +
164 "') cannot be found. It's not being built by this package (" + build_package_name +
165 ") and is also not provided by any other package installed on your machine. For a node to be discoverable, "
166 "one must register it using auto_apms_behavior_tree_register_nodes() in the "
167 "CMakeLists.txt of a ROS 2 package.");
168 }
169
170 // Save the manifest (Merged multiple files into a single one)
171 output_manifest.toFile(output_file);
172
173 // Print two lines to stdout:
174 // Line 1: shared library paths (semicolon-separated)
175 // Line 2: registration type mappings (semicolon-separated class_name=registration_type)
176 bool first = true;
177 for (const auto & path : library_paths) {
178 if (!first) std::cout << ';';
179 std::cout << path;
180 first = false;
181 }
182 std::cout << '\n';
183 first = true;
184 for (const auto & [class_name, reg_type] : registration_types) {
185 if (!first) std::cout << ';';
186 std::cout << class_name << '=' << reg_type;
187 first = false;
188 }
189 } catch (const std::exception & e) {
190 std::cerr << "ERROR (create_node_manifest): " << e.what() << "\n";
191 return EXIT_FAILURE;
192 }
193 return EXIT_SUCCESS;
194}
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 void registerLocalManifest(const NodeManifestResourceIdentity &id, const NodeManifest &manifest, bool allow_override=false)
Register a manifest in the process-local lookup table.
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:112
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:41
Powerful tooling for incorporating behavior trees for task development.
Definition behavior.hpp:32
Struct that encapsulates the identity string for a registered behavior tree node manifest.