message_definition_cache.cpp
Go to the documentation of this file.
2 
3 #include <filesystem>
4 #include <fstream>
5 #include <optional>
6 #include <regex>
7 #include <set>
8 #include <string>
9 #include <unordered_set>
10 #include <utility>
11 
12 #include <ament_index_cpp/get_package_share_directory.hpp>
13 #include <ament_index_cpp/get_resource.hpp>
14 #include <ament_index_cpp/get_resources.hpp>
15 #include <rcutils/logging_macros.h>
16 
17 namespace foxglove {
18 
19 // Match datatype names (foo_msgs/Bar or foo_msgs/msg/Bar or foo_msgs/srv/Bar)
20 static const std::regex PACKAGE_TYPENAME_REGEX{
21  R"(^([a-zA-Z0-9_]+)/(?:(msg|srv|action)/)?([a-zA-Z0-9_]+)$)"};
22 
23 // Match field types from .msg definitions ("foo_msgs/Bar" in "foo_msgs/Bar[] bar")
24 static const std::regex MSG_FIELD_TYPE_REGEX{R"((?:^|\n)\s*([a-zA-Z0-9_/]+)(?:\[[^\]]*\])?\s+)"};
25 
26 // match field types from `.idl` definitions ("foo_msgs/msg/bar" in #include <foo_msgs/msg/Bar.idl>)
27 static const std::regex IDL_FIELD_TYPE_REGEX{
28  R"((?:^|\n)#include\s+(?:"|<)([a-zA-Z0-9_/]+)\.idl(?:"|>))"};
29 
30 static const std::unordered_set<std::string> PRIMITIVE_TYPES{
31  "bool", "byte", "char", "float32", "float64", "int8", "uint8",
32  "int16", "uint16", "int32", "uint32", "int64", "uint64", "string"};
33 
34 static std::set<std::string> parse_msg_dependencies(const std::string& text,
35  const std::string& package_context) {
36  std::set<std::string> dependencies;
37 
38  for (std::sregex_iterator iter(text.begin(), text.end(), MSG_FIELD_TYPE_REGEX);
39  iter != std::sregex_iterator(); ++iter) {
40  std::string type = (*iter)[1];
41  if (PRIMITIVE_TYPES.find(type) != PRIMITIVE_TYPES.end()) {
42  continue;
43  }
44  if (type.find('/') == std::string::npos) {
45  dependencies.insert(package_context + '/' + std::move(type));
46  } else {
47  dependencies.insert(std::move(type));
48  }
49  }
50  return dependencies;
51 }
52 
53 static std::set<std::string> parse_idl_dependencies(const std::string& text) {
54  std::set<std::string> dependencies;
55 
56  for (std::sregex_iterator iter(text.begin(), text.end(), IDL_FIELD_TYPE_REGEX);
57  iter != std::sregex_iterator(); ++iter) {
58  dependencies.insert((*iter)[1]);
59  }
60  return dependencies;
61 }
62 
63 std::set<std::string> parse_dependencies(MessageDefinitionFormat format, const std::string& text,
64  const std::string& package_context) {
65  switch (format) {
67  return parse_msg_dependencies(text, package_context);
69  return parse_idl_dependencies(text);
70  default:
71  throw std::runtime_error("switch is not exhaustive");
72  }
73 }
74 
76  switch (format) {
78  return ".msg";
80  return ".idl";
81  default:
82  throw std::runtime_error("switch is not exhaustive");
83  }
84 }
85 
86 static std::string delimiter(const DefinitionIdentifier& definition_identifier) {
87  std::string result =
88  "================================================================================\n";
89  switch (definition_identifier.format) {
91  result += "MSG: ";
92  break;
94  result += "IDL: ";
95  break;
96  default:
97  throw std::runtime_error("switch is not exhaustive");
98  }
99  result += definition_identifier.package_resource_name;
100  result += "\n";
101  return result;
102 }
103 
104 static std::vector<std::string> split_string(const std::string& str,
105  const std::string& delimiter = "\n") {
106  std::vector<std::string> strings;
107  std::string::size_type pos = 0;
108  std::string::size_type prev = 0;
109 
110  while ((pos = str.find(delimiter, prev)) != std::string::npos) {
111  strings.push_back(str.substr(prev, pos - prev));
112  prev = pos + delimiter.size();
113  }
114 
115  // Get the last substring (or only, if delimiter is not found)
116  strings.push_back(str.substr(prev));
117 
118  return strings;
119 }
120 
124 static std::tuple<std::string, std::string, std::string> split_action_msg_definition(
125  const std::string& action_definition) {
126  constexpr char SEP[] = "\n---\n";
127 
128  const auto definitions = split_string(action_definition, SEP);
129  if (definitions.size() != 3) {
130  throw std::invalid_argument("Invalid action definition:\n" + action_definition);
131  }
132 
133  return {definitions[0], definitions[1], definitions[2]};
134 }
135 
136 inline bool ends_with(const std::string& str, const std::string& suffix) {
137  return str.size() >= suffix.size() &&
138  0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix);
139 }
140 
141 std::string remove_action_subtype(const std::string action_type) {
142  const auto action_subtype_suffixes = {
143  std::string(ACTION_FEEDBACK_MESSAGE_SUFFIX),
148  };
149 
150  for (const auto& suffix : action_subtype_suffixes) {
151  if (ends_with(action_type, suffix)) {
152  return action_type.substr(0, action_type.length() - suffix.length());
153  }
154  }
155 
156  return action_type;
157 }
158 
160  const std::string& package_context)
161  : dependencies(parse_dependencies(format, text, package_context))
162  , text(std::move(text))
163  , format(format) {}
164 
166  const DefinitionIdentifier& definition_identifier) {
167  if (auto it = msg_specs_by_definition_identifier_.find(definition_identifier);
168  it != msg_specs_by_definition_identifier_.end()) {
169  return it->second;
170  }
171  std::smatch match;
172  if (!std::regex_match(definition_identifier.package_resource_name, match,
174  throw std::invalid_argument("Invalid package resource name: " +
175  definition_identifier.package_resource_name);
176  }
177  const std::string package = match[1].str();
178  const std::string subfolder = match[2].str();
179  const std::string type_name = match[3].str();
180  const bool is_action_type = subfolder == "action";
181 
182  // The action type name includes the subtype which we have to remove to get the action name.
183  // Type name: Fibonacci_FeedbackMessage -> Action name: Fibonacci
184  const std::string action_name = is_action_type ? remove_action_subtype(type_name) : "";
185  const std::string filename = is_action_type
186  ? action_name + ".action"
187  : type_name + extension_for_format(definition_identifier.format);
188 
189  // Get the package share directory, or throw a PackageNotFoundError
190  const std::string share_dir = ament_index_cpp::get_package_share_directory(package);
191 
192  // Get the rosidl_interfaces index contents for this package
193  std::string index_contents;
194  if (!ament_index_cpp::get_resource("rosidl_interfaces", package, index_contents)) {
195  throw DefinitionNotFoundError(definition_identifier.package_resource_name);
196  }
197 
198  // Find the first line that ends with the filename we're looking for
199  const auto lines = split_string(index_contents);
200  const auto it = std::find_if(lines.begin(), lines.end(), [&filename](const std::string& line) {
201  std::filesystem::path filePath(line);
202  return filePath.filename() == filename;
203  });
204  if (it == lines.end()) {
205  throw DefinitionNotFoundError(definition_identifier.package_resource_name);
206  }
207 
208  // Read the file
209  const std::string full_path = share_dir + std::filesystem::path::preferred_separator + *it;
210  std::ifstream file{full_path};
211  if (!file.good()) {
212  throw DefinitionNotFoundError(definition_identifier.package_resource_name);
213  }
214  const std::string contents{std::istreambuf_iterator(file), {}};
215 
216  if (is_action_type) {
217  if (definition_identifier.format == MessageDefinitionFormat::MSG) {
218  const auto [goalDef, resultDef, feedbackDef] = split_action_msg_definition(contents);
219 
220  // Define type definitions for each action subtype.
221  // These type definitions may include additional fields such as the goal_id.
222  // See also https://design.ros2.org/articles/actions.html
223  const std::map<std::string, std::string> action_type_definitions = {
224  {ACTION_FEEDBACK_MESSAGE_SUFFIX, "unique_identifier_msgs/UUID goal_id\n" + feedbackDef},
226  "unique_identifier_msgs/UUID goal_id\n"},
228  "int8 status\n" + resultDef},
230  "unique_identifier_msgs/UUID goal_id\n" + goalDef},
232  "bool accepted\nbuiltin_interfaces/msg/Time stamp"}};
233 
234  // Create a MessageSpec instance for every action subtype and add it to the cache.
235  for (const auto& [action_suffix, definition] : action_type_definitions) {
236  DefinitionIdentifier definition_id;
237  definition_id.format = definition_identifier.format;
238  definition_id.package_resource_name = package + "/action/" + action_name + action_suffix;
239  msg_specs_by_definition_identifier_.emplace(
240  definition_id, MessageSpec(definition_id.format, definition, package));
241  }
242 
243  // Find the the subtype that was originally requested and return it.
244  const auto it = msg_specs_by_definition_identifier_.find(definition_identifier);
245  if (it == msg_specs_by_definition_identifier_.end()) {
246  throw DefinitionNotFoundError(definition_identifier.package_resource_name);
247  }
248  return it->second;
249  } else {
250  RCUTILS_LOG_ERROR_NAMED("foxglove_bridge",
251  "Action IDL definitions are currently not supported");
252  throw DefinitionNotFoundError(definition_identifier.package_resource_name);
253  }
254  } else {
255  // Normal message type.
256  const MessageSpec& spec =
257  msg_specs_by_definition_identifier_
258  .emplace(definition_identifier,
259  MessageSpec(definition_identifier.format, std::move(contents), package))
260  .first->second;
261 
262  // "References and pointers to data stored in the container are only invalidated by erasing that
263  // element, even when the corresponding iterator is invalidated."
264  return spec;
265  }
266 }
267 
268 std::pair<MessageDefinitionFormat, std::string> MessageDefinitionCache::get_full_text(
269  const std::string& root_package_resource_name) {
270  std::unordered_set<DefinitionIdentifier, DefinitionIdentifierHash> seen_deps;
271 
272  std::function<std::string(const DefinitionIdentifier&)> append_recursive =
273  [&](const DefinitionIdentifier& definition_identifier) {
274  const MessageSpec& spec = load_message_spec(definition_identifier);
275  std::string result = spec.text;
276  for (const auto& dep_name : spec.dependencies) {
277  DefinitionIdentifier dep{definition_identifier.format, dep_name};
278  bool inserted = seen_deps.insert(dep).second;
279  if (inserted) {
280  result += "\n";
281  result += delimiter(dep);
282  result += append_recursive(dep);
283  }
284  }
285  return result;
286  };
287 
288  std::string result;
290  try {
291  result = append_recursive(DefinitionIdentifier{format, root_package_resource_name});
292  } catch (const DefinitionNotFoundError& err) {
293  // log that we've fallen back
294  RCUTILS_LOG_WARN_NAMED("foxglove_bridge", "no .msg definition for %s, falling back to IDL",
295  err.what());
297  DefinitionIdentifier root_definition_identifier{format, root_package_resource_name};
298  result = delimiter(root_definition_identifier) + append_recursive(root_definition_identifier);
299  }
300  return std::make_pair(format, result);
301 }
302 
303 } // namespace foxglove
constexpr char ACTION_GOAL_SERVICE_SUFFIX[]
std::set< std::string > parse_dependencies(MessageDefinitionFormat format, const std::string &text, const std::string &package_context)
bool ends_with(const std::string &str, const std::string &suffix)
constexpr char SERVICE_RESPONSE_MESSAGE_SUFFIX[]
string package
constexpr char ACTION_RESULT_SERVICE_SUFFIX[]
MessageDefinitionFormat format
const MessageSpec & load_message_spec(const DefinitionIdentifier &definition_identifier)
static std::tuple< std::string, std::string, std::string > split_action_msg_definition(const std::string &action_definition)
Split an action definition into individual goal, result and feedback definitions. ...
static const char * extension_for_format(MessageDefinitionFormat format)
MessageSpec(MessageDefinitionFormat format, std::string text, const std::string &package_context)
const std::set< std::string > dependencies
static std::string delimiter(const DefinitionIdentifier &definition_identifier)
constexpr char ACTION_FEEDBACK_MESSAGE_SUFFIX[]
const char * what() const noexcept override
constexpr char SERVICE_REQUEST_MESSAGE_SUFFIX[]
static const std::regex PACKAGE_TYPENAME_REGEX
static const std::regex IDL_FIELD_TYPE_REGEX
std::pair< MessageDefinitionFormat, std::string > get_full_text(const std::string &package_resource_name)
static std::set< std::string > parse_msg_dependencies(const std::string &text, const std::string &package_context)
static const std::regex MSG_FIELD_TYPE_REGEX
static std::vector< std::string > split_string(const std::string &str, const std::string &delimiter="\)
static const std::unordered_set< std::string > PRIMITIVE_TYPES
const char * definition()
Definition: format.py:1
static std::set< std::string > parse_idl_dependencies(const std::string &text)
std::string remove_action_subtype(const std::string action_type)


foxglove_bridge
Author(s): Foxglove
autogenerated on Mon Jul 3 2023 02:12:22