Skip to content

Commit

Permalink
Passes package incl./excl. to bridge generation
Browse files Browse the repository at this point in the history
* CMake passes package include/ignore lists to python scripts generating
the bridge c++ code
* Generation of factories and mappings restricted to packages filtered
by include/ignore lists

ros2#284

Signed-off-by: norro <[email protected]>
Co-authored-by: James Prestwood <[email protected]>
  • Loading branch information
norro and jprestwo committed Jun 13, 2024
1 parent 3160f17 commit 4ea6aec
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 21 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ add_custom_command(
COMMAND Python3::Interpreter
ARGS bin/ros1_bridge_generate_factories
--output-path "${generated_path}" --template-dir resource
--pkg-includes '${BRIDGE_INCLUDE_PKGS}'
--pkg-ignores '${BRIDGE_IGNORE_PKGS}'
DEPENDS ${target_dependencies}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Generating factories for interface types")
Expand Down
10 changes: 10 additions & 0 deletions bin/ros1_bridge_generate_factories
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,22 @@ def main(argv=sys.argv[1:]):
'--template-dir',
required=True,
help='The location of the template files')
parser.add_argument(
'--pkg-includes',
required=False,
help='ROS packages to include in the bridge')
parser.add_argument(
'--pkg-ignores',
required=False,
help='ROS packages to ignore in the bridge')
args = parser.parse_args(argv)

try:
return generate_cpp(
args.output_path,
args.template_dir,
args.pkg_includes,
args.pkg_ignores,
)
except RuntimeError as e:
print(str(e), file=sys.stderr)
Expand Down
105 changes: 84 additions & 21 deletions ros1_bridge/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,27 +80,41 @@
variable_length_array_pattern = re.compile(r'\[\]$')


def generate_cpp(output_path, template_dir):
def generate_cpp(output_path, template_dir, pkg_includes, pkg_ignores):
# Temporary directory is used to hold generated files, which are only
# copied into place if they differ from previously generated files. This
# ensures that timestamps on files that have not changed are not updated
# and saves a ton of compile time.
with tempfile.TemporaryDirectory() as tempdir:
_generate_cpp(output_path, template_dir, tempdir)
_generate_cpp(output_path, template_dir, tempdir, pkg_includes, pkg_ignores)


def _generate_cpp(output_path, template_dir, temporary_dir):
def _generate_cpp(output_path, template_dir, temporary_dir, pkg_includes, pkg_ignores):
rospack = rospkg.RosPack()
data = generate_messages(rospack)
data = generate_messages(rospack, pkg_includes, pkg_ignores)
message_string_pairs = {
(
'%s/%s' % (m.ros1_msg.package_name, m.ros1_msg.message_name),
'%s/%s' % (m.ros2_msg.package_name, m.ros2_msg.message_name))
for m in data['mappings']}

data.update(
generate_services(rospack, message_string_pairs=message_string_pairs))
data.update(generate_actions(
rospack, message_string_pairs=message_string_pairs))
generate_services(
rospack,
message_string_pairs=message_string_pairs,
includes=pkg_includes,
ignores=pkg_ignores,
)
)

data.update(
generate_actions(
rospack,
message_string_pairs=message_string_pairs,
includes=pkg_includes,
ignores=pkg_ignores
)
)

# create output folder if it doesn't exist
if not os.path.exists(output_path):
Expand Down Expand Up @@ -228,14 +242,16 @@ def update_output_file(temporary_file, output_file):
shutil.copyfile(temporary_file, output_file)


def generate_messages(rospack=None):
def generate_messages(rospack=None, includes=[], ignores=[]):
ros1_msgs = get_ros1_messages(rospack=rospack)
ros2_package_names, ros2_msgs, mapping_rules = get_ros2_messages()
ros2_package_names, ros2_msgs, mapping_rules = get_ros2_messages(includes, ignores)

package_pairs = determine_package_pairs(
ros1_msgs, ros2_msgs, mapping_rules)
ros1_msgs, ros2_msgs, mapping_rules, includes, ignores
)
message_pairs = determine_message_pairs(
ros1_msgs, ros2_msgs, package_pairs, mapping_rules)
ros1_msgs, ros2_msgs, package_pairs, mapping_rules
)

mappings = []
# add custom mapping for builtin_interfaces
Expand Down Expand Up @@ -299,9 +315,9 @@ def generate_messages(rospack=None):
}


def generate_services(rospack=None, message_string_pairs=None):
def generate_services(rospack=None, message_string_pairs=None, includes=[], ignores=[]):
ros1_srvs = get_ros1_services(rospack=rospack)
ros2_pkgs, ros2_srvs, mapping_rules = get_ros2_services()
ros2_pkgs, ros2_srvs, mapping_rules = get_ros2_services(includes, ignores)
services = determine_common_services(
ros1_srvs, ros2_srvs, mapping_rules,
message_string_pairs=message_string_pairs)
Expand All @@ -312,12 +328,20 @@ def generate_services(rospack=None, message_string_pairs=None):
}


def generate_actions(rospack=None, message_string_pairs=None):
def generate_actions(rospack=None, message_string_pairs=None, includes=[], ignores=[]):
ros1_actions = get_ros1_actions(rospack)
ros2_pkgs, ros2_actions, mapping_rules = get_ros2_actions()
ros2_pkgs, ros2_actions, mapping_rules = get_ros2_actions(includes=includes, ignores=ignores)

actions = determine_common_actions(
ros1_actions, ros2_actions, mapping_rules, message_string_pairs=message_string_pairs)
ros1_actions,
ros2_actions,
mapping_rules,
message_string_pairs=message_string_pairs,
includes=includes,
ignores=ignores
)


return {
'actions': actions,
'ros2_package_names_actions': ros2_pkgs,
Expand All @@ -336,7 +360,7 @@ def get_ros1_messages(rospack=None):
return msgs


def get_ros2_messages():
def get_ros2_messages(includes=[], ignores=[]):
pkgs = []
msgs = []
rules = []
Expand All @@ -351,8 +375,16 @@ def get_ros2_messages():
})
for package_name, val_tuple in resources.items():
prefix_path, resource_type = val_tuple

if (includes and not package_name in includes) or (
ignores and package_name in ignores
):
# Ignore package
continue

if resource_type == 'rosidl_interfaces': # Required, otherwise linking fails
pkgs.append(package_name)

resource, _ = ament_index_python.get_resource(resource_type, package_name)
interfaces = resource.splitlines()
message_names = {
Expand Down Expand Up @@ -399,7 +431,7 @@ def get_ros1_services(rospack=None):
return srvs


def get_ros2_services():
def get_ros2_services(includes=[], ignores=[]):
pkgs = []
srvs = []
rules = []
Expand All @@ -414,8 +446,16 @@ def get_ros2_services():
resource_type = 'rosidl_interfaces'
for package_name, val_tuple in resources.items():
prefix_path, resource_type = val_tuple

if (includes and not package_name in includes) or (
ignores and package_name in ignores
):
# Ignore package
continue

if resource_type == 'rosidl_interfaces': # Required, otherwise linking fails
pkgs.append(package_name)

resource, _ = ament_index_python.get_resource(resource_type, package_name)
interfaces = resource.splitlines()
service_names = {
Expand Down Expand Up @@ -475,13 +515,17 @@ def get_ros1_actions(rospack=None):
return actions


def get_ros2_actions():
def get_ros2_actions(includes=[], ignores=[]):
pkgs = []
actions = []
rules = []
resource_type = 'rosidl_interfaces'
resources = ament_index_python.get_resources(resource_type)
for package_name, prefix_path in resources.items():
if (includes and not package_name in includes) or (ignores and package_name in ignores):
# Ignore package
continue

pkgs.append(package_name)
resource, _ = ament_index_python.get_resource(
resource_type, package_name)
Expand Down Expand Up @@ -708,7 +752,7 @@ def __str__(self):
return 'ActionMappingRule(%s, %s)' % (self.ros1_package_name, self.ros2_package_name)


def determine_package_pairs(ros1_msgs, ros2_msgs, mapping_rules):
def determine_package_pairs(ros1_msgs, ros2_msgs, mapping_rules, includes, ignores):
pairs = []
# determine package names considered equal between ROS 1 and ROS 2
ros1_suffix = '_msgs'
Expand All @@ -720,7 +764,19 @@ def determine_package_pairs(ros1_msgs, ros2_msgs, mapping_rules):
continue
ros1_package_basename = ros1_package_name[:-len(ros1_suffix)]

if (includes and not ros1_package_name in includes) or (
ignores and ros1_package_name in ignores
):
# Ignore package
continue

for ros2_package_name in ros2_package_names:
if (includes and not ros2_package_name in includes) or (
ignores and ros2_package_name in ignores
):
# Ignore package
continue

for ros2_suffix in ros2_suffixes:
if ros2_package_name.endswith(ros2_suffix):
break
Expand Down Expand Up @@ -881,7 +937,7 @@ def determine_common_services(


def determine_common_actions(
ros1_actions, ros2_actions, mapping_rules, message_string_pairs=None
ros1_actions, ros2_actions, mapping_rules, message_string_pairs=None, includes=[], ignores=[]
):
if message_string_pairs is None:
message_string_pairs = set()
Expand All @@ -891,12 +947,19 @@ def determine_common_actions(
for ros1_action in ros1_actions:
for ros2_action in ros2_actions:
if ros1_action.package_name == ros2_action.package_name:
if (includes and not ros1_action.package_name in includes) or (ignores and ros1_action.package_name in ignores):
# Ignore package
continue

if ros1_action.message_name == ros2_action.message_name:
pairs.append((ros1_action, ros2_action))

for rule in mapping_rules:
for ros1_action in ros1_actions:
for ros2_action in ros2_actions:
if (includes and not ros1_action.package_name in includes) or (ignores and ros1_action.package_name in ignores):
# Ignore package
continue
if rule.ros1_package_name == ros1_action.package_name and \
rule.ros2_package_name == ros2_action.package_name:
if rule.ros1_action_name is None and rule.ros2_action_name is None:
Expand Down

0 comments on commit 4ea6aec

Please sign in to comment.